What should a Multipart HTTP request with multiple file inputs look like? [duplicate]
Asked Answered
B

2

126

I'm working on an iPhone app that makes a multipart HTTP request with multiple image files.

It looks like what's happening, on the server side, is that one of the images is getting parsed properly, but the other two files are not.

Can anybody post a sample HTTP multipart request that contains multiple image files?

Bhili answered 27/5, 2009 at 2:3 Comment(0)
E
197

Well, note that the request contains binary data, so I'm not posting the request as such - instead, I've converted every non-printable-ascii character into a dot (".").

POST /cgi-bin/qtest HTTP/1.1
Host: aram
User-Agent: Mozilla/5.0 Gecko/2009042316 Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://aram/~martind/banner.htm
Content-Type: multipart/form-data; boundary=2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Length: 514

--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile1"; filename="r.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile2"; filename="g.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile3"; filename="b.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f--

Note that every line (including the last one) is terminated by a \r\n sequence.

Enmity answered 27/5, 2009 at 2:59 Comment(10)
just to avoid any confusion: notice that before each boundary string in the content there are two extra dashes --<boundary>. For the last line is --<boundary>--Soppy
Any good ideas on how to create a multipart file like this with a for loop? My problem lies with that extra "--" before the final \r\n. If it was just at the end, i could append it on... Would one do a check if you were to the final object in your for-loop array and append something different?Mccleary
@Mccleary - two options come to mind immediately: 1) write your loop as (print boundary, print stuff), and then after the for loop completes follow that with (print boundary with extra dashes). That's probably the easiest way. 2) write your for() loop to run whatever index variable you have down to 0, instead of up from 0. Then add an extra two dashes when you print the boundary at the end if the index variable is 0.Enmity
This is really NOT a good example. Why would you choose a boundary that already has -- in it for an example. If someone doesn't know that that boundary is the again prefixed with another 2 -- you're screwed.Calzada
This is though exactly what my web browser produced at the time. Real browsers use boundaries with many dashes in them.Enmity
Yes, browsers put dashes in the boundaries. Better people find out in this answer than later.Keratin
For each file entry 'name' field is auto created by browser ? (talking about 'datafile3', 'datafile2', 'datafile1') If not auto created, from where these values are populated? Browser allows user to select multiple files for single file input in html form.Condensable
There's one quirky situation which no one really mentions: the boundaries could be wrapped one inside another! Check out the last example at W3: w3.org/TR/html4/interact/forms.html#h-17.13.4.2 We define first boundary A, then we use it (--A), then we define second boundary B, then we use it (--B), then we terminate inner boundary fist (--B--) then the outer boundary (--A--).Varga
Is it possible to preempt the full receipt of a multipart request. Say I am sending a large payload and I am sending a little bit of metadata about that payload so the server can perform some validations and fail the request sooner if some conditions are not met. Currently, I receive the full request and then perform the validation, but it seems extraneous to receive and store the payload even temporarily if the request ultimately must be failed by the server. I can send the metadata at the start of the request followed by the payload.Linton
I was confused by the boundary comments at first. It turns out that SMR's 2019 edit rendered some of them irrelevant. Explanation: The original boundary was ----------287032381131322. It was hard to notice that it was prefixed with two (more) hyphens in the content; hence the comment about it being a bad example. SMR changed it to 2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f presumably to make the content hyphens easier to spot.Flail
H
68

EDIT: I am maintaining a similar, but more in-depth answer at: https://mcmap.net/q/45052/-what-does-enctype-39-multipart-form-data-39-mean

To see exactly what is happening, use nc -l and a user agent like a browser or cURL.

Save the form to an .html file:

<form action="http://localhost:8000" method="post" enctype="multipart/form-data">
  <p><input type="text" name="text" value="text default">
  <p><input type="file" name="file1">
  <p><input type="file" name="file2">
  <p><button type="submit">Submit</button>
</form>

Create files to upload:

echo 'Content of a.txt.' > a.txt
echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html

Run:

nc -l localhost 8000

Open the HTML on your browser, select the files and click on submit and check the terminal.

nc prints the request received. Firefox sent:

POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"

text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain

Content of a.txt.

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------9051914041544843365972754266--

Aternativelly, cURL should send the same POST request as your a browser form:

nc -l localhost 8000
curl -F "text=default" -F "[email protected]" -F "[email protected]" localhost:8000

You can do multiple tests with:

while true; do printf '' | nc -l localhost 8000; done
Hoagy answered 7/5, 2014 at 12:43 Comment(2)
Thank you for the great post. How do you compute the content length? Is is the length of all the contents of contents (e.g. just "text default") or including the descriptions (from ---90xx66 until --90xx66--)?Counterpunch
@Counterpunch I think it counts everything, but not 100% sure. Try a minimal example on your own browser with this technique + wc to check it out + try to read the HTTP standard ;-) I think copy paste from stack overflow + xsel -b | wc does not match up because servers reply with \r\n at the end of each line, but those were converted to just \n at some point. Ping me if you conclude anything.Hoagy

© 2022 - 2024 — McMap. All rights reserved.