"Innovation comes only from readily and seamlessly sharing information rather than hoarding it."- Tom Peters
Development Blog

Protecting Images using PHP and htaccess

Image protection on the internet can be quite tough. With view source, and tools like Firebug, it takes a combination of tactics. In this article, we'll go over a few techniques that can be used.

Sources to check

In this article, we're going to tackle a few different tactics to safeguard images. But, let's first think about what ways people can get images.

  • Direct path - if a user knows the path to an image, they can simply go straight to it, and download the image.
  • Drag and drop - in most browsers today, you can simply drag and drop an image right onto your desktop.
  • Hotlinking - another website can link directly to your image
  • Screenshot - a visitor can simply take a screenshot of your page and crop the image.

How to fix them

So, now that we've established where our problems are, how can we fix them?

  • Direct path - we're going to create a PHP script to "hide" the direct path.
  • Drag and drop - we're going to implement something that Flickr does... placing a 1x1 transparent GIF on top of the image.
  • Hotlinking - using .htaccess, we can prevent hotlinking from occurring
  • Screenshot - not much you can do here. The best recommendation I have is to add a watermark to your image (not covered in this tutorial)

Blocking Direct Path Access

To block a user from using the direct path route, we will implement a combination of PHP and .htaccess. What we will do is create a PHP file that serves as the "proxy" for the image, and prevents the user from seeing the final path. So...let's get to it.

image.php


$_GET['f'] = "protectedImages/" . $_GET['f'];
$type = getFileType($_GET['f']);
if (acceptableType($type)) {
header("Content-type: $type");

echo file_get_contents($_GET['f']);
exit;
}
header('HTTP/1.1 403 Forbidden');
exit;

We first set the image path for the image. We point it into the 'protectedImage' directory to prevent someone from browsing around. True, they could simply set ?f=../../ and get out, but that is why we use the getFileType and acceptableType functions.

getFileType()


function getFileType($file) {
//Deprecated, but still works if defined...
if (function_exists("mime_content_type"))
return mime_content_type($file);

//New way to get file type, but not supported by all yet.
else if (function_exists("finfo_open")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $file);
finfo_close($finfo);
return $type;
}

//Otherwise...just use the file extension
else {
$types = array(
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'bmp' => 'image/bmp'
);
$ext = substr($file, strrpos($file, '.') + 1);
if (key_exists($ext, $types)) return $types[$ext];
return "unknown";
}
}

In this simple function, we first try using mime_content_type. This function has been deprecated, but if it's defined, we'll try to use it. Otherwise, we'll try the Fileinfo library that was added into the PHP 5.3.0 and PECL fileinfo 0.1.0. But, if that isn't available, we'll just revert to using the good ol' file extension method. Since I assume you uploaded all of the files yourself, I assume it is safe to use. You may want to alter this if users are allowed to upload their own pictures.

acceptableTypes()

Now that we have our file type, let's validate it. This will prevent users from trying to get PHP files, or any sort of file they shouldn't get. Here it is...


function acceptableType($type) {
$array = array("image/jpeg", "image/jpg", "image/png", "image/png");
if (in_array($type, $array))
return true;
return false;
}

Of course, if you want to use other image types, feel free to add them to the array. But, those are some of the common ones (sorry bmp and gif).

From there, we set our header to tell the browser what we're returning, and then send the browser the contents of the image we want to send. The exit is important because you don't want to return a 403, which is the default for a bad request.

Your image script should work just fine now. Try it out, and see how it goes. Don't forget to change the image directory if you have a folder named something other than "protectedImages".

Preventing Drag and Drop

To prevent drag and drop of images, we're going to use a little trick that I noticed Flickr was using. They simply stretch a 1x1 transparent gif over the image, so when a user either tries to drag and drop (or right-click and "Save image as"), the image saved is the gif, not the actual image.

First, download a 1x1 transparent gif here.

Now, in your HTML, all you will need to do is add in the image. So, let's do that first. Here is a pretty simple html page we'll work with.


