horst

Protect original Images in site/assets/files/

Recommended Posts

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

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:

# 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>

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?

  • Like 2

Share this post


Link to post
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...

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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]

Share this post


Link to post
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 Pim2 and PW 2.5.11+ / PW 3+

Spoiler

        # -----------------------------------------------------------------------------------------------
        # 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;
}
Edited by horst
updated with newer settings for PW 2.5.11+ and Pim2
  • Like 5

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

Thanks for your reply. Indeed there was some misunderstanding. I got your solution working now and should be fine for now. Thanks @horst :).

  • Like 1

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By louisstephens
      Ill be honest, I am a bit unsure how accomplish this. I have a repeater (dev_repeater) that contains an image field set to 1 image. Nested within this repeater, is another repeater (dev_child_repeater) that allows a user to add in some urls. However, there is also a hidden field that I am trying to pass the parent repeater's image path. 
      I know I can output all the data by using:
      <?php foreach($page->dev_repeater as $repeater) { foreach($repeater as $url) { # do some stuff } } ?> For the life of me, I can not figure out how to obtain the image url in my php to pass to a variable inside the nested foreach loop. Hopefully this made some sense.
    • By Jennifer Stock
      Greetings. I would like to restrict access to certain sections of my organization's ProcessWire site using pubcookie. We are rolling out Shibboleth authentication later this year but for now, it seems I can only make use of our institution's single sign-on routine by utilizing rules in an .htaccess file. 
      I am wondering if there is a way to ask PW to apply these rules to certain pages in the site, whether via template type or location in the page tree:
      AuthType UWNetID PubcookieAppID "MyApplication" require type staff faculty  
    • By Xonox
      Hi,
      I have a template that it's working fine in development, however I can't get it to work on production! It shows every information inside repeater fields except the images.
      Here's the template:

      These are the circuit_day_image settings:

      This is the code:
      <?php foreach($page->circuit_days as $circuit_day) { if($circuit_day->circuit_day_image) { $day_image = $circuit_day->circuit_day_image->size(300, 300)->url; echo '<img src="' . $day_image . '" />'; } else { echo 'No image! :('; } echo '<h2>' . $circuit_day->title . '</h2>'; echo $circuit_day->body; } ?> I always get "No image! :("
      I think I'm doing everything right!
      Anyone else with a similar problem?
      Update
      After uploading the production database into my server, the images stopped working. It can be one of two problems:
      1. Bad field configuration;
      2. Something wrong with the Database.
      I can't find the problem. Any suggestion is welcome, thanks,
      Update 2
      I forgot to upload the images. It's working on dev and not on production. Still no clue!
      Clue 1
      When I insert
       <pre><?php print_r($circuit_day); ?></pre>
      On development I get a clean list for each repetition:

      However, on production, the command gets on a weird recursive loop that takes forever (it even slows the browser to a halt):

      What might be going on?
    • By LMD
      I'm wondering if it is possible to add a label to the description input in image fields (in the admin).
      I'm using the module Image Extra, which has labels for each input, but I'd like to add a label to the default 'description' input too. The image below illustrates this:

       
      If there is no way (no hook?) then, I suppose I could just not use the default description and add a new description input with the Image Extra module.  But I thought I'd ask in case I (or others) ever want to do this without using the module (i.e. just the one input required).
      I'm using PW 3.0.98
      Thanks.
      ---
      FYI: yes, that is a cat and not a quilt -- this is on my local dev server and I don't have the actual photos yet! She is on a quilt, so it counts... technically.
       
    • By dweeda
      I installed an SSL Certificate, then edited my .htaccess file:
        # -----------------------------------------------------------------------------------------------
        # 9. If you only want to allow HTTPS, uncomment the RewriteCond and RewriteRule lines below.
        # -----------------------------------------------------------------------------------------------
        RewriteCond %{HTTPS} off
        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
      by uncommenting out the Rewrite lines.
      Now I get 404 error pages when I try go to any .../processwire-master/<pagename>/
      This includes my admin page at .../processwire-master/processwire/, so i can't get into my admin.
      What else do I need to do?