I have a solution, even if it is not very beautiful, because it leaves the default error 403 document changed even if the image is not hotlinked:
This goes into apache2.conf:
SetEnvIfNoCase Referer "livejournal.com" hotlink
<FilesMatch ".(gif|jpg|jpeg|png)$">
ErrorDocument 403 /server404/no_hotlink.php
</FilesMatch>
<FilesMatch ".(gif|jpg|jpeg|png|mpg|avi|flv)$">
deny from env=hotlink
</FilesMatch>
Alias /server404 "/var/www/_404"
<Directory "/var/www/_404">
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
We used the /server404 path for a serverwide 404 error document for some time, so we already had that. You could also call it 403 or GlobalNoHotlink or whatever you want.
For images (first FilesMatch) we set the "forbidden" ErrorDocument to a PHP file in our /server404 directory. Then we check if it is hotlinked (there we also do that for video files, thus two FilesMatch tags) and forbid access if hotlinked.
This goes into /var/www/_404/no_hotlink.php - the PHP file called on forbidden image:
<?PHP
$file = dirname(__FILE__) . '/_default/no_hotlink.png';
header('HTTP/1.1 200 OK', true, 200);
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
header('Content-length: ' . filesize($file));
header('Content-type: image/png');
readfile($file);
?>
- The first line is used to identify the image file we want to send out.
- Then we set the HTTP header to 200 (this is optional. Decide for yourself),
- add some headers to avoid caching of the image (we don't want to let that 8000x8000 pink pixel file appear "from cache" on our site if the person decides to go to our site, do we?).
- Content-length according to the dummy file's size and
- MIME type (You might have to change the latter of not using PNG).
- Then we read the file.
It works - but it is not as beautiful as I'd like to have it, so will still be happy about other solutions.