2

Okay so I have an idea on how I want to serve and cache my images. I don't know if this is the right way to do it but if it is, I'd like to know how to go about preventing abuse.

Situation:

index.php

<img src="images/cache/200x150-picture_001.jpg" />

images/cache/.htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ images/image.php?f=$1 [L]

The above checks if the image exists, if not it will be rewritten to image.php

image.php PSEUDO code

get height and width from filename

resize and save image to cache folder

serve the image with content-type and readfile.

This way I'm trying to reduce HTTP requests and PHP load with readfiles. The browser gets the image/jpeg if it exists and if not it will be generated.

Eventually all images will be cached and served in the right height and width so browsers won't be downloading oversized images.

The only catch.. if you change the image url to different dimensions, you can fill up the server.

Am I doing it right, what's the right way to do it. Is there any chance of stopping this from happening?

I know this whole concept of caching has been refined a million times, please enlighten me.

Rick Kuipers
  • 6,616
  • 2
  • 17
  • 37

4 Answers4

2

Not sure if I'm not too late with the answer, but why don't you simple use SLIR (Smart Lencioni Image Resizer) for that? It can do whatever you need (include caching and cache management), so you simple drop it in and use.

Alexander
  • 464
  • 1
  • 5
  • 17
1

Some approaches:

  • Maintain an array of allowed resolutions, and check whether the requested resolution is in that array. Downside: you can't quickly add a resolution without editing the array.

  • If this is in a CMS context: allow the creation of new images (that are not in the cache yet) only by authenticated users; refuse the request otherwise. When an authenticated user adds an image in the CMS, they preview it, and doing that generates the resized image. Downside: not entirely easy to implement.

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • The array of allowed resolutions doesn't sound too bad considering this particular situation is for a webshop. – Rick Kuipers Jun 23 '12 at 11:25
  • The CMS context is possible, I usually use CKeditor for my CMS and would be able to edit the code so it points to the file like I want it to. – Rick Kuipers Jun 23 '12 at 11:25
  • I decided to accept this answer because I feel like these solutions are easiest to apply in the current situation. Thanks. – Rick Kuipers Jun 23 '12 at 19:17
1

Caching is a distinctly non-trivial issue - your solution seems reasonable, but is indeed open to intentional and unintentional denial of service attacks. It also doesn't address what happens when the image changes - how do you remove all the resized images from the cache? It doesn't set cache headers to allow "downstream" caches. It doesn't deal with the risk of the entire cache being flushed at the same time, requiring all images to be re-generated in the context of an HTTP request, which could be a major performance drain..

Have you looked at "off the shelf" solutions such as Apache's caching module?

Neville Kuyt
  • 29,247
  • 1
  • 37
  • 52
  • True, having the cache be re-generated is a major performance loss on first visit. For this particular situation the website is running on IIS and I have no rights to install modules. However for my other projects I will definitely look into that. Thank you. – Rick Kuipers Jun 23 '12 at 11:27
0

I just do:

<img src="/thumbnail.php?thumb=mybigpicture.ext" ... />

And here's how I do it. Note that this implementation scales images whether they're wider than taller or vice-versa, and does a decent job of scaling unlike most PHP attempts.

<?php

function thumb_image($request = "") {
  $cfgthumb['folder'] = "/images/cache";
  $cfgthumb['height'] = 150;
  $cfgthumb['width'] = 200;
  $cfgthumb['error'] = "/images/error.jpg";
  $cfgthumb['default'] = "/images/notfound.jpg";

  $thumb = $cfgthumb['folder'] . "/" . md5($request);
  header("Content-Type: image/jpeg");
  if (is_readable($thumb)) echo file_get_contents($thumb);
  elseif (is_readable($request)) {
    $extension = strtolower(end(explode(".", $request)));
    switch ($extension) {
    case "gif":
      $simage = imagecreatefromgif($request);
      break;
    case "jpeg":
    case "jpg":
      $simage = imagecreatefromjpeg($request);
      break;
    case "png":
      $simage = imagecreatefrompng($request);
      break;
    }
    if ($simage) {
      $simage_width = imagesx($simage);
      $simage_height = imagesy($simage);
      if (($simage_width > $cfgthumb['width']) || ($simage_height > $cfgthumb['height'])) {
        if ($simage_width > $simage_height) {
          $dimage_width = $cfgthumb['width'];
          $dimage_height = floor($simage_height * ($cfgthumb['width'] / $simage_width));
        } else {
          $dimage_width = floor($simage_width * ($cfgthumb['height'] / $simage_height));
          $dimage_height = $cfgthumb['height'];
        }
      } else {
        $dimage_width = $simage_width;
        $dimage_height = $simage_height;
      }
      $dimage = imagecreatetruecolor($dimage_width, $dimage_height);
      imagegammacorrect($simage, 2.2, 1.0);
      imagecopyresampled($dimage, $simage, 0, 0, 0, 0, $dimage_width, $dimage_height, $simage_width, $simage_height);
      imagegammacorrect($dimage, 1.0, 2.2);
      imagejpeg($dimage, $thumb, 100);
      imagejpeg($dimage, NULL, 100);
      imagedestroy($simage);
      imagedestroy($dimage);
    } else echo file_get_contents($cfgthumb['error']);
  } else echo file_get_contents($cfgthumb['default']);
}

?>
dogglebones
  • 387
  • 1
  • 8
  • I already have this code except mine deals with dynamic dimensions plus the way I do it I reduce server load, letting the browser GET the image file instead of: the server and then the browser. The problem is I want a way to prevent people from filling up my server. Which can be achieved by @Pekka's allowed dimensions array. – Rick Kuipers Jun 23 '12 at 11:55
  • Sorry if this doesn't really address your specific question. I prefer to let PHP handle the entire process instead of using a mod_rewrite or similar approach (i.e., that may enable access to static files on disk) for two reasons: first, you only need to bother with the source image files and the thumbnails "take care of themselves", and second, you don't need to generate a whole bunch of thumbnails all at once as they're generated on-demand. – dogglebones Jun 23 '12 at 12:20
  • Like I said having PHP serve the file increases load as little as it may be. It's quicker to directly point the visitor to the image file then to a php file that serves the image. Also, the way I explained it everything will be generated on-demand. "you only need to bother with the source image files", what do you mean? My method does that as well? There's is hardly any difference between our methods except I take out the step of php serving a file by using mod_rewrite and only serve file with php when it doesn't exist and has to be generated, the times after that it'll take the generated img. – Rick Kuipers Jun 23 '12 at 13:35
  • You're right. The only advantage to my method is that you don't end up in .htaccess and/or httpd.conf + apachectl restart (the way it -should- be done, by the way, if you're concerned about performance), which is notably less of an advantage than that of having apache (cache and) serve static images, if existent. – dogglebones Jun 23 '12 at 14:22