<html>
<head>
<title>Page Title</title>
</head>
<body>
<div class="image">
<img src="image.php?f=image.jpg" alt="Image" />
<div class="cover"><img src="imageCover.gif" alt="" /></div>
</div>
</body>
</html>

You'll see that we added an image tag that uses the image.php file we just created. Of course, we will need to add some css to make this all work correctly though. Here's how to do it:


.image {
overflow: hidden;
position: relative;
float: left;
}
.image .cover, .image .cover img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}

So, what's this do? The wrapping div (.image) is given a position: relative to allow us to absolute position the cover image inside this div. Otherwise, the cover image would be positioned to the browser's top-left corner (unless another div had position:relative). The float:left and overflow:hidden forces the div to be wrapped only around the content of the div, not the entire width of the browser. This also ensures that the image cover, when given 100% width uses the width of the div, not the window.

So, test it out and see what happens! The cover image appears on top, and if you try to right-click and save, it grabs the transparent image, not the real image. Then, try to drag and drop it onto your desktop. Same thing!

Disabling hotlinking

In this scenario, we are forcing PHP to handle the image handling. So, we can technically deny ALL requests for images within the protectedImages directory. How do we do that? Create a .htaccess file within the protectedImages folder, and paste this code into it.


#Prevent directory listing
Options -Indexes

#Prevent images from being viewed
<Files *>
deny from all
</Files>

The first line prevents any sort of directory listings. Some servers do this automatically, but others may not. Without this, a user may be able to see a directory listing of all files. The second set of parameters tells the server to deny any and all files. If anyone tries to visit the page, they will get a "Forbidden" message.

Further protection on the image.php file

Although we have the image.php file handling all of the images, it really isn't protected. Try inserting the URL for the image src directly into your browser. You'll get an image, and you can download it. Let's do a little trick to prevent that. What we'll do is two things: force the image.php to be referred by the site and allow the user to get images only within two seconds of visiting a page.

Forcing the referral

When a browser visits your index.html page, any images that are requested will have a referrer of your page. So, we can use that. We'll use .htaccess to handle that!


RewriteEngine on
RewriteCond %{HTTP_REFERER} ^$
RewriteCond %{SCRIPT_FILENAME} image\.php
RewriteRule (.*) image.php?onlyHappensFromHTACCESS=denied [QSA,L]

This uses the RewriteEngine module in Apache servers. It has two different conditions. It first ensures that the referrer is not empty, and checks that the page being requested is the image.php script. If both of these are true, we'll redirect the request to go to image.php?onlyHappensFromHTACCESS=denied. What we'll do is display a different message to the user.

In the image.php file, we need to add a check for this. Around your previous code, wrap a check for $_GET['onlyHappensFromHTACCESS']. Here's what it looks like:


if (!isset($_GET['onlyHappensFromHTACCESS'])) {
//Our previous code here
}

Since our page exits every time if we run our own code, the 'else' for the above statement can be a plain HTML page. Here's what I use.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Image Denied</title>
<style type="text/css" media="screen">
body {
background-color: #ccc;
font-family: Helvetica, Arial;
}
#wrapper {
margin: 30px auto;
background-color: #ffffff;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
width: 800px;
padding: 20px;
}
</style>
</head>

<div id="wrapper">
<h3>Access Denied!</h3>
<p>You have tried to access an image, but due to security reasons, you cannot view the image.</p>

<p>If you wish to use the image you requested, please contact me.</p>
</div>
</html>

Session timer

We'll also add in a two-second "timer" to ensure the user visited one of our pages first.

In each page you are going to use (index.php in our case), add a simple session variable we can check. Here's an example:


session_start();
$_SESSION['lastcheck'] = time();

Then, in our image.php, we need to check this variable, and see if it's within our allotted time frame. Here's what I have:


function goodTiming() {
$n = time();
session_start();
if ($n - $_SESSION['lastcheck'] > 2 )
return false;
return true;
}

