Advertisement
Guest User

Untitled

a guest
Sep 26th, 2016
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 24.23 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. echo(http_build_url([
  77.   'user' => 'username',
  78.   'host' => 'example.com',
  79.   'path' => 'path/to/handler',
  80.   'query' => 'query',
  81.   'fragment' => 'hash'
  82. ]) . PHP_EOL);
  83.  
  84. echo(http_build_url([
  85.   'scheme' => 'https',
  86.   'user' => 'username',
  87.   'pass' => 'password',
  88.   'host' => 'example.com',
  89.   'path' => 'path/to/handler',
  90.   'query' => 'query',
  91.   'fragment' => 'hash'
  92. ]) . PHP_EOL);
  93.  
  94. $u = 'google.com:8080/path/to/handler?#';
  95. echo('Исходный URL: ' . $u . PHP_EOL);
  96. $p = parse_url($u);
  97. echo("Результат парсинга: ");
  98. print_r($p);
  99. echo('Результат http_build_url(): ' . http_build_url($p) . PHP_EOL);*/
  100.  
  101. /* Класс для работы с HTTP-соединением. */
  102. class HTTPConnection {
  103.   const PORT = 80;
  104.   const SSL_PORT = 443;
  105.   const CONNECTION_TIMEOUT = 10;
  106.   const TIMEOUT = 10;
  107.  
  108.   protected $hostname;
  109.   protected $port;
  110.   protected $ssl;
  111.   protected $connectionTimeout;
  112.   protected $timeout;
  113.   protected $socket;
  114.  
  115.   public function __construct($hostname, $port = null, $ssl = false, $connection_timeout = null, $timeout = null) {
  116.     $this->hostname = $hostname;
  117.     $this->ssl = $ssl;
  118.     $this->port = is_null($port) ? ($this->ssl ? static::SSL_PORT : static::PORT) : $port;
  119.     $this->connectionTimeout = is_null($connection_timeout) ? static::CONNECTION_TIMEOUT : $connection_timeout;
  120.     $this->timeout = is_null($timeout) ? static::TIMEOUT : $timeout;
  121.     $this->connect();
  122.   }
  123.  
  124.   public function getHostname() {
  125.     return $this->hostname;
  126.   }
  127.  
  128.   public function getSsl() {
  129.     return $this->ssl;
  130.   }
  131.  
  132.   public function getPort() {
  133.     return $this->port;
  134.   }
  135.  
  136.   public function getConnectionTimeout() {
  137.     return $this->connectionTimeout;
  138.   }
  139.  
  140.   public function getTimeout() {
  141.     return $this->timeout;
  142.   }
  143.  
  144.   public function request($method, $uri, $http_version = '1.1', $headers = array(), $data = '') {
  145.     $request = "$method $uri HTTP/$http_version\r\n";
  146.    
  147.     foreach ($headers as $k => $v) {
  148.       $request .= "$k: $v\r\n";
  149.     }
  150.  
  151.     $request .= "\r\n$data";
  152.     $this->write($request);
  153.   }
  154.  
  155.   public function write($data) {
  156.     $bytes = @fwrite($this->socket, $data);
  157.  
  158.     if ($bytes === false) {
  159.       $this->checkTimeout();
  160.       throw new SocketError('Could not write to socket');
  161.     }
  162.  
  163.     return $bytes;
  164.   }
  165.  
  166.   public function read($size) {
  167.     // Если достигли конца файла, вернет пустую строку. Только в случае ошибки возвращает false
  168.     $data = @fread($this->socket, $size);
  169.  
  170.     if ($data === false) {
  171.       $this->checkTimeout();
  172.       throw new SocketError('Could not read from socket');
  173.     }
  174.  
  175.     return $data;
  176.   }
  177.  
  178.   public function readLine($size = null) {
  179.     if (is_null($size)) {
  180.       // fgets($fp, null) приводит к ошибке
  181.       // Читает строку пока не встретит \n либо конец файла
  182.       $data = @fgets($this->socket);
  183.     } else {
  184.       $data = @fgets($this->socket, $size);
  185.     }
  186.  
  187.     if ($data === false) {
  188.       $this->checkTimeout();
  189.       throw new SocketError('Could not read from socket');
  190.     }
  191.  
  192.     return $data;
  193.   }
  194.  
  195.   public function eof() {
  196.     return @feof($this->socket);
  197.   }
  198.  
  199.   public function getStreamMetaData() {
  200.     // Вернет false, если ресурс закрыт
  201.     return @stream_get_meta_data($this->socket);
  202.   }
  203.  
  204.   public function isConnected() {
  205.     return is_resource($this->socket);
  206.   }
  207.  
  208.   public function disconnect() {
  209.     // <br />
  210.     // <b>Warning</b>:  fclose(): 4 is not a valid stream resource in <b>...</b><br />
  211.     @fclose($this->socket);
  212.   }
  213.  
  214.   public function __destruct() {
  215.     $this->disconnect();
  216.   }
  217.  
  218.   protected function connect() {
  219.     $hostname = ($this->ssl ? 'ssl' : 'tcp') ? '://' . $this->hostname;  
  220.     $this->socket = @fsockopen($hostname, $this->port, $errno, $errstr, $this->connectionTimeout);
  221.  
  222.     if (!$this->socket) {
  223.       // Функции типа socket_last_error отключены по-умолчанию
  224.       throw new SocketError("Socket error $errno: $errstr");    
  225.     }
  226.  
  227.     // http://forum.sources.ru/index.php?showtopic=88374
  228.     // stream_set_blocking($this->socket, true);
  229.     // timeout на чтение/запись в сокет
  230.     stream_set_timeout($this->socket, $this->timeout);
  231.   }
  232.  
  233.   protected function checkTimeout() {
  234.     $info = $this->getStreamMetaData();
  235.  
  236.     if ($info && $info['timed_out']) {
  237.       throw new SocketTimeout('Socket timed out');
  238.     }
  239.   }
  240. }
  241.  
  242. class SocketError extends Exception {}
  243.  
  244.  
  245. class SocketTimeot extends SocketError {}
  246.  
  247.  
  248. class Header {
  249.   protected $name;
  250.   protected $value;
  251.  
  252.   public function __construct($name, $value) {
  253.     $this->setName($name);
  254.     $this->setValue($value);
  255.   }
  256.  
  257.   public function setName($name) {
  258.     $this->name = $name;
  259.   }
  260.  
  261.   public function setValue($value) {
  262.     $this->value = $value;
  263.   }
  264.  
  265.   public function getName() {
  266.     return $this->name;
  267.   }
  268.  
  269.   public function getValue() {
  270.     return $this->value;
  271.   }
  272.  
  273.   public function __toString() {
  274.     return sprintf('%s: %s', $this->name,  $this->value);
  275.   }
  276. }
  277.  
  278.  
  279. /**
  280.   * Класс для работы с http-заголовками.
  281.   *
  282.   * Имена полей регистронезависимы. Значения полей можно получить, обращаясь к
  283.   * объекту как к массиву. К объекту применимы вызовы функций count, isset и
  284.   * unset, а так же возможен по нему обход с помощью цикла foreach.
  285.   */
  286. class Headers implements ArrayAccess, Countable, IteratorAggregate {
  287.   protected $headers = array();
  288.  
  289.   public function __construct($headers = array()) {
  290.     $this->update($headers);
  291.   }
  292.  
  293.   /**
  294.     * Добавляет новый заголовок.
  295.     */
  296.   public function add($name, $value) {
  297.     $this->headers[strtolower($name)] = new Header($name, $value);
  298.   }
  299.  
  300.   /**
  301.     * Добавляет новый заголовок либо заменяет значение существующего, при этом
  302.     * оригинальное имя сохраняется.
  303.     */
  304.   public function set($name, $value) {
  305.     $lower_name = strtolower($name);
  306.  
  307.     if (isset($this->headers[$lower_name])) {
  308.       $this->headers[$lower_name]->setValue($value);
  309.     } else {
  310.       $this->headers[$lower_name] = new Header($name, $value);
  311.     }
  312.  
  313.     // возвращаем установленное значение
  314.     return $value;
  315.   }
  316.  
  317.   public function update($data) {
  318.     foreach ($data as $name => $value) {
  319.       $this->set($name, $value);
  320.     }
  321.   }
  322.  
  323.   public function get($name, $default = null) {
  324.     $name = strtolower($name);
  325.     return isset($this->headers[$name]) ? $this->headers[$name]->getValue() : $default;
  326.   }
  327.  
  328.   /**
  329.     * Возвращает оригинальное имя заголовка.
  330.     */
  331.   public function getOriginalName($name) {
  332.     $name = strtolower($name);
  333.  
  334.     if (isset($this->headers[$name])) {
  335.       return $this->headers[$name]->getName();
  336.     }
  337.   }
  338.  
  339.   public function has($name) {
  340.     return isset($this->headers[strtolower($name)]);
  341.   }
  342.  
  343.   public function remove($name) {
  344.     unset($this->headers[strtolower($name)]);
  345.   }
  346.  
  347.   public function toArray() {
  348.     $headers = array();
  349.  
  350.     foreach ($this->headers as $header) {
  351.       $headers[$header->getName()] = $header->getValue();
  352.     }
  353.  
  354.     return $headers;
  355.   }
  356.  
  357.   public function __toString() {
  358.     $out = '';
  359.    
  360.     foreach ($this->headers as $header) {
  361.       $out .= $header->__toString() . "\r\n";
  362.     }
  363.  
  364.     return $out;
  365.   }
  366.  
  367.   public function __clone() {
  368.     foreach ($this->headers as &$h) {
  369.       $h = clone $h;
  370.     }
  371.   }
  372.  
  373.   public static function parse($header_str) {
  374.     // new self будет создавать экземпляр класса, где был объявлен метод
  375.     $headers = new static;
  376.  
  377.     if (strlen($header_str)) {
  378.       // normalize LWS
  379.       $header_str = preg_replace('/\r\n[\x20\t]+/', ' ', $header_str);
  380.       $offset = 0;
  381.       $length = strlen($header_str);
  382.  
  383.       while (true) {
  384.         $pos = strpos($header_str, "\r\n", $offset);
  385.  
  386.         if ($pos === false) {
  387.           throw new MalformedHeaderError('Header should end with CRLF');
  388.         }
  389.  
  390.         $line = substr($header_str, $offset, $pos - $offset);
  391.         $parts = explode(':', $line, 2);
  392.  
  393.         if (count($parts) < 2) {
  394.           throw new MalformedHeaderError('Header without colon');
  395.         }
  396.  
  397.         list($name, $value) = $parts;
  398.         $value = trim($value);
  399.        
  400.         if (isset($headers[$name])) {
  401.           // по rfc заголовки с одинаковыми либо различающимися лишь регистром
  402.           // именами могут быть объеденены в один. Их значения должны быть
  403.           // добавлены к значениям первого заголовка и разделены запятыми.
  404.           //
  405.           // Пример:
  406.           //
  407.           // Cache-Control: no-cache
  408.           // cache-control: no-store
  409.           //
  410.           // После нормализации:
  411.           //
  412.           // Cache-Control: no-cache, no-store
  413.           $headers[$name] .= ', ' . $value;
  414.         } else {
  415.           $headers[$name] = $value;
  416.         }
  417.  
  418.         $offset = $pos + 2;
  419.  
  420.         if ($offset == $length) {
  421.           break;
  422.         }
  423.       }
  424.     }
  425.  
  426.     return $headers;
  427.   }
  428.  
  429.   public function offsetSet($offset, $value) {
  430.     return $this->set($offset, $value);
  431.   }
  432.  
  433.   public function offsetExists($offset) {
  434.     return $this->has($offset);
  435.   }
  436.  
  437.   public function offsetUnset($offset) {
  438.     $this->remove($offset);
  439.   }
  440.  
  441.   public function offsetGet($offset) {
  442.     return $this->get($offset);
  443.   }
  444.  
  445.   public function count() {
  446.     return count($this->headers);
  447.   }
  448.  
  449.   public function getIterator() {
  450.     return new ArrayIterator($this->toArray());
  451.   }
  452. }
  453.  
  454. class MalformedHeaderError extends Exception {}
  455.  
  456.  
  457. // за основу взят этот код:
  458. // https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/MediaType.java
  459. // там неправильная регулярка для токенов
  460. // http://rfc2.ru/2068.rfc/6#p2.2
  461. class MediaType {
  462.   const TOKEN_RE = '([!#$%&\'*+-.^`|~\w]+)';
  463.   // quoted-string  = ( <"> *(qdtext) <"> )
  464.   // qdtext         = <любой TEXT не включающий <">>
  465.   const QUOTED_RE = '"([^"]*)"';
  466.  
  467.   protected $mediaType;
  468.   protected $type;
  469.   protected $subtype;
  470.   protected $parameters;
  471.  
  472.   public function __construct($str) {
  473.     $this->mediaType = $str;
  474.     $this->parse();
  475.   }
  476.  
  477.   public function getMediaType() {
  478.     return $this->mediaType;
  479.   }
  480.  
  481.   public function getType() {
  482.     return $this->type;
  483.   }
  484.  
  485.   public function getSubtype() {
  486.     return $this->subtype;
  487.   }
  488.  
  489.   public function getMimeType() {
  490.     return sprintf('%s/%s', $this->type, $this->subtype);
  491.   }
  492.  
  493.   public function getParameter($name, $default = null) {
  494.     $name = strtolower($name);
  495.    
  496.     if (isset($this->parameters[$name])) {
  497.       return $this->parameters[$name];
  498.     }
  499.  
  500.     return $default;
  501.   }
  502.  
  503.   public function getParameters() {
  504.     return $this->parameters;
  505.   }
  506.  
  507.   public function hasParameter($name) {
  508.     return isset($this->parameters[strtolower($name)]);
  509.   }
  510.  
  511.   public function __toString() {
  512.     return $this->mediaType;
  513.   }
  514.  
  515.   protected function parse() {
  516.     // media-type     = type "/" subtype *( ";" parameter )
  517.     // type           = token
  518.     // subtype        = token
  519.     $media_type_re = '/^\s*' . static::TOKEN . '\/' . static::TOKEN . '\s*(?=;|$)/';
  520.     // echo($media_type_re . PHP_EOL);
  521.     // parameter      = attribute "=" value
  522.     // attribute      = token
  523.     // value          = token | quoted-string
  524.     $parameter_re = '/^;\s*' . static::TOKEN . '\s*=\s*(?:' . static::TOKEN . '|' . static::QUOTED . ')\s*/';
  525.     // echo($parameter_re . PHP_EOL);
  526.  
  527.     if (!preg_match($media_type_re, $this->mediaType, $matches)) {
  528.       throw new MediaTypeError('Invalid media type');
  529.     }
  530.  
  531.     $this->type = strtolower($matches[1]);
  532.     $this->subtype = strtolower($matches[2]);
  533.     $offset = strlen($matches[0]);
  534.     $str = $this->mediaType;
  535.  
  536.     while ($offset < strlen($str)) {
  537.       $str = substr($str, $offset);
  538.  
  539.       if (!preg_match($parameter_re, $str, $matches)) {
  540.         // throw new MediaTypeError('Invalid parameter');
  541.         // прекращаем парсинг
  542.         break;
  543.       }
  544.  
  545.       // print_r($matches);
  546.       $offset = strlen($matches[0]);
  547.       $name = strtolower($matches[1]);
  548.       $value = isset($matches[3]) ? $this->unquote($matches[3]) : $matches[2];
  549.       $this->parameters[$name] = $value;
  550.     }
  551.   }
  552.  
  553.   protected function unquote($v) {
  554.     // TODO: сделать замену escape-последовательностей
  555.     return $v;
  556.   }
  557. }
  558.  
  559.  
  560. class MediaTypeError extends Exception {}
  561.  
  562.  
  563. class Request {
  564.   const SCHEME_RE = '/^https?$/i';
  565.  
  566.   // объявим публичными чтобы не писать кучу методов типа getFoo, setFoo
  567.   public $connectionTimeout;
  568.   public $timeout;
  569.  
  570.   protected $defaultHeaders = array(
  571.     'Accept' => '*/*',
  572.     'Accept-Encoding' => 'gzip, deflate',
  573.     'User-Agent' => 'Mozilla/5.0'
  574.   );
  575.  
  576.   protected $method;
  577.   protected $url;
  578.   protected $username;
  579.   protected $password;
  580.   protected $headers;
  581.  
  582.   public function __construct($method, $url, $username = null, $password = null) {
  583.     $this->method = $method;
  584.     $this->url = $url;
  585.     $this->username = $username;
  586.     $this->password = $password;
  587.     $this->headers = new Headers($this->defaultHeaders);
  588.   }
  589.  
  590.   public function setHeader($name, $value) {
  591.      $this->headers[$name] = $value;
  592.   }
  593.  
  594.   public function send($data = '') {
  595.     $url = parse_url($this->url);
  596.  
  597.     if ($url === false || !isset($url['scheme']) || !isset($url['host'])) {
  598.       throw new RequestError('Bad URL');
  599.     }
  600.  
  601.     $scheme = $url['scheme'];
  602.  
  603.     if (!preg_match(static::SCHEME_RE, $scheme)) {
  604.       throw new RequestError(sprintf('Unsupported scheme: %s', $scheme));
  605.     }
  606.  
  607.     $hostname = $url['host'];
  608.     $port = isset($url['port']) ? $url['port'] : null;
  609.     $class = strtoupper($scheme) . 'Connection';
  610.     $connection = new $class($hostname, $port, $this->connectionTimeout, $this->timeout);
  611.     $endpoint = isset($url['path']) ? $url['path'] : '/';
  612.  
  613.     if (isset($url['query'])) {
  614.       $endpoint .= '?' . $url['query'];
  615.     }
  616.  
  617.     $headers = clone $this->headers;
  618.  
  619.     // if (isset($url['user'])) {
  620.     //   $username = $url['user'];
  621.     //   $password = isset($url['pass']) ? $url['pass'] : '';
  622.     //   $headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password);
  623.     // }
  624.  
  625.     if (!is_null($this->username)) {
  626.       $headers['Authorization'] = 'Basic ' . base64_encode(sprintf('%s:%s', $this->username, $this->password));
  627.     }
  628.  
  629.     $content_length = strlen($data);
  630.  
  631.     if ($content_length) {
  632.       $headers['Content-Length'] = $content_length;
  633.     }
  634.  
  635.     $connection->request($this->method, $endpoint, $headers, $data);
  636.     return new Response($connection, $this->url);
  637.   }
  638. }
  639.  
  640.  
  641. class RequestError extends Exception {}
  642.  
  643.  
  644. class Response {
  645.   const STATUS_RE = '/^HTTP\/(\d\.\d) ([1-5]\d\d) (.*)/';
  646.   const CHUNK_RE = '/^[a-fA-F0-9]+(?=;|\r\n)/';
  647.   const NUM_RE = '/^(0|[1-9]\d*)$/';
  648.   const STATUS_LENGTH = 128;
  649.   const HEADER_LENGTH = 4096;
  650.   const READ_SIZE = 4096;
  651.  
  652.   public $url;
  653.   public $httpVersion;
  654.   public $statusCode;
  655.   public $reasonPhrase;
  656.   public $mimeType = '';
  657.   public $mainType = '';
  658.   public $subType = '';
  659.   public $charset = '';
  660.  
  661.   protected $connection;
  662.   protected $headers;
  663.   protected $content;
  664.  
  665.   public function __construct($connection, $url) {
  666.     $this->connection = $connection;
  667.     $this->url = $url;
  668.     $this->processResponse();
  669.   }
  670.  
  671.   public function getContent() {
  672.     if (!is_null($this->content)) {
  673.       return $this->content;
  674.     }
  675.  
  676.     // print_r($this->headers);
  677.     $content = '';
  678.     // Если установлен Transfer-Encoding Content-Length игнорируется.
  679.     // If a Content-Length header field (section 14.13) is present, its decimal
  680.     // value in OCTETs represents both the entity-length and the transfer-length.
  681.     // The Content-Length header field MUST NOT be sent if these two lengths are
  682.     // different (i.e., if a Transfer-Encoding header field is present). If a
  683.     // message is received with both a Transfer-Encoding header field and a
  684.     // Content-Length header field, the latter MUST be ignored.
  685.     if (isset($this->headers['transfer-encoding'])) {
  686.       $enc = strtolower($this->headers['transfer-encoding']);
  687.  
  688.       if ($enc != 'chunked') {
  689.         throw new ResponseError(sprintf('Unsupported transfer-encoding: %s', $enc));
  690.       }
  691.  
  692.       for (;;) {
  693.         $chunk = $this->connection->readLine();
  694.  
  695.         if (!preg_match(static::CHUNK_RE, $chunk, $matches)) {
  696.           throw new ResponseError('Invalid chunk');
  697.         }
  698.  
  699.         $size = hexdec($matches[0]);
  700.  
  701.         if (!$size) {
  702.           break;
  703.         }
  704.  
  705.         $content .= $this->connection->read($size);
  706.         // trailer CRLF
  707.         $this->connection->readLine();
  708.       }
  709.     } else {
  710.       if (isset($this->headers['content-length'])) {
  711.         if (!preg_match(static::NUM_RE, $this->headers['content-length'])) {
  712.           throw new Exception('Invalid content-length');
  713.         }
  714.  
  715.         // читаем количество байт указанных в заголовке
  716.         $length = intval($this->headers['content-length']);
  717.  
  718.         if ($length) {
  719.           $content = $this->connection->read($length);
  720.         }
  721.       } else {
  722.         // Если заголовок Content-Length не установлен, клиент должен читать из
  723.         // сокета, пока не будет разорвано соединение
  724.         while (!$this->connection->eof()) {
  725.           $content .= $this->connection->read(static::READ_SIZE);
  726.         }
  727.       }
  728.     }
  729.  
  730.     // Если были переданы Transfer-Encoding и Content-Encoding, то мы сначала
  731.     // собираем собираем строку из чанков, а только потом декодируем
  732.     if (isset($this->headers['content-encoding'])) {
  733.       $enc = strtolower($this->headers['content-encoding']);
  734.       $decoded = $this->decodeContent($content, $enc);
  735.  
  736.       if ($decoded === false) {
  737.         throw new ResponseError(sprintf('Unable to decode content with content-encoding: %s', $enc));
  738.       }
  739.  
  740.       $content = $decoded;
  741.     }
  742.  
  743.     $this->closeConnection();
  744.     return $this->content = $content;
  745.   }
  746.  
  747.   public function getHeaders() {
  748.     return clone $this->headers;
  749.   }
  750.  
  751.   public function __destruct() {
  752.     $this->closeConnection();
  753.   }
  754.  
  755.   protected function processResponse() {
  756.     $line = $this->connection->readLine(static::STATUS_LENGTH);
  757.  
  758.     if (!preg_match(static::STATUS_RE, $line, $matches)) {
  759.       throw new Exception('Invalid status line');
  760.     }
  761.  
  762.     $this->httpVersion = $matches[1];
  763.     $this->statusCode = intval($matches[2]);
  764.     $this->reasonPhrase = $matches[3];
  765.     $header = '';
  766.  
  767.     while (!$this->connection->eof()) {
  768.       $line = $this->connection->readLine(static::HEADER_LENGTH);
  769.  
  770.       if ($line == "\r\n") {
  771.         break;
  772.       }
  773.  
  774.       $header .= $line;
  775.     }
  776.  
  777.     $this->headers = Headers::parse($header);
  778.  
  779.     if (isset($this->headers['content-type'])) {
  780.       try {
  781.         $mt = new MediaType($this->headers['content-type']);
  782.         $this->mimeType = $mt->getFullType();
  783.         $this->mainType = $mt->getType();
  784.         $this->subType = $mt->getSubType();
  785.         $this->charset = $mt->getParameter('charset', $this->charset);
  786.       } catch (Exception $e) {}
  787.     }
  788.   }
  789.  
  790.   protected function decodeContent($content, $encoding) {
  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.       // Про флаги тут:
  800.       // http://www.forensicswiki.org/wiki/Gzip
  801.  
  802.       if (substr($content, 0, 3) == "\x1f\x8b\x08") {
  803.         $offset = 10;
  804.         $flags = ord(substr($content, 3, 1));
  805.  
  806.         if ($flags) {
  807.           if ($flags & 0x04) {
  808.             list($extra_length) = unpack('v',substr($content, $offset, 2));
  809.             $offset += 2 + $extra_length;
  810.           }
  811.          
  812.           if ($flags & 0x08) {
  813.             $offset = strpos($content, "\0", $offset) + 1;
  814.           }
  815.  
  816.           if ($flags & 0x10) {
  817.             $offset = strpos($content, "\0", $offset) + 1;
  818.           }
  819.  
  820.           if ($flags & 0x02) {
  821.             $offset += 2;
  822.           }
  823.         }
  824.  
  825.         return @gzinflate(substr($content, $offset, -8));
  826.       }
  827.     } else if ($encoding == 'deflate') {
  828.       // https://github.com/zendframework/zend-http/blob/master/src/Response.php
  829.       // first byte is usually 0x78 but can also be 0x08,
  830.       // 0x18, 0x28, 0x38, 0x48, 0x58, or 0x68.  The first two bytes, when
  831.       // interpreted as an unsigned 16-bit number in big-endian byte order,
  832.       // contain a value that is a multiple of 31.
  833.       $zlib_header = unpack('n', substr($content, 0, 2));
  834.       // print_r($zlib_header);
  835.  
  836.       if ($zlib_header[1] % 31 == 0) {
  837.         return @gzuncompress($content);
  838.       }
  839.  
  840.       return @gzinflate($content);
  841.     }
  842.  
  843.     return false;
  844.   }
  845.  
  846.   protected function closeConnection() {
  847.     $this->connection->close();
  848.   }
  849. }
  850.  
  851.  
  852. class ResponseError extends Exception {}
  853.  
  854. // https://httpbin.org/gzip
  855. // https://httpbin.org/deflate
  856. // http://www.gbase.de/global/news/0/55322.html
  857. // https://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx
  858.  
  859. $request = new Request('GET', 'http://xseeds.net');
  860. $response = $request->send();
  861. // print_r($response->getHeaders()->toArray());
  862. $headers = $response->getHeaders();
  863. // header('Content-Type: ' . $headers['content-type']);
  864. echo($response->getContent());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement