If your subscribers have to login to your website, then you could use a PHP script in your images folder to check that they are logged in before serving an image in the same way you would check this before serving them a subscriber-only webpage.
Your .htaccess file might have:
# Protect subscriber-only assets from hot-linking (*.gif)
RewriteCond %{REQUEST_URI} (.*)\.gif
RewriteRule ^(.*\.)(gif)$ protected-asset.php?f=$1$2 [L]
# Protect subscriber-only assets from hot-linking (*.jpg)
RewriteCond %{REQUEST_URI} (.*)\.jpg
RewriteRule ^(.*\.)(jpg)$ protected-asset.php?f=$1$2 [L]
And then your protected-asset.php file might have:
<?php
$sWebrootPath = '/var/www/www.mydomain.com/';
$sAsset = isset( $_GET['f'] ) ? $sWebrootPath . $_GET['f'] : '';
$aHeaders = array();
/* HTTP 404 if asset does not exist */
if( !file_exists( $sAsset )) {
header( 'HTTP/1.0 404 Not Found' );
exit;
}
/* Check if request comes from authenticated subscriber */
require_once( 'classes/session.php' );
if( Session::UserId() == 0 ) {
header( 'HTTP/1.0 401 Unauthorized' );
exit;
}
/* Declare MIME-Type */
$sFileExt = strtolower( substr( $sAsset, 1+stripos( $sAsset, '.' )));
switch( $sFileExt ) {
case 'gif': $sMimeType = 'image/gif'; break;
case 'jpg': $sMimeType = 'image/jpg'; break;
default: $sMimeType = 'text/plain'; break;
}
$aHeaders[] = 'Content-Type: ' . $sMimeType . '; charset: UTF-8';
/* Cache for 1 hour (you could extend this as required) */
$iCacheSecs = 60 * 60;
$dtNow = time();
$dtExpires = strtotime( sprintf( '+%s seconds', $iCacheSecs ));
$aHeaders[] = 'Expires: ' . date( 'r', $dtExpires );
$aHeaders[] = 'Last-Modified: ' . date( 'r', $dtNow );
$aHeaders[] = 'Cache-Control: public, must-revalidate, ' .
sprintf( 'max-age=%s', $iCacheSecs );
/* Generate unique ETag */
$sData = @file_get_contents( $sAsset );
$sETag = md5( $sData );
$aHeaders[] = sprintf( 'ETag: %s', $sETag );
/* HTTP 304 if asset has matching ETag to browser cached copy */
$sSuppliedETag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ?
$_SERVER['HTTP_IF_NONE_MATCH'] : '';
if( $sSuppliedETag == $sETag ) {
header( 'HTTP/1.1 304 Not Modified' );
header( 'Content-Length: 0' );
exit;
}
/* GZip Compression */
if( !ob_start( 'ob_gzhandler' )) ob_start();
/* Respond with Asset headers and data */
foreach( $aHeaders as $sHeader ) header( $sHeader );
echo( $sData );
The two snippets in the above code that won't just work straight away and will need replacing with your own code are the require_once( 'classes/session.php' );
and Session::UserId()
. The premise here being that the require_once loads any pre-requisite files you might need to identify if someone is logged-in or not, and my UserId() function was returning 0 to indicate no authenticated user assigned to the current session. You'll probably need to use a cookie to manage your sessions etc but it sounds like you already have the subscriber login part your website already setup and working so just copy some code across to replace these two parts and it should work ok for you.
While in principle I'm all for best practice, I generally don't put <IfModule>
statements in .htaccess files as they can typically cause more problems than they solve in my experience - it may be worth trying your existing code without these before trying my solution!