119

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?

Vladimir Panteleev
  • 24,651
  • 6
  • 70
  • 114
bpapa
  • 21,409
  • 25
  • 99
  • 147

2 Answers2

191

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.

SMR
  • 6,628
  • 2
  • 35
  • 56
Daniel Martin
  • 23,083
  • 6
  • 50
  • 70
  • 77
    just to avoid any confusion: notice that before each boundary string in the content there are two extra dashes --. For the last line is ---- – Radu Simionescu Jun 14 '13 at 12:03
  • 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? – turkeyhundt Oct 21 '14 at 17:06
  • 1
    @turkeyhundt - 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. – Daniel Martin Oct 24 '14 at 16:29
  • 31
    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. –  Jun 04 '15 at 22:16
  • 9
    This is though exactly what my web browser produced at the time. Real browsers use boundaries with many dashes in them. – Daniel Martin Jun 05 '15 at 01:26
  • 3
    Yes, browsers put dashes in the boundaries. Better people find out in this answer than later. – sudo Aug 07 '15 at 21:11
  • 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. – Dhanaraj Durairaj Mar 18 '16 at 07:18
  • 1
    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: https://www.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--). – Ignas2526 Jun 20 '17 at 15:54
  • 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. – Web User Jun 20 '18 at 18:11
  • 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. – Eric Eskildsen Oct 28 '21 at 00:06
67

EDIT: I am maintaining a similar, but more in-depth answer at: https://stackoverflow.com/a/28380690/895245

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 "file1=@a.txt" -F "file2=@a.html" localhost:8000

You can do multiple tests with:

while true; do printf '' | nc -l localhost 8000; done
leo
  • 3,677
  • 7
  • 34
  • 46
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 1
    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--)? – mojovski Mar 01 '16 at 12:19
  • @mojovski 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. – Ciro Santilli OurBigBook.com Mar 01 '16 at 14:27