Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Valid images output by PHP always "contain errors", what could be causing this?

Tags:

php

image

A few months ago I wrote a website for a customer using PHP 5.3. It works perfectly on my own LAMP webserver. However, when he tried to install it in his own server (currently an OVH server running DirectAdmin on CentOS 5) he ran into an issue I'm having trouble figuring out.

The website can store images uploaded through forms. Images are watermarked on upload and moved to a directory in the webserver (some metadata is stored in the database, but this isn't related to this issue).

In order to display these images back to the user, a script is used like so:

header("Content-type: image/jpeg");
ob_start();
echo file_get_contents($path);
$size = ob_get_length();
$img = ob_get_contents();
ob_end_clean();
header ("Content-length: " . $size);
echo $img;

Unfortunately, this always returns a broken image (in Firefox, "The image cannot be displayed because it contains errors"). Now, after careful testing, I know that:

  • Images are correctly uploaded to the server. The image data stored in the webserver is valid and can be obtained via FTP as the regular image.

  • If I store $img to a file immediately before the final line of the previous script, like so:

    $fh = fopen("test.jpg", "w");
    fwrite($fh, $img);
    fclose($fh);
    

    It will save the correct image data to the file also. So the data is intact immediately before being sent to the user's web browser.

  • The headers are being sent correctly.

However! If I use a text/plain header rather than image/jpeg, I can see that the gibberish returned is different from the gibberish displayed if I open the file locally with notepad (or send the image directly through apache as a text file). In the original image, I can see some EXIF. In the image generated by PHP and then send to the user's web browser, I still see the JFIF magic code (for JPEG File Image Format) but the rest looks different.

I'm afraid I have a configuration-related issue on either PHP or Apache related to encoding, buffering, content compression or something like that. Does anyone know anything I can try to solve this issue?

EDIT:

Changed the script to use:

$img = file_get_contents($path);
$size = filesize($path);

The issue remains unchanged, but the content now looks exactly the same comparing the real image to the image sent from PHP. According to the headers, the content encoding is gzip. Any ideas?

like image 817
Protected Avatar asked Sep 10 '11 13:09

Protected


2 Answers

Well, after some investigation it turned to be infamous Byte Order Mark signature (in conjunction, of course, with output buffering which suppressed an error) in PHP script.
It seems just re-saving the file without BOM will solve the problem

does it work?

header("Content-type: image/jpeg");
echo file_get_contents($path);

or this?

header("Content-type: image/jpeg");
readfile($path);

download this image (using wget or make a link on it and use 'Save as') and see the difference. It may shed the light on the cause

And yeah. ob has absolutely nothing to do here. if you want to get a file size - there is a (surprise!) a function for it

header("Content-type: image/jpeg");
header ("Content-length: " . filesize($path));
readfile($path);
like image 186
Your Common Sense Avatar answered Sep 23 '22 15:09

Your Common Sense


First, if you haven't already I would confirm that the problem image is indeed a jpeg and not some other type of image. Many image viewing programs will display them properly even if given the wrong type or extension but it could still cause issues if you send a jpeg type for a non-jpeg.

Second, I would move the ob_start() to the first line of your file.

Third, while it is best practice to send the Content-length header it doesn't need to be sent so I would remove it just to eliminate one possible source of a problem with invalid data being sent.

Finally, if you have GD installed and it suites your usage requirements this alternate solution may work for you.

ob_start();
$image = imagecreatefromjpeg( $path );
if (!$image ) {
    // error trapping / other logic here
}
ob_end_clean();
header( "Content-type: image/jpeg" );
@imagejpeg( $image );
if ( $image ) {
    imagedestroy( $image );
}
like image 30
Night Owl Avatar answered Sep 23 '22 15:09

Night Owl