45

How can I rewrite the following CURL command, so that it doesn't use the -F option, but still generates the exact same HTTP request? i.e. so that it passes the multipart/form-data in the body directly.

curl -X POST -F example=test http://localhost:3000/test

7 Answers 7

77

Solved:

curl \
  -X POST \
  -H "Content-Type: multipart/form-data; boundary=----------------------------4ebf00fbcf09" \
  --data-binary @test.txt \
  http://localhost:3000/test

Where test.txt contains the following text, and most importantly has CRLF (\r\n) line endings:

------------------------------4ebf00fbcf09
Content-Disposition: form-data; name="example"

test
------------------------------4ebf00fbcf09--

Notes: it is important to use --data-binary instead of plain old -d as the former preserves the line endings (which are very important). Also, note that the boundary in the body starts with an extra --.

I'm going to repeat it because it's so important, but that request-body file must have CRLF line endings. A multi-platform text editor with good line-ending support is jEdit (how to set the line endings in jEdit).

If you're interested in how I worked this out (debugging with a Ruby on Rails app) and not just the final solution, I wrote up my debugging steps on my blog.

9
  • 3
    Well done, sir. It took me 4 hours straight to get to you telling me the line endings need to be CRLF. Thanks so much. Commented Mar 28, 2013 at 19:29
  • Tim, you're welcome. This puzzled me for ages. The RFC has the key tools.ietf.org/html/rfc2046 (search 'CRLF'). The worse part is that curl will actually munge the line endings if you use -d! Commented Jun 5, 2013 at 7:00
  • Why for the love of Unix must the line endings be CRLFs?! Thanks for pointing this out. Commented Feb 22, 2016 at 1:38
  • Also I just noticed, in your test.txt example you have a trailing --, is it on purpose? Commented Feb 22, 2016 at 20:01
  • 1
    @CamiloMartin yes, see tools.ietf.org/html/rfc2046#section-5.1.1, specifically the text The boundary delimiter line following the last body part is a distinguished delimiter that indicates that no further body parts will follow. Such a delimiter line is identical to the previous delimiter lines, with the addition of two more hyphens after the boundary parameter value. You only need to do this on the last item. Commented Feb 22, 2016 at 20:14
34

You can use the --form argument with an explicitly

curl -H "Content-Type: multipart/related" \
  --form "[email protected];type=image/jpeg" http://localhost:3000/test
3
  • 4
    thanks but I'm specifically wanting to pass in the raw data for testing, so that I can understand it – and then use it in a non-curl based program. Commented Dec 12, 2012 at 0:58
  • 5
    Thank you, the "type=..." helped me so much!
    – Hemeroc
    Commented Aug 16, 2017 at 15:53
  • This should be the accepted answer, given that it allows to do this with just curl CLI features and without constructing multipart requests by hand,
    – ixi
    Commented Jul 10, 2023 at 13:16
16

Here's an alternative answer with the original CURL statement re-written using -d as a one-liner, without temporary files. Personally I think the temporary files approach is easier to understand, but I'm putting this here for reference as well:

curl -X POST -H "Content-Type: multipart/form-data; boundary=----------------------------4ebf00fbcf09" -d $'------------------------------4ebf00fbcf09\r\nContent-Disposition: form-data; name="example"\r\n\r\ntest\r\n------------------------------4ebf00fbcf09--\r\n' http://localhost:3000/test

Notes: the $'blar' syntax is so that bash will parse the \r\n as a CRLF token. Thanks to this answer for that tip.

1
  • Thanks. It's also helpful to note that the Content-Type header should not specify the boundary value unless you are also passing raw data with a static boundary value. Setting the boundary value while also using -F key=value parameters can make curl suffix a 2nd boundary= value, which is obviously unintended.
    – Jeff R
    Commented Jul 23, 2021 at 18:18
5

This is what I'm using, I think it's clean and doesn't need temporary files nor gobbles up RAM in case you want to upload whole files (so no reading files into memory).

# Set these two.
file='path/to/yourfile.ext'
url='http://endpoint.example.com/foo/bar'

delim="-----MultipartDelimeter$$$RANDOM$RANDOM$RANDOM"
nl=$'\r\n'
mime="$(file -b --mime-type "$file")"

# This is the "body" of the request.
data() {
    # Also make sure to set the fields you need.
    printf %s "--$delim${nl}Content-Disposition: form-data; name=\"userfile\"${nl}Content-Type: $mime$nl$nl"
    cat "$file"
    printf %s "$nl--$delim--$nl"
}

# You can later grep this, or something.
response="$(data | curl -# "$url" -H "content-type: multipart/form-data; boundary=$delim" --data-binary @-)"
1

This is to upload one image file using "Content-Type: multipart/related",

curl --trace trace.txt -X POST -H 'Content-Type: multipart/related; boundary=boundary_1234' --data-binary $'--boundary_1234\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n{\r\n\t"title": "TestFile"\r\n}\r\n\r\n--boundary_1234\r\nContent-Type: image/jpeg\r\n\r\n' --data-binary '@Image0177.jpg' --data-binary $'\r\n--boundary_1234--\r\n' 'http://localhost:3000/google/upload/drive/v2/files?uploadType=multipart'
0

Here it is how I would do it:

curl https://httpbin.org/post \
    -H 'content-type: multipart/form-data; boundary=----FormBoundary123456789' \
    --data-binary $'------FormBoundary123456789\r
Content-Disposition: form-data; name="example"\r
\r
test\r
------FormBoundary123456789--\r
'

Or a bit more sophisticated (should be portable to most modern shells):

DELIM=----FormBoundary$RANDOM$RANDOM

curl https://httpbin.org/post \
    -H "content-type: multipart/form-data; boundary=$DELIM" \
    --data-binary --$DELIM$'\r
Content-Disposition: form-data; name="example"\r
\r
test\r
'--$DELIM--$'\r
'
-3

This is for multipart/form-data request method. for uploading a file add --form filename="@path/image.jpg;type=image/jpeg"

curl --form key="value" --form key="value" http://localhost:3000/test

1
  • This doesn't answer the question, since -F and --form are the same option; in the manual page -F, --form <name=content>. Commented Jan 19, 2016 at 11:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.