imagecopy fails when source image is corrupt - php

I wrote a simple PHP script to add additional information to a webcam JPG file. It adds header and footer with some text. To do so I create a new image with this information and then copy the original JPG inside using imagecopy. And it all works fine.
The webcam has a poor wifi connection to the internet, so it seldom happens that the JPG file uploaded via FTP is partial or corrupt: I can open it using GIMP or other image software, and I see it has some missing information to the bottom. When this happens the above imagecopy seems to fail in copying image data, and the destination image remains blank (for the image area).
I tried everything I found to check the original JPG is valid:
// Check image
if (exif_imagetype($last) != IMAGETYPE_JPEG) // Not a valid jpeg
continue;
$details = getimagesize($last);
if ($details === FALSE) // Not a valid mage
continue;
$im = imagecreatefromjpeg($last);
but all tests pass. I also added:
if (imagecopy($dest_image, $im, 0, $top_banner_height+1, 0, 0, $img_width, $img_heigth) === FALSE) {
imagedestroy($im);
imagedestroy($dest_image);
continue;
}
but I still cannot catch an unterminated upload. How can I check if the image is valid for GD processing?
edit: this is how the source image appears in GIMP:
This is a portion of the original uploaded file.
As requested I added how I open the file, using imagecreatefromjpeg. It cannot be a permission problem because the script works fine 90% of the times, it's just when it encounters such images which fails.
edit2: I originally thought it could be a concurrency problem, being that I run the script via cron every minute, but the FTP upload is out of server control, so they run asynchronously. So perhaps the script was accessing the file exactly while being uploaded, but I checked and it's not the case, as I wrote above the uploaded file is corrupt in the beginning.
edit3: the suggested imagecolorat is not a solution (at least not at for all cases): I just found a messed up picture that would pass that test. jpeginfo says: Corrupt JPEG data: 10839 extraneous bytes before marker 0xd9

Thanks for all the suggestions, but in the end I found another possible way.
As said above and in other answers the command line tool jpeginfo -c filename checks if the image is a valid jpeg, printing a warning or and error (and a return code != 0). So it's the most precise solution so far, but involves an external command.
On the PHP side there doesn't seem to be a perfect check, as also the counterpart Imagick::valid doesn't perform a validity check on the full image.
At that very same page there's another proposed solution:
// check for the existence of the EOI segment header at the end of the file
$file = fopen($image_file, "r");
if (0 !== fseek($file, -2, SEEK_END) || "\xFF\xD9" !== fread($file, 2)) {
// jpeg is not valid
fclose($file);
return FALSE;
}
This works great, but only for unfinished images (first case). The example posted in edit 3 still passes this test.

Related

Compressing Images as JPEG from $_FILES array

I've been searching the web and trying to understand how to scale and compress my image uploads with PHP. I want users to be able to upload, say, a 1MB file, but then to actually save a much more compressed version of that file to my server since for this application, details aren't as important. I've come up with the following code:
print_r($_FILES);
// Check if the file size is too big
if ($_FILES['image']['size'] > MAX_FILE_SIZE)
{
// Compress it
imagejpeg($_FILES['image']['tmp_name'], $_FILES['image']['tmp_name'], 60);
print_r($_FILES);
// Check file size again
if ($_FILES['image']['size'] > MAX_FILE_SIZE)
{
// Image too big still...
return;
}
}
At this point, I'm every time getting caught in my "// Check again" block and in both my "print_r" statements, I'm seeing the file size remain the same. Can anyone please point me in the right direction in terms of what I'm doing wrong? Is there an entirely different but better way of handling this? Thanks a lot!
Your imagejpeg function isn't being passed the correct arguments. You need to first open the file using a GD function like imagecreatefromjpeg and use the resource it returns to manipulate the image (in this case, compress it).
Try something like:
// Create image resource from file (try a different function if not JPG)
$im = imagecreatefromjpeg($_FILES['image']['tmp_name']);
// Check if successfully opened
if($im){
// Resize the resource and save it back to the temporary file name
imagejpeg($im, $_FILES['image']['tmp_name'], 60);
}

Is ImageMagick identify enough to verify that the image is indeed an image

