473

I would like to use cURL to not only send data parameters in HTTP POST but to also upload files with specific form name. How should I go about doing that ?

HTTP Post parameters:

userid = 12345 filecomment = This is an image file

HTTP File upload: File location = /home/user1/Desktop/test.jpg Form name for file = image (correspond to the $_FILES['image'] at the PHP side)

I figured part of the cURL command as follows:

curl -d "userid=1&filecomment=This is an image file" --data-binary @"/home/user1/Desktop/test.jpg" localhost/uploader.php

The problem I am getting is as follows:

Notice: Undefined index: image in /var/www/uploader.php

The problem is I am using $_FILES['image'] to pick up files in the PHP script.

How do I adjust my cURL commands accordingly ?

Syscall
  • 16,959
  • 9
  • 22
  • 41
thotheolh
  • 6,222
  • 6
  • 27
  • 43

9 Answers9

725

You need to use the -F option:
-F/--form <name=content> Specify HTTP multipart POST data (H)

Try this:

curl \
  -F "userid=1" \
  -F "filecomment=This is an image file" \
  -F "image=@/home/user1/Desktop/test.jpg" \
  localhost/uploader.php
jwfearn
  • 26,394
  • 26
  • 89
  • 117
jimp
  • 15,885
  • 3
  • 23
  • 35
  • 1
    I'm confused by the part about url-encoding the file. I have uploaded JPG and PNG files like this without modifying them, without any problems. – Deanna Gelbart May 16 '13 at 00:27
  • 1
    @DavidGelbart You're right. My initial answer referenced the `-d` option by mistake, which needs the input URL-encoded. I should have removed that when I updated the answer to the `-F` option. Thanks for catching that. – jimp Jun 25 '13 at 19:06
  • I'm trying to the same but some reason i'm not able to send files .. it says file not attached – user7044 Oct 15 '14 at 08:07
  • @jimp how do we add multiple files in the curl command for POST – user956424 Nov 08 '16 at 11:23
  • @user956424 Just repeat the `-F` argument for each file you want to send. – jimp Nov 10 '16 at 23:39
  • @jimp True, forgot to mention that the number of files uploaded is dynamic and each file is associated with a different form field value – user956424 Nov 11 '16 at 03:33
  • 3
    @user956424 In the example, set "image" to the name of your field. And some languages, such as PHP, will build an array if you specify something like "image[]" for the inputs that need to be grouped together. – jimp Nov 11 '16 at 20:48
  • Does the file path for the `-F` argument have to be a local path on that server or can it be file accessible on a remote server like `http://www.example.com/path/to/some/image.jpg`? – The Unknown Dev Jan 16 '17 at 00:17
  • Worked fine with me too.. I need to pass some parameters along with image/video uploaded – minhas23 Feb 06 '17 at 09:12
  • I can confirm this is working fine with a Java spring boot backend! thanks you. – Thomas Decaux Sep 26 '17 at 13:26
  • 5
    What is the `@` in `image=@/..`? – Timo Jul 16 '18 at 15:51
  • 6
    @Timo It means the content for the named form field should be loaded from a file path. Without it the string argument itself is passed through. – jimp Jul 18 '18 at 04:55
  • how to make an array these kind of post data? – muneeb_ahmed May 28 '20 at 20:53
  • @muneeb_ahmed, you can just repeat -F "image=@/somefilepath.jpg/" for each file. – William Barela Feb 26 '21 at 04:13
109

Catching the user id as path variable (recommended):

curl -i -X POST -H "Content-Type: multipart/form-data" 
-F "data=@test.mp3" http://mysuperserver/media/1234/upload/

Catching the user id as part of the form:

curl -i -X POST -H "Content-Type: multipart/form-data" 
-F "data=@test.mp3;userid=1234" http://mysuperserver/media/upload/

or:

