About upload a file via 'multipart/form-data'

Has anyone been able to succesfully upload a file as 'multipart/form-data' via script HttpRequest object?
A small example maybe if so?
I've been trying to use:

req.AddPostData('file', blob);
req.AddPostData('file', blob, 'multipart/form-data');
req.AddPostData('file', DOpus.Create.StringTools.Encode(blob, 'base64'), 'multipart/form-data');

where blob is obtained via item.Open().Read().
But I always get Error 400 : Bad Request as response.
Any ideas?

TIA

Hard to know what the problem might be without knowing what you're trying to upload and to where.

There's an echo server at echo.free.beeceptor.com that will report back what it received; this example prints the results to the output log, and shows no errors sending two small chunks of data.

function OnClick(clickData)
{
	var Dlg = DOpus.Dlg;
	Dlg.window = clickData.func.sourcetab;
	Dlg.template = "testdlg";
	Dlg.detach = true;
	Dlg.Show();

	var newReq = Dlg.NewHTTPReq();
	newReq.AddPostData("testdata1", DOpus.Create.Blob(1,2,3,4,5,6,7,8,9,10));
	newReq.AddPostData("testdata2", DOpus.Create.Blob(11,12,13,14,15,16,17,18,19,20));
	newReq.SendRequest("https://echo.free.beeceptor.com/sample-request");

	while (true)
	{
	    var Msg = Dlg.GetMsg();
    	if (!Msg.result) break;

		if (Msg.event == "http" && !newReq.complete)
		{
			if (Msg.value == "data")
			{
				var reqData = newReq.ReadResponse();
				var headers = newReq.GetResponseHeaders();

				for (var hdr = new Enumerator(headers); !hdr.atEnd(); hdr.moveNext())
				{
					DOpus.Output(hdr.item() + ": " + headers(hdr.item()));
				}
				DOpus.Output("----");
				DOpus.Output(reqData);
				newReq.shutdown();
			}
			else if (Msg.value == "error")
			{
				var headers = newReq.GetResponseHeaders();
				for (var hdr = new Enumerator(headers); !hdr.atEnd(); hdr.moveNext())
				{
					DOpus.Output(hdr.item() + ": " + headers(hdr.item()));
				}
			}			
			else DOpus.Output(Msg.value);
		}
	}
}
1 Like

Thanks Jon. I was trying to upload a file using the VirusTotal API. Currently, my command for that works 'okay-ish', except when the file is not found in the database and needs to be uploaded.

Horribly simplified, and using your example as a base, using this script :

function OnClick(clickData) {
	if (!clickData.func.sourcetab.stats.selfiles) return;
	var item = clickData.func.sourcetab.selected_files(0);
	var Dlg = DOpus.Dlg;
	Dlg.window = clickData.func.sourcetab;
	Dlg.template = "testdlg";
	Dlg.detach = true;
	Dlg.Show();
	var api_key = 'api_key_goes_here';
	Dlg.title = item.name;
	var newReq = Dlg.NewHTTPReq();
	newReq.AddHeader('accept', 'application/json');
	newReq.AddHeader('content-type', 'multipart/form-data');
        newReq.AddHeader('x-apikey', api_key);
	newReq.AddPostData("file", item.Open().Read());
	newReq.SendRequest("https://www.virustotal.com/api/v3/files");

	while (true) {
		var Msg = Dlg.GetMsg();
		if (!Msg.result) break;

		if (Msg.event == "http" && !newReq.complete) {
			if (Msg.value == "data") {
				var reqData = newReq.ReadResponse();
				var headers = newReq.GetResponseHeaders();
				DOpus.Output("----Headers----");
				for (var hdr = new Enumerator(headers); !hdr.atEnd(); hdr.moveNext()) {
					DOpus.Output(hdr.item() + ": " + headers(hdr.item()));
				}
				DOpus.Output("----Response----");
				DOpus.Output(reqData);
				newReq.shutdown();
			}
			else if (Msg.value == "error") {
				var headers = newReq.GetResponseHeaders();
				for (var hdr = new Enumerator(headers); !hdr.atEnd(); hdr.moveNext()) {
					DOpus.Output(hdr.item() + ": " + headers(hdr.item()));
				}
			}
			else DOpus.Output(Msg.value);
		}
	}
}

post example.dcf (3.4 KB)

(to test it you need an API key, which you can get starting from here)
(Also, the file to be uploaded must be smaller than 32 MB)

I get :

pending
----Headers----
Content-Length: 109
Content-Type: application/json
Date: Thu, 13 Jun 2024 01:35:31 GMT
HTTP/1.0 400 Bad Request: 
Server: Google Frontend
X-Cloud-Trace-Context: 6376bc5e9b24c0e20b42cbb9e0479d71
----Response----
0

Any advice?
TIA

===
As a sitenote, I think there's something wrong when using the action parameter for HTTPRequest.SendRequest(), you can try to change this line in your example and you're going to get an error 400.

newReq.SendRequest("https://echo.free.beeceptor.com/sample-request","POST"); //it should work too but it doesn't

Continuing with the discussion, testing the HTTP POST Request - Multipart example from here, from the same page that @Jon posted above:

curl --location 'https://echo.free.beeceptor.com/' --form 'action="form-submit"' --form 'file=@"sample_file.jpg"'

The output is:

{
    "method": "POST",
    "path": "/",
    "ip": "136.185.41.120",
    "headers": {
        "host": "echo.free.beeceptor.com",
        "user-agent": "curl/7.88.1",
        "content-length": "55561",
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "content-type": "multipart/form-data; boundary=--------------------------362521674601421993043015"
    },
    "parsedQueryParams": { },
    "parsedBody": {
        "textFields": {
            "action": "form-submit"
        },
        "files": [
            {
                "name": "file",
                "fileName": "sample_file.jpg",
                "contentType": "image/jpeg"
            }
        ]
    }
}

