Reputation: 23615
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
Upvotes: 25
Views: 47259
Reputation: 386
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.
Upvotes: 1
Reputation: 173
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)
Upvotes: 2
Reputation: 10033
Sending images through a script is nice for other things like resizing and caching on demand.
As answered by Pascal MARTIN the function readfile
and these headers are the requirements:
header('Content-Type: image/gif');
mime_content_type
image/gif
image/jpeg
image/png
But beside the obvious content-type you should also look at other headers such as:
header('Content-Length: 348');
filesize
header('Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT');
filemtime
and date
to format it into the required RFC 2822 format
header('Last-Modified: '.date(DATE_RFC2822, filemtime($filename)));
header("HTTP/1.1 304 Not Modified");
For last modified time, look for this in $_SERVER
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
$_SERVER
with the key http_if_modified_since
Upvotes: 42
Reputation: 1027
Instead of changing the direct image url in the HTML, you can put a line in the Apache configuration or .htaccess
to rewrite all the requests of images in a directory to a php script. Then in that script you can make use of the request headers and the $_server
array to process the request and serve the file.
First in your .htaccess:
RewriteRule ^(.*)\.jpg$ serve.php [NC]
RewriteRule ^(.*)\.jpeg$ serve.php [NC]
RewriteRule ^(.*)\.png$ serve.php [NC]
RewriteRule ^(.*)\.gif$ serve.php [NC]
RewriteRule ^(.*)\.bmp$ serve.php [NC]
The script serve.php
must be in the same directory as .htaccess
. You will probably write something like this:
<?php
$filepath=$_SERVER['REQUEST_URI'];
$filepath='.'.$filepath;
if (file_exists($filepath))
{
touch($filepath,filemtime($filepath),time()); // this will just record the time of access in file inode. you can write your own code to do whatever
$path_parts=pathinfo($filepath);
switch(strtolower($path_parts['extension']))
{
case "gif":
header("Content-type: image/gif");
break;
case "jpg":
case "jpeg":
header("Content-type: image/jpeg");
break;
case "png":
header("Content-type: image/png");
break;
case "bmp":
header("Content-type: image/bmp");
break;
}
header("Accept-Ranges: bytes");
header('Content-Length: ' . filesize($filepath));
header("Last-Modified: Fri, 03 Mar 2004 06:32:31 GMT");
readfile($filepath);
}
else
{
header( "HTTP/1.0 404 Not Found");
header("Content-type: image/jpeg");
header('Content-Length: ' . filesize("404_files.jpg"));
header("Accept-Ranges: bytes");
header("Last-Modified: Fri, 03 Mar 2004 06:32:31 GMT");
readfile("404_files.jpg");
}
/*
By Samer Mhana
www.dorar-aliraq.net
*/
?>
(This script can be improved!)
Upvotes: 9
Reputation: 20492
I use the "passthru" function to call "cat" command, like this:
header('Content-type: image/jpeg');
passthru('cat /path/to/image/file.jpg');
Works on Linux. Saves resources.
Upvotes: 12
Reputation: 3752
Also, if you want to the user to see a real filename instead of your scriptname when the user RMC's on the image and selects "Save As", you'll need to also set this header:
header('Content-Disposition: filename=$filename');
Upvotes: 5
Reputation: 546025
You're probably better off examining your server access logs for this. Running all images through php might put a bit of load on your server.
Upvotes: 2
Reputation: 400962
To achieve something like this, your script will need to :
This is done with the header
function, with some code like this :
header("Content-type: image/gif");
Or
header("Content-type: image/jpeg");
or whatever, depending on the type of the image.
To send the data of the image, you can use the readfile
function :
Reads a file and writes it to the output buffer.
This way, in one function, you both read the file, and output its content.
As a sidenote :
serveImage.php?file=/etc/passwd
should be OK, for instance.Upvotes: 23
Reputation: 19231
You must set the content type:
header("Content-type: image/jpeg");
Then you load the image and output it like this:
$image=imagecreatefromjpeg($_GET['img']);
imagejpeg($image);
Upvotes: 11