Jump to content

Protect original Images in site/assets/files/


horst
 Share

Recommended Posts

Hi, on a site I want to disable access to original images and only allow to access thumbnails and watermarked image variations.

EDIT:
A good solution for protecting original images can be found a bit down in this thread:

 

Old content of this initial post:

Spoiler

To create the watermarked images I use the PiM, what creates filenames with prefix "pim_". For individual thumbnails I use the Thumbnails-Module and define the prefix like "thumb_".

A simple way would be to use a htaccess file in top of the assets folder with something like this for example:

The only downside is that I cannot access the original images from the admin backend. While it isn't an option to use a proxy-script from the front-end, it could be an option for the admin.

Any ideas how I could access the originals when in admin backend?


# deny access to all jpegs and pngs
<FilesMatch "^.*\.(jpg|png)$">
    Deny from all
</FilesMatch>

# allow access to pim_ - variations (my watermarked images)
<FilesMatch "^pim_.*\.(jpg|png)$">
    Allow from all
</FilesMatch>

# allow access to thumb_ - prefixed files (my individual thumbnails)
<FilesMatch "^thumb_.*\.(jpg|png)$">
    Allow from all
</FilesMatch>

# allow access to 100px thumbnails as used by default in PW-Admin when images-field is configured to display thumbnails
<FilesMatch "^.*\.0x100\.(jpg|png)$">
    Allow from all
</FilesMatch>
<FilesMatch "^.*\.100x0\.(jpg|png)$">
    Allow from all
</FilesMatch>

 

  • Like 3
Link to comment
Share on other sites

Hi Horst, 

maybe you could set up a rule which takes care of the referrer, something like this?

RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]

Only if the referrer equals your domain, allow access to the originals. Problem could be that your server does not use http referrers...

Link to comment
Share on other sites

 Only if the referrer equals your domain, allow access to the originals. Problem could be that your server does not use http referrers...

Maybe something like domain/processwire, but a referrer header could be set to anything one want, I guess.

Better could be to use the REMOTE_ADDR. Does someone know how a variable for apache could be set by script? If this could be done everytime a superuser log in I could set this IP to a apache / environment variable for which access is allowed.

Link to comment
Share on other sites

Private file handling is your solution. You would abstract all files from public access by doing something like... 

/files.php?file=myfile.jpg

vs.

/myfile.jpg

Then in your private file handler you can check permissions and show the users the correct image according to their access level. I'm not sure if idea is native to PW.

  • Like 1
Link to comment
Share on other sites

@sshaw: what you are describing is what I have called proxy-script in my opening post. I don't want that for frontend output, because it is not needed and I want avoid overhead. All images that should have public access can be accessed directly. Others (the originals) are blocked to the public. Thats fine.

The only downside is that an admin in backend also cannot access the originals.

What I'm looking for is how I can define this in htaccess file:

all images that are now blocked for direct access should be not served but handled by a proxy script.

Link to comment
Share on other sites

To do it your way, maybe use RedirectMatch in place of your first FilesMatch
RedirectMatch \.(png|gif|jpg|jpeg)$ http://example.com/image_handler.php
I think mod rewrite will be your ideal solution, several examples below that will get you close. Personally I'd rewrite all image requests to my proxy script, then store my allowed prefixes in an array and simply loop through the array. It's not the best solution, but it's simple and allows for some scalability. 
 
in .htaccess 
 
Redirect all images to proxy script
RewriteEngine On
RewriteBase /
RewriteRule ^(.*)\.(jpg|png|jpeg|gif)$ watermark.php?image=$1.$2 [NC,L]
in your proxy script watermark.php
$prefixes[] = "thumb_";
$prefixes[] = "100x0";

foreach ($prefixes[] as $prefix) {
    if((substr(0,strlen($prefix)) == $prefix) xor ($user->isSuperuser())) {
        //return the image
    } else {
        //return a different image (maybe one that says access to this file is denied)
    }
}
Some rewrite examples that may help:
 
Redirect requests that don't originate from the host www.example.com (stop hotlinking) 
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !www.example.com [NC]
RewriteRule \.(gif|jpg|png)$ /images/go-away.png [R,NC]
Check for a login cookie and redirect if not logged in
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP_COOKIE} !CookieName= [NC]
RewriteRule .* http://www.example.com/members/login.php [L]
Link to comment
Share on other sites

coming back to this: I have made good progress!
 
I do not use the htaccess file in site/assets/files/ anymore but have edited the htaccess file in pw root folder. Somewhere at top of the mod_rewrite directives I have added my lines that should redirect requests to original images to a proxy-script and let others pass through:
 

htaccess with Pim1 and PW < 2.5.11

Spoiler