Of course, if you're using a session earlier, you don't need to start it in this function. Now, the final step is to add it into the image.php checks.


if (!isset($_GET['onlyHappensFromHTACCESS'])) {
$_GET['f'] = "pictures/" . $_GET['f'];
$type = getFileType($_GET['f']);
if (acceptableType($type)) {
if (goodTiming()) {
header("Content-type: $type");

echo file_get_contents($_GET['f']);
exit;
}
}
header('HTTP/1.1 403 Forbidden');
exit;
}

Now that we've done all of this, our images are pretty much safe. Of course, there are still ways to get it, but there is no 100% way to safeguard your images. If you've got it on the internet, it can be retrieved. But, this will at least deter the simple attempts.

Full code source

image.php


<?php
if (!isset($_GET['onlyHappensFromHTACCESS'])) {
$_GET['f'] = "protectedImages/" . $_GET['f'];
$type = getFileType($_GET['f']);
if (acceptableType($type)) {
if (goodTiming()) {
header("Content-type: $type");

echo file_get_contents($_GET['f']);
exit;
}
}
header('HTTP/1.1 403 Forbidden');
exit;
}

function getFileType($file) {
if (function_exists("mime_content_type"))
return mime_content_type($file);
else if (function_exists("finfo_open")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $file);
finfo_close($finfo);
return $type;
}
else {
$types = array(
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'bmp' => 'image/bmp'
);
$ext = substr($file, strrpos($file, '.') + 1);
if (key_exists($ext, $types)) return $types[$ext];
return "unknown";
}
}

function acceptableType($type) {
$array = array("image/jpeg", "image/jpg", "image/png", "image/png");
if (in_array($type, $array))
return true;
return false;
}

function goodTiming() {
$n = time();
session_start();
if ($n - $_SESSION['lastcheck'] > 2 )
return false;
return true;
}

?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Image Denied</title>
<style type="text/css" media="screen">
body {
background-color: #ccc;
font-family: Helvetica, Arial;
}
#wrapper {
margin: 30px auto;
background-color: #ffffff;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
width: 800px;
padding: 20px;
}
</style>
</head>

<div id="wrapper">
<h3>Access Denied!</h3>
<p>You have tried to access an image, but due to security reasons, you cannot view the image.</p>

<p>If you wish to use the image you requested, please contact me.</p>
</div>
</html>

index.php


<?php session_start(); $_SESSION['lastcheck'] = time(); ?>
<html>
<head>
<title>Page Title</title>
<style type="text/css">
.image {
overflow: hidden;
position: relative;
float: left;
}
.image .cover, .image .cover img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="image">
<img src="image.php?f=image.jpg" alt="Image" />
<div class="cover"><img src="imageCover.gif" alt="" /></div>
</div>
</body>
</html>

.htaccess in main folder


RewriteEngine on
RewriteCond %{HTTP_REFERER} ^$
RewriteCond %{SCRIPT_FILENAME} image\.php
RewriteRule (.*) image.php?onlyHappensFromHTACCESS=denied [QSA,L]

.htaccess in protectedImages folder


#Prevent directory listing
Options -Indexes

#Prevent images from being viewed
<Files *>
deny from all
</Files>

Feel free to post comments!


Dave's picture
Mar 26, 2010
4:28 pm
Dave

Thanks for your interesting post, I´ve just diabled hotlinking and tried some other things as it takes a lot of time to get a real good photograph, and I want to show it on the internet, but I don´t want otherst to use ist for their one pages.

Tracy's picture
Oct 30, 2012
10:41 pm
Tracy

Hi there everybody, here every person is sharing these familiarity, thus it's nice to read this webpage, and I used to visit this blog everyday.

Addie's picture
Mar 14, 2013
10:57 am
Addie

Indeed it's tough to protect images online but we should be thankful at least that we have these options. What happens when you work with social networking sites? Are there any security methods we should be aware of?

Henk's picture
Apr 6, 2010
4:43 am
Henk

Hello,

I am testing the source on my ISP server Apache.
Work ferry nice and good.
Now i want to test it on my localhost XAMPP Apache server at home.
This dont work, i think the problem is in htaccess??
I have rewrite on and all for overwrite.
Can you help me?

Regards Henk from the Netherlands.

mikesir87's picture
Apr 6, 2010
7:33 am
mikesir87

Are you sure you've got mod_rewrite enabled on your XAMPP server? If you just did it, make sure to restart the server, and then try again.

Try this link and see if it helps out:
http://www.lancelhoff.com/enabling-htaccess-in-apache-on-windows/

Henk's picture
Apr 6, 2010
3:35 pm
Henk

Dear Mikesir87,

Thanks for your reaction,
I have found why it dont work on XAMPP.
Its in de image.php source.
I have marked oud this function
[code]
if (acceptableType($type)) {
xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxx
} [/code]
and yes it works fine.
I wil try to find out why?

Thanks and regards Henk...

mikesir87's picture
Apr 6, 2010
5:15 pm
mikesir87

What kind of image are you trying to use? (jpg, gif, png, etc.) Check what the $type variable is set to, and let me know what that is.

Fraso's picture
Apr 7, 2010
7:49 pm
Fraso

Does this stop people from saving the webpage to their computer and then going through the files that come with the html page? Or using firefox's nifty media browser?

