Restricting image access

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
vivekjain
Forum Commoner
Posts: 76
Joined: Thu Jan 08, 2004 12:38 am

Restricting image access

Post by vivekjain »

Hi,
I am developing an application, wherein users would need to subscribe to access a set of images. But if the users knows of the image name, he can type in the URL and access the image directly, so there is no purpose of the subscription. How do we restrict access to the images directly?
We tried the directory protection, but then when an user signs in, it keeps asking him to enter the username and password which is frustrating. Is there any way out for this?


Thanks
bdlang
Forum Contributor
Posts: 395
Joined: Tue May 16, 2006 8:46 pm
Location: Ventura, CA US

Post by bdlang »

Using a (relatively) simple method with session data (the user), fopen() and fpassthru(), you can create a script to restrict user access to images, e.g.

Code: Select all

<?php
// image.php

// start the session
session_start();

// image base path, out of web root entirely
define('IMG_BASE_PATH', '/path/to/images/');

//  default $imagefile value
// if the image name was passed in the GET string, set; if not, set to NULL
// value will be tested later
if ( isset($_GET['img']) ) {
    $imagefile= IMG_BASE_PATH. htmlspecialchars($_GET['img']);
}
else {
    $imagefile= NULL;
}

// check the user login (very basic check)
if ( !isset($_SESSION['user']) ) {
    // user not verified
    // bogus image file for non-subscribers
    $imagefile= IMG_BASE_PATH.'baduser.jpg';

}
elseif ( !isset($imagefile) || !is_file($imagefile) )  {
    // user is verified, but image not specified or invalid
    // show 'image not here' or whatever image you like
    $imagefile= IMG_BASE_PATH.'not_here.jpg';
}

// whatever the outcome of the above, we output an image

// binary safe call to fopen
$fp= fopen($imagefile, 'rb');
// set content type to image type
header("Content-type:image/jpg");
header("Content-length:" .filesize($imagefile));
// fpassthru dumps the image contents
fpassthru($fp);
exit;

?>
and the calling page

Code: Select all

<html>
 <body>
  <img src="image.php?img=bluesky001.jpg" />
 </body>
</html>
You can see that the script checks the GET string for the image file name, then checks the user validity. If the user wasn't logged in, send a 'baduser.jpg' image. If logged in, but with a bad image file, send something else. Otherwise, outputs the image requested. Can be easily modified to allow the calling script to check user login, etc. Probably a dozen ways to do this based around the same basic calls to fopen() and fpassthru(), but I like the idea of giving the user back 'some image' rather than a broken image, so you know the application works as intended.

You should have more checks in place than this, but it's a scratched together example.

All files are stored outside the web root and cannot be accessed by any user simply typing it into the URL. Note you could also have an array stored with valid image file names, and use in_array() to check the validity of a correct file. You might also have the script check MIME type on the file requested and not hardwire a type (like image/jpg).
User avatar
bokehman
Forum Regular
Posts: 509
Joined: Wed May 11, 2005 2:33 am
Location: Alicante (Spain)

Post by bokehman »

Any solution that entails moving established files to a different directory, changing established URLs or having to edit the content of potentially hundreds or even thousands of <img> elements is, (in my opinion) not a very good one. The following is my solution: no file moving, URL swapping or document editing necessary. Simply add two lines to .htaccess and use a servlet similar to the above.

Code: Select all

<?php

/*
place the following two lines in .htaccess

RewriteEngine On
RewriteRule ^(.*)\.jpg$ servelet.php [NC,L]
*/

# check client authority here !!!

# image directory
$image_dir = './';

# check the image exists, make sure it is an image, don't deviate from the 
file_exists($image = $image_dir.basename(preg_replace('/[.][^.]+$/', '', $_SERVER['REQUEST_URI'])).'.jpg') 
	or die(header('HTTP/1.x 404 Not Found'));

# get some variables
$last_modified = date("D, d M Y H:i:s \G\M\T", (filemtime($image) - date('Z'))); // Date last modified
$image_size = filesize($image);  // Get the file size 

# If file not modified since last viewing don't send it
if($last_modified == @$_SERVER['HTTP_IF_MODIFIED_SINCE'])
{
    die(header("HTTP/1.1 304 Not Modified"));
}
else # Send the file along with correct headers
{
    header("Content-Type: image/jpeg");       
    header("Content-Length: $image_size");    
    header("Last-Modified: $last_modified");
    readfile($image);                
}

?>
Last edited by bokehman on Sat Jun 10, 2006 10:34 am, edited 4 times in total.
bdlang
Forum Contributor
Posts: 395
Joined: Tue May 16, 2006 8:46 pm
Location: Ventura, CA US

Post by bdlang »

bokehman wrote:Any solution that entails moving established files to a different directory, changing established URLs or having to edit the content of potentially hundreds or even thousands of <img> elements is, (in my opinion) not a very good one. The following is my solution: no file moving, URL swapping or document editing necessary. Simply add two lines to .htaccess and use a servlet similar to the above.
Excellent point, nice example, I think I'll use it. ;)
Post Reply