Serve image with PHP script vs direct loading an image

Viewed 44030

I want to monitor how often some external images are loaded. So my idea is instead of giving a uri directly like this:

www.site.com/image1.jpg

I can create a PHP script which reads the image, so I built a PHP file and my HTML would look like this:

<img src="www.site.com/serveImage.php?img=image1.jpg">

but I don't know how to read the image from disk and return it. Would I return a byte array or set the content type?

Kind regards, Michel

9 Answers

I serve my images with readfile as well, but I have gone the extra mile both for security and extra functionality.

I have a database set up which stores the image id, its dimensions and file extension. This also means that images need to be uploaded (allowing optional resizing), so I only use the system for content and not images needed for the website itself (like backgrounds or sprites).

It also does a very good job at making sure you can only request images.

So, for serving the simplified workflow would be like this (cannot post production code here):

1) get the ID of the requested image

2) Look it up in the database

3) Throw headers based on the extension ("jpg" gets remapped to "jpeg" on upload)

4) readfile("/images/$id.$extension");

5) Optionally, protect /images/ dir so it cannot be indexed (not a problem in my own system as it maps URLS like /image/view/11 to something like /index.php?module=image&action=view&id=11)

There are a lot of good answers above, but none of them provide working code that you can use in your PHP app. I've set mine up so that I lookup the name of the image in a database table based off a different identifier. The client never sets the name of the file to download as this is a security risk.

Once the image name is found, I explode it to obtain the extension. This is important to know what type of header to serve based off the image type (i.e. png, jpg, jpeg, gif, etc.). I use a switch to do this for security reasons and to convert jpg -> jpeg for the proper header name. I've included a few additional headers in my code that ensure the file is not cached, that revalidation is required, to change the name (otherwise it will be the name of the script that is called), and finally to read the file from the server and transmit it.

I like this method since it never exposes the directory or actual file name. Be sure you authenticate the user before running the script if you are trying to do this securely.

$temp = explode('.', $image_filename);
$extension = end($temp);    // jpg, jpeg, gif, png - add other flavors based off your use case

switch ($extension) {
    case "jpg":
        header('Content-type: image/jpeg');
        break;
    case "jpeg":
    case "gif":
    case "png":
        header('Content-type: image/'.$extension);
        break;
    default:
        die;    // avoid security issues with prohibited extensions
}

header('Content-Disposition: filename=photo.'.$extension);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
readfile('../SECURE_DIRECTORY/'.$image_filename);

PHP 8 lets you use the match feature, which will further optimize the code by getting rid of the switch and ugly looking nested cases.

Related