cache_dir = 'cache'; // Директория хранения файлов кэша $mcp->cache_time = 24; // Период хранения кэша в часах (0 -- не кэшировать) // Пути, для которых следует применить индивидуальный период хранения кэша $mcp->custom_cache = array ( "^/news" => 0, // Не кэшировать страницу /news ".dhtml$" => 4 // Обновлять кэш для файлов .dhtml раз в 4 часа ); // Ограничение доступа к различным ресурсам, страницам или файлам $mcp->forbidden = array ( "^/badpage.php", // Закрыть доступ к странице /badpage.php ".mp3$" // Закрыть доступ ко всем файлам .mp3 ); $mcp->connect = 'http://www.hsdn.org'; // Адрес подключения // Автозамена реального имени сайта на имя сайта зеркала в заголовках HTTP $mcp->head_replace = array ( 'www.hsdn.org' => $_SERVER['SERVER_NAME'] // Для заголовков, пр. Location ); // Автозамена реального имени сайта на имя сайта зеркала в теле страницы, // а также в ссылках на страницах $mcp->body_replace = array ( 'http://www.hsdn.org' => '' // Заменяем ссылки вида: // href="http://www.hsdn.org/link" на href="/link" ); $mcp->run_cache(); // Запуск класса * * Для кэширования нескольких сайтов: * uri_array(); // Получение аргументов URI // Выбор сайта по первому аргументу URI switch($path[1]) { // Первый сайт (доступен как: http://my-cache.ru/www.hsdn.org/) case 'www.hsdn.org': $mcp->cache_dir = 'cache/www.hsdn.org'; // Директория хранения файлов кэша $mcp->cache_time = 24; // Период хранения кэша в часах (0 -- не кэшировать) $mcp->connect = 'http://www.hsdn.org'; // Адрес подключения $mcp->path = '/www.hsdn.org'; // Рабочий путь класса // Возможно, придется заменить некоторые ссылки $mcp->body_replace = array ( '="/' => '="'.$mcp->path.'/', // Заменяем ссылки вида: // href="/link" на href="/www.hsdn.org/link" ); // ... ниже можно определить остальные переменные, как в первом примере break; // Второй сайт (доступен как: http://my-cache.ru/www.ya.ru/) case 'www.ya.ru': $mcp->cache_dir = 'cache/www.ya.ru'; // Директория хранения файлов кэша $mcp->cache_time = 0; // Период хранения кэша в часах (0 -- не кэшировать) $mcp->connect = 'http://www.ya.ru'; // Адрес подключения $mcp->path = '/www.ya.ru'; // Рабочий путь класса // Возможно, придется заменить некоторые ссылки $mcp->body_replace = array ( '="/' => '="'.$mcp->path.'/', // Заменяем ссылки вида: // href="/link" на href="/www.ya.ru/link" ); // ... ниже можно определить остальные переменные, как в первом примере break; // Сайт по-умолчанию (доступен как: http://my-cache.ru/) default: $mcp->cache_dir = 'cache'; // Директория хранения файлов кэша $mcp->cache_time = 0; // Период хранения кэша в часах (0 -- не кэшировать) $mcp->connect = 'http://www.mail.ru'; // Адрес подключения // ... ниже можно определить остальные переменные, как в первом примере break; } $mcp->run_cache(); // Запуск класса * */ /* * Версия класса */ define('MCP_VERSION', '2.2.3b'); /** * Класс Cacher * * @author HSDN Team */ class Cacher { /* * Хост и порт подключения * * @access public * @var string */ var $connect = null; /* * Хост подключения * * @access private * @var string */ var $host = null; /* * Порт подключения * * @access private * @var int */ var $port = null; /* * Схема запроса * * @access private * @var string */ var $scheme = null; /* * Рабочий путь класса * * @access public * @var string */ var $path = null; /* * Хост и порт подключения через прокси * * @access public * @var string */ var $proxy_connect = null; /* * Хост подключения через прокси * * @access private * @var string */ var $proxy_host = null; /* * Порт подключения через прокси * * @access private * @var int */ var $proxy_port = null; /* * Пользователь прокси-сервера * * @access public * @var string */ var $proxy_user = null; /* * Пароль прокси-сервера * * @access public * @var string */ var $proxy_pass = null; /* * Директория хранения файлов кэша * * @access public * @var string */ var $cache_dir = null; /* * Период хранения кэша * * @access public * @var int */ var $cache_time = null; /* * Массив индивидуального кэширования * * @access public * @var array */ var $custom_cache = array(); /* * Массив запрета доступа к ресурсам * * @access public * @var array */ var $forbidden = array(); /* * Игнорировать "SESSION ID" * * @access public * @var bool */ var $ignore_sid = false; /* * Не кэшировать POST-запросы * * @access public * @var bool */ var $post_no_cache = true; /* * Отправлять заголовок X-Forwarded-For * * @access public * @var bool */ var $forwarded = true; /* * Заменить хост в отправляемых заголовках HTTP_REFERER * * @access public * @var bool */ var $replace_ref = true; /* * Сокет * * @access private * @var resource */ var $socket = null; /* * Кэш * * @access private * @var resource */ var $cache = null; /* * Файл кэширования * * @access private * @var string */ var $cache_file = null; /* * Аргументы URI * * @access private * @var string */ var $uri_argument = null; /* * HTTP-запрос * * @access private * @var string */ var $request = null; /* * Массив замены элементов в заголовках * * @access public * @var array */ var $head_replace = array(); /* * Массив удаления элементов из заголовков * * @access public * @var array */ var $head_cut = array(); /* * Массив замены элементов в теле страницы * * @access public * @var array */ var $body_replace = array(); /* * Массив удаления элементов из тела страницы * * @access public * @var array */ var $body_cut = array(); /* * Тип мета-данных * * @access private * @var string */ var $ret_type = null; /* * Контент заголовков * * @access private * @var string */ var $head = null; /* * Контент тела страницы * * @access private * @var string */ var $body = null; /* * Обозначение создания SSL cоединения с сервером * * @access private * @var string */ var $http_ssl = false; /* * Тип SSL соединения с сервером * * @access private * @var string */ var $http_ssl_type = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; /* * Кодировка страницы * * @access private * @var string */ var $charset = 'UTF-8'; /** * Запуск класса * * @access private */ function run_cache() { ob_start(); $this->get_connect(); if (!$this->uri_argument = $this->prepare_uri()) { return false; } if ($this->check_forbidden()) { $this->print_error('Not access!', 'You don\'t have permission to access '.$this->uri_argument.' resource.'); } $this->cache_file = $this->get_file(); $this->request = $this->get_request(); if ($this->check_exclude()) { $this->no_cache(); } else { if (file_exists($this->cache_file)) { if ((time() - $this->second_to_hour($this->cache_time)) < filemtime($this->cache_file)) { $this->read_cache(); } else { $this->create_cache(); } } else { $this->create_cache(); } } $headers = $this->headers_parse($this->head); $this->ret_type = $headers['Content-Type']; $this->charset = $this->charset_page(); if ($this->is_text()) { if (isset($headers['Content-Encoding']) && preg_match('#gzip#i', $headers['Content-Encoding']) && function_exists('gzinflate')) { $this->body = $this->gz_decode($this->body); } $this->print_headers($this->header_wrapper($this->head)); $this->print_body($this->body_wrapper($this->body)); } } /** * Проверка на исключения и индивидуальное кэширование * * @return bool * @access private */ function check_exclude() { if ($this->post_no_cache) { if ($_SERVER['REQUEST_METHOD'] == 'POST') { return true; } } foreach ($this->custom_cache as $key => $val) { if (preg_match('#'.str_replace('#', '\\#', $key).'#i', $this->uri_argument)) { if ($val) { $this->cache_time = $val; return false; } return true; } } } /** * Проверка на запрет доступа * * @return bool * @access private */ function check_forbidden() { foreach ($this->forbidden as $key => $val) { if (preg_match('#'.str_replace('#', '\\#', $val).'#i', $this->uri_argument)) { return true; } } return false; } /** * Перевод часов в секунды * * @param string $second * @return string * @access private */ function second_to_hour($second) { return $second * 3600; } /** * Сформировать HTTP-запрос * * @return string * @access private */ function get_request() { $method = $_SERVER['REQUEST_METHOD']; $headers = $this->get_user_headers(); $post = $this->get_user_post($headers); $request = $method.' '.(($this->proxy_connect !== null) ? $this->scheme.'://'.$this->host : '').$this->uri_argument." HTTP/1.0\r\n"; $headers['Host'] = $this->host; if ($this->forwarded) { $headers['X-Forwarded-For'] = $_SERVER['REMOTE_ADDR']; } if (isset($headers['Referer']) && $this->replace_ref) { $referer = parse_url($headers['Referer']); $headers['Referer'] = $referer['scheme'].'://'.$this->host.$referer['path'].(isset($referer['query']) ? '?'.$referer['query'] : ''); } if ($this->proxy_user && $this->proxy_pass) { $headers['Proxy-Authorization'] = 'basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass); } unset($headers['Accept-Encoding']); $headers['Connection'] = 'close'; if ($method == 'POST') { $headers['Content-Length'] = strlen($post); $request .= $this->headers_build($headers)."\r\n"; $request .= $post; } else { $request .= $this->headers_build($headers)."\r\n"; } return $request; } /** * Получить отправленные POST-значения * * @return string * @access private */ function get_user_post($headers) { if (empty($HTTP_RAW_POST_DATA)) { if (strlen(file_get_contents("php://input")) > 0) { return file_get_contents("php://input"); } return $this->multipart_user_post($headers); } else { return $HTTP_RAW_POST_DATA; } } /** * Собрать отправленные POST-значения (multipart/form-data) * * @return string * @access private */ function multipart_user_post($headers) { $boundary = preg_replace("|multipart\/form-data; boundary\=(.*)|is", "\\1", $headers['Content-Type']); if (sizeof($_FILES) > 0 and strlen($boundary)) { $data = ''; $boundary = '--'.$boundary; foreach ($_FILES as $key => $file_data) { $data .= $boundary."\r\n"; $data .= 'Content-Disposition: form-data; name="'.$key.'"; filename="'.$file_data['name'].'"'."\r\n"; $data .= 'Content-Type: '.$file_data['type']."\r\n\r\n"; $data .= file_get_contents($file_data['tmp_name'])."\r\n"; } foreach ($_POST as $key => $value) { $data .= $boundary."\r\n"; $data .= 'Content-Disposition: form-data; name="'.$key.'" '."\r\n\r\n"; $data .= $value."\r\n"; } $data .= $boundary.'--'; $post = 'Content-Type: '.$headers['Content-Type']."\r\n"; $post .= 'Content-Length: '.strlen($data)."\r\n\r\n"; $post .= $data; return $post; } return false; } /** * Получить отправленное значение URI * * @return string * @access private */ function get_user_uri() { if ($uri = $_SERVER['REQUEST_URI']) { return $uri; } else { return '/'; } } /** * Получить отправленные заголовки HTTP * * @return string * @access private */ function get_user_headers() { if (!function_exists('getallheaders')) { $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $name = strtolower(str_replace('_', ' ', substr($name, 5))); $name = str_replace(' ', '-', ucwords($name)); $headers[$name] = $value; } } return $headers; } else { return getallheaders(); } } /** * Создать массив элементов URI * * @return array * @access public */ function uri_array() { return explode('/', $this->get_user_uri()); } /** * Получить и подготовить URI * * @return mixed * @access private */ function prepare_uri() { $uri = $this->get_user_uri(); $path = $this->prepare_path(); if (!preg_match('#^/'.str_replace('#', '\\#', $path).'#', $uri)) { return false; } if ($path) { $uri = preg_replace('|^/'.str_replace('|', '\\|', $path).'|isU', '', $uri); } if ($this->ignore_sid) { $uri = $this->replace_sid($uri); } return $uri; } /** * Подготовить путевое значение * * @return string * @access private */ function prepare_path() { return trim($this->path, '/'); } /** * Получить имя хоста и порт для соединения */ function get_connect() { $connect = parse_url($this->connect); $proxy_connect = parse_url($this->proxy_connect); $this->host = $connect['host']; $this->port = isset($connect['port']) ? $connect['port'] : ($connect['scheme'] == 'https' ? 443 : 80); $this->scheme = $connect['scheme']; if (isset($proxy_connect['host']) && isset($proxy_connect['port'])) { $this->proxy_host = $proxy_connect['host']; $this->proxy_port = isset($proxy_connect['port']) ? $proxy_connect['port'] : 80; } } /** * Вывести заголовки * * @param string $raw * @access private */ function print_headers($raw) { $array = $this->headers_parse($raw); $user_headers = $this->get_user_headers(); if (isset($array['ETag']) && strlen($array['ETag']) > 0 && isset($user_headers['If-None-Match']) && isset($array['ETag']) && ($user_headers['If-None-Match'] == $array['ETag'])) { header('HTTP/1.0 304 Not Modified'); die; } else { if ($raw_array = explode("\n", $raw)) { header('HTTP/1.0 '.trim(substr($raw_array[0], 9, strlen($raw_array[0])))); } } $array['X-Mirrored-By'] = 'HSDN Mirroring Cacher'.(defined('MCP_VERSION') ? '/'.MCP_VERSION : null); foreach ($array as $key => $value) { if ($key && $value && $key != 'Transfer-Encoding') { header($key.': '.$value); } } ob_end_flush(); } /** * Вывести тело страницы * * @param string $body * @access private */ function print_body($body) { print $body; flush(); } /** * Не кэшировать контент * * @access private */ function no_cache() { $this->delete_file(); $this->socket = $this->create_socket(); $this->write_socket($this->request); $this->head = $this->get_head($this->socket); $headers = $this->headers_parse($this->head); $this->ret_type = $headers['Content-Type']; $this->body = $this->get_body($this->socket); $this->close_socket(); } /** * Создать кэш * * @access private */ function create_cache() { $this->socket = $this->create_socket(); $this->write_socket($this->request); $this->head = $this->get_head($this->socket); $headers = $this->headers_parse($this->head); $this->ret_type = $headers['Content-Type']; if (file_exists($this->cache_file)) { if (isset($headers['ETag'])) { $this->read_cache(); $cache_headers = $this->headers_parse($this->head); if (file_exists($this->cache_file) && ($headers['ETag'] == $cache_headers['ETag'])) { $body = $this->body; } else { $body = $this->get_body($this->socket); $this->write_cache($this->head, $body); } } if (!isset($headers['Content-Length'])) { $headers['Content-Length'] = 0; } if (filesize($this->cache_file) != $headers['Content-Length']) // Ошибка? { if (!strlen($body)) { $body = $this->get_body($this->socket); } $this->write_cache($this->head, $body); } } else { $body = $this->get_body($this->socket); $this->write_cache($this->head, $body); } $this->close_socket(); $this->body = $body; } /** * Записать в кэш * * @param string $head * @param string $body * @access private */ function write_cache($head, $body) { if ($this->httpcode_parse($head) == 200 && trim($head) && trim($body)) { $this->cache = $this->create_file(); $this->write_file($head."\r\n".$body); $this->close_file(); } } /** * Прочитать кэш * * @access private */ function read_cache() { $this->cache = $this->open_file(); $this->head = $this->get_head($this->cache); $headers = $this->headers_parse($this->head); $this->ret_type = $headers['Content-Type']; $this->body = $this->get_body($this->cache); $this->close_file(); } /** * Подключиться к серверу и открыть сокет * * @return resource * @access private */ function create_socket() { if ($this->proxy_connect) { $fp = @fsockopen($this->proxy_host, $this->proxy_port, $errno, $errstr, 10); } else { $fp = @fsockopen($this->host, $this->port, $errno, $errstr, 10); } if (!is_resource($fp)) { $this->print_error('Bad Gateway!', $errstr.'.'); } if ($this->http_ssl and function_exists('stream_socket_enable_crypto') and function_exists('stream_get_transports') and in_array('ssl', stream_get_transports())) { stream_socket_enable_crypto($fp, TRUE, $this->http_ssl_type); } return $fp; } /** * Записать данные в сокет * * @param string $request * @access private */ function write_socket($request) { if (!fputs($this->socket, $request)) { $this->print_error('Remote Error!', 'Can\'t send request to server.'); } } /** * Закрыть сокет * * @access private */ function close_socket() { if (is_resource($this->socket)) { fclose($this->socket); } $this->socket = null; } /** * Получить имя файла хранения кэша * * @return string * @access private */ function get_file() { return $this->cache_dir.'/'.md5($this->uri_argument); } /** * Открыть файл хранения кэша * * @return resource * @access private */ function open_file() { if (!$cache = @fopen($this->cache_file, 'r')) { $this->print_error('Internal Error!', 'Can\'t read '.$this->cache_file.' Cache File.'); } return $cache; } /** * Закрыть кэш-файла * * @access private */ function close_file() { if (is_resource($this->cache)) { fclose($this->cache); } $this->cache = null; } /** * Создать файл хранения кэша * * @return resource * @access private */ function create_file() { $this->create_dir($this->cache_dir); if (!$cache = @fopen($this->cache_file, 'w')) { $this->print_error('Internal Error!', 'Can\'t open '.$this->cache_file.' Cache File for write.'); } return $cache; } /** * Создать директории для хранения кэша (в том числе рекурсивно) * * @param string $path * @param int $mode * @return bool * @access private */ function create_dir($path, $mode = 0777) { if (file_exists($path)) { return; } $dirs = explode(DIRECTORY_SEPARATOR, $path); $count = count($dirs); $path = ''; for ($i = 0; $i < $count; ++$i) { $path .= $dirs[$i].DIRECTORY_SEPARATOR; if (!@is_dir($path)) { @mkdir($path, $mode); } } } /** * Удалить файл хранения кэша * * @access private */ function delete_file() { if (file_exists($this->cache)) { if (!@unlink($this->cache)) { $this->print_error('Internal Error!', 'Can\'t delete '.$this->cache_file.' Cache File.'); } } } /** * Записать данные в файл хранения кэша * * @param string $source * @access private */ function write_file($source) { if (!fwrite($this->cache, $source)) { $this->print_error('Internal Error!', 'Can\'t write '.$this->cache_file.' Cache File.'); } } /** * Прочитать заголовки из сокета * * @param resource $handler * @return string * @access private */ function get_head($handler) { $head = ''; while (!feof($handler)) { $line = fgets($handler, 2048); if ($line == "\r\n" || $line == "\n") { break; } $head .= $line; } return $head; } /** * Прочитать тело из сокета * * @param resource $handler * @return string * @access private */ function get_body($handler) { $body = ''; if (!$this->is_text()) { $this->print_headers($this->header_wrapper($this->head)); } while (!feof($handler)) { $body .= $line = fread($handler, 4096); if (!$this->is_text()) { $this->print_body($line); } } return $body; } /** * Проверить тип контента на текстовый * * @return bool * @access private */ function is_text() { if (preg_match('#(text|xhtml|xml)#', $this->ret_type)) { return true; } return false; } /** * Получить кодировку полученной страницы * * @return string * @access private */ function charset_page() { if (@preg_match("/charset=(.*)/", $this->ret_type, $charset)) { return $charset[1]; } if (@preg_match("/charset=(.*)(\'|\")/", $this->body, $charset)) { return $charset[1]; } return $this->charset; } /** * Получить HTTP-код из заголовков * * @param string $head * @return string * @access private */ function httpcode_parse($head) { if (strlen($head) < 12) { return FALSE; } $status = substr($head, 9, 3); if (!is_numeric($status)) { return FALSE; } return $status; } /** * Создать массив заголовков HTTP * * @param string $headers * @return mixed * @access private */ function headers_parse($headers) { $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $headers)); $headers = array(); if (sizeof($lines) > 0) { foreach ($lines as $line) { $header = explode(': ', $line); if (isset($header[0]) AND isset($header[1])) { $headers[$header[0]] = trim($header[1]); } } } return $headers; } /** * Собрать HTTP-заголовки из массива * * @param array $headers * @return string * @access private */ function headers_build($headers) { if (sizeof($headers) == 0) { return ''; } $headers_string = ''; foreach ($headers as $name => $value) { if ($name != '' AND $value != '') { $headers_string .= $name.': '.$value."\r\n"; } } return $headers_string."\r\n"; } /** * Вывести сообщение об ошибки и завершить работу класса * * @param string $error * @param string $subject * @access private */ function print_error($error, $subject) { header('HTTP/1.0 500 Internal Server Error'); $for = ($this->host) ? ' for '.$this->host.'' : null; $ver = defined('MCP_VERSION') ? '/'.MCP_VERSION : null; print '

'.$error.'

'. $subject.'


HSDN Mirroring Cacher'.$ver. $for.'
'; exit(); } /** * Обработать заголовки * * @param string $raw * @return string * @access private */ function header_wrapper($raw) { $raw = $this->replace($this->head_replace, $raw); $raw = $this->cut($this->head_cut, $raw); return $raw; } /** * Обработать тело страницы * * @param string $raw * @return string * @access private */ function body_wrapper($raw) { $raw = $this->replace($this->body_replace, $raw); $raw = $this->cut($this->body_cut, $raw); return $raw; } /** * Удалить "SESSION ID" из URI * * @param string $raw * @return string * @access private */ function replace_sid($url) { $ret = preg_replace('/([0-9A-Za-z]+)\=([0-9a-f]{32})/', '', $url); if ($ret != $url) { $ret = preg_replace('/\?\&/', '?', $ret); $ret = preg_replace('/((\&|\?){1})$/', '', $ret); } return $ret; } /** * Заменить фрагменты в контенте * * @param array $array * @param string $source * @return string * @access private */ function replace($array, $source) { foreach ($array as $key => $val) { $source = preg_replace('|'.str_replace('|', '\\|', $key).'|sU', $val, $source); } return $source; } /** * Удалить фрагменты из контента * * @param array $array * @param string $source * @return string * @access private */ function cut($array, $source) { foreach ($array as $val) { preg_match('|'.str_replace('|', '\\|', $val).'|isU', $source, $rep); if (isset($rep[0])) { $source = str_replace($rep[0], '', $source); } } return $source; } /** * Декодировать сжатый GZip-контент * * @param string $data * @return string * @access private */ function gz_decode($data) { $flags = ord(substr($data, 3, 1)); $headerlen = 10; $extralen = 0; $filenamelen = 0; if ($flags & 4) { $extralen = unpack('v' ,substr($data, 10, 2)); $extralen = $extralen[1]; $headerlen += 2 + $extralen; } if ($flags & 8) { $headerlen = strpos($data, chr(0), $headerlen) + 1; } if ($flags & 16) { $headerlen = strpos($data, chr(0), $headerlen) + 1; } if ($flags & 2) { $headerlen += 2; } $unpacked = gzinflate(substr($data, $headerlen)); if ($unpacked === false) { $unpacked = $data; } return $unpacked; } } // End