1

I've created an endpoint in Paw (and/or Postman) for handling file uploads. It works great!

enter image description here

It's pretty simple. And in my application I can do something like:

echo file_get_contents('php://input');

And it will print out an encoded representation of the image/file (a cute kitten).

The problem is, I cannot seem to reproduce this behaviour using just cURL (Current application uses Guzzle).

When I try something like:

$target_url = 'http://my-document-handling-service.net:8000/documents';

$request = curl_init($target_url);

curl_setopt($request, CURLOPT_POST, true);
curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
curl_setopt($request, CURLOPT_POSTREDIR, 3);

curl_setopt(
    $request,
    CURLOPT_POSTFIELDS,
    array(
      'file' => '/Users/xxxxxxx-xxxx/Documents/kitten-test-upload-image-1.jpg'
    ));

echo curl_exec($request);

curl_close($request);

The body (file_get_contents()) is always empty. I don't really understand cURL I've always just used Guzzle - but now I have to use cURL and I can't get the body.

What am I doing wrong?

mikelovelyuk
  • 4,042
  • 9
  • 49
  • 95
  • Maybe try changing that to `echo file_get_contents($_POST['file']);` .... although you should probably do some filtering and whitelisting and checks on that value BEFORE tossing it into `file_get_contents`... just saying. Thats scary security risk stuff. – IncredibleHat Mar 23 '18 at 14:25
  • Then I get `Warning: file_get_contents(/Users/mike/code/local-dev/kitten.jpg): failed to open stream: No such file or directory in ... (my docker filepath)`. So it's like it doesn't send the file - it's just sending the name of the file? – mikelovelyuk Mar 23 '18 at 14:34
  • Right, you are sending a 'path' to a file. So I'm assuming that path does not exist on the `my-document-handling-service.net`. Which in turn tosses that error. So you are trying to send the ACTUAL file, over... through curl? – IncredibleHat Mar 23 '18 at 14:37
  • Add `'@' .` in front of your path... `'file' => '@'.'/Users/xxxxxxx-xxxx/Documents/kitten-test-upload-image-1.jpg'` – IncredibleHat Mar 23 '18 at 14:38
  • Uploading files using php curl: https://stackoverflow.com/a/15200804/2960971 ... http://code.iamkate.com/php/sending-files-using-curl/ – IncredibleHat Mar 23 '18 at 14:41
  • Although doing that means you have to access the file with `$_FILES['file']['tmp_name']` instead of using `$_POST['file']` ... `php://input` won't contain the file in the same way as guzzle sent it . (I'm not supplying any of this as an answer, because I have no way to actually write up a test in this fashion to know 100% if what I'm saying is true lol!) – IncredibleHat Mar 23 '18 at 14:46
  • Appreciated. I've seen the iamkate blog from earlier. Still has the same result of empy/null. – mikelovelyuk Mar 23 '18 at 15:05

2 Answers2

1

(too long for a comment, the existing answer is very close to being correct, albeit bugged and won't work, but doesn't say why your origianl code failed) - when you give CURLOPT_POSTFIELDS an array, it encodes the input in multipart/form-data-format, and PHP parses multipart/form-data into $_POST (and for file uploads, $_FILES), and empties php://input in the process (it does this for any encoding it has built-in support for, and as of writing, there are 2, application/x-www-form-urlencoded, andmultipart/form-data`), and your code

curl_setopt(
    $request,
    CURLOPT_POSTFIELDS,
    array(
      'file' => '/Users/xxxxxxx-xxxx/Documents/kitten-test-upload-image-1.jpg'
    ));

sends the variable file with the value '/Users/xxxxxxx-xxxx/Documents/kitten-test-upload-image-1.jpg' to the server in the multipart/form-data format, thus your value is actually in $_POST['file'], but if you want to send the raw image directly, no encoding, so you can use php://input on the target server, use CURLOPT_INFILE or CURLOPT_POSTFIELDS, for example

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_INFILE,($fp=fopen('abc.txt','rb')));
curl_setopt($ch, CURLOPT_INFILESIZE,123);
(...)
curl_exec($ch);
fclose($fp);

or

curl_setopt($ch,CURLOPT_POSTFIELDS,file_get_contents("abc.txt"));
curl_setopt($ch,CURLOPT_HTTPHEADER,array('Content-Type: application/octet-stream'));
curl_exec($ch);

(note that the second method will use more ram, it will put the entire file in memory prior to uploading, while the first method will send the data in chunks, making it possible to upload any size files without running out of ram, unlike the second method)

hanshenrik
  • 19,904
  • 4
  • 43
  • 89
  • First one doesn't work but second does work with `file_get_contents('php://input')`. There was considerable latency though so I don't think it's suitable for this. Perhaps my whole approach is wrong I was trying to implement the ideas put forward here for my api: https://philsturgeon.uk/api/2016/01/04/http-rest-api-file-uploads/ – mikelovelyuk Mar 23 '18 at 15:16
  • 1
    @mikelovelyuk my recommendation is, for now, just support the standard multipart/form-data format, it will save you a lot of headache for the initial implementation of whatever you're doing. and perhaps in the future when it's time to micro-optimize your code, you can revisit the raw upload method (because it does have lower overhead, but is more difficult to support efficiently, neither nginx nor apache nor php likes it when you send big files this way, but `multipart/form-data` is well supported just about everywhere.) – hanshenrik Mar 23 '18 at 15:26
0

Normally CURL will send the Postfields encoded with application/x-www-form-urlencoded or multipart/form-data. (Details).

What you need is a RAW Encoding in the POST Data. I haven't test it yet. But it would be something like:

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
$res=fopen("abc.txt","rb"));   //CURLOPT_INFILE needs a stream resource
curl_setopt($ch, CURLOPT_INFILE, $res);
curl_setopt($ch, CURLOPT_INFILESIZE,123);
...
curl_exec($ch);
...
fclose($res);
powerpete
  • 2,663
  • 2
  • 23
  • 49
  • I'd be interested if this works. I am unable to test it either... but it sounds promising. – IncredibleHat Mar 23 '18 at 14:48
  • 1
    this would work if his code wasn't bugged, INFILE requires a FILE resource, not a string. `curl_setopt($ch, CURLOPT_INFILE,fopen("abc.txt","rb"));` should work, but will leak memory. `curl_setopt($ch, CURLOPT_INFILE,($fp=fopen("abc.txt","rb"))); curl_exec($ch); fclose($fp);` should also work, and not leak any memory. – hanshenrik Mar 23 '18 at 14:50
  • 1
    That's correct hanshenrik. Thank you for your Hint. I corrected the answer. – powerpete Mar 23 '18 at 14:59
  • I've tried this (before and after edit). Still the file_get_contents, $_FILES, $_POST etc is all NULL or just an empty string. – mikelovelyuk Mar 23 '18 at 15:03