...

  # -----------------------------------------------------------------------------------------------
  # Optional: Set a rewrite base if rewrites aren't working properly on your server.
  # And if your site directory starts with a "~" you will most likely have to use this.
  # -----------------------------------------------------------------------------------------------

  RewriteBase /
  # RewriteBase /pw/
  # RewriteBase /~user/

    # -----------------------------------------------------------------------------------------------
    # CUSTOMSETTING : redirect original images to proxy-script - /pwimg.php?fn=...
    # -----------------------------------------------------------------------------------------------

    RewriteCond %{REQUEST_FILENAME} -f
    RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/(.*?)/
    RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$ [NC]
    RewriteCond %{REQUEST_FILENAME} !/pim_
   #RewriteCond %{REQUEST_FILENAME} !/(thumb_|thumb2_)
    RewriteCond %{REQUEST_FILENAME} !.*/.*?\.([0-9]+)x([0-9]+)\.(jpg|png|jpeg|gif)$ [NC]

    RewriteRule ^(.*)$ pwimg.php?fn=$1 [L,QSA]

  # -----------------------------------------------------------------------------------------------
  # Access Restrictions: Keep web users out of dirs that begin with a period
  # -----------------------------------------------------------------------------------------------

...

 

 

 .htaccess with PW 2.5.11+ / PW 3+

        # -----------------------------------------------------------------------------------------------
        # CUSTOMSETTING : redirect original images to proxy-script - /pwimg.php?fn=...
        # -----------------------------------------------------------------------------------------------

        RewriteCond %{REQUEST_FILENAME} -f
        RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/(.*?)/

        RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$ [NC]
        RewriteCond %{REQUEST_FILENAME} !-piacrop
        RewriteCond %{REQUEST_FILENAME} !-piacontain
        RewriteCond %{REQUEST_FILENAME} !-pim2-full
        RewriteCond %{REQUEST_FILENAME} !-blogthumb
        RewriteCond %{REQUEST_FILENAME} !.*/.*?\.([0-9]+)x([0-9]+)\.(jpg|png|jpeg|gif)$ [NC]
        RewriteRule ^(.*)$ pwimg.php?fn=$1 [L]

Now from all existing images the originals get redirected to the proxy-script and the others will delivered directly by apache. Requests to none existing imagefiles get answered by a 404.
 
So as everything seems to work fine, the RewriteConditions could be optimized a bit.
 
 
---- pwimg.php ----

<?php

// check filename
$imgFilename = isset($_GET['fn']) ? preg_replace('/[^a-zA-Z0-9_\-\/\.@]/', '', $_GET['fn']) : false;
$imgFilename = is_file(dirname(__FILE__) . "/$imgFilename") && is_readable(dirname(__FILE__) . "/$imgFilename") ? dirname(__FILE__) . "/$imgFilename" : false;
if (false == $imgFilename) {
    header('HTTP/1.1 404 Not Found');
    exit(2);
}

// check imagetype
$imgType = getImageType($imgFilename);
if (false == $imgType) {
    header('HTTP/1.1 403 Forbidden');
    header('Content-type: image/jpeg');
    exit(1);
}

// bootstrap PW
require_once(dirname(__FILE__) . '/index.php');

// check user-account
if (! wire('user')->hasRole('superuser|editor')) {
    header('HTTP/1.1 403 Forbidden');
    header('Content-type: ' . $imgType);
    exit(1);
}

// collect infos
$maxAge = (60 * 60 * 2); // 2 hours
$imgTimestamp = filemtime($imgFilename);
$imgExpiration = intval(time() + $maxAge);

// create headers
$imgHeaders = array();
$imgHeaders[] = 'Content-type: ' . $imgType;
$imgHeaders[] = 'Content-Length: ' . filesize($imgFilename);
$imgHeaders[] = 'Date: ' . gmdate('D, d M Y H:i:s',time()) . ' GMT';
$imgHeaders[] = 'Last-Modified: ' . gmdate('D, d M Y H:i:s',$imgTimestamp) . ' GMT';
$imgHeaders[] = 'Expires: ' . gmdate('D, d M Y H:i:s', $imgExpiration) . ' GMT';
$imgHeaders[] = 'pragma: cache';
$imgHeaders[] = "Cache-Control: no-transform, private, s-maxage={$maxAge}, max-age={$maxAge}";

// send headers
foreach($imgHeaders as $imgHeader) header($imgHeader);

// send file
$errorCode = @readfile($imgFilename) === FALSE ? 1 : 0;

// and exit
exit($errorCode);



// --- functions ---