Obviously these things will stop regular users from getting your images, but I always thought the only way to really protect the source image was using flash... and if they are really determined (and don't care about the quality) they could always SS that.

I saw your "If you've got it on the internet, it can be retrieved. But, this will at least deter the simple attempts." disclaimer, so obviously tech-savvy people are going to be the ones getting around it. Would it be too much of a pain to put up a demo (or if you know of a site that uses all of these) I wanna try to break it in as many ways as I can. =D

Not the theme of the post, but you could always have copyright law on your side... if it's your picture... published works are automatically copyright.

mikesir87's picture
Apr 8, 2010
5:20 pm
mikesir87

@Fraso

I added a demo...and you are correct. There are still ways to get the image. This just simply blocks the "typical" user from figuring out how to get it. Maybe I'll do a post on how to add a watermark or something to the image.

Have fun with trying to break it, and let me know what you find!

Christian Voigt's picture
May 25, 2010
3:47 pm
Christian Voigt

Turn off css (for FF : View->Page Style->no style), right click, save image...
or left click on the little area left of your adress bar, click the media-tab and save the image.

Thats just two ways to do it in FF.

not much to it i'm afraid :)

If you really want to prevent the public from downloading your pictures, don't make them public... jumping through hoops for something you can break with four mouse-clicks is really not worth all that work.

mikesir87's picture
May 25, 2010
3:52 pm
mikesir87

Yeah... you're right. But, this will stop those that aren't really the developers and know the hoops you can go through... which accounts for a pretty good majority of the normal internet users.

I do agree with you though... if you don't want people to get it, don't make it public.

Christian Voigt's picture
May 25, 2010
4:24 pm
Christian Voigt

Two years ago, i would have agreed. I would still agree, but things are happening so fast. The six year old son of a friend of mine wanted to save a protected picture, fiddled with it for a few seconds and then googled, "download protected picture". One of the first four hits took him to a tutorial on how do do it fast and easy. (just googled it myself, very nice tutorials on how to break any kind of protection)

So i agree that you can protect your pics from 100% noncoms... but what would they do with it anyway. And everyone remotely familiar with google will have no trouble at all.

I would recommend protecting your pictures from hotlinking, that is still a useful technique and will prevent hijacking.

"normal" internet users are a dying breed. Even my mum is ripping mp3's from online flash-players, after googling for a few minutes on how to do it. She calls on me to update her Antivirus-Software, but she knows how to get what she wants if she sets her mind to it :)

It's a nice approach though, good stuff for a tutorial, i am sure many people can learn a thing or two from your code.

Anonymous's picture
Jul 27, 2010
10:30 pm
Anonymous

Instead of using an tag for the image use a div with a CSS background. This way, if they disable the style the won't see an image.

With the fix, it would prevent everything but screen capture. Now, I just need to implement this using a jQuery gallery. Any thoughts?

Christian Voigt 's picture
Jul 28, 2010
4:42 am
Christian Voigt

You'd still have it pretty easy with integrated media-browsers. Like the aforementioned Media-Tab in Firefox for Example.

A way to really inconvenience people would be to slice the image to chunks and rebuild them on display. They could still download the chunks, but then have to start up some image editor to piece the puzzle together. Or go with the screen-grabber option after all...

I am sure some scripts would help on the developer side, like automatic splitting and generating needed html/css fragments for display...

But as always, I haven't put much thought to this idea, and I still doubt that any effort to protect public images any other way than with a simple copyright notice is just too much work to be economically viable.

mikesir87's picture
Jul 28, 2010
6:43 pm
mikesir87

I agree with the things Christian said. To really protect images, the only thing you can really do is watermark it. There's just too many tools out there to scrap them (or screen capture them).

However, I do like the idea of a image-slicing tool. Might be worth developing at some point... ??

Jason's picture
Jul 28, 2010
6:56 pm
Jason

For the average viewer it will be tough but still possible for programmers like us. I think smugmug has the best one out there. You really have to sift though using firebug in order to find the image but alas it can be found.

I think the best way is going to use a flash gallery. This will give the only option of screen capture. I really don't want to rack my brain about trying to protect images anymore, it just can't be done. Even some watermarks can be erased using photoshop. As a joke, my friend un watermarked one of my photos and sent it back to me with his watermark!!

Bob Holloran's picture
Aug 24, 2010
7:01 pm
Bob Holloran

The jpg and pdf images that I need to protect are only accessible from a password protected php/MySQL driven website. The only vulnerability of concern is the direct downloading using the URL. So, most of the code listed is not necessary. However, several of my php templates need to be able to access the files in the protected directory based upon SQL queries of the database that provide the file name of the image/pdf to be rendered. I have also tried locating the files in directories outside the html root directory, but my php scripts have not been able to render the images located in these directories. I am able to upload both jpg and pdf files and relocate them to the directories outside the html root directory, as well as to .htaccess protected directories.

What parts of your code do I need to use and where would they go?

mikesir87's picture
Aug 24, 2010
7:20 pm
mikesir87

Bob,

If the images are accessible when only logged in, you can add whatever checks you want at the beginning of the image.php script I have above. You can check if they are logged in, if they have the correct access privileges, etc.

As far as why your php scripts have not been able to render the images in your directories, I would first check permissions on the directories. I would assume they are right since you can write to it, but still worth a check on the read ability. Your php scripts won't be affected by your htaccess protection, so should be able to simply use file_get_contents, and echo that out (after setting your headers appropriately).

As I'm not 100% sure what errors you're getting, feel free to send a contact me, and we can start corresponding via email. I would love to help out.

Sunil's picture
Dec 21, 2010
2:58 am
Sunil

Thanks...........
This is an awesome article.
And i learnt a lot from this article.
Keep it up!!!
Once again thanks for sharing your knowledge...

Anonymous's picture
Jan 25, 2011
7:42 am
Anonymous

Try this... PrintScreen :-] PWNED! ;-)

Ajit Kumar Singh's picture
Feb 22, 2011
2:44 pm
Ajit Kumar Singh

I think its a interesting article, But there be a small problem on it.
By using firfox >> Go to source file >> save >> image.php

Now just rename the file image.php to image.jpg and you will able to view.

Anonymous's picture
Mar 8, 2011
12:07 pm
Anonymous

"Innovation comes only from readily and seamlessly sharing information rather than hoarding it."- Tom Peters

Anonymous's picture
Apr 16, 2011
6:54 am
Anonymous

Hi your code was really excellent. Finally I decided to use your code in my project. But will definitely share the code if any thing amended.