I use the code below to test whether an uploaded file is indeed an image. (The code below is not the same OOP style found in the php website when using ImageMagick because I am on a shared server and that is the instructions my hosting provided when using ImageMagick AND Actual script will involve rerouting users, unlinking or deleting the file uploaded and more so please do not criticize the code below, I just want to dwell in the concept of using IMAGICK IDENTIFY as an image verification tool.)
<?php
if(!exec('/usr/bin/identify /home/user/public_html/joteco_test_folder/thisisanimage.jpg'))
{
echo "NOT AN IMAGE";
}
else
{
echo exec('/path/here/identify /path/here/thisisanimage.jpg');
}
?>
I tried the following on the code above:
A .jpg made with photoshop cs6.
(It passed and echoed the following details "810x203 810x203+0+0 8-bit DirectClass 32.4KB 0.000u 0:00.000")
A .txt file. (It failed and echoed "NOT AN IMAGE")
A .jpg file made with notepad that has text characters written that says "I AM AN IMAGE". (It failed and echoed "NOT AN IMAGE")
(In my point of view I think it was a success, but I know that hackers do more than what I did in test #3.)
SO! Do you think that would be enough as a security check to verify if an uploaded file is indeed an image? Or are there other tools in ImageMagick that I can use for this purpose? Your thoughts?
(Pls. do not suggest or mention ( MIME | EXTENSION | GETIMAGESIZE ) as it has been repeatedly mentioned in stackoverflow as useless methods in verifying uploaded files. Thank you)
This should help you out.
$im = #imagecreatefromjpeg($imgname);
if(!$im)
{
return false;
}
else
{
imagedestroy($im);
return true;
}
In addition to checking if its an image it also returns false for incomplete (partaily uploaded) images

Premature end of JPEG file

I am getting this Premature end of JPEG file error while resizing some of the images. Interesting and strange part is that i am getting this error only when i upload any camera taken images, like from mobile, other than those every thing works great. I thought this could be because of the chunk size used in plupload. So, i uploaded with larger sized image to somewhat like 3mb to test. Works fine with images other than camera taken images. So, whenever i upload camera pics i get this error.
Further elaborations on the error: php function imagecreatefromjpeg is throwing an error "imgname.jpg is not a valid JPEG file".
To resize images i am using Codeigniter's image Manipulation Class.
Based on what you have provided, I can only give you my deductions.
Camera images are usually very big. I suggest that you try to resize the camera images and see if it works.
What is your PHP version? There is a bug related to this: https://bugs.php.net/bug.php?id=29878
Please also check if your JPEG files are in RGB format. Somewhere in the manual mentioned that it could not properly load CMYK for certain versions of the GD library.
Are you open to using another class? I use this class to resize images and have not encountered any problems with it for years.
Resizing images is as easy as:
<?php
include('SimpleImage.php');
$image = new SimpleImage();
$image->load('picture.jpg');
$image->resizeToHeight(500);
$image->save('picture2.jpg');
$image->resizeToHeight(200);
$image->save('picture3.jpg');
?>
If all the suggestions did not work out, you can try using ImageMagick.
Also "Premature end of JPEG file" it's a general software error if image content is not complete. Software determines it by color of last pixel.
I've get 'Premature end of JPEG file' from tesseract(Open Source OCR Engine) because file was not copied properly through network.
did you found that sometimes it hang the php when imagecreatefromjpeg() run on bad JPEG. I found that this is cause by the JPEG file U used dont have EOI (end of image)
the FF D9 at the end of your JPEG
JPEG image should start with 0xFFD8 and end with 0xFFD9
// this may help to fix the error
function check_jpeg($f, $fix=false )
{
# check for jpeg file header and footer - also try to fix it
if ( false !== (#$fd = fopen($f, 'r+b' )) ){
if ( fread($fd,2)==chr(255).chr(216) ){
fseek ( $fd, -2, SEEK_END );
if ( fread($fd,2)==chr(255).chr(217) ){
fclose($fd);
return true;
}else{
if ( $fix && fwrite($fd,chr(255).chr(217)) ){return true;}
fclose($fd);
return false;
}
}else{fclose($fd); return false;}
}else{
return false;
}
}
You can set default value of gd.jpeg_ignore_warning=1 in php.ini
OR
You can set it like this ini_set('gd.jpeg_ignore_warning', true); in your PHP script before calling imagecreatefromjpeg()
After implementing any of the above, GD Library will ignore the error where it use to fail and imagecreatefromjpeg() will return an image resource identifier.
Note: In PHP 7.1.0 the default value of gd.jpeg_ignore_warning has been changed from 0 to 1.
Reference

PHP test for corrupt (but apparently fine) images

I have a simple image upload script that uses SimpleImage.php
(http://www.white-hat-web-design.co.uk/blog/resizing-images-with-php/)
to resize and save 2 copies of an uploaded image.
There isn't a massive amount of validation, just checking that it exists and that the file extension is fine, and also an exif_imagetype(); call.
This has worked with no problems so far until I tried to upload a seemingly normal jpeg which turned out to be invisibly (and untestably?) corrupt. There was something not right about it, but I know very little about image corruption - it looked fine and opened no problem on anything, but when I tried to save a scaled copy in my script I got a white page.
The problem is definitely that specific image, I've tested exhastively with other images both from my local stock and from stock image sites, and only that one image breaks it.
I resized a copy using Photoshop (the predicted file size thingy gave me some wierd numbers - 45meg for top quality jpeg) and that uploaded with no issues.
So my question is, how do I test for this?
The image in question is here: http://chinawin.co.uk/broken.jpg //beware, 700k
notes: I've tested with similar resolutions, image sizes and names, everything else worked apart from this image.
UPDATE:
Through trial and error I've narrowed down where the script breaks to the line where I load the image into a var for SimpleImage. Strangely this is the second line that does so (the first being to create the large copy, this one to create a thumbnail).
Commenting it out means the rest works ok... perhaps some refactoring will avoid this problem.
2nd Update:
Here's a snippet of code and some context from the line that fails:
//check if our image is OK
if ($image && $imageThumb)
{
//check if image is a jpeg
if (exif_imagetype($_FILES[$k]['tmp_name']) == IMAGETYPE_JPEG)
{
list($width, $height, $type, $attr) = getimagesize($_FILES[$k]['tmp_name']);
//echo 1;
$image = new SimpleImage();
//echo 2;
$image->load($_FILES[$k]['tmp_name']);
//echo 3;
$imageThumb = new SimpleImage();
//echo 4;
//this next line topples my script, but only for that one image - why?:
$imageThumb->load($_FILES[$k]['tmp_name']);
//echo '5<br/><br/>-------<br/>';
//do stuff, save & update db, etc
}
}
Final edit:
Turns out my script was running out of memory, and with good reason - 4900x3900 image with 240 ppi turns out to be around 48 meg when loaded into memory, twice - so I was using probably > 90meg of ram, per image.
Hats off to #Pekka for spotting this.
Refactoring the script to only have the image loaded once, and then this variable used instead of it's sibling, fixed my script. Still having (different) issues with upoading larger (2.5meg) images but this is for another question.
This is most likely a memory issue: Your JPG is very large (more than 4000 x 4000 pixels) and, uncompressed, will indeed eat up around 48 Megabytes of RAM.
Activate error reporting to make sure. If it's the reason, see e.g. here on what to do: Uploading images with PHP and hitting the script memory limit

Secure User Image Upload Capabilities in PHP

I'm implementing a user-based image uploading tool for my website. The system should allow any users to upload JPEG and PNG files only. I'm, of course, worried about security and so I'm wondering how the many smarter people than myself feel about the following checks for allowing uploads:
1) First white list the allowable file extensions in PHP to allow only PNG, png, jpg, JPG and JPEG. Retrieve the user's file's extension via a function such as:
return end(explode(".", $filename));
This should help disallow the user from uploading something malicious like .png.php. If this passes, move to step 2.
2) Run the php function getimageize() on the TMP file. Via something like:
getimagesize($_FILES['userfile']['tmp_name']);
If this does not return false, proceed.
3) Ensure a .htaccess file is placed within the uploads directory so that any files within this directory cannot parse PHP files:
php_admin_value engine Off
4) Rename the user's file to something pre-determined. I.E.
$filename = 'some_pre_determined_unique_value' . $the_file_extension;
This will also help prevent SQL injection as the filename will be the only user-determined variable in any queries used.
If I perform the above, how vulnerable for attack am I still? Before accepting a file I should hopefully have 1) only allowed jpgs and pngs, 2) Verified that PHP says it's a valid image, 3) disabled the directory the images are in from executing .php files and 4) renamed the users file to something unique.
Thanks,
Regarding file names, random names are definitely a good idea and take away a lot of headaches.
If you want to make totally sure the content is clean, consider using GD or ImageMagick to copy the incoming image 1:1 into a new, empty one.
That will slightly diminish image quality because content gets compressed twice, but it will remove any EXIF information present in the original image. Users are often not even aware how much info gets put into the Metadata section of JPG files! Camera info, position, times, software used... It's good policy for sites that host images to remove that info for the user.
Also, copying the image will probably get rid of most exploits that use faulty image data to cause overflows in the viewer software, and inject malicious code. Such manipulated images will probably simply turn out unreadable for GD.
Regarding your number 2), don't just check for FALSE. getimagesize will also return the mime type of the image. This is by far a more secure way to check proper image type than looking at the mime type the client supplies:
$info = getimagesize($_FILES['userfile']['tmp_name']);
if ($info === FALSE) {
die("Couldn't read image");
}
if (($info[2] !== IMAGETYPE_PNG) && ($info[2] !== IMAGETYPE_JPEG)) {
die("Not a JPEG or PNG");
}
All the checks seem good, number 3 in particular. If performance is not an issue, or you are doing this in the background, you could try accessing the image using GD and seeing if it is indeed an image and not just a bunch of crap that someone is trying to fill your server with.
Concerning No. 2, I read on php.net (documentation of the function getimagesize() ):
Do not use getimagesize() to check that a given file is a valid image. Use a purpose-built solution such as the Fileinfo extension instead.

Categories