+
This commit is contained in:
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Normal file
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP message base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see MessageInterface
|
||||
*/
|
||||
abstract class AbstractMessage implements MessageInterface
|
||||
{
|
||||
/**
|
||||
* [Internal] Regex used to match all request header fields into an array, thanks to @kelunik for checking the HTTP specs and coming up with this regex
|
||||
*
|
||||
* @internal
|
||||
* @var string
|
||||
*/
|
||||
const REGEX_HEADERS = '/^([^()<>@,;:\\\"\/\[\]?={}\x00-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m';
|
||||
|
||||
/** @var array<string,string[]> */
|
||||
private $headers = array();
|
||||
|
||||
/** @var array<string,string> */
|
||||
private $headerNamesLowerCase = array();
|
||||
|
||||
/** @var string */
|
||||
private $protocolVersion;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $body;
|
||||
|
||||
/**
|
||||
* @param string $protocolVersion
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
*/
|
||||
protected function __construct($protocolVersion, array $headers, StreamInterface $body)
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
if ($value !== array()) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower])) {
|
||||
$value = \array_merge($this->headers[$this->headerNamesLowerCase[$lower]], $value);
|
||||
unset($this->headers[$this->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$this->headers[$name] = $value;
|
||||
$this->headerNamesLowerCase[$lower] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->protocolVersion = (string) $protocolVersion;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
return $this->protocolVersion;
|
||||
}
|
||||
|
||||
public function withProtocolVersion($version)
|
||||
{
|
||||
if ((string) $version === $this->protocolVersion) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->protocolVersion = (string) $version;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function hasHeader($name)
|
||||
{
|
||||
return isset($this->headerNamesLowerCase[\strtolower($name)]);
|
||||
}
|
||||
|
||||
public function getHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
return isset($this->headerNamesLowerCase[$lower]) ? $this->headers[$this->headerNamesLowerCase[$lower]] : array();
|
||||
}
|
||||
|
||||
public function getHeaderLine($name)
|
||||
{
|
||||
return \implode(', ', $this->getHeader($name));
|
||||
}
|
||||
|
||||
public function withHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this->withoutHeader($name);
|
||||
} elseif (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower]) && $this->headerNamesLowerCase[$lower] === (string) $name && $this->headers[$this->headerNamesLowerCase[$lower]] === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
if (isset($message->headerNamesLowerCase[$lower])) {
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$message->headers[$name] = $value;
|
||||
$message->headerNamesLowerCase[$lower] = $name;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function withAddedHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->withHeader($name, \array_merge($this->getHeader($name), \is_array($value) ? $value : array($value)));
|
||||
}
|
||||
|
||||
public function withoutHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
if (!isset($this->headerNamesLowerCase[$lower])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]], $message->headerNamesLowerCase[$lower]);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function withBody(StreamInterface $body)
|
||||
{
|
||||
if ($body === $this->body) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->body = $body;
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Normal file
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\Http\Message\Uri;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP request base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see RequestInterface
|
||||
*/
|
||||
abstract class AbstractRequest extends AbstractMessage implements RequestInterface
|
||||
{
|
||||
/** @var ?string */
|
||||
private $requestTarget;
|
||||
|
||||
/** @var string */
|
||||
private $method;
|
||||
|
||||
/** @var UriInterface */
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string|UriInterface $uri
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
* @param string unknown $protocolVersion
|
||||
*/
|
||||
protected function __construct(
|
||||
$method,
|
||||
$uri,
|
||||
array $headers,
|
||||
StreamInterface $body,
|
||||
$protocolVersion
|
||||
) {
|
||||
if (\is_string($uri)) {
|
||||
$uri = new Uri($uri);
|
||||
} elseif (!$uri instanceof UriInterface) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Argument #2 ($uri) expected string|Psr\Http\Message\UriInterface'
|
||||
);
|
||||
}
|
||||
|
||||
// assign default `Host` request header from URI unless already given explicitly
|
||||
$host = $uri->getHost();
|
||||
if ($host !== '') {
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\strtolower($name) === 'host' && $value !== array()) {
|
||||
$host = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($host !== '') {
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
$headers = array('Host' => $host) + $headers;
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($protocolVersion, $headers, $body);
|
||||
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function getRequestTarget()
|
||||
{
|
||||
if ($this->requestTarget !== null) {
|
||||
return $this->requestTarget;
|
||||
}
|
||||
|
||||
$target = $this->uri->getPath();
|
||||
if ($target === '') {
|
||||
$target = '/';
|
||||
}
|
||||
if (($query = $this->uri->getQuery()) !== '') {
|
||||
$target .= '?' . $query;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withRequestTarget($requestTarget)
|
||||
{
|
||||
if ((string) $requestTarget === $this->requestTarget) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->requestTarget = (string) $requestTarget;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function withMethod($method)
|
||||
{
|
||||
if ((string) $method === $this->method) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->method = (string) $method;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getUri()
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function withUri(UriInterface $uri, $preserveHost = false)
|
||||
{
|
||||
if ($uri === $this->uri) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->uri = $uri;
|
||||
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && $host !== '' && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
// update `Host` request header if URI contains a new host and `$preserveHost` is false
|
||||
if ($host !== '' && (!$preserveHost || $request->getHeaderLine('Host') === '')) {
|
||||
// first remove all headers before assigning `Host` header to ensure it always comes first
|
||||
foreach (\array_keys($request->getHeaders()) as $name) {
|
||||
$request = $request->withoutHeader($name);
|
||||
}
|
||||
|
||||
// add `Host` header first, then all other original headers
|
||||
$request = $request->withHeader('Host', $host);
|
||||
foreach ($this->withoutHeader('Host')->getHeaders() as $name => $value) {
|
||||
$request = $request->withHeader($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Normal file
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] PSR-7 message body implementation using an in-memory buffer
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BufferedBody implements StreamInterface
|
||||
{
|
||||
private $buffer = '';
|
||||
private $position = 0;
|
||||
private $closed = false;
|
||||
|
||||
/**
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function __construct($buffer)
|
||||
{
|
||||
$this->buffer = $buffer;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->seek(0);
|
||||
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
$this->closed = true;
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
$this->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->closed ? null : \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to tell position of closed stream');
|
||||
}
|
||||
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return $this->position >= \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = \SEEK_SET)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to seek on closed stream');
|
||||
}
|
||||
|
||||
$old = $this->position;
|
||||
|
||||
if ($whence === \SEEK_SET) {
|
||||
$this->position = $offset;
|
||||
} elseif ($whence === \SEEK_CUR) {
|
||||
$this->position += $offset;
|
||||
} elseif ($whence === \SEEK_END) {
|
||||
$this->position = \strlen($this->buffer) + $offset;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid seek mode given');
|
||||
}
|
||||
|
||||
if (!\is_int($this->position) || $this->position < 0) {
|
||||
$this->position = $old;
|
||||
throw new \RuntimeException('Unable to seek to position');
|
||||
}
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to write to closed stream');
|
||||
}
|
||||
|
||||
if ($string === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->position > 0 && !isset($this->buffer[$this->position - 1])) {
|
||||
$this->buffer = \str_pad($this->buffer, $this->position, "\0");
|
||||
}
|
||||
|
||||
$len = \strlen($string);
|
||||
$this->buffer = \substr($this->buffer, 0, $this->position) . $string . \substr($this->buffer, $this->position + $len);
|
||||
$this->position += $len;
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if ($length < 1) {
|
||||
throw new \InvalidArgumentException('Invalid read length given');
|
||||
}
|
||||
|
||||
if ($this->position + $length > \strlen($this->buffer)) {
|
||||
$length = \strlen($this->buffer) - $this->position;
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position += $length;
|
||||
|
||||
return \substr($this->buffer, $pos, $length);
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position = \strlen($this->buffer);
|
||||
|
||||
return \substr($this->buffer, $pos);
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return $key === null ? array() : null;
|
||||
}
|
||||
}
|
||||
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Normal file
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Decodes "Transfer-Encoding: chunked" from given stream and returns only payload data.
|
||||
*
|
||||
* This is used internally to decode incoming requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedDecoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
const MAX_CHUNK_HEADER_SIZE = 1024;
|
||||
|
||||
private $closed = false;
|
||||
private $input;
|
||||
private $buffer = '';
|
||||
private $chunkSize = 0;
|
||||
private $transferredSize = 0;
|
||||
private $headerCompleted = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = '';
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
while ($this->buffer !== '') {
|
||||
if (!$this->headerCompleted) {
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === false) {
|
||||
// Header shouldn't be bigger than 1024 bytes
|
||||
if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
|
||||
$this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$header = \strtolower((string)\substr($this->buffer, 0, $positionCrlf));
|
||||
$hexValue = $header;
|
||||
|
||||
if (\strpos($header, ';') !== false) {
|
||||
$array = \explode(';', $header);
|
||||
$hexValue = $array[0];
|
||||
}
|
||||
|
||||
if ($hexValue !== '') {
|
||||
$hexValue = \ltrim(\trim($hexValue), "0");
|
||||
if ($hexValue === '') {
|
||||
$hexValue = "0";
|
||||
}
|
||||
}
|
||||
|
||||
$this->chunkSize = @\hexdec($hexValue);
|
||||
if (!\is_int($this->chunkSize) || \dechex($this->chunkSize) !== $hexValue) {
|
||||
$this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf + 2);
|
||||
$this->headerCompleted = true;
|
||||
if ($this->buffer === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$chunk = (string)\substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
|
||||
|
||||
if ($chunk !== '') {
|
||||
$this->transferredSize += \strlen($chunk);
|
||||
$this->emit('data', array($chunk));
|
||||
$this->buffer = (string)\substr($this->buffer, \strlen($chunk));
|
||||
}
|
||||
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === 0) {
|
||||
if ($this->chunkSize === 0) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
return;
|
||||
}
|
||||
$this->chunkSize = 0;
|
||||
$this->headerCompleted = false;
|
||||
$this->transferredSize = 0;
|
||||
$this->buffer = (string)\substr($this->buffer, 2);
|
||||
} elseif ($this->chunkSize === 0) {
|
||||
// end chunk received, skip all trailer data
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
|
||||
// the first 2 characters are not CRLF, send error event
|
||||
$this->handleError(new Exception('Chunk does not end with a CRLF'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && \strlen($this->buffer) < 2) {
|
||||
// No CRLF found, wait for additional data which could be a CRLF
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Normal file
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
|
||||
*
|
||||
* This is used internally to encode outgoing requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array(
|
||||
\dechex(\strlen($data)) . "\r\n" . $data . "\r\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('data', array("0\r\n\r\n"));
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Normal file
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Manages outgoing HTTP connections for the HTTP client
|
||||
*
|
||||
* @internal
|
||||
* @final
|
||||
*/
|
||||
class ClientConnectionManager
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/** @var string[] */
|
||||
private $idleUris = array();
|
||||
|
||||
/** @var ConnectionInterface[] */
|
||||
private $idleConnections = array();
|
||||
|
||||
/** @var TimerInterface[] */
|
||||
private $idleTimers = array();
|
||||
|
||||
/** @var \Closure[] */
|
||||
private $idleStreamHandlers = array();
|
||||
|
||||
/** @var float */
|
||||
private $maximumTimeToKeepAliveIdleConnection = 0.001;
|
||||
|
||||
public function __construct(ConnectorInterface $connector, LoopInterface $loop)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromiseInterface<ConnectionInterface>
|
||||
*/
|
||||
public function connect(UriInterface $uri)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
if ($scheme !== 'https' && $scheme !== 'http') {
|
||||
return \React\Promise\reject(new \InvalidArgumentException(
|
||||
'Invalid request URL given'
|
||||
));
|
||||
}
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
$uri = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
|
||||
// Reuse idle connection for same URI if available
|
||||
foreach ($this->idleConnections as $id => $connection) {
|
||||
if ($this->idleUris[$id] === $uri) {
|
||||
assert($this->idleStreamHandlers[$id] instanceof \Closure);
|
||||
$connection->removeListener('close', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('data', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('error', $this->idleStreamHandlers[$id]);
|
||||
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
return \React\Promise\resolve($connection);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new connection if no idle connection to same URI is available
|
||||
return $this->connector->connect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hands back an idle connection to the connection manager for possible future reuse.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function keepAlive(UriInterface $uri, ConnectionInterface $connection)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
assert($scheme === 'https' || $scheme === 'http');
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
|
||||
$this->idleUris[] = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
$this->idleConnections[] = $connection;
|
||||
|
||||
$that = $this;
|
||||
$cleanUp = function () use ($connection, $that) {
|
||||
// call public method to support legacy PHP 5.3
|
||||
$that->cleanUpConnection($connection);
|
||||
};
|
||||
|
||||
// clean up and close connection when maximum time to keep-alive idle connection has passed
|
||||
$this->idleTimers[] = $this->loop->addTimer($this->maximumTimeToKeepAliveIdleConnection, $cleanUp);
|
||||
|
||||
// clean up and close connection when unexpected close/data/error event happens during idle time
|
||||
$this->idleStreamHandlers[] = $cleanUp;
|
||||
$connection->on('close', $cleanUp);
|
||||
$connection->on('data', $cleanUp);
|
||||
$connection->on('error', $cleanUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return void
|
||||
*/
|
||||
public function cleanUpConnection(ConnectionInterface $connection) // private (PHP 5.4+)
|
||||
{
|
||||
$id = \array_search($connection, $this->idleConnections, true);
|
||||
if ($id === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(\is_int($id));
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
$connection->close();
|
||||
}
|
||||
}
|
||||
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Normal file
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/** @internal */
|
||||
class ClientRequestState
|
||||
{
|
||||
/** @var int */
|
||||
public $numRequests = 0;
|
||||
|
||||
/** @var ?\React\Promise\PromiseInterface */
|
||||
public $pending = null;
|
||||
|
||||
/** @var ?\React\EventLoop\TimerInterface */
|
||||
public $timeout = null;
|
||||
}
|
||||
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Normal file
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @event response
|
||||
* @event drain
|
||||
* @event error
|
||||
* @event close
|
||||
* @internal
|
||||
*/
|
||||
class ClientRequestStream extends EventEmitter implements WritableStreamInterface
|
||||
{
|
||||
const STATE_INIT = 0;
|
||||
const STATE_WRITING_HEAD = 1;
|
||||
const STATE_HEAD_WRITTEN = 2;
|
||||
const STATE_END = 3;
|
||||
|
||||
/** @var ClientConnectionManager */
|
||||
private $connectionManager;
|
||||
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var ?ConnectionInterface */
|
||||
private $connection;
|
||||
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
|
||||
private $responseFactory;
|
||||
private $state = self::STATE_INIT;
|
||||
private $ended = false;
|
||||
|
||||
private $pendingWrites = '';
|
||||
|
||||
public function __construct(ClientConnectionManager $connectionManager, RequestInterface $request)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return self::STATE_END > $this->state && !$this->ended;
|
||||
}
|
||||
|
||||
private function writeHead()
|
||||
{
|
||||
$this->state = self::STATE_WRITING_HEAD;
|
||||
|
||||
$expected = 0;
|
||||
$headers = "{$this->request->getMethod()} {$this->request->getRequestTarget()} HTTP/{$this->request->getProtocolVersion()}\r\n";
|
||||
foreach ($this->request->getHeaders() as $name => $values) {
|
||||
if (\strpos($name, ':') !== false) {
|
||||
$expected = -1;
|
||||
break;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers .= "$name: $value\r\n";
|
||||
++$expected;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $m legacy PHP 5.3 only */
|
||||
if (!\preg_match('#^\S+ \S+ HTTP/1\.[01]\r\n#m', $headers) || \substr_count($headers, "\n") !== ($expected + 1) || (\PHP_VERSION_ID >= 50400 ? \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers) : \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers, $m)) !== $expected) {
|
||||
$this->closeError(new \InvalidArgumentException('Unable to send request with invalid request headers'));
|
||||
return;
|
||||
}
|
||||
|
||||
$connectionRef = &$this->connection;
|
||||
$stateRef = &$this->state;
|
||||
$pendingWrites = &$this->pendingWrites;
|
||||
$that = $this;
|
||||
|
||||
$promise = $this->connectionManager->connect($this->request->getUri());
|
||||
$promise->then(
|
||||
function (ConnectionInterface $connection) use ($headers, &$connectionRef, &$stateRef, &$pendingWrites, $that) {
|
||||
$connectionRef = $connection;
|
||||
assert($connectionRef instanceof ConnectionInterface);
|
||||
|
||||
$connection->on('drain', array($that, 'handleDrain'));
|
||||
$connection->on('data', array($that, 'handleData'));
|
||||
$connection->on('end', array($that, 'handleEnd'));
|
||||
$connection->on('error', array($that, 'handleError'));
|
||||
$connection->on('close', array($that, 'close'));
|
||||
|
||||
$more = $connection->write($headers . "\r\n" . $pendingWrites);
|
||||
|
||||
assert($stateRef === ClientRequestStream::STATE_WRITING_HEAD);
|
||||
$stateRef = ClientRequestStream::STATE_HEAD_WRITTEN;
|
||||
|
||||
// clear pending writes if non-empty
|
||||
if ($pendingWrites !== '') {
|
||||
$pendingWrites = '';
|
||||
|
||||
if ($more) {
|
||||
$that->emit('drain');
|
||||
}
|
||||
}
|
||||
},
|
||||
array($this, 'closeError')
|
||||
);
|
||||
|
||||
$this->on('close', function() use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// write directly to connection stream if already available
|
||||
if (self::STATE_HEAD_WRITTEN <= $this->state) {
|
||||
return $this->connection->write($data);
|
||||
}
|
||||
|
||||
// otherwise buffer and try to establish connection
|
||||
$this->pendingWrites .= $data;
|
||||
if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $data) {
|
||||
$this->write($data);
|
||||
} else if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
$this->ended = true;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleDrain()
|
||||
{
|
||||
$this->emit('drain');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
// buffer until double CRLF (or double LF for compatibility with legacy servers)
|
||||
$eom = \strpos($this->buffer, "\r\n\r\n");
|
||||
$eomLegacy = \strpos($this->buffer, "\n\n");
|
||||
if ($eom !== false || $eomLegacy !== false) {
|
||||
try {
|
||||
if ($eom !== false && ($eomLegacy === false || $eom < $eomLegacy)) {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eom + 2));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eom + 4);
|
||||
} else {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eomLegacy + 1));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eomLegacy + 2);
|
||||
}
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
$this->closeError($exception);
|
||||
return;
|
||||
}
|
||||
|
||||
// response headers successfully received => remove listeners for connection events
|
||||
$connection = $this->connection;
|
||||
assert($connection instanceof ConnectionInterface);
|
||||
$connection->removeListener('drain', array($this, 'handleDrain'));
|
||||
$connection->removeListener('data', array($this, 'handleData'));
|
||||
$connection->removeListener('end', array($this, 'handleEnd'));
|
||||
$connection->removeListener('error', array($this, 'handleError'));
|
||||
$connection->removeListener('close', array($this, 'close'));
|
||||
$this->connection = null;
|
||||
$this->buffer = '';
|
||||
|
||||
// take control over connection handling and check if we can reuse the connection once response body closes
|
||||
$that = $this;
|
||||
$request = $this->request;
|
||||
$connectionManager = $this->connectionManager;
|
||||
$successfulEndReceived = false;
|
||||
$input = $body = new CloseProtectionStream($connection);
|
||||
$input->on('close', function () use ($connection, $that, $connectionManager, $request, $response, &$successfulEndReceived) {
|
||||
// only reuse connection after successful response and both request and response allow keep alive
|
||||
if ($successfulEndReceived && $connection->isReadable() && $that->hasMessageKeepAliveEnabled($response) && $that->hasMessageKeepAliveEnabled($request)) {
|
||||
$connectionManager->keepAlive($request->getUri(), $connection);
|
||||
} else {
|
||||
$connection->close();
|
||||
}
|
||||
|
||||
$that->close();
|
||||
});
|
||||
|
||||
// determine length of response body
|
||||
$length = null;
|
||||
$code = $response->getStatusCode();
|
||||
if ($this->request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == Response::STATUS_NO_CONTENT || $code == Response::STATUS_NOT_MODIFIED) {
|
||||
$length = 0;
|
||||
} elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
|
||||
$body = new ChunkedDecoder($body);
|
||||
} elseif ($response->hasHeader('Content-Length')) {
|
||||
$length = (int) $response->getHeaderLine('Content-Length');
|
||||
}
|
||||
$response = $response->withBody($body = new ReadableBodyStream($body, $length));
|
||||
$body->on('end', function () use (&$successfulEndReceived) {
|
||||
$successfulEndReceived = true;
|
||||
});
|
||||
|
||||
// emit response with streaming response body (see `Sender`)
|
||||
$this->emit('response', array($response, $body));
|
||||
|
||||
// re-emit HTTP response body to trigger body parsing if parts of it are buffered
|
||||
if ($bodyChunk !== '') {
|
||||
$input->handleData($bodyChunk);
|
||||
} elseif ($length === 0) {
|
||||
$input->handleEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"Connection ended before receiving response"
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"An error occurred in the underlying stream",
|
||||
0,
|
||||
$error
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function closeError(\Exception $error)
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
$this->emit('error', array($error));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->state = self::STATE_END;
|
||||
$this->pendingWrites = '';
|
||||
$this->buffer = '';
|
||||
|
||||
if ($this->connection instanceof ConnectionInterface) {
|
||||
$this->connection->close();
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return bool
|
||||
* @link https://www.rfc-editor.org/rfc/rfc9112#section-9.3
|
||||
* @link https://www.rfc-editor.org/rfc/rfc7230#section-6.1
|
||||
*/
|
||||
public function hasMessageKeepAliveEnabled(MessageInterface $message)
|
||||
{
|
||||
// @link https://www.rfc-editor.org/rfc/rfc9110#section-7.6.1
|
||||
$connectionOptions = \array_map('trim', \explode(',', \strtolower($message->getHeaderLine('Connection'))));
|
||||
|
||||
if (\in_array('close', $connectionOptions, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($message->getProtocolVersion() === '1.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (\in_array('keep-alive', $connectionOptions, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
54
vendor/react/http/src/Io/Clock.php
vendored
Normal file
54
vendor/react/http/src/Io/Clock.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* [internal] Clock source that returns current timestamp and memoize clock for same tick
|
||||
*
|
||||
* This is mostly used as an internal optimization to avoid unneeded syscalls to
|
||||
* get the current system time multiple times within the same loop tick. For the
|
||||
* purpose of the HTTP server, the clock is assumed to not change to a
|
||||
* significant degree within the same loop tick. If you need a high precision
|
||||
* clock source, you may want to use `\hrtime()` instead (PHP 7.3+).
|
||||
*
|
||||
* The API is modelled to resemble the PSR-20 `ClockInterface` (in draft at the
|
||||
* time of writing this), but uses a `float` return value for performance
|
||||
* reasons instead.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about for outside use.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Clock
|
||||
{
|
||||
/** @var LoopInterface $loop */
|
||||
private $loop;
|
||||
|
||||
/** @var ?float */
|
||||
private $now;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/** @return float */
|
||||
public function now()
|
||||
{
|
||||
if ($this->now === null) {
|
||||
$this->now = \microtime(true);
|
||||
|
||||
// remember clock for current loop tick only and update on next tick
|
||||
$now =& $this->now;
|
||||
$this->loop->futureTick(function () use (&$now) {
|
||||
assert($now !== null);
|
||||
$now = null;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->now;
|
||||
}
|
||||
}
|
||||
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Normal file
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Protects a given stream from actually closing and only discards its incoming data instead.
|
||||
*
|
||||
* This is used internally to prevent the underlying connection from closing, so
|
||||
* that we can still send back a response over the same stream.
|
||||
*
|
||||
* @internal
|
||||
* */
|
||||
class CloseProtectionStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input stream that will be discarded instead of closing it on an 'close' event.
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = true;
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
// stop listening for incoming events
|
||||
$this->input->removeListener('data', array($this, 'handleData'));
|
||||
$this->input->removeListener('error', array($this, 'handleError'));
|
||||
$this->input->removeListener('end', array($this, 'handleEnd'));
|
||||
$this->input->removeListener('close', array($this, 'close'));
|
||||
|
||||
// resume the stream to ensure we discard everything from incoming connection
|
||||
if ($this->paused) {
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
}
|
||||
}
|
||||
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Normal file
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between an empty StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to represent an empty body stream of an
|
||||
* incoming response from the client. This is similar to the `HttpBodyStream`,
|
||||
* but is specifically designed for the common case of having an empty message
|
||||
* body.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see HttpBodyStream
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class EmptyBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
private $closed = false;
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
}
|
||||
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Normal file
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to stream the body of an incoming response
|
||||
* from the client. This allows us to stream big amounts of data without having
|
||||
* to buffer this data. Similarly, this used to stream the body of an outgoing
|
||||
* request body to the client. The data will be sent directly to the client.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class HttpBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
public $input;
|
||||
private $closed = false;
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input Stream data from $stream as a body of a PSR-7 object4
|
||||
* @param int|null $size size of the data body
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input, $size)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/react/http/src/Io/IniUtil.php
vendored
Normal file
48
vendor/react/http/src/Io/IniUtil.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class IniUtil
|
||||
{
|
||||
/**
|
||||
* Convert a ini like size to a numeric size in bytes.
|
||||
*
|
||||
* @param string $size
|
||||
* @return int
|
||||
*/
|
||||
public static function iniSizeToBytes($size)
|
||||
{
|
||||
if (\is_numeric($size)) {
|
||||
return (int)$size;
|
||||
}
|
||||
|
||||
$suffix = \strtoupper(\substr($size, -1));
|
||||
$strippedSize = \substr($size, 0, -1);
|
||||
|
||||
if (!\is_numeric($strippedSize)) {
|
||||
throw new \InvalidArgumentException("$size is not a valid ini size");
|
||||
}
|
||||
|
||||
if ($strippedSize <= 0) {
|
||||
throw new \InvalidArgumentException("Expect $size to be higher isn't zero or lower");
|
||||
}
|
||||
|
||||
if ($suffix === 'K') {
|
||||
return $strippedSize * 1024;
|
||||
}
|
||||
if ($suffix === 'M') {
|
||||
return $strippedSize * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'G') {
|
||||
return $strippedSize * 1024 * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'T') {
|
||||
return $strippedSize * 1024 * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
return (int)$size;
|
||||
}
|
||||
}
|
||||
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Normal file
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Limits the amount of data the given stream can emit
|
||||
*
|
||||
* This is used internally to limit the size of the underlying connection stream
|
||||
* to the size defined by the "Content-Length" header of the incoming request.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LengthLimitedStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $stream;
|
||||
private $closed = false;
|
||||
private $transferredLength = 0;
|
||||
private $maxLength;
|
||||
|
||||
public function __construct(ReadableStreamInterface $stream, $maxLength)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->maxLength = $maxLength;
|
||||
|
||||
$this->stream->on('data', array($this, 'handleData'));
|
||||
$this->stream->on('end', array($this, 'handleEnd'));
|
||||
$this->stream->on('error', array($this, 'handleError'));
|
||||
$this->stream->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->stream->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->stream->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->stream->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->stream->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if (($this->transferredLength + \strlen($data)) > $this->maxLength) {
|
||||
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
|
||||
$data = (string)\substr($data, 0, $this->maxLength - $this->transferredLength);
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
$this->transferredLength += \strlen($data);
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
if ($this->transferredLength === $this->maxLength) {
|
||||
// 'Content-Length' reached, stream will end
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
$this->stream->removeListener('data', array($this, 'handleData'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new \Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Normal file
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Middleware runner to expose an array of middleware request handlers as a single request handler callable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MiddlewareRunner
|
||||
{
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
private $middleware;
|
||||
|
||||
/**
|
||||
* @param callable[] $middleware
|
||||
*/
|
||||
public function __construct(array $middleware)
|
||||
{
|
||||
$this->middleware = \array_values($middleware);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
if (empty($this->middleware)) {
|
||||
throw new \RuntimeException('No middleware to run');
|
||||
}
|
||||
|
||||
return $this->call($request, 0);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function call(ServerRequestInterface $request, $position)
|
||||
{
|
||||
// final request handler will be invoked without a next handler
|
||||
if (!isset($this->middleware[$position + 1])) {
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$next = function (ServerRequestInterface $request) use ($that, $position) {
|
||||
return $that->call($request, $position + 1);
|
||||
};
|
||||
|
||||
// invoke middleware request handler with next handler
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request, $next);
|
||||
}
|
||||
}
|
||||
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Normal file
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Parses a string body with "Content-Type: multipart/form-data" into structured data
|
||||
*
|
||||
* This is use internally to parse incoming request bodies into structured data
|
||||
* that resembles PHP's `$_POST` and `$_FILES` superglobals.
|
||||
*
|
||||
* @internal
|
||||
* @link https://tools.ietf.org/html/rfc7578
|
||||
* @link https://tools.ietf.org/html/rfc2046#section-5.1.1
|
||||
*/
|
||||
final class MultipartParser
|
||||
{
|
||||
/**
|
||||
* @var ServerRequestInterface|null
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxFileSize;
|
||||
|
||||
/**
|
||||
* Based on $maxInputVars and $maxFileUploads
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxMultipartBodyParts;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_vars"
|
||||
*
|
||||
* Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
|
||||
*/
|
||||
private $maxInputVars = 1000;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_nesting_level"
|
||||
*
|
||||
* Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
|
||||
*/
|
||||
private $maxInputNestingLevel = 64;
|
||||
|
||||
/**
|
||||
* ini setting "upload_max_filesize"
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $uploadMaxFilesize;
|
||||
|
||||
/**
|
||||
* ini setting "max_file_uploads"
|
||||
*
|
||||
* Additionally, setting "file_uploads = off" effectively sets this to zero.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxFileUploads;
|
||||
|
||||
private $multipartBodyPartCount = 0;
|
||||
private $postCount = 0;
|
||||
private $filesCount = 0;
|
||||
private $emptyCount = 0;
|
||||
private $cursor = 0;
|
||||
|
||||
/**
|
||||
* @param int|string|null $uploadMaxFilesize
|
||||
* @param int|null $maxFileUploads
|
||||
*/
|
||||
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
|
||||
{
|
||||
$var = \ini_get('max_input_vars');
|
||||
if ($var !== false) {
|
||||
$this->maxInputVars = (int)$var;
|
||||
}
|
||||
$var = \ini_get('max_input_nesting_level');
|
||||
if ($var !== false) {
|
||||
$this->maxInputNestingLevel = (int)$var;
|
||||
}
|
||||
|
||||
if ($uploadMaxFilesize === null) {
|
||||
$uploadMaxFilesize = \ini_get('upload_max_filesize');
|
||||
}
|
||||
|
||||
$this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
|
||||
$this->maxFileUploads = $maxFileUploads === null ? (\ini_get('file_uploads') === '' ? 0 : (int)\ini_get('max_file_uploads')) : (int)$maxFileUploads;
|
||||
|
||||
$this->maxMultipartBodyParts = $this->maxInputVars + $this->maxFileUploads;
|
||||
}
|
||||
|
||||
public function parse(ServerRequestInterface $request)
|
||||
{
|
||||
$contentType = $request->getHeaderLine('content-type');
|
||||
if(!\preg_match('/boundary="?(.*?)"?$/', $contentType, $matches)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->parseBody('--' . $matches[1], (string)$request->getBody());
|
||||
|
||||
$request = $this->request;
|
||||
$this->request = null;
|
||||
$this->multipartBodyPartCount = 0;
|
||||
$this->cursor = 0;
|
||||
$this->postCount = 0;
|
||||
$this->filesCount = 0;
|
||||
$this->emptyCount = 0;
|
||||
$this->maxFileSize = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function parseBody($boundary, $buffer)
|
||||
{
|
||||
$len = \strlen($boundary);
|
||||
|
||||
// ignore everything before initial boundary (SHOULD be empty)
|
||||
$this->cursor = \strpos($buffer, $boundary . "\r\n");
|
||||
|
||||
while ($this->cursor !== false) {
|
||||
// search following boundary (preceded by newline)
|
||||
// ignore last if not followed by boundary (SHOULD end with "--")
|
||||
$this->cursor += $len + 2;
|
||||
$end = \strpos($buffer, "\r\n" . $boundary, $this->cursor);
|
||||
if ($end === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
// parse one part and continue searching for next
|
||||
$this->parsePart(\substr($buffer, $this->cursor, $end - $this->cursor));
|
||||
$this->cursor = $end;
|
||||
|
||||
if (++$this->multipartBodyPartCount > $this->maxMultipartBodyParts) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parsePart($chunk)
|
||||
{
|
||||
$pos = \strpos($chunk, "\r\n\r\n");
|
||||
if ($pos === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
|
||||
$body = (string)\substr($chunk, $pos + 4);
|
||||
|
||||
if (!isset($headers['content-disposition'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
|
||||
if ($name === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
|
||||
if ($filename !== null) {
|
||||
$this->parseFile(
|
||||
$name,
|
||||
$filename,
|
||||
isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
|
||||
$body
|
||||
);
|
||||
} else {
|
||||
$this->parsePost($name, $body);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseFile($name, $filename, $contentType, $contents)
|
||||
{
|
||||
$file = $this->parseUploadedFile($filename, $contentType, $contents);
|
||||
if ($file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withUploadedFiles($this->extractPost(
|
||||
$this->request->getUploadedFiles(),
|
||||
$name,
|
||||
$file
|
||||
));
|
||||
}
|
||||
|
||||
private function parseUploadedFile($filename, $contentType, $contents)
|
||||
{
|
||||
$size = \strlen($contents);
|
||||
|
||||
// no file selected (zero size and empty filename)
|
||||
if ($size === 0 && $filename === '') {
|
||||
// ignore excessive number of empty file uploads
|
||||
if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_NO_FILE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// ignore excessive number of file uploads
|
||||
if (++$this->filesCount > $this->maxFileUploads) {
|
||||
return;
|
||||
}
|
||||
|
||||
// file exceeds "upload_max_filesize" ini setting
|
||||
if ($size > $this->uploadMaxFilesize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_INI_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// file exceeds MAX_FILE_SIZE value
|
||||
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_FORM_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody($contents),
|
||||
$size,
|
||||
\UPLOAD_ERR_OK,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
private function parsePost($name, $value)
|
||||
{
|
||||
// ignore excessive number of post fields
|
||||
if (++$this->postCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withParsedBody($this->extractPost(
|
||||
$this->request->getParsedBody(),
|
||||
$name,
|
||||
$value
|
||||
));
|
||||
|
||||
if (\strtoupper($name) === 'MAX_FILE_SIZE') {
|
||||
$this->maxFileSize = (int)$value;
|
||||
|
||||
if ($this->maxFileSize === 0) {
|
||||
$this->maxFileSize = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseHeaders($header)
|
||||
{
|
||||
$headers = array();
|
||||
|
||||
foreach (\explode("\r\n", \trim($header)) as $line) {
|
||||
$parts = \explode(':', $line, 2);
|
||||
if (!isset($parts[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = \strtolower(trim($parts[0]));
|
||||
$values = \explode(';', $parts[1]);
|
||||
$values = \array_map('trim', $values);
|
||||
$headers[$key] = $values;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function getParameterFromHeader(array $header, $parameter)
|
||||
{
|
||||
foreach ($header as $part) {
|
||||
if (\preg_match('/' . $parameter . '="?(.*?)"?$/', $part, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function extractPost($postFields, $key, $value)
|
||||
{
|
||||
$chunks = \explode('[', $key);
|
||||
if (\count($chunks) == 1) {
|
||||
$postFields[$key] = $value;
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
// ignore this key if maximum nesting level is exceeded
|
||||
if (isset($chunks[$this->maxInputNestingLevel])) {
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[0], ']');
|
||||
$parent = &$postFields;
|
||||
for ($i = 1; isset($chunks[$i]); $i++) {
|
||||
$previousChunkKey = $chunkKey;
|
||||
|
||||
if ($previousChunkKey === '') {
|
||||
$parent[] = array();
|
||||
\end($parent);
|
||||
$parent = &$parent[\key($parent)];
|
||||
} else {
|
||||
if (!isset($parent[$previousChunkKey]) || !\is_array($parent[$previousChunkKey])) {
|
||||
$parent[$previousChunkKey] = array();
|
||||
}
|
||||
$parent = &$parent[$previousChunkKey];
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[$i], ']');
|
||||
}
|
||||
|
||||
if ($chunkKey === '') {
|
||||
$parent[] = $value;
|
||||
} else {
|
||||
$parent[$chunkKey] = $value;
|
||||
}
|
||||
|
||||
return $postFields;
|
||||
}
|
||||
}
|
||||
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Normal file
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Pauses a given stream and buffers all events while paused
|
||||
*
|
||||
* This class is used to buffer all events that happen on a given stream while
|
||||
* it is paused. This allows you to pause a stream and no longer watch for any
|
||||
* of its events. Once the stream is resumed, all buffered events will be
|
||||
* emitted. Explicitly closing the resulting stream clears all buffers.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about.
|
||||
*
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class PauseBufferStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
private $dataPaused = '';
|
||||
private $endPaused = false;
|
||||
private $closePaused = false;
|
||||
private $errorPaused;
|
||||
private $implicit = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'handleClose'));
|
||||
}
|
||||
|
||||
/**
|
||||
* pause and remember this was not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function pauseImplicit()
|
||||
{
|
||||
$this->pause();
|
||||
$this->implicit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* resume only if this was previously paused implicitly and not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function resumeImplicit()
|
||||
{
|
||||
if ($this->implicit) {
|
||||
$this->resume();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->input->pause();
|
||||
$this->paused = true;
|
||||
$this->implicit = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->implicit = false;
|
||||
|
||||
if ($this->dataPaused !== '') {
|
||||
$this->emit('data', array($this->dataPaused));
|
||||
$this->dataPaused = '';
|
||||
}
|
||||
|
||||
if ($this->errorPaused) {
|
||||
$this->emit('error', array($this->errorPaused));
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->endPaused) {
|
||||
$this->endPaused = false;
|
||||
$this->emit('end');
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->closePaused) {
|
||||
$this->closePaused = false;
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->dataPaused = '';
|
||||
$this->endPaused = $this->closePaused = false;
|
||||
$this->errorPaused = null;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->dataPaused .= $data;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->errorPaused = $e;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->endPaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleClose()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->closePaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Normal file
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $position = 0;
|
||||
private $size;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $size = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$that = $this;
|
||||
$pos =& $this->position;
|
||||
$input->on('data', function ($data) use ($that, &$pos, $size) {
|
||||
$that->emit('data', array($data));
|
||||
|
||||
$pos += \strlen($data);
|
||||
if ($size !== null && $pos >= $size) {
|
||||
$that->handleEnd();
|
||||
}
|
||||
});
|
||||
$input->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
});
|
||||
$input->on('end', array($that, 'handleEnd'));
|
||||
$input->on('close', array($that, 'close'));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->isReadable();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->position !== $this->size && $this->size !== null) {
|
||||
$this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
|
||||
} else {
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Normal file
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Parses an incoming request header from an input stream
|
||||
*
|
||||
* This is used internally to parse the request header from the connection and
|
||||
* then process the remaining connection as the request body.
|
||||
*
|
||||
* @event headers
|
||||
* @event error
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RequestHeaderParser extends EventEmitter
|
||||
{
|
||||
private $maxSize = 8192;
|
||||
|
||||
/** @var Clock */
|
||||
private $clock;
|
||||
|
||||
/** @var array<string|int,array<string,string>> */
|
||||
private $connectionParams = array();
|
||||
|
||||
public function __construct(Clock $clock)
|
||||
{
|
||||
$this->clock = $clock;
|
||||
}
|
||||
|
||||
public function handle(ConnectionInterface $conn)
|
||||
{
|
||||
$buffer = '';
|
||||
$maxSize = $this->maxSize;
|
||||
$that = $this;
|
||||
$conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) {
|
||||
// append chunk of data to buffer and look for end of request headers
|
||||
$buffer .= $data;
|
||||
$endOfHeader = \strpos($buffer, "\r\n\r\n");
|
||||
|
||||
// reject request if buffer size is exceeded
|
||||
if ($endOfHeader > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) {
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
$that->emit('error', array(
|
||||
new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE),
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore incomplete requests
|
||||
if ($endOfHeader === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// request headers received => try to parse request
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
try {
|
||||
$request = $that->parseRequest(
|
||||
(string)\substr($buffer, 0, $endOfHeader + 2),
|
||||
$conn
|
||||
);
|
||||
} catch (Exception $exception) {
|
||||
$buffer = '';
|
||||
$that->emit('error', array(
|
||||
$exception,
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$contentLength = 0;
|
||||
if ($request->hasHeader('Transfer-Encoding')) {
|
||||
$contentLength = null;
|
||||
} elseif ($request->hasHeader('Content-Length')) {
|
||||
$contentLength = (int)$request->getHeaderLine('Content-Length');
|
||||
}
|
||||
|
||||
if ($contentLength === 0) {
|
||||
// happy path: request body is known to be empty
|
||||
$stream = new EmptyBodyStream();
|
||||
$request = $request->withBody($stream);
|
||||
} else {
|
||||
// otherwise body is present => delimit using Content-Length or ChunkedDecoder
|
||||
$stream = new CloseProtectionStream($conn);
|
||||
if ($contentLength !== null) {
|
||||
$stream = new LengthLimitedStream($stream, $contentLength);
|
||||
} else {
|
||||
$stream = new ChunkedDecoder($stream);
|
||||
}
|
||||
|
||||
$request = $request->withBody(new HttpBodyStream($stream, $contentLength));
|
||||
}
|
||||
|
||||
$bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : '';
|
||||
$buffer = '';
|
||||
$that->emit('headers', array($request, $conn));
|
||||
|
||||
if ($bodyBuffer !== '') {
|
||||
$conn->emit('data', array($bodyBuffer));
|
||||
}
|
||||
|
||||
// happy path: request body is known to be empty => immediately end stream
|
||||
if ($contentLength === 0) {
|
||||
$stream->emit('end');
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $headers buffer string containing request headers only
|
||||
* @param ConnectionInterface $connection
|
||||
* @return ServerRequestInterface
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public function parseRequest($headers, ConnectionInterface $connection)
|
||||
{
|
||||
// reuse same connection params for all server params for this connection
|
||||
$cid = \PHP_VERSION_ID < 70200 ? \spl_object_hash($connection) : \spl_object_id($connection);
|
||||
if (isset($this->connectionParams[$cid])) {
|
||||
$serverParams = $this->connectionParams[$cid];
|
||||
} else {
|
||||
// assign new server params for new connection
|
||||
$serverParams = array();
|
||||
|
||||
// scheme is `http` unless TLS is used
|
||||
$localSocketUri = $connection->getLocalAddress();
|
||||
$localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri);
|
||||
if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') {
|
||||
$serverParams['HTTPS'] = 'on';
|
||||
}
|
||||
|
||||
// apply SERVER_ADDR and SERVER_PORT if server address is known
|
||||
// address should always be known, even for Unix domain sockets (UDS)
|
||||
// but skip UDS as it doesn't have a concept of host/port.
|
||||
if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) {
|
||||
$serverParams['SERVER_ADDR'] = $localParts['host'];
|
||||
$serverParams['SERVER_PORT'] = $localParts['port'];
|
||||
}
|
||||
|
||||
// apply REMOTE_ADDR and REMOTE_PORT if source address is known
|
||||
// address should always be known, unless this is over Unix domain sockets (UDS)
|
||||
$remoteSocketUri = $connection->getRemoteAddress();
|
||||
if ($remoteSocketUri !== null) {
|
||||
$remoteAddress = \parse_url($remoteSocketUri);
|
||||
$serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
|
||||
$serverParams['REMOTE_PORT'] = $remoteAddress['port'];
|
||||
}
|
||||
|
||||
// remember server params for all requests from this connection, reset on connection close
|
||||
$this->connectionParams[$cid] = $serverParams;
|
||||
$params =& $this->connectionParams;
|
||||
$connection->on('close', function () use (&$params, $cid) {
|
||||
assert(\is_array($params));
|
||||
unset($params[$cid]);
|
||||
});
|
||||
}
|
||||
|
||||
// create new obj implementing ServerRequestInterface by preserving all
|
||||
// previous properties and restoring original request-target
|
||||
$serverParams['REQUEST_TIME'] = (int) ($now = $this->clock->now());
|
||||
$serverParams['REQUEST_TIME_FLOAT'] = $now;
|
||||
|
||||
return ServerRequest::parseMessage($headers, $serverParams);
|
||||
}
|
||||
}
|
||||
152
vendor/react/http/src/Io/Sender.php
vendored
Normal file
152
vendor/react/http/src/Io/Sender.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Client\Client as HttpClient;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Sends requests and receives responses
|
||||
*
|
||||
* The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
|
||||
* the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
|
||||
* and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
|
||||
*
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
|
||||
*
|
||||
* The `Sender` class mostly exists in order to abstract changes on the underlying
|
||||
* components away from this package in order to provide backwards and forwards
|
||||
* compatibility.
|
||||
*
|
||||
* @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
|
||||
* @see Browser
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
/**
|
||||
* create a new default sender attached to the given event loop
|
||||
*
|
||||
* This method is used internally to create the "default sender".
|
||||
*
|
||||
* You may also use this method if you need custom DNS or connector
|
||||
* settings. You can use this method manually like this:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector(array(), $loop);
|
||||
* $sender = \React\Http\Io\Sender::createFromLoop($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector)
|
||||
{
|
||||
return new self(new HttpClient(new ClientConnectionManager($connector, $loop)));
|
||||
}
|
||||
|
||||
private $http;
|
||||
|
||||
/**
|
||||
* [internal] Instantiate Sender
|
||||
*
|
||||
* @param HttpClient $http
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(HttpClient $http)
|
||||
{
|
||||
$this->http = $http;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
// support HTTP/1.1 and HTTP/1.0 only, ensured by `Browser` already
|
||||
assert(\in_array($request->getProtocolVersion(), array('1.0', '1.1'), true));
|
||||
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size !== 0) {
|
||||
// automatically assign a "Content-Length" request header if the body size is known and non-empty
|
||||
$request = $request->withHeader('Content-Length', (string)$size);
|
||||
} elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
|
||||
// only assign a "Content-Length: 0" request header if the body is expected for certain methods
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
} elseif ($body instanceof ReadableStreamInterface && $size !== 0 && $body->isReadable() && !$request->hasHeader('Content-Length')) {
|
||||
// use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
|
||||
$request = $request->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// do not use chunked encoding if size is known or if this is an empty request body
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
// automatically add `Authorization: Basic …` request header if URL includes `user:pass@host`
|
||||
if ($request->getUri()->getUserInfo() !== '' && !$request->hasHeader('Authorization')) {
|
||||
$request = $request->withHeader('Authorization', 'Basic ' . \base64_encode($request->getUri()->getUserInfo()));
|
||||
}
|
||||
|
||||
$requestStream = $this->http->request($request);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($requestStream) {
|
||||
// close request stream if request is cancelled
|
||||
$reject(new \RuntimeException('Request cancelled'));
|
||||
$requestStream->close();
|
||||
});
|
||||
|
||||
$requestStream->on('error', function($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
$requestStream->on('response', function (ResponseInterface $response) use ($deferred, $request) {
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
if ($body->isReadable()) {
|
||||
// length unknown => apply chunked transfer-encoding
|
||||
if ($size === null) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// pipe body into request stream
|
||||
// add dummy write to immediately start request even if body does not emit any data yet
|
||||
$body->pipe($requestStream);
|
||||
$requestStream->write('');
|
||||
|
||||
$body->on('close', $close = function () use ($deferred, $requestStream) {
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('end', function () use ($close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
});
|
||||
} else {
|
||||
// stream is not readable => end request without body
|
||||
$requestStream->end();
|
||||
}
|
||||
} else {
|
||||
// body is fully buffered => write as one chunk
|
||||
$requestStream->end((string)$body);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
405
vendor/react/http/src/Io/StreamingServer.php
vendored
Normal file
405
vendor/react/http/src/Io/StreamingServer.php
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* The internal `StreamingServer` class is responsible for handling incoming connections and then
|
||||
* processing each incoming HTTP request.
|
||||
*
|
||||
* Unlike the [`HttpServer`](#httpserver) class, it does not buffer and parse the incoming
|
||||
* HTTP request body by default. This means that the request handler will be
|
||||
* invoked with a streaming request body. Once the request headers have been
|
||||
* received, it will invoke the request handler function. This request handler
|
||||
* function needs to be passed to the constructor and will be invoked with the
|
||||
* respective [request](#request) object and expects a [response](#response)
|
||||
* object in return:
|
||||
*
|
||||
* ```php
|
||||
* $server = new StreamingServer($loop, function (ServerRequestInterface $request) {
|
||||
* return new Response(
|
||||
* Response::STATUS_OK,
|
||||
* array(
|
||||
* 'Content-Type' => 'text/plain'
|
||||
* ),
|
||||
* "Hello World!\n"
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Each incoming HTTP request message is always represented by the
|
||||
* [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
|
||||
* see also following [request](#request) chapter for more details.
|
||||
* Each outgoing HTTP response message is always represented by the
|
||||
* [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
|
||||
* see also following [response](#response) chapter for more details.
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` through the [`listen()`](#listen) method
|
||||
* as described in the following chapter. In its most simple form, you can attach
|
||||
* this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $server = new StreamingServer($loop, $handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080', array(), $loop);
|
||||
* $server->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also the [`listen()`](#listen) method and the [first example](examples) for more details.
|
||||
*
|
||||
* The `StreamingServer` class is considered advanced usage and unless you know
|
||||
* what you're doing, you're recommended to use the [`HttpServer`](#httpserver) class
|
||||
* instead. The `StreamingServer` class is specifically designed to help with
|
||||
* more advanced use cases where you want to have full control over consuming
|
||||
* the incoming HTTP request body and concurrency settings.
|
||||
*
|
||||
* In particular, this class does not buffer and parse the incoming HTTP request
|
||||
* in memory. It will invoke the request handler function once the HTTP request
|
||||
* headers have been received, i.e. before receiving the potentially much larger
|
||||
* HTTP request body. This means the [request](#request) passed to your request
|
||||
* handler function may not be fully compatible with PSR-7. See also
|
||||
* [streaming request](#streaming-request) below for more details.
|
||||
*
|
||||
* @see \React\Http\HttpServer
|
||||
* @see \React\Http\Message\Response
|
||||
* @see self::listen()
|
||||
* @internal
|
||||
*/
|
||||
final class StreamingServer extends EventEmitter
|
||||
{
|
||||
private $callback;
|
||||
private $parser;
|
||||
|
||||
/** @var Clock */
|
||||
private $clock;
|
||||
|
||||
/**
|
||||
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` which emits underlying streaming
|
||||
* connections in order to then parse incoming data as HTTP.
|
||||
* See also [listen()](#listen) for more details.
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param callable $requestHandler
|
||||
* @see self::listen()
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, $requestHandler)
|
||||
{
|
||||
if (!\is_callable($requestHandler)) {
|
||||
throw new \InvalidArgumentException('Invalid request handler given');
|
||||
}
|
||||
|
||||
$this->callback = $requestHandler;
|
||||
$this->clock = new Clock($loop);
|
||||
$this->parser = new RequestHeaderParser($this->clock);
|
||||
|
||||
$that = $this;
|
||||
$this->parser->on('headers', function (ServerRequestInterface $request, ConnectionInterface $conn) use ($that) {
|
||||
$that->handleRequest($conn, $request);
|
||||
});
|
||||
|
||||
$this->parser->on('error', function(\Exception $e, ConnectionInterface $conn) use ($that) {
|
||||
$that->emit('error', array($e));
|
||||
|
||||
// parsing failed => assume dummy request and send appropriate error
|
||||
$that->writeError(
|
||||
$conn,
|
||||
$e->getCode() !== 0 ? $e->getCode() : Response::STATUS_BAD_REQUEST,
|
||||
new ServerRequest('GET', '/')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for HTTP requests on the given socket server instance
|
||||
*
|
||||
* @param ServerInterface $socket
|
||||
* @see \React\Http\HttpServer::listen()
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$socket->on('connection', array($this->parser, 'handle'));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $request)
|
||||
{
|
||||
if ($request->getProtocolVersion() !== '1.0' && '100-continue' === \strtolower($request->getHeaderLine('Expect'))) {
|
||||
$conn->write("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
}
|
||||
|
||||
// execute request handler callback
|
||||
$callback = $this->callback;
|
||||
try {
|
||||
$response = $callback($request);
|
||||
} catch (\Exception $error) {
|
||||
// request handler callback throws an Exception
|
||||
$response = Promise\reject($error);
|
||||
} catch (\Throwable $error) { // @codeCoverageIgnoreStart
|
||||
// request handler callback throws a PHP7+ Error
|
||||
$response = Promise\reject($error); // @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// cancel pending promise once connection closes
|
||||
$connectionOnCloseResponseCancelerHandler = function () {};
|
||||
if ($response instanceof PromiseInterface && \method_exists($response, 'cancel')) {
|
||||
$connectionOnCloseResponseCanceler = function () use ($response) {
|
||||
$response->cancel();
|
||||
};
|
||||
$connectionOnCloseResponseCancelerHandler = function () use ($connectionOnCloseResponseCanceler, $conn) {
|
||||
if ($connectionOnCloseResponseCanceler !== null) {
|
||||
$conn->removeListener('close', $connectionOnCloseResponseCanceler);
|
||||
}
|
||||
};
|
||||
$conn->on('close', $connectionOnCloseResponseCanceler);
|
||||
}
|
||||
|
||||
// happy path: response returned, handle and return immediately
|
||||
if ($response instanceof ResponseInterface) {
|
||||
return $this->handleResponse($conn, $request, $response);
|
||||
}
|
||||
|
||||
// did not return a promise? this is an error, convert into one for rejection below.
|
||||
if (!$response instanceof PromiseInterface) {
|
||||
$response = Promise\resolve($response);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$response->then(
|
||||
function ($response) use ($that, $conn, $request) {
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but resolved with "%s" instead.';
|
||||
$message = \sprintf($message, \is_object($response) ? \get_class($response) : \gettype($response));
|
||||
$exception = new \RuntimeException($message);
|
||||
|
||||
$that->emit('error', array($exception));
|
||||
return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
}
|
||||
$that->handleResponse($conn, $request, $response);
|
||||
},
|
||||
function ($error) use ($that, $conn, $request) {
|
||||
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but rejected with "%s" instead.';
|
||||
$message = \sprintf($message, \is_object($error) ? \get_class($error) : \gettype($error));
|
||||
|
||||
$previous = null;
|
||||
|
||||
if ($error instanceof \Throwable || $error instanceof \Exception) {
|
||||
$previous = $error;
|
||||
}
|
||||
|
||||
$exception = new \RuntimeException($message, 0, $previous);
|
||||
|
||||
$that->emit('error', array($exception));
|
||||
return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
}
|
||||
)->then($connectionOnCloseResponseCancelerHandler, $connectionOnCloseResponseCancelerHandler);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function writeError(ConnectionInterface $conn, $code, ServerRequestInterface $request)
|
||||
{
|
||||
$response = new Response(
|
||||
$code,
|
||||
array(
|
||||
'Content-Type' => 'text/plain',
|
||||
'Connection' => 'close' // we do not want to keep the connection open after an error
|
||||
),
|
||||
'Error ' . $code
|
||||
);
|
||||
|
||||
// append reason phrase to response body if known
|
||||
$reason = $response->getReasonPhrase();
|
||||
if ($reason !== '') {
|
||||
$body = $response->getBody();
|
||||
$body->seek(0, SEEK_END);
|
||||
$body->write(': ' . $reason);
|
||||
}
|
||||
|
||||
$this->handleResponse($conn, $request, $response);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function handleResponse(ConnectionInterface $connection, ServerRequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
// return early and close response body if connection is already closed
|
||||
$body = $response->getBody();
|
||||
if (!$connection->isWritable()) {
|
||||
$body->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$code = $response->getStatusCode();
|
||||
$method = $request->getMethod();
|
||||
|
||||
// assign HTTP protocol version from request automatically
|
||||
$version = $request->getProtocolVersion();
|
||||
$response = $response->withProtocolVersion($version);
|
||||
|
||||
// assign default "Server" header automatically
|
||||
if (!$response->hasHeader('Server')) {
|
||||
$response = $response->withHeader('Server', 'ReactPHP/1');
|
||||
} elseif ($response->getHeaderLine('Server') === ''){
|
||||
$response = $response->withoutHeader('Server');
|
||||
}
|
||||
|
||||
// assign default "Date" header from current time automatically
|
||||
if (!$response->hasHeader('Date')) {
|
||||
// IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
|
||||
$response = $response->withHeader('Date', gmdate('D, d M Y H:i:s', (int) $this->clock->now()) . ' GMT');
|
||||
} elseif ($response->getHeaderLine('Date') === ''){
|
||||
$response = $response->withoutHeader('Date');
|
||||
}
|
||||
|
||||
// assign "Content-Length" header automatically
|
||||
$chunked = false;
|
||||
if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === Response::STATUS_NO_CONTENT) {
|
||||
// 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header
|
||||
$response = $response->withoutHeader('Content-Length');
|
||||
} elseif ($method === 'HEAD' && $response->hasHeader('Content-Length')) {
|
||||
// HEAD Request: preserve explicit Content-Length
|
||||
} elseif ($code === Response::STATUS_NOT_MODIFIED && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) {
|
||||
// 304 Not Modified: preserve explicit Content-Length and preserve missing header if body is empty
|
||||
} elseif ($body->getSize() !== null) {
|
||||
// assign Content-Length header when using a "normal" buffered body string
|
||||
$response = $response->withHeader('Content-Length', (string)$body->getSize());
|
||||
} elseif (!$response->hasHeader('Content-Length') && $version === '1.1') {
|
||||
// assign chunked transfer-encoding if no 'content-length' is given for HTTP/1.1 responses
|
||||
$chunked = true;
|
||||
}
|
||||
|
||||
// assign "Transfer-Encoding" header automatically
|
||||
if ($chunked) {
|
||||
$response = $response->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// remove any Transfer-Encoding headers unless automatically enabled above
|
||||
$response = $response->withoutHeader('Transfer-Encoding');
|
||||
}
|
||||
|
||||
// assign "Connection" header automatically
|
||||
$persist = false;
|
||||
if ($code === Response::STATUS_SWITCHING_PROTOCOLS) {
|
||||
// 101 (Switching Protocols) response uses Connection: upgrade header
|
||||
// This implies that this stream now uses another protocol and we
|
||||
// may not persist this connection for additional requests.
|
||||
$response = $response->withHeader('Connection', 'upgrade');
|
||||
} elseif (\strtolower($request->getHeaderLine('Connection')) === 'close' || \strtolower($response->getHeaderLine('Connection')) === 'close') {
|
||||
// obey explicit "Connection: close" request header or response header if present
|
||||
$response = $response->withHeader('Connection', 'close');
|
||||
} elseif ($version === '1.1') {
|
||||
// HTTP/1.1 assumes persistent connection support by default, so we don't need to inform client
|
||||
$persist = true;
|
||||
} elseif (strtolower($request->getHeaderLine('Connection')) === 'keep-alive') {
|
||||
// obey explicit "Connection: keep-alive" request header and inform client
|
||||
$persist = true;
|
||||
$response = $response->withHeader('Connection', 'keep-alive');
|
||||
} else {
|
||||
// remove any Connection headers unless automatically enabled above
|
||||
$response = $response->withoutHeader('Connection');
|
||||
}
|
||||
|
||||
// 101 (Switching Protocols) response (for Upgrade request) forwards upgraded data through duplex stream
|
||||
// 2xx (Successful) response to CONNECT forwards tunneled application data through duplex stream
|
||||
if (($code === Response::STATUS_SWITCHING_PROTOCOLS || ($method === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) {
|
||||
if ($request->getBody()->isReadable()) {
|
||||
// request is still streaming => wait for request close before forwarding following data from connection
|
||||
$request->getBody()->on('close', function () use ($connection, $body) {
|
||||
if ($body->input->isWritable()) {
|
||||
$connection->pipe($body->input);
|
||||
$connection->resume();
|
||||
}
|
||||
});
|
||||
} elseif ($body->input->isWritable()) {
|
||||
// request already closed => forward following data from connection
|
||||
$connection->pipe($body->input);
|
||||
$connection->resume();
|
||||
}
|
||||
}
|
||||
|
||||
// build HTTP response header by appending status line and header fields
|
||||
$expected = 0;
|
||||
$headers = "HTTP/" . $version . " " . $code . " " . $response->getReasonPhrase() . "\r\n";
|
||||
foreach ($response->getHeaders() as $name => $values) {
|
||||
if (\strpos($name, ':') !== false) {
|
||||
$expected = -1;
|
||||
break;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers .= $name . ": " . $value . "\r\n";
|
||||
++$expected;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $m legacy PHP 5.3 only */
|
||||
if ($code < 100 || $code > 999 || \substr_count($headers, "\n") !== ($expected + 1) || (\PHP_VERSION_ID >= 50400 ? \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers) : \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers, $m)) !== $expected) {
|
||||
$this->emit('error', array(new \InvalidArgumentException('Unable to send response with invalid response headers')));
|
||||
$this->writeError($connection, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
return;
|
||||
}
|
||||
|
||||
// response to HEAD and 1xx, 204 and 304 responses MUST NOT include a body
|
||||
// exclude status 101 (Switching Protocols) here for Upgrade request handling above
|
||||
if ($method === 'HEAD' || ($code >= 100 && $code < 200 && $code !== Response::STATUS_SWITCHING_PROTOCOLS) || $code === Response::STATUS_NO_CONTENT || $code === Response::STATUS_NOT_MODIFIED) {
|
||||
$body->close();
|
||||
$body = '';
|
||||
}
|
||||
|
||||
// this is a non-streaming response body or the body stream already closed?
|
||||
if (!$body instanceof ReadableStreamInterface || !$body->isReadable()) {
|
||||
// add final chunk if a streaming body is already closed and uses `Transfer-Encoding: chunked`
|
||||
if ($body instanceof ReadableStreamInterface && $chunked) {
|
||||
$body = "0\r\n\r\n";
|
||||
}
|
||||
|
||||
// write response headers and body
|
||||
$connection->write($headers . "\r\n" . $body);
|
||||
|
||||
// either wait for next request over persistent connection or end connection
|
||||
if ($persist) {
|
||||
$this->parser->handle($connection);
|
||||
} else {
|
||||
$connection->end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->write($headers . "\r\n");
|
||||
|
||||
if ($chunked) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// Close response stream once connection closes.
|
||||
// Note that this TCP/IP close detection may take some time,
|
||||
// in particular this may only fire on a later read/write attempt.
|
||||
$connection->on('close', array($body, 'close'));
|
||||
|
||||
// write streaming body and then wait for next request over persistent connection
|
||||
if ($persist) {
|
||||
$body->pipe($connection, array('end' => false));
|
||||
$parser = $this->parser;
|
||||
$body->on('end', function () use ($connection, $parser, $body) {
|
||||
$connection->removeListener('close', array($body, 'close'));
|
||||
$parser->handle($connection);
|
||||
});
|
||||
} else {
|
||||
$body->pipe($connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
330
vendor/react/http/src/Io/Transaction.php
vendored
Normal file
330
vendor/react/http/src/Io/Transaction.php
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ResponseException;
|
||||
use React\Http\Message\Uri;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Transaction
|
||||
{
|
||||
private $sender;
|
||||
private $loop;
|
||||
|
||||
// context: http.timeout (ini_get('default_socket_timeout'): 60)
|
||||
private $timeout;
|
||||
|
||||
// context: http.follow_location (true)
|
||||
private $followRedirects = true;
|
||||
|
||||
// context: http.max_redirects (10)
|
||||
private $maxRedirects = 10;
|
||||
|
||||
// context: http.ignore_errors (false)
|
||||
private $obeySuccessCode = true;
|
||||
|
||||
private $streaming = false;
|
||||
|
||||
private $maximumSize = 16777216; // 16 MiB = 2^24 bytes
|
||||
|
||||
public function __construct(Sender $sender, LoopInterface $loop)
|
||||
{
|
||||
$this->sender = $sender;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return self returns new instance, without modifying existing instance
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$transaction = clone $this;
|
||||
foreach ($options as $name => $value) {
|
||||
if (property_exists($transaction, $name)) {
|
||||
// restore default value if null is given
|
||||
if ($value === null) {
|
||||
$default = new self($this->sender, $this->loop);
|
||||
$value = $default->$name;
|
||||
}
|
||||
|
||||
$transaction->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$state = new ClientRequestState();
|
||||
$deferred = new Deferred(function () use ($state) {
|
||||
if ($state->pending !== null) {
|
||||
$state->pending->cancel();
|
||||
$state->pending = null;
|
||||
}
|
||||
});
|
||||
|
||||
// use timeout from options or default to PHP's default_socket_timeout (60)
|
||||
$timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout"));
|
||||
|
||||
$loop = $this->loop;
|
||||
$this->next($request, $deferred, $state)->then(
|
||||
function (ResponseInterface $response) use ($state, $deferred, $loop, &$timeout) {
|
||||
if ($state->timeout !== null) {
|
||||
$loop->cancelTimer($state->timeout);
|
||||
$state->timeout = null;
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->resolve($response);
|
||||
},
|
||||
function ($e) use ($state, $deferred, $loop, &$timeout) {
|
||||
if ($state->timeout !== null) {
|
||||
$loop->cancelTimer($state->timeout);
|
||||
$state->timeout = null;
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
$body = $request->getBody();
|
||||
if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
|
||||
$that = $this;
|
||||
$body->on('close', function () use ($that, $deferred, $state, &$timeout) {
|
||||
if ($timeout >= 0) {
|
||||
$that->applyTimeout($deferred, $state, $timeout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->applyTimeout($deferred, $state, $timeout);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param number $timeout
|
||||
* @return void
|
||||
*/
|
||||
public function applyTimeout(Deferred $deferred, ClientRequestState $state, $timeout)
|
||||
{
|
||||
$state->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred, $state) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'Request timed out after ' . $timeout . ' seconds'
|
||||
));
|
||||
if ($state->pending !== null) {
|
||||
$state->pending->cancel();
|
||||
$state->pending = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function next(RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$this->progress('request', array($request));
|
||||
|
||||
$that = $this;
|
||||
++$state->numRequests;
|
||||
|
||||
$promise = $this->sender->send($request);
|
||||
|
||||
if (!$this->streaming) {
|
||||
$promise = $promise->then(function ($response) use ($deferred, $state, $that) {
|
||||
return $that->bufferResponse($response, $deferred, $state);
|
||||
});
|
||||
}
|
||||
|
||||
$state->pending = $promise;
|
||||
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($request, $that, $deferred, $state) {
|
||||
return $that->onResponse($response, $request, $deferred, $state);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function bufferResponse(ResponseInterface $response, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$body = $response->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size > $this->maximumSize) {
|
||||
$body->close();
|
||||
return \React\Promise\reject(new \OverflowException(
|
||||
'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
|
||||
// body is not streaming => already buffered
|
||||
if (!$body instanceof ReadableStreamInterface) {
|
||||
return \React\Promise\resolve($response);
|
||||
}
|
||||
|
||||
/** @var ?\Closure $closer */
|
||||
$closer = null;
|
||||
$maximumSize = $this->maximumSize;
|
||||
|
||||
return $state->pending = new Promise(function ($resolve, $reject) use ($body, $maximumSize, $response, &$closer) {
|
||||
// resolve with current buffer when stream closes successfully
|
||||
$buffer = '';
|
||||
$body->on('close', $closer = function () use (&$buffer, $response, $maximumSize, $resolve, $reject) {
|
||||
$resolve($response->withBody(new BufferedBody($buffer)));
|
||||
});
|
||||
|
||||
// buffer response body data in memory
|
||||
$body->on('data', function ($data) use (&$buffer, $maximumSize, $body, $closer, $reject) {
|
||||
$buffer .= $data;
|
||||
|
||||
// close stream and reject promise if limit is exceeded
|
||||
if (isset($buffer[$maximumSize])) {
|
||||
$buffer = '';
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
$reject(new \OverflowException(
|
||||
'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// reject buffering if body emits error
|
||||
$body->on('error', function (\Exception $e) use ($reject) {
|
||||
$reject(new \RuntimeException(
|
||||
'Error while buffering response body: ' . $e->getMessage(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
});
|
||||
}, function () use ($body, &$closer) {
|
||||
// cancelled buffering: remove close handler to avoid resolving, then close and reject
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
throw new \RuntimeException('Cancelled buffering response body');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @throws ResponseException
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function onResponse(ResponseInterface $response, RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$this->progress('response', array($response, $request));
|
||||
|
||||
// follow 3xx (Redirection) response status codes if Location header is present and not explicitly disabled
|
||||
// @link https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
if ($this->followRedirects && ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) && $response->hasHeader('Location')) {
|
||||
return $this->onResponseRedirect($response, $request, $deferred, $state);
|
||||
}
|
||||
|
||||
// only status codes 200-399 are considered to be valid, reject otherwise
|
||||
if ($this->obeySuccessCode && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400)) {
|
||||
throw new ResponseException($response);
|
||||
}
|
||||
|
||||
// resolve our initial promise
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @param Deferred $deferred
|
||||
* @param ClientRequestState $state
|
||||
* @return PromiseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
// resolve location relative to last request URI
|
||||
$location = Uri::resolve($request->getUri(), new Uri($response->getHeaderLine('Location')));
|
||||
|
||||
$request = $this->makeRedirectRequest($request, $location, $response->getStatusCode());
|
||||
$this->progress('redirect', array($request));
|
||||
|
||||
if ($state->numRequests >= $this->maxRedirects) {
|
||||
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
|
||||
}
|
||||
|
||||
return $this->next($request, $deferred, $state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param UriInterface $location
|
||||
* @param int $statusCode
|
||||
* @return RequestInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function makeRedirectRequest(RequestInterface $request, UriInterface $location, $statusCode)
|
||||
{
|
||||
// Remove authorization if changing hostnames (but not if just changing ports or protocols).
|
||||
$originalHost = $request->getUri()->getHost();
|
||||
if ($location->getHost() !== $originalHost) {
|
||||
$request = $request->withoutHeader('Authorization');
|
||||
}
|
||||
|
||||
$request = $request->withoutHeader('Host')->withUri($location);
|
||||
|
||||
if ($statusCode === Response::STATUS_TEMPORARY_REDIRECT || $statusCode === Response::STATUS_PERMANENT_REDIRECT) {
|
||||
if ($request->getBody() instanceof ReadableStreamInterface) {
|
||||
throw new \RuntimeException('Unable to redirect request with streaming body');
|
||||
}
|
||||
} else {
|
||||
$request = $request
|
||||
->withMethod($request->getMethod() === 'HEAD' ? 'HEAD' : 'GET')
|
||||
->withoutHeader('Content-Type')
|
||||
->withoutHeader('Content-Length')
|
||||
->withBody(new BufferedBody(''));
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function progress($name, array $args = array())
|
||||
{
|
||||
return;
|
||||
|
||||
echo $name;
|
||||
|
||||
foreach ($args as $arg) {
|
||||
echo ' ';
|
||||
if ($arg instanceof ResponseInterface) {
|
||||
echo 'HTTP/' . $arg->getProtocolVersion() . ' ' . $arg->getStatusCode() . ' ' . $arg->getReasonPhrase();
|
||||
} elseif ($arg instanceof RequestInterface) {
|
||||
echo $arg->getMethod() . ' ' . $arg->getRequestTarget() . ' HTTP/' . $arg->getProtocolVersion();
|
||||
} else {
|
||||
echo $arg;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
130
vendor/react/http/src/Io/UploadedFile.php
vendored
Normal file
130
vendor/react/http/src/Io/UploadedFile.php
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* [Internal] Implementation of the PSR-7 `UploadedFileInterface`
|
||||
*
|
||||
* This is used internally to represent each incoming file upload.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `UploadedFileInterface` for more details.
|
||||
*
|
||||
* @see UploadedFileInterface
|
||||
* @internal
|
||||
*/
|
||||
final class UploadedFile implements UploadedFileInterface
|
||||
{
|
||||
/**
|
||||
* @var StreamInterface
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $mediaType;
|
||||
|
||||
/**
|
||||
* @param StreamInterface $stream
|
||||
* @param int $size
|
||||
* @param int $error
|
||||
* @param string $filename
|
||||
* @param string $mediaType
|
||||
*/
|
||||
public function __construct(StreamInterface $stream, $size, $error, $filename, $mediaType)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->size = $size;
|
||||
|
||||
if (!\is_int($error) || !\in_array($error, array(
|
||||
\UPLOAD_ERR_OK,
|
||||
\UPLOAD_ERR_INI_SIZE,
|
||||
\UPLOAD_ERR_FORM_SIZE,
|
||||
\UPLOAD_ERR_PARTIAL,
|
||||
\UPLOAD_ERR_NO_FILE,
|
||||
\UPLOAD_ERR_NO_TMP_DIR,
|
||||
\UPLOAD_ERR_CANT_WRITE,
|
||||
\UPLOAD_ERR_EXTENSION,
|
||||
))) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid error code, must be an UPLOAD_ERR_* constant'
|
||||
);
|
||||
}
|
||||
$this->error = $error;
|
||||
$this->filename = $filename;
|
||||
$this->mediaType = $mediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
if ($this->error !== \UPLOAD_ERR_OK) {
|
||||
throw new RuntimeException('Cannot retrieve stream due to upload error');
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function moveTo($targetPath)
|
||||
{
|
||||
throw new RuntimeException('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClientFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClientMediaType()
|
||||
{
|
||||
return $this->mediaType;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user