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 = STREAM_CLIENT_CONNECT, $context = null) {
- if (is_null($connectionTimeout)) {
- $connectionTimeout = ini_get('default_socket_timeout');
- }
- // 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 getResource() {
- return $this->resource;
- }
- public function write($data) {
- $bytes = @fwrite($this->resource, $data);
- $this->checkTimeout();
- if ($bytes === false) {
- throw new SocketError('Could not write to socket');
- }
- return $bytes;
- }
- public function read($size) {
- // Если достигли конца файла, вернет пустую строку. Только в случае ошибки возвращает false
- $data = @fread($this->resource, $size);
- $this->checkTimeout();
- if ($data === false) {
- throw new SocketError('Could not read from socket');
- }
- return $data;
- }
- // читает данные пока сервер не разорвет соединение, а поэтому может повесить скрипт
- public function getContents($maxLength = -1, $offset = -1) {
- // http://php.net/manual/ru/function.stream-get-contents.php
- $data = @stream_get_contents($this->resource, $maxLength, $offset);
- // $data не равна false при истечении таймаута
- $this->checkTimeout();
- if ($data === false) {
- throw new SocketError('Could not get contents 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);
- }
- $this->checkTimeout();
- if ($data === false) {
- throw new SocketError('Could not read from socket');
- }
- return $data;
- }
- public function eof() {
- return $this->isResource() ? feof($this->resource) : true;
- }
- public function tell() {
- return @ftell($this->resource);
- }
- 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, $type = null) {
- // http://php.net/manual/ru/function.stream-socket-enable-crypto.php
- if (!is_null($type)) {
- return @stream_socket_enable_crypto($this->resource, $enable, $type);
- }
- 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 close() {
- if ($this->isResource()) {
- fclose($this->resource);
- }
- }
- public function isResource() {
- return is_resource($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 set($name, $value, $replace = true) {
- $lower_name = strtolower($name);
- if (isset($this->headers[$lower_name]) && $replace) {
- $this->headers[$lower_name]->setValue($value);
- } else {
- $this->headers[$lower_name] = new Header($name, $value);
- }
- // возвращаем установленное значение
- return $value;
- }
- public function update($data) {
- foreach ($data as $k => $v) {
- $this->set($k, $v);
- }
- }
- 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 &$v) {
- $v = clone $v;
- }
- }
- 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);
- $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 {}
- class Request {
- const SCHEME_RE = '/^https?$/i';
- const HTTP_PORT = 80;
- const HTTPS_PORT = 443;
- // объявим публичными чтобы не писать кучу методов типа getFoo, setFoo
- public $connectionTimeout;
- public $timeout;
- public $time;
- 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 = '') {
- $startTime = microtime(true);
- $parsedUrl = parse_url($this->url);
- if ($parsedUrl === false || !isset($parsedUrl['scheme']) || !isset($parsedUrl['host'])) {
- throw new RequestError('Bad URL');
- }
- $scheme = $parsedUrl['scheme'];
- if (!preg_match(static::SCHEME_RE, $scheme)) {
- throw new RequestError(sprintf('Unsupported scheme: %s', $scheme));
- }
- $isHttps = strtolower($scheme) == 'https';
- $host = $parsedUrl['host'];
- $port = isset($parsedUrl['port']) ? $parsedUrl['port'] : ($isHttps ? static::HTTPS_PORT : static::HTTP_PORT);
- if ($this->method == 'OPTIONS') {
- $requestUri = '*';
- } else {
- $requestUri = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/';
- if (isset($parsedUrl['query'])) {
- $requestUri .= '?' . $parsedUrl['query'];
- }
- }
- $transport = $isHttps ? 'tls' : 'tcp';
- $address = sprintf('%s://%s:%d', $transport, $host, $port);
- // die($address);
- // 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);
- $connection = new SocketClient(
- $address,
- $this->connectionTimeout,
- // STREAM_CLIENT_CONNECT,
- STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
- $context
- );
- echo(sprintf('tell(): %d<br>', $connection->tell()));
- dump($connection->getMetaData('unread_bytes'));
- // $connection->close();
- // $connection->setBlocking(true);
- if ($this->timeout) {
- // Таймаут на чтение/запись для сокета
- $connection->setTimeout($this->timeout);
- }
- if ($isHttps) {
- // Включаем шифрование трафика
- $connection->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
- }
- $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;
- $hostValue = $isDefaultPort ? $host : sprintf('%s:%d', $host, $port);
- $request = "{$this->method} $requestUri HTTP/1.1\r\nHost: $hostValue\r\n";
- foreach ($headers as $k => $v) {
- $request .= "$k: $v\r\n";
- }
- $request .= "\r\n$data";
- $connection->write($request);
- $response = new Response($connection, $this->method, $this->url);
- if ($response->isCloseConnection) {
- // echo('Закрываем соединение.<br>');
- $connection->close();
- }
- $endTime = microtime(true);
- $this->time = $endTime - $startTime;
- return $response;
- }
- }
- class RequestError extends Exception {}
- class Response {
- const STATUS_LINE_RE = '/^(HTTP\/(\d)\.(\d)) ([1-5]\d\d) ([^\r\n]*)\r\n$/';
- const CHUNK_RE = '/^([a-fA-F0-9]+)(;[^\r\n]*)?\r\n$/';
- const NUMBER_RE = '/^(0|[1-9]\d*)$/';
- const CHARSET_RE = '/;\s*charset\s*=\s*(?:"([^"]+)"|([^\s";]+))/i';
- public $method;
- public $url;
- public $httpVersion;
- public $httpVersionMajor;
- public $httpVersionMinor;
- public $statusCode;
- public $reasonPhrase;
- public $isOk;
- public $isRedirect;
- public $headers;
- public $location;
- public $isCloseConnection;
- public $contentType;
- public $mimeType;
- public $mainType;
- public $subType;
- public $charset;
- public $content;
- public $trailer;
- protected $connection;
- protected $redirectCodes = array(301, 302, 303, 307);
- public function __construct($connection, $method, $url) {
- $this->connection = $connection;
- $this->method = $method;
- $this->url = $url;
- $this->processResponse();
- }
- protected function eof() {
- return $this->connection->eof();
- }
- protected function read($size = null) {
- return $size ? $this->connection->read($size) : $this->connection->getContents();
- }
- protected function readLine() {
- return $this->connection->readLine();
- }
- protected function getHeaders() {
- $header = '';
- while (!$this->eof()) {
- $line = $this->readLine();
- if ($line == '' || $line == "\r\n") {
- break;
- }
- $header .= $line;
- }
- return Headers::parse($header);
- }
- protected function processResponse() {
- $statusLine = $this->readLine();
- $this->parseStatusLine($statusLine);
- $this->isOk = $this->statusCode == 200;
- $this->isRedirect = in_array($this->statusCode, $this->redirectCodes);
- $this->headers = $this->getHeaders();
- // dump($this->headers);
- // Должно ли быть закрыто соединение после чтения из сокета?
- // http://stackoverflow.com/questions/17437950/when-does-an-http-1-0-server-close-the-connection
- $this->isCloseConnection =
- // HTTP Version = 0.9
- !$this->httpVersionMajor ||
- // HTTP Version = 1.0
- (
- $this->httpVersionMajor == 1 && $this->httpVersionMinor == 0 &&
- (
- // Соединение по-умолчанию должно быть закрыто
- !isset($this->headers['connection']) ||
- strtolower($this->headers['connection']) != 'keep-alive'
- )
- ) ||
- (
- // Соединение по-умолчанию должно быть открыто
- isset($this->headers['connection']) &&
- strtolower($this->headers['connection']) == 'close'
- );
- 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]);
- }
- }
- $this->content = $this->getContent();
- }
- protected function parseStatusLine($statusLine) {
- if (!preg_match(static::STATUS_LINE_RE, $statusLine, $matches)) {
- throw new ResponseError('Invalid status line');
- }
- $this->httpVersion = $matches[1];
- $this->httpVersionMajor = intval($matches[2]);
- $this->httpVersionMinor = intval($matches[3]);
- $this->statusCode = intval($matches[4]);
- $this->reasonPhrase = $matches[5];
- }
- protected function decodeSlashes($v) {
- return $v;
- }
- protected function getContent() {
- $content = '';
- // На запрос, отправленный методом HEAD, сервер не должен возвращать ответ,
- // а также с кодами 1.x.x, 204 и 304
- if ($this->method != 'HEAD' &&
- $this->statusCode >= 200 &&
- $this->statusCode != 204 &&
- $this->statusCode != 304) {
- // Если установлен 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'])) {
- $encoding = strtolower($this->headers['transfer-encoding']);
- if ($encoding != 'chunked') {
- throw new ResponseError(sprintf('Unsupported transfer-encoding: %s',
- $this->headers['transfer-encoding']));
- }
- // Chunked-Body = *chunk
- // last-chunk
- // trailer
- // CRLF
- // chunk = chunk-size [ chunk-extension ] CRLF
- // chunk-data CRLF
- // chunk-size = 1*HEX
- // last-chunk = 1*("0") [ chunk-extension ] CRLF
- // chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
- // chunk-ext-name = token
- // chunk-ext-val = token | quoted-string
- // chunk-data = chunk-size(OCTET)
- // trailer = *(entity-header CRLF)
- do {
- $chunk = $this->readLine();
- if (!preg_match(static::CHUNK_RE, $chunk, $matches)) {
- throw new ResponseError('Invalid chunk');
- }
- $size = hexdec($matches[1]);
- if (isset($matches[2])) {
- $chunkExtension = $matches[2];
- // do something with $chunkExtension
- }
- if ($size) {
- // read chunk data
- $content .= $this->read($size);
- assert($this->read(2) == "\r\n");
- } else {
- $this->trailer = $this->getHeaders();
- }
- } while ($size);
- } else {
- // unset($this->headers['content-length']);
- if (isset($this->headers['content-length'])) {
- if (!preg_match(static::NUMBER_RE, $this->headers['content-length'])) {
- throw new Exception('Invalid content-length');
- }
- // читаем количество байт указанных в заголовке
- $length = intval($this->headers['content-length']);
- if ($length) {
- $content = $this->read($length);
- }
- } else {
- // Не понятно, что делать, если не установлены Content-Length и Transfer-Encoding
- // http://stackoverflow.com/questions/12738519/http-response-headers-valid-with-no-transfer-encoding-and-content-length
- }
- }
- // Если были переданы Transfer-Encoding и Content-Encoding, то мы сначала
- // собираем строку из чанков, а только потом декодируем
- if (isset($this->headers['content-encoding'])) {
- $decoded = $this->decodeContent($content, $this->headers['content-encoding']);
- if ($decoded === false) {
- throw new ResponseError(sprintf('Unable to decode content with content-encoding: %s',
- $this->headers['content-encoding']));
- }
- $content = $decoded;
- }
- }
- return $content;
- }
- protected function decodeContent($content, $encoding) {
- // За основу взят этот код:
- // https://github.com/zendframework/zend-http/blob/master/src/Response.php
- $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') {
- // 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;
- }
- }
- class ResponseError extends Exception {}
- header('Content-Type: text/html; charset=utf-8');
- // $url = 'https://httpbin.org/gzip'; // ok
- // $url = 'https://httpbin.org/deflate'; // ok
- // $url = 'http://www.gbase.de/global/news/0/55322.html'; // fail
- $url = 'http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx'; // ok
- $request = new Request('GET', $url);
- $request->setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36');
- // $request->setHeader('Connection', 'close');
- $response = $request->send();
- echo("Request time (sec): {$request->time}<br>");
- dump($response);
- file_put_contents('image.jpg', $response->content);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement