summaryrefslogtreecommitdiffstats
path: root/vendor/react/http/src/Io
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/react/http/src/Io')
-rw-r--r--vendor/react/http/src/Io/BufferedBody.php179
-rw-r--r--vendor/react/http/src/Io/ChunkedDecoder.php175
-rw-r--r--vendor/react/http/src/Io/ChunkedEncoder.php92
-rw-r--r--vendor/react/http/src/Io/CloseProtectionStream.php111
-rw-r--r--vendor/react/http/src/Io/EmptyBodyStream.php142
-rw-r--r--vendor/react/http/src/Io/HttpBodyStream.php182
-rw-r--r--vendor/react/http/src/Io/IniUtil.php48
-rw-r--r--vendor/react/http/src/Io/LengthLimitedStream.php108
-rw-r--r--vendor/react/http/src/Io/MiddlewareRunner.php61
-rw-r--r--vendor/react/http/src/Io/MultipartParser.php328
-rw-r--r--vendor/react/http/src/Io/PauseBufferStream.php188
-rw-r--r--vendor/react/http/src/Io/ReadableBodyStream.php153
-rw-r--r--vendor/react/http/src/Io/RequestHeaderParser.php281
-rw-r--r--vendor/react/http/src/Io/Sender.php160
-rw-r--r--vendor/react/http/src/Io/StreamingServer.php383
-rw-r--r--vendor/react/http/src/Io/Transaction.php303
-rw-r--r--vendor/react/http/src/Io/UploadedFile.php130
17 files changed, 3024 insertions, 0 deletions
diff --git a/vendor/react/http/src/Io/BufferedBody.php b/vendor/react/http/src/Io/BufferedBody.php
new file mode 100644
index 0000000..4a4d839
--- /dev/null
+++ b/vendor/react/http/src/Io/BufferedBody.php
@@ -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;
+ }
+}
diff --git a/vendor/react/http/src/Io/ChunkedDecoder.php b/vendor/react/http/src/Io/ChunkedDecoder.php
new file mode 100644
index 0000000..2f58f42
--- /dev/null
+++ b/vendor/react/http/src/Io/ChunkedDecoder.php
@@ -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;
+ }
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/ChunkedEncoder.php b/vendor/react/http/src/Io/ChunkedEncoder.php
new file mode 100644
index 0000000..c84ef54
--- /dev/null
+++ b/vendor/react/http/src/Io/ChunkedEncoder.php
@@ -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();
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/CloseProtectionStream.php b/vendor/react/http/src/Io/CloseProtectionStream.php
new file mode 100644
index 0000000..2e1ed6e
--- /dev/null
+++ b/vendor/react/http/src/Io/CloseProtectionStream.php
@@ -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));
+ }
+}
diff --git a/vendor/react/http/src/Io/EmptyBodyStream.php b/vendor/react/http/src/Io/EmptyBodyStream.php
new file mode 100644
index 0000000..5056219
--- /dev/null
+++ b/vendor/react/http/src/Io/EmptyBodyStream.php
@@ -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;
+ }
+}
diff --git a/vendor/react/http/src/Io/HttpBodyStream.php b/vendor/react/http/src/Io/HttpBodyStream.php
new file mode 100644
index 0000000..25d15a1
--- /dev/null
+++ b/vendor/react/http/src/Io/HttpBodyStream.php
@@ -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();
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/IniUtil.php b/vendor/react/http/src/Io/IniUtil.php
new file mode 100644
index 0000000..612aae2
--- /dev/null
+++ b/vendor/react/http/src/Io/IniUtil.php
@@ -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;
+ }
+}
diff --git a/vendor/react/http/src/Io/LengthLimitedStream.php b/vendor/react/http/src/Io/LengthLimitedStream.php
new file mode 100644
index 0000000..bc64c54
--- /dev/null
+++ b/vendor/react/http/src/Io/LengthLimitedStream.php
@@ -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'));
+ }
+ }
+
+}
diff --git a/vendor/react/http/src/Io/MiddlewareRunner.php b/vendor/react/http/src/Io/MiddlewareRunner.php
new file mode 100644
index 0000000..dedf6ff
--- /dev/null
+++ b/vendor/react/http/src/Io/MiddlewareRunner.php
@@ -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);
+ }
+}
diff --git a/vendor/react/http/src/Io/MultipartParser.php b/vendor/react/http/src/Io/MultipartParser.php
new file mode 100644
index 0000000..536694f
--- /dev/null
+++ b/vendor/react/http/src/Io/MultipartParser.php
@@ -0,0 +1,328 @@
+<?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;
+
+ /**
+ * 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 $postCount = 0;
+ private $filesCount = 0;
+ private $emptyCount = 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;
+ }
+
+ 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->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)
+ $start = \strpos($buffer, $boundary . "\r\n");
+
+ while ($start !== false) {
+ // search following boundary (preceded by newline)
+ // ignore last if not followed by boundary (SHOULD end with "--")
+ $start += $len + 2;
+ $end = \strpos($buffer, "\r\n" . $boundary, $start);
+ if ($end === false) {
+ break;
+ }
+
+ // parse one part and continue searching for next
+ $this->parsePart(\substr($buffer, $start, $end - $start));
+ $start = $end;
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/vendor/react/http/src/Io/PauseBufferStream.php b/vendor/react/http/src/Io/PauseBufferStream.php
new file mode 100644
index 0000000..fb5ed45
--- /dev/null
+++ b/vendor/react/http/src/Io/PauseBufferStream.php
@@ -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();
+ }
+}
diff --git a/vendor/react/http/src/Io/ReadableBodyStream.php b/vendor/react/http/src/Io/ReadableBodyStream.php
new file mode 100644
index 0000000..daef45f
--- /dev/null
+++ b/vendor/react/http/src/Io/ReadableBodyStream.php
@@ -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();
+ }
+}
diff --git a/vendor/react/http/src/Io/RequestHeaderParser.php b/vendor/react/http/src/Io/RequestHeaderParser.php
new file mode 100644
index 0000000..e5554c4
--- /dev/null
+++ b/vendor/react/http/src/Io/RequestHeaderParser.php
@@ -0,0 +1,281 @@
+<?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;
+
+ 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->getRemoteAddress(),
+ $conn->getLocalAddress()
+ );
+ } 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 ?string $remoteSocketUri
+ * @param ?string $localSocketUri
+ * @return ServerRequestInterface
+ * @throws \InvalidArgumentException
+ * @internal
+ */
+ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
+ {
+ // additional, stricter safe-guard for request line
+ // because request parser doesn't properly cope with invalid ones
+ $start = array();
+ if (!\preg_match('#^(?<method>[^ ]+) (?<target>[^ ]+) HTTP/(?<version>\d\.\d)#m', $headers, $start)) {
+ throw new \InvalidArgumentException('Unable to parse invalid request-line');
+ }
+
+ // only support HTTP/1.1 and HTTP/1.0 requests
+ if ($start['version'] !== '1.1' && $start['version'] !== '1.0') {
+ throw new \InvalidArgumentException('Received request with invalid protocol version', Response::STATUS_VERSION_NOT_SUPPORTED);
+ }
+
+ // match all request header fields into array, thanks to @kelunik for checking the HTTP specs and coming up with this regex
+ $matches = array();
+ $n = \preg_match_all('/^([^()<>@,;:\\\"\/\[\]?={}\x01-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m', $headers, $matches, \PREG_SET_ORDER);
+
+ // check number of valid header fields matches number of lines + request line
+ if (\substr_count($headers, "\n") !== $n + 1) {
+ throw new \InvalidArgumentException('Unable to parse invalid request header fields');
+ }
+
+ // format all header fields into associative array
+ $host = null;
+ $fields = array();
+ foreach ($matches as $match) {
+ $fields[$match[1]][] = $match[2];
+
+ // match `Host` request header
+ if ($host === null && \strtolower($match[1]) === 'host') {
+ $host = $match[2];
+ }
+ }
+
+ // create new obj implementing ServerRequestInterface by preserving all
+ // previous properties and restoring original request-target
+ $serverParams = array(
+ 'REQUEST_TIME' => \time(),
+ 'REQUEST_TIME_FLOAT' => \microtime(true)
+ );
+
+ // scheme is `http` unless TLS is used
+ $localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri);
+ if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') {
+ $scheme = 'https://';
+ $serverParams['HTTPS'] = 'on';
+ } else {
+ $scheme = 'http://';
+ }
+
+ // default host if unset comes from local socket address or defaults to localhost
+ $hasHost = $host !== null;
+ if ($host === null) {
+ $host = isset($localParts['host'], $localParts['port']) ? $localParts['host'] . ':' . $localParts['port'] : '127.0.0.1';
+ }
+
+ if ($start['method'] === 'OPTIONS' && $start['target'] === '*') {
+ // support asterisk-form for `OPTIONS *` request line only
+ $uri = $scheme . $host;
+ } elseif ($start['method'] === 'CONNECT') {
+ $parts = \parse_url('tcp://' . $start['target']);
+
+ // check this is a valid authority-form request-target (host:port)
+ if (!isset($parts['scheme'], $parts['host'], $parts['port']) || \count($parts) !== 3) {
+ throw new \InvalidArgumentException('CONNECT method MUST use authority-form request target');
+ }
+ $uri = $scheme . $start['target'];
+ } else {
+ // support absolute-form or origin-form for proxy requests
+ if ($start['target'][0] === '/') {
+ $uri = $scheme . $host . $start['target'];
+ } else {
+ // ensure absolute-form request-target contains a valid URI
+ $parts = \parse_url($start['target']);
+
+ // make sure value contains valid host component (IP or hostname), but no fragment
+ if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) {
+ throw new \InvalidArgumentException('Invalid absolute-form request-target');
+ }
+
+ $uri = $start['target'];
+ }
+ }
+
+ // apply REMOTE_ADDR and REMOTE_PORT if source address is known
+ // address should always be known, unless this is over Unix domain sockets (UDS)
+ if ($remoteSocketUri !== null) {
+ $remoteAddress = \parse_url($remoteSocketUri);
+ $serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
+ $serverParams['REMOTE_PORT'] = $remoteAddress['port'];
+ }
+
+ // 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'];
+ }
+
+ $request = new ServerRequest(
+ $start['method'],
+ $uri,
+ $fields,
+ '',
+ $start['version'],
+ $serverParams
+ );
+
+ // only assign request target if it is not in origin-form (happy path for most normal requests)
+ if ($start['target'][0] !== '/') {
+ $request = $request->withRequestTarget($start['target']);
+ }
+
+ if ($hasHost) {
+ // Optional Host request header value MUST be valid (host and optional port)
+ $parts = \parse_url('http://' . $request->getHeaderLine('Host'));
+
+ // make sure value contains valid host component (IP or hostname)
+ if (!$parts || !isset($parts['scheme'], $parts['host'])) {
+ $parts = false;
+ }
+
+ // make sure value does not contain any other URI component
+ if (\is_array($parts)) {
+ unset($parts['scheme'], $parts['host'], $parts['port']);
+ }
+ if ($parts === false || $parts) {
+ throw new \InvalidArgumentException('Invalid Host header value');
+ }
+ } elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') {
+ // require Host request header for HTTP/1.1 (except for CONNECT method)
+ throw new \InvalidArgumentException('Missing required Host request header');
+ } elseif (!$hasHost) {
+ // remove default Host request header for HTTP/1.0 when not explicitly given
+ $request = $request->withoutHeader('Host');
+ }
+
+ // ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
+ if ($request->hasHeader('Transfer-Encoding')) {
+ if (\strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
+ throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', Response::STATUS_NOT_IMPLEMENTED);
+ }
+
+ // Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
+ // as per https://tools.ietf.org/html/rfc7230#section-3.3.3
+ if ($request->hasHeader('Content-Length')) {
+ throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', Response::STATUS_BAD_REQUEST);
+ }
+ } elseif ($request->hasHeader('Content-Length')) {
+ $string = $request->getHeaderLine('Content-Length');
+
+ if ((string)(int)$string !== $string) {
+ // Content-Length value is not an integer or not a single integer
+ throw new \InvalidArgumentException('The value of `Content-Length` is not valid', Response::STATUS_BAD_REQUEST);
+ }
+ }
+
+ return $request;
+ }
+}
diff --git a/vendor/react/http/src/Io/Sender.php b/vendor/react/http/src/Io/Sender.php
new file mode 100644
index 0000000..2f04c79
--- /dev/null
+++ b/vendor/react/http/src/Io/Sender.php
@@ -0,0 +1,160 @@
+<?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\Http\Message\Response;
+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 = null)
+ {
+ return new self(new HttpClient($loop, $connector));
+ }
+
+ 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)
+ {
+ $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 && $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;
+ }
+
+ $headers = array();
+ foreach ($request->getHeaders() as $name => $values) {
+ $headers[$name] = implode(', ', $values);
+ }
+
+ $requestStream = $this->http->request($request->getMethod(), (string)$request->getUri(), $headers, $request->getProtocolVersion());
+
+ $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, ReadableStreamInterface $body) use ($deferred, $request) {
+ $length = null;
+ $code = $response->getStatusCode();
+ if ($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');
+ }
+
+ $deferred->resolve($response->withBody(new ReadableBodyStream($body, $length)));
+ });
+
+ 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();
+ }
+}
diff --git a/vendor/react/http/src/Io/StreamingServer.php b/vendor/react/http/src/Io/StreamingServer.php
new file mode 100644
index 0000000..7818f0b
--- /dev/null
+++ b/vendor/react/http/src/Io/StreamingServer.php
@@ -0,0 +1,383 @@
+<?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\CancellablePromiseInterface;
+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;
+ private $loop;
+
+ /**
+ * 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->loop = $loop;
+
+ $this->callback = $requestHandler;
+ $this->parser = new RequestHeaderParser();
+
+ $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
+ if ($response instanceof CancellablePromiseInterface) {
+ $conn->on('close', function () use ($response) {
+ $response->cancel();
+ });
+ }
+
+ // 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);
+ }
+ );
+ }
+
+ /** @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') . ' 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 ($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
+ $headers = "HTTP/" . $version . " " . $code . " " . $response->getReasonPhrase() . "\r\n";
+ foreach ($response->getHeaders() as $name => $values) {
+ foreach ($values as $value) {
+ $headers .= $name . ": " . $value . "\r\n";
+ }
+ }
+
+ // 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);
+ }
+ }
+}
diff --git a/vendor/react/http/src/Io/Transaction.php b/vendor/react/http/src/Io/Transaction.php
new file mode 100644
index 0000000..7bf7008
--- /dev/null
+++ b/vendor/react/http/src/Io/Transaction.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace React\Http\Io;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\UriInterface;
+use RingCentral\Psr7\Request;
+use RingCentral\Psr7\Uri;
+use React\EventLoop\LoopInterface;
+use React\Http\Message\ResponseException;
+use React\Promise\Deferred;
+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)
+ {
+ $deferred = new Deferred(function () use (&$deferred) {
+ if (isset($deferred->pending)) {
+ $deferred->pending->cancel();
+ unset($deferred->pending);
+ }
+ });
+
+ $deferred->numRequests = 0;
+
+ // 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)->then(
+ function (ResponseInterface $response) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $timeout = -1;
+ $deferred->resolve($response);
+ },
+ function ($e) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $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, &$timeout) {
+ if ($timeout >= 0) {
+ $that->applyTimeout($deferred, $timeout);
+ }
+ });
+ } else {
+ $this->applyTimeout($deferred, $timeout);
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @internal
+ * @param Deferred $deferred
+ * @param number $timeout
+ * @return void
+ */
+ public function applyTimeout(Deferred $deferred, $timeout)
+ {
+ $deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) {
+ $deferred->reject(new \RuntimeException(
+ 'Request timed out after ' . $timeout . ' seconds'
+ ));
+ if (isset($deferred->pending)) {
+ $deferred->pending->cancel();
+ unset($deferred->pending);
+ }
+ });
+ }
+
+ private function next(RequestInterface $request, Deferred $deferred)
+ {
+ $this->progress('request', array($request));
+
+ $that = $this;
+ ++$deferred->numRequests;
+
+ $promise = $this->sender->send($request);
+
+ if (!$this->streaming) {
+ $promise = $promise->then(function ($response) use ($deferred, $that) {
+ return $that->bufferResponse($response, $deferred);
+ });
+ }
+
+ $deferred->pending = $promise;
+
+ return $promise->then(
+ function (ResponseInterface $response) use ($request, $that, $deferred) {
+ return $that->onResponse($response, $request, $deferred);
+ }
+ );
+ }
+
+ /**
+ * @internal
+ * @param ResponseInterface $response
+ * @return PromiseInterface Promise<ResponseInterface, Exception>
+ */
+ public function bufferResponse(ResponseInterface $response, $deferred)
+ {
+ $stream = $response->getBody();
+
+ $size = $stream->getSize();
+ if ($size !== null && $size > $this->maximumSize) {
+ $stream->close();
+ return \React\Promise\reject(new \OverflowException(
+ 'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ ));
+ }
+
+ // body is not streaming => already buffered
+ if (!$stream instanceof ReadableStreamInterface) {
+ return \React\Promise\resolve($response);
+ }
+
+ // buffer stream and resolve with buffered body
+ $maximumSize = $this->maximumSize;
+ $promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then(
+ function ($body) use ($response) {
+ return $response->withBody(new BufferedBody($body));
+ },
+ function ($e) use ($stream, $maximumSize) {
+ // try to close stream if buffering fails (or is cancelled)
+ $stream->close();
+
+ if ($e instanceof \OverflowException) {
+ $e = new \OverflowException(
+ 'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ );
+ }
+
+ throw $e;
+ }
+ );
+
+ $deferred->pending = $promise;
+
+ return $promise;
+ }
+
+ /**
+ * @internal
+ * @param ResponseInterface $response
+ * @param RequestInterface $request
+ * @throws ResponseException
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred)
+ {
+ $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);
+ }
+
+ // 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
+ * @return PromiseInterface
+ * @throws \RuntimeException
+ */
+ private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred)
+ {
+ // resolve location relative to last request URI
+ $location = Uri::resolve($request->getUri(), $response->getHeaderLine('Location'));
+
+ $request = $this->makeRedirectRequest($request, $location);
+ $this->progress('redirect', array($request));
+
+ if ($deferred->numRequests >= $this->maxRedirects) {
+ throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
+ }
+
+ return $this->next($request, $deferred);
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param UriInterface $location
+ * @return RequestInterface
+ */
+ private function makeRedirectRequest(RequestInterface $request, UriInterface $location)
+ {
+ $originalHost = $request->getUri()->getHost();
+ $request = $request
+ ->withoutHeader('Host')
+ ->withoutHeader('Content-Type')
+ ->withoutHeader('Content-Length');
+
+ // Remove authorization if changing hostnames (but not if just changing ports or protocols).
+ if ($location->getHost() !== $originalHost) {
+ $request = $request->withoutHeader('Authorization');
+ }
+
+ // naïve approach..
+ $method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
+
+ return new Request($method, $location, $request->getHeaders());
+ }
+
+ 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;
+ }
+}
diff --git a/vendor/react/http/src/Io/UploadedFile.php b/vendor/react/http/src/Io/UploadedFile.php
new file mode 100644
index 0000000..f2a6c9e
--- /dev/null
+++ b/vendor/react/http/src/Io/UploadedFile.php
@@ -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;
+ }
+}