A List Apart

Menu
Issue № 185

Smarter Image Hotlinking Prevention

by Published in The Server Side · 90 Comments

Hey! That’s mine!

Most web professionals are all too aware of the problems caused by hotlinkers. Leechers. Bandwidth thieves. People who use images hosted on your web server on their own pages.

For some lucky people who don’t pay by the gigabyte for the amount of data they transfer, that’s not too big a deal. Who cares if some little-trafficked weblog uses your photograph of snow falling in New York?

For other sites, however, it’s a much bigger problem. If a 100K JPEG is hotlinked on a site that gets, say, 1,000 hits a day, that’s 100MB of data transferred from your site without a single person actually visiting your site. If you have only a few gigabytes of transfer available per month — or worse, pay money per gigabyte — this can add up. And if someone were to leech an entire gallery from your site …

The trouble is that the usual approaches for preventing hotlinking have a couple of side effects.

Quick fixes aren’t perfect

The usual approach is to instruct the server to deny all requests for images where the HTTP referer header 1 is not either from your own site (or blank). Thus, only people actually browsing your web site — or those whose browsers are not passing referrer headers for whatever reason — will be able to see the image.

A second approach is to redirect off-site traffic to an alternate image — either a general “hotlinking denied” image, or (in the case of some mischievous webmasters) something more shocking.

The trouble with these techniques is that regular linking is also prevented. Since browsers also send referrer headers when someone clicks a link to one of your images, the only way for people to go directly to your pictures would be to copy and paste a link into a new browser window. Granted, some webmasters might like this — it ensures that people link to the pages that photos appear on — but others may want links to succeed. Plus, if you have a gallery page with lots of images, this method makes it difficult for someone to point directly to a particular piece of your fantastic artwork.

The solution I’m about to suggest solves this problem while giving credit to you when people link to your pictures.

Where do we go from here?

With PHP and mod_rewrite, you can disallow embedding and allow linking while automatically creating gallery pages for those direct linkers. It’s the best of all worlds, and here’s how to do it.

You’ll need an Apache server capable of running PHP, with mod_rewrite enabled. If you don’t know what you have, ask your hosting company, or give it a try — if it fails, you’ll know you don’t have them.

First, create a new file called showpic.php and put this code in it:

<?php
  header("Content-type: text/html");
  header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  header("Cache-Control: no-store, no-cache,
          must-revalidate");
  header("Cache-Control: post-check=0, pre-check=0",
          false);
  header("Pragma: no-cache");
  $pic = strip_tags( $_GET['pic'] );
  if ( ! $pic ) {
    die("No picture specified.");
  }
?>

<html>
<head>
<title><?php echo($pic); ?></title>
<meta
  http-equiv="Content-Type"   c charset=iso-8859-1"
>
</head>
<body>

Image

Image from your web site.

</body>
</html>

Needless to say, you should change the HTML to match your own web site.

Let’s take a look at the PHP in there. The first line is a header to make sure the Content-Type sent to the browser identifies the document as HTML. We’ll see why this is important in a moment. The second line checks that a variable $pic has been passed to the script. If not, it skips to the end and exits quite abruptly. However, since this script should never be called without that variable (again, we’ll see why later), that’s not too much of an issue.

Assuming that this variable is there, the other lines of PHP strip any tags from it (to prevent cross-site scripting exploits), output the variable in the right place to create a valid tag, and add the file name to the page <title>.

So far, this is just a simple script. Go to www.yoursite.com/showpic.php?pic=yourimage.gif and it will output a simple page showing yourname.gif and a credit.

Now it gets interesting

If you’re an .htaccess neophyte, take a look at this introduction which will take you through the basics.

The next step is to add the following code to your .htaccess file:

RewriteEngine OnRewriteCond %{REQUEST_FILENAME} .*jpg$|.*gif$|.*png$ [NC]
RewriteCond %{HTTP_REFERER} !^$ 
RewriteCond %{HTTP_REFERER} !yoursite.com [NC] 
RewriteCond %{HTTP_REFERER} !friendlysite.com [NC]  
RewriteCond %{HTTP_REFERER} !google. [NC] 
RewriteCond %{HTTP_REFERER} !search?q=cache [NC]RewriteRule (.*) /showpic.php?pic=$1

Let’s go through this one line at a time. RewriteEngine On gets mod_rewrite ready to do its stuff. First come the conditions:

RewriteCond %{REQUEST_FILENAME} .*jpg$|.*gif$|.*png$ [NC]

Okay. First condition: the file name must end in .jpg, .gif, or .png. This makes sure our hotlink prevention only triggers on images. You might want to change this to include .swf, .mp3, or other similar files.

RewriteCond %{HTTP_REFERER} !^$

Second condition: the referrer must not be blank. This means that people who aren’t passing referrer headers, for whatever reason, will still be able to see your images.

RewriteCond %{HTTP_REFERER} !yoursite.com [NC]
RewriteCond %{HTTP_REFERER} !friendlysite.com [NC]

These next conditions allow linking from your own site, and any other friendly sites that you want to allow linking from. Change the sites to your own, of course. Apache isn’t psychic.

(Don’t know what the !  .*$ is all about? It’s a regular expression. If you keep the format the same, you don’t need to worry about it.)

RewriteCond %{HTTP_REFERER} !google. [NC] 
RewriteCond %{HTTP_REFERER} !search?q=cache [NC]

Okay. Finally, let’s let Google get through. These last conditions allow people using the Google cache and Google Image Search to see your pictures. (You might want to remove this if you don’t want people to find your pictures this way, but I don’t recommend it.)

All together now

Now let’s hook the two together. On to the last line of the .htaccess file, which is:

RewriteRule (.*) /showpic.php?pic=$1

This last rule silently redirects the request to /showpic.php?pic=[the requested file]. Thanks to the wonder of Apache, this will automatically include all necessary slashes and path information, and not be visible to the end user.

So what happens?

Now, the only way a request will have got this far is if:

  • It’s for an image file, and
  • it’s not coming from a domain that you own or are friends with.

So firstly, and most importantly, if someone tries to hotlink one of your images, it’ll fail — the browser, instead of receiving an image file, will receive the result of showpic.php, which is sent as text/html. It’ll realise it can’t display it, and produce a broken image placeholder. Bandwidth saved.

On the other hand, if someone tries to link directly to your images, they’ll get silently redirected to an HTML page with your credit on it! No red X, no silly “denied” image — just a handy page that shows them the image they want to see, and gives you credit for your work.

See it in action

First of all, let’s check that the script still allows images to load for people visiting your own web site. Yes, that looks fine. Now, let’s see if A List Apart can hotlink my images. Nope, guess not. And what happens if you just link straight to the image file? Well, there’s a nicely formatted page.

Taking it further

If you’re using some kind of content management system like Gallery, there might be a way to tie a script like this into a database of pictures, and automatically generate ALT tags and more information about the picture.

Of course, I’ll leave that as an exercise for the reader.


1 For some reason, the HTTP specifications misspell “referrer” as “referer.”

Editor’s Note: The PHP code example in this article has been edited to address a small potential cross-site scripting vulnerability, to work with register_globals and short_tag off, and to work with caching. Thanks to everyone who helped make it better.

About the Author

90 Reader Comments

Load Comments