Advertisement
Guest User

Untitled

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