Efd9b80fdfb3c174d630effdf58d4ae3

I was looking for a way to cache images locally instead of hotlinking them from other sites. The following script works, but I'm wondering if it has any glaring security holes or if it could/should be made more efficient somehow. Also, there's currently not a lot of error checking built in; I haven't tested it much, and have no idea what will happen if I pass something besides an image into it. Lastly, it currently caches the image once and then never checks to see if the image changes. Should I build something in that would flush the cache from time to time to make sure I've always got the latest image? Would love any feedback you may have. Thanks!

<?php
/**
 * WarmLink created by Joey Day
 * http://www.joeyday.com
 * 
 * GNU General Public License, version 3
 * http://www.gnu.org/licenses/gpl-3.0.html
 * 
 * This script caches hotlinked images locally to save external sites
 * unnecessary bandwidth and speed up loading of local pages.
 * 
 * The folder where you place the script must be chmod 777.
 */

# Send all activity to log.txt
define ('LOG_ACTIVITY', TRUE);

# Where to cache the images
define ('CACHE_DIR', './cache/');
 
# Whitelist of sites images can be hotlinked from (to prevent abuse)
$whitelistSites = array (
	'flickr.com',
	'picasa.com',
	'blogger.com',
	'wordpress.com',
	'img.youtube.com',
);


$src = $_GET['src'];

# Make sure the site we're hotlinking from is on the whitelist
$isWhitelistedSite = false;
$parsed_src = parse_url($src);
foreach ($whitelistSites as $site) {
	if (stristr($parsed_src['host'], $site) != false) {
		$isWhitelistedSite = true;
	}
}
if (!$isWhitelistedSite) {
	echo "hotlinking not allowed from this site";
	die();
}


# Check to see if the cache directory exists, create it if not
if (!file_exists(CACHE_DIR)) {
	mkdir (CACHE_DIR);
	chmod (CACHE_DIR, 0777);
}

# Name the image by simply hashing the given source URL
$local_file = CACHE_DIR . sha1($src);

# See if the image already exists in the cache
if (!file_exists($local_file)) {
        # Hotlink the image (this is the expensive stuff we want to avoid!)
        file_put_contents($local_file, file_get_contents($src));
	if (LOG_ACTIVITY) {
		file_put_contents('log.txt', 'HOTLINK  ' . date('m-d-Y H:i:s') . ' ' . $src . PHP_EOL, FILE_APPEND);
	}
}

# Output the image with appropriate mime type
header("Content-type: " . image_type_to_mime_type(exif_imagetype($local_file)));
readfile($local_file);
if (LOG_ACTIVITY) {
	file_put_contents('log.txt', 'WARMLINK  ' . date('m-d-Y H:i:s') . ' ' . $src . PHP_EOL, FILE_APPEND);
}

Refactorings

No refactoring yet !

85b03650a2ec5235376b0b983a49511a

Christian Joudrey

March 14, 2011, March 14, 2011 22:45, permalink

No rating. Login to rate!

Quickly glancing at the code here are my thoughts.

You might want to check the extension of the $src file. Maybe validate that it is an image and validate the max file size?

As well as the following changes:

foreach ($whitelistSites as $site) {
	if (stristr($parsed_src['host'], $site) != false) {
		$isWhitelistedSite = true;
	}
}
foreach ($whitelistSites as $site) {
	if (stristr($parsed_src['host'], $site) != false) {
		$isWhitelistedSite = true;
		break;
	}
}
chmod (CACHE_DIR, 0777);
# This really shouldn't be 0777. It shouldn't be 0755 either, but it's better than 0777.
chmod (CACHE_DIR, 0755);
Efd9b80fdfb3c174d630effdf58d4ae3

Joey Day

March 29, 2011, March 29, 2011 03:20, permalink

No rating. Login to rate!

Christian, thanks for your refactor. Your suggestions are excellent. I like your idea of imposing a max file size. As for validating the image type, at least in my testing, the image_type_to_mime_type() and/or exif_imagetype() methods do a pretty good job of that. If you point the script at anything other than an image, either one or both of those methods will throw an error. Of course, by then the file has already been downloaded and stored locally, so maybe that is still a problem?

Your refactoring





Format Copy from initial code

or Cancel