############################################################################### # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### /// Name: Image Proxy /// Author: Dwayne C. Litzenberger /// Description: This plugin proxies images that might otherwise be protected by a "referer check". /// Configuration: __imageproxy_config /// Version: 0.6 define('IMAGEPROXY_VERSION', '0.6'); define('IMAGEPROXY_CONFIG_HASHKEY', 'imageproxy.hash_key'); define('IMAGEPROXY_HASHKEY_LENGTH', 16); # 128 bits function __imageproxy_random($bytes) { # Generate random bits _pf("__imageproxy_random"); if (function_exists("mcrypt_create_iv")) { _pf("... using mcrypt_create_iv to generate key"); $data = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); if (!$data) { srand(); $data = mcrypt_create_iv($bytes, MCRYPT_RAND); } } else { // SECURITY: This is insecure _pf("... Warning: generating insecure key using mt_rand"); mt_srand(); $data = ""; for($i = 0; $i < $bytes; $i++) { $data .= chr(mt_rand(0, 255)); } } if (!isset($data) or !$data or strlen($data) != $bytes) { _pf("... error"); die("Internal error while generating random data"); } return $data; } function __imageproxy_make_hash($url, $hash_key=null) { # Hash format: # url = *OCTET # key = 16OCTET ; the hash key # url-length = 1*DIGIT ; length of the url # unique = "36:6338dc36-5add-11db-8783-001617467794," # key-ns = "16:" key "," # url-ns = url-length ":" url "," # hash-data = unique key-ns url-ns # hash = BIN2HEX( SHA1( SHA1( hash-data ) ) ) # Test vector: # key = "ABCDEFGHIJKLMNOP" (hex: 4142434445464748494a4b4c4d4e4f50) # url = "about:blank" # hash-data = "36:6338dc36-5add-11db-8783-001617467794,16:ABCDEFGHIJKLMNOP,11:about:blank," # hash = "1d6f8564bc3555f00ebda1a6b1a349741688d4e6" if (!__imageproxy_is_valid_key($hash_key)) { $hash_key = __imageproxy_get_key(); } $binary_hash_key = pack('H*', $hash_key); $uuid = "6338dc36-5add-11db-8783-001617467794"; $hash = sha1(sha1( strlen($uuid) . ":" . $uuid . "," . strlen($binary_hash_key) . ":" . $binary_hash_key . "," . strlen($url) . ":" . $url . ",", true)); return $hash; } function __imageproxy_config() { $encoding = (getConfig('rss.output.encoding') ? getConfig('rss.output.encoding') : DEFAULT_OUTPUT_ENCODING); $hash_key = __imageproxy_get_key(); if (rss_plugins_is_submit()) { $hash_key = $_REQUEST['hash_key']; if (!__imageproxy_is_valid_key($hash_key)) { // The specified key is invalid. Generate a new one. $hash_key = __imageproxy_generate_key(); } else { // Normalize the key $hash_key = bin2hex(pack('H*', $hash_key)); } // Save the key in the database rss_plugins_add_option(IMAGEPROXY_CONFIG_HASHKEY, $hash_key, 'string', 'x'); return; } print "
\n"; print " " . rss_htmlspecialchars(__('Security')) . "\n"; print "


\n"; print "

"; print "

" . rss_htmlspecialchars(__('Required length (in hexadecimal digits):')) . " " . (IMAGEPROXY_HASHKEY_LENGTH * 2) . "

\n"; print "

" . rss_htmlspecialchars(__('Entering a blank or invalid key will cause a new key to be generated.')) . "

\n"; print "
\n"; } function __imageproxy_generate_key() { return bin2hex(__imageproxy_random(IMAGEPROXY_HASHKEY_LENGTH)); } function __imageproxy_get_key() { $hash_key = rss_plugins_get_option(IMAGEPROXY_CONFIG_HASHKEY); if (!__imageproxy_is_valid_key($hash_key)) { $hash_key = __imageproxy_generate_key(); rss_plugins_add_option(IMAGEPROXY_CONFIG_HASHKEY, $hash_key, 'string', 'x'); } return $hash_key; } function __imageproxy_is_valid_key($hash_key) { if (empty($hash_key)) { return false; } $tmp = @pack('H*', $hash_key); if (empty($tmp) or strlen($tmp) != IMAGEPROXY_HASHKEY_LENGTH) { return false; } return true; } function __imageproxy_changeSrc__cb($m) { $pre = $m[1]; $src = $m[2]; $post = $m[3]; $encoding = (getConfig('rss.output.encoding') ? getConfig('rss.output.encoding') : DEFAULT_OUTPUT_ENCODING); $q = substr($src, 0, 1); $q2 = substr($src, strlen($src)-1, 1); if ($q === $q2 and ($q === "'" or $q === '"')) { $url = html_entity_decode(substr($src, 1, strlen($src)-2), ENT_QUOTES, $encoding); } else { $url = html_entity_decode($src, ENT_QUOTES, $encoding); } $hash = __imageproxy_make_hash($url); $url = rss_plugins_get_plugins_http_path() . "imageproxy.php?do_proxy=1&hash=".urlencode($hash)."&url=" . urlencode($url); return $pre . '"' . htmlspecialchars($url, ENT_COMPAT, $encoding) . '"' . $post; } function __imageproxy_filter($item) { if (!array_key_exists('description', $item)) { return $item; } $content = $item->description; $content = preg_replace_callback( '#(]*(?<=\s)src=)("[^<>"]*"|\'[^<>\']*\'|[^<>\s]*)(?=[>\s])([^<>]*>)#is', '__imageproxy_changeSrc__cb', $content); $item->description = $content; return $item; } function __imageproxy_send_badimage() { $data = base64_decode('R0lGODlhBQAFAIABAP8AAP///ywAAAAABQAFAAACB0RueWHIjwoAOw=='); header('HTTP/1.0 403 Forbidden'); header('Content-Type: image/gif'); header('Content-Length: ' . strlen($data)); print $data; exit; } function __imageproxy_do_proxy() { $url = $_REQUEST['url']; $hash = $_REQUEST['hash']; $referer = @$_SERVER['HTTP_REFERER']; // Hash check $real_hash = __imageproxy_make_hash($url); if ($hash !== $real_hash) { header("x-plugin: Invalid hash"); __imageproxy_send_badimage(); return; } // Referer check if (!empty($referer)) { $u = parse_url($referer); if ($u['host'] !== $_SERVER['HTTP_HOST']) { header('x-plugin: Illegal referer'); __imageproxy_send_badimage(); return; } } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); curl_setopt($ch, CURLOPT_HTTPHEADER, array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); curl_setopt($ch, CURLOPT_USERAGENT, "Gregarius-Plugin-ImageProxy/".IMAGEPROXY_VERSION); ob_start(); curl_exec($ch); $data = ob_get_clean(); if (curl_errno($ch) != 0) { die("curl fetch failed on url '$url': " . curl_error($ch)); } $content_type = preg_replace('#[\\r\\n]#s', ' ', curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); curl_close($ch); header("Content-Type: " . $content_type); header("Content-Length: " . strlen($data)); print $data; exit; } if (isset($_REQUEST['do_proxy'])) { require_once "../init.php"; __imageproxy_do_proxy(); } rss_set_hook('rss.plugins.items.beforerender', '__imageproxy_filter'); # vim:set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: