I have an API that accepts base64 encoded image data, and will need to decode the data, save the image file, then create a thumbnail from that image.
I am concerned that malicious code could get executed if I do not properly validate the contents of the POST payload before attempting to create a thumbnail.
The basic workflow I have thus far is below. Is there enough validation that I do not need to be concerned about security? I guess I am worried about someone encoding something bad, then when one of the image functions below is called, the internet explodes.
<?php
$decodedImage = base64_decode($_POST["canvas"]);
if ($decodedImage === false) {
// Error out
}
$imageSizeValidation = getimagesizefromstring($decodedImage);
if ($imageSizeValidation[0] < 1 || $imageSizeValidation[1] < 1 || !$imageSizeValidation['mime']) {
// Error out
}
$tempFilePath = "/tmp/" . microtime(true) . "-canvas-web.jpg";
file_put_contents($tempFilePath, $decodedImage);
$originalWidth = $imageSizeValidation[0];
$originalHeight = $imageSizeValidation[1];
$newWidth = 49;
$newHeight = 49;
$scaleWidth = $newWidth / $originalWidth;
$scaleHeight = $newHeight / $originalHeight;
$scale = min($scaleWidth, $scaleHeight);
$width = (int)($originalWidth * $scale);
$height = (int)($originalHeight * $scale);
$xpos = (int)(($newWidth - $width) / 2);
$ypos = (int)(($newHeight - $height) / 2);
$oldImage = imagecreatefromjpeg($tempFilePath);
$newImage = imagecreatetruecolor($width, $height);
$background = imagecolorallocate($oldImage, 255, 255, 255);
imagefilledrectangle($newImage, 0, 0, $width, $height, $background);
imagecopyresampled($newImage, $oldImage, $xpos, $ypos, 0, 0, $width, $height, $originalWidth, $originalHeight);
imagedestroy($oldImage);
imagejpeg($newImage, "/path/to/new.jpg", 90);
imagedestroy($newImage);
Didn't end up getting any answers, so for those that are interested in what I ultimately did:
After investigating this a bit more, I found that one of my biggest concerns was valid image files encoded with inline PHP, Ruby, etc. EG: An image with the following at the end:
<?php phpinfo();
I ended up taking the decoded image data, and giving it to imagecreatefromstring(), then saving the image to a temp directory via imagejpeg().
This seemed to remove any encoded PHP from the original image data. At that point I validated the image size data of the saved image using getimagesize(). Assuming that everything was valid at that point, I moved the image to a permanent location.
Another thing I changed, was instead of using a filename based on a static string and microtime(), I used a hash.
Regarding the concern of images with code injected, I found this link to be helpful:
https://www.owasp.org/index.php/Unrestricted_File_Upload
I also found this other SO post useful, as far as overall ideas go:
Validating base64 encoded images
Finally, the following book brought the concern of images with code to my attention in the first place:
http://www.apress.com/9781430233183
My image is not getting displayed after resizing it.
require('connect.php');
if (isset($_FILES['image']) && $_FILES['image']['size'] > 0) {
// Temporary file name stored on the server
$tmpName = $_FILES['image']['tmp_name'];
$imagename = $_FILES["image"]["name"];
// Get new dimensions
list($width, $height) = getimagesize($tmpName);
echo "orginal width".$width."<br/>";
echo "orginal height".$height."<br/>";
$new_width= $width * 0.5;
$new_height = $height * 0.5;
echo "new width".$new_width."<br/>";
echo "new height".$new_height."<br/>"; //Its working fine till here.
$image_p = imagecreatetruecolor($new_width, $new_height);
$image = imagecreatefromjpeg($tmpName);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
echo "My image"."<br>";
echo '<img src="data:image/png|image/jpeg|image/gif;base64,' . base64_encode( $image_p ) . '" style="max-width:400px; max-height:300px;"/>';
/* This line is not able to display the image.
Also tried imagejpeg($image_p, null, 100);. Not working , but anyway i need to
display image alongwith other content so i have used echo. Not sure which
variable i should work with to display the image and how do i do it.*/
}
It seems to me that you are missing one step, encoding the image resource as a jpeg image (for example...).
You could output the result from something like imagejpeg to a file and just serve the file or you could capture its output using output buffering and serve it as you do now.
I want to allow users to upload avatar-type images in a variety of formats (GIF, JPEG, and PNG at least), but to save them all as PNG database BLOBs. If the images are oversized, pixelwise, I want to resize them before DB-insertion.
What is the best way to use GD to do the resizing and PNG conversion?
Edit: Sadly, only GD is available on the server I need to use, no ImageMagick.
<?php
/*
Resizes an image and converts it to PNG returning the PNG data as a string
*/
function imageToPng($srcFile, $maxSize = 100) {
list($width_orig, $height_orig, $type) = getimagesize($srcFile);
// Get the aspect ratio
$ratio_orig = $width_orig / $height_orig;
$width = $maxSize;
$height = $maxSize;
// resize to height (orig is portrait)
if ($ratio_orig < 1) {
$width = $height * $ratio_orig;
}
// resize to width (orig is landscape)
else {
$height = $width / $ratio_orig;
}
// Temporarily increase the memory limit to allow for larger images
ini_set('memory_limit', '32M');
switch ($type)
{
case IMAGETYPE_GIF:
$image = imagecreatefromgif($srcFile);
break;
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($srcFile);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($srcFile);
break;
default:
throw new Exception('Unrecognized image type ' . $type);
}
// create a new blank image
$newImage = imagecreatetruecolor($width, $height);
// Copy the old image to the new image
imagecopyresampled($newImage, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
// Output to a temp file
$destFile = tempnam();
imagepng($newImage, $destFile);
// Free memory
imagedestroy($newImage);
if ( is_file($destFile) ) {
$f = fopen($destFile, 'rb');
$data = fread($f);
fclose($f);
// Remove the tempfile
unlink($destFile);
return $data;
}
throw new Exception('Image conversion failed.');
}
Your process steps should look like this:
Verify the filetype
Load the image if it is a supported filetype into GD using imagecreatefrom*
Resizing using imagecopyresize or imagecopyresampled
Save the image using imagepng($handle, 'filename.png', $quality, $filters)
ImageMagick is faster, generates better images, is more configurable, and finally is (IMO) much easier to code for.
#ceejayoz Just wait for the new GD - it's OOP like MySQLi and it's actually not bad :)
If you want to use gdlib, use gdlib 2 or higher. It has a function called imagecopyresampled(), which will interpolate pixels while resizing and look much better.
Also, I've always heard noted around the net that storing images in the database is bad form:
It's slower to access than the disk
Your server will need to run a script to get to the image instead
of simply serving a file
Your script now is responsible for a lot of stuff the web server used
to handle:
Setting the proper Content-Type header
Setting the proper caching/timeout/E-tag headers, so clients can properly cache the image. If do not do this properly, the image serving script will be hit on every request, increasing the load on the server even more.
The only advantage I can see is that you don't need to keep your database and image files synchronized. I would still recommend against it though.
Are you sure you have no ImageMagick on server?
I guest you use PHP (question is tagged with PHP). Hosting company which I use has no ImageMagick extension turned on according to phpinfo().
But when I asked them about they said here is the list of ImageMagick programs available from PHP code. So simply -- there are no IM interface in PHP, but I can call IM programs directly from PHP.
I hope you have the same option.
And I strongly agree -- storing images in database is not good idea.
Something like this, perhaps:
<?php
//Input file
$file = "myImage.png";
$img = ImageCreateFromPNG($file);
//Dimensions
$width = imagesx($img);
$height = imagesy($img);
$max_width = 300;
$max_height = 300;
$percentage = 1;
//Image scaling calculations
if ( $width > $max_width ) {
$percentage = ($height / ($width / $max_width)) > $max_height ?
$height / $max_height :
$width / $max_width;
}
elseif ( $height > $max_height) {
$percentage = ($width / ($height / $max_height)) > $max_width ?
$width / $max_width :
$height / $max_height;
}
$new_width = $width / $percentage;
$new_height = $height / $percentage;
//scaled image
$out = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($out, $img, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
//output image
imagepng($out);
?>
I haven't tested the code so there might be some syntax errors, however it should give you a fair presentation on how it could be done. Also, I assumed a PNG file. You might want to have some kind of switch statement to determine the file type.
Is GD absolutely required? ImageMagick is faster, generates better images, is more configurable, and finally is (IMO) much easier to code for.
This article seems like it would fit what you want. You'll need to change the saving imagejpeg() function to imagepng() and have it save the file to a string rather than output it to the page, but other than that it should be easy copy/paste into your existing code.
I think this page is a good starting point. It uses imagecreatefrom(jpeg/gif/png) and resize and converts the image and then outputs to the browser. Instead of outputting the browser you could output to a BLOB in a DB without many minuttes of code-rewrite.
phpThumb is a high-level abstraction that may be worth looking at.
What's the most efficient way to resize large images in PHP?
I'm currently using the GD function imagecopyresampled to take high resolution images, and cleanly resize them down to a size for web viewing (roughly 700 pixels wide by 700 pixels tall).
This works great on small (under 2 MB) photos and the entire resize operation takes less than a second on the server. However, the site will eventually service photographers who may be uploading images up to 10 MB in size (or images up to 5000x4000 pixels in size).
Doing this kind of resize operation with large images tends to increase the memory usage by a very large margin (larger images can spike the memory usage for the script past 80 MB). Is there any way to make this resize operation more efficient? Should I be using an alternate image library such as ImageMagick?
Right now, the resize code looks something like this
function makeThumbnail($sourcefile, $endfile, $thumbwidth, $thumbheight, $quality) {
// Takes the sourcefile (path/to/image.jpg) and makes a thumbnail from it
// and places it at endfile (path/to/thumb.jpg).
// Load image and get image size.
$img = imagecreatefromjpeg($sourcefile);
$width = imagesx( $img );
$height = imagesy( $img );
if ($width > $height) {
$newwidth = $thumbwidth;
$divisor = $width / $thumbwidth;
$newheight = floor( $height / $divisor);
} else {
$newheight = $thumbheight;
$divisor = $height / $thumbheight;
$newwidth = floor( $width / $divisor );
}
// Create a new temporary image.
$tmpimg = imagecreatetruecolor( $newwidth, $newheight );
// Copy and resize old image into new image.
imagecopyresampled( $tmpimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height );
// Save thumbnail into a file.
imagejpeg( $tmpimg, $endfile, $quality);
// release the memory
imagedestroy($tmpimg);
imagedestroy($img);
People say that ImageMagick is much faster. At best just compare both libraries and measure that.
Prepare 1000 typical images.
Write two scripts -- one for GD, one
for ImageMagick.
Run both of them a few times.
Compare results (total execution
time, CPU and I/O usage, result
image quality).
Something which the best everyone else, could not be the best for you.
Also, in my opinion, ImageMagick has much better API interface.
Here's a snippet from the php.net docs that I've used in a project and works fine:
<?
function fastimagecopyresampled (&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 3) {
// Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled.
// Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled".
// Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting.
// Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain.
//
// Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero.
// Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect.
// 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized.
// 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3.
// 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster.
// 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images.
// 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled.
if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; }
if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) {
$temp = imagecreatetruecolor ($dst_w * $quality + 1, $dst_h * $quality + 1);
imagecopyresized ($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h);
imagecopyresampled ($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality);
imagedestroy ($temp);
} else imagecopyresampled ($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
return true;
}
?>
http://us.php.net/manual/en/function.imagecopyresampled.php#77679
phpThumb uses ImageMagick whenever possible for speed (falling back to GD if necessary) and seems to cache pretty well to reduce the load on the server. It's pretty lightweight to try out (to resize an image, just call phpThumb.php with a GET query that includes the graphic filename and output dimensions), so you might give it a shot to see if it meets your needs.
For larger images use libjpeg to resize on image load in ImageMagick and thereby significantly reducing memory usage and improving performance, it is not possible with GD.
$im = new Imagick();
try {
$im->pingImage($file_name);
} catch (ImagickException $e) {
throw new Exception(_('Invalid or corrupted image file, please try uploading another image.'));
}
$width = $im->getImageWidth();
$height = $im->getImageHeight();
if ($width > $config['width_threshold'] || $height > $config['height_threshold'])
{
try {
/* send thumbnail parameters to Imagick so that libjpeg can resize images
* as they are loaded instead of consuming additional resources to pass back
* to PHP.
*/
$fitbyWidth = ($config['width_threshold'] / $width) > ($config['height_threshold'] / $height);
$aspectRatio = $height / $width;
if ($fitbyWidth) {
$im->setSize($config['width_threshold'], abs($width * $aspectRatio));
} else {
$im->setSize(abs($height / $aspectRatio), $config['height_threshold']);
}
$im->readImage($file_name);
/* Imagick::thumbnailImage(fit = true) has a bug that it does fit both dimensions
*/
// $im->thumbnailImage($config['width_threshold'], $config['height_threshold'], true);
// workaround:
if ($fitbyWidth) {
$im->thumbnailImage($config['width_threshold'], 0, false);
} else {
$im->thumbnailImage(0, $config['height_threshold'], false);
}
$im->setImageFileName($thumbnail_name);
$im->writeImage();
}
catch (ImagickException $e)
{
header('HTTP/1.1 500 Internal Server Error');
throw new Exception(_('An error occured reszing the image.'));
}
}
/* cleanup Imagick
*/
$im->destroy();
From you quesion, it seems you are kinda new to GD, I will share some experence of mine,
maybe this is a bit off topic, but I think it will be helpful to someone new to GD like you:
Step 1, validate file. Use the following function to check if the $_FILES['image']['tmp_name'] file is valid file:
function getContentsFromImage($image) {
if (#is_file($image) == true) {
return file_get_contents($image);
} else {
throw new \Exception('Invalid image');
}
}
$contents = getContentsFromImage($_FILES['image']['tmp_name']);
Step 2, get file format Try the following function with finfo extension to check file format of the file(contents). You would say why don't you just use $_FILES["image"]["type"] to check file format? Because it ONLY check file extension not file contents, if someone rename a file originally called world.png to world.jpg, $_FILES["image"]["type"] will return jpeg not png, so $_FILES["image"]["type"] may return wrong result.
function getFormatFromContents($contents) {
$finfo = new \finfo();
$mimetype = $finfo->buffer($contents, FILEINFO_MIME_TYPE);
switch ($mimetype) {
case 'image/jpeg':
return 'jpeg';
break;
case 'image/png':
return 'png';
break;
case 'image/gif':
return 'gif';
break;
default:
throw new \Exception('Unknown or unsupported image format');
}
}
$format = getFormatFromContents($contents);
Step.3, Get GD resource Get GD resource from contents we have before:
function getGDResourceFromContents($contents) {
$resource = #imagecreatefromstring($contents);
if ($resource == false) {
throw new \Exception('Cannot process image');
}
return $resource;
}
$resource = getGDResourceFromContents($contents);
Step 4, get image dimension Now you can get image dimension with the following simple code:
$width = imagesx($resource);
$height = imagesy($resource);
Now, Let's see what variable we got from the original image then:
$contents, $format, $resource, $width, $height
OK, lets move on
Step 5, calculate resized image arguments This step is related to your question, the purpose of the following function is to get resize arguments for GD function imagecopyresampled(), the code is kinda long, but it works great, it even has three options: stretch, shrink, and fill.
stretch: output image's dimension is the same as the new dimension you set. Won't keep height/width ratio.
shrink: output image's dimension won't exceed the new dimension you give, and keep image height/width ratio.
fill: output image's dimension will be the same as new dimension you give, it will crop & resize image if needed, and keep image height/width ratio. This option is what you need in your question.
function getResizeArgs($width, $height, $newwidth, $newheight, $option) {
if ($option === 'stretch') {
if ($width === $newwidth && $height === $newheight) {
return false;
}
$dst_w = $newwidth;
$dst_h = $newheight;
$src_w = $width;
$src_h = $height;
$src_x = 0;
$src_y = 0;
} else if ($option === 'shrink') {
if ($width <= $newwidth && $height <= $newheight) {
return false;
} else if ($width / $height >= $newwidth / $newheight) {
$dst_w = $newwidth;
$dst_h = (int) round(($newwidth * $height) / $width);
} else {
$dst_w = (int) round(($newheight * $width) / $height);
$dst_h = $newheight;
}
$src_x = 0;
$src_y = 0;
$src_w = $width;
$src_h = $height;
} else if ($option === 'fill') {
if ($width === $newwidth && $height === $newheight) {
return false;
}
if ($width / $height >= $newwidth / $newheight) {
$src_w = (int) round(($newwidth * $height) / $newheight);
$src_h = $height;
$src_x = (int) round(($width - $src_w) / 2);
$src_y = 0;
} else {
$src_w = $width;
$src_h = (int) round(($width * $newheight) / $newwidth);
$src_x = 0;
$src_y = (int) round(($height - $src_h) / 2);
}
$dst_w = $newwidth;
$dst_h = $newheight;
}
if ($src_w < 1 || $src_h < 1) {
throw new \Exception('Image width or height is too small');
}
return array(
'dst_x' => 0,
'dst_y' => 0,
'src_x' => $src_x,
'src_y' => $src_y,
'dst_w' => $dst_w,
'dst_h' => $dst_h,
'src_w' => $src_w,
'src_h' => $src_h
);
}
$args = getResizeArgs($width, $height, 150, 170, 'fill');
Step 6, resize image Use $args, $width, $height, $format and $resource we got from above into the following function and get the new resource of the resized image:
function runResize($width, $height, $format, $resource, $args) {
if ($args === false) {
return; //if $args equal to false, this means no resize occurs;
}
$newimage = imagecreatetruecolor($args['dst_w'], $args['dst_h']);
if ($format === 'png') {
imagealphablending($newimage, false);
imagesavealpha($newimage, true);
$transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
imagefill($newimage, 0, 0, $transparentindex);
} else if ($format === 'gif') {
$transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
imagefill($newimage, 0, 0, $transparentindex);
imagecolortransparent($newimage, $transparentindex);
}
imagecopyresampled($newimage, $resource, $args['dst_x'], $args['dst_y'], $args['src_x'], $args['src_y'], $args['dst_w'], $args['dst_h'], $args['src_w'], $args['src_h']);
imagedestroy($resource);
return $newimage;
}
$newresource = runResize($width, $height, $format, $resource, $args);
Step 7, get new contents, Use the following function to get contents from the new GD resource:
function getContentsFromGDResource($resource, $format) {
ob_start();
switch ($format) {
case 'gif':
imagegif($resource);
break;
case 'jpeg':
imagejpeg($resource, NULL, 100);
break;
case 'png':
imagepng($resource, NULL, 9);
}
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
$newcontents = getContentsFromGDResource($newresource, $format);
Step 8 get extension, Use the following function to get extension of from image format(note, image format is not equal to image extension):
function getExtensionFromFormat($format) {
switch ($format) {
case 'gif':
return 'gif';
break;
case 'jpeg':
return 'jpg';
break;
case 'png':
return 'png';
}
}
$extension = getExtensionFromFormat($format);
Step 9 save image If we have a user named mike, you can do the following, it will save to the same folder as this php script:
$user_name = 'mike';
$filename = $user_name . '.' . $extension;
file_put_contents($filename, $newcontents);
Step 10 destroy resource Don't forget destroy GD resource!
imagedestroy($newresource);
or you can write all your code into a class, and simply use the following:
public function __destruct() {
#imagedestroy($this->resource);
}
TIPS
I recommend not to convert file format that user upload, you will meet many problems.
I suggest that you work something along these lines:
Perform a getimagesize( ) on the uploaded file to check image type and size
Save any uploaded JPEG image smaller than 700x700px in to the destination folder "as-is"
Use GD library for medium size images (see this article for code sample: Resize Images Using PHP and GD Library)
Use ImageMagick for large images. You can use ImageMagick in background if you prefer.
To use ImageMagick in background, move the uploaded files to a temporary folder and schedule a CRON job that "convert"s all files to jpeg and resizes them accordingly. See command syntax at: imagemagick-command line processing
You can prompt the user that file is uploaded and scheduled to be processed. The CRON job could be scheduled to run daily at a specific interval. The source image could be deleted after processing to assure that an image is not processed twice.
I've heard big things about the Imagick library, unfortunately I couldn't install it at my work computer and neither at home (and trust me, I spent hours and hours on all kinds of forums).
Afterwords, I've decided to try this PHP class:
http://www.verot.net/php_class_upload.htm
It's pretty cool and I can resize all kinds of images (I can convert them to JPG also).
ImageMagick is multithreaded, so it appears to be faster, but actually uses a lot more resources than GD. If you ran several PHP scripts in parallel all using GD then they'd beat ImageMagick in speed for simple operations. ExactImage is less powerful than ImageMagick but a lot faster, though not available through PHP, you'll have to install it on the server and run it through exec.
For larger images use phpThumb(). Here is how to use it: http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/. It also works for large corrupted images.