Skip to content

Instantly share code, notes, and snippets.

@RobThree
Created August 30, 2012 22:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RobThree/3542795 to your computer and use it in GitHub Desktop.
Save RobThree/3542795 to your computer and use it in GitHub Desktop.
PHP Hotlink checker
<?php
/*
* Class for checking if images are hotlink-protected
*/
class HotlinkChecker {
// You can set this property to any random domain; this will be sent
// as the referring domain to check the "hotlink protected" version
public $testhost = 'www.somerandomdomain.com';
// You can change (or add/remove) any cURL options through this property
public $curloptions = array(
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_UNRESTRICTED_AUTH => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_DNS_CACHE_TIMEOUT => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0' //Identify ourselves as firefox, can be anything you want
);
/*
* Returns TRUE for images that are hotlink protected (or images of size 0,
* any non-image contenttypes or url's returning HTTP error status codes).
* Returns FALSE when a loaded image equals a newly loaded image with a forged
* referer (wich is done to ensure "hotlink prevention" is triggered)
* in size, hash of the imagedata, http status code and content-type
*/
public function IsImageHotlinkProtected($url, $referer = null) {
//Get the image as if it is loaded from the site itself
$imgnormal = $this->RetrieveData($url, $referer === null ? $url : $referer);
//Got at least more than 0 bytes and was the contenttype of image/* and a non-error HTTP status was returned?
if (($imgnormal->size>0) && (strcasecmp(substr($imgnormal->contenttype, 0, 6),'image/')===0) && ($imgnormal->httpstatuscode < 400)) {
//Then we'll replace the original host of the just checked image
//...with our testhost.
//So http://www.testdomain.com/img/foo.jpg?x=y will become http://www.somerandomdomain.com/img/foo.jpg?x=y
//This value will be sent as referer value in the HTTP headers
//Requires pecl_http: http://php.net/manual/en/book.http.php
$parsedurl = parse_url($url);
$parsedurl['host'] = $this->testhost;
$imghotlnk = $this->RetrieveData($url, http_build_url($parsedurl));
//If you don't have (or don't want to install) pecl_http you can also do this:
//Remove above 3 lines of code and uncomment this line:
//$imghotlnk = $this->RetrieveData($url, $this->testhost);
//Now check if the images downloaded are actually equal to ensure we didn't get
//some "replaced version" of the original (showing a "you're stealing bandwith" or
//"trollface" or "No hotlinking!"-sign or worse, tubgirl...)
//Return the _negated_ result of the check; if both images are equal the
//image is _NOT_ hotlinkprotected.
return !$imgnormal->Equals($imghotlnk);
}
//We got a 404 or no actual image (according to the contenttype) or 0 bytes or something...
return true; //So we return TRUE and assume the worst: the image is hotlinked
}
//Retrieves data from a remote server.
//Requires cURL: http://php.net/manual/en/book.curl.php
private function RetrieveData($url, $referer) {
//Setup cUrl
$ch = curl_init();
curl_setopt_array($ch, $this->curloptions);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_REFERER, $referer);
//Execute request
$result = curl_exec($ch);
//Succeeded?
if (curl_errno($ch))
throw new Exception(sprintf('CURL error %d: %s', curl_errno($ch), curl_error($ch)));
//Store the cURL info and HTTP status code of the request
$reqinfo = curl_getinfo($ch);
//We no longer need cUrl
curl_close($ch);
//Return an ImageResult object
return new ImageResult(
$this->GetVar($reqinfo,'download_content_length'),
$this->GetVar($reqinfo,'content_type'),
intval($this->GetVar($reqinfo,'http_code')),
md5($result)
);
}
//Simple "alias" to avoid having "isset(foo[bar]) ? foo[bar] : default" all over the place
private function GetVar($object, $name, $default = null) {
return isset($object[$name]) ? $object[$name] : $default;
}
}
/*
* Represents a retrieved (over HTTP) image
*/
class ImageResult {
//MD5 hash to check if image A actually equals image B in terms of content/pixels
public $imagehash;
//Bytesize of the image
public $size;
//The contenttype the image was served with
public $contenttype;
//The HTTP statuscode returned when the image was served
public $httpstatuscode;
//Construction. Just a bunch of lefthand => righthand code to set our properties
public function ImageResult($size, $contenttype, $httpstatuscode, $imagehash) {
$this->size = $size;
$this->contenttype = $contenttype;
$this->imagehash = $imagehash;
$this->httpstatuscode = $httpstatuscode;
}
//Taking advantage of short circuiting we first compare image size, http status codes, contenttypes and finally hashes
public function Equals(ImageResult $other) {
return ($this->size === $other->size)
&& ($this->httpstatuscode === $other->httpstatuscode)
&& (strcasecmp($this->contenttype, $other->contenttype)===0)
&& ($this->imagehash === $other->imagehash);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment