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']: '');
- }
- }
- class SocketClient {
- protected $resource;
- public function __construct($remoteSocket, $connectionTimeout = null, $flags = null, $context = null) {
- if (is_null($connectionTimeout)) {
- $connectionTimeout = ini_get('default_socket_timeout');
- }
- if (is_null($flags)) {
- $flags = STREAM_CLIENT_CONNECT;
- }
- // http://php.net/manual/ru/function.set-error-handler.php
- set_error_handler(array($this, 'errorHandler'));
- if (is_null($context)) {
- $this->resource = stream_socket_client($remoteSocket, $errNo, $errStr, $connectionTimeout, $flags);
- } else {
- $this->resource = stream_socket_client($remoteSocket, $errNo, $errStr, $connectionTimeout, $flags, $context);
- }
- restore_error_handler();
- // никогда не выполнится?
- if ($this->resource === false) {
- // $errStr = iconv('windows-1251', 'utf-8', $errStr);
- throw new SocketError(sprintf('Connection to %s failed: %s (%d)', $remoteSocket, $errStr, $errNo));
- }
- }
- public function write($data) {
- $bytes = @fwrite($this->resource, $data);
- if ($bytes === false) {
- $this->checkTimeout();
- throw new SocketError('Could not write to socket');
- }
- return $bytes;
- }
- public function read($size) {
- // Если достигли конца файла, вернет пустую строку. Только в случае ошибки возвращает false
- $data = @fread($this->resource, $size);
- if ($data === false) {
- // $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->resource);
- } else {
- $data = @fgets($this->resource, $size);
- }
- if ($data === false) {
- $this->checkTimeout();
- throw new SocketError('Could not read from socket');
- }
- return $data;
- }
- public function eof() {
- return $this->isResource() ? feof($this->resource) : true;
- }
- public function setTimeout($seconds, $microseconds = 0) {
- return stream_set_timeout($this->resource, $seconds, $microseconds);
- }
- public function setBlocking($mode) {
- return stream_set_blocking($this->resource, $mode);
- }
- public function enableCrypto($enable, $cryptoType = null, $sessionStream = null) {
- // http://php.net/manual/ru/function.stream-socket-enable-crypto.php
- if (!is_null($sessionStream)) {
- return @stream_socket_enable_crypto($this->resource, $enable, $cryptoType, $sessionStream);
- }
- if (!is_null($cryptoType)) {
- return @stream_socket_enable_crypto($this->resource, $enable, $cryptoType);
- }
- return @stream_socket_enable_crypto($this->resource, $enable);
- }
- public function getMetaData($key = null) {
- if ($this->isResource()) {
- $meta = stream_get_meta_data($this->resource);
- if (is_null($key)) {
- return $meta;
- }
- if (isset($meta[$key])) {
- return $meta[$key];
- }
- }
- // return null;
- }
- public function isResource() {
- return is_resource($this->resource);
- }
- public function close() {
- if ($this->isResource()) {
- fclose($this->resource);
- }
- }
- public function __destruct() {
- $this->close();
- }
- protected function errorHandler($errNo, $errStr, $errFile, $errLine) {
- throw new SocketError(sprintf('Socket error %d: %s', $errNo, $errStr));
- }
- protected function checkTimeout() {
- if ($this->getMetaData('timed_out')) {
- throw new SocketTimeout('Socket timed out');
- }
- }
- }
- class SocketError extends Exception {}
- class SocketTimeout 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 ($offset < $length) {
- $pos = strpos($header_str, "\r\n", $offset);
- if ($pos === false) {
- throw new MalformedHeaderError('Header should end with CRLF (\x0d\x0a)');
- }
- $line = substr($header_str, $offset, $pos - $offset);
- $offset = $pos + 2;
- $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;
- }
- // 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 {}
- class Request {
- const SCHEME_RE = '/^https?$/i';
- const HTTP_PORT = 80;
- const HTTPS_PORT = 443;
- // объявим публичными чтобы не писать кучу методов типа 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'];
- $isHttps = strtolower($scheme) == 'https';
- $port = isset($url['port']) ? $url['port'] : ($isHttps ? static::HTTPS_PORT : static::HTTP_PORT);
- $address = sprintf('%s://%s:%d', $isHttps ? 'tls' : 'tcp', $hostname, $port);
- // http://php.net/manual/ru/context.ssl.php
- $options = array(
- 'ssl' => array(
- 'verify_peer' => false,
- 'verify_peer_name' => false,
- 'allow_self_signed' => true,
- // 'local_cert' => '',
- // 'local_pk' => '',
- // 'passphrase' => '',
- )
- );
- $context = stream_context_create($options);
- $socket = new SocketClient($address, $this->connectionTimeout, null, $context);
- if ($this->timeout) {
- // Таймаут на чтение/запись для сокета
- $socket->setTimeout($this->timeout);
- }
- if ($isHttps) {
- // Включаем шифрование трафика
- $socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
- }
- $uri = isset($url['path']) ? $url['path'] : '/';
- if (isset($url['query'])) {
- $uri .= '?' . $url['query'];
- }
- $headers = $this->headers;
- // $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;
- }
- $isDefaultPort = $isHttps ? $port == static::HTTPS_PORT : $port == static::HTTP_PORT;
- $host = $isDefaultPort ? $hostname : sprintf('%s:%d', $hostname, $port);
- $request = "{$this->method} $uri HTTP/1.1\r\nHost: $host\r\n";
- foreach ($headers as $k => $v) {
- $request .= "$k: $v\r\n";
- }
- $request .= "\r\n$data";
- $socket->write($request);
- return new Response($socket, $this->method, $this->url);
- }
- }
- class RequestError extends Exception {}
- class Response {
- const STATUS_RE = '/^HTTP\/(\d\.\d) ([1-5]\d\d) (.*)\r\n$/';
- const CHUNK_RE = '/^[a-fA-F0-9]+(?=;|\r\n)/';
- const NUM_RE = '/^(0|[1-9]\d*)$/';
- const CHARSET_RE = '/;\s*charset\s*=\s*(?:"([^"]+)"|([^\s";]+))/i';
- const STATUS_MAXLENGTH = 4096;
- const HEADER_MAXLENGTH = 4096;
- const BLOCK_SIZE = 4096;
- public $method;
- public $url;
- public $httpVersion;
- public $statusCode;
- public $reasonPhrase;
- public $headers;
- public $location = '';
- public $contentType = '';
- public $mimeType = '';
- public $mainType = '';
- public $subType = '';
- public $charset = '';
- public $content = '';
- protected $socket;
- protected $redirectCodes = array(301, 302, 303, 307);
- public function __construct($socket, $method, $url) {
- $this->socket = $socket;
- $this->method = $method;
- $this->url = $url;
- $this->processResponse();
- }
- public function isOk() {
- return $this->statusCode == 200;
- }
- public function isRedirect() {
- return in_array($this->statusCode, $this->redirectCodes);
- }
- protected function processResponse() {
- $statusLine = $this->socket->readLine(static::STATUS_MAXLENGTH);
- if (!preg_match(static::STATUS_RE, $statusLine, $matches)) {
- throw new ResponseError('Invalid status line');
- }
- $this->httpVersion = $matches[1];
- $this->statusCode = intval($matches[2]);
- $this->reasonPhrase = $matches[3];
- $headers = '';
- while (!$this->socket->eof()) {
- $line = $this->socket->readLine(static::HEADER_MAXLENGTH);
- if ($line == '' || $line == "\r\n") {
- break;
- }
- $headers .= $line;
- }
- // dump($headers);
- $this->headers = Headers::parse($headers);
- if (isset($this->headers['location'])) {
- $this->location = $this->headers['location'];
- }
- if (isset($this->headers['content-type'])) {
- $this->contentType = $this->headers['content-type'];
- $ex = explode(';', $this->contentType, 1);
- $this->mimeType = trim($ex[0]);
- $ex = explode('/', $this->mimeType, 2);
- $this->mainType = $ex[0];
- if (isset($ex[1])) {
- $this->subType = $ex[1];
- }
- // Кодировка
- if (preg_match(static::CHARSET_RE, $this->contentType, $m)) {
- $this->charset = isset($m[2]) ? $m[2] : $this->decodeSlashes($m[1]);
- }
- }
- // На запрос, отправленный методом HEAD, сервер не должен возвращать ответ
- if ($this->method != 'HEAD') {
- // Если установлен 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->socket->readLine();
- if (!preg_match(static::CHUNK_RE, $chunk, $matches)) {
- throw new ResponseError('Invalid chunk');
- }
- $size = hexdec($matches[0]);
- if (!$size) {
- break;
- }
- $this->content .= $this->socket->read($size);
- // go to /dev/null
- $trailerLine = $this->socket->readLine();
- }
- } else {
- // unset($this->headers['content-length']);
- 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) {
- $this->content = $this->socket->read($length);
- }
- } else {
- // Читаем данные пока не кончатся?
- while (!$this->socket->eof()) {
- $data = $this->socket->read(static::BLOCK_SIZE);
- $this->content .= $data;
- if (strlen($data) < static::BLOCK_SIZE) {
- break;
- }
- }
- }
- }
- // Если были переданы Transfer-Encoding и Content-Encoding, то мы сначала
- // собираем строку из чанков, а только потом декодируем
- if (isset($this->headers['content-encoding'])) {
- $enc = strtolower($this->headers['content-encoding']);
- $decoded = $this->decodeContent($this->content, $enc);
- if ($decoded === false) {
- throw new ResponseError(sprintf('Unable to decode content with content-encoding: %s', $enc));
- }
- $this->content = $decoded;
- }
- }
- $this->socket->close();
- }
- 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
- return @gzinflate(substr($content, 10, -8));
- }
- 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.
- $zlibHeader = unpack('n', substr($content, 0, 2));
- if ($zlibHeader[1] % 31 == 0) {
- return @gzuncompress($content);
- }
- return @gzinflate($content);
- }
- return false;
- }
- protected function decodeSlashes($v) {
- return $v;
- }
- }
- class ResponseError extends Exception {}
- // $url = 'https://httpbin.org/gzip'; // ok
- // $url = 'https://httpbin.org/deflate'; // ok
- // $url = 'http://www.gbase.de/global/news/0/55322.html'; // fail
- $url = 'https://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx'; // ok
- $request = new Request('GET', $url);
- $response = $request->send();
- header("Content-Type: {$response->contentType}");
- echo($response->content);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement