<?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 !
Christian Joudrey
March 14, 2011, March 14, 2011 22:45, permalink
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);
Joey Day
March 29, 2011, March 29, 2011 03:20, permalink
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?
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!