views:

78

answers:

2

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'
        )
    );
+3  A: 

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
Originally misread the question and got rightfully downvoted for it. Rewrote into something that could work.
Piskvor
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
A: 

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

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

$options = array(
    CURLOPT_RETURNTRANSFER  => true
    ,CURLOPT_SSL_VERIFYPEER => false
    ,CURLOPT_SSL_VERIFYHOST => 1
    ,CURLOPT_HTTPHEADER     => array(
        'Content-type: multipart/form-data'
    )
    ,CURLOPT_INFILE         => $fh
    ,CURLOPT_INFILESIZE     => strlen($content)
);
erik.wiffin
Will this allow for the rest of the postfields that are being sent with the file?
Neil Aitken
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