Advertisement
Guest User

Untitled

a guest
Oct 5th, 2016
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 24.75 KB | None | 0 0
  1. <?php
  2.  
  3. function dump($v) {
  4.   echo('<pre>' . var_export($v, true) . '</pre>');
  5. }
  6.  
  7.  
  8. error_reporting(E_ALL);
  9.  
  10. // header("http/1.0              404                \t\t\tNot Found\t");
  11. // header("Content-Type:\t \t  text/plain;charset=windows-1251 =пизда%00;charset   \t\t\t\t= \"utf-8\" foo, bar");
  12. header('cache-control: no-cache');
  13. header('Cache-control: no-store'); // cache-control: no-store
  14. // echo("Тест\n");
  15.  
  16. // list(, $h) = unpack('n', "\0\x20");
  17. // print_r($h);
  18.  
  19. // $f = fopen('long_string.txt', 'r');
  20. // echo(fgets($f, null));
  21. // fgets($f, null);
  22. // fgets($f);
  23. // fread($f, 65536);
  24. // var_dump(fread($f, 1));
  25. // echo(fread($f, null));
  26. // die;
  27.  
  28.  
  29. // preg_match('~;\s*charset\s*=\s*(?:"([^"]+)"|([^\s;]+))~i', "text/html;  charset\t=   utf-8\t\"q;", $matches);
  30. // var_dump($matches);
  31. // echo('charset: ' . (count($matches) ? (isset($matches[2]) ? $matches[2] : $matches[1]) : ''));
  32. // echo('\\\t\'\0 ');
  33.  
  34.  
  35. preg_match_all('/[\x00-\xff]/', 'тест', $m);
  36. // var_dump($m);
  37.  
  38. $headers = "HTTP/1.1 200\r\n" .
  39.            "Content-Type:" .
  40.            "\r\n\ttext/html;" .
  41.            "\r\n\t charset=utf-8";
  42.  
  43. // echo("Headers:\n");
  44. // echo(preg_replace('/\r\n[ \t]+/', ' ', $headers));
  45. // Bad chars: \x00-\x08\x0b\x0c\x0e-\x1f\x7f
  46.  
  47. // set_time_limit(5);
  48. // echo(str_repeat('-', 80) . PHP_EOL);
  49.  
  50. // list($a, $b) = explode(':', '');
  51.  
  52. // die;
  53.  
  54. if (!function_exists('http_build_url')) {
  55.   function http_build_url($parts) {
  56.     if (!is_array($parts)) {
  57.       return false;
  58.     }
  59.  
  60.     return (isset($parts['scheme']) ? $parts['scheme'] . '://' : '') .
  61.       (isset($parts['user']) ?
  62.         $parts['user'] .
  63.         (isset($parts['pass']) ? ':' . $parts['pass'] : '') . '@'
  64.         : '') .
  65.       (isset($parts['host']) ? $parts['host']: '') .
  66.       (isset($parts['port']) ? ':' . $parts['port']: '') .
  67.       (isset($parts['path']) ?
  68.         (substr($parts['path'], 0, 1) != '/' ? '/' : '') . $parts['path']
  69.         : '') .
  70.       (isset($parts['query']) ? '?' . $parts['query']: '') .
  71.       (isset($parts['fragment']) ? '#' . $parts['fragment']: '');
  72.   }
  73. }
  74.  
  75.  
  76. class SocketClient {
  77.   protected $resource;
  78.  
  79.   public function __construct($remoteSocket, $connectionTimeout = null, $flags = STREAM_CLIENT_CONNECT, $context = null) {
  80.     if (is_null($connectionTimeout)) {
  81.       $connectionTimeout = ini_get('default_socket_timeout');
  82.     }
  83.     // http://php.net/manual/ru/function.set-error-handler.php
  84.     set_error_handler(array($this, 'errorHandler'));
  85.     if (is_null($context)) {
  86.       $this->resource = stream_socket_client($remoteSocket, $errNo, $errStr, $connectionTimeout, $flags);
  87.     } else {
  88.       $this->resource = stream_socket_client($remoteSocket, $errNo, $errStr, $connectionTimeout, $flags, $context);
  89.     }
  90.     restore_error_handler();
  91.     // if ($this->resource === false) {
  92.       // $errStr = iconv('windows-1251', 'utf-8', $errStr);
  93.     //   throw new SocketError(sprintf('Connection to %s failed: %s (%d)', $remoteSocket, $errStr, $errNo));
  94.     // }
  95.   }
  96.  
  97.   public function write($data) {
  98.     $bytes = @fwrite($this->resource, $data);
  99.     $this->checkTimeout();
  100.     if ($bytes === false) {
  101.       throw new SocketError('Could not write to socket');
  102.     }
  103.     return $bytes;
  104.   }
  105.  
  106.   public function read($size) {
  107.     // Если достигли конца файла, вернет пустую строку. Только в случае ошибки возвращает false
  108.     $data = @fread($this->resource, $size);
  109.     $this->checkTimeout();
  110.     if ($data === false) {
  111.       throw new SocketError('Could not read from socket');
  112.     }
  113.     return $data;
  114.   }
  115.  
  116.   // читает данные пока сервер не разорвет соединение, а поэтому может повесить скрипт
  117.   public function getContents($maxLength = -1, $offset = -1) {
  118.     // http://php.net/manual/ru/function.stream-get-contents.php
  119.     $data = @stream_get_contents($this->resource, $maxLength, $offset);
  120.     // $data не равна false при истечении таймаута
  121.     $this->checkTimeout();
  122.     if ($data === false) {
  123.       throw new SocketError('Could not get contents from socket');
  124.     }
  125.     return $data;
  126.   }
  127.  
  128.   public function readLine($size = null) {
  129.     if (is_null($size)) {
  130.       // fgets($fp, null) приводит к ошибке
  131.       // Читает строку пока не встретит \n либо конец файла
  132.       $data = @fgets($this->resource);
  133.     } else {
  134.       $data = @fgets($this->resource, $size);
  135.     }
  136.     $this->checkTimeout();
  137.     if ($data === false) {
  138.       throw new SocketError('Could not read from socket');
  139.     }
  140.     return $data;
  141.   }
  142.  
  143.   public function eof() {
  144.     return $this->isResource() ? feof($this->resource) : true;
  145.   }
  146.  
  147.   public function setTimeout($seconds, $microseconds = 0) {
  148.     return stream_set_timeout($this->resource, $seconds, $microseconds);
  149.   }
  150.  
  151.   public function setBlocking($mode) {
  152.     return stream_set_blocking($this->resource, $mode);
  153.   }
  154.  
  155.   public function enableCrypto($enable, $type = null) {
  156.     // http://php.net/manual/ru/function.stream-socket-enable-crypto.php
  157.     if (!is_null($type)) {
  158.       return @stream_socket_enable_crypto($this->resource, $enable, $type);
  159.     }
  160.     return @stream_socket_enable_crypto($this->resource, $enable);
  161.   }
  162.  
  163.   public function getMetaData($key = null) {
  164.     if ($this->isResource()) {
  165.       $meta = stream_get_meta_data($this->resource);
  166.       if (is_null($key)) {
  167.         return $meta;
  168.       }
  169.       if (isset($meta[$key])) {
  170.         return $meta[$key];
  171.       }
  172.     }
  173.     // return null;
  174.   }
  175.  
  176.   public function isResource() {
  177.     return is_resource($this->resource);
  178.   }
  179.  
  180.   public function close() {
  181.     if ($this->isResource()) {
  182.       fclose($this->resource);
  183.     }
  184.   }
  185.  
  186.   public function __destruct() {
  187.     // Может понадобиться постоянное соединение
  188.     // $this->close();
  189.   }
  190.  
  191.   protected function errorHandler($errNo, $errStr, $errFile, $errLine) {
  192.     throw new SocketError(sprintf('Socket error #%d: %s', $errNo, $errStr));
  193.   }
  194.  
  195.   protected function checkTimeout() {
  196.     if ($this->getMetaData('timed_out')) {
  197.       throw new SocketTimeout('Socket timed out');
  198.     }
  199.   }
  200. }
  201.  
  202.  
  203. class SocketError extends Exception {}
  204.  
  205.  
  206. class SocketTimeout extends SocketError {}
  207.  
  208.  
  209. class Header {
  210.   protected $name;
  211.   protected $value;
  212.  
  213.   public function __construct($name, $value) {
  214.     $this->setName($name);
  215.     $this->setValue($value);
  216.   }
  217.  
  218.   public function setName($name) {
  219.     $this->name = $name;
  220.   }
  221.  
  222.   public function setValue($value) {
  223.     $this->value = $value;
  224.   }
  225.  
  226.   public function getName() {
  227.     return $this->name;
  228.   }
  229.  
  230.   public function getValue() {
  231.     return $this->value;
  232.   }
  233.  
  234.   public function __toString() {
  235.     return sprintf('%s: %s', $this->name,  $this->value);
  236.   }
  237. }
  238.  
  239.  
  240. /**
  241.   * Класс для работы с http-заголовками.
  242.   *
  243.   * Имена полей регистронезависимы. Значения полей можно получить, обращаясь к
  244.   * объекту как к массиву. К объекту применимы вызовы функций count, isset и
  245.   * unset, а так же возможен по нему обход с помощью цикла foreach.
  246.   */
  247. class Headers implements ArrayAccess, Countable, IteratorAggregate {
  248.   protected $headers = array();
  249.  
  250.   public function __construct($headers = array()) {
  251.     $this->update($headers);
  252.   }
  253.  
  254.   /**
  255.     * Добавляет новый заголовок.
  256.     */
  257.   public function add($name, $value) {
  258.     $this->headers[strtolower($name)] = new Header($name, $value);
  259.   }
  260.  
  261.   /**
  262.     * Добавляет новый заголовок либо заменяет значение существующего, при этом
  263.     * оригинальное имя сохраняется.
  264.     */
  265.   public function set($name, $value) {
  266.     $lower_name = strtolower($name);
  267.  
  268.     if (isset($this->headers[$lower_name])) {
  269.       $this->headers[$lower_name]->setValue($value);
  270.     } else {
  271.       $this->headers[$lower_name] = new Header($name, $value);
  272.     }
  273.  
  274.     // возвращаем установленное значение
  275.     return $value;
  276.   }
  277.  
  278.   public function update($data) {
  279.     foreach ($data as $name => $value) {
  280.       $this->set($name, $value);
  281.     }
  282.   }
  283.  
  284.   public function get($name, $default = null) {
  285.     $name = strtolower($name);
  286.     return isset($this->headers[$name]) ? $this->headers[$name]->getValue() : $default;
  287.   }
  288.  
  289.   /**
  290.     * Возвращает оригинальное имя заголовка.
  291.     */
  292.   public function getOriginalName($name) {
  293.     $name = strtolower($name);
  294.  
  295.     if (isset($this->headers[$name])) {
  296.       return $this->headers[$name]->getName();
  297.     }
  298.   }
  299.  
  300.   public function has($name) {
  301.     return isset($this->headers[strtolower($name)]);
  302.   }
  303.  
  304.   public function remove($name) {
  305.     unset($this->headers[strtolower($name)]);
  306.   }
  307.  
  308.   public function toArray() {
  309.     $headers = array();
  310.  
  311.     foreach ($this->headers as $header) {
  312.       $headers[$header->getName()] = $header->getValue();
  313.     }
  314.  
  315.     return $headers;
  316.   }
  317.  
  318.   public function __toString() {
  319.     $out = '';
  320.    
  321.     foreach ($this->headers as $header) {
  322.       $out .= $header->__toString() . "\r\n";
  323.     }
  324.  
  325.     return $out;
  326.   }
  327.  
  328.   public function __clone() {
  329.     foreach ($this->headers as &$h) {
  330.       $h = clone $h;
  331.     }
  332.   }
  333.  
  334.   public static function parse($header_str) {
  335.     // new self будет создавать экземпляр класса, где был объявлен метод
  336.     $headers = new static;
  337.  
  338.     if ($header_str) {
  339.       // normalize LWS
  340.       $header_str = preg_replace('/\r\n[\x20\t]+/', ' ', $header_str);
  341.       $offset = 0;
  342.       $length = strlen($header_str);
  343.  
  344.       while ($offset < $length) {
  345.         $pos = strpos($header_str, "\r\n", $offset);
  346.  
  347.         if ($pos === false) {
  348.           throw new MalformedHeaderError('Header should end with CRLF (\x0d\x0a)');
  349.         }
  350.  
  351.         $line = substr($header_str, $offset, $pos - $offset);
  352.         $parts = explode(':', $line, 2);
  353.  
  354.         if (count($parts) < 2) {
  355.           throw new MalformedHeaderError('Header without colon (:)');
  356.         }
  357.  
  358.         list($name, $value) = $parts;
  359.         $value = trim($value);
  360.        
  361.         if (isset($headers[$name])) {
  362.           // по rfc заголовки с одинаковыми либо различающимися лишь регистром
  363.           // именами могут быть объеденены в один. Их значения должны быть
  364.           // добавлены к значениям первого заголовка и разделены запятыми.
  365.           //
  366.           // Пример:
  367.           //
  368.           // Cache-Control: no-cache
  369.           // cache-control: no-store
  370.           //
  371.           // После нормализации:
  372.           //
  373.           // Cache-Control: no-cache, no-store
  374.           $headers[$name] .= ', ' . $value;
  375.         } else {
  376.           $headers[$name] = $value;
  377.         }
  378.  
  379.         $offset = $pos + 2;
  380.  
  381.         // if ($offset == $length) {
  382.         //   break;
  383.         // }
  384.       }
  385.     }
  386.  
  387.     return $headers;
  388.   }
  389.  
  390.   public function offsetSet($offset, $value) {
  391.     return $this->set($offset, $value);
  392.   }
  393.  
  394.   public function offsetExists($offset) {
  395.     return $this->has($offset);
  396.   }
  397.  
  398.   public function offsetUnset($offset) {
  399.     $this->remove($offset);
  400.   }
  401.  
  402.   public function offsetGet($offset) {
  403.     return $this->get($offset);
  404.   }
  405.  
  406.   public function count() {
  407.     return count($this->headers);
  408.   }
  409.  
  410.   public function getIterator() {
  411.     return new ArrayIterator($this->toArray());
  412.   }
  413. }
  414.  
  415. class MalformedHeaderError extends Exception {}
  416.  
  417.  
  418. class Request {
  419.   const SCHEME_RE = '/^https?$/i';
  420.   const HTTP_PORT = 80;
  421.   const HTTPS_PORT = 443;
  422.  
  423.   // объявим публичными чтобы не писать кучу методов типа getFoo, setFoo
  424.   public $connectionTimeout;
  425.   public $timeout;
  426.   public $time;
  427.  
  428.   protected $defaultHeaders = array(
  429.     'Accept' => '*/*',
  430.     'Accept-Encoding' => 'gzip, deflate',
  431.     'User-Agent' => 'Mozilla/5.0'
  432.   );
  433.  
  434.   protected $method;
  435.   protected $url;
  436.   protected $username;
  437.   protected $password;
  438.   protected $headers;
  439.  
  440.   public function __construct($method, $url, $username = null, $password = null) {
  441.     $this->method = $method;
  442.     $this->url = $url;
  443.     $this->username = $username;
  444.     $this->password = $password;
  445.     $this->headers = new Headers($this->defaultHeaders);
  446.   }
  447.  
  448.   public function setHeader($name, $value) {
  449.      $this->headers[$name] = $value;
  450.   }
  451.  
  452.   public function send($data = '') {
  453.     $startTime = microtime(true);
  454.     $parsedUrl = parse_url($this->url);
  455.  
  456.     if ($parsedUrl === false || !isset($parsedUrl['scheme']) || !isset($parsedUrl['host'])) {
  457.       throw new RequestError('Bad URL');
  458.     }
  459.  
  460.     $scheme = $parsedUrl['scheme'];
  461.  
  462.     if (!preg_match(static::SCHEME_RE, $scheme)) {
  463.       throw new RequestError(sprintf('Unsupported scheme: %s', $scheme));
  464.     }
  465.  
  466.     $isHttps = strtolower($scheme) == 'https';
  467.     $host = $parsedUrl['host'];
  468.     $port = isset($parsedUrl['port']) ? $parsedUrl['port'] : ($isHttps ? static::HTTPS_PORT : static::HTTP_PORT);
  469.     $absPath = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/';
  470.  
  471.     if (isset($parsedUrl['query'])) {
  472.       $absPath .= '?' . $parsedUrl['query'];
  473.     }
  474.  
  475.     $transport = $isHttps ? 'tls' : 'tcp';
  476.     $address = sprintf('%s://%s:%d', $transport, $host, $port);
  477.     // die($address);
  478.  
  479.     // http://php.net/manual/ru/context.ssl.php
  480.     $options = array(
  481.       'ssl' => array(
  482.         'verify_peer' => false,
  483.         'verify_peer_name' => false,
  484.         'allow_self_signed' => true,
  485.         // 'local_cert' => '',
  486.         // 'local_pk' => '',
  487.         // 'passphrase' => '',
  488.       )
  489.     );
  490.  
  491.     $context = stream_context_create($options);
  492.    
  493.     $connection = new SocketClient(
  494.       $address,
  495.       $this->connectionTimeout,
  496.       STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
  497.       $context
  498.     );
  499.  
  500.     // $connection->setBlocking(true);
  501.  
  502.     if ($this->timeout) {
  503.       // Таймаут на чтение/запись для сокета
  504.       $connection->setTimeout($this->timeout);
  505.     }
  506.  
  507.     if ($isHttps) {
  508.       // Включаем шифрование трафика
  509.       $connection->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
  510.     }
  511.  
  512.     $headers = $this->headers;
  513.     // $headers = clone $this->headers;
  514.  
  515.     // if (isset($url['user'])) {
  516.     //   $username = $url['user'];
  517.     //   $password = isset($url['pass']) ? $url['pass'] : '';
  518.     //   $headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password);
  519.     // }
  520.  
  521.     if (!is_null($this->username)) {
  522.       $headers['Authorization'] = 'Basic ' .
  523.         base64_encode(sprintf('%s:%s', $this->username, $this->password));
  524.     }
  525.  
  526.     $content_length = strlen($data);
  527.  
  528.     if ($content_length) {
  529.       $headers['Content-Length'] = $content_length;
  530.     }
  531.  
  532.     $isDefaultPort = $isHttps ? $port == static::HTTPS_PORT : $port == static::HTTP_PORT;
  533.     $hostname = $isDefaultPort ? $host : sprintf('%s:%d', $host, $port);
  534.     $request = "{$this->method} $absPath HTTP/1.1\r\nHost: $hostname\r\n";
  535.  
  536.     foreach ($headers as $k => $v) {
  537.       $request .= "$k: $v\r\n";
  538.     }
  539.  
  540.     $request .= "\r\n$data";
  541.     $connection->write($request);
  542.     $response = new Response($connection, $this->method, $this->url);
  543.  
  544.     if ($response->isCloseConnection) {
  545.       // echo('Закрываем соединение.<br>');
  546.       $connection->close();
  547.     }
  548.  
  549.     $endTime = microtime(true);
  550.     $this->time = $endTime - $startTime;
  551.     return $response;
  552.   }
  553. }
  554.  
  555.  
  556. class RequestError extends Exception {}
  557.  
  558.  
  559. class Response {
  560.   const HTTP_VERSION_RE = '/^HTTP\/(\d)\.(\d)$/';
  561.   const STATUS_CODE_RE = '/^[1-5]\d\d$/';
  562.   // кривая регулярка
  563.   const CHUNK_RE = '/^[a-fA-F0-9]+(?=;|\r\n)/';
  564.   const NUM_RE = '/^(0|[1-9]\d*)$/';
  565.   const CHARSET_RE = '/;\s*charset\s*=\s*(?:"([^"]+)"|([^\s";]+))/i';
  566.   const LINE_LENGTH = 4096;
  567.  
  568.   public $method;
  569.   public $url;
  570.   public $httpVersion;
  571.   public $httpVersionMajor;
  572.   public $httpVersionMinor;
  573.   public $statusCode;
  574.   public $reasonPhrase;
  575.   public $isOk;
  576.   public $isRedirect;
  577.   public $headers;
  578.   public $location;
  579.   public $isCloseConnection;
  580.   public $contentType;
  581.   public $mimeType;
  582.   public $mainType;
  583.   public $subType;
  584.   public $charset;
  585.   public $content;
  586.  
  587.   protected $connection;
  588.   protected $redirectCodes = array(301, 302, 303, 307);
  589.  
  590.   public function __construct($connection, $method, $url) {
  591.     $this->connection = $connection;
  592.     $this->method = $method;
  593.     $this->url = $url;
  594.     $this->processResponse();
  595.   }
  596.  
  597.   protected function eof() {
  598.     return $this->connection->eof();
  599.   }
  600.  
  601.   protected function read($size = null) {
  602.     return $size ? $this->connection->read($size) : $this->connection->getContents();
  603.   }
  604.  
  605.   protected function readLine() {
  606.     return $this->connection->readLine(static::LINE_LENGTH);
  607.   }
  608.  
  609.   protected function getHeaders() {
  610.     $header = '';
  611.     while (!$this->eof()) {
  612.       $line = $this->readLine();
  613.       if ($line == '' || $line == "\r\n") {
  614.         break;
  615.       }
  616.       $header .= $line;
  617.     }
  618.     return Headers::parse($header);
  619.   }
  620.  
  621.   protected function processResponse() {
  622.     $statusLine = $this->readLine();
  623.     var_export($statusLine);
  624.     $this->parseStatusLine($statusLine);
  625.     $this->isOk = $this->statusCode == 200;
  626.     $this->isRedirect = in_array($this->statusCode, $this->redirectCodes);
  627.     $this->headers = $this->getHeaders();
  628.     // dump($this->headers);
  629.  
  630.     // Должно ли быть закрыто соединение после чтения из сокета?
  631.     // http://stackoverflow.com/questions/17437950/when-does-an-http-1-0-server-close-the-connection
  632.     $this->isCloseConnection =
  633.       // HTTP Version = 0.9
  634.       !$this->httpVersionMajor ||
  635.       // HTTP Version = 1.0
  636.       (
  637.         $this->httpVersionMajor == 1 && $this->httpVersionMinor == 0 &&
  638.         (
  639.           // Соединение по-умолчанию должно быть закрыто
  640.           !isset($this->headers['connection']) ||
  641.           strtolower($this->headers['connection']) != 'keep-alive'
  642.         )
  643.       ) ||
  644.       (
  645.         // Соединение по-умолчанию должно быть открыто
  646.         isset($this->headers['connection']) &&
  647.         strtolower($this->headers['connection']) == 'close'
  648.       );
  649.  
  650.     if (isset($this->headers['location'])) {
  651.       $this->location = $this->headers['location'];
  652.     }
  653.  
  654.     if (isset($this->headers['content-type'])) {
  655.       $this->contentType = $this->headers['content-type'];
  656.       $ex = explode(';', $this->contentType, 1);
  657.       $this->mimeType = trim($ex[0]);
  658.       $ex = explode('/', $this->mimeType, 2);
  659.       $this->mainType = $ex[0];
  660.      
  661.       if (isset($ex[1])) {
  662.         $this->subType = $ex[1];
  663.       }
  664.  
  665.       // Кодировка
  666.       if (preg_match(static::CHARSET_RE, $this->contentType, $m)) {
  667.         $this->charset = isset($m[2]) ? $m[2] : $this->decodeSlashes($m[1]);
  668.       }
  669.     }
  670.  
  671.     $this->content = $this->getContent();
  672.   }
  673.  
  674.   protected function parseStatusLine($statusLine) {
  675.     $parts = explode(' ', $statusLine, 3);
  676.  
  677.     if (!preg_match(static::HTTP_VERSION_RE, $parts[0], $matches)) {
  678.       throw new ResponseError('Invalid HTTP version');
  679.     }
  680.  
  681.     $this->httpVersion = $parts[0];
  682.     $this->httpVersionMajor = intval($matches[1]);
  683.     $this->httpVersionMinor = intval($matches[2]);
  684.  
  685.     if (!isset($parts[1]) || !preg_match(static::STATUS_CODE_RE, $parts[1])) {
  686.       throw new ResponseError('Invalid status code');
  687.     }
  688.  
  689.     $this->statusCode = intval($parts[1]);
  690.  
  691.     if (isset($parts[2])) {
  692.       $this->reasonPhrase = trim($parts[2]);
  693.     }
  694.   }
  695.  
  696.   protected function decodeSlashes($v) {
  697.     return $v;
  698.   }
  699.  
  700.   protected function getContent() {
  701.     $content = '';
  702.  
  703.     // На запрос, отправленный методом HEAD, сервер не должен возвращать ответ,
  704.     // а также с кодами 1.x.x, 204 и 304
  705.     if ($this->method != 'HEAD' &&
  706.         $this->statusCode >= 200 &&
  707.         $this->statusCode != 204 &&
  708.         $this->statusCode != 304) {
  709.       // Если установлен Transfer-Encoding Content-Length игнорируется.
  710.       // If a Content-Length header field (section 14.13) is present, its decimal
  711.       // value in OCTETs represents both the entity-length and the transfer-length.
  712.       // The Content-Length header field MUST NOT be sent if these two lengths are
  713.       // different (i.e., if a Transfer-Encoding header field is present). If a
  714.       // message is received with both a Transfer-Encoding header field and a
  715.       // Content-Length header field, the latter MUST be ignored.
  716.       if (isset($this->headers['transfer-encoding'])) {
  717.         $encoding = strtolower($this->headers['transfer-encoding']);
  718.  
  719.         if ($encoding != 'chunked') {
  720.           throw new ResponseError(sprintf('Unsupported transfer-encoding: %s',
  721.             $this->headers['transfer-encoding']));
  722.         }
  723.  
  724.         while (true) {
  725.           $chunk = $this->readLine();
  726.  
  727.           if (!preg_match(static::CHUNK_RE, $chunk, $matches)) {
  728.             throw new ResponseError('Invalid chunk');
  729.           }
  730.  
  731.           $size = hexdec($matches[0]);
  732.  
  733.           if (!$size) {
  734.             break;
  735.           }
  736.  
  737.           $content .= $this->read($size);
  738.           $this->readLine();
  739.         }
  740.       } else {
  741.         // unset($this->headers['content-length']);
  742.         if (isset($this->headers['content-length'])) {
  743.           if (!preg_match(static::NUM_RE, $this->headers['content-length'])) {
  744.             throw new Exception('Invalid content-length');
  745.           }
  746.  
  747.           // читаем количество байт указанных в заголовке
  748.           $length = intval($this->headers['content-length']);
  749.  
  750.           if ($length) {
  751.             $content = $this->read($length);
  752.           }
  753.         } else {
  754.           // Не понятно, что делать, если не установлены Content-Length и Transfer-Encoding
  755.           // http://stackoverflow.com/questions/12738519/http-response-headers-valid-with-no-transfer-encoding-and-content-length        
  756.         }
  757.       }
  758.  
  759.       // Если были переданы Transfer-Encoding и Content-Encoding, то мы сначала
  760.       // собираем строку из чанков, а только потом декодируем
  761.       if (isset($this->headers['content-encoding'])) {
  762.         $decoded = $this->decodeContent($content, $this->headers['content-encoding']);
  763.  
  764.         if ($decoded === false) {
  765.           throw new ResponseError(sprintf('Unable to decode content with content-encoding: %s',
  766.             $this->headers['content-encoding']));
  767.         }
  768.  
  769.         $content = $decoded;
  770.       }
  771.     }
  772.  
  773.     return $content;
  774.   }
  775.  
  776.   protected function decodeContent($content, $encoding) {
  777.     // За основу взят этот код:
  778.     // https://github.com/zendframework/zend-http/blob/master/src/Response.php
  779.     $encoding = strtolower($encoding);
  780.  
  781.     if ($encoding == 'identity') {
  782.       return $content;
  783.     }
  784.  
  785.     if ($encoding == 'gzip') {
  786.       // http://php.net/manual/ru/function.gzinflate.php#77336
  787.       return @gzinflate(substr($content, 10, -8));
  788.     }
  789.  
  790.     if ($encoding == 'deflate') {
  791.       // first byte is usually 0x78 but can also be 0x08,
  792.       // 0x18, 0x28, 0x38, 0x48, 0x58, or 0x68.  The first two bytes, when
  793.       // interpreted as an unsigned 16-bit number in big-endian byte order,
  794.       // contain a value that is a multiple of 31.
  795.       $zlibHeader = unpack('n', substr($content, 0, 2));
  796.  
  797.       if ($zlibHeader[1] % 31 == 0) {
  798.         return @gzuncompress($content);
  799.       }
  800.  
  801.       return @gzinflate($content);
  802.     }
  803.  
  804.     return false;
  805.   }
  806. }
  807.  
  808.  
  809. class ResponseError extends Exception {}
  810.  
  811.  
  812. header('Content-Type: text/html; charset=utf-8');
  813. // $url = 'https://httpbin.org/gzip'; // ok
  814. // $url = 'https://httpbin.org/deflate'; // ok
  815. // $url = 'http://www.gbase.de/global/news/0/55322.html'; // fail
  816. $url = 'http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx'; // ok
  817.  
  818. $request = new Request('GET', $url);
  819. $request->setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36');
  820. // $request->setHeader('Connection', 'close');
  821. $response = $request->send();
  822. echo("Request time (sec): {$request->time}<br>");
  823. dump($response);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement