50

I realise I can do this with CURL very easily, but I was wondering if it was possible to use file_get_contents() with the http stream context to upload a file to a remote web server, and if so, how?

halfer
  • 19,824
  • 17
  • 99
  • 186
Shabbyrobe
  • 12,298
  • 15
  • 60
  • 87

2 Answers2

101

First of all, the first rule of multipart Content-Type is to define a boundary that will be used as a delimiter between each part (because as the name says, it can have multiple parts). The boundary can be any string that is not contained in the content body. I will usually use a timestamp:

define('MULTIPART_BOUNDARY', '--------------------------'.microtime(true));

Once your boundary is defined, you must send it with the Content-Type header to tell the webserver what delimiter to expect:

$header = 'Content-Type: multipart/form-data; boundary='.MULTIPART_BOUNDARY;

Once that is done, you must build a proper content body that matches the HTTP specification and the header you sent. As you know, when POSTing a file from a form, you will usually have a form field name. We'll define it:

// equivalent to <input type="file" name="uploaded_file"/>
define('FORM_FIELD', 'uploaded_file'); 

Then we build the content body:

$filename = "/path/to/uploaded/file.zip";
$file_contents = file_get_contents($filename);    

$content =  "--".MULTIPART_BOUNDARY."\r\n".
            "Content-Disposition: form-data; name=\"".FORM_FIELD."\"; filename=\"".basename($filename)."\"\r\n".
            "Content-Type: application/zip\r\n\r\n".
            $file_contents."\r\n";

// add some POST fields to the request too: $_POST['foo'] = 'bar'
$content .= "--".MULTIPART_BOUNDARY."\r\n".
            "Content-Disposition: form-data; name=\"foo\"\r\n\r\n".
            "bar\r\n";

// signal end of request (note the trailing "--")
$content .= "--".MULTIPART_BOUNDARY."--\r\n";

As you can see, we're sending the Content-Disposition header with the form-data disposition, along with the name parameter (the form field name) and the filename parameter (the original filename). It is also important to send the Content-Type header with the proper MIME type, if you want to correctly populate the $_FILES[]['type'] thingy.

If you had multiple files to upload, you just repeat the process with the $content bit, with of course, a different FORM_FIELD for each file.

Now, build the context:

$context = stream_context_create(array(
    'http' => array(
          'method' => 'POST',
          'header' => $header,
          'content' => $content,
    )
));

And execute:

file_get_contents('http://url/to/upload/handler', false, $context);

NOTE: There is no need to encode your binary file before sending it. HTTP can handle binary just fine.

netcoder
  • 66,435
  • 19
  • 125
  • 142
  • Wow, awesome answer. Will try this out tonight. – Shabbyrobe Nov 23 '10 at 13:20
  • 2
    That worked beautifully. Is there any chance you could also add to your answer how to add extra post fields with the upload? Also, is it possible to do multiple this way just by tacking on another boundary? – Shabbyrobe Nov 24 '10 at 03:42
  • Sure. For the POST fields, see my updated answer. As for your second question, I'm not sure what you mean. – netcoder Nov 24 '10 at 04:06
  • Thanks for the update. I just meant could I upload two files at once by tacking on another one of these: $content = "--".MULTIPART_BOUNDARY."\r\n". "Content-Disposition: form-data; name=\"form_field_2\"; filename=\"".basename($otherFileName)."\"\r\n". "Content-Type: image/jpeg\r\n\r\n". $otherFileContents."\r\n"; – Shabbyrobe Nov 24 '10 at 04:48
  • I corrected little typing errors. It is a very good solution ! – Macbric Sep 12 '13 at 14:01
  • Could anyone explain what the `name=\"foo\"\r\n\r\n"."bar\r\n";` part? Wouldn't it just be `foo=bar`? I'm very confused on that part. – bryan Feb 14 '15 at 04:33
  • @bryan: It's the equivalent to `foo=bar` but in the `multipart/form-data` format. – netcoder Feb 14 '15 at 14:24
  • Note: The boundary must be prefixed with "--" in the body but not in the head. This might save you the trouble that I had with my http request being invalid. – kosinix Oct 27 '15 at 04:34
  • @kosinix FYI it's already the case in the above answer. If you follow it as is, it will work – netcoder Oct 27 '15 at 04:46
  • 2
    What if the file you want to send is very large, is there no way of sending chunks of data? – MacWise May 20 '16 at 17:38
  • Just FYI, sending data as chunked is a completely different topic. You'll have to use the chunked transfer encoding for that one, and this question and answer do not describe that protocol and weren't meant to. – netcoder Feb 04 '21 at 05:26
-1

Or maybe you can just do :

$postdata = http_build_query(
array(
    'var1' => 'some content',
    'file' => file_get_contents('path/to/file')
)
);

$opts = array('http' =>
    array(
        'method'  => 'POST',
        'header'  => 'Content-Type: application/x-www-form-urlencoded',
        'content' => $postdata
    )
);

$context  = stream_context_create($opts);
$result = file_get_contents('http://example.com/submit.php', false, $context);

You'd change the '/path/to/file' to the appropriate path

infantry
  • 336
  • 1
  • 5
  • 15