Send POST multipart/form-data request using restbed C++ - c++

I am working on a C++ rest client using restbed lib that will send a base64 encoded image using a POST request.
The code I wrote so far is :
auto request = make_shared< Request >(Uri("http://127.0.0.1:8080/ProcessImage"));
request->set_header("Accept", "*/*");
request->set_header("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
request->set_header("Cache-Control", "no-cache");
request->set_method("POST");
string test = "------WebKitFormBoundary7MA4YWxkTrZu0gW"
"Content-Disposition:form-data;name=\"image\""
""
"testMessage"
"------WebKitFormBoundary7MA4YWxkTrZu0gW--";
request->set_body(imgContent);
auto response = Http::sync(request)
I am not sure how I should set the request body. I tried with simple image="blabla" and also with this long version message I took from postman.
But in every case I received a "error 400 Bad request" answer.
Update:
Tested also with this version of code but with no success:
auto request = make_shared< Request >(Uri("http://127.0.0.1:8080/ProcessImage"));
request->set_header("Accept", "*/*");
request->set_header("Host","127.0.0.1:8080");
request->set_method("POST");
request->set_header("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
request->set_header("Cache-Control", "no-cache");
string imgContent = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n"
"Content-Disposition: form-data; name=\"image\"\r\n"
"\r\n"
"test\r\n"
"------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n";
request->set_body(imgContent
auto response = Http::sync(request);
The response I get from the server:
*** Response ***
Status Code: 400
Status Message: BAD REQUEST
HTTP Version: 1.0
HTTP Protocol: HTTP
Header 'Content-Length' > '192'
Header 'Content-Type' > 'text/html'
Header 'Date' > 'Sun, 04 Feb 2018 21:09:45 GMT'
Header 'Server' > 'Werkzeug/0.14.1 Python/3.5.4'
Body:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>
²²²²∩x...
Also on the server side (which is using python flask) I added:
encoded_img = request.form.get('image') and printed the string. The print result was: "None"

Your body content is missing explicit line break characters at the end of each line. C++ does not insert them automatically for you.
Also, if you are going to send base64 data, you should include a Content-Transfer-Encoding header, too.
Try this:
string imgContent = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n"
"Content-Disposition: form-data; name=\"image\"\r\n"
"Content-Transfer-Encoding: base64\r\n"
"\r\n"
"<base64 image data here>\r\n"
"------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n";
request->set_body(imgContent);

Related

Post a multipart/form-data HTTP request with WinHTTP

I have been trying for the past few days to send an HTTP POST request to my SpringBoot application with the Win32 API, but I'm always receiving the same error. The request is a multipart consisting of a binary file and a JSON. Sending the request via Postman works with no problems, and I'm able to receive the file and the JSON correctly.
I have looked at almost every post and question regarding how to construct an HTTP request with WinHTTP, and it seems they all do exactly what I did, but for some reason I'm getting a weirdly constructed request, as seen in the WireShark image below.
It seems as if the request is not recognized as several parts, but rather as one chunk of data.
Looking in WireShark at the correct request which was sent with postman, shows that the request consists of all parts, just as it supposed to.
Here is my code:
LPCWSTR additionalHeaders = L"Accept: application/json\r\nContent-Type: multipart/form-data; boundary=----------------------------346435246262465368257857\r\n";
if (data->type == RequestType::MULTIPART) {
size_t filesize = 0;
char* fileData = fileToString("img.png", "rb", &filesize);
WinHttpAddRequestHeaders(hRequest, additionalHeaders, -1L, WINHTTP_ADDREQ_FLAG_ADD);
char postData1[] =
"----------------------------346435246262465368257857\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"img.png\"\r\n"
"Content-Type: image/png\r\n\r\n";
char postData2[] =
"\r\n----------------------------346435246262465368257857\r\n"
"Content-Disposition: form-data; name=\"newData\"\r\n"
"Content-Type: application/json\r\n\r\n"
"{\"dataType\":\"DEVICE_SETTINGS\"}"
"\r\n----------------------------346435246262465368257857--\r\n";
if (hRequest)
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
lstrlenA(postData1) + lstrlenA(postData2) + filesize, NULL);
DWORD dwBytesWritten = 0;
if (bResults)
bResults = WinHttpWriteData(hRequest, postData1, lstrlenA(postData1), &dwBytesWritten);
if (bResults)
bResults = WinHttpWriteData(hRequest,(LPCVOID) fileData, filesize, &dwBytesWritten);
if (bResults)
bResults = WinHttpWriteData(hRequest, postData2, lstrlenA(postData2), &dwBytesWritten);
}
errorMessageID = ::GetLastError();
// End the request.
if (bResults)
bResults = WinHttpReceiveResponse(hRequest, NULL);
Valid request sent with Postman
Invalid request sent with WinHTTP
Postman configuration 1
Postman configuration 2
SpringBoot controller
Here is the error I receive in my SpringBoot app log:
2022-03-04 14:48:34.520 WARN 25412 --- [nio-8010-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]
I would really appreciate any help here solving this mystery!
The MIME boundaries in your postdata1 and postdata2 strings are incomplete, which is why WireShark and the SpringBoot app are not parsing your data correctly.
Every MIME boundary in the body data must start with a leading --, followed by the value you specified in the Content-Type's boundary attribute, followed by a trailing -- in the final termination boundary.
Let's look at an simpler example that doesn't use any - in the boundary value at all, this should make it clearer to you:
POST /resource HTTP/1.1
Host: ...
Content-Type: multipart/form-data; boundary=myboundary\r\n";
--myboundary
Content-Disposition: form-data; name="file"; filename="img.png"
Content-Type: image/png
<file data>
--myboundary
Content-Disposition: form-data; name="newData"
Content-Type: application/json
<json data>
--myboundary--
As you can see above, and in Postman's data, the leading -- is present on each boundary, but is missing in your data:
Postman's boundary attribute declares a value with 26 leading -s, and each boundary in the body data begins with 28 leading -s.
Your boundary attribute declares a value with 28 leading -s, and each boundary in the body data also begins with 28 leading -s.
Hence, the leading -- is missing from each boundary in your data.
Simply remove 2 -s from the value in your Content-Type's boundary attribute, and then you should be fine.

libcurl HTTP request set content-disposition and content-type in MIME data

I'm trying to upload an image on twitter using libcurl, I used the twurl command line tool to generate an HTTP request and see how it should look like, what I get is this:
POST /1.1/media/upload.json HTTP/1.1
Accept: */
Content-Type: multipart/form-data, boundary="00Twurl342528555775455418lruwT99"
Authorization: OAuth oauth_body_hash="XXX", oauth_consumer_key="XXX", oauth_nonce="XXX", oauth_signature="XXX", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1603308767", oauth_token="XXX", oauth_version="1.0"
Connection: close
Host: upload.twitter.com
Content-Length: 612739
--00Twurl342528555775455418lruwT99
Content-Disposition: form-data; name="media"; filename="image.png"
Content-Type: application/octet-stream
binary data of image.png
--00Twurl342528555775455418lruwT99--
The request that I can generate via libcurl (got it using curl verbose) for the moment is this one:
POST /1.1/media/upload.json HTTP/2
Host: upload.twitter.com
accept: */*
authorization: OAuth oauth_consumer_key="XXX",oauth_nonce="XXX",oauth_signature="XXX",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1603372043",oauth_token="XXX",oauth_version="1.0"
content-length: 268
content-type: multipart/form-data; boundary=------------------------d1b0fc28e693c24a
Using the following code:
curl_mime *mime = nullptr;
curl_mimepart *part = nullptr;
mime = curl_mime_init(request_handle);
part = curl_mime_addpart(mime);
curl_mime_name(part, "media");
curl_mime_filename(part, "image.png");
curl_easy_setopt(request_handle, CURLOPT_MIMEPOST, mime);
The problem is that I don't know how to make my request similar to the first one with libcurl, how do I specify Content-Type and Content-Disposition ?
Edit: solution
Full code
curl_mime* mime = nullptr;
curl_mimepart* part = nullptr;
/* initialize mime part */
mime = curl_mime_init(request_handle);
part = curl_mime_addpart(mime);
/* content-disposition: form-data; name="media"; filename="image.png" */
curl_mime_name(part, "media");
curl_mime_filename(part, "image.png");
/* add file content */
curl_mime_filedata(part, "image.png");
/* content-type: application/octet-stream */
curl_mime_type(part, "application/octet-stream");
/* link the MIME data to your curl handle */
curl_easy_setopt(request_handle, CURLOPT_MIMEPOST, mime);
I didn't do it to highlight the functions to use, but check function return.
how do I specify Content-Type and Content-Disposition ?
Just read the fine manual (which you can navigate to from the fine example postit2.c)
CURLcode curl_mime_type(curl_mimepart * part, const char * mimetype);
curl_mime_type sets a mime part's content type.
CURLcode curl_mime_filename(curl_mimepart * part, const char * filename);
curl_mime_filename sets a mime part's remote file name. When remote file name is set, content data is processed as a file, whatever is the part's content source. A part's remote file name is transmitted to the server in the associated Content-Disposition generated header.
The official libcurl tutorial is also a nice read.

Nginx+uWSGI+Django are returning 502 when big request body and expired session

I have a Django view that process POST request with random size(between 20 char to 30k char). This API is only available for registered users and is validated with a session header. The API works well with my test cases but I notice some 502 in the Nginx log. The error log show this line::
2016/12/26 19:53:15 [error] 1048#0: *72 sendfile() failed (32: Broken pipe) while sending request to upstream, client: XXX.XXX.XXX.XXX, server: , request: "POST /api/v1/purchase HTTP/1.1", upstream: "uwsgi://unix:///opt/project/sockets/uwsgi.sock:", host: "staging.example.com"
After some tests, I managed to recreate this call with a big body request.
curl -XPOST https://staging.example.com/api/v1/purchase \
-H "Accept: application/json" \
-H "token: development-token" \
-H "session: bad-session" \
-i -d '{"receipt-data": "<25677 character string>"}'
HTTP/1.1 100 Continue
HTTP/1.1 502 Bad Gateway
Server: nginx/1.4.6 (Ubuntu)
Date: Mon, 26 Dec 2016 19:54:32 GMT
Content-Type: text/html
Content-Length: 181
Connection: keep-alive
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.4.6 (Ubuntu)</center>
</body>
</html>
What it seems to happen is that the Django checks that the session is not valid and return the response(403) before the client finish delivers the body.
If I'm correct, is there a way to make Django send that 100 status after checking the headers instead of the Nginx?
If not, is there a more elegant solution than wait for the body before checking the headers?
I've found a statement that adding HTTP header connection:keep-alive to the client should fix this issue. I'll verify it later, but already posting it here, hope it will help someone.

CPPREST redirect location for response code 302

I am using CPPREST http_client to get a RSS feed from :
http://www.20min.ch/rss/rss.tmpl?type=channel&get=68
but I am receiving redirect response code : 302
When i check the body of the response it is :
Received response status code:302
response is [HTTP/1.1 302 Found
Age: 0
Connection: keep-alive
Content-Type: text/html
Date: Mon, 14 Mar 2016 06:30:48 GMT
Keep-Alive: timeout=30, max=100
Location: http://www.20min.ch/redirect?url=www.20min.ch:80
Server: Kaesebrot 1.23-rc1
....
Redirecting to http://www.20min.ch/redirect?url=www.20min.ch:80">http://www.20min.ch/redirect?url=
Now when I retry with URL received in location field of response i.e. http://www.20min.ch/redirect?url=www.20min.ch:80
I still get the same response of 302.
M I using the wrong redirect URL?
Also for finding the redirect url in CPPREST I could not find any direct method , I had to find the search the response body and find the substr.
I have also retried with :
http://www.20min.ch:80/rss/rss.tmpl?type=channel&get=68
but same 302 respone.
Kindly advice.
Location: http://www.20min.ch/redirect?url=www.20min.ch:80
looks like a cyclic redirect to me.

Django/App-Engine: Getting HTML error response when doing HTTP POST request (HTTP GET works)

If I do a nc 192.168.2.10 8080 and then GET /test/ I get as expected a JSON response:
Content-Type: text/javascript
Cache-Control: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Content-Length: 74
{ ... a JSON message ...}
However, if I do a POST /test/ I get the following HTML doc as a result:
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 400.
<p>Message: Bad HTTP/0.9 request type ('POST').
<p>Error code explanation: 400 = Bad request syntax or unsupported method.
</body>
Anyone an idea where the problem could be?
As Nick Johnson said in his comment, try a tool that forms requests properly for you.
Another common source of these sorts of errors is trying to parse a GET request on the server (for arguments or whatever) while you're getting a post request.
Also something that always gets me, but that's a 403, is csrf protection. Remember to turn it off for requests you want to make via curl and similar :)