POST a file string using cURL in PHP?
Asked Answered
A

2

39

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'
        )
    );
Astonishing answered 21/6, 2010 at 15:28 Comment(3)
@Vin Definitely not a duplicate. This is a nice question. I don't know of any way to add part headers, as would be required here, short of manually crafting all the POST data.Pylle
@Artefacto: Right, I didn't understand the question correctlyOverreach
by raw data do you mean file_get_contents($path_to_file); ?Homologize
H
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.

Hangup answered 21/6, 2010 at 15:38 Comment(9)
Originally misread the question and got rightfully downvoted for it. Rewrote into something that could work.Hangup
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.Astonishing
You know, you can also post a file by prepending the @ symbol to the full path of the file. php.net/manual/en/function.curl-setopt.php check the CURLOPT_POSTFIELDS section.Sheelagh
@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...Hangup
@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 :)Hangup
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}}Plummy
When populating $postFields at the end of the first foreach you are missing $data .= $content . "\r\n"; otherwise the field content is not sent.Kelci
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.Prefer
I found that setting the name for the file Content-Disposition form-data to "files[]" allows a receiving php script to access the files using the $_FILES global variable. In other words, changing line: $data .= 'Content-Disposition: form-data; name="' . $name . '";' . ' filename="' . $name . '"' . "\r\n"; to: $data .= 'Content-Disposition: form-data; name="files[]"; filename="' . $name . '"' . "\r\n"; allows $_FILES to work in a receiving php script.Klarrisa
S
18

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)
);
Selden answered 21/6, 2010 at 16:12 Comment(6)
Will this allow for the rest of the postfields that are being sent with the file?Pacifistic
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.Astonishing
The code in the question can upload multiple files at once. Can PUT with INFILE upload multiple files too?Memberg
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,]Brechtel
@AlexP. because in 2010 php did not allow having comma after the last array element. They added this 'feature' some years laterMissy
How does this specify the name of the file upload field ("data" in question)?Kesley

© 2022 - 2024 — McMap. All rights reserved.