Anonymous's picture
Jun 4, 2011
12:33 am
Anonymous

Hi. I'm using jquery prettyphoto image gallery to display my images. When u click on the image on a page it will bring a modal window which shows the image and u can click next or prev to view other pic. So will this .htaccess works (which prevent Forcing the referral)? That modal window I believe it's just something created from ccs and jquery so will it still have referral? Hope u can let me know what u think. Thank you.

Pradeep Singh's picture
Jul 23, 2011
6:14 am
Pradeep Singh

Hi,

I am able to save this image very easly in IE8 browser.

Can you please check and provide more secure solution.

Thank You,
Pradeep K Singh

Anonymous's picture
Sep 19, 2011
11:13 am
Anonymous

do you have a solution for Wordpress?

Detlev's picture
Dec 26, 2011
2:37 am
Detlev

I don't recommend using this method. This entire download-prevention is based on a single CSS div overlaying on top of the image. Disable styling in your browser and bye-bye security.

Tobias Beuving's picture
Jan 29, 2012
5:51 am
Tobias Beuving

Dear Michael,

In the code examples appear breaks and paragraph tags, might be a little confusing for novice coders. Probably easily fixable.
Great article though - I like the 'onlyHappensFromHtAccess' trick! :)

Cheers,

Tobias

James's picture
Feb 24, 2012
2:19 am
James

Hello, I loved your tutorial and the clever tricks it used; however, it slowed my image loading drastically. This is because I don't know how to add expire headers on the image viewer php file. Can you give me tips on how to do this?

Thanks a ton!

James

James's picture
Feb 24, 2012
1:53 pm
James

Sorry for the spam... I didn't see my comments being posted even when I reloaded. I solved my problem, there was a syntax error in my header declaration.

Thank you,

James

hassan's picture
Feb 28, 2012
8:53 am
hassan

I m looking for somewhat similar script and using CHImageGuard Script, but both scripts has following flaws.

In Internet Explorer (IE), protected image can be found in Internet Files Cache (a little hard thing)

In FireFox (FF):
right click on the page, then
"View Page Info" then click on the
"Media" Tab and the protected image is right with
"Save As" option (veryyyy easy thing)

To overcome this image cache / media problem, I found a trick and thats to display image through flash container. I m using SWFIR.SWF by passing image url through FLASHVARS.

Now the 2nd Problem I face (both in your and CHImageGuard Script, that flashvars do not send refer info to FireFox browser and thus, both scripts take it as someone trying to access image directly and results in no image.

If you further improve your script to bypass refer check and use some other mechanism, so this script can work with FlashVars too, this might become the best image protection script :)

My statements can be verified on the given home page http://kitaabghar.net/images/ht-php-img-sec/flash-test.php

Anonymous's picture
May 8, 2012
10:26 am
Anonymous

Hello

My name is George

Did you notice something wrong ?

matt's picture
Aug 23, 2012
5:37 pm
matt

I'm trying to modify your script to serve .m4v files from a protected directory. I can get it to serve jpg images fine from the same directory my m4v file is in but I just get a broken image icon when trying to link to an m4v file instead.

I've added the extension and mime type in the appropriate places:

$types = array(
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'bmp' => 'image/bmp', 'm4v' => 'video/mp4'
);

and

function acceptableType($type) {
$array = array("image/jpeg", "image/jpg", "image/png", "video/mp4");

Is there something else I need to do? I'm not using the htaccess part (my directory is above my web root so should only be accessible from content in this script).

Thanks for any help you can offer. :-)

-Matt

matt's picture
Aug 27, 2012
4:27 pm
matt

OK, I figured it out for myself and then some. Giving back now. :-)

I cobbled together stuff from this page as well as a page at http://mobiforge.com/developing/story/content-delivery-mobile-devices to build what I wanted, which is essentially a php proxy server for a couple of different media types.

