38

I was wondering if it is possible to post a file - along with other form data - when the file is just a string?

I know that you can post a file that is already on the filesystem by prefixing the filepath with "@".

However I'd like to bypass creating a temporary file and send just the file as a string, but I am unsure how to construct the request using cURL in PHP.

Cheers

    $postFields = array(
        'otherFields'   => 'Yes'
        ,'filename'     => 'my_file.csv'
        ,'data'         => 'comma seperated content'
    );

    $options = array(
        CURLOPT_RETURNTRANSFER  => true
        ,CURLOPT_SSL_VERIFYPEER => false
        ,CURLOPT_SSL_VERIFYHOST => 1
        ,CURLOPT_POSTFIELDS     => $postFields
        ,CURLOPT_HTTPHEADER     => array(
            'Content-type: multipart/form-data'
        )
    );
gawpertron
  • 1,867
  • 3
  • 21
  • 37

2 Answers2

29

Should be possible: here's a form, posted through a browser (irrelevant fields omitted):

POST http://host.example.com/somewhere HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7da16b2e4026c
Content-Length: 105732

-----------------------------7da16b2e4026c
Content-Disposition: form-data; name="NewFile"; filename="test.jpg"
Content-Type: image/jpeg

(...raw JPEG data here...)
-----------------------------7da16b2e4026c
Content-Disposition: form-data; name="otherformfield"

content of otherformfield is this text
-----------------------------7da16b2e4026c--

So, if we build the POST body ourselves and set an extra header or two, we should be able to simulate this:

// form field separator
$delimiter = '-------------' . uniqid();
// file upload fields: name => array(type=>'mime/type',content=>'raw data')
$fileFields = array(
    'file1' => array(
        'type' => 'text/plain',
        'content' => '...your raw file content goes here...'
    ), /* ... */
);
// all other fields (not file upload): name => value
$postFields = array(
    'otherformfield'   => 'content of otherformfield is this text',
    /* ... */
);

$data = '';

// populate normal fields first (simpler)
foreach ($postFields as $name => $content) {
   $data .= "--" . $delimiter . "\r\n";
    $data .= 'Content-Disposition: form-data; name="' . $name . '"';
    // note: double endline
    $data .= "\r\n\r\n";
}
// populate file fields
foreach ($fileFields as $name => $file) {
    $data .= "--" . $delimiter . "\r\n";
    // "filename" attribute is not essential; server-side scripts may use it
    $data .= 'Content-Disposition: form-data; name="' . $name . '";' .
             ' filename="' . $name . '"' . "\r\n";
    // this is, again, informative only; good practice to include though
    $data .= 'Content-Type: ' . $file['type'] . "\r\n";
    // this endline must be here to indicate end of headers
    $data .= "\r\n";
    // the file itself (note: there's no encoding of any kind)
    $data .= $file['content'] . "\r\n";
}
// last delimiter
$data .= "--" . $delimiter . "--\r\n";

$handle = curl_init($url);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_HTTPHEADER , array(
    'Content-Type: multipart/form-data; boundary=' . $delimiter,
    'Content-Length: ' . strlen($data)));  
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
curl_exec($handle);

This way, we're doing all the heavy lifting ourselves, and trusting cURL not to mangle it.

Piskvor left the building
  • 91,498
  • 46
  • 177
  • 222
  • 1
    Originally misread the question and got rightfully downvoted for it. Rewrote into something that could work. – Piskvor left the building Jun 21 '10 at 16:12
  • 2
    I've constructed the request as Piskvor suggested and yes you can build the body yourself and it will work. Although you have to be 100% accurate with the EOLs. At the end of the "Content-Length" and "Content-Disposition" a double return is required. For the file after "Content-Type: text/plain" a double return is also required. The delimiter is also slightly different for the body values than that is decleared in the "Boundary". Each boundary line has two extra hyphens prepended to the begining. The last delimiter has two extra hypens at the begining and the end. – gawpertron Jun 22 '10 at 12:00
  • You know, you can also post a file by prepending the @ symbol to the full path of the file. http://www.php.net/manual/en/function.curl-setopt.php check the `CURLOPT_POSTFIELDS` section. – Chris Henry Feb 18 '11 at 05:43
  • 2
    @Chris Henry: Indeed. I suggest that you read the question; quote: "I know that you can post a file that is already on the filesystem by prefixing the filepath with "@". However I'd like to bypass creating a temporary file..." So, your solution to "how to do this without @/some/file/name ?" is "use @/some/file/name"? The mind, it boggles... – Piskvor left the building Feb 18 '11 at 08:48
  • @lifebest: "fix generate $delimiter" - how, specifically, was the delimiter generation broken? IIRC, the delimiter can be any ASCII string that's sufficiently "random" so it doesn't accidentally appear elsewhere in the data (the example I have posted is just an *example* of a valid delimiter), no need to kludge around with rand() and md5(); also, your method is unlikely to generate a delimiter with more entropy. Anyway, thanks for catching the syntax errors :) – Piskvor left the building Sep 06 '12 at 18:45
  • could you please provide a complete working example? I have an image and want to pass it as raw content, how should I do this? I tried myself, and received following exception from facebook: `{"error":{"message":"(#324) Requires upload file","type":"OAuthException","code":324}}` – Zain Shaikh Apr 28 '13 at 23:44
  • 8
    When populating `$postFields` at the end of the first `foreach` you are missing `$data .= $content . "\r\n";` otherwise the field content is not sent. – Cesar Jul 18 '13 at 17:27
  • 1
    Thanks, @Piskvor! A note on the "name=" in the Content-Disposition line. It should be set to the name of the element that would have gotten the "@" file in the array given to CURLOPT_POSTFIELDS. If you set the file in curl_setopts CurlFile with $params->file = "@myfilename.pdf", then you'll want name="file". I suspect this is the same as a tag's "name" in HTML. Setting it to be the same as the file name might not be what the server side wants. – Mark Kasson Sep 18 '15 at 14:02
17

PHP has access to a temporary location "php://memory", which actually makes what you're trying to do fairly easy.

$fileHandle = fopen('php://memory', 'rw');
fwrite($fileHandle, $content);
rewind($fileHandle);

$options = array(
    CURLOPT_RETURNTRANSFER  => true
    ,CURLOPT_SSL_VERIFYPEER => false
    ,CURLOPT_SSL_VERIFYHOST => 1
    ,CURLOPT_HTTPHEADER     => array(
        'Content-type: multipart/form-data',
    )
    ,CURLOPT_INFILE         => $fileHandle
    ,CURLOPT_INFILESIZE     => strlen($content)
);
hakre
  • 193,403
  • 52
  • 435
  • 836
erik.wiffin
  • 183
  • 5
  • Will this allow for the rest of the postfields that are being sent with the file? – Neil Aitken Jun 21 '10 at 16:16
  • 4
    It definitely creates a file handler and I gave it a try, but I think CURLOPT_INFILE and CURLOPT_INFILESIZE are used to PUT files, No files showed up in the $_FILES array. – gawpertron Jun 21 '10 at 18:32
  • The code in the question can upload multiple files at once. Can `PUT` with `INFILE` upload multiple files too? – Daniel W. Jul 10 '13 at 13:31
  • Why do you put comma at the start of lines? It's PHP, not C, you can just end all items with comma. `[123, 456,]` – Alex P. Feb 01 '21 at 20:36
  • @AlexP. because in 2010 php did not allow having comma after the last array element. They added this 'feature' some years later – Andrew Dec 16 '22 at 12:41
  • How does this specify the name of the file upload field ("data" in question)? – hakre Jul 12 '23 at 09:38