curl -i -X POST -H "Content-Type: multipart/form-data" 
-F "data=@test.mp3" -F "userid=1234" http://mysuperserver/media/upload/
r1ckr
  • 5,130
  • 4
  • 17
  • 23
  • 20
    use -F needn't set ```"Content-Type: multipart/form-data"``` – William Hu Jan 15 '16 at 02:26
  • 13
    I couldn't get -F to work properly with that semicolon separator you indicated. Instead, I had to provide two redundant -F arguments. Like: -F "data=@test.mp3" -F "userid=1234" – robbpriestley Oct 23 '16 at 17:38
22

Here is my solution, I have been reading a lot of posts and they were really helpful. Finally I wrote some code for small files, with cURL and PHP that I think its really useful.

public function postFile()
{    
        $file_url = "test.txt";  //here is the file route, in this case is on same directory but you can set URL too like "http://examplewebsite.com/test.txt"
        $eol = "\r\n"; //default line-break for mime type
        $BOUNDARY = md5(time()); //random boundaryid, is a separator for each param on my post curl function
        $BODY=""; //init my curl body
        $BODY.= '--'.$BOUNDARY. $eol; //start param header
        $BODY .= 'Content-Disposition: form-data; name="sometext"' . $eol . $eol; // last Content with 2 $eol, in this case is only 1 content.
        $BODY .= "Some Data" . $eol;//param data in this case is a simple post data and 1 $eol for the end of the data
        $BODY.= '--'.$BOUNDARY. $eol; // start 2nd param,
        $BODY.= 'Content-Disposition: form-data; name="somefile"; filename="test.txt"'. $eol ; //first Content data for post file, remember you only put 1 when you are going to add more Contents, and 2 on the last, to close the Content Instance
        $BODY.= 'Content-Type: application/octet-stream' . $eol; //Same before row
        $BODY.= 'Content-Transfer-Encoding: base64' . $eol . $eol; // we put the last Content and 2 $eol,
        $BODY.= chunk_split(base64_encode(file_get_contents($file_url))) . $eol; // we write the Base64 File Content and the $eol to finish the data,
        $BODY.= '--'.$BOUNDARY .'--' . $eol. $eol; // we close the param and the post width "--" and 2 $eol at the end of our boundary header.



        $ch = curl_init(); //init curl
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                         'X_PARAM_TOKEN : 71e2cb8b-42b7-4bf0-b2e8-53fbd2f578f9' //custom header for my api validation you can get it from $_SERVER["HTTP_X_PARAM_TOKEN"] variable
                         ,"Content-Type: multipart/form-data; boundary=".$BOUNDARY) //setting our mime type for make it work on $_FILE variable
                    );
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/1.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0'); //setting our user agent
        curl_setopt($ch, CURLOPT_URL, "api.endpoint.post"); //setting our api post url
        curl_setopt($ch, CURLOPT_COOKIEJAR, $BOUNDARY.'.txt'); //saving cookies just in case we want
        curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); // call return content
        curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 1); navigate the endpoint
        curl_setopt($ch, CURLOPT_POST, true); //set as post
        curl_setopt($ch, CURLOPT_POSTFIELDS, $BODY); // set our $BODY 


        $response = curl_exec($ch); // start curl navigation

     print_r($response); //print response

}

With this we should be get on the "api.endpoint.post" the following vars posted. You can easily test with this script, and you should be receive this debugs on the function postFile() at the last row.

print_r($response); //print response

public function getPostFile()
{

    echo "\n\n_SERVER\n";
    echo "<pre>";
    print_r($_SERVER['HTTP_X_PARAM_TOKEN']);
    echo "/<pre>";
    echo "_POST\n";
    echo "<pre>";
    print_r($_POST['sometext']);
    echo "/<pre>";
    echo "_FILES\n";
    echo "<pre>";
    print_r($_FILEST['somefile']);
    echo "/<pre>";
}

It should work well, they may be better solutions but this works and is really helpful to understand how the Boundary and multipart/from-data mime works on PHP and cURL library.

halfer
  • 18,701
  • 13
  • 79
  • 158
Libertese
  • 239
  • 2
  • 2
  • if you need to send not-encoded file change this lines $BODY.= 'Content-Transfer-Encoding: multipart/form-data' . $eol . $eol; // we put the last Content and 2 $eol, $BODY.= file_get_contents($file_url) . $eol; // we write the Base64 File Content and the $eol to finish the data, – icy Apr 13 '15 at 08:24
15

if you are uploading binary file such as csv, use below format to upload file

curl -X POST \
    'http://localhost:8080/workers' \
    -H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyIsInR5cGUiOiJhY2Nlc3MifQ.eyJ1c2VySWQiOjEsImFjY291bnRJZCI6MSwiaWF0IjoxNTExMzMwMzg5LCJleHAiOjE1MTM5MjIzODksImF1ZCI6Imh0dHBzOi8veW91cmRvbWFpbi5jb20iLCJpc3MiOiJmZWF0aGVycyIsInN1YiI6ImFub255bW91cyJ9.HWk7qJ0uK6SEi8qSeeB6-TGslDlZOTpG51U6kVi8nYc' \
    -H 'content-type: application/x-www-form-urlencoded' \
    --data-binary '@/home/limitless/Downloads/iRoute Masters - Workers.csv'
KARTHIKEYAN.A
  • 11,752
  • 4
  • 81
  • 93
  • 7
    I would like to see an example of a binary csv file. – polis Jan 11 '19 at 06:33
  • 5
    @polis the option `--data-binary` instructs `curl` to _not_ do any pre-processing of the data, as opposed to `--data` flag. to address your comment directly, note that text is also binary, but we can interpret it as ASCII characters. If you really want a distinct example, think about a CSV whose fields contain emoji. Their bytes do not directly map to text – Ciprian Tomoiagă May 09 '19 at 11:52
  • 1
    if anyone is googling: `--data-binary` works on AzureBlob direct upload url https://docs.microsoft.com/en-us/rest/api/storageservices/Put-Blob?redirectedfrom=MSDN – equivalent8 Jul 14 '20 at 06:43
7

After a lot of tries this command worked for me:

curl -v -F filename=image.jpg -F upload=@image.jpg http://localhost:8080/api/upload
Evandro Pomatti
  • 7,960
  • 11
  • 56
  • 111
4

The issue that lead me here turned out to be a basic user error - I wasn't including the @ sign in the path of the file and so curl was posting the path/name of the file rather than the contents. The Content-Length value was therefore 8 rather than the 479 I expected to see given the legnth of my test file.

The Content-Length header will be automatically calculated when curl reads and posts the file.

curl -i -H "Content-Type: application/xml" --data "@test.xml" -v -X POST https://<url>/<uri/

... < Content-Length: 479 ...

Posting this here to assist other newbies in future.

shonky linux user
  • 5,223
  • 2
  • 36
  • 66
3

As an alternative to curl, you can use HTTPie, it'a CLI, cURL-like tool for humans.

  1. Installation instructions: https://github.com/jakubroztocil/httpie#installation

  2. Then, run:

    http -f POST http://localhost:4040/api/users username=johnsnow photo@images/avatar.jpg
    
    HTTP/1.1 200 OK
    Access-Control-Expose-Headers: X-Frontend
    Cache-control: no-store
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Length: 89
    Content-Type: text/html; charset=windows-1251
    Date: Tue, 26 Jun 2018 11:11:55 GMT
    Pragma: no-cache
    Server: Apache
    Vary: Accept-Encoding
    X-Frontend: front623311
    
    ...
    
Gianfranco P.
  • 6,858
  • 3
  • 40
  • 56
1

Here is how to correctly escape arbitrary filenames of uploaded files with bash:

#!/bin/bash
set -eu

f="$1"
f=${f//\\/\\\\}
f=${f//\"/\\\"}
f=${f//;/\\;}

curl --silent --form "uploaded=@\"$f\"" "$2"
Vladimir Panteleev
  • 23,894
  • 6
  • 66
  • 106
1

I got it worked with this command curl -F 'filename=@/home/yourhomedirextory/file.txt' http://yourserver/upload

Shraavan Hebbar
  • 179
  • 1
  • 7