215 lines
5.6 KiB
PHP
Executable File
215 lines
5.6 KiB
PHP
Executable File
<?php
|
|
|
|
/**
|
|
* This is a lightweight proxy for serving images, generally meant to be used alongside SSL
|
|
*
|
|
* Simple Machines Forum (SMF)
|
|
*
|
|
* @package SMF
|
|
* @author Simple Machines http://www.simplemachines.org
|
|
* @copyright 2016 Simple Machines and individual contributors
|
|
* @license http://www.simplemachines.org/about/smf/license.php BSD
|
|
*
|
|
* @version 2.0.16
|
|
*/
|
|
|
|
define('SMF', 'proxy');
|
|
|
|
/**
|
|
* Class ProxyServer
|
|
*/
|
|
class ProxyServer
|
|
{
|
|
/** @var bool $enabled Whether or not this is enabled */
|
|
protected $enabled;
|
|
|
|
/** @var int $maxSize The maximum size for files to cache */
|
|
protected $maxSize;
|
|
|
|
/** @var string $secret A secret code used for hashing */
|
|
protected $secret;
|
|
|
|
/** @var string The cache directory */
|
|
protected $cache;
|
|
|
|
|
|
/**
|
|
* Constructor, loads up the Settings for the proxy
|
|
*
|
|
* @access public
|
|
*/
|
|
public function __construct()
|
|
{
|
|
global $image_proxy_enabled, $image_proxy_maxsize, $image_proxy_secret, $cachedir, $sourcedir;
|
|
|
|
require_once(dirname(__FILE__) . '/Settings.php');
|
|
require_once($sourcedir . '/Class-CurlFetchWeb.php');
|
|
|
|
// Turn off all error reporting; any extra junk makes for an invalid image.
|
|
error_reporting(0);
|
|
|
|
$this->enabled = (bool) $image_proxy_enabled;
|
|
$this->maxSize = (int) $image_proxy_maxsize;
|
|
$this->secret = (string) $image_proxy_secret;
|
|
$this->cache = $cachedir . '/images';
|
|
}
|
|
|
|
/**
|
|
* Checks whether the request is valid or not
|
|
*
|
|
* @access public
|
|
* @return bool Whether the request is valid
|
|
*/
|
|
public function checkRequest()
|
|
{
|
|
if (!$this->enabled)
|
|
return false;
|
|
|
|
// Try to create the image cache directory if it doesn't exist
|
|
if (!file_exists($this->cache))
|
|
{
|
|
if (!mkdir($this->cache) || !copy(dirname($this->cache) . '/index.php', $this->cache . '/index.php'))
|
|
return false;
|
|
}
|
|
|
|
if (empty($_GET['hash']) || empty($_GET['request']) || ($_GET['request'] === 'http:') || ($_GET['request'] === 'https:'))
|
|
return false;
|
|
|
|
$hash = $_GET['hash'];
|
|
$request = $_GET['request'];
|
|
|
|
if (hash_hmac('sha1', $request, $this->secret) != $hash)
|
|
return false;
|
|
|
|
// Attempt to cache the request if it doesn't exist
|
|
if (!$this->isCached($request))
|
|
return $this->cacheImage($request);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serves the request
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function serve()
|
|
{
|
|
$request = $_GET['request'];
|
|
$cached_file = $this->getCachedPath($request);
|
|
$cached = json_decode(file_get_contents($cached_file), true);
|
|
|
|
// Did we get an error when trying to fetch the image
|
|
$response = $this->checkRequest();
|
|
if ($response === null)
|
|
{
|
|
// Throw a 404
|
|
header('HTTP/1.1 404 Not Found');
|
|
exit;
|
|
}
|
|
// Right, image not cached? Simply redirect, then.
|
|
if ($response === false)
|
|
header('Location: ' . $request, false, 301);
|
|
|
|
// Is the cache expired? Try to refresh it.
|
|
if (!$cached || time() - $cached['time'] > (5 * 86400))
|
|
{
|
|
@unlink($cached_file);
|
|
if ($this->checkRequest())
|
|
$this->serve();
|
|
exit;
|
|
}
|
|
|
|
// Make sure we're serving an image
|
|
$contentParts = explode('/', !empty($cached['content_type']) ? $cached['content_type'] : '');
|
|
if ($contentParts[0] != 'image')
|
|
exit;
|
|
|
|
// Check whether the ETag was sent back, and cache based on that...
|
|
$eTag = '"' . substr(sha1($request) . $cached['time'], 0, 64) . '"';
|
|
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
|
|
{
|
|
header('HTTP/1.1 304 Not Modified');
|
|
exit;
|
|
}
|
|
|
|
header('Content-type: ' . $cached['content_type']);
|
|
header('Content-length: ' . $cached['size']);
|
|
|
|
// Add some caching
|
|
header('Cache-control: public');
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($cached_file)) . ' GMT');
|
|
header('ETag: ' . $eTag);
|
|
|
|
echo base64_decode($cached['body']);
|
|
}
|
|
|
|
/**
|
|
* Returns the request's hashed filepath
|
|
*
|
|
* @access protected
|
|
* @param string $request The request to get the path for
|
|
* @return string The hashed filepath for the specified request
|
|
*/
|
|
protected function getCachedPath($request)
|
|
{
|
|
return $this->cache . '/' . sha1($request . $this->secret);
|
|
}
|
|
|
|
/**
|
|
* Check whether the image exists in local cache or not
|
|
*
|
|
* @access protected
|
|
* @param string $request The image to check for in the cache
|
|
* @return bool Whether or not the requested image is cached
|
|
*/
|
|
protected function isCached($request)
|
|
{
|
|
return file_exists($this->getCachedPath($request));
|
|
}
|
|
|
|
/**
|
|
* Attempts to cache the image while validating it
|
|
*
|
|
* @access protected
|
|
* @param string $request The image to cache/validate
|
|
* @return bool|null Whether the specified image was cached; null if not found or not an image.
|
|
*/
|
|
protected function cacheImage($request)
|
|
{
|
|
$dest = $this->getCachedPath($request);
|
|
$curl = new curl_fetch_web_data(array(CURLOPT_BINARYTRANSFER => 1));
|
|
$request = $curl->get_url_data($request);
|
|
$responseCode = $request->result('code');
|
|
$response = $request->result();
|
|
|
|
if (empty($response) || $responseCode != 200)
|
|
return null;
|
|
|
|
$headers = $response['headers'];
|
|
|
|
// Make sure the url is returning an image
|
|
$contentParts = explode('/', !empty($headers['content-type']) ? $headers['content-type'] : '');
|
|
if ($contentParts[0] != 'image')
|
|
return null;
|
|
|
|
// Validate the filesize
|
|
if ($response['size'] > ($this->maxSize * 1024))
|
|
return false;
|
|
|
|
return file_put_contents($dest, json_encode(array(
|
|
'content_type' => $headers['content-type'],
|
|
'size' => $response['size'],
|
|
'time' => time(),
|
|
'body' => base64_encode($response['body']),
|
|
))) !== false;
|
|
}
|
|
}
|
|
|
|
$proxy = new ProxyServer();
|
|
$proxy->serve();
|
|
|
|
?>
|