Send and receive binary files using PHP and cURL

Ocean of 0s and 1s with missing digits in the shape of a camera icon.
This could be your file right here.

I was working on a project where I had to send and receive binary files to and from a REST API, so I decided to document some of the code I wrote. Keep in mind that I’m extracting all of these code snippets from a custom PHP class I wrote, so if you’re working with an API, I’d encourage you to create a class of your own.

Setup a form to submit a file

Before you can send a binary file to an API, you have to get it from somewhere. In my project, that file came from a form. The form could be as simple as this:

<form method="post" action="index.php" enctype="multipart/form-data">
  <input name="file" type="file" />
  <input type="submit" value="Upload" />
</form>

Note the enctype attribute is set to multipart/form-data. You must have this set, otherwise the file will not be submitted.

Grab the submitted file

Normally when you process a form, the contents are found inside of the global $_POST variable, however file data is located within the global $_FILES variable.

If you print out the contents of $_FILES on the page the form was submitted to, you’d see something like:

Array
(
  [file] => Array
    (
      [name] => Array
        (
          [0] => 400.png
        )
      [type] => Array
        (
          [0] => image/png
        )
      [tmp_name] => Array
        (
          [0] => /tmp/php5Wx0aJ
        )
      [error] => Array
        (
          [0] => 0
        )
      [size] => Array
        (
          [0] => 15726
        )
    )
)

What’s unusual about this array is that instead of grouping each file’s data in one array, each piece of information about a file is grouped together. So the values at index 0 in name, type, tmp_name, error and size belong to one file.

The binary file data is located in a temporary storage on your server. In this case, at /tmp/php5Wx0aJ.

Send the binary contents via cURL

The following snippet sends the binary file to a URL:

$url = 'https://example.org/api';
$header = array('Content-Type: multipart/form-data');
$fields = array('file' => '@' . $_FILES['file']['tmp_name'][0]);
$token = 'NfxoS9oGjA6MiArPtwg4aR3Cp4ygAbNA2uv6Gg4m';

$resource = curl_init();
curl_setopt($resource, CURLOPT_URL, $url);
curl_setopt($resource, CURLOPT_HTTPHEADER, $header);
curl_setopt($resource, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($resource, CURLOPT_POST, 1);
curl_setopt($resource, CURLOPT_POSTFIELDS, $fields);
curl_setopt($resource, CURLOPT_COOKIE, 'apiToken=' . $token);
$result = json_decode(curl_exec($resource));
curl_close($resource);

Let’s talk about each of these lines, because some of this will depend on the requirements of the API you are using and may not be necessary.

  • $url is where cURL will post your data.
  • $header tells the API that there is a file coming its way. You can supply more headers by adding additional values to the array.
  • $fields contains an associative array. The field name depends on what the API expects it to be. The @ sign in the value tells cURL that we’re dealing with a file.
  • $token is a token that the REST API expects as a cookie. Usually you login to the API once during a transaction, then use a token for all subsequent requests. The cookie is called apiToken, but this may differ depending on the API’s expectations.
  • CURLOPT_RETURNTRANSFER tells cURL we want a response.
  • CURLOPT_POST tells cURL we want our data to be posted.
  • CURLOPT_COOKIE sets the token as a cookie.

Lastly, notice the last line:

$result = json_decode(curl_exec($resource));

In my case, I know that the response I’m getting from the API will be in the JSON format, which is why I’m decoding it. You may not need this if you’re getting back a response in XML, for example.

This completes sending a binary file via cURL.

Next, we’ll look at retrieving that file.

Retrieve the binary contents via cURL

Here’s how to retrieve a binary file:

$resource = curl_init();
curl_setopt($resource, CURLOPT_URL, $url);
curl_setopt($resource, CURLOPT_HEADER, 1);
curl_setopt($resource, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($resource, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($resource, CURLOPT_COOKIE, 'apiToken=' . $token);
$file = curl_exec($resource);
curl_close($resource);

We don’t have to go over anything except for:

  • CURLOPT_HEADER tells cURL that we expect there to be a header. This is important, because it tells us what kind of file we’re getting, i.e. an image, a Word document, a PDF, etc.
  • CURLOPT_BINARYTRANSFER tells PHP that the result will contain binary data. Lots of people claim you don’t need this line. I say try it with and without, just to be sure.

Now you have the binary file with its headers stored in $file.

Give the file back to the user

Let’s go over on how to give the file back to the user. You could either store the file on the server and give the user a URL to the file, or you can just let PHP handle all that on the fly and simply prompt the user to save the file.

Here is how I did that:

$file_array = explode("\n\r", $file, 2);
$header_array = explode("\n", $file_array[0]);
foreach($header_array as $header_value) {
  $header_pieces = explode(':', $header_value);
  if(count($header_pieces) == 2) {
    $headers[$header_pieces[0]] = trim($header_pieces[1]);
  }
}
header('Content-type: ' . $headers['Content-Type']);
header('Content-Disposition: ' . $headers['Content-Disposition']);
echo substr($file_array[1], 1);
  • On line 1 I separate the header from the rest of the file.
  • On line 2-8 I parse the header contents and put them in an array so I can easily reference the elements I need.
  • On line 9 & 10 I tell PHP what the content type is and what the file should be called.
  • On line 11 I output all of the binary contents. Together with the header, it prompts the user to save the file (or displays it in the browser if it’s an image, for example).

I want to touch on the function on line 11: substr($file_array[1], 1);

In my case, the binary file had an extra space at the beginning of the file. The substr function removes that. I’m not sure whether that space came from the API or from the explode on line 1, so if this doesn’t work for you, try changing that line to: echo $file_array[1];

Hopefully this info will shed some light on how to post and retrieve files in a binary format. If you have any questions, leave them in a comment below.

Featured image by Alexander Sinn.


Comments (27)

Previously posted in WordPress and transferred to Ghost.

Mahesh
May 14, 2013 at 6:28 am

Hi ,

I want to fetch a image using curl api and want to show directly in the fancy box, but it is showing in binary format always.Even I have added header there.

The same code is working if I don’t use the fancybox.

Can you please help me.

Ryan Sechrest
May 23, 2013 at 7:53 pm

Have you tried storing the image in a temporary file, so that you can embed it in the fancybox?

Dev
May 31, 2013 at 1:16 pm

Hi,
Can you suggest me on how can I verify that my file is uploaded correctly to the server?
1) Can I send some kind of file checksum to the server using CURL? so that the server also calculates the checksum on received file and compares two checksums and reply with proper response code.

How to do it in CURL?

Ryan Sechrest
May 31, 2013 at 2:25 pm

You can create a script on the server that expects two values: a checksum and a filename. Then you can post those two values using a cURL. The server will then calculate the checksum of the existing file and compare it to what you sent. If it matches, you can return something like true and if it doesn’t, you can return false (and optionally remove the file). So, the first request uploads the file, and the second request verifies it.

Bharat Masimukku
December 20, 2013 at 2:22 am

Hello Ryan

I have the following requirement. Upload a jpg format image to jpegmini server and receive the compressed image as the response.

Please refer to the below jpegmini documentation:

Method: POST

URL: http://ec2-54-209-163-171.compute-1.amazonaws.com/api/v1/optimize

Body: The data of the source jpeg photo

Command: curl -X POST -H 'Content-Type: image/jpeg' –data-binary @/tmp/input.jpg –output /tmp/output.jpg http://ec2-54-209-163-171.compute-1.amazonaws.com/api/v1/optimize

If we break down the above command:

-X POST – specify http POST method

-H 'Content-Type: image/jpeg' – specify body content is a jpeg

-data-binary @/tmp/input.jpg – send /tmp/input.jpg as the data of the request

-output /tmp/output.jpg – store response in /tmp/output.jpg (instead of stdout)

Please note that the jpegmini URL is an AWS instance. I am stopping the instance when not using it. If you want me to run the instance please let me know.

I want to make this cURL request and receive response using PHP.

Please help me out.

Ryan Sechrest
December 20, 2013 at 4:26 pm

Have you tried your cURL command via command line and did it give you the response you’re looking for? If so, have you already tried converting it to PHP and were unsuccessful? If so, post the PHP code you tried in a Gist so I can take a look at it.

Manish
March 11, 2014 at 9:00 am

Hello,

As you define in your blog, I have same folllow the above steps to upload and download files using curl, but its not working anymore. Can you define by the examples. So I can understand easily.

Thanks in Advance
Manish Arora

Ryan Sechrest
March 11, 2014 at 9:09 am

I’ll need a bit more information. What specifically isn’t working and what kind of response are you getting? When you say it isn’t working anymore, was it working for you before?

Manish
March 12, 2014 at 2:57 am

Hello Ryan,

I use your code and as you say that download popup will display on client browser, but its not showing and display only the content of file.

So how to download file, Please describe it here.

Thanks in advance
Manish

Ryan Sechrest
March 12, 2014 at 9:13 am

First, I would echo out Content-Type and Content-Disposition to ensure those are right. Second, have you tried echoing out $file_array[1] without using substr()? Third, for testing, save the file to the disk, download it, and then open it to verify the file was transmitted back to you properly.

phpdeveloper
May 7, 2014 at 10:16 am

Ryan,

Thanks for the informative post. I have been banging my head over this problem of uploading a form with file using curl but simply can’t figure out what is wrong with my code.

Here is my code:
$cfile = new CURLFile('/home/xyz/myimge.jpg', 'image/jpeg', 'mymage.jpg');
      
$params = array(
  "userid_id" => "some id #",
  "file" => $cfile,
  "name" => "test_file",
  "gallery" => "myalbum"
);
     
$uri = 'http://myserver.com/somedir/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true ); 
curl_setopt($ch, CURLOPT_POST, true );
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
       
$response_data = curl_exec($ch);
     
var_dump(curl_getinfo($ch));
        
var_dump($response_data);
curl_close( $ch );
unset( $ch );
But when I see my output from curl_getinfo, i see that content type is still text/html, where as all the docs I have seen suggest that, once you add a file to postfields, curl automatically converts the content type to multipart/form-data. I get the other form data but do not get file data where i am posting my data.

Not sure what am I doing wrong here. Any ideas?

I am on php 5.5, so appending @ to the file path is not working, since it is deprecated.

Ryan Sechrest
May 7, 2014 at 11:59 am

@phpdeveloper

I have not used the CURLFile class, but your syntax looks right.

Try manually setting the header to multipart/form-data using the CURLOPT_HTTPHEADER option:
$header = array('Content-Type: multipart/form-data');
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
Also, with regard to you not getting the file, you’ve checked $_FILES and it was empty?

phpdeveloper
May 7, 2014 at 1:22 pm

Ryan,

I tried manually setting the content_type to “multipart/form-data” but it has no effect.

Like this:
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data'));
Yes, $_FILES is empty.

Ryan Sechrest
May 7, 2014 at 5:04 pm

OK, so I setup a local environment, running PHP 5.5.12 and cURL 7.19.7, setup the following test:
$cfile = new CURLFile('/var/www/html/image.jpg', 'image/jpeg', 'image.jpg');

$params = array(
  "userid_id" => "123",
  "file" => $cfile,
  "name" => "test_file",
  "gallery" => "myalbum"
);

$uri = 'http://server02.local.vps/upload.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt($ch, CURLOPT_POST, true );
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);

$response_data = curl_exec($ch);

echo print_r(curl_getinfo($ch), true);

echo $response_data;

curl_close( $ch );
unset( $ch );
Where upload.php contained the following:
echo print_r($_POST, true);
echo print_r($_FILES, true);
And I got the following output:
Array
(
    [url] => http://server02.local.vps/upload.php
    [content_type] => text/html
    [http_code] => 200
    [header_size] => 227
    [request_size] => 199
    [filetime] => -1
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.04464
    [namelookup_time] => 0.0001
    [connect_time] => 0.000162
    [pretransfer_time] => 0.000163
    [size_upload] => 52700
    [size_download] => 314
    [speed_download] => 7034
    [speed_upload] => 1180555
    [download_content_length] => 314
    [upload_content_length] => 52700
    [starttransfer_time] => 0.001441
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => 127.0.0.1
    [certinfo] => Array
        (
        )

)

Array
(
    [userid_id] => 123
    [name] => test_file
    [gallery] => myalbum
)

Array
(
    [file] => Array
        (
            [name] => image.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpYLjbUA
            [error] => 0
            [size] => 52207
        )

)
Give the code a try and see if you get a different result. If this doesn’t work, make sure image permissions are correct, try a different (perhaps smaller) image, and make sure PHP can write to the tmp directory. I’m basically thinking it’s something other than your code.

PS: I noticed myimge.jpg was misspelled, so make sure that there is a file by that name (missing the letter ‘a’ in image).

phpdeveloper
May 8, 2014 at 9:55 am

Ryan,

Thanks for your help.

Your code looks same as mine and yetl I get different result. I am wondering if it has to do with the framework I am using. I am using Kohana 3.3 and capturing the request object in my api to get the post variables. I am going to see if there is some issue on that front. Please let me know if you have any experience with Kohana.

phpdeveloper
May 8, 2014 at 11:44 am

Ryan,

I started from the beginning, deleted all my code. Then copied your code and tested it, and it worked.

Looks like I had something in the code which was making it not work right. Not sure what it was though.
Thanks a lot for your help.

Ryan Sechrest
May 8, 2014 at 11:49 am

Great, glad to hear (minus the part that we don’t know what actually caused the issue).

Brian Anderson
December 14, 2014 at 4:50 pm

Ryan,
I hope you’re still monitoring this thread.
Thank you in advance for reviewing this!
I’m feeling pretty stupid about this and am willing to pay for somebody else’s smarts on a consulting deal.

I’m trying to write a receiver for the following curl command line:

curl -d @it20010002.xml -H “Content-Type: application/xml” https://upofx.proautomation.com/w2uploadtest.php > it20010002e.xml

Here is my sender code
$resource = curl_init();
curl_setopt($resource, CURLOPT_URL, $url);
// curl_setopt($resource, CURLOPT_HTTPHEADER, $header);
curl_setopt($resource, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($resource, CURLOPT_POST, 1);
curl_setopt($resource, CURLOPT_POSTFIELDS, $params);
$result = curl_exec($resource);
var_dump(curl_getinfo($resource));
echo “result=”;
var_dump($result);
curl_close($resource);
echo “——result=$result—–“;
Here is my receiver code:
<?php

echo "inside w2uploadtest";
$uploaddir = realpath(‘ . /’) . ‘ / ’;
$uploadfile = $uploaddir . basename($_FILES[‘file’][‘name’]);
echo ‘Hereiswhathappens’;
if (move_uploaded_file($_FILES[‘file’][‘tmp_name’], $uploadfile))
{
    echo “Fileisvalid, and wassuccessfullyuploaded . \n”;
}
else
{
    echo “Possiblefileuploadattack!\n”;
}
echo ‘Hereissomemoredebugginginfo:’;
print_r($_FILES);
echo “\n\n”;
print_r($_POST);
echo “returningback”;

?>
my variables returned are:
array (size=26)
  ‘url’ => string ‘https://upofx.proautomation.com/w2uploadtest.php’ (length=48)
  ‘content_type’ => null
  ‘http_code’ => int 0
  ‘header_size’ => int 0
  ‘request_size’ => int 0
  ‘filetime’ => int -1
  ‘ssl_verify_result’ => int 0
  ‘redirect_count’ => int 0
  ‘total_time’ => float 0.109
  ‘namelookup_time’ => float 0
  ‘connect_time’ => float 0.031
  ‘pretransfer_time’ => float 0
  ‘size_upload’ => float 0
  ‘size_download’ => float 0
  ‘speed_download’ => float 0
  ‘speed_upload’ => float 0
  ‘download_content_length’ => float -1
  ‘upload_content_length’ => float -1
  ‘starttransfer_time’ => float 0
  ‘redirect_time’ => float 0
  ‘redirect_url’ => string ” (length=0)
  ‘primary_ip’ => string ‘50.246.192.200’ (length=14)
  ‘certinfo’ =>array (size=0)empty
  ‘primary_port’ => int 443
  ‘local_ip’ => string ‘192.168.0.44’ (length=12)
  ‘local_port’ => int 59452

Ryan Sechrest
December 15, 2014 at 3:08 am

Hi Brian,

Can you describe what you’re trying to accomplish and what issue you’re running into?

Ikram
April 3, 2015 at 2:23 pm

Hi Ryan,

I am trying to use the PUT method to send 2 files to a REST API. I am using following code:
$sURL = "http://some_valid_url";
$header = array('Content-Type: multipart/form-data');
$fields = array(
  'file1' => '@' . $xmlFilePath,
  'file2' => '@' . $fileFilePath
);

$resource = curl_init();
curl_setopt($resource, CURLOPT_URL, $sURL);
curl_setopt($resource, CURLOPT_HTTPHEADER, $header);
curl_setopt($resource, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($resource, CURLOPT_PUT, 1);
curl_setopt($resource, CURLOPT_POSTFIELDS, $fields);

$result = curl_exec($resource);
curl_close($resource);
print_r($result); //print response
The server is generating error.
Error: {"Message":"Expected MIME multipart request."}
Please, guide

Jignesh Boricha
June 17, 2015 at 9:14 am

Hi Ryan,

Thank you for your brief explanation. It has saved my day. My team mates were having so many issues but your post which I have found saved them a lot. We are very thankful to you.

Regards.
Thank you

Daisy
September 14, 2015 at 8:58 pm

Thank you very much. You’re amazing. It works as expected.

hitesh
February 8, 2018 at 12:25 am

Hii Sir

Need to help to me

In Send file Successfully in HTTP ” POST ” request
But it’s not work in ” PUT ”
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, “PUT”);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));
can you let me know
How to send file in HTTP ” PUT” method
and How to to get it on server side.

