Advertisement
Guest User

Untitled

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