The same example in DOpus:

function OnClick(clickData)
{
	DOpus.ClearOutput();
	if (!clickData.func.sourcetab.stats.selfiles) return;
	var item = clickData.func.sourcetab.selected_files(0);
	if (item.size == 0) return;
	DOpus.Output("item : " + item);
	var Dlg = DOpus.Dlg;
	Dlg.window = clickData.func.sourcetab;
	Dlg.template = "testdlg";
	Dlg.detach = true;
	Dlg.Show();
	var newReq = Dlg.NewHTTPReq();
	var blob = DOpus.Create.Blob();
	blob.CopyFrom(item.Open.Read());
	DOpus.Output("file size : " + item.size);
	DOpus.Output("blob size : " + blob.size);
	newReq.AddPostData("action", "formsubmit");
	newReq.AddPostData("file",blob);
	newReq.SendRequest("https://echo.free.beeceptor.com");

	while (true)
	{
	    var Msg = Dlg.GetMsg();
    	if (!Msg.result) break;

		if (Msg.event == "http" && !newReq.complete)
		{
			if (Msg.value == "data")
			{
				var reqData = newReq.ReadResponse();
				DOpus.SetClip(reqData);
				newReq.shutdown();
			}
			else if (Msg.value == "error")
			{
				var headers = newReq.GetResponseHeaders();
				for (var hdr = new Enumerator(headers); !hdr.atEnd(); hdr.moveNext())
				{
					DOpus.Output(hdr.item() + ": " + headers(hdr.item()));
				}
			}			
			else DOpus.Output(Msg.value);
		}
	}
}

The response:

{
  "method": "POST",
  "protocol": "https",
  "host": "echo.free.beeceptor.com",
  "path": "/",
  "ip": "xxxxxxxxxx",
  "headers": {
    "Host": "echo.free.beeceptor.com",
    "User-Agent": "Mozilla/4.0",
    "Content-Length": "90840",
    "Accept": "*/*",
    "Accept-Encoding": "identity",
    "Accept-Language": "en-us",
    "Content-Type": "multipart/form-data, boundary=__DOPUS__xXxXxXxX__"
  },
  "parsedQueryParams": {},
  "parsedBody": {
    "textFields": {
      "action": "formsubmit",
      "file": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIYSUNDX1BST0ZJTEUAAQEAAAIIAAAAAAQwAABtbnRyUkdC..."
    },
    "files": []
  }
}

So it would seem that the uploaded files are not in the correct location (files), but rather in the values treated as text (textFields).
Any comment about this?

Ok. Let's try again. This time, something more straightforward: uploading an image to Litterbox. Here's the information for their API.

Using Jon's first example as a template isn't working; I keep getting a frustrating 412 error.

Note that I used to be able to upload images here using regular JScript's HttpRequest. The drawback was it wasn't asynchronous. Is a workaround where I built the entire form-data using ADODB.Stream(). So, it looked something like this:

--------------------------3fdea02c07094305
Content-Disposition: form-data; name="reqtype"
Content-Type: "text/plain"

fileupload
--------------------------3fdea02c07094305
Content-Disposition: form-data; name="time"
Content-Type: "text/plain"

1h
--------------------------3fdea02c07094305
Content-Disposition: form-data; name="fileToUpload"; filename="sample_file.jpg"
Content-Type: "image/jpeg"

@file_data
--------------------------3fdea02c07094305--

Adapting it for use with HTTPReq from DO, I managed to upload 'something' (code = 200), but unfortunately, it was a broken file.

The script in a button :
post example embed.dcf (6.5 KB)

My guess is currently we can't send files via form-data as attachments, because we can't add individual headers like filename, among others.

@Jon , @Leo , or anyone else, could you please provide a real working example of a multi-form request with file upload included, using the API from above or a similar one?

Thanks.

According to the specs the "filename" argument is optional, but we'll add support for this in 13.7.1.

Separate to that, I worked out why the upload to VirusTotal was failing - another header had a comma in it when it should have been a semi-colon. That'll be fixed in 13.7.1 as well.

3 Likes

Many thanks for taking a look at this! And thumbs up for the superb support.

1 Like

Solved in 13.7.1! You guys are amazing!
I tried with many sites and it works flawlessly now.

Here's a simple visual example to showcase what DO can do and hopefully encourage others to dive into DO scripting.

Scanning a file with VT: You can check any file up to 650mb.

When the file is in the database, it looks like this:

You can check even the behaviour in sandboxes!
If it's not, the upload now works smoothly without any workaround!

(I don't continue the query here on purpose, because the file stays 'queued' when consulting via API for like a minute or more, better to open the link IMO)

Many thanks!

3 Likes

This is cool. Imma have to try this out.
I've heard that the end user license allows anyone with a paid Virus Total account to download any uploaded file. So therefore if someone doesn't have a VT account, to disallow that in their preferences, they've just given that file to the whole world.

...

I want your context menu. :heart_eyes:

I thought this thread was a script share for this uploader thing of yours.
Report Thing

Cause us normie peasants get this haggard hash check pop up instead, that no one has ever needed ever.
Haggard

If and when I can actually get it to work:
No Permalink

It still just fires up the VT website on the browser. So it's faster to just have VT bookmarked and drag and drop right on the webpage, without the "desktop uploader" in the first place. Don't know why Google hasn't provided a proper uploader app anyway.