1
votes

I want to replicate this part of Python code in Delphi using Indy:

postdata = {'data': '{"data":{"xMode":0,"overrideOS":1,"messageId":"","vmProfileList":"11","submitType":"0","url":""},"filePriorityQ":"run_now" }'}
file_up = {'amas_filename':open('/home/samples/temp/vtest32.exe','r')}
file_upload_req=requests.post(url,postdata,files=file_up,headers=headers,verify=False)

I tried it like this:

Params.AddFormField('data', '{"data":{"xMode": '+ xMode +',"analyzeAgain":1,"overrideOS":1,' +
                            '"vmProfileList":"' + DBProfileID.Value + '","submitType":0,"url":""}}');
Params.AddFile('amas_filename', DBTestFilePath.Value, GetMIMEType(DBTestFilePath.Value));
Params.Position := 0;
HTTP1.Request.ContentType := 'application/x-www-form-urlencoded';
JSON := HTTP1.Post(URL, Params);

but it gives me a HTTP error "HTTP/1.0 400 Bad Request" and webserver says "Bad Request. Check input data and payload size". I know the data size is small enough.

This is the request from client and response from the server:

Client Side Says

POST /php/fileupload.php HTTP/1.0
Content-Type: multipart/form-data; boundary=--------031317093926335
Content-Length: 248815
VE-SDK-API: <<APIKEYWasHere>>
Host: Server_IP
Accept: application/vnd.ve.v1.0+json
Accept-Encoding: identity
User-Agent: Mozilla/3.0 - NBL
Cookie: PHPSESSID=<<Cookie_Was_Here>>

----------031317093926335
Content-Disposition: form-data; name="data"
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

{"data":{"xMode": 0,"analyzeAgain":1,"overrideOS":1,"vmProfileList":"2=
4","submitType":0,"url":""},"filePriorityQ":"run_now"}
----------031317093926335
Content-Disposition: form-data; name="amas_filename"; filename="Process.exe"
Content-Type: application/x-msdownload
Content-Transfer-Encoding: binary

MZP
<<FileDataWasHere>>

Server Service Says

HTTP/1.0 400 Bad Request
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
Cache-Control: no-store, no-cache, must-revalidate, private,max-age=0
Cache-Control: no-store, no-cache, must-revalidate, private,max-age=0
Pragma: no-cache
Pragma: no-cache
Expires: Sat, 26 Jul 1997 05:00:00 GMT
Content-type: text/html; charset=UTF-8
Content-Length: 89
Connection: close
Date: Mon, 13 Mar 2017 06:39:19 GMT
Server: Server FIPS

{"success":false,"errorMessage":"Bad Request. Check input data and payload size(<=200M)"}

What is wrong with my code?

PS: I have not done a file upload with form data before.

1
It is better to use TIdMultipartFormDataStream for form upload, check out my answer here. - whosrdaddy
@whosrdaddy the code is clearly already using TIdMultipartFormDataStream, by virtue of the fact that it is calling AddFormField and AddFile. However. Setting the Request.ContentType to application/x-www-form-urlencoded is erroneous in that scenario, though TIdHTTP will ignore it since the TIdMultiPartFormDataStream will overwrite it. - Remy Lebeau
t@RemyLebeau: I see, i was mislead due to the fact that he was setting the ContentType :) - whosrdaddy
@ErkanSen offhand, the code looks fine (though the ContentType is wrong). Can you show what the actual HTTP request sent by requeats.post() looks like compared to the HTTP request sent by TIdHTTP? - Remy Lebeau

1 Answers

1
votes

Next time you have trouble interacting with someone else's API, you should indicate what that API actually is so people have a chance to look up its documentation to see if anything is missing or wrong. In this case, you appear to be using the McAfee Advanced Threat Defense API.

A few things that stand out to me in your shown HTTP request:

  1. HTTP1.Request.ContentType := 'application/x-www-form-urlencoded';

    This is just plain wrong. The correct content type is multipart/form-data instead. However, TIdHTTP handles this for you when posting a TIdMultipartFormDataStream, so you don't need to assign a value to the Request.ContentType property at all, TIdHTTP will just overwrite it. But that does not change the fact that there is a bug in your code nonetheless.

  2. Accept-Encoding: identity

    This indicates to me that you are using an older version of Indy. You should consider upgrading to a more recent version. TIdHTTP no longer sends identity in the Accept-Encoding request header unless the TIdHTTP.Request.AcceptEncoding property contains other values, which is not the case here. Some servers have trouble handling Accept-Encoding: identity when explicitly stated in a request, which is why it is no longer sent by default.

  3. Content-Type: text/plain

    Your JSON field should have a Content-Type of application/json, or maybe even application/vnd.ve.v1.0+json in this API. The default is text/plain when not specified otherwise. AddFormField() has an AContentType parameter for this purpose. The web server might be sensitive to that value. JSON is also typically encoded using UTF-8, so you should indicate that as well. AddFormField() has an ACharset parameter for that purpose.

  4. Content-Transfer-Encoding: quoted-printable

    Your JSON string is being encoded using quoted-printable, which is normally fine for textual content in MIME, however not all web servers handle that in webform submissions, and it may not be appropriate for non-text/... media types, like JSON. AddFormField() returns a TIdFormDataField object. To disable the QP encoding, you can set the TIdFormDataField.ContentTransfer property to either 8bit or binary.

That being said, try something more like this:

Params.AddFormField('data', '{"data":{"xMode": ' + xMode + ',"analyzeAgain":1,"overrideOS":1,' +
                            '"vmProfileList":"' + DBProfileID.Value + '","submitType":0,"url":""}}',
                    'utf-8',
                    'application/json'
).ContentTransfer := '8bit';

// using GetMIMEType() to specify the ContentType is redundant as
// AddFile() already does that internally for you using Indy's own
// GetMIMETypeFromFile() function...
Params.AddFile('amas_filename', DBTestFilePath.Value);

JSON := HTTP1.Post(URL, Params);

If that still does not work for you, I suggest you contact McAfee directly for further help.