Thank you

vijaysinh Parmar
August 27, 2019 at 1:05 pm

I am trying to make code for download file into browser, my data is in db is uploaded as binary file using upload functionality.. I am able to download files like .ppt, .txt, .doc but image files are not opening after downloading.. Can any one please help?

Viper503
April 22, 2020 at 12:02 pm

Thank you so much man, the binary transfer option was missing in my code, and that got me stuck for about 3 hours until i came here. I finally connected to my api and succesfully got a response, i dont have words to thank you. Greetings!

Ryan Sechrest
April 22, 2020 at 12:05 pm

Haha, I’m glad that this post is still helpful after all these years.

Rifas Ali
May 10, 2020 at 4:16 am

Hi, I am trying to post an image to xero invoice. Its working from postman. I have the $filename. Also i have the file path were file exist.
$filename = “01.jpg”; // (same folder as in php file)
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => “https://api.xero.com/api.xro/2.0/Invoices/55c2f140-45a0-4f90-b5a0-8d650bddae07/Attachments/01.jpeg”,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => “”,
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => “PUT”,
  CURLOPT_POSTFIELDS => “”,
  CURLOPT_HTTPHEADER => array(
    “Content-Type: image/jpeg”,
    “Accept: application/json”,
    “Authorization: Bearer $token
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
How do i pass that to postfields? In postman it is working. But i think postman is sending file in binary format. Please help