function getImageType($fn, $returnAsInteger = false) {

    $types1 = array(1 => 'gif', 2 => 'jpg', 3 => 'png');
    $types2 = array('gif' => 1, 'jpg' => 2, 'jpeg' => 2, 'png' => 3);

    if (function_exists('exif_imagetype') && isset($types1[@exif_imagetype($fn)])) {
        $success = $types1[exif_imagetype($fn)];
    }
    if (!isset($success) && function_exists('getimagesize')) {
        $info = @getimagesize($fn);
        if (isset($info[2]) && isset($types1[$info[2]])) {
            $success = $types1[$info[2]];
        }
    }
    if (!isset($success)) {
        $extension = strtolower(pathinfo($fn, PATHINFO_EXTENSION));
        if (isset($types2[$extension])) {
            $success = $types1[$types2[$extension]];
        }
    }

    if (!isset($success)) return false;

    return true === $returnAsInteger ? $types2[$success] : $success;
}
  • Like 5
Link to comment
Share on other sites

  • 3 years later...

Hi @horst,

Im looking for an exact same solution, website is showing images, but can not be accessed through going directly to the URL.

I tried your solution, but also the page where the image is displayed is getting a 404 response for this image.

I also have de pwimg.php as a template and page /pwimg/ instead of a 'normal' php file, is that maybe a problem?

I hope you can help me with this!
Thnx.

Link to comment
Share on other sites

Please read & follow my solution exactly. The way you described doesn't work & it isn't meant to do so.
It describes a way to only protect the original images. Therefor you need to deny all access to them via htaccess file. In a second step, you enable access for superusers (or who ever you like) via a proxy (php) script. Nothing (!) is embedded into your PW-installation, only the htaccess file.

Link to comment
Share on other sites

  • 2 years later...

Code for .htaccess file:

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteCond %{HTTP_REFERER} !^http://([^.]+\.)?domain\.com/ [NC]
 RewriteCond %{HTTP_USER_AGENT} !(googlebot-image|msnbot|psbot|yahoo-mmcrawler) [NC]
 RewriteRule (^.*\.(bmp|gif|jpe?g|png)$) /watermark.php?src=$1 [L]
</IfModule>

Code for watermark.php PHP file:

<?php  

 header('content-type: image/jpeg');
 
 $image = imagecreatefromjpeg($_GET['src']);
 
 $watermark = imagecreatefrompng('watermark.png');
 
 $watermark_width = imagesx($watermark);
 $watermark_height = imagesy($watermark);
 
 $dest_x = imagesx($image) - $watermark_width;
 $dest_y = imagesy($image) - $watermark_height;
 
 imagecopymerge($image, $watermark, $dest_x - 5, 5, 0, 0, $watermark_width, $watermark_height, 80);
 
 imagejpeg($image, NULL, 85);
 
 imagedestroy($image);
 imagedestroy($watermark);

?>

Hello, thanks for the advice.
BUT .png images don't work for me

Writes - The image cannot be displayed because it contains errors.

What is the reason? Thank you!

Link to comment
Share on other sites

  • 1 year later...

Inspired by @horst's helpful post above, here is some code for preventing guests from accessing original images that leverages more of the PW API...

In .htaccess, just after "RewriteEngine On"

# When original images are requested, rewrite for PHP user verification
# If a matching file exists
RewriteCond %{REQUEST_FILENAME} -f
# And the file is within the /site/assets/files/ directory
RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/(.*?)/
# And the file extension is for an image type that we want to restrict access to
RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$ [NC]
# And the filename portion of the path contains less than two dots (i.e. is not an image variation)
RewriteCond %{REQUEST_FILENAME} !^(.+\/)?[^\/]+\.[^\/]+\.[^\/]+$ [NC]
# Then rewrite to a hooked URL for user verification
RewriteRule ^(.*)$ index.php?it=/original-image/ [L,QSA]

In /site/ready.php

// A URL hook for verifying that the user is allowed to view original images
$wire->addHook('/original-image/', function($event) {
	$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
	$ext = pathinfo($path, PATHINFO_EXTENSION);
	$filename = $event->wire()->config->paths->root . ltrim($path, '/');
	$allowed_exts = ['jpg', 'jpeg', 'png', 'gif'];

	// Optional: get the Pageimage and the Page it belongs to
	// if this affects whether the user is allowed to view the file
	// $pm = $event->wire()->pages(1)->filesManager;
	// $pageimage = $pm->getFile($filename);
	// $page = $pageimage->page;

	// Send the file if the user is allowed to view it
	if(
		// The file extension must be one of the allowed extensions
		in_array($ext, $allowed_exts) &&
		// The user must be logged in
		$event->wire()->user->isLoggedin() &&
		// The file must exist
		is_file($filename)
	) {
		$event->wire()->files->send($filename);
	}

	// Otherwise, return a 403 Forbidden response
	header('HTTP/1.0 403 Forbidden');
	die('403 Forbidden');
});

 

  • Like 5
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...