Reasons for doing this:
1. I want to protect my content (jpeg thumbnails and nice-sized m4v files) by placing it into a directory OUTSIDE of my web root
2. I want a method of direct-like linking to show a particular m4v file via simple links while still keeping the files outside of the web root
3. I want to be able to authenticate access to these files against a user login system (mysql db) to keep things secure
4. I want it to work with desktop browsers as well as iPads/iPhones (hence the rangeDownload function to deal with some peculiarities of this)

My testing so far indicates that this thing actually works. :-)

So, to link to images/m4vs in a web page, my links look like so:

some image

If I want to email a link to just a particular file, I can just send a link such as:

http://foo.com/proxy.php?f=5248.m4v

and it will redirect to a login page to authenticate against my database (I left the authentication code out as it is specific to my setup) and then serve the file if authentication is successful.

I've tested it on a few iPads, iPhones and my desktop machine (Chrome, Safari) and it all seems to work.

-Matt

<?php
$_GET['f'] = "../media/" . $_GET['f'];
$type = getFileType($_GET['f']);

if (acceptableType($type)) {
header("Content-type: $type");
rangeDownload($_GET['f']);
exit;
}
// if requested file not in acceptable list
header('HTTP/1.1 403 Forbidden');
exit;

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

function getFileType($file) {
if (function_exists("mime_content_type"))
return mime_content_type($file);
else if (function_exists("finfo_open")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $file);
finfo_close($finfo);
return $type;
}
else {
$types = array(
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'bmp' => 'image/bmp',
'm4v' => 'video/mp4'
);
$ext = substr($file, strrpos($file, '.') + 1);
if (key_exists($ext, $types)) return $types[$ext];
return "unknown";
}
}

function acceptableType($type) {
$array = array("image/jpeg", "image/jpg", "image/png", "video/mp4", "video/x-m4v");
if (in_array($type, $array))
return true;
return false;
}

function rangeDownload($file) {

$fp = @fopen($file, 'rb');

$size = filesize($file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
header("Accept-Ranges: 0-$length");
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (isset($_SERVER['HTTP_RANGE'])) {

$c_start = $start;
$c_end = $end;
// Extract the range string
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
// Make sure the client hasn't sent us a multibyte range
if (strpos($range, ',') !== false) {

// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
// (?) Echo some info to the client?
exit;
}
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if ($range0 == '-') {

// The n-number of the last bytes is requested
$c_start = $size - substr($range, 1);
}
else {

$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
$c_end = ($c_end > $end) ? $end : $c_end;
// Validate the requested range and return an error if it's not correct.
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {

header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
// (?) Echo some info to the client?
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1; // Calculate new content length
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
// Notify the client the byte range we'll be outputting
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");

// Start buffered download
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {

if ($p + $buffer > $end) {

// In case we're only outputting a chunk, make sure we don't
// read past the length
$buffer = $end - $p + 1;
}
set_time_limit(0); // Reset time limit for big files
echo fread($fp, $buffer);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}

fclose($fp);

}

?>

Kawa's picture
Sep 17, 2012
8:39 am
Kawa

thanks to much , i liked this code , it is served me

cheers

cj's picture
Oct 9, 2012
12:11 am
cj

..I managed to get the image using Opera Browser "Save Page with images" feature :(

thomas's picture
Oct 10, 2012
5:45 am
thomas

I can use firebug and display:none the cover class and I can save your image

Me's picture
Nov 8, 2012
4:47 pm
Me

Hi, I am trying to use image.php to display image but it does not seem to be working! Being a novice programmer it is kinda hard to get my head around this.

image.php == the whole file you have displayed on your website.
.htaccess == stored inside the images folder

location of image.php : php/image.php
location of images: Images/

So, I changed "protectedImages/" to "Images/"

In my html file, I display an image using

Image

But somehow I get a text "Image" on the webpage and no image?

An guidance would be appreciated?

Anonymous's picture
Aug 11, 2013
5:44 pm
Anonymous

Do you have any way except timing session?

I have tried this, it works, but if there are many pictures in 1 pages the other pictures will be not loaded.

Thank you.