uploading a local document using php - php

I'm trying to test a rest api for upload a local file to a https url. I am currently using curl to do it. The problem is that my POST in curl is getting converted into PUT (http://curl.haxx.se/docs/manpage.html) mentions the same.
To test from command line I'm using the following:
curl -H "Content-Type: application/json" -H "Authorization:Bearer XXXXXXXXXXXXXXXYYYYYzzzzzz" -H "Accept-Version: ~1" -H "Except: 100-continue" -H "Accept-Version: ~1 " -T "somefile.pdf" "https://abc-xyz.co/docs"
Output:
{"code":"BadMethod","message":"/docs does not support PUT"}
my php code:
$url = '"https://abc-xyz.co/docs';
$filepath = '/Users/me/Documents/somefile.pdf'; //mac path
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt($ch, CURLOPT_HTTPHEADER,
array('Authorization: Bearer XXXXXXXXXXXXXXXYYYYYzzzzzz', 'Accept-Version: ~1', 'Except: 100-continue');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
$file = "#".$filepath;
curl_setopt($ch, CURLOPT_POSTFIELDS,$file);
$response = curl_exec($ch);
if(!CURL_ERROR($ch))
{
echo"\n The output is:".$response;
}
else
{
echo "\nUpload failed ".curl_error($ch);
exit;
}
curl_close($ch);
Output: I get parse error.
Can some one educate me with 3 things:
Can I use curl to do POST for https? if yes, how? and if no what should my approach be?
How to pass the file path name in my php? I tried "#".$filepath too but it still doesn't like it.
If I want to restrict my file upload type to be only pdf should i be using mime type? (application/pdf????)
Thanks,
DR

From the commandline, see post fileupload with curl.
From PHP, see the manual
The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with # and use the full path. The filetype can be explicitly specified by following the filename with the type in the format ';type=mimetype'. This parameter can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data. As of PHP 5.2.0, value must be an array if files are passed to this option with the # prefix.
curl_setopt($ch, CURLOPT_POSTFIELDS,array('someuploadname'=> "#".$filepath));

Related

Cannot reproduce simple POSTMAN query into php curl

I can send a POST request successfully with POSTMAN and when I turn the request to code I get:
curl --location --request POST 'https://webservice.apiCommerce/import' \
--form 'id=1186' \
--form 'image=#/C:/collection/img/ecomm/01.jpg'
Now, I am trying the same thing with PHP curl and I don't get the expected result.
$imagePath = "./img/$fileName";
$post = [
"id" => $id,
"image" => '#'.$imagePath
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_URL, $url);
$response = curl_exec($ch);
$payload = json_decode($response, true);
The request gets sent, but the image doesn't get uploaded correctly. Is there a reason why there's a mismatch? I can't figure out what's wrong. I tried removing the header and so forth, but it doesn't seem to make any difference at all. I even hard-coded the img path and it doesn't seem to work.
The first request is succeeding because it is being encoded as multipart/form-data, as uploads should be. As the curl --help menu explains, the --form name=value command specifies multipart MIME data, as opposed to the --data option and its variants. In the second request, however, you are specifying the content type as application/x-www-urlencoding. This encoding is usually preferred, but will not in this case do what you want.
From the MDN web docs:
Non-alphanumeric characters in both keys and values are percent encoded: this is the reason why this type is not suitable to use with binary data (use multipart/form-data instead)

How do I make this Salesforce cURL request in PHP?

I'm using PHP to make cURL requests to Salesforce's REST API.
I've got most of the requests I need to make figured out, but I'm not sure how to convert the following curl command on the following Salesforce API page to a cURL request in PHP:
curl https://yourInstance.salesforce.com/services/data/v20.0/sobjects/Account/customExtIdField__c/11999 -H "Authorization: Bearer token" -H "Content-Type: application/json" -d #newrecord.json -X PATCH
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_upsert.htm
I know that that -H option is for headers, which I'm handing with the following:
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
And I think that the -X PATCH part can be accomplished with the following PHP cURL option:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
However, how do I handle the -d #newrecord.json part in PHP cURL?
Thanks.
You should POST the json
$post = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS,$post);
What you are doing with -d #newrecord.json is uploading a (JSON) file for the endpoint to use. To replicate this in PHP, you need to pass an array with a file element to CUROPT_POSTFIELDS, like this:
$file = [
"file" => "#newrecord.json";
];
curl_setopt($ch, CURLOPT_POSTFIELDS, $file);
Make sure to give the correct file path. You can use realpath() to aid with this.
Alternatively, you could just send the JSON encoded data:
$data = [
"site" => "Stack Overflow",
"help" => true,
];
$jsonData = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
Don't forget to set your Content-Type: application/json header!
Lastly, your guess about the PATCH request is correct:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');

Is it possible to use cURL to stream upload a file using POST?

Or does PHP not allow this? I have read it is possible using PUT but the server is expecting POST only.
cURL has already support for streams, try curl --help | grep binary and you will get:
--data-binary DATA HTTP POST binary data (H)
An example:
curl -v -XPOST http://example:port/path --data-binary #file.tar
-H "Content-Type: application/octet-stream"
Yes it is possible to stream upload a file using POST file uploads with cURL. This is the default and you need to provide the filename of the file(s) you would like to stream in form of strings.
// URL on which we have to post data
$url = "http://localhost/tutorials/post_action.php";
// Any other field you might want to catch
$post_data['name'] = "khan";
// File you want to upload/post
$post_data['file'] = "#c:/logs.log";
// Initialize cURL
$ch = curl_init();
// Set URL on which you want to post the Form and/or data
curl_setopt($ch, CURLOPT_URL, $url);
// Data+Files to be posted
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
// Pass TRUE or 1 if you want to wait for and catch the response against the request made
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// For Debug mode; shows up any error encountered during the operation
curl_setopt($ch, CURLOPT_VERBOSE, 1);
// Execute the request
$response = curl_exec($ch);
// Just for debug: to see response
echo $response;
Apart from that default method, it is not possible to use cURL to stream upload a file using the POST method.
Command to upload a binary file
curl --location --request POST 'http://localhost:8080/hello/read' \
-H "Content-Type: application/octet-stream" \
--data-binary '#/Users/krishna/Documents/demo.bin'
command to upload a text file
curl --location --request POST 'http://localhost:8080/hello/read' \
--header 'Content-Type: text/plain' \
--data-binary '#/Users/krishna/Documents/demo.txt'
Set the Content-Type as per the file content.

How to set multiple Content-Types?

How to set multiple Content-Types? I have to pass an array to request body. That array contains a text and an image in binary.
curl_setopt($apiCon, CURLOPT_POSTFIELDS, array(
'status' => rawurlencode($status),
'photo' => $photoInBinary
));
curl_setopt($apiCon, CURLOPT_HTTPHEADER, array(
'Host: api.mixi-platform.com',
'Authorization: OAuth ' . $accessToken,
'Content-Type: multipart/form-data'
));
The problem is the host doesn't understand the format of the image, so I need to pass more content type 'image/jpg' but I don't know where to put it.
The above code works but it posts only the status.
Update:
Ok, my goal is to post a status with a photo to a social network page.
For more information, read this:
http://developer.mixi.co.jp/en/connect/mixi_graph_api/mixi_io_spec_top/voice-api/#toc-10
This is my code. It works but post only the status, not photo.
$apiCon = curl_init(self::API_POST_STATUS_WITH_PHOTO);
curl_setopt($apiCon, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($apiCon, CURLOPT_RETURNTRANSFER, true);
curl_setopt($apiCon, CURLOPT_POST, true);
curl_setopt($apiCon, CURLOPT_POSTFIELDS, array('status' => rawurlencode($status), 'photo' => $photoInBinary));
curl_setopt($apiCon, CURLOPT_HTTPHEADER, array('Host: api.mixi-platform.com', 'Authorization: OAuth ' . $accessToken, 'Content-Type: multipart/form-data'));
$result = curl_exec($apiCon);
curl_close($apiCon);
First try not encoding your status field (i.e., remove rawurlencode). This is a double-encode and possibly this is why your host is complaining.
(As an aside, you don't need to set the content-type header explicitly; CURL will do that.)
If this isn't enough, you either have to rearrange your request to use CURL's magic file upload mechanism, or you have to construct the entire multipart/form-data string by yourself.
This is how CURL's magic file mechanism works (from the documentation to CURLOPT_POSTFIELDS:
The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with # and use the full path. The filetype can be explicitly specified by following the filename with the type in the format ';type=mimetype'. This parameter can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data. As of PHP 5.2.0, value must be an array if files are passed to this option with the # prefix.
The only way to pass the content-type of a specific field in a multipart/form-data POST using CURL is with this syntax for the field value: #filepath;type=mime/type.
If your $photoInBinary started life in a file, simply pass the filename in the above format to CURL instead of opening the file and reading in the data.
However, if you created the photo yourself in memory, you need to write that to a temporary file. Below is how you might do that:
function curlFileUploadData($data, $type='') {
// $data can be a string or a stream
$filename = tempnam(sys_get_temp_dir(), 'curlupload-');
file_put_contents($filename, $data);
$curlparam = "#{$filename}";
if ($type) {
$curlparam .= ";type={$type}";
}
return array($curlparam, $filename);
}
list($curlparam, $tmpfile) = curlFileUploadData('thedata', 'image/bmp');
// You can see the raw bits CURL sends if you run 'nc -l 9999' at a command line first
$c = curl_init('http://localhost:9999');
curl_setopt($c, CURLOPT_POSTFIELDS, array('status' => 'good', 'photo' => $curlparam));
curl_exec($c);
curl_close($c);
unlink($tmpfile); // REMEMBER TO DO THIS!!!!
Note that CURL will set the filename parameter on the multipart file upload. Be sure the host doesn't use this for anything important. As far as I know there's no way to override the filename CURL sends--it will always be exactly what was given.
If you are not willing to create a temporary file and you must use CURL, you will have to create the entire multipart/form-data body as a string in memory, and give that to CURL as a string to CURL_POSTFIELDS, and manually set the content-type header to multipart/form-data. This is left as an exercise for the reader. By this time you should consider using the HTTP extension instead, or even fopen with stream_context_create() to set the http method and headers.

PHP CURL: Manually setting the content-length header

Say I upload a file with PHP, CURL:
$postData = array();
$postData['file_name'] = "test.txt";
$postData['submit'] = "UPLOAD";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData );
Now assume I have to manually set the content-length header.
$headers=array(
"POST /rest/objects HTTP/1.1",
'accept: */*',
"content-length: 0" //instead of 0, how could I get the length of the body from curl?
)
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //set headers
$response = curl_exec($ch);
How would I measure the size of the body? (just specifying the filesize as content length doesn't seem to work)
In another example what if the body contains data that is not an actual file. (manually set by postfields) In that situation how would I get the content length of the body?
Thanks for any light shed on this, it appears to be a tough issue.
To get the length of a post body, try formatting the fields int a GET style string (aka param1=value1&param2=value2) then setting that string as the CURL_POSTFIELDS with curl_setopt. An array does not have to be supplied. You can simply use strlen() to get the value to use for the content-length header.
If you are posting a file (or files) in addition to other fields, as you appear to be in the example above, you have to supply the value for the file as #/path/to/file, then get the filesize in bytes and add that to the total content-length.
So for the above example, assuming the file test.txt is in the /test dir of your server, the post value string would be file_name=#/test/text.txt&submit=UPLOAD. You MUST url_encode this string as well, before you assign it as the curl post value. To get the content length you get the length of that string (post url-encoding) and add it to the filesize of /test/test.txt.
That sounds wrong. The data generated will include sub-headers and Content-Length needs to also include those sub-headers. And since the sub-headers include a boundary, may include this and that data, this whole thing cannot work (the size cannot be specified without knowing exactly what the complete request is going to be.)
Actually, the only one that can compute the size, from what I can see is cURL itself. But it doesn't do it 8-P

Categories