Page 1 of 1

Restricting image access

Posted: Sat Jun 10, 2006 12:27 am
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

Posted: Sat Jun 10, 2006 3:10 am
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).

Posted: Sat Jun 10, 2006 9:11 am
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);                
}

?>

Posted: Sat Jun 10, 2006 10:25 am
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. ;)