Created
August 30, 2012 22:15
-
-
Save RobThree/3542795 to your computer and use it in GitHub Desktop.
PHP Hotlink checker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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