mcacher.class.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. <?php
  2. /**
  3. * HSDN Mirroring Cacher
  4. *
  5. *
  6. * http://www.hsdn.org
  7. * ICQ: 980001, 988881
  8. *
  9. * @descr Зеркалирующий прокси-сервер
  10. * @author HSDN Team
  11. * @copyright Copyright (c) 2007-2017 Information Networks Ltd.
  12. * @version 2.2.3b
  13. */
  14. /*
  15. * ОПИСАНИЕ
  16. *
  17. * Данный класс позволяет создать зеркало любого удаленного ресурса на
  18. * локальном сервере. Созданное зеркало по сути является прокси сервером,
  19. * который будет непосредственно взаимодействовать с удаленным хостом.
  20. * Благодаря системе встроенного кэширования страниц и картинок, нет нужды
  21. * многократно загружать одну и ту же страницу с сервера.
  22. */
  23. /*
  24. * ВОЗМОЖНОСТИ
  25. *
  26. * - POST/GET и другие HTTP-запросы
  27. * - Кэширование страниц и картинок
  28. * - Возможность загрузки файлов
  29. * - Возможность передачи заголовков к удаленному хосту
  30. * - Поддержка Cookies и Сессий
  31. * - Возможность замены содержимого страниц ресурса
  32. * - Возможность назначать исключения из кэширования
  33. * - Возможность работы через HTTP-прокси
  34. * - Возможность передачи файлов на удаленный сервер
  35. */
  36. /*
  37. * СИСТЕМНЫЕ ТРЕБОВАНИЯ
  38. *
  39. * - PHP 5.2.0 или выше с поддержкой Сокетов
  40. * - Apache 1.3.0 или выше с поддержкой mod_rewrite
  41. */
  42. /*
  43. * ЛИЦЕНЗИЯ
  44. *
  45. * Данный скрипт распространяется по Универсальной общедоступной лицензии
  46. * GNU General Public License (GNU/GPL).
  47. *
  48. * Авторы не несут ответственность за любой ущерб, причиненный любой
  49. * стороне в результате использования этого скрипта.
  50. */
  51. /*
  52. * УСТАНОВКА
  53. *
  54. * 1. Скопируйте этот файл в корневой каталог на вашем хосте.
  55. *
  56. * 2. Создайте директорию `cache' и установите на нее права `0777'
  57. * (подробнее читайте описание команды `chmod').
  58. *
  59. * 3. Создайте файл с названием `cacher.php' и поместите в него скрипт
  60. * вызова данного класса (см. ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ).
  61. *
  62. * 4. Настройте необходимые переменные, указав адрес сайта, путь к
  63. * директории `cache' и т.д. (см. ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ).
  64. *
  65. * 5. Создайте и разместите в корневом каталоге файл `.htaccess' со
  66. * следующим содержанием:
  67. *
  68. DirectoryIndex cacher.php
  69. ErrorDocument 404 /cacher.php
  70. Options FollowSymLinks
  71. RewriteEngine on
  72. RewriteBase /
  73. RewriteRule ^(.*)$ cacher.php
  74. *
  75. * 6. Откройте в браузере ваш хост для просмотра результата работы
  76. * данного класса.
  77. */
  78. /*
  79. * СПРАВКА ПО ПЕРЕМЕННЫМ
  80. *
  81. * Настройки:
  82. * - cache_dir -- Директория хранения файлов кэша
  83. * - cache_time -- Период хранения кэша в часах (0 -- не кэшировать)
  84. * - custom_cache -- Выборочное кэширование
  85. * - forbidden -- Ограничение доступа к определенным ресурсам
  86. * - post_no_cache -- Не кэшировать POST-запросы
  87. * - ignore_sid -- Игнорировать SESSION ID в путях (URI)
  88. * - forwarded -- Отправлять заголовок X-Forwarded-For
  89. * - replace_ref -- Заменить хост в отправляемых заголовках HTTP_REFERER
  90. *
  91. * Соединение:
  92. * - connect -- Адрес подключения
  93. * - proxy_connect -- Адрес подключения через HTTP-прокси
  94. * - proxy_user -- Пользователь прокси-сервера
  95. * - proxy_pass -- Пароль прокси-сервера
  96. * - path -- Рабочий путь класса
  97. *
  98. * Обрабока:
  99. * - head_replace -- Автозамена фрагментов текста в заголовках
  100. * - head_cut -- Автоудаление фрагментов текста из теле заголовков
  101. * - body_replace -- Автозамена фрагментов текста в теле страницы
  102. * - body_cut -- Автоудаление фрагментов текста из теле страницы
  103. */
  104. /*
  105. * ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
  106. *
  107. * Для кэширования одного сайта:
  108. *
  109. <?php
  110. include 'mcacher.class.php'; // Подключение класса
  111. $mcp = new Cacher; // Определение класса
  112. $mcp->cache_dir = 'cache'; // Директория хранения файлов кэша
  113. $mcp->cache_time = 24; // Период хранения кэша в часах (0 -- не кэшировать)
  114. // Пути, для которых следует применить индивидуальный период хранения кэша
  115. $mcp->custom_cache = array
  116. (
  117. "^/news" => 0, // Не кэшировать страницу /news
  118. ".dhtml$" => 4 // Обновлять кэш для файлов .dhtml раз в 4 часа
  119. );
  120. // Ограничение доступа к различным ресурсам, страницам или файлам
  121. $mcp->forbidden = array
  122. (
  123. "^/badpage.php", // Закрыть доступ к странице /badpage.php
  124. ".mp3$" // Закрыть доступ ко всем файлам .mp3
  125. );
  126. $mcp->connect = 'http://www.hsdn.org'; // Адрес подключения
  127. // Автозамена реального имени сайта на имя сайта зеркала в заголовках HTTP
  128. $mcp->head_replace = array
  129. (
  130. 'www.hsdn.org' => $_SERVER['SERVER_NAME'] // Для заголовков, пр. Location
  131. );
  132. // Автозамена реального имени сайта на имя сайта зеркала в теле страницы,
  133. // а также в ссылках на страницах
  134. $mcp->body_replace = array
  135. (
  136. 'http://www.hsdn.org' => '' // Заменяем ссылки вида:
  137. // href="http://www.hsdn.org/link" на href="/link"
  138. );
  139. $mcp->run_cache(); // Запуск класса
  140. *
  141. * Для кэширования нескольких сайтов:
  142. *
  143. <?php
  144. include 'mcacher.class.php'; // Подключение класса
  145. $mcp = new Cacher; // Определение класса
  146. $path = $mcp->uri_array(); // Получение аргументов URI
  147. // Выбор сайта по первому аргументу URI
  148. switch($path[1])
  149. {
  150. // Первый сайт (доступен как: http://my-cache.ru/www.hsdn.org/)
  151. case 'www.hsdn.org':
  152. $mcp->cache_dir = 'cache/www.hsdn.org'; // Директория хранения файлов кэша
  153. $mcp->cache_time = 24; // Период хранения кэша в часах (0 -- не кэшировать)
  154. $mcp->connect = 'http://www.hsdn.org'; // Адрес подключения
  155. $mcp->path = '/www.hsdn.org'; // Рабочий путь класса
  156. // Возможно, придется заменить некоторые ссылки
  157. $mcp->body_replace = array
  158. (
  159. '="/' => '="'.$mcp->path.'/', // Заменяем ссылки вида:
  160. // href="/link" на href="/www.hsdn.org/link"
  161. );
  162. // ... ниже можно определить остальные переменные, как в первом примере
  163. break;
  164. // Второй сайт (доступен как: http://my-cache.ru/www.ya.ru/)
  165. case 'www.ya.ru':
  166. $mcp->cache_dir = 'cache/www.ya.ru'; // Директория хранения файлов кэша
  167. $mcp->cache_time = 0; // Период хранения кэша в часах (0 -- не кэшировать)
  168. $mcp->connect = 'http://www.ya.ru'; // Адрес подключения
  169. $mcp->path = '/www.ya.ru'; // Рабочий путь класса
  170. // Возможно, придется заменить некоторые ссылки
  171. $mcp->body_replace = array
  172. (
  173. '="/' => '="'.$mcp->path.'/', // Заменяем ссылки вида:
  174. // href="/link" на href="/www.ya.ru/link"
  175. );
  176. // ... ниже можно определить остальные переменные, как в первом примере
  177. break;
  178. // Сайт по-умолчанию (доступен как: http://my-cache.ru/)
  179. default:
  180. $mcp->cache_dir = 'cache'; // Директория хранения файлов кэша
  181. $mcp->cache_time = 0; // Период хранения кэша в часах (0 -- не кэшировать)
  182. $mcp->connect = 'http://www.mail.ru'; // Адрес подключения
  183. // ... ниже можно определить остальные переменные, как в первом примере
  184. break;
  185. }
  186. $mcp->run_cache(); // Запуск класса
  187. *
  188. */
  189. /*
  190. * Версия класса
  191. */
  192. define('MCP_VERSION', '2.2.3b');
  193. /**
  194. * Класс Cacher
  195. *
  196. * @author HSDN Team
  197. */
  198. class Cacher
  199. {
  200. /*
  201. * Хост и порт подключения
  202. *
  203. * @access public
  204. * @var string
  205. */
  206. var $connect = null;
  207. /*
  208. * Хост подключения
  209. *
  210. * @access private
  211. * @var string
  212. */
  213. var $host = null;
  214. /*
  215. * Порт подключения
  216. *
  217. * @access private
  218. * @var int
  219. */
  220. var $port = null;
  221. /*
  222. * Схема запроса
  223. *
  224. * @access private
  225. * @var string
  226. */
  227. var $scheme = null;
  228. /*
  229. * Рабочий путь класса
  230. *
  231. * @access public
  232. * @var string
  233. */
  234. var $path = null;
  235. /*
  236. * Хост и порт подключения через прокси
  237. *
  238. * @access public
  239. * @var string
  240. */
  241. var $proxy_connect = null;
  242. /*
  243. * Хост подключения через прокси
  244. *
  245. * @access private
  246. * @var string
  247. */
  248. var $proxy_host = null;
  249. /*
  250. * Порт подключения через прокси
  251. *
  252. * @access private
  253. * @var int
  254. */
  255. var $proxy_port = null;
  256. /*
  257. * Пользователь прокси-сервера
  258. *
  259. * @access public
  260. * @var string
  261. */
  262. var $proxy_user = null;
  263. /*
  264. * Пароль прокси-сервера
  265. *
  266. * @access public
  267. * @var string
  268. */
  269. var $proxy_pass = null;
  270. /*
  271. * Директория хранения файлов кэша
  272. *
  273. * @access public
  274. * @var string
  275. */
  276. var $cache_dir = null;
  277. /*
  278. * Период хранения кэша
  279. *
  280. * @access public
  281. * @var int
  282. */
  283. var $cache_time = null;
  284. /*
  285. * Массив индивидуального кэширования
  286. *
  287. * @access public
  288. * @var array
  289. */
  290. var $custom_cache = array();
  291. /*
  292. * Массив запрета доступа к ресурсам
  293. *
  294. * @access public
  295. * @var array
  296. */
  297. var $forbidden = array();
  298. /*
  299. * Игнорировать "SESSION ID"
  300. *
  301. * @access public
  302. * @var bool
  303. */
  304. var $ignore_sid = false;
  305. /*
  306. * Не кэшировать POST-запросы
  307. *
  308. * @access public
  309. * @var bool
  310. */
  311. var $post_no_cache = true;
  312. /*
  313. * Отправлять заголовок X-Forwarded-For
  314. *
  315. * @access public
  316. * @var bool
  317. */
  318. var $forwarded = true;
  319. /*
  320. * Заменить хост в отправляемых заголовках HTTP_REFERER
  321. *
  322. * @access public
  323. * @var bool
  324. */
  325. var $replace_ref = true;
  326. /*
  327. * Сокет
  328. *
  329. * @access private
  330. * @var resource
  331. */
  332. var $socket = null;
  333. /*
  334. * Кэш
  335. *
  336. * @access private
  337. * @var resource
  338. */
  339. var $cache = null;
  340. /*
  341. * Файл кэширования
  342. *
  343. * @access private
  344. * @var string
  345. */
  346. var $cache_file = null;
  347. /*
  348. * Аргументы URI
  349. *
  350. * @access private
  351. * @var string
  352. */
  353. var $uri_argument = null;
  354. /*
  355. * HTTP-запрос
  356. *
  357. * @access private
  358. * @var string
  359. */
  360. var $request = null;
  361. /*
  362. * Массив замены элементов в заголовках
  363. *
  364. * @access public
  365. * @var array
  366. */
  367. var $head_replace = array();
  368. /*
  369. * Массив удаления элементов из заголовков
  370. *
  371. * @access public
  372. * @var array
  373. */
  374. var $head_cut = array();
  375. /*
  376. * Массив замены элементов в теле страницы
  377. *
  378. * @access public
  379. * @var array
  380. */
  381. var $body_replace = array();
  382. /*
  383. * Массив удаления элементов из тела страницы
  384. *
  385. * @access public
  386. * @var array
  387. */
  388. var $body_cut = array();
  389. /*
  390. * Тип мета-данных
  391. *
  392. * @access private
  393. * @var string
  394. */
  395. var $ret_type = null;
  396. /*
  397. * Контент заголовков
  398. *
  399. * @access private
  400. * @var string
  401. */
  402. var $head = null;
  403. /*
  404. * Контент тела страницы
  405. *
  406. * @access private
  407. * @var string
  408. */
  409. var $body = null;
  410. /*
  411. * Обозначение создания SSL cоединения с сервером
  412. *
  413. * @access private
  414. * @var string
  415. */
  416. var $http_ssl = false;
  417. /*
  418. * Тип SSL соединения с сервером
  419. *
  420. * @access private
  421. * @var string
  422. */
  423. var $http_ssl_type = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
  424. /*
  425. * Кодировка страницы
  426. *
  427. * @access private
  428. * @var string
  429. */
  430. var $charset = 'UTF-8';
  431. /**
  432. * Запуск класса
  433. *
  434. * @access private
  435. */
  436. function run_cache()
  437. {
  438. ob_start();
  439. $this->get_connect();
  440. if (!$this->uri_argument = $this->prepare_uri())
  441. {
  442. return false;
  443. }
  444. if ($this->check_forbidden())
  445. {
  446. $this->print_error('Not access!', 'You don\'t have permission to access '.$this->uri_argument.' resource.');
  447. }
  448. $this->cache_file = $this->get_file();
  449. $this->request = $this->get_request();
  450. if ($this->check_exclude())
  451. {
  452. $this->no_cache();
  453. }
  454. else
  455. {
  456. if (file_exists($this->cache_file))
  457. {
  458. if ((time() - $this->second_to_hour($this->cache_time)) < filemtime($this->cache_file))
  459. {
  460. $this->read_cache();
  461. }
  462. else
  463. {
  464. $this->create_cache();
  465. }
  466. }
  467. else
  468. {
  469. $this->create_cache();
  470. }
  471. }
  472. $headers = $this->headers_parse($this->head);
  473. $this->ret_type = $headers['Content-Type'];
  474. $this->charset = $this->charset_page();
  475. if ($this->is_text())
  476. {
  477. if (isset($headers['Content-Encoding']) && preg_match('#gzip#i', $headers['Content-Encoding']) && function_exists('gzinflate'))
  478. {
  479. $this->body = $this->gz_decode($this->body);
  480. }
  481. $this->print_headers($this->header_wrapper($this->head));
  482. $this->print_body($this->body_wrapper($this->body));
  483. }
  484. }
  485. /**
  486. * Проверка на исключения и индивидуальное кэширование
  487. *
  488. * @return bool
  489. * @access private
  490. */
  491. function check_exclude()
  492. {
  493. if ($this->post_no_cache)
  494. {
  495. if ($_SERVER['REQUEST_METHOD'] == 'POST')
  496. {
  497. return true;
  498. }
  499. }
  500. foreach ($this->custom_cache as $key => $val)
  501. {
  502. if (preg_match('#'.str_replace('#', '\\#', $key).'#i', $this->uri_argument))
  503. {
  504. if ($val)
  505. {
  506. $this->cache_time = $val;
  507. return false;
  508. }
  509. return true;
  510. }
  511. }
  512. }
  513. /**
  514. * Проверка на запрет доступа
  515. *
  516. * @return bool
  517. * @access private
  518. */
  519. function check_forbidden()
  520. {
  521. foreach ($this->forbidden as $key => $val)
  522. {
  523. if (preg_match('#'.str_replace('#', '\\#', $val).'#i', $this->uri_argument))
  524. {
  525. return true;
  526. }
  527. }
  528. return false;
  529. }
  530. /**
  531. * Перевод часов в секунды
  532. *
  533. * @param string $second
  534. * @return string
  535. * @access private
  536. */
  537. function second_to_hour($second)
  538. {
  539. return $second * 3600;
  540. }
  541. /**
  542. * Сформировать HTTP-запрос
  543. *
  544. * @return string
  545. * @access private
  546. */
  547. function get_request()
  548. {
  549. $method = $_SERVER['REQUEST_METHOD'];
  550. $headers = $this->get_user_headers();
  551. $post = $this->get_user_post($headers);
  552. $request = $method.' '.(($this->proxy_connect !== null) ? $this->scheme.'://'.$this->host : '').$this->uri_argument." HTTP/1.0\r\n";
  553. $headers['Host'] = $this->host;
  554. if ($this->forwarded)
  555. {
  556. $headers['X-Forwarded-For'] = $_SERVER['REMOTE_ADDR'];
  557. }
  558. if (isset($headers['Referer']) && $this->replace_ref)
  559. {
  560. $referer = parse_url($headers['Referer']);
  561. $headers['Referer'] = $referer['scheme'].'://'.$this->host.$referer['path'].(isset($referer['query']) ? '?'.$referer['query'] : '');
  562. }
  563. if ($this->proxy_user && $this->proxy_pass)
  564. {
  565. $headers['Proxy-Authorization'] = 'basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
  566. }
  567. unset($headers['Accept-Encoding']);
  568. $headers['Connection'] = 'close';
  569. if ($method == 'POST')
  570. {
  571. $headers['Content-Length'] = strlen($post);
  572. $request .= $this->headers_build($headers)."\r\n";
  573. $request .= $post;
  574. }
  575. else
  576. {
  577. $request .= $this->headers_build($headers)."\r\n";
  578. }
  579. return $request;
  580. }
  581. /**
  582. * Получить отправленные POST-значения
  583. *
  584. * @return string
  585. * @access private
  586. */
  587. function get_user_post($headers)
  588. {
  589. if (empty($HTTP_RAW_POST_DATA))
  590. {
  591. if (strlen(file_get_contents("php://input")) > 0)
  592. {
  593. return file_get_contents("php://input");
  594. }
  595. return $this->multipart_user_post($headers);
  596. }
  597. else
  598. {
  599. return $HTTP_RAW_POST_DATA;
  600. }
  601. }
  602. /**
  603. * Собрать отправленные POST-значения (multipart/form-data)
  604. *
  605. * @return string
  606. * @access private
  607. */
  608. function multipart_user_post($headers)
  609. {
  610. $boundary = preg_replace("|multipart\/form-data; boundary\=(.*)|is", "\\1", $headers['Content-Type']);
  611. if (sizeof($_FILES) > 0 and strlen($boundary))
  612. {
  613. $data = '';
  614. $boundary = '--'.$boundary;
  615. foreach ($_FILES as $key => $file_data)
  616. {
  617. $data .= $boundary."\r\n";
  618. $data .= 'Content-Disposition: form-data; name="'.$key.'"; filename="'.$file_data['name'].'"'."\r\n";
  619. $data .= 'Content-Type: '.$file_data['type']."\r\n\r\n";
  620. $data .= file_get_contents($file_data['tmp_name'])."\r\n";
  621. }
  622. foreach ($_POST as $key => $value)
  623. {
  624. $data .= $boundary."\r\n";
  625. $data .= 'Content-Disposition: form-data; name="'.$key.'" '."\r\n\r\n";
  626. $data .= $value."\r\n";
  627. }
  628. $data .= $boundary.'--';
  629. $post = 'Content-Type: '.$headers['Content-Type']."\r\n";
  630. $post .= 'Content-Length: '.strlen($data)."\r\n\r\n";
  631. $post .= $data;
  632. return $post;
  633. }
  634. return false;
  635. }
  636. /**
  637. * Получить отправленное значение URI
  638. *
  639. * @return string
  640. * @access private
  641. */
  642. function get_user_uri()
  643. {
  644. if ($uri = $_SERVER['REQUEST_URI'])
  645. {
  646. return $uri;
  647. }
  648. else
  649. {
  650. return '/';
  651. }
  652. }
  653. /**
  654. * Получить отправленные заголовки HTTP
  655. *
  656. * @return string
  657. * @access private
  658. */
  659. function get_user_headers()
  660. {
  661. if (!function_exists('getallheaders'))
  662. {
  663. $headers = array();
  664. foreach ($_SERVER as $name => $value)
  665. {
  666. if (substr($name, 0, 5) == 'HTTP_')
  667. {
  668. $name = strtolower(str_replace('_', ' ', substr($name, 5)));
  669. $name = str_replace(' ', '-', ucwords($name));
  670. $headers[$name] = $value;
  671. }
  672. }
  673. return $headers;
  674. }
  675. else
  676. {
  677. return getallheaders();
  678. }
  679. }
  680. /**
  681. * Создать массив элементов URI
  682. *
  683. * @return array
  684. * @access public
  685. */
  686. function uri_array()
  687. {
  688. return explode('/', $this->get_user_uri());
  689. }
  690. /**
  691. * Получить и подготовить URI
  692. *
  693. * @return mixed
  694. * @access private
  695. */
  696. function prepare_uri()
  697. {
  698. $uri = $this->get_user_uri();
  699. $path = $this->prepare_path();
  700. if (!preg_match('#^/'.str_replace('#', '\\#', $path).'#', $uri))
  701. {
  702. return false;
  703. }
  704. if ($path)
  705. {
  706. $uri = preg_replace('|^/'.str_replace('|', '\\|', $path).'|isU', '', $uri);
  707. }
  708. if ($this->ignore_sid)
  709. {
  710. $uri = $this->replace_sid($uri);
  711. }
  712. return $uri;
  713. }
  714. /**
  715. * Подготовить путевое значение
  716. *
  717. * @return string
  718. * @access private
  719. */
  720. function prepare_path()
  721. {
  722. return trim($this->path, '/');
  723. }
  724. /**
  725. * Получить имя хоста и порт для соединения
  726. */
  727. function get_connect()
  728. {
  729. $connect = parse_url($this->connect);
  730. $proxy_connect = parse_url($this->proxy_connect);
  731. $this->host = $connect['host'];
  732. $this->port = isset($connect['port']) ? $connect['port'] : ($connect['scheme'] == 'https' ? 443 : 80);
  733. $this->scheme = $connect['scheme'];
  734. if (isset($proxy_connect['host']) && isset($proxy_connect['port']))
  735. {
  736. $this->proxy_host = $proxy_connect['host'];
  737. $this->proxy_port = isset($proxy_connect['port']) ? $proxy_connect['port'] : 80;
  738. }
  739. }
  740. /**
  741. * Вывести заголовки
  742. *
  743. * @param string $raw
  744. * @access private
  745. */
  746. function print_headers($raw)
  747. {
  748. $array = $this->headers_parse($raw);
  749. $user_headers = $this->get_user_headers();
  750. if (isset($array['ETag']) && strlen($array['ETag']) > 0 && isset($user_headers['If-None-Match']) && isset($array['ETag']) && ($user_headers['If-None-Match'] == $array['ETag']))
  751. {
  752. header('HTTP/1.0 304 Not Modified');
  753. die;
  754. }
  755. else
  756. {
  757. if ($raw_array = explode("\n", $raw))
  758. {
  759. header('HTTP/1.0 '.trim(substr($raw_array[0], 9, strlen($raw_array[0]))));
  760. }
  761. }
  762. $array['X-Mirrored-By'] = 'HSDN Mirroring Cacher'.(defined('MCP_VERSION') ? '/'.MCP_VERSION : null);
  763. foreach ($array as $key => $value)
  764. {
  765. if ($key && $value && $key != 'Transfer-Encoding')
  766. {
  767. header($key.': '.$value);
  768. }
  769. }
  770. ob_end_flush();
  771. }
  772. /**
  773. * Вывести тело страницы
  774. *
  775. * @param string $body
  776. * @access private
  777. */
  778. function print_body($body)
  779. {
  780. print $body;
  781. flush();
  782. }
  783. /**
  784. * Не кэшировать контент
  785. *
  786. * @access private
  787. */
  788. function no_cache()
  789. {
  790. $this->delete_file();
  791. $this->socket = $this->create_socket();
  792. $this->write_socket($this->request);
  793. $this->head = $this->get_head($this->socket);
  794. $headers = $this->headers_parse($this->head);
  795. $this->ret_type = $headers['Content-Type'];
  796. $this->body = $this->get_body($this->socket);
  797. $this->close_socket();
  798. }
  799. /**
  800. * Создать кэш
  801. *
  802. * @access private
  803. */
  804. function create_cache()
  805. {
  806. $this->socket = $this->create_socket();
  807. $this->write_socket($this->request);
  808. $this->head = $this->get_head($this->socket);
  809. $headers = $this->headers_parse($this->head);
  810. $this->ret_type = $headers['Content-Type'];
  811. if (file_exists($this->cache_file))
  812. {
  813. if (isset($headers['ETag']))
  814. {
  815. $this->read_cache();
  816. $cache_headers = $this->headers_parse($this->head);
  817. if (file_exists($this->cache_file) && ($headers['ETag'] == $cache_headers['ETag']))
  818. {
  819. $body = $this->body;
  820. }
  821. else
  822. {
  823. $body = $this->get_body($this->socket);
  824. $this->write_cache($this->head, $body);
  825. }
  826. }
  827. if (!isset($headers['Content-Length']))
  828. {
  829. $headers['Content-Length'] = 0;
  830. }
  831. if (filesize($this->cache_file) != $headers['Content-Length']) // Ошибка?
  832. {
  833. if (!strlen($body))
  834. {
  835. $body = $this->get_body($this->socket);
  836. }
  837. $this->write_cache($this->head, $body);
  838. }
  839. }
  840. else
  841. {
  842. $body = $this->get_body($this->socket);
  843. $this->write_cache($this->head, $body);
  844. }
  845. $this->close_socket();
  846. $this->body = $body;
  847. }
  848. /**
  849. * Записать в кэш
  850. *
  851. * @param string $head
  852. * @param string $body
  853. * @access private
  854. */
  855. function write_cache($head, $body)
  856. {
  857. if ($this->httpcode_parse($head) == 200 && trim($head) && trim($body))
  858. {
  859. $this->cache = $this->create_file();
  860. $this->write_file($head."\r\n".$body);
  861. $this->close_file();
  862. }
  863. }
  864. /**
  865. * Прочитать кэш
  866. *
  867. * @access private
  868. */
  869. function read_cache()
  870. {
  871. $this->cache = $this->open_file();
  872. $this->head = $this->get_head($this->cache);
  873. $headers = $this->headers_parse($this->head);
  874. $this->ret_type = $headers['Content-Type'];
  875. $this->body = $this->get_body($this->cache);
  876. $this->close_file();
  877. }
  878. /**
  879. * Подключиться к серверу и открыть сокет
  880. *
  881. * @return resource
  882. * @access private
  883. */
  884. function create_socket()
  885. {
  886. if ($this->proxy_connect)
  887. {
  888. $fp = @fsockopen($this->proxy_host, $this->proxy_port, $errno, $errstr, 10);
  889. }
  890. else
  891. {
  892. $fp = @fsockopen($this->host, $this->port, $errno, $errstr, 10);
  893. }
  894. if (!is_resource($fp))
  895. {
  896. $this->print_error('Bad Gateway!', $errstr.'.');
  897. }
  898. if ($this->http_ssl and
  899. function_exists('stream_socket_enable_crypto') and
  900. function_exists('stream_get_transports') and
  901. in_array('ssl', stream_get_transports()))
  902. {
  903. stream_socket_enable_crypto($fp, TRUE, $this->http_ssl_type);
  904. }
  905. return $fp;
  906. }
  907. /**
  908. * Записать данные в сокет
  909. *
  910. * @param string $request
  911. * @access private
  912. */
  913. function write_socket($request)
  914. {
  915. if (!fputs($this->socket, $request))
  916. {
  917. $this->print_error('Remote Error!', 'Can\'t send request to server.');
  918. }
  919. }
  920. /**
  921. * Закрыть сокет
  922. *
  923. * @access private
  924. */
  925. function close_socket()
  926. {
  927. if (is_resource($this->socket))
  928. {
  929. fclose($this->socket);
  930. }
  931. $this->socket = null;
  932. }
  933. /**
  934. * Получить имя файла хранения кэша
  935. *
  936. * @return string
  937. * @access private
  938. */
  939. function get_file()
  940. {
  941. return $this->cache_dir.'/'.md5($this->uri_argument);
  942. }
  943. /**
  944. * Открыть файл хранения кэша
  945. *
  946. * @return resource
  947. * @access private
  948. */
  949. function open_file()
  950. {
  951. if (!$cache = @fopen($this->cache_file, 'r'))
  952. {
  953. $this->print_error('Internal Error!', 'Can\'t read '.$this->cache_file.' Cache File.');
  954. }
  955. return $cache;
  956. }
  957. /**
  958. * Закрыть кэш-файла
  959. *
  960. * @access private
  961. */
  962. function close_file()
  963. {
  964. if (is_resource($this->cache))
  965. {
  966. fclose($this->cache);
  967. }
  968. $this->cache = null;
  969. }
  970. /**
  971. * Создать файл хранения кэша
  972. *
  973. * @return resource
  974. * @access private
  975. */
  976. function create_file()
  977. {
  978. $this->create_dir($this->cache_dir);
  979. if (!$cache = @fopen($this->cache_file, 'w'))
  980. {
  981. $this->print_error('Internal Error!', 'Can\'t open '.$this->cache_file.' Cache File for write.');
  982. }
  983. return $cache;
  984. }
  985. /**
  986. * Создать директории для хранения кэша (в том числе рекурсивно)
  987. *
  988. * @param string $path
  989. * @param int $mode
  990. * @return bool
  991. * @access private
  992. */
  993. function create_dir($path, $mode = 0777)
  994. {
  995. if (file_exists($path))
  996. {
  997. return;
  998. }
  999. $dirs = explode(DIRECTORY_SEPARATOR, $path);
  1000. $count = count($dirs);
  1001. $path = '';
  1002. for ($i = 0; $i < $count; ++$i)
  1003. {
  1004. $path .= $dirs[$i].DIRECTORY_SEPARATOR;
  1005. if (!@is_dir($path))
  1006. {
  1007. @mkdir($path, $mode);
  1008. }
  1009. }
  1010. }
  1011. /**
  1012. * Удалить файл хранения кэша
  1013. *
  1014. * @access private
  1015. */
  1016. function delete_file()
  1017. {
  1018. if (file_exists($this->cache))
  1019. {
  1020. if (!@unlink($this->cache))
  1021. {
  1022. $this->print_error('Internal Error!', 'Can\'t delete '.$this->cache_file.' Cache File.');
  1023. }
  1024. }
  1025. }
  1026. /**
  1027. * Записать данные в файл хранения кэша
  1028. *
  1029. * @param string $source
  1030. * @access private
  1031. */
  1032. function write_file($source)
  1033. {
  1034. if (!fwrite($this->cache, $source))
  1035. {
  1036. $this->print_error('Internal Error!', 'Can\'t write '.$this->cache_file.' Cache File.');
  1037. }
  1038. }
  1039. /**
  1040. * Прочитать заголовки из сокета
  1041. *
  1042. * @param resource $handler
  1043. * @return string
  1044. * @access private
  1045. */
  1046. function get_head($handler)
  1047. {
  1048. $head = '';
  1049. while (!feof($handler))
  1050. {
  1051. $line = fgets($handler, 2048);
  1052. if ($line == "\r\n" || $line == "\n")
  1053. {
  1054. break;
  1055. }
  1056. $head .= $line;
  1057. }
  1058. return $head;
  1059. }
  1060. /**
  1061. * Прочитать тело из сокета
  1062. *
  1063. * @param resource $handler
  1064. * @return string
  1065. * @access private
  1066. */
  1067. function get_body($handler)
  1068. {
  1069. $body = '';
  1070. if (!$this->is_text())
  1071. {
  1072. $this->print_headers($this->header_wrapper($this->head));
  1073. }
  1074. while (!feof($handler))
  1075. {
  1076. $body .= $line = fread($handler, 4096);
  1077. if (!$this->is_text())
  1078. {
  1079. $this->print_body($line);
  1080. }
  1081. }
  1082. return $body;
  1083. }
  1084. /**
  1085. * Проверить тип контента на текстовый
  1086. *
  1087. * @return bool
  1088. * @access private
  1089. */
  1090. function is_text()
  1091. {
  1092. if (preg_match('#(text|xhtml|xml)#', $this->ret_type))
  1093. {
  1094. return true;
  1095. }
  1096. return false;
  1097. }
  1098. /**
  1099. * Получить кодировку полученной страницы
  1100. *
  1101. * @return string
  1102. * @access private
  1103. */
  1104. function charset_page()
  1105. {
  1106. if (@preg_match("/charset=(.*)/", $this->ret_type, $charset))
  1107. {
  1108. return $charset[1];
  1109. }
  1110. if (@preg_match("/charset=(.*)(\'|\")/", $this->body, $charset))
  1111. {
  1112. return $charset[1];
  1113. }
  1114. return $this->charset;
  1115. }
  1116. /**
  1117. * Получить HTTP-код из заголовков
  1118. *
  1119. * @param string $head
  1120. * @return string
  1121. * @access private
  1122. */
  1123. function httpcode_parse($head)
  1124. {
  1125. if (strlen($head) < 12)
  1126. {
  1127. return FALSE;
  1128. }
  1129. $status = substr($head, 9, 3);
  1130. if (!is_numeric($status))
  1131. {
  1132. return FALSE;
  1133. }
  1134. return $status;
  1135. }
  1136. /**
  1137. * Создать массив заголовков HTTP
  1138. *
  1139. * @param string $headers
  1140. * @return mixed
  1141. * @access private
  1142. */
  1143. function headers_parse($headers)
  1144. {
  1145. $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $headers));
  1146. $headers = array();
  1147. if (sizeof($lines) > 0)
  1148. {
  1149. foreach ($lines as $line)
  1150. {
  1151. $header = explode(': ', $line);
  1152. if (isset($header[0]) AND isset($header[1]))
  1153. {
  1154. $headers[$header[0]] = trim($header[1]);
  1155. }
  1156. }
  1157. }
  1158. return $headers;
  1159. }
  1160. /**
  1161. * Собрать HTTP-заголовки из массива
  1162. *
  1163. * @param array $headers
  1164. * @return string
  1165. * @access private
  1166. */
  1167. function headers_build($headers)
  1168. {
  1169. if (sizeof($headers) == 0)
  1170. {
  1171. return '';
  1172. }
  1173. $headers_string = '';
  1174. foreach ($headers as $name => $value)
  1175. {
  1176. if ($name != '' AND $value != '')
  1177. {
  1178. $headers_string .= $name.': '.$value."\r\n";
  1179. }
  1180. }
  1181. return $headers_string."\r\n";
  1182. }
  1183. /**
  1184. * Вывести сообщение об ошибки и завершить работу класса
  1185. *
  1186. * @param string $error
  1187. * @param string $subject
  1188. * @access private
  1189. */
  1190. function print_error($error, $subject)
  1191. {
  1192. header('HTTP/1.0 500 Internal Server Error');
  1193. $for = ($this->host) ? ' for <a href="http://'.$this->host.'">'.$this->host.'</a>' : null;
  1194. $ver = defined('MCP_VERSION') ? '/'.MCP_VERSION : null;
  1195. print '<html><head></head><body bgcolor="#CCCCFF"><h1>'.$error.'</h1>'.
  1196. $subject.'<p><hr><address>HSDN Mirroring Cacher'.$ver.
  1197. $for.'</address></body></html>';
  1198. exit();
  1199. }
  1200. /**
  1201. * Обработать заголовки
  1202. *
  1203. * @param string $raw
  1204. * @return string
  1205. * @access private
  1206. */
  1207. function header_wrapper($raw)
  1208. {
  1209. $raw = $this->replace($this->head_replace, $raw);
  1210. $raw = $this->cut($this->head_cut, $raw);
  1211. return $raw;
  1212. }
  1213. /**
  1214. * Обработать тело страницы
  1215. *
  1216. * @param string $raw
  1217. * @return string
  1218. * @access private
  1219. */
  1220. function body_wrapper($raw)
  1221. {
  1222. $raw = $this->replace($this->body_replace, $raw);
  1223. $raw = $this->cut($this->body_cut, $raw);
  1224. return $raw;
  1225. }
  1226. /**
  1227. * Удалить "SESSION ID" из URI
  1228. *
  1229. * @param string $raw
  1230. * @return string
  1231. * @access private
  1232. */
  1233. function replace_sid($url)
  1234. {
  1235. $ret = preg_replace('/([0-9A-Za-z]+)\=([0-9a-f]{32})/', '', $url);
  1236. if ($ret != $url)
  1237. {
  1238. $ret = preg_replace('/\?\&/', '?', $ret);
  1239. $ret = preg_replace('/((\&|\?){1})$/', '', $ret);
  1240. }
  1241. return $ret;
  1242. }
  1243. /**
  1244. * Заменить фрагменты в контенте
  1245. *
  1246. * @param array $array
  1247. * @param string $source
  1248. * @return string
  1249. * @access private
  1250. */
  1251. function replace($array, $source)
  1252. {
  1253. foreach ($array as $key => $val)
  1254. {
  1255. $source = preg_replace('|'.str_replace('|', '\\|', $key).'|sU', $val, $source);
  1256. }
  1257. return $source;
  1258. }
  1259. /**
  1260. * Удалить фрагменты из контента
  1261. *
  1262. * @param array $array
  1263. * @param string $source
  1264. * @return string
  1265. * @access private
  1266. */
  1267. function cut($array, $source)
  1268. {
  1269. foreach ($array as $val)
  1270. {
  1271. preg_match('|'.str_replace('|', '\\|', $val).'|isU', $source, $rep);
  1272. if (isset($rep[0]))
  1273. {
  1274. $source = str_replace($rep[0], '', $source);
  1275. }
  1276. }
  1277. return $source;
  1278. }
  1279. /**
  1280. * Декодировать сжатый GZip-контент
  1281. *
  1282. * @param string $data
  1283. * @return string
  1284. * @access private
  1285. */
  1286. function gz_decode($data)
  1287. {
  1288. $flags = ord(substr($data, 3, 1));
  1289. $headerlen = 10;
  1290. $extralen = 0;
  1291. $filenamelen = 0;
  1292. if ($flags & 4)
  1293. {
  1294. $extralen = unpack('v' ,substr($data, 10, 2));
  1295. $extralen = $extralen[1];
  1296. $headerlen += 2 + $extralen;
  1297. }
  1298. if ($flags & 8)
  1299. {
  1300. $headerlen = strpos($data, chr(0), $headerlen) + 1;
  1301. }
  1302. if ($flags & 16)
  1303. {
  1304. $headerlen = strpos($data, chr(0), $headerlen) + 1;
  1305. }
  1306. if ($flags & 2)
  1307. {
  1308. $headerlen += 2;
  1309. }
  1310. $unpacked = gzinflate(substr($data, $headerlen));
  1311. if ($unpacked === false)
  1312. {
  1313. $unpacked = $data;
  1314. }
  1315. return $unpacked;
  1316. }
  1317. }
  1318. // End