Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- function dump($v) {
- echo('<pre>' . var_export($v, true) . '</pre>');
- }
- error_reporting(E_ALL);
- // header("http/1.0 404 \t\t\tNot Found\t");
- // header("Content-Type:\t \t text/plain;charset=windows-1251 =пизда%00;charset \t\t\t\t= \"utf-8\" foo, bar");
- header('cache-control: no-cache');
- header('Cache-control: no-store'); // cache-control: no-store
- // echo("Тест\n");
- // list(, $h) = unpack('n', "\0\x20");
- // print_r($h);
- // $f = fopen('long_string.txt', 'r');
- // echo(fgets($f, null));
- // fgets($f, null);
- // fgets($f);
- // fread($f, 65536);
- // var_dump(fread($f, 1));
- // echo(fread($f, null));
- // die;
- // preg_match('~;\s*charset\s*=\s*(?:"([^"]+)"|([^\s;]+))~i', "text/html; charset\t= utf-8\t\"q;", $matches);
- // var_dump($matches);
- // echo('charset: ' . (count($matches) ? (isset($matches[2]) ? $matches[2] : $matches[1]) : ''));
- // echo('\\\t\'\0 ');
- preg_match_all('/[\x00-\xff]/', 'тест', $m);
- // var_dump($m);
- $headers = "HTTP/1.1 200\r\n" .
- "Content-Type:" .
- "\r\n\ttext/html;" .
- "\r\n\t charset=utf-8";
- // echo("Headers:\n");
- // echo(preg_replace('/\r\n[ \t]+/', ' ', $headers));
- // Bad chars: \x00-\x08\x0b\x0c\x0e-\x1f\x7f
- // set_time_limit(5);
- // echo(str_repeat('-', 80) . PHP_EOL);
- // list($a, $b) = explode(':', '');
- // die;
- if (!function_exists('http_build_url')) {
- function http_build_url($parts) {
- if (!is_array($parts)) {
- return false;
- }
- return (isset($parts['scheme']) ? $parts['scheme'] . '://' : '') .
- (isset($parts['user']) ?
- $parts['user'] .
- (isset($parts['pass']) ? ':' . $parts['pass'] : '') . '@'
- : '') .
- (isset($parts['host']) ? $parts['host']: '') .
- (isset($parts['port']) ? ':' . $parts['port']: '') .
- (isset($parts['path']) ?
- (substr($parts['path'], 0, 1) != '/' ? '/' : '') . $parts['path']
- : '') .
- (isset($parts['query']) ? '?' . $parts['query']: '') .
- (isset($parts['fragment']) ? '#' . $parts['fragment']: '');
- }
- }
- /*
- echo(http_build_url([
- 'user' => 'username',
- 'host' => 'example.com',
- 'path' => 'path/to/handler',
- 'query' => 'query',
- 'fragment' => 'hash'
- ]) . PHP_EOL);
- echo(http_build_url([
- 'scheme' => 'https',
- 'user' => 'username',
- 'pass' => 'password',
- 'host' => 'example.com',
- 'path' => 'path/to/handler',
- 'query' => 'query',
- 'fragment' => 'hash'
- ]) . PHP_EOL);
- $u = 'google.com:8080/path/to/handler?#';
- echo('Исходный URL: ' . $u . PHP_EOL);
- $p = parse_url($u);
- echo("Результат парсинга: ");
- print_r($p);
- echo('Результат http_build_url(): ' . http_build_url($p) . PHP_EOL);*/
- /* Класс для работы с HTTP-соединением. */
- class HTTPConnection {
- const PORT = 80;
- const SSL_PORT = 443;
- const CONNECTION_TIMEOUT = 10;
- const TIMEOUT = 10;
- protected $hostname;
- protected $port;
- protected $ssl;
- protected $connectionTimeout;
- protected $timeout;
- protected $socket;
- public function __construct($hostname, $port = null, $ssl = false, $connection_timeout = null, $timeout = null) {
- $this->hostname = $hostname;
- $this->ssl = $ssl;
- $this->port = is_null($port) ? ($this->ssl ? static::SSL_PORT : static::PORT) : $port;
- $this->connectionTimeout = is_null($connection_timeout) ? static::CONNECTION_TIMEOUT : $connection_timeout;
- $this->timeout = is_null($timeout) ? static::TIMEOUT : $timeout;
- $this->connect();
- }
- public function getHostname() {
- return $this->hostname;
- }
- public function getSsl() {
- return $this->ssl;
- }
- public function getPort() {
- return $this->port;
- }
- public function getConnectionTimeout() {
- return $this->connectionTimeout;
- }
- public function getTimeout() {
- return $this->timeout;
- }
- public function request($method, $uri, $http_version = '1.1', $headers = array(), $data = '') {
- $request = "$method $uri HTTP/$http_version\r\n";
- foreach ($headers as $k => $v) {
- $request .= "$k: $v\r\n";
- }
- $request .= "\r\n$data";
- $this->write($request);
- }
- public function write($data) {
- $bytes = @fwrite($this->socket, $data);
- if ($bytes === false) {
- $this->checkTimeout();
- throw new SocketError('Could not write to socket');
- }
- return $bytes;
- }
- public function read($size) {
- // Если достигли конца файла, вернет пустую строку. Только в случае ошибки возвращает false
- $data = @fread($this->socket, $size);
- if ($data === false) {
- $this->checkTimeout();
- throw new SocketError('Could not read from socket');
- }
- return $data;
- }
- public function readLine($size = null) {
- if (is_null($size)) {
- // fgets($fp, null) приводит к ошибке
- // Читает строку пока не встретит \n либо конец файла
- $data = @fgets($this->socket);
- } else {
- $data = @fgets($this->socket, $size);
- }
- if ($data === false) {
- $this->checkTimeout();
- throw new SocketError('Could not read from socket');
- }
- return $data;
- }
- public function eof() {
- return @feof($this->socket);
- }
- public function getStreamMetaData() {
- // Вернет false, если ресурс закрыт
- return @stream_get_meta_data($this->socket);
- }
- public function isConnected() {
- return is_resource($this->socket);
- }
- public function disconnect() {
- // <br />
- // <b>Warning</b>: fclose(): 4 is not a valid stream resource in <b>...</b><br />
- @fclose($this->socket);
- }
- public function __destruct() {
- $this->disconnect();
- }
- protected function connect() {
- $hostname = ($this->ssl ? 'ssl' : 'tcp') ? '://' . $this->hostname;
- $this->socket = @fsockopen($hostname, $this->port, $errno, $errstr, $this->connectionTimeout);
- if (!$this->socket) {
- // Функции типа socket_last_error отключены по-умолчанию
- throw new SocketError("Socket error $errno: $errstr");
- }
- // http://forum.sources.ru/index.php?showtopic=88374
- // stream_set_blocking($this->socket, true);
- // timeout на чтение/запись в сокет
- stream_set_timeout($this->socket, $this->timeout);
- }
- protected function checkTimeout() {
- $info = $this->getStreamMetaData();
- if ($info && $info['timed_out']) {
- throw new SocketTimeout('Socket timed out');
- }
- }
- }
- class SocketError extends Exception {}
- class SocketTimeot extends SocketError {}
- class Header {
- protected $name;
- protected $value;
- public function __construct($name, $value) {
- $this->setName($name);
- $this->setValue($value);
- }
- public function setName($name) {
- $this->name = $name;
- }
- public function setValue($value) {
- $this->value = $value;
- }
- public function getName() {
- return $this->name;
- }
- public function getValue() {
- return $this->value;
- }
- public function __toString() {
- return sprintf('%s: %s', $this->name, $this->value);
- }
- }
- /**
- * Класс для работы с http-заголовками.
- *
- * Имена полей регистронезависимы. Значения полей можно получить, обращаясь к
- * объекту как к массиву. К объекту применимы вызовы функций count, isset и
- * unset, а так же возможен по нему обход с помощью цикла foreach.
- */
- class Headers implements ArrayAccess, Countable, IteratorAggregate {
- protected $headers = array();
- public function __construct($headers = array()) {
- $this->update($headers);
- }
- /**
- * Добавляет новый заголовок.
- */
- public function add($name, $value) {
- $this->headers[strtolower($name)] = new Header($name, $value);
- }
- /**
- * Добавляет новый заголовок либо заменяет значение существующего, при этом
- * оригинальное имя сохраняется.
- */
- public function set($name, $value) {
- $lower_name = strtolower($name);
- if (isset($this->headers[$lower_name])) {
- $this->headers[$lower_name]->setValue($value);
- } else {
- $this->headers[$lower_name] = new Header($name, $value);
- }
- // возвращаем установленное значение
- return $value;
- }
- public function update($data) {
- foreach ($data as $name => $value) {
- $this->set($name, $value);
- }
- }
- public function get($name, $default = null) {
- $name = strtolower($name);
- return isset($this->headers[$name]) ? $this->headers[$name]->getValue() : $default;
- }
- /**
- * Возвращает оригинальное имя заголовка.
- */
- public function getOriginalName($name) {
- $name = strtolower($name);
- if (isset($this->headers[$name])) {
- return $this->headers[$name]->getName();
- }
- }
- public function has($name) {
- return isset($this->headers[strtolower($name)]);
- }
- public function remove($name) {
- unset($this->headers[strtolower($name)]);
- }
- public function toArray() {
- $headers = array();
- foreach ($this->headers as $header) {
- $headers[$header->getName()] = $header->getValue();
- }
- return $headers;
- }
- public function __toString() {
- $out = '';
- foreach ($this->headers as $header) {
- $out .= $header->__toString() . "\r\n";
- }
- return $out;
- }
- public function __clone() {
- foreach ($this->headers as &$h) {
- $h = clone $h;
- }
- }
- public static function parse($header_str) {
- // new self будет создавать экземпляр класса, где был объявлен метод
- $headers = new static;
- if (strlen($header_str)) {
- // normalize LWS
- $header_str = preg_replace('/\r\n[\x20\t]+/', ' ', $header_str);
- $offset = 0;
- $length = strlen($header_str);
- while (true) {
- $pos = strpos($header_str, "\r\n", $offset);
- if ($pos === false) {
- throw new MalformedHeaderError('Header should end with CRLF');
- }
- $line = substr($header_str, $offset, $pos - $offset);
- $parts = explode(':', $line, 2);
- if (count($parts) < 2) {
- throw new MalformedHeaderError('Header without colon');
- }
- list($name, $value) = $parts;
- $value = trim($value);
- if (isset($headers[$name])) {
- // по rfc заголовки с одинаковыми либо различающимися лишь регистром
- // именами могут быть объеденены в один. Их значения должны быть
- // добавлены к значениям первого заголовка и разделены запятыми.
- //
- // Пример:
- //
- // Cache-Control: no-cache
- // cache-control: no-store
- //
- // После нормализации:
- //
- // Cache-Control: no-cache, no-store
- $headers[$name] .= ', ' . $value;
- } else {
- $headers[$name] = $value;
- }
- $offset = $pos + 2;
- if ($offset == $length) {
- break;
- }
- }
- }
- return $headers;
- }
- public function offsetSet($offset, $value) {
- return $this->set($offset, $value);
- }
- public function offsetExists($offset) {
- return $this->has($offset);
- }
- public function offsetUnset($offset) {
- $this->remove($offset);
- }
- public function offsetGet($offset) {
- return $this->get($offset);
- }
- public function count() {
- return count($this->headers);
- }
- public function getIterator() {
- return new ArrayIterator($this->toArray());
- }
- }
- class MalformedHeaderError extends Exception {}
- // за основу взят этот код:
- // https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/MediaType.java
- // там неправильная регулярка для токенов
- // http://rfc2.ru/2068.rfc/6#p2.2
- class MediaType {
- const TOKEN_RE = '([!#$%&\'*+-.^`|~\w]+)';
- // quoted-string = ( <"> *(qdtext) <"> )
- // qdtext = <любой TEXT не включающий <">>
- const QUOTED_RE = '"([^"]*)"';
- protected $mediaType;
- protected $type;
- protected $subtype;
- protected $parameters;
- public function __construct($str) {
- $this->mediaType = $str;
- $this->parse();
- }
- public function getMediaType() {
- return $this->mediaType;
- }
- public function getType() {
- return $this->type;
- }
- public function getSubtype() {
- return $this->subtype;
- }
- public function getMimeType() {
- return sprintf('%s/%s', $this->type, $this->subtype);
- }
- public function getParameter($name, $default = null) {
- $name = strtolower($name);
- if (isset($this->parameters[$name])) {
- return $this->parameters[$name];
- }
- return $default;
- }
- public function getParameters() {
- return $this->parameters;
- }
- public function hasParameter($name) {
- return isset($this->parameters[strtolower($name)]);
- }
- public function __toString() {
- return $this->mediaType;
- }
- protected function parse() {
- // media-type = type "/" subtype *( ";" parameter )
- // type = token
- // subtype = token
- $media_type_re = '/^\s*' . static::TOKEN . '\/' . static::TOKEN . '\s*(?=;|$)/';
- // echo($media_type_re . PHP_EOL);
- // parameter = attribute "=" value
- // attribute = token
- // value = token | quoted-string
- $parameter_re = '/^;\s*' . static::TOKEN . '\s*=\s*(?:' . static::TOKEN . '|' . static::QUOTED . ')\s*/';
- // echo($parameter_re . PHP_EOL);
- if (!preg_match($media_type_re, $this->mediaType, $matches)) {
- throw new MediaTypeError('Invalid media type');
- }
- $this->type = strtolower($matches[1]);
- $this->subtype = strtolower($matches[2]);
- $offset = strlen($matches[0]);
- $str = $this->mediaType;
- while ($offset < strlen($str)) {
- $str = substr($str, $offset);
- if (!preg_match($parameter_re, $str, $matches)) {
- // throw new MediaTypeError('Invalid parameter');
- // прекращаем парсинг
- break;
- }
- // print_r($matches);
- $offset = strlen($matches[0]);
- $name = strtolower($matches[1]);
- $value = isset($matches[3]) ? $this->unquote($matches[3]) : $matches[2];
- $this->parameters[$name] = $value;
- }
- }
- protected function unquote($v) {
- // TODO: сделать замену escape-последовательностей
- return $v;
- }
- }
- class MediaTypeError extends Exception {}
- class Request {
- const SCHEME_RE = '/^https?$/i';
- // объявим публичными чтобы не писать кучу методов типа getFoo, setFoo
- public $connectionTimeout;
- public $timeout;
- protected $defaultHeaders = array(
- 'Accept' => '*/*',
- 'Accept-Encoding' => 'gzip, deflate',
- 'User-Agent' => 'Mozilla/5.0'
- );
- protected $method;
- protected $url;
- protected $username;
- protected $password;
- protected $headers;
- public function __construct($method, $url, $username = null, $password = null) {
- $this->method = $method;
- $this->url = $url;
- $this->username = $username;
- $this->password = $password;
- $this->headers = new Headers($this->defaultHeaders);
- }
- public function setHeader($name, $value) {
- $this->headers[$name] = $value;
- }
- public function send($data = '') {
- $url = parse_url($this->url);
- if ($url === false || !isset($url['scheme']) || !isset($url['host'])) {
- throw new RequestError('Bad URL');
- }
- $scheme = $url['scheme'];
- if (!preg_match(static::SCHEME_RE, $scheme)) {
- throw new RequestError(sprintf('Unsupported scheme: %s', $scheme));
- }
- $hostname = $url['host'];
- $port = isset($url['port']) ? $url['port'] : null;
- $class = strtoupper($scheme) . 'Connection';
- $connection = new $class($hostname, $port, $this->connectionTimeout, $this->timeout);
- $endpoint = isset($url['path']) ? $url['path'] : '/';
- if (isset($url['query'])) {
- $endpoint .= '?' . $url['query'];
- }
- $headers = clone $this->headers;
- // if (isset($url['user'])) {
- // $username = $url['user'];
- // $password = isset($url['pass']) ? $url['pass'] : '';
- // $headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password);
- // }
- if (!is_null($this->username)) {
- $headers['Authorization'] = 'Basic ' . base64_encode(sprintf('%s:%s', $this->username, $this->password));
- }
- $content_length = strlen($data);
- if ($content_length) {
- $headers['Content-Length'] = $content_length;
- }
- $connection->request($this->method, $endpoint, $headers, $data);
- return new Response($connection, $this->url);
- }
- }
- class RequestError extends Exception {}
- class Response {
- const STATUS_RE = '/^HTTP\/(\d\.\d) ([1-5]\d\d) (.*)/';
- const CHUNK_RE = '/^[a-fA-F0-9]+(?=;|\r\n)/';
- const NUM_RE = '/^(0|[1-9]\d*)$/';
- const STATUS_LENGTH = 128;
- const HEADER_LENGTH = 4096;
- const READ_SIZE = 4096;
- public $url;
- public $httpVersion;
- public $statusCode;
- public $reasonPhrase;
- public $mimeType = '';
- public $mainType = '';
- public $subType = '';
- public $charset = '';
- protected $connection;
- protected $headers;
- protected $content;
- public function __construct($connection, $url) {
- $this->connection = $connection;
- $this->url = $url;
- $this->processResponse();
- }
- public function getContent() {
- if (!is_null($this->content)) {
- return $this->content;
- }
- // print_r($this->headers);
- $content = '';
- // Если установлен Transfer-Encoding Content-Length игнорируется.
- // If a Content-Length header field (section 14.13) is present, its decimal
- // value in OCTETs represents both the entity-length and the transfer-length.
- // The Content-Length header field MUST NOT be sent if these two lengths are
- // different (i.e., if a Transfer-Encoding header field is present). If a
- // message is received with both a Transfer-Encoding header field and a
- // Content-Length header field, the latter MUST be ignored.
- if (isset($this->headers['transfer-encoding'])) {
- $enc = strtolower($this->headers['transfer-encoding']);
- if ($enc != 'chunked') {
- throw new ResponseError(sprintf('Unsupported transfer-encoding: %s', $enc));
- }
- for (;;) {
- $chunk = $this->connection->readLine();
- if (!preg_match(static::CHUNK_RE, $chunk, $matches)) {
- throw new ResponseError('Invalid chunk');
- }
- $size = hexdec($matches[0]);
- if (!$size) {
- break;
- }
- $content .= $this->connection->read($size);
- // trailer CRLF
- $this->connection->readLine();
- }
- } else {
- if (isset($this->headers['content-length'])) {
- if (!preg_match(static::NUM_RE, $this->headers['content-length'])) {
- throw new Exception('Invalid content-length');
- }
- // читаем количество байт указанных в заголовке
- $length = intval($this->headers['content-length']);
- if ($length) {
- $content = $this->connection->read($length);
- }
- } else {
- // Если заголовок Content-Length не установлен, клиент должен читать из
- // сокета, пока не будет разорвано соединение
- while (!$this->connection->eof()) {
- $content .= $this->connection->read(static::READ_SIZE);
- }
- }
- }
- // Если были переданы Transfer-Encoding и Content-Encoding, то мы сначала
- // собираем собираем строку из чанков, а только потом декодируем
- if (isset($this->headers['content-encoding'])) {
- $enc = strtolower($this->headers['content-encoding']);
- $decoded = $this->decodeContent($content, $enc);
- if ($decoded === false) {
- throw new ResponseError(sprintf('Unable to decode content with content-encoding: %s', $enc));
- }
- $content = $decoded;
- }
- $this->closeConnection();
- return $this->content = $content;
- }
- public function getHeaders() {
- return clone $this->headers;
- }
- public function __destruct() {
- $this->closeConnection();
- }
- protected function processResponse() {
- $line = $this->connection->readLine(static::STATUS_LENGTH);
- if (!preg_match(static::STATUS_RE, $line, $matches)) {
- throw new Exception('Invalid status line');
- }
- $this->httpVersion = $matches[1];
- $this->statusCode = intval($matches[2]);
- $this->reasonPhrase = $matches[3];
- $header = '';
- while (!$this->connection->eof()) {
- $line = $this->connection->readLine(static::HEADER_LENGTH);
- if ($line == "\r\n") {
- break;
- }
- $header .= $line;
- }
- $this->headers = Headers::parse($header);
- if (isset($this->headers['content-type'])) {
- try {
- $mt = new MediaType($this->headers['content-type']);
- $this->mimeType = $mt->getFullType();
- $this->mainType = $mt->getType();
- $this->subType = $mt->getSubType();
- $this->charset = $mt->getParameter('charset', $this->charset);
- } catch (Exception $e) {}
- }
- }
- protected function decodeContent($content, $encoding) {
- // $encoding = strtolower($encoding);
- if ($encoding == 'identity') {
- return $content;
- }
- if ($encoding == 'gzip') {
- // http://php.net/manual/ru/function.gzinflate.php#77336
- // Про флаги тут:
- // http://www.forensicswiki.org/wiki/Gzip
- if (substr($content, 0, 3) == "\x1f\x8b\x08") {
- $offset = 10;
- $flags = ord(substr($content, 3, 1));
- if ($flags) {
- if ($flags & 0x04) {
- list($extra_length) = unpack('v',substr($content, $offset, 2));
- $offset += 2 + $extra_length;
- }
- if ($flags & 0x08) {
- $offset = strpos($content, "\0", $offset) + 1;
- }
- if ($flags & 0x10) {
- $offset = strpos($content, "\0", $offset) + 1;
- }
- if ($flags & 0x02) {
- $offset += 2;
- }
- }
- return @gzinflate(substr($content, $offset, -8));
- }
- } else if ($encoding == 'deflate') {
- // https://github.com/zendframework/zend-http/blob/master/src/Response.php
- // first byte is usually 0x78 but can also be 0x08,
- // 0x18, 0x28, 0x38, 0x48, 0x58, or 0x68. The first two bytes, when
- // interpreted as an unsigned 16-bit number in big-endian byte order,
- // contain a value that is a multiple of 31.
- $zlib_header = unpack('n', substr($content, 0, 2));
- // print_r($zlib_header);
- if ($zlib_header[1] % 31 == 0) {
- return @gzuncompress($content);
- }
- return @gzinflate($content);
- }
- return false;
- }
- protected function closeConnection() {
- $this->connection->close();
- }
- }
- class ResponseError extends Exception {}
- // https://httpbin.org/gzip
- // https://httpbin.org/deflate
- // http://www.gbase.de/global/news/0/55322.html
- // https://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx
- $request = new Request('GET', 'http://xseeds.net');
- $response = $request->send();
- // print_r($response->getHeaders()->toArray());
- $headers = $response->getHeaders();
- // header('Content-Type: ' . $headers['content-type']);
- echo($response->getContent());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement