How to display binary data from curl in php - php

I'm writing simple php proxy and I have trouble displaying png file, the output is
and it should be:
The images are opened in Notepad++. My php curl code look like this:
$ua = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
curl_setopt($ch, CURLOPT_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
$content = curl_exec($ch);
$info = curl_getinfo($ch);
header('Content-Type:' . $info['content_type']);
echo $content
I try with and without CURLOPT_BINARYTRANSFER the output is the same and the image is not displaying. How can I display the image?
EDIT: when I'm saving the data to the file and redirect using Location header the image is displayed correctly:
$file = fopen('proxy_tmp~', 'w');
fwrite($file, $content);
fclose($file);
header('Location: ' . DIR . 'proxy_tmp~');
EDIT 2: I had gzip compression, bu when I disabled it I have the same issue, when I open both files in Notepad++ one is DOS/Windows ANSI (original) and the other is DOS/Windows UTF-8 (the file opened by a script). When I open a file in Notepad and change encoding to ANSI and save the file, everything is ok.
EDIT 3: I think I did the same thing on GNU/Linux but without CURLOPT_BINARYTRANSFER option and it's working fine, here is my project https://github.com/jcubic/yapp. I've also test it on Windows 10 with Wamp and also work fine.

Here is how to send the file directly back to the user for download (uses your $content var):
$file_array = explode("\n\r", $content, 2);
$header_array = explode("\n", $file_array[0]);
foreach($header_array as $header_value) {
$header_pieces = explode(':', $header_value);
if(count($header_pieces) == 2) {
$headers[$header_pieces[0]] = trim($header_pieces[1]);
}
}
header('Content-type: ' . $headers['Content-Type']);
header('Content-Disposition: ' . $headers['Content-Disposition']);
echo substr($file_array[1], 1);
There is a full example here:
http://ryansechrest.com/2012/07/send-and-receive-binary-files-using-php-and-curl/

never use the 'w' mode. it means the "text mode", because the default is, unfortunately, text mode.
on Windows, "text mode" means: whenever you try to write an \n byte (ascii 10, newline) and it's not preceded by an \r byte (ascii 13, carriage-return), insert an \r byte before writing the \n byte. (it also means text mode on linux/modern MacOS, but on Linux/modern MacOS, the text mode does absolutely nothing and is treated the same way as binary mode. * not true for classic MacOS <=9 from <=2001, on Classic MacOS, text mode did weird shit like it does weird shit on Windows)
always use the binary mode, eg wb, to make your program portable across both Windows and Linux
(actually modern versions of PHP automatically convert it to binary mode when not specified, but this question was asked in 2013, and this is still a very important rule-of-thumb if you ever use any other language than PHP. if you actually want text mode, explicitly enable text mode with wt, but you pretty much never want text mode, it's a horrible thing, made worse by the fact that it's the default mode for the underlying fopen() C api.. it's so horrible in fact, that Linux never supported it at all, and Apple/MacOS dropped support in 2001 with the release of MacOS X.. AFAIK Windows is the last major OS to actually support it, somethingsomething backwards-compatibility )

Which php version are you using?
Are you above PHP version 5.1.3? Then the CURLOPT_BINARYTRANSFER will have no effect.
Source:
http://php.net/manual/en/function.curl-setopt.php
Looking at the file, you should add the following header to you're page:
header('Content-Type: image/png');

I have used following with basic header authentication.. used postman to get auth key
// key : > converted to basic auth
// Content type
header('Content-Type: image/jpeg');
$url = 'http://com.com.com/api/images/products/264/7971';
$headers = array(
'Content-Type: image/png',
'Authorization: Basic ##$%##$%##$%##$%##$%##$%' );
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
//curl_setopt($ch, CURLOPT_USERPWD, "$login:$password");
$file = curl_exec($ch);
curl_close($ch);
//echo($result);
// create image from output of the screen
$img = #imagecreatefromstring($file);
if ($img === false) {
echo $file;
exit;
}
$width = imagesx($img);
$height = imagesy($img);
$newWidth = 300;
$ratio = $newWidth / $width;
$newHeight = $ratio * $height;
$resized = #imagecreatetruecolor($newWidth, $newHeight);
if ($resized === false) {
echo $file;
exit;
}
$quality = 90;
if (#imagecopyresampled($resized, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height)) {
#imagejpeg($resized, NULL, $quality);
} else {
echo $file;
}

Use $content_arr = explode("\r\n\r\n",$content,2); to divide the content into headers and body, set the image/png header and then do echo trim($content_arr[1]); to get rid of spaces or blank lines that prevent the browser from being able to read the image.

In my case adding Content-Length header fixed problems with binary files proxy.

I have this issue, even if I read png content from disk (file_get_contents function). One php source file's encoding was UTF-8 with signature and that was the source of "my" problem.
So I remove the signature with Notepad2. There is option to change file encoding.

Related

Preventing hotlinking on Amazon S3 with PHP

In my site i am doing a image protection section to cut the costs of Amazon S3. So as a part of that i have made anti hot-linking links for images using php (to the best of my understanding).
<video src="/media.php?id=711/video.mp4"></video>
Then my media.php file looks like:
if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] != 'example.com')
{
header('HTTP/1.1 503 Hot Linking Not Permitted');
header("Content-type: image/jpeg");
readfile("http://example.com/monkey.jpg");
exit;
}
$url = 'https://s3.example.com';
$s3 = "$url/$file";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $s3);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$results = explode("\n", trim(curl_exec($ch)));
foreach($results as $line) {
if (strtok($line, ':') == 'Content-Type') {
$parts = explode(":", $line);
$mime = trim($parts[1]);
}
}
header("Content-type: $mime");
readfile($s3);
To make it less obvious, I have set up a rewrite to route /711/video.mp4 into cdn/711/video.mp4. That way, it doesn't look like there is an PHP script.
RewriteRule ^cdn/([0-9a-zA-Z_]+)/([0-9a-zA-Z-\w.-]+)([\w.-]+)?$ media\.php\?id=$1/$2 [QSA,L]
This above system is working fine but the issue is when i load image directly the loading time of the image is 237ms and when the image is loaded through this PHP script the loading time is 1.65s
I have shared the entire code i have, so if there is any chance of improvement in it please guide me in the right direction so i can make changes accordingly.
The reason your script takes longer than querying s3 directly is that you've added a lot of overhead to the image request. Your webserver needs to download the image and then forward it to your users. That is almost definitely your biggest bottleneck.
The first thing I would suggest doing is using the S3 API. This still uses curl() under the hood but has optimizations that should nominally increase performance. This would also allow you to make your s3 bucket "private" which would make obscuring the s3 url unnecessary.
All of that said, the recommended way to prevent hotlinking with AWS is to use cloudfront with referrer checking. How that is done is outlined by AWS here.
If you don't want to refactor your infrastructure, the best way to improve performance is likely to implement a local cache. At its most basic, that would look something like this:
$cacheDir = '/path/to/a/local/directory/';
$cacheFileName = str_replace('/', '_', $file);
if (file_exists($cacheDir . $cacheFileName)){
$mime = mime_content_type($cacheDir . $cacheFileName);
$content = file_get_contents($cacheDir . $cacheFileName);
} else {
$url = 'https://s3.example.com';
$s3 = "$url/$file";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $s3);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$results = explode("\n", trim(curl_exec($ch)));
foreach($results as $line) {
if (strtok($line, ':') == 'Content-Type') {
$parts = explode(":", $line);
$mime = trim($parts[1]);
}
}
$content = file_get_contents($s3);
file_put_contents($cacheDir . $cacheFileName, $content);
}
header("Content-type: $mime");
echo $content;
This stores a copy of the file locally so that the server does not need to download it from s3 every time it is requested. That should reduce your overhead somewhat, though it will not do as well as a purely AWS based solution. With this solution you'll also have to add ways of cache-breaking, periodically expiring the cache, etc. Just to reiterate, you shouldn't just copy/paste this into a production environment, it is a start but is more a proof of concept than production ready code.

Show on-the-fly PDF (Content-type:application/pdf) in Laravel Blade view

I've created the DHL label creation script in Laravel and get the generated PDF, in the Controller. The "$image" is created based on the user input and supplied by DHL API as on the fly. In general (in normal PHP script, not in Laravel), we do the following to show the PDF as I see this is working fine:
header("Content-type:application/pdf");
header("Content-Disposition:inline;filename=label_123demo.pdf");
echo $image;
So, in the controller file, I have written the code as below:
$data['image'] = $image; // generated image from DHL API
return view('Administrator.shipments.dhl-post', $data);
And in the Blade file, I wrote the following:
<div style="border: 2px solid #B0AFBE; width:800px;">
#php
header("Content-type:application/pdf");
header("Content-Disposition:inline;filename=label_123demo.pdf");
echo $image;
#endphp
</div>
But it shows nothing.
The DHL Label creation script is below:
$message_ref = '';
for ($i=0; $i< 30; $i++)
{
$message_ref .= rand(0, 9);
}
$message_time = date("Y-m-d") . "T" . date("H:i:sP");
$ab_date = date("Y-m-d", strtotime("+1 day"));
$query =<<<EOT
<?xml version="1.0" encoding="ISO-8859-1"?>\n
<req:ShipmentValidateRequest xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com ship-val-req.xsd">\n
<Request>\n
<ServiceHeader>\n
<MessageTime>$message_time</MessageTime>\n
<MessageReference>$message_ref</MessageReference>\n
<SiteID>XXXX</SiteID>\n
<Password>XXXX</Password>\n
</ServiceHeader>\n
</Request>\n
<RequestedPickupTime>N</RequestedPickupTime>\n
<NewShipper>N</NewShipper>\n
<LanguageCode>en</LanguageCode>\n
<PiecesEnabled>Y</PiecesEnabled>\n
<Billing>\n
<ShipperAccountNumber>XXXXX</ShipperAccountNumber>\n
<ShippingPaymentType>S</ShippingPaymentType>\n
<BillingAccountNumber>XXXXX</BillingAccountNumber>\n
<DutyPaymentType>R</DutyPaymentType>\n
</Billing>\n
<Consignee>\n
<CompanyName>XYZ</CompanyName>\n
<AddressLine>XXXXXX</AddressLine>\n
<City>Lekki</City>\n
<Division>Lagos</Division>\n
<DivisionCode>LG</DivisionCode>\n
<PostalCode>98981</PostalCode>\n
<CountryCode>NG</CountryCode>\n
<CountryName>Nigeria</CountryName>\n
<Contact>\n
<PersonName>MyName</PersonName>\n
<PhoneNumber>713-530-1160</PhoneNumber>\n
<PhoneExtension></PhoneExtension>\n
</Contact>\n
</Consignee>\n
<Dutiable>\n
<DeclaredValue>200</DeclaredValue>\n
<DeclaredCurrency>USD</DeclaredCurrency>\n
<TermsOfTrade>DAP</TermsOfTrade>\n
</Dutiable>\n
<Reference>\n
<ReferenceID>13</ReferenceID>\n
<ReferenceType>St</ReferenceType>\n
</Reference>\n
<ShipmentDetails>\n
<NumberOfPieces>3</NumberOfPieces>\n
<Pieces><Piece>\n
<PieceID>3</PieceID>\n
<PackageType>EE</PackageType>\n
<Weight>8</Weight>\n
<DimWeight>8</DimWeight>\n
<Width>6</Width>\n
<Height>8</Height>\n
<Depth>8</Depth>\n
<PieceContents></PieceContents>\n
</Piece></Pieces>\n
<Weight>287</Weight>\n
<WeightUnit>L</WeightUnit>\n
<GlobalProductCode>P</GlobalProductCode>\n
<Date>2018-06-27</Date>\n
<Contents>SHIPMENT #13</Contents>\n
<DoorTo>DD</DoorTo>\n
<DimensionUnit>I</DimensionUnit>\n
<PackageType>EE</PackageType>\n
<IsDutiable>N</IsDutiable>\n
<CurrencyCode>USD</CurrencyCode>\n
</ShipmentDetails>\n
<Shipper>\n
<ShipperID>XXXXXX</ShipperID>\n
<CompanyName>MyCompany</CompanyName>\n
<RegisteredAccount>XXXXX</RegisteredAccount>\n
<AddressLine>Address XYX demo</AddressLine>\n
<City>Tuscon</City>\n
<Division>Iowa</Division>\n
<DivisionCode>IW</DivisionCode>\n
<PostalCode>50020</PostalCode>\n
<CountryCode>US</CountryCode>\n
<CountryName>United States</CountryName>\n
<Contact>\n
<PersonName>PersonName</PersonName>\n
<PhoneNumber>12345600</PhoneNumber>\n
<PhoneExtension></PhoneExtension>\n
</Contact>\n
</Shipper>\n
<EProcShip>N</EProcShip>\n
<LabelImageFormat>PDF</LabelImageFormat>\n
<RequestArchiveDoc>Y</RequestArchiveDoc>\n
<Label>\n
<LabelTemplate>8X4_thermal</LabelTemplate>\n
<Logo>Y</Logo>\n
<Resolution>200</Resolution>\n
</Label>\n
</req:ShipmentValidateRequest>
EOT;
$url = "https://xmlpi-ea.dhl.com/XMLShippingServlet?isUTF8Support=true";
$ch = curl_init($url);
#curl_setopt($ch, CURLOPT_MUTE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
curl_setopt($ch, CURLOPT_POSTFIELDS, "$query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
preg_match("/<OutputImage>(.*?)<\/OutputImage>/", $output, $matches);
$image = base64_decode($matches[1]);
$data['image'] = $image;
$data['demo'] = "demo.pdf";
return view('Administrator.shipments.dhl-post', $data);
Your understanding of php's header function is slightly off.
Link to php header manual
header() is used to send a raw HTTP header. See the ยป HTTP/1.1
specification for more information on HTTP headers.
Remember that header() must be called before any actual output is
sent, either by normal HTML tags, blank lines in a file, or from PHP.
It is a very common error to read code with include, or require,
functions, or another file access function, and have spaces or empty
lines that are output before header() is called. The same problem
exists when using a single PHP/HTML file.
You should either temp save the file, use a stream, or link it from another page/route
PHP Temp File Manual
Since this doesn't answer the question, merely points out an incorrect use of header() I didn't want to post as answer, but did so by request

Strange "JPEG datastream contains no image" with PHP

I am getting a remote image and checking the size of it.
Sometimes I get a strange error saying JPEG datastream contains no image and I narrowed it down that it's happening at this step, in fact it's happening EXACTLY at imagecreatefromstring. What could be the problem? An issue with the image? Or do I need to increase some kind of memory setting in php.ini or..?
function ranger($url) {
$headers = array(
"Range: bytes=0-32768"
);
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($curl);
curl_close($curl);
return $data;
}
$url = $prod['IMAGE1'];
$raw1 = ranger($url);
$im = imagecreatefromstring($raw1);
$width = imagesx($im);
$height = imagesy($im);
You need to follow these three steps to solve your problem:
As the error message clearly states, you do not have an image. You need to determine why you do not have an image. You could start by running var_dump($raw1) but you could take a look at the algorithm in general. After this step you should know why you do not have an image where you expected one.
?
User a valid image resource as a parameter of imagecreatefromstring.

curl and resize remote image

I use this script to download and resize a remote image. In the resize part something goes wrong. What is it?
<?php
$img[]='http://i.indiafm.com/stills/celebrities/sada/thumb1.jpg';
$img[]='http://i.indiafm.com/stills/celebrities/sada/thumb5.jpg';
foreach($img as $i){
save_image($i);
if(getimagesize(basename($i))){
echo '<h3 style="color: green;">Image ' . basename($i) . ' Downloaded OK</h3>';
}else{
echo '<h3 style="color: red;">Image ' . basename($i) . ' Download Failed</h3>';
}
}
function save_image($img,$fullpath='basename'){
if($fullpath=='basename'){
$fullpath = basename($img);
}
$ch = curl_init ($img);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
$rawdata=curl_exec($ch);
curl_close ($ch);
// now you make an image out of it
$im = imagecreatefromstring($rawdata);
$x=300;
$y=250;
// then you create a second image, with the desired size
// $x and $y are the desired dimensions
$im2 = imagecreatetruecolor($x,$y);
imagecopyresized($im2,$im,0,0,0,0,$x,$y,imagesx($im),imagesy($im));
imagecopyresampled($im2,$im,0,0,0,0,$x,$y,imagesx($im),imagesy($im));
// delete the original image to save resources
imagedestroy($im);
if(file_exists($fullpath)){
unlink($fullpath);
}
$fp = fopen($fullpath,'x');
fwrite($fp, $im2);
fclose($fp);
// remember to free resources
imagedestroy($im2);
}
?>
When I run it, PHP gives me the following error:
Warning: fwrite() expects parameter 2 to be string, resource given ... on line 53
fwrite() writes a string to a file. You want to use the GD function imagejpeg() to save the GD resource to a file. It works for me when I change
$fp = fopen($fullpath,'x');
fwrite($fp, $im2);
fclose($fp);
to
imagejpeg($im2, $fullpath);
On an unrelated note, if all you're doing with cURL is grabbing the files, you can simply use file_get_contents() instead of cURL, assuming PHP is configured to allow full URLs in fopen functions. (I believe it is by default.) See the Notes section on the file_get_contents() manual page for more info. The function is binary-safe, so it works with images in addition to text files. To use it, I just replaced all six lines of the cURL functions with this line:
$rawdata = file_get_contents($img);
Update:
In response to your question below, you can specify a new file name for them in the array keys like so:
<?php
$img['img1.jpg']='http://i.indiafm.com/stills/celebrities/sada/thumb1.jpg';
$img['img2.jpg']='http://i.indiafm.com/stills/celebrities/sada/thumb5.jpg';
foreach($img as $newname => $i){
save_image($i, $newname);
if(getimagesize(basename($newname))){

how to check if a url is an image url with php?

I need to check the url is image url or not? How can i do this?
Examples :
http://www.google.com/ is not an image url.
http://www.hoax-slayer.com/images/worlds-strongest-dog.jpg is an image url.
https://stackoverflow.com/search?q=.jpg is not an image url.
http://www.google.com/profiles/c/photos/private/AIbEiAIAAABECK386sLjh92M4AEiC3ZjYXJkX3Bob3RvKigyOTEzMmFmMDI5ODQ3MzQxNWQxY2VlYjYwYmE2ZTA4YzFhNDhlMjBmMAEFQ7chSa4PMFM0qw02kilNVE1Hpw is an image url.
If you want to be absolutely sure, and your PHP is enabled for remote connections, you can just use
getimagesize('url');
If it returns an array, it is an image type recognized by PHP, even if the image extension is not in the url (per your second link). You have to keep in mind that this method will make a remote connection for each request, so perhaps cache urls that you already probed in a database to lower connections.
You can send a HEAD request to the server and then check the Content-type. This way you at least know what the server "thinks" what the type is.
You can check if a url is an image by using the getimagesize function like below.
function validImage($file) {
$size = getimagesize($file);
return (strtolower(substr($size['mime'], 0, 5)) == 'image' ? true : false);
}
$image = validImage('http://www.example.com/image.jpg');
echo 'this image ' . ($image ? ' is' : ' is not') . ' an image file.';
i think that the idea is to get a content of the header url via curl
and check the headers
After calling curl_exec() to get a web page, call curl_getinfo() to get the content type string from the HTTP header
look how to do it in this link :
http://nadeausoftware.com/articles/2007/06/php_tip_how_get_web_page_content_type#IfyouareusingCURL
Can use this:
$is = #getimagesize ($link);
if ( !$is ) $link='';
elseif ( !in_array($is[2], array(1,2,3)) ) $link='';
elseif ( ($is['bits']>=8) ) $srcs[] = $link;
Here is a way that requires curl, but is faster than getimagesize, as it does not download the whole image.
Disclaimer: it checks the headers, and they are not always correct.
function is_url_image($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
$output = curl_exec($ch);
curl_close($ch);
$headers = array();
foreach(explode("\n",$output) as $line){
$parts = explode(':' ,$line);
if(count($parts) == 2){
$headers[trim($parts[0])] = trim($parts[1]);
}
}
return isset($headers["Content-Type"]) && strpos($headers['Content-Type'], 'image/') === 0;
}
$ext = strtolower(end(explode('.', $filename)));
switch($ext)
{
case 'jpg':
///Blah
break;
}
Hard version (just trying)
//Turn off E_NOTICE reporting first
if(getimagesize($url) !== false)
{
//Image
}

Categories