mirror of
https://github.com/itflow-org/itflow
synced 2026-09-21 22:21:15 +00:00
Update imapengine dependancies too
This commit is contained in:
90
libs/vendor/guzzlehttp/psr7/src/AppendStream.php
vendored
90
libs/vendor/guzzlehttp/psr7/src/AppendStream.php
vendored
@@ -13,17 +13,16 @@ use Psr\Http\Message\StreamInterface;
|
||||
*/
|
||||
final class AppendStream implements StreamInterface
|
||||
{
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var StreamInterface[] Streams being decorated */
|
||||
private $streams = [];
|
||||
private array $streams = [];
|
||||
|
||||
/** @var bool */
|
||||
private $seekable = true;
|
||||
private bool $seekable = true;
|
||||
|
||||
/** @var int */
|
||||
private $current = 0;
|
||||
private int $current = 0;
|
||||
|
||||
/** @var int */
|
||||
private $pos = 0;
|
||||
private int $pos = 0;
|
||||
|
||||
/**
|
||||
* @param StreamInterface[] $streams Streams to decorate. Each stream must
|
||||
@@ -38,18 +37,9 @@ final class AppendStream implements StreamInterface
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
$this->rewind();
|
||||
$this->rewind();
|
||||
|
||||
return $this->getContents();
|
||||
} catch (\Throwable $e) {
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||
|
||||
return '';
|
||||
}
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +122,7 @@ final class AppendStream implements StreamInterface
|
||||
if ($s === null) {
|
||||
return null;
|
||||
}
|
||||
$size += $s;
|
||||
$size = Integers::add($size, $s);
|
||||
}
|
||||
|
||||
return $size;
|
||||
@@ -153,26 +143,8 @@ final class AppendStream implements StreamInterface
|
||||
/**
|
||||
* Attempts to seek to the given position. Only supports SEEK_SET.
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->seekable) {
|
||||
throw new \RuntimeException('This AppendStream is not seekable');
|
||||
} elseif ($whence !== SEEK_SET) {
|
||||
@@ -203,15 +175,10 @@ final class AppendStream implements StreamInterface
|
||||
/**
|
||||
* Reads from all of the appended streams until the length is met or EOF.
|
||||
*/
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
if ($this->streams === []) {
|
||||
@@ -233,7 +200,7 @@ final class AppendStream implements StreamInterface
|
||||
++$this->current;
|
||||
}
|
||||
|
||||
$result = $this->streams[$this->current]->read($remaining);
|
||||
$result = StreamTimeout::read($this->streams[$this->current], $remaining, 'Unable to read from stream: timed out');
|
||||
|
||||
if ($result === '') {
|
||||
$progressToNext = true;
|
||||
@@ -244,7 +211,7 @@ final class AppendStream implements StreamInterface
|
||||
$remaining = $length - strlen($buffer);
|
||||
}
|
||||
|
||||
$this->pos += strlen($buffer);
|
||||
$this->pos = Integers::add($this->pos, strlen($buffer));
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
@@ -264,34 +231,13 @@ final class AppendStream implements StreamInterface
|
||||
return $this->seekable;
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Cannot write to an AppendStream');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null): ?array
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
}
|
||||
|
||||
return $key ? null : [];
|
||||
return $key === null ? [] : null;
|
||||
}
|
||||
}
|
||||
|
||||
71
libs/vendor/guzzlehttp/psr7/src/BufferStream.php
vendored
71
libs/vendor/guzzlehttp/psr7/src/BufferStream.php
vendored
@@ -16,22 +16,22 @@ use Psr\Http\Message\StreamInterface;
|
||||
*/
|
||||
final class BufferStream implements StreamInterface
|
||||
{
|
||||
/** @var int */
|
||||
private $hwm;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
private int $hwm;
|
||||
|
||||
private string $buffer = '';
|
||||
|
||||
/**
|
||||
* @param int $hwm High water mark, representing the preferred maximum
|
||||
* buffer size. If the size of the buffer exceeds the high
|
||||
* water mark, then calls to write will continue to succeed
|
||||
* but will return 0 to inform writers to slow down
|
||||
* buffer size. If the size of the buffer reaches or exceeds
|
||||
* the high water mark, then calls to write will continue to
|
||||
* succeed but will return 0 to inform writers to slow down
|
||||
* until the buffer has been drained by reading from it.
|
||||
*/
|
||||
public function __construct(int $hwm = 16384)
|
||||
{
|
||||
$this->hwm = $hwm;
|
||||
$this->hwm = Integers::assertNonNegativeInteger($hwm, 'High water mark');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
@@ -84,26 +84,8 @@ final class BufferStream implements StreamInterface
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Cannot seek a BufferStream');
|
||||
}
|
||||
|
||||
@@ -120,15 +102,10 @@ final class BufferStream implements StreamInterface
|
||||
/**
|
||||
* Reads data from the buffer.
|
||||
*/
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
$currentLength = strlen($this->buffer);
|
||||
@@ -149,17 +126,8 @@ final class BufferStream implements StreamInterface
|
||||
/**
|
||||
* Writes data to the buffer.
|
||||
*/
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
$this->buffer .= $string;
|
||||
|
||||
if (strlen($this->buffer) >= $this->hwm) {
|
||||
@@ -172,21 +140,12 @@ final class BufferStream implements StreamInterface
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null)
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
}
|
||||
|
||||
if ($key === 'hwm') {
|
||||
return $this->hwm;
|
||||
}
|
||||
|
||||
return $key ? null : [];
|
||||
return $key === null ? [] : null;
|
||||
}
|
||||
}
|
||||
|
||||
123
libs/vendor/guzzlehttp/psr7/src/CachingStream.php
vendored
123
libs/vendor/guzzlehttp/psr7/src/CachingStream.php
vendored
@@ -13,26 +13,30 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class CachingStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var StreamInterface Stream being wrapped */
|
||||
private $remoteStream;
|
||||
private StreamInterface $remoteStream;
|
||||
|
||||
/** @var int Number of bytes to skip reading due to a write on the buffer */
|
||||
private $skipReadBytes = 0;
|
||||
private int $skipReadBytes = 0;
|
||||
|
||||
/**
|
||||
* @var StreamInterface
|
||||
*/
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
/** @var bool */
|
||||
private $detached = false;
|
||||
private bool $detached = false;
|
||||
|
||||
private bool $closed = false;
|
||||
|
||||
/**
|
||||
* We will treat the buffer object as the body of the stream
|
||||
*
|
||||
* @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream.
|
||||
* @param StreamInterface $target Optionally specify where data is cached
|
||||
* @param StreamInterface $target Optionally specify where data is cached. Defaults to a "php://temp"
|
||||
* stream. A custom target is used as a random-access byte buffer to
|
||||
* replay the remote stream, so it must be readable, writable, and
|
||||
* seekable, report an accurate position and size, and store writes
|
||||
* losslessly. Lossy or non-seekable streams such as BufferStream and
|
||||
* DroppingStream are not valid targets.
|
||||
*/
|
||||
public function __construct(
|
||||
StreamInterface $stream,
|
||||
@@ -62,36 +66,31 @@ final class CachingStream implements StreamInterface
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
if ($whence === SEEK_SET) {
|
||||
$byte = $offset;
|
||||
} elseif ($whence === SEEK_CUR) {
|
||||
$byte = $offset + $this->tell();
|
||||
$byte = Integers::addSigned($this->tell(), $offset);
|
||||
} elseif ($whence === SEEK_END) {
|
||||
$size = $this->remoteStream->getSize();
|
||||
if ($size === null) {
|
||||
// Discovering the size reads the remote stream to EOF and
|
||||
// moves the cursor, so restore the cursor if the computed
|
||||
// target is rejected to keep a failed seek side-effect free.
|
||||
$position = $this->tell();
|
||||
$size = $this->cacheEntireStream();
|
||||
|
||||
try {
|
||||
$byte = Integers::addSigned($size, $offset);
|
||||
} catch (\Throwable $e) {
|
||||
$this->stream->seek($position);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
$byte = Integers::addSigned($size, $offset);
|
||||
}
|
||||
$byte = $size + $offset;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid whence');
|
||||
}
|
||||
@@ -119,15 +118,10 @@ final class CachingStream implements StreamInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
// Perform a regular read on any previously read data from the buffer
|
||||
@@ -140,8 +134,10 @@ final class CachingStream implements StreamInterface
|
||||
// been filled from the remote stream, then we must skip bytes on
|
||||
// the remote stream to emulate overwriting bytes from that
|
||||
// position. This mimics the behavior of other PHP stream wrappers.
|
||||
$remoteData = $this->remoteStream->read(
|
||||
$remaining + $this->skipReadBytes
|
||||
$remoteData = StreamTimeout::read(
|
||||
$this->remoteStream,
|
||||
Integers::add($remaining, $this->skipReadBytes),
|
||||
'Unable to read from stream: timed out'
|
||||
);
|
||||
|
||||
if ($this->skipReadBytes) {
|
||||
@@ -161,24 +157,15 @@ final class CachingStream implements StreamInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
// When appending to the end of the currently read stream, you'll want
|
||||
// to skip bytes from being read from the remote stream to emulate
|
||||
// other stream wrappers. Basically replacing bytes of data of a fixed
|
||||
// length.
|
||||
$overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
|
||||
$overflow = Integers::add(strlen($string), $this->tell()) - $this->remoteStream->tell();
|
||||
if ($overflow > 0) {
|
||||
$this->skipReadBytes += $overflow;
|
||||
$this->skipReadBytes = Integers::add($this->skipReadBytes, $overflow);
|
||||
}
|
||||
|
||||
return $this->stream->write($string);
|
||||
@@ -207,13 +194,39 @@ final class CachingStream implements StreamInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Close both the remote stream and buffer stream
|
||||
* Close the remote stream and any attached cache stream.
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->remoteStream->close();
|
||||
$this->stream->close();
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$closeCache = !$this->detached;
|
||||
$this->closed = true;
|
||||
$this->detached = true;
|
||||
|
||||
$exception = null;
|
||||
|
||||
try {
|
||||
$this->remoteStream->close();
|
||||
} catch (\Throwable $e) {
|
||||
$exception = $e;
|
||||
}
|
||||
|
||||
if ($closeCache) {
|
||||
try {
|
||||
$this->stream->close();
|
||||
} catch (\Throwable $e) {
|
||||
if ($exception === null) {
|
||||
$exception = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($exception !== null) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
private function cacheEntireStream(): int
|
||||
|
||||
63
libs/vendor/guzzlehttp/psr7/src/DiagnosticValue.php
vendored
Normal file
63
libs/vendor/guzzlehttp/psr7/src/DiagnosticValue.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* Escapes control characters and malformed UTF-8 for use in diagnostics.
|
||||
*/
|
||||
final class DiagnosticValue
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes C0, DEL, and C1 controls as uppercase `\xNN` sequences.
|
||||
*
|
||||
* ASCII bytes from 0x20 through 0x7E and valid UTF-8 characters outside
|
||||
* those control ranges remain unchanged. If the input is malformed UTF-8 or
|
||||
* PCRE cannot process it, every byte outside printable ASCII is escaped.
|
||||
* Valid C1 characters are rendered as `\xNN` using their Unicode code
|
||||
* points. During bytewise fallback, each original byte outside printable
|
||||
* ASCII is rendered in the same form. The result is diagnostic text, not a
|
||||
* reversible encoding.
|
||||
*
|
||||
* This does not encode values for HTML, JSON, shells, terminals, URLs, or
|
||||
* protocol fields.
|
||||
*/
|
||||
public static function escape(string $value): string
|
||||
{
|
||||
$escaped = \preg_replace_callback(
|
||||
'/[\x{0000}-\x{001F}\x{007F}-\x{009F}]/u',
|
||||
static function (array $matches): string {
|
||||
$character = $matches[0];
|
||||
$codePoint = \strlen($character) === 1 ? \ord($character) : \ord($character[1]);
|
||||
|
||||
return \sprintf('\\x%02X', $codePoint);
|
||||
},
|
||||
$value
|
||||
);
|
||||
|
||||
return $escaped ?? self::escapeBytes($value);
|
||||
}
|
||||
|
||||
private static function escapeBytes(string $value): string
|
||||
{
|
||||
$escaped = '';
|
||||
|
||||
for ($offset = 0, $length = \strlen($value); $offset < $length; ++$offset) {
|
||||
$byte = \ord($value[$offset]);
|
||||
if ($byte >= 0x20 && $byte <= 0x7E) {
|
||||
$escaped .= $value[$offset];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$escaped .= \sprintf('\\x%02X', $byte);
|
||||
}
|
||||
|
||||
return $escaped;
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,11 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class DroppingStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var int */
|
||||
private $maxLength;
|
||||
private int $maxLength;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
/**
|
||||
* @param StreamInterface $stream Underlying stream to decorate.
|
||||
@@ -27,20 +26,11 @@ final class DroppingStream implements StreamInterface
|
||||
public function __construct(StreamInterface $stream, int $maxLength)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->maxLength = $maxLength;
|
||||
$this->maxLength = Integers::assertNonNegativeInteger($maxLength, 'Maximum length');
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
$diff = $this->maxLength - $this->stream->getSize();
|
||||
|
||||
// Begin returning 0 when the underlying stream is too large.
|
||||
|
||||
14
libs/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php
vendored
Normal file
14
libs/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown when a stream operation times out.
|
||||
*/
|
||||
class TimeoutException extends RuntimeException
|
||||
{
|
||||
}
|
||||
153
libs/vendor/guzzlehttp/psr7/src/FnStream.php
vendored
153
libs/vendor/guzzlehttp/psr7/src/FnStream.php
vendored
@@ -15,6 +15,8 @@ use Psr\Http\Message\StreamInterface;
|
||||
#[\AllowDynamicProperties]
|
||||
final class FnStream implements StreamInterface
|
||||
{
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
private const SLOTS = [
|
||||
'__toString', 'close', 'detach', 'rewind',
|
||||
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
|
||||
@@ -22,7 +24,9 @@ final class FnStream implements StreamInterface
|
||||
];
|
||||
|
||||
/** @var array<string, callable> */
|
||||
private $methods;
|
||||
private array $methods;
|
||||
|
||||
private bool $detached = false;
|
||||
|
||||
/**
|
||||
* @param array<string, callable> $methods Hash of method name to a callable.
|
||||
@@ -44,8 +48,7 @@ final class FnStream implements StreamInterface
|
||||
*/
|
||||
public function __get(string $name): void
|
||||
{
|
||||
throw new \BadMethodCallException(str_replace('_fn_', '', $name)
|
||||
.'() is not implemented in the FnStream');
|
||||
throw new \BadMethodCallException(\sprintf('%s() is not implemented in the FnStream', DiagnosticValue::escape(str_replace('_fn_', '', $name))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +56,14 @@ final class FnStream implements StreamInterface
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_fn_close)) {
|
||||
($this->_fn_close)();
|
||||
if ($this->detached || !isset($this->_fn_close)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->close();
|
||||
} catch (\Throwable $e) {
|
||||
// Destructors must not surface cleanup failures.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +74,18 @@ final class FnStream implements StreamInterface
|
||||
*/
|
||||
public function __wakeup(): void
|
||||
{
|
||||
throw new \LogicException('FnStream should never be unserialized');
|
||||
$this->methods = [];
|
||||
$this->detached = true;
|
||||
|
||||
throw new \LogicException(static::class.' should never be unserialized');
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->methods = [];
|
||||
$this->detached = true;
|
||||
|
||||
throw new \LogicException(static::class.' should never be unserialized');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,10 +94,8 @@ final class FnStream implements StreamInterface
|
||||
*
|
||||
* @param StreamInterface $stream Stream to decorate
|
||||
* @param array<string, callable> $methods Hash of method name to a callable
|
||||
*
|
||||
* @return FnStream
|
||||
*/
|
||||
public static function decorate(StreamInterface $stream, array $methods)
|
||||
public static function decorate(StreamInterface $stream, array $methods): self
|
||||
{
|
||||
// If any of the required methods were not provided, then simply
|
||||
// proxy to the decorated stream.
|
||||
@@ -92,110 +110,113 @@ final class FnStream implements StreamInterface
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
/** @var string */
|
||||
return ($this->_fn___toString)();
|
||||
} catch (\Throwable $e) {
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||
$this->assertAttached();
|
||||
|
||||
return '';
|
||||
}
|
||||
/** @var string */
|
||||
return ($this->_fn___toString)();
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
($this->_fn_close)();
|
||||
if ($this->detached) {
|
||||
return;
|
||||
}
|
||||
|
||||
$close = $this->_fn_close;
|
||||
$this->detached = true;
|
||||
$close();
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
return ($this->_fn_detach)();
|
||||
if ($this->detached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$detach = $this->_fn_detach;
|
||||
$result = $detach();
|
||||
$this->detached = true;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSize(): ?int
|
||||
{
|
||||
if ($this->detached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ($this->_fn_getSize)();
|
||||
}
|
||||
|
||||
public function tell(): int
|
||||
{
|
||||
$this->assertAttached();
|
||||
|
||||
return ($this->_fn_tell)();
|
||||
}
|
||||
|
||||
public function eof(): bool
|
||||
{
|
||||
$this->assertAttached();
|
||||
|
||||
return ($this->_fn_eof)();
|
||||
}
|
||||
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
if ($this->detached) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->_fn_isSeekable)();
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->assertAttached();
|
||||
|
||||
($this->_fn_rewind)();
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
$this->assertAttached();
|
||||
|
||||
($this->_fn_seek)($offset, $whence);
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
if ($this->detached) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->_fn_isWritable)();
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
$this->assertAttached();
|
||||
|
||||
return ($this->_fn_write)($string);
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
if ($this->detached) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->_fn_isReadable)();
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
$this->assertAttached();
|
||||
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
return ($this->_fn_read)($length);
|
||||
@@ -203,23 +224,27 @@ final class FnStream implements StreamInterface
|
||||
|
||||
public function getContents(): string
|
||||
{
|
||||
$this->assertAttached();
|
||||
|
||||
return ($this->_fn_getContents)();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null)
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
if ($this->detached) {
|
||||
return $key === null ? [] : null;
|
||||
}
|
||||
|
||||
return ($this->_fn_getMetadata)($key);
|
||||
}
|
||||
|
||||
private function assertAttached(): void
|
||||
{
|
||||
if ($this->detached) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
libs/vendor/guzzlehttp/psr7/src/Header.php
vendored
58
libs/vendor/guzzlehttp/psr7/src/Header.php
vendored
@@ -6,11 +6,14 @@ namespace GuzzleHttp\Psr7;
|
||||
|
||||
final class Header
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of header values containing ";" separated data into an
|
||||
* array of associative arrays representing the header key value pair data
|
||||
* of the header. When a parameter does not contain a value, but just
|
||||
* contains a key, this function will inject a key with a '' string value.
|
||||
* Parses semicolon-separated header parameters into associative arrays, one
|
||||
* per comma-separated header value. Parameters without a value are appended
|
||||
* as values under integer keys.
|
||||
*
|
||||
* @param string|array $header Header to parse into components.
|
||||
*/
|
||||
@@ -23,7 +26,13 @@ final class Header
|
||||
foreach (self::splitList($value) as $val) {
|
||||
$part = [];
|
||||
foreach (self::splitParameters($val) as $kvp) {
|
||||
if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
|
||||
$count = preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches);
|
||||
|
||||
if ($count === false) {
|
||||
throw new \RuntimeException('Unable to parse header parameters: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($count !== 0) {
|
||||
$m = $matches[0];
|
||||
if (isset($m[1])) {
|
||||
$part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
|
||||
@@ -86,37 +95,16 @@ final class Header
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of header values that may contain comma separated
|
||||
* headers into an array of headers with no comma separated values.
|
||||
* Splits an HTTP header defined to contain a comma-separated list into each
|
||||
* individual value. Empty values are removed.
|
||||
*
|
||||
* @param string|array $header Header to normalize.
|
||||
* Example headers include `accept`, `cache-control`, and `if-none-match`.
|
||||
*
|
||||
* @deprecated Use self::splitList() instead.
|
||||
*/
|
||||
public static function normalize($header): array
|
||||
{
|
||||
\trigger_deprecation('guzzlehttp/psr7', '2.3', 'Header::normalize() is deprecated and will be removed in guzzlehttp/psr7 3.0. Use Header::splitList() instead.');
|
||||
|
||||
$result = [];
|
||||
foreach ((array) $header as $value) {
|
||||
foreach (self::splitList($value) as $parsed) {
|
||||
$result[] = $parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a HTTP header defined to contain a comma-separated list into
|
||||
* each individual value. Empty values will be removed.
|
||||
* This method must not be used to parse headers that are not defined as a
|
||||
* list, such as `user-agent` or `set-cookie`.
|
||||
*
|
||||
* Example headers include 'accept', 'cache-control' and 'if-none-match'.
|
||||
*
|
||||
* This method must not be used to parse headers that are not defined as
|
||||
* a list, such as 'user-agent' or 'set-cookie'.
|
||||
*
|
||||
* @param string|string[] $values Header value as returned by MessageInterface::getHeader()
|
||||
* @param string|string[] $values Header value as returned by
|
||||
* MessageInterface::getHeader()
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
@@ -144,7 +132,7 @@ final class Header
|
||||
}
|
||||
|
||||
if (!$isQuoted && $value[$i] === ',') {
|
||||
$v = \trim($v, " \n\r\t\0\x0B");
|
||||
$v = \trim($v, " \t\n\r");
|
||||
if ($v !== '') {
|
||||
$result[] = $v;
|
||||
}
|
||||
@@ -169,7 +157,7 @@ final class Header
|
||||
$v .= $value[$i];
|
||||
}
|
||||
|
||||
$v = \trim($v, " \n\r\t\0\x0B");
|
||||
$v = \trim($v, " \t\n\r");
|
||||
if ($v !== '') {
|
||||
$result[] = $v;
|
||||
}
|
||||
|
||||
18
libs/vendor/guzzlehttp/psr7/src/HttpFactory.php
vendored
18
libs/vendor/guzzlehttp/psr7/src/HttpFactory.php
vendored
@@ -36,7 +36,13 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter
|
||||
$size = $stream->getSize();
|
||||
}
|
||||
|
||||
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
|
||||
return new UploadedFile(
|
||||
$stream,
|
||||
Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size'),
|
||||
$error,
|
||||
$clientFilename,
|
||||
$clientMediaType
|
||||
);
|
||||
}
|
||||
|
||||
public function createStream(string $content = ''): StreamInterface
|
||||
@@ -50,7 +56,7 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter
|
||||
$resource = Utils::tryFopen($file, $mode);
|
||||
} catch (\RuntimeException $e) {
|
||||
if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e);
|
||||
throw new \InvalidArgumentException(sprintf('Invalid file opening mode: %s', DiagnosticValue::escape($mode)), 0, $e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
@@ -64,8 +70,12 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter
|
||||
return Utils::streamFor($resource);
|
||||
}
|
||||
|
||||
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
|
||||
{
|
||||
public function createServerRequest(
|
||||
string $method,
|
||||
$uri,
|
||||
#[\SensitiveParameter]
|
||||
array $serverParams = []
|
||||
): ServerRequestInterface {
|
||||
if (empty($method)) {
|
||||
if (!empty($serverParams['REQUEST_METHOD'])) {
|
||||
$method = $serverParams['REQUEST_METHOD'];
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use GuzzleHttp\Psr7\Exception\TimeoutException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
@@ -20,12 +21,15 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class InflateStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
private ?StreamInterface $source;
|
||||
|
||||
public function __construct(StreamInterface $stream)
|
||||
{
|
||||
$this->source = $stream;
|
||||
$resource = StreamWrapper::getResource($stream);
|
||||
// Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data
|
||||
// See https://www.zlib.net/manual.html#Advanced definition of inflateInit2
|
||||
@@ -34,4 +38,64 @@ final class InflateStream implements StreamInterface
|
||||
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]);
|
||||
$this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
|
||||
}
|
||||
|
||||
public function read(int $length): string
|
||||
{
|
||||
if ($length <= 0 || $this->source === null) {
|
||||
return $this->stream->read($length);
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->stream->read($length);
|
||||
} catch (TimeoutException $e) {
|
||||
throw $e;
|
||||
} catch (\RuntimeException $e) {
|
||||
if (StreamTimeout::isReadTimedOut($this->source)) {
|
||||
throw new TimeoutException('Unable to read from stream: timed out', 0, $e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($data === '' && StreamTimeout::isReadTimedOut($this->source)) {
|
||||
throw new TimeoutException('Unable to read from stream: timed out');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$source = $this->source;
|
||||
$this->source = null;
|
||||
|
||||
$exception = null;
|
||||
|
||||
try {
|
||||
$this->stream->close();
|
||||
} catch (\Throwable $e) {
|
||||
$exception = $e;
|
||||
}
|
||||
|
||||
if ($source !== null) {
|
||||
try {
|
||||
$source->close();
|
||||
} catch (\Throwable $e) {
|
||||
if ($exception === null) {
|
||||
$exception = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($exception !== null) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
$this->source = null;
|
||||
|
||||
return $this->stream->detach();
|
||||
}
|
||||
}
|
||||
|
||||
104
libs/vendor/guzzlehttp/psr7/src/Integers.php
vendored
Normal file
104
libs/vendor/guzzlehttp/psr7/src/Integers.php
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Integers
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function add(int $a, int $b): int
|
||||
{
|
||||
if ($a < 0 || $b < 0) {
|
||||
throw new \InvalidArgumentException('Integer operands must be non-negative');
|
||||
}
|
||||
|
||||
if ($b > \PHP_INT_MAX - $a) {
|
||||
throw new \OverflowException('Stream byte count exceeds the maximum integer size supported on this platform');
|
||||
}
|
||||
|
||||
return $a + $b;
|
||||
}
|
||||
|
||||
public static function addSigned(int $base, int $delta): int
|
||||
{
|
||||
if ($base < 0) {
|
||||
throw new \InvalidArgumentException('Stream offset must be non-negative');
|
||||
}
|
||||
|
||||
if ($delta > 0 && $delta > \PHP_INT_MAX - $base) {
|
||||
throw new \OverflowException('Stream offset exceeds the maximum integer size supported on this platform');
|
||||
}
|
||||
|
||||
$value = $base + $delta;
|
||||
if ($value < 0) {
|
||||
// A negative computed offset is a seek failure at runtime, so throw
|
||||
// RuntimeException per PSR-7, unlike the precondition check above.
|
||||
throw new \RuntimeException('Stream offset must be non-negative');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assertEngineInteger($value, string $what): ?int
|
||||
{
|
||||
if ($value === false || $value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\is_int($value) || $value < 0) {
|
||||
throw new \OverflowException($what.' exceeds the maximum integer size supported on this platform');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assertOptionalNonNegativeSize($value, string $name): ?int
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\is_int($value) || $value < 0) {
|
||||
throw new \InvalidArgumentException($name.' must be a non-negative integer or null');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assertNonNegativeInteger($value, string $name): int
|
||||
{
|
||||
if (!\is_int($value) || $value < 0) {
|
||||
throw new \InvalidArgumentException($name.' must be a non-negative integer');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assertLimitInteger($value, string $name): int
|
||||
{
|
||||
if (!\is_int($value) || $value < -1) {
|
||||
throw new \InvalidArgumentException($name.' must be -1 or a non-negative integer');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,13 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class LazyOpenStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var string */
|
||||
private $filename;
|
||||
private string $filename;
|
||||
|
||||
/** @var string */
|
||||
private $mode;
|
||||
private string $mode;
|
||||
|
||||
/**
|
||||
* @var StreamInterface
|
||||
*/
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
/**
|
||||
* @param string $filename File to lazily open
|
||||
@@ -39,6 +35,13 @@ final class LazyOpenStream implements StreamInterface
|
||||
unset($this->stream);
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->stream = new BufferStream();
|
||||
|
||||
throw new \LogicException(static::class.' should never be unserialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the underlying stream lazily when required.
|
||||
*/
|
||||
|
||||
99
libs/vendor/guzzlehttp/psr7/src/LimitStream.php
vendored
99
libs/vendor/guzzlehttp/psr7/src/LimitStream.php
vendored
@@ -12,15 +12,15 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class LimitStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var int Offset to start reading from */
|
||||
private $offset;
|
||||
private int $offset;
|
||||
|
||||
/** @var int Limit the number of bytes that can be read */
|
||||
private $limit;
|
||||
private int $limit;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
/**
|
||||
* @param StreamInterface $stream Stream to wrap
|
||||
@@ -51,7 +51,7 @@ final class LimitStream implements StreamInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->stream->tell() >= $this->offset + $this->limit;
|
||||
return $this->stream->tell() >= Integers::add($this->offset, $this->limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,26 +75,8 @@ final class LimitStream implements StreamInterface
|
||||
/**
|
||||
* Allow for a bounded seek on the read limited stream
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
if ($whence !== SEEK_SET || $offset < 0) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Cannot seek to offset %s with whence %s',
|
||||
@@ -103,11 +85,12 @@ final class LimitStream implements StreamInterface
|
||||
));
|
||||
}
|
||||
|
||||
$offset += $this->offset;
|
||||
$offset = Integers::add($this->offset, $offset);
|
||||
|
||||
if ($this->limit !== -1) {
|
||||
if ($offset > $this->offset + $this->limit) {
|
||||
$offset = $this->offset + $this->limit;
|
||||
$upperBound = Integers::add($this->offset, $this->limit);
|
||||
if ($offset > $upperBound) {
|
||||
$offset = $upperBound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,17 +114,48 @@ final class LimitStream implements StreamInterface
|
||||
*/
|
||||
public function setOffset(int $offset): void
|
||||
{
|
||||
$offset = Integers::assertNonNegativeInteger($offset, 'Offset');
|
||||
|
||||
$current = $this->stream->tell();
|
||||
|
||||
if ($current !== $offset) {
|
||||
// If the stream cannot seek to the offset position, then read to it
|
||||
if ($this->stream->isSeekable()) {
|
||||
$this->stream->seek($offset);
|
||||
} elseif ($current > $offset) {
|
||||
throw new \RuntimeException("Could not seek to stream offset $offset");
|
||||
} else {
|
||||
$this->stream->read($offset - $current);
|
||||
if ($current === $offset) {
|
||||
$this->offset = $offset;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the stream cannot seek to the offset position, then read to it.
|
||||
if ($this->stream->isSeekable()) {
|
||||
$this->stream->seek($offset);
|
||||
$this->offset = $offset;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($current > $offset) {
|
||||
throw new \RuntimeException("Could not seek to stream offset $offset");
|
||||
}
|
||||
|
||||
while ($current < $offset) {
|
||||
if ($this->stream->eof()) {
|
||||
$this->offset = $current;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->stream->read($offset - $current);
|
||||
|
||||
if ($result === '') {
|
||||
if ($this->stream->eof()) {
|
||||
$this->offset = $current;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Could not seek to stream offset $offset");
|
||||
}
|
||||
|
||||
$current = Integers::add($current, strlen($result));
|
||||
}
|
||||
|
||||
$this->offset = $offset;
|
||||
@@ -156,18 +170,13 @@ final class LimitStream implements StreamInterface
|
||||
*/
|
||||
public function setLimit(int $limit): void
|
||||
{
|
||||
$this->limit = $limit;
|
||||
$this->limit = Integers::assertLimitInteger($limit, 'Limit');
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
if ($this->limit === -1) {
|
||||
@@ -176,7 +185,7 @@ final class LimitStream implements StreamInterface
|
||||
|
||||
// Check if the current position is less than the total allowed
|
||||
// bytes + original offset
|
||||
$remaining = ($this->offset + $this->limit) - $this->stream->tell();
|
||||
$remaining = Integers::add($this->offset, $this->limit) - $this->stream->tell();
|
||||
if ($remaining > 0) {
|
||||
// Only return the amount of requested data, ensuring that the byte
|
||||
// limit is not exceeded
|
||||
|
||||
258
libs/vendor/guzzlehttp/psr7/src/Message.php
vendored
258
libs/vendor/guzzlehttp/psr7/src/Message.php
vendored
@@ -7,9 +7,16 @@ namespace GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class Message
|
||||
{
|
||||
private const DEFAULT_BODY_SUMMARY_TRUNCATE_AT = 120;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of an HTTP message.
|
||||
*
|
||||
@@ -22,7 +29,7 @@ final class Message
|
||||
.$message->getRequestTarget(), " \n\r\t\0\x0B")
|
||||
.' HTTP/'.$message->getProtocolVersion();
|
||||
if (!$message->hasHeader('host')) {
|
||||
$msg .= "\r\nHost: ".$message->getUri()->getHost();
|
||||
$msg .= "\r\nHost: ".self::hostHeaderFromUri($message->getUri());
|
||||
}
|
||||
} elseif ($message instanceof ResponseInterface) {
|
||||
$msg = 'HTTP/'.$message->getProtocolVersion().' '
|
||||
@@ -45,16 +52,39 @@ final class Message
|
||||
return "{$msg}\r\n\r\n".$message->getBody();
|
||||
}
|
||||
|
||||
private static function hostHeaderFromUri(UriInterface $uri): string
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
|
||||
if ($host === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
Uri::assertValidHost($host);
|
||||
|
||||
if (($port = $uri->getPort()) !== null) {
|
||||
$host .= ':'.$port;
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short summary of the message body.
|
||||
*
|
||||
* Will return `null` if the response is not printable.
|
||||
*
|
||||
* Reads seekable bodies from the beginning and restores the original cursor
|
||||
* position before returning. Pass `null` for `$truncateAt` to use the
|
||||
* default summary length.
|
||||
*
|
||||
* @param MessageInterface $message The message to get the body summary
|
||||
* @param int $truncateAt The maximum allowed size of the summary
|
||||
* @param int|null $truncateAt Maximum allowed size of the summary
|
||||
*/
|
||||
public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
|
||||
public static function bodySummary(MessageInterface $message, ?int $truncateAt = null): ?string
|
||||
{
|
||||
$truncateAt ??= self::DEFAULT_BODY_SUMMARY_TRUNCATE_AT;
|
||||
|
||||
$body = $message->getBody();
|
||||
|
||||
if (!$body->isSeekable() || !$body->isReadable()) {
|
||||
@@ -67,19 +97,23 @@ final class Message
|
||||
return null;
|
||||
}
|
||||
|
||||
$body->rewind();
|
||||
$summary = $body->read($truncateAt);
|
||||
$position = $body->tell();
|
||||
|
||||
if ($size > $truncateAt) {
|
||||
if (preg_match('//u', $summary) !== 1) {
|
||||
$summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3));
|
||||
try {
|
||||
$body->rewind();
|
||||
$summary = $body->read($truncateAt);
|
||||
|
||||
if ($size > $truncateAt) {
|
||||
if (preg_match('//u', $summary) !== 1) {
|
||||
$summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3));
|
||||
}
|
||||
|
||||
$summary .= ' (truncated...)';
|
||||
}
|
||||
|
||||
$summary .= ' (truncated...)';
|
||||
} finally {
|
||||
$body->seek($position);
|
||||
}
|
||||
|
||||
$body->rewind();
|
||||
|
||||
// Matches any printable character, including unicode characters:
|
||||
// letters, marks, numbers, punctuation, spacing, and separators.
|
||||
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
|
||||
@@ -165,184 +199,49 @@ final class Message
|
||||
/**
|
||||
* Parses an HTTP message into an associative array.
|
||||
*
|
||||
* The array contains the "start-line" key containing the start line of
|
||||
* the message, "headers" key containing an associative array of header
|
||||
* array values, and a "body" key containing the body of the message.
|
||||
* The array contains the `start-line` key containing the start line of the
|
||||
* message, `headers` key containing an associative array of header array
|
||||
* values, and a `body` key containing the body of the message.
|
||||
*
|
||||
* @param string $message HTTP request or response to parse.
|
||||
*/
|
||||
public static function parseMessage(string $message): array
|
||||
{
|
||||
if (!$message) {
|
||||
throw new \InvalidArgumentException('Invalid message');
|
||||
}
|
||||
|
||||
$message = ltrim($message, "\r\n");
|
||||
|
||||
$messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
|
||||
|
||||
if ($messageParts === false) {
|
||||
throw new \RuntimeException('Unable to split HTTP message: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if (count($messageParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
|
||||
}
|
||||
|
||||
[$rawHeaders, $body] = $messageParts;
|
||||
$rawHeaders .= "\r\n"; // Put back the delimiter we split previously
|
||||
$headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
|
||||
|
||||
if ($headerParts === false) {
|
||||
throw new \RuntimeException('Unable to split HTTP message headers: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if (count($headerParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing status line');
|
||||
}
|
||||
|
||||
[$startLine, $rawHeaders] = $headerParts;
|
||||
|
||||
$versionMatch = preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches);
|
||||
|
||||
if ($versionMatch === false) {
|
||||
throw new \RuntimeException('Unable to parse HTTP start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($versionMatch === 1 && $matches[1] === '1.0') {
|
||||
// Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
|
||||
$rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
|
||||
|
||||
if ($rawHeaders === null) {
|
||||
throw new \RuntimeException('Unable to unfold HTTP headers: '.preg_last_error_msg());
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array[] $headerLines */
|
||||
$count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
|
||||
|
||||
if ($count === false) {
|
||||
throw new \RuntimeException('Unable to parse HTTP headers: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
// If these aren't the same, then one line didn't match and there's an invalid header.
|
||||
if ($count !== substr_count($rawHeaders, "\n")) {
|
||||
// Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
|
||||
$hasFoldedHeader = preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders);
|
||||
|
||||
if ($hasFoldedHeader === false) {
|
||||
throw new \RuntimeException('Unable to inspect HTTP header folding: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($hasFoldedHeader === 1) {
|
||||
throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Invalid header syntax');
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
|
||||
foreach ($headerLines as $headerLine) {
|
||||
$headers[$headerLine[1]][] = $headerLine[2];
|
||||
}
|
||||
|
||||
return [
|
||||
'start-line' => $startLine,
|
||||
'headers' => $headers,
|
||||
'body' => $body,
|
||||
];
|
||||
return MessageParser::parseMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a URI for an HTTP request message.
|
||||
*
|
||||
* The URI is composed from the start-line path and the `Host` header, using
|
||||
* `https` when the host's port is `443` and `http` otherwise. Without a
|
||||
* `Host` header, only the path is returned, with extra leading slashes
|
||||
* collapsed so an origin-form target cannot be parsed as a network-path
|
||||
* reference with its own authority. An `InvalidArgumentException` is thrown
|
||||
* when the `Host` header is invalid.
|
||||
*
|
||||
* @param string $path Path from the start-line
|
||||
* @param array $headers Array of headers (each value an array).
|
||||
*/
|
||||
public static function parseRequestUri(string $path, array $headers): string
|
||||
{
|
||||
$host = self::getHostFromHeaders($headers);
|
||||
|
||||
// If no host is found, then a full URI cannot be constructed.
|
||||
// Collapse leading slashes so an origin-form target cannot be
|
||||
// parsed as a network-path reference with its own authority.
|
||||
if ($host === null) {
|
||||
return self::normalizePathForOriginForm($path);
|
||||
}
|
||||
|
||||
$scheme = substr($host, -4) === ':443' ? 'https' : 'http';
|
||||
|
||||
return $scheme.'://'.$host.'/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
private static function normalizePathForOriginForm(string $path): string
|
||||
{
|
||||
if (0 === strpos($path, '//')) {
|
||||
return '/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers Array of headers (each value an array).
|
||||
*/
|
||||
private static function getHostFromHeaders(array $headers): ?string
|
||||
{
|
||||
$hostKey = array_filter(array_keys($headers), function ($k) {
|
||||
// Numeric array keys are converted to int by PHP.
|
||||
$k = (string) $k;
|
||||
|
||||
return Utils::asciiToLower($k) === 'host';
|
||||
});
|
||||
|
||||
if (!$hostKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = $headers[reset($hostKey)][0];
|
||||
if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
return $host;
|
||||
return MessageParser::parseRequestUri($path, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a request message string into a request object.
|
||||
*
|
||||
* The request-target must be in origin form, absolute form (without a
|
||||
* userinfo component), authority form (`CONNECT`), or asterisk form
|
||||
* (`OPTIONS`), and any `Host` header must be a single valid value;
|
||||
* otherwise an `InvalidArgumentException` is thrown. Non-origin-form
|
||||
* targets are preserved on the returned request via `withRequestTarget()`.
|
||||
*
|
||||
* @param string $message Request message string.
|
||||
*/
|
||||
public static function parseRequest(string $message): RequestInterface
|
||||
{
|
||||
$data = self::parseMessage($message);
|
||||
if (strpbrk($data['start-line'], "\r\n") !== false) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
$requestStartLineMatch = preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches);
|
||||
|
||||
if ($requestStartLineMatch === false) {
|
||||
throw new \RuntimeException('Unable to parse request start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($requestStartLineMatch === 0) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
$parts = explode(' ', $data['start-line'], 3);
|
||||
$version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
|
||||
|
||||
$request = new Request(
|
||||
$parts[0],
|
||||
$matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$version
|
||||
);
|
||||
|
||||
return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
|
||||
return MessageParser::parseRequest($message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,31 +251,6 @@ final class Message
|
||||
*/
|
||||
public static function parseResponse(string $message): ResponseInterface
|
||||
{
|
||||
$data = self::parseMessage($message);
|
||||
if (strpbrk($data['start-line'], "\r\n") !== false) {
|
||||
throw new \InvalidArgumentException('Invalid response string');
|
||||
}
|
||||
|
||||
// According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
|
||||
// the space between status-code and reason-phrase is required. But
|
||||
// browsers accept responses without space and reason as well.
|
||||
$responseStartLineMatch = preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/D', $data['start-line']);
|
||||
|
||||
if ($responseStartLineMatch === false) {
|
||||
throw new \RuntimeException('Unable to parse response start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($responseStartLineMatch === 0) {
|
||||
throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
|
||||
}
|
||||
$parts = explode(' ', $data['start-line'], 3);
|
||||
|
||||
return new Response(
|
||||
(int) $parts[1],
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
explode('/', $parts[0])[1],
|
||||
$parts[2] ?? null
|
||||
);
|
||||
return MessageParser::parseResponse($message);
|
||||
}
|
||||
}
|
||||
|
||||
363
libs/vendor/guzzlehttp/psr7/src/MessageParser.php
vendored
Normal file
363
libs/vendor/guzzlehttp/psr7/src/MessageParser.php
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MessageParser
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function parseMessage(string $message): array
|
||||
{
|
||||
if (!$message) {
|
||||
throw new \InvalidArgumentException('Invalid message');
|
||||
}
|
||||
|
||||
$message = ltrim($message, "\r\n");
|
||||
|
||||
$messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
|
||||
|
||||
if ($messageParts === false) {
|
||||
throw new \RuntimeException('Unable to split HTTP message: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if (count($messageParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
|
||||
}
|
||||
|
||||
[$rawHeaders, $body] = $messageParts;
|
||||
$rawHeaders .= "\r\n"; // Put back the delimiter we split previously
|
||||
$headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
|
||||
|
||||
if ($headerParts === false) {
|
||||
throw new \RuntimeException('Unable to split HTTP message headers: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if (count($headerParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing status line');
|
||||
}
|
||||
|
||||
[$startLine, $rawHeaders] = $headerParts;
|
||||
|
||||
$versionMatch = preg_match(
|
||||
'/(?:^HTTP\/|^'.Rfc9110::TOKEN_PATTERN.' '.Rfc9112::REQUEST_TARGET_PATTERN.' HTTP\/)('.Rfc9112::PROTOCOL_VERSION_PATTERN.')/i',
|
||||
$startLine,
|
||||
$matches
|
||||
);
|
||||
|
||||
if ($versionMatch === false) {
|
||||
throw new \RuntimeException('Unable to parse HTTP start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($versionMatch === 1 && $matches[1] === '1.0') {
|
||||
// Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
|
||||
$rawHeaders = preg_replace(Rfc9112::HEADER_FOLD_REGEX, ' ', $rawHeaders);
|
||||
|
||||
if ($rawHeaders === null) {
|
||||
throw new \RuntimeException('Unable to unfold HTTP headers: '.preg_last_error_msg());
|
||||
}
|
||||
}
|
||||
|
||||
$count = preg_match_all(Rfc9112::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
|
||||
/** @var list<array<int, string>> $headerLines */
|
||||
if ($count === false) {
|
||||
throw new \RuntimeException('Unable to parse HTTP headers: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
// If these aren't the same, then one line didn't match and there's an invalid header.
|
||||
if ($count !== substr_count($rawHeaders, "\n")) {
|
||||
// Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc9112#section-5.2
|
||||
$hasFoldedHeader = preg_match(Rfc9112::HEADER_FOLD_REGEX, $rawHeaders);
|
||||
|
||||
if ($hasFoldedHeader === false) {
|
||||
throw new \RuntimeException('Unable to inspect HTTP header folding: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($hasFoldedHeader === 1) {
|
||||
throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Invalid header syntax');
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
|
||||
foreach ($headerLines as $headerLine) {
|
||||
$headers[$headerLine[1]][] = $headerLine[2];
|
||||
}
|
||||
|
||||
return [
|
||||
'start-line' => $startLine,
|
||||
'headers' => $headers,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
public static function parseRequestUri(string $path, array $headers): string
|
||||
{
|
||||
$host = self::getHostFromHeaders($headers);
|
||||
|
||||
// If no host is found, then a full URI cannot be constructed.
|
||||
// Collapse leading slashes so an origin-form target cannot be
|
||||
// parsed as a network-path reference with its own authority.
|
||||
if ($host === null) {
|
||||
return self::normalizePathForOriginForm($path);
|
||||
}
|
||||
|
||||
[$authorityHost, $port] = self::parseHostHeaderAuthority($host);
|
||||
$scheme = $port === 443 ? 'https' : 'http';
|
||||
|
||||
return $scheme.'://'.self::composeAuthority($authorityHost, $port).'/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
private static function normalizePathForOriginForm(string $path): string
|
||||
{
|
||||
if (str_starts_with($path, '//')) {
|
||||
return '/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int|null}
|
||||
*/
|
||||
private static function parseHostHeaderAuthority(string $authority): array
|
||||
{
|
||||
$parsed = Rfc9112::parseHostHeader($authority);
|
||||
if ($parsed === null) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
private static function composeAuthority(string $host, ?int $port): string
|
||||
{
|
||||
return $host.($port !== null ? ':'.$port : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers Array of headers (each value an array).
|
||||
*/
|
||||
private static function getHostFromHeaders(array $headers): ?string
|
||||
{
|
||||
$host = self::getSingleHostHeader($headers);
|
||||
if ($host === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
self::parseHostHeaderAuthority($host);
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers Array of headers (each value an array).
|
||||
*/
|
||||
private static function getSingleHostHeader(array $headers): ?string
|
||||
{
|
||||
$host = null;
|
||||
$found = false;
|
||||
|
||||
foreach ($headers as $name => $values) {
|
||||
if (Utils::asciiToLower((string) $name) !== 'host') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($found || !is_array($values) || count($values) !== 1) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
$found = true;
|
||||
$host = reset($values);
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($host)) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers Array of headers (each value an array).
|
||||
*/
|
||||
private static function parseRequestAuthorityUri(array $headers): string
|
||||
{
|
||||
$host = self::getHostFromHeaders($headers);
|
||||
if ($host === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
[$authorityHost, $port] = self::parseHostHeaderAuthority($host);
|
||||
$scheme = $port === 443 ? 'https' : 'http';
|
||||
|
||||
return $scheme.'://'.self::composeAuthority($authorityHost, $port);
|
||||
}
|
||||
|
||||
public static function parseRequest(string $message): RequestInterface
|
||||
{
|
||||
$data = self::parseMessage($message);
|
||||
$matches = [];
|
||||
$matched = preg_match(
|
||||
'/^(?P<method>'.Rfc9110::TOKEN_PATTERN.') (?P<target>'.Rfc9112::REQUEST_TARGET_PATTERN.') HTTP\/(?P<version>'.Rfc9112::PROTOCOL_VERSION_PATTERN.')$/D',
|
||||
$data['start-line'],
|
||||
$matches
|
||||
);
|
||||
|
||||
if ($matched === false) {
|
||||
throw new \RuntimeException('Unable to parse request start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($matched === 0) {
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
self::getHostFromHeaders($data['headers']);
|
||||
|
||||
if (str_starts_with($matches['target'], '/')) {
|
||||
return new Request(
|
||||
$matches['method'],
|
||||
self::parseRequestUri($matches['target'], $data['headers']),
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$matches['version']
|
||||
);
|
||||
}
|
||||
|
||||
$absoluteFormUri = self::parseAbsoluteFormRequestTarget($matches['target']);
|
||||
if ($absoluteFormUri !== null) {
|
||||
return (new Request(
|
||||
$matches['method'],
|
||||
$absoluteFormUri,
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$matches['version']
|
||||
))->withRequestTarget($matches['target']);
|
||||
}
|
||||
|
||||
if (Rfc9112::isAsteriskFormRequestTarget($matches['method'], $matches['target'])) {
|
||||
return (new Request(
|
||||
$matches['method'],
|
||||
self::parseRequestAuthorityUri($data['headers']),
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$matches['version']
|
||||
))->withRequestTarget($matches['target']);
|
||||
}
|
||||
|
||||
$connectUri = self::parseConnectAuthorityFormRequestTarget($matches['method'], $matches['target']);
|
||||
if ($connectUri !== null) {
|
||||
return (new Request(
|
||||
$matches['method'],
|
||||
$connectUri,
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$matches['version']
|
||||
))->withRequestTarget($matches['target']);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Invalid request string');
|
||||
}
|
||||
|
||||
private static function parseAbsoluteFormRequestTarget(string $target): ?Uri
|
||||
{
|
||||
if (!Rfc9112::isAbsoluteFormRequestTarget($target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$authority = substr($target, strpos($target, '//') + 2);
|
||||
$authority = substr($authority, 0, strcspn($authority, '/?#'));
|
||||
|
||||
// RFC 9110 deprecates userinfo in message target URIs and directs
|
||||
// recipients to treat its presence as an error, since it can obscure
|
||||
// the authority. Host headers and CONNECT targets already reject it.
|
||||
if (str_contains($authority, '@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$uri = new Uri($target);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($uri->getHost() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
self::parseHostHeaderAuthority(self::composeAuthority($uri->getHost(), $uri->getPort()));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?Uri
|
||||
{
|
||||
if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parsed = Rfc9112::parseHostHeader($target);
|
||||
if ($parsed === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$host, $port] = $parsed;
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Uri('//'.self::composeAuthority($host, $port));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseResponse(string $message): ResponseInterface
|
||||
{
|
||||
$data = self::parseMessage($message);
|
||||
// According to https://datatracker.ietf.org/doc/html/rfc9112#section-4
|
||||
// the space between status-code and reason-phrase is required. But
|
||||
// browsers accept responses without space and reason as well.
|
||||
$matched = preg_match(
|
||||
'/^HTTP\/(?P<version>'.Rfc9112::PROTOCOL_VERSION_PATTERN.') (?P<status>[1-5][0-9]{2})(?: (?P<reason>'.Rfc9110::FIELD_VALUE_PATTERN.'))?$/D',
|
||||
$data['start-line'],
|
||||
$matches
|
||||
);
|
||||
|
||||
if ($matched === false) {
|
||||
throw new \RuntimeException('Unable to parse response start line: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($matched === 0) {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid response string: %s', DiagnosticValue::escape($data['start-line'])));
|
||||
}
|
||||
|
||||
return new Response(
|
||||
(int) $matches['status'],
|
||||
$data['headers'],
|
||||
$data['body'],
|
||||
$matches['version'],
|
||||
$matches['reason'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
195
libs/vendor/guzzlehttp/psr7/src/MessageTrait.php
vendored
195
libs/vendor/guzzlehttp/psr7/src/MessageTrait.php
vendored
@@ -13,16 +13,14 @@ use Psr\Http\Message\StreamInterface;
|
||||
trait MessageTrait
|
||||
{
|
||||
/** @var string[][] Map of all registered headers, as original name => array of values */
|
||||
private $headers = [];
|
||||
private array $headers = [];
|
||||
|
||||
/** @var string[] Map of lowercase header name => original name at registration */
|
||||
private $headerNames = [];
|
||||
private array $headerNames = [];
|
||||
|
||||
/** @var string */
|
||||
private $protocol = '1.1';
|
||||
private string $protocol = '1.1';
|
||||
|
||||
/** @var StreamInterface|null */
|
||||
private $stream;
|
||||
private ?StreamInterface $stream = null;
|
||||
|
||||
public function getProtocolVersion(): string
|
||||
{
|
||||
@@ -32,17 +30,8 @@ trait MessageTrait
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withProtocolVersion($version): MessageInterface
|
||||
public function withProtocolVersion(string $version): MessageInterface
|
||||
{
|
||||
if (!\is_string($version)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to MessageInterface::withProtocolVersion() is deprecated; guzzlehttp/psr7 3.0 requires string.',
|
||||
\get_debug_type($version)
|
||||
);
|
||||
}
|
||||
|
||||
$this->assertProtocolVersion($version);
|
||||
|
||||
if ($this->protocol === $version) {
|
||||
@@ -60,14 +49,14 @@ trait MessageTrait
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function hasHeader($header): bool
|
||||
public function hasHeader(string $name): bool
|
||||
{
|
||||
return isset($this->headerNames[Utils::asciiToLower($header)]);
|
||||
return isset($this->headerNames[Utils::asciiToLower($name)]);
|
||||
}
|
||||
|
||||
public function getHeader($header): array
|
||||
public function getHeader(string $name): array
|
||||
{
|
||||
$header = Utils::asciiToLower($header);
|
||||
$header = Utils::asciiToLower($name);
|
||||
|
||||
if (!isset($this->headerNames[$header])) {
|
||||
return [];
|
||||
@@ -78,39 +67,26 @@ trait MessageTrait
|
||||
return $this->headers[$header];
|
||||
}
|
||||
|
||||
public function getHeaderLine($header): string
|
||||
public function getHeaderLine(string $name): string
|
||||
{
|
||||
return implode(', ', $this->getHeader($header));
|
||||
return implode(', ', $this->getHeader($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withHeader($header, $value): MessageInterface
|
||||
public function withHeader(string $name, $value): MessageInterface
|
||||
{
|
||||
$this->assertHeader($header);
|
||||
$values = \is_array($value) ? $value : [$value];
|
||||
foreach ($values as $item) {
|
||||
if (!\is_string($item) && (\is_scalar($item) || $item === null)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to MessageInterface::withHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].',
|
||||
\get_debug_type($item)
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertHeader($name);
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = Utils::asciiToLower($header);
|
||||
$normalized = Utils::asciiToLower($name);
|
||||
|
||||
$new = clone $this;
|
||||
if (isset($new->headerNames[$normalized])) {
|
||||
unset($new->headers[$new->headerNames[$normalized]]);
|
||||
}
|
||||
$new->headerNames[$normalized] = $header;
|
||||
$new->headers[$header] = $value;
|
||||
$new->headerNames[$normalized] = $name;
|
||||
$new->headers[$name] = $value;
|
||||
|
||||
return $new;
|
||||
}
|
||||
@@ -118,32 +94,19 @@ trait MessageTrait
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withAddedHeader($header, $value): MessageInterface
|
||||
public function withAddedHeader(string $name, $value): MessageInterface
|
||||
{
|
||||
$this->assertHeader($header);
|
||||
$values = \is_array($value) ? $value : [$value];
|
||||
foreach ($values as $item) {
|
||||
if (!\is_string($item) && (\is_scalar($item) || $item === null)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to MessageInterface::withAddedHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].',
|
||||
\get_debug_type($item)
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertHeader($name);
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = Utils::asciiToLower($header);
|
||||
$normalized = Utils::asciiToLower($name);
|
||||
|
||||
$new = clone $this;
|
||||
if (isset($new->headerNames[$normalized])) {
|
||||
$header = $this->headerNames[$normalized];
|
||||
$new->headers[$header] = array_merge($this->headers[$header], $value);
|
||||
$name = $this->headerNames[$normalized];
|
||||
$new->headers[$name] = array_merge($this->headers[$name], $value);
|
||||
} else {
|
||||
$new->headerNames[$normalized] = $header;
|
||||
$new->headers[$header] = $value;
|
||||
$new->headerNames[$normalized] = $name;
|
||||
$new->headers[$name] = $value;
|
||||
}
|
||||
|
||||
return $new;
|
||||
@@ -152,18 +115,18 @@ trait MessageTrait
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withoutHeader($header): MessageInterface
|
||||
public function withoutHeader(string $name): MessageInterface
|
||||
{
|
||||
$normalized = Utils::asciiToLower($header);
|
||||
$normalized = Utils::asciiToLower($name);
|
||||
|
||||
if (!isset($this->headerNames[$normalized])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$header = $this->headerNames[$normalized];
|
||||
$name = $this->headerNames[$normalized];
|
||||
|
||||
$new = clone $this;
|
||||
unset($new->headers[$header], $new->headerNames[$normalized]);
|
||||
unset($new->headers[$name], $new->headerNames[$normalized]);
|
||||
|
||||
return $new;
|
||||
}
|
||||
@@ -203,20 +166,6 @@ trait MessageTrait
|
||||
$header = (string) $header;
|
||||
|
||||
$this->assertHeader($header);
|
||||
$values = \is_array($value) ? $value : [$value];
|
||||
foreach ($values as $item) {
|
||||
if (!\is_string($item) && (\is_scalar($item) || $item === null)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to %s::__construct() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].',
|
||||
\get_debug_type($item),
|
||||
static::class
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = Utils::asciiToLower($header);
|
||||
if (isset($this->headerNames[$normalized])) {
|
||||
@@ -237,11 +186,7 @@ trait MessageTrait
|
||||
private function normalizeHeaderValue($value): array
|
||||
{
|
||||
if (is_array($value) && $value === []) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing an empty array as a header value is deprecated; guzzlehttp/psr7 3.0 rejects empty header value arrays.'
|
||||
);
|
||||
throw new \InvalidArgumentException('Header value must be a non-empty array or string.');
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
@@ -263,25 +208,19 @@ trait MessageTrait
|
||||
*
|
||||
* @return string[] Trimmed header values
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
|
||||
*/
|
||||
private function trimAndValidateHeaderValues(array $values): array
|
||||
{
|
||||
return array_map(function ($value) {
|
||||
if (!is_scalar($value) && null !== $value) {
|
||||
return array_map(function ($value): string {
|
||||
if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Header value must be scalar or null but %s provided.',
|
||||
is_object($value) ? get_class($value) : gettype($value)
|
||||
'Header value must be a string or array of strings but %s provided.',
|
||||
\get_debug_type($value)
|
||||
));
|
||||
}
|
||||
|
||||
// Convert non-finite floats explicitly, as implicit coercion of
|
||||
// NAN emits a warning on PHP 8.5.
|
||||
if (is_float($value) && !is_finite($value)) {
|
||||
$value = is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
|
||||
}
|
||||
|
||||
$trimmed = trim((string) $value, " \t");
|
||||
$trimmed = trim($value, " \t");
|
||||
$this->assertValue($trimmed);
|
||||
|
||||
return $trimmed;
|
||||
@@ -289,45 +228,24 @@ trait MessageTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
|
||||
*
|
||||
* @param mixed $header
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.1
|
||||
*/
|
||||
private function assertHeader($header): void
|
||||
private function assertHeader(string $header): void
|
||||
{
|
||||
if (!is_string($header)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Header name must be a string but %s provided.',
|
||||
is_object($header) ? get_class($header) : gettype($header)
|
||||
));
|
||||
if (!Rfc9110::isToken($header)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid header name: %s', DiagnosticValue::escape($header)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('"%s" is not valid header name.', $header)
|
||||
);
|
||||
private function assertProtocolVersion(string $version): void
|
||||
{
|
||||
if (!Rfc9112::isValidProtocolVersion($version)) {
|
||||
throw new \InvalidArgumentException('Protocol version must be a valid HTTP version number.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $version
|
||||
*/
|
||||
private function assertProtocolVersion($version): void
|
||||
{
|
||||
if (is_string($version)) {
|
||||
$this->assertNoLineSeparators($version, 'Protocol version');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertNoLineSeparators(string $value, string $field): void
|
||||
{
|
||||
if (strpbrk($value, "\r\n") !== false) {
|
||||
throw new \InvalidArgumentException($field.' must not contain CR or LF characters.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
|
||||
*
|
||||
* field-value = *( field-content / obs-fold )
|
||||
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
@@ -338,21 +256,20 @@ trait MessageTrait
|
||||
*/
|
||||
private function assertValue(string $value): void
|
||||
{
|
||||
// The regular expression intentionally does not support the obs-fold production, because as
|
||||
// per RFC 7230#3.2.4:
|
||||
// The regular expression intentionally does not support the obs-fold
|
||||
// production, because as per RFC 9112#5.2:
|
||||
//
|
||||
// A sender MUST NOT generate a message that includes
|
||||
// line folding (i.e., that has any field-value that contains a match to
|
||||
// the obs-fold rule) unless the message is intended for packaging
|
||||
// within the message/http media type.
|
||||
// A sender MUST NOT generate a message that includes line folding
|
||||
// (i.e., that has any field-value that contains a match to the obs-fold
|
||||
// rule) unless the message is intended for packaging within the
|
||||
// message/http media type.
|
||||
//
|
||||
// Clients must not send a request with line folding and a server sending folded headers is
|
||||
// likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
|
||||
// folding is not likely to break any legitimate use case.
|
||||
if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('"%s" is not valid header value.', $value)
|
||||
);
|
||||
// Clients must not send a request with line folding and a server
|
||||
// sending folded headers is likely very rare. Line folding is a fairly
|
||||
// obscure feature of HTTP/1.1 and thus not accepting folding is not
|
||||
// likely to break any legitimate use case.
|
||||
if (!Rfc9110::isFieldValue($value)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid header value: %s', DiagnosticValue::escape($value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
libs/vendor/guzzlehttp/psr7/src/MimeType.php
vendored
8
libs/vendor/guzzlehttp/psr7/src/MimeType.php
vendored
@@ -6,6 +6,10 @@ namespace GuzzleHttp\Psr7;
|
||||
|
||||
final class MimeType
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
private const MIME_TYPES = [
|
||||
'123' => 'application/vnd.lotus-1-2-3',
|
||||
'1km' => 'application/vnd.1000minds.decision-model+xml',
|
||||
@@ -1284,7 +1288,7 @@ final class MimeType
|
||||
];
|
||||
|
||||
/**
|
||||
* Determines the mimetype of a file by looking at its extension.
|
||||
* Determines the MIME type of a file by looking at its extension.
|
||||
*
|
||||
* @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
|
||||
*/
|
||||
@@ -1294,7 +1298,7 @@ final class MimeType
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a file extensions to a mimetype.
|
||||
* Maps a file extension to a MIME type.
|
||||
*
|
||||
* @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
|
||||
*/
|
||||
|
||||
110
libs/vendor/guzzlehttp/psr7/src/MultipartStream.php
vendored
110
libs/vendor/guzzlehttp/psr7/src/MultipartStream.php
vendored
@@ -13,12 +13,11 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class MultipartStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var string */
|
||||
private $boundary;
|
||||
private string $boundary;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
private const BOUNDARY_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=? ";
|
||||
|
||||
@@ -43,15 +42,11 @@ final class MultipartStream implements StreamInterface
|
||||
*/
|
||||
public function __construct(array $elements = [], ?string $boundary = null)
|
||||
{
|
||||
if ($boundary !== null && !self::isValidBoundary($boundary)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing an invalid multipart boundary to MultipartStream::__construct() is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart boundaries.'
|
||||
);
|
||||
if ($boundary !== null) {
|
||||
self::validateBoundary($boundary);
|
||||
}
|
||||
|
||||
$this->boundary = $boundary ?: bin2hex(random_bytes(20));
|
||||
$this->boundary = $boundary ?? bin2hex(random_bytes(20));
|
||||
$this->stream = $this->createStream($elements);
|
||||
}
|
||||
|
||||
@@ -75,10 +70,14 @@ final class MultipartStream implements StreamInterface
|
||||
$str = '';
|
||||
foreach ($headers as $key => $value) {
|
||||
$key = (string) $key;
|
||||
|
||||
self::validatePartHeaderName($key);
|
||||
self::validatePartHeaderValue($value);
|
||||
|
||||
$str .= "{$key}: {$value}\r\n";
|
||||
}
|
||||
|
||||
return "--{$this->boundary}\r\n".trim($str, " \n\r\t\0\x0B")."\r\n\r\n";
|
||||
return "--{$this->boundary}\r\n".rtrim($str, "\r\n")."\r\n\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,18 +128,10 @@ final class MultipartStream implements StreamInterface
|
||||
if (is_scalar($contents) && !is_string($contents)) {
|
||||
// Multipart field values are byte strings on the wire, so finite
|
||||
// numeric and boolean field values are cast to string here rather
|
||||
// than tripping streamFor()'s non-string-scalar deprecation. Non-finite
|
||||
// floats are deprecated and normalized here too, so the deprecation is
|
||||
// reported against MultipartStream instead of transitively through
|
||||
// streamFor().
|
||||
// than rejected by streamFor(). Non-finite floats cannot be
|
||||
// represented and are rejected.
|
||||
if (is_float($contents) && !is_finite($contents)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.12',
|
||||
'Passing a non-finite float as multipart contents is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.'
|
||||
);
|
||||
|
||||
$contents = is_nan($contents) ? 'NAN' : ($contents > 0 ? 'INF' : '-INF');
|
||||
throw new \InvalidArgumentException('Cannot create a stream from a non-finite float.');
|
||||
}
|
||||
|
||||
$contents = (string) $contents;
|
||||
@@ -149,7 +140,7 @@ final class MultipartStream implements StreamInterface
|
||||
|
||||
if (empty($element['filename'])) {
|
||||
$uri = $element['contents']->getMetadata('uri');
|
||||
if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
|
||||
if ($uri && \is_string($uri) && !str_starts_with($uri, 'php://') && !str_starts_with($uri, 'data://')) {
|
||||
$element['filename'] = $uri;
|
||||
}
|
||||
}
|
||||
@@ -196,21 +187,14 @@ final class MultipartStream implements StreamInterface
|
||||
// Set a default content-disposition header if one was no provided
|
||||
$disposition = self::getHeader($headers, 'content-disposition');
|
||||
if (!$disposition) {
|
||||
$escapedName = self::escapeContentDispositionParameter($name);
|
||||
$headers['Content-Disposition'] = ($filename === '0' || $filename)
|
||||
? sprintf(
|
||||
'form-data; name="%s"; filename="%s"',
|
||||
$name,
|
||||
basename($filename)
|
||||
$escapedName,
|
||||
self::escapeContentDispositionParameter(basename($filename))
|
||||
)
|
||||
: "form-data; name=\"{$name}\"";
|
||||
}
|
||||
|
||||
// Set a default content-length header if one was no provided
|
||||
$length = self::getHeader($headers, 'content-length');
|
||||
if (!$length) {
|
||||
if ($length = $stream->getSize()) {
|
||||
$headers['Content-Length'] = (string) $length;
|
||||
}
|
||||
: sprintf('form-data; name="%s"', $escapedName);
|
||||
}
|
||||
|
||||
// Set a default Content-Type if one was not supplied
|
||||
@@ -237,15 +221,17 @@ final class MultipartStream implements StreamInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function isValidBoundary(string $boundary): bool
|
||||
private static function validateBoundary(string $boundary): void
|
||||
{
|
||||
$length = strlen($boundary);
|
||||
|
||||
if ($length < 1 || $length > 70 || $boundary[$length - 1] === ' ') {
|
||||
return false;
|
||||
throw new \InvalidArgumentException('Invalid multipart boundary.');
|
||||
}
|
||||
|
||||
return strspn($boundary, self::BOUNDARY_CHARS) === $length;
|
||||
if (strspn($boundary, self::BOUNDARY_CHARS) !== $length) {
|
||||
throw new \InvalidArgumentException('Invalid multipart boundary.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,27 +244,15 @@ final class MultipartStream implements StreamInterface
|
||||
$normalized = [];
|
||||
|
||||
foreach ($headers as $key => $value) {
|
||||
self::deprecateInvalidPartHeaderName((string) $key);
|
||||
$key = (string) $key;
|
||||
|
||||
self::validatePartHeaderName($key);
|
||||
|
||||
if (!is_string($value)) {
|
||||
if (!is_scalar($value) && $value !== null && !(is_object($value) && method_exists($value, '__toString'))) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Multipart part header value must be a string or stringable value but %s provided.',
|
||||
\get_debug_type($value)
|
||||
));
|
||||
}
|
||||
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s as a multipart part header value is deprecated; guzzlehttp/psr7 3.0 requires string multipart part header values.',
|
||||
\get_debug_type($value)
|
||||
);
|
||||
throw new \InvalidArgumentException('Multipart part header value must be a string.');
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
|
||||
self::deprecateInvalidPartHeaderValue($value);
|
||||
self::validatePartHeaderValue($value);
|
||||
|
||||
$normalized[$key] = $value;
|
||||
}
|
||||
@@ -286,25 +260,23 @@ final class MultipartStream implements StreamInterface
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function deprecateInvalidPartHeaderName(string $name): void
|
||||
private static function validatePartHeaderName(string $name): void
|
||||
{
|
||||
if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing an invalid multipart part header name to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header names.'
|
||||
);
|
||||
if (!Rfc9110::isToken($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid multipart part header name: %s', DiagnosticValue::escape($name)));
|
||||
}
|
||||
}
|
||||
|
||||
private static function deprecateInvalidPartHeaderValue(string $value): void
|
||||
private static function validatePartHeaderValue(string $value): void
|
||||
{
|
||||
if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing an invalid multipart part header value to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header values.'
|
||||
);
|
||||
if (!Rfc9110::isFieldValue($value)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid multipart part header value: %s', DiagnosticValue::escape($value)));
|
||||
}
|
||||
}
|
||||
|
||||
private static function escapeContentDispositionParameter(string $value): string
|
||||
{
|
||||
// Match WHATWG browser multipart/form-data behavior: escape CR, LF, and DQUOTE only.
|
||||
return str_replace(["\r", "\n", '"'], ['%0D', '%0A', '%22'], $value);
|
||||
}
|
||||
}
|
||||
|
||||
24
libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php
vendored
24
libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php
vendored
@@ -12,30 +12,12 @@ use Psr\Http\Message\StreamInterface;
|
||||
final class NoSeekStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Cannot seek a NoSeekStream');
|
||||
}
|
||||
|
||||
|
||||
21
libs/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php
vendored
Normal file
21
libs/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait NonSerializableStreamTrait
|
||||
{
|
||||
public function __serialize(): array
|
||||
{
|
||||
throw new \LogicException(static::class.' should never be serialized');
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
throw new \LogicException(static::class.' should never be unserialized');
|
||||
}
|
||||
}
|
||||
136
libs/vendor/guzzlehttp/psr7/src/PumpStream.php
vendored
136
libs/vendor/guzzlehttp/psr7/src/PumpStream.php
vendored
@@ -13,38 +13,37 @@ use Psr\Http\Message\StreamInterface;
|
||||
* number of bytes to read to the callable. The callable can choose to ignore
|
||||
* this value and return fewer or more bytes than requested. Any extra data
|
||||
* returned by the callable is buffered internally until drained using the
|
||||
* read() function of the PumpStream. The callable MUST return false or null
|
||||
* when there is no more data to read.
|
||||
* read() function of the PumpStream. The callable MUST return a non-empty
|
||||
* string when data is available, or false or null when there is no more data
|
||||
* to read.
|
||||
*
|
||||
* Userland callables that declare no parameters are tolerated by PHP, but
|
||||
* length-aware callables remain the recommended formal shape.
|
||||
*/
|
||||
final class PumpStream implements StreamInterface
|
||||
{
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var callable|null */
|
||||
private $source;
|
||||
|
||||
/** @var int|null */
|
||||
private $size;
|
||||
private ?int $size;
|
||||
|
||||
/** @var int */
|
||||
private $tellPos = 0;
|
||||
private int $tellPos = 0;
|
||||
|
||||
/** @var array */
|
||||
private $metadata;
|
||||
private array $metadata;
|
||||
|
||||
/** @var BufferStream */
|
||||
private $buffer;
|
||||
private BufferStream $buffer;
|
||||
|
||||
/**
|
||||
* @param (callable(): (string|false|null))|(callable(int): (string|false|null)) $source Source of the stream data. The callable receives
|
||||
* the suggested number of bytes to read, may ignore
|
||||
* that value, and may return fewer or more bytes.
|
||||
* Extra bytes are buffered. The callable MUST return
|
||||
* a string when called, or false|null on error or EOF.
|
||||
* Userland callables that declare no parameters are
|
||||
* tolerated by PHP, but length-aware callables remain
|
||||
* the recommended formal shape.
|
||||
* a non-empty string when producing data, or false|null
|
||||
* on error or EOF. Userland callables that declare no
|
||||
* parameters are tolerated by PHP, but length-aware
|
||||
* callables remain the recommended formal shape.
|
||||
* @param array{size?: int, metadata?: array} $options Stream options:
|
||||
* - metadata: Hash of metadata to use with stream.
|
||||
* - size: Size of the stream, if known.
|
||||
@@ -52,23 +51,25 @@ final class PumpStream implements StreamInterface
|
||||
public function __construct(callable $source, array $options = [])
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->size = $options['size'] ?? null;
|
||||
$this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size');
|
||||
$this->metadata = $options['metadata'] ?? [];
|
||||
$this->buffer = new BufferStream();
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->source = null;
|
||||
$this->size = null;
|
||||
$this->tellPos = 0;
|
||||
$this->metadata = [];
|
||||
$this->buffer = new BufferStream();
|
||||
|
||||
throw new \LogicException(static::class.' should never be unserialized');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return Utils::copyToString($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||
|
||||
return '';
|
||||
}
|
||||
return Utils::copyToString($this);
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
@@ -80,6 +81,7 @@ final class PumpStream implements StreamInterface
|
||||
{
|
||||
$this->tellPos = 0;
|
||||
$this->source = null;
|
||||
$this->buffer->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -109,26 +111,8 @@ final class PumpStream implements StreamInterface
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Cannot seek a PumpStream');
|
||||
}
|
||||
|
||||
@@ -137,17 +121,8 @@ final class PumpStream implements StreamInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Cannot write to a PumpStream');
|
||||
}
|
||||
|
||||
@@ -156,56 +131,35 @@ final class PumpStream implements StreamInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
$bufferLength = $this->buffer->getSize() ?? 0;
|
||||
|
||||
if ($length > $bufferLength) {
|
||||
$this->pump($length - $bufferLength);
|
||||
}
|
||||
|
||||
$data = $this->buffer->read($length);
|
||||
$readLen = strlen($data);
|
||||
$this->tellPos += $readLen;
|
||||
$remaining = $length - $readLen;
|
||||
|
||||
if ($remaining) {
|
||||
$this->pump($remaining);
|
||||
$data .= $this->buffer->read($remaining);
|
||||
$this->tellPos += strlen($data) - $readLen;
|
||||
}
|
||||
$this->tellPos = Integers::add($this->tellPos, strlen($data));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getContents(): string
|
||||
{
|
||||
$result = '';
|
||||
while (!$this->eof()) {
|
||||
$result .= $this->read(1000000);
|
||||
}
|
||||
|
||||
return $result;
|
||||
return Utils::copyToString($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null)
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$key) {
|
||||
if ($key === null) {
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
@@ -216,12 +170,18 @@ final class PumpStream implements StreamInterface
|
||||
{
|
||||
if ($this->source !== null) {
|
||||
do {
|
||||
/** @var string|false|null $data */
|
||||
$data = ($this->source)($length);
|
||||
if ($data === false || $data === null) {
|
||||
$this->source = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data === '') {
|
||||
throw new \RuntimeException('PumpStream source returned an empty string');
|
||||
}
|
||||
|
||||
$this->buffer->write($data);
|
||||
$length -= strlen($data);
|
||||
} while ($length > 0);
|
||||
|
||||
83
libs/vendor/guzzlehttp/psr7/src/Query.php
vendored
83
libs/vendor/guzzlehttp/psr7/src/Query.php
vendored
@@ -6,13 +6,17 @@ namespace GuzzleHttp\Psr7;
|
||||
|
||||
final class Query
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a query string into an associative array.
|
||||
*
|
||||
* If multiple values are found for the same key, the value of that key
|
||||
* value pair will become an array. This function does not parse nested
|
||||
* PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
|
||||
* will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
|
||||
* If multiple values are found for the same key, the value of that
|
||||
* key-value pair becomes an array. This function does not parse nested PHP
|
||||
* style arrays into an associative array. For example, `foo[a]=1&foo[b]=2`
|
||||
* will be parsed into `['foo[a]' => '1', 'foo[b]' => '2']`.
|
||||
*
|
||||
* @param string $str Query string to parse
|
||||
* @param int|bool $urlEncoding How the query string is encoded
|
||||
@@ -26,15 +30,19 @@ final class Query
|
||||
}
|
||||
|
||||
if ($urlEncoding === true) {
|
||||
$decoder = function ($value) {
|
||||
return rawurldecode(str_replace('+', ' ', (string) $value));
|
||||
$decoder = function (string $value): string {
|
||||
return \rawurldecode(str_replace('+', ' ', $value));
|
||||
};
|
||||
} elseif ($urlEncoding === PHP_QUERY_RFC3986) {
|
||||
$decoder = 'rawurldecode';
|
||||
$decoder = static function (string $value): string {
|
||||
return \rawurldecode($value);
|
||||
};
|
||||
} elseif ($urlEncoding === PHP_QUERY_RFC1738) {
|
||||
$decoder = 'urldecode';
|
||||
$decoder = static function (string $value): string {
|
||||
return \urldecode($value);
|
||||
};
|
||||
} else {
|
||||
$decoder = function ($str) {
|
||||
$decoder = function (string $str): string {
|
||||
return $str;
|
||||
};
|
||||
}
|
||||
@@ -57,11 +65,11 @@ final class Query
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a query string from an array of key value pairs.
|
||||
* Build a query string from an array of key-value pairs.
|
||||
*
|
||||
* This function can use the return value of `parse()` to build a query
|
||||
* string. This function does not modify the provided keys when an array is
|
||||
* encountered (like `http_build_query()` would).
|
||||
* encountered, unlike `http_build_query()`.
|
||||
*
|
||||
* @param array $params Query string parameters.
|
||||
* @param int|false $encoding Set to false to not encode,
|
||||
@@ -82,31 +90,33 @@ final class Query
|
||||
return $str;
|
||||
};
|
||||
} elseif ($encoding === PHP_QUERY_RFC3986) {
|
||||
$encoder = 'rawurlencode';
|
||||
$encoder = static function (string $value): string {
|
||||
return \rawurlencode($value);
|
||||
};
|
||||
} elseif ($encoding === PHP_QUERY_RFC1738) {
|
||||
$encoder = 'urlencode';
|
||||
$encoder = static function (string $value): string {
|
||||
return \urlencode($value);
|
||||
};
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid type');
|
||||
}
|
||||
|
||||
$castBool = $treatBoolsAsInts ? static function ($v) { return (int) $v; } : static function ($v) { return $v ? 'true' : 'false'; };
|
||||
|
||||
$qs = '';
|
||||
foreach ($params as $k => $v) {
|
||||
$k = $encoder((string) $k);
|
||||
if (!is_array($v)) {
|
||||
$qs .= $k;
|
||||
$v = is_bool($v) ? $castBool($v) : self::normalizeNonFiniteFloat($v);
|
||||
$v = self::normalizeValue($v, $treatBoolsAsInts);
|
||||
if ($v !== null) {
|
||||
$qs .= '='.$encoder((string) $v);
|
||||
$qs .= '='.$encoder($v);
|
||||
}
|
||||
$qs .= '&';
|
||||
} else {
|
||||
foreach ($v as $vv) {
|
||||
$qs .= $k;
|
||||
$vv = is_bool($vv) ? $castBool($vv) : self::normalizeNonFiniteFloat($vv);
|
||||
$vv = self::normalizeValue($vv, $treatBoolsAsInts);
|
||||
if ($vv !== null) {
|
||||
$qs .= '='.$encoder((string) $vv);
|
||||
$qs .= '='.$encoder($vv);
|
||||
}
|
||||
$qs .= '&';
|
||||
}
|
||||
@@ -117,25 +127,30 @@ final class Query
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts non-finite floats to the strings PHP coerces them to, as
|
||||
* implicit coercion of NAN emits a warning on PHP 8.5.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function normalizeNonFiniteFloat($value)
|
||||
private static function normalizeValue($value, bool $treatBoolsAsInts): ?string
|
||||
{
|
||||
if (is_float($value) && !is_finite($value)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.12',
|
||||
'Passing a non-finite float to Query::build() is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.'
|
||||
);
|
||||
|
||||
return is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
if (is_bool($value)) {
|
||||
return $treatBoolsAsInts ? (string) (int) $value : ($value ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (is_float($value) && !is_finite($value)) {
|
||||
throw new \InvalidArgumentException('Query string values must be finite; non-finite floats are not supported.');
|
||||
}
|
||||
|
||||
if (is_scalar($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
if (is_object($value) && method_exists($value, '__toString')) {
|
||||
return $value->__toString();
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Query string values must be scalar, null, or stringable objects');
|
||||
}
|
||||
}
|
||||
|
||||
137
libs/vendor/guzzlehttp/psr7/src/Request.php
vendored
137
libs/vendor/guzzlehttp/psr7/src/Request.php
vendored
@@ -16,14 +16,11 @@ class Request implements RequestInterface
|
||||
{
|
||||
use MessageTrait;
|
||||
|
||||
/** @var string */
|
||||
private $method;
|
||||
private string $method;
|
||||
|
||||
/** @var string|null */
|
||||
private $requestTarget;
|
||||
private ?string $requestTarget = null;
|
||||
|
||||
/** @var UriInterface */
|
||||
private $uri;
|
||||
private UriInterface $uri;
|
||||
|
||||
/**
|
||||
* @param string $method HTTP method
|
||||
@@ -45,9 +42,9 @@ class Request implements RequestInterface
|
||||
if (!$uri instanceof UriInterface) {
|
||||
$uri = new Uri($uri);
|
||||
}
|
||||
self::getRequestTargetFromUri($uri);
|
||||
|
||||
self::warnOnMethodCasingChange($method);
|
||||
$this->method = Utils::asciiToUpper($method);
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
$this->setHeaders($headers);
|
||||
$this->protocol = $version;
|
||||
@@ -67,30 +64,12 @@ class Request implements RequestInterface
|
||||
return $this->requestTarget;
|
||||
}
|
||||
|
||||
$target = $this->uri->getPath();
|
||||
if ($target === '') {
|
||||
$target = '/';
|
||||
}
|
||||
if ($this->uri->getQuery() != '') {
|
||||
$target .= '?'.$this->uri->getQuery();
|
||||
}
|
||||
|
||||
return $target;
|
||||
return self::getRequestTargetFromUri($this->uri);
|
||||
}
|
||||
|
||||
public function withRequestTarget($requestTarget): RequestInterface
|
||||
public function withRequestTarget(string $requestTarget): RequestInterface
|
||||
{
|
||||
$hasWhitespace = preg_match('#\s#', $requestTarget);
|
||||
|
||||
if ($hasWhitespace === false) {
|
||||
throw new \RuntimeException('Unable to validate request target: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($hasWhitespace === 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid request target provided; cannot contain whitespace'
|
||||
);
|
||||
}
|
||||
self::assertRequestTarget($requestTarget);
|
||||
|
||||
$new = clone $this;
|
||||
$new->requestTarget = $requestTarget;
|
||||
@@ -103,12 +82,11 @@ class Request implements RequestInterface
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function withMethod($method): RequestInterface
|
||||
public function withMethod(string $method): RequestInterface
|
||||
{
|
||||
$this->assertMethod($method);
|
||||
self::warnOnMethodCasingChange($method);
|
||||
$new = clone $this;
|
||||
$new->method = Utils::asciiToUpper($method);
|
||||
$new->method = $method;
|
||||
|
||||
return $new;
|
||||
}
|
||||
@@ -118,26 +96,30 @@ class Request implements RequestInterface
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
|
||||
public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface
|
||||
{
|
||||
if (!\is_bool($preserveHost)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to RequestInterface::withUri() is deprecated; guzzlehttp/psr7 3.0 requires bool for $preserveHost.',
|
||||
\get_debug_type($preserveHost)
|
||||
);
|
||||
$sameUri = $uri === $this->uri;
|
||||
|
||||
if (!$sameUri && $this->requestTarget === null) {
|
||||
self::getRequestTargetFromUri($uri);
|
||||
}
|
||||
|
||||
if ($uri === $this->uri) {
|
||||
$currentHost = $this->getHeaderLine('Host');
|
||||
$host = null;
|
||||
|
||||
if (!$preserveHost || $currentHost === '') {
|
||||
$host = $this->getHostFromUri($uri);
|
||||
}
|
||||
|
||||
if ($sameUri && ($host === null || $currentHost === $host)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->uri = $uri;
|
||||
|
||||
if (!$preserveHost || !isset($this->headerNames['host'])) {
|
||||
$new->updateHostFromUri();
|
||||
if ($host !== null) {
|
||||
$new->setHostHeader($host);
|
||||
}
|
||||
|
||||
return $new;
|
||||
@@ -145,20 +127,36 @@ class Request implements RequestInterface
|
||||
|
||||
private function updateHostFromUri(): void
|
||||
{
|
||||
$host = $this->uri->getHost();
|
||||
$host = $this->getHostFromUri($this->uri);
|
||||
|
||||
if ($host == '') {
|
||||
if ($host === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setHostHeader($host);
|
||||
}
|
||||
|
||||
private function getHostFromUri(UriInterface $uri): ?string
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
|
||||
if ($host === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri::assertValidHost($host);
|
||||
|
||||
if (($port = $this->uri->getPort()) !== null) {
|
||||
if (($port = $uri->getPort()) !== null) {
|
||||
$host .= ':'.$port;
|
||||
}
|
||||
|
||||
$this->assertValue($host);
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
private function setHostHeader(string $host): void
|
||||
{
|
||||
if (isset($this->headerNames['host'])) {
|
||||
$header = $this->headerNames['host'];
|
||||
} else {
|
||||
@@ -166,30 +164,47 @@ class Request implements RequestInterface
|
||||
$this->headerNames['host'] = 'Host';
|
||||
}
|
||||
// Ensure Host is the first header.
|
||||
// See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
|
||||
// See: https://datatracker.ietf.org/doc/html/rfc9110#section-7.2
|
||||
$this->headers = [$header => [$host]] + $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $method
|
||||
*/
|
||||
private function assertMethod($method): void
|
||||
private function assertMethod(string $method): void
|
||||
{
|
||||
if (!is_string($method) || $method === '') {
|
||||
throw new InvalidArgumentException('Method must be a non-empty string.');
|
||||
if (!Rfc9110::isToken($method)) {
|
||||
throw new InvalidArgumentException('Method must be a valid HTTP token.');
|
||||
}
|
||||
|
||||
$this->assertNoLineSeparators($method, 'Method');
|
||||
}
|
||||
|
||||
private static function warnOnMethodCasingChange(string $method): void
|
||||
private static function getRequestTargetFromUri(UriInterface $uri): string
|
||||
{
|
||||
if ($method !== Utils::asciiToUpper($method)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing a non-uppercase HTTP method is deprecated; guzzlehttp/psr7 3.0 preserves method casing and will no longer uppercase it. Normalize the method before constructing or modifying requests if uppercase is required.'
|
||||
$target = self::normalizePathForOriginForm($uri->getPath());
|
||||
if ($target === '') {
|
||||
$target = '/';
|
||||
}
|
||||
if ($uri->getQuery() != '') {
|
||||
$target .= '?'.$uri->getQuery();
|
||||
}
|
||||
|
||||
self::assertRequestTarget($target);
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
private static function assertRequestTarget(string $requestTarget): void
|
||||
{
|
||||
if (!Rfc9112::isValidRequestTarget($requestTarget)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid request target provided; cannot be empty or contain whitespace or control characters'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function normalizePathForOriginForm(string $path): string
|
||||
{
|
||||
if (str_starts_with($path, '//')) {
|
||||
return '/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
52
libs/vendor/guzzlehttp/psr7/src/Response.php
vendored
52
libs/vendor/guzzlehttp/psr7/src/Response.php
vendored
@@ -78,11 +78,9 @@ class Response implements ResponseInterface
|
||||
511 => 'Network Authentication Required',
|
||||
];
|
||||
|
||||
/** @var string */
|
||||
private $reasonPhrase;
|
||||
private string $reasonPhrase;
|
||||
|
||||
/** @var int */
|
||||
private $statusCode;
|
||||
private int $statusCode;
|
||||
|
||||
/**
|
||||
* @param int $status Status code
|
||||
@@ -114,7 +112,7 @@ class Response implements ResponseInterface
|
||||
$reasonPhrase = (string) $reason;
|
||||
}
|
||||
|
||||
$this->assertNoLineSeparators($reasonPhrase, 'Reason phrase');
|
||||
$this->assertReasonPhrase($reasonPhrase);
|
||||
$this->reasonPhrase = $reasonPhrase;
|
||||
|
||||
$this->protocol = $version;
|
||||
@@ -130,56 +128,32 @@ class Response implements ResponseInterface
|
||||
return $this->reasonPhrase;
|
||||
}
|
||||
|
||||
public function withStatus($code, $reasonPhrase = ''): ResponseInterface
|
||||
public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface
|
||||
{
|
||||
if (!\is_int($code) && \filter_var($code, \FILTER_VALIDATE_INT) !== false) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires int for $code.',
|
||||
\get_debug_type($code)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_string($reasonPhrase)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires string for $reasonPhrase.',
|
||||
\get_debug_type($reasonPhrase)
|
||||
);
|
||||
}
|
||||
|
||||
$this->assertStatusCodeIsInteger($code);
|
||||
$code = (int) $code;
|
||||
$this->assertStatusCodeRange($code);
|
||||
|
||||
$new = clone $this;
|
||||
$new->statusCode = $code;
|
||||
if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) {
|
||||
if ($reasonPhrase === '' && isset(self::PHRASES[$new->statusCode])) {
|
||||
$reasonPhrase = self::PHRASES[$new->statusCode];
|
||||
}
|
||||
$reasonPhrase = (string) $reasonPhrase;
|
||||
$this->assertNoLineSeparators($reasonPhrase, 'Reason phrase');
|
||||
$this->assertReasonPhrase($reasonPhrase);
|
||||
$new->reasonPhrase = $reasonPhrase;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $statusCode
|
||||
*/
|
||||
private function assertStatusCodeIsInteger($statusCode): void
|
||||
{
|
||||
if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
|
||||
throw new \InvalidArgumentException('Status code must be an integer value.');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertStatusCodeRange(int $statusCode): void
|
||||
{
|
||||
if ($statusCode < 100 || $statusCode >= 600) {
|
||||
throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertReasonPhrase(string $reasonPhrase): void
|
||||
{
|
||||
if (!Rfc9112::isValidReasonPhrase($reasonPhrase)) {
|
||||
throw new \InvalidArgumentException('Reason phrase must not contain invalid control characters.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
338
libs/vendor/guzzlehttp/psr7/src/Rfc3986.php
vendored
338
libs/vendor/guzzlehttp/psr7/src/Rfc3986.php
vendored
@@ -5,14 +5,21 @@ declare(strict_types=1);
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Syntax predicates and canonicalization helpers for the URI grammar defined
|
||||
* by RFC 3986.
|
||||
*/
|
||||
final class Rfc3986
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-delims for use in a regex.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
|
||||
|
||||
@@ -20,6 +27,335 @@ final class Rfc3986
|
||||
* Unreserved characters for use in a regex.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
|
||||
|
||||
/**
|
||||
* The two hex digits of a percent-encoded octet (the "3A" in "%3A"), for use in a regex.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const HEX_OCTET = '[A-Fa-f0-9]{2}';
|
||||
|
||||
/**
|
||||
* Whether the string is a valid URI scheme.
|
||||
*
|
||||
* Per RFC 3986 a scheme must start with a letter, followed by letters,
|
||||
* digits, `+`, `-`, or `.`. The empty string is also accepted, since a URI
|
||||
* reference may omit the scheme.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
|
||||
*/
|
||||
public static function isValidScheme(string $scheme): bool
|
||||
{
|
||||
return $scheme === '' || preg_match('/^[A-Za-z][A-Za-z0-9.+-]*$/D', $scheme) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the string is a valid URI host.
|
||||
*
|
||||
* Per RFC 3986 the host is `IP-literal / IPv4address / reg-name`. An empty
|
||||
* host is accepted, since the authority (and thus the host) may be empty.
|
||||
* Bracketed values are validated as IPv6 / IPvFuture literals; any other
|
||||
* value is rejected if it contains control characters, whitespace, an
|
||||
* authority or path delimiter (`/ ? # @ \`), an embedded colon denoting
|
||||
* a port, a malformed percent-sequence, or a percent-encoded octet that
|
||||
* decodes to one of those rejected bytes, to a bracket, or to `%` itself.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
|
||||
*/
|
||||
public static function isValidHost(string $host): bool
|
||||
{
|
||||
if ($host === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host);
|
||||
|
||||
if ($invalidHost === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($invalidHost === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_contains($host, '[') || str_contains($host, ']')) {
|
||||
return self::isValidIpLiteralHost($host);
|
||||
}
|
||||
|
||||
if (str_contains($host, ':')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !str_contains($host, '%') || self::hasValidHostPercentEncoding($host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the string is a valid port number (0-65535).
|
||||
*
|
||||
* RFC 3986 defines the port as `*DIGIT`, which also permits an empty port
|
||||
* and has no upper bound. This applies the stricter policy used throughout
|
||||
* the library instead: the value must be a non-empty run of digits (leading
|
||||
* zeros are accepted and normalized) that resolves to 0-65535.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
|
||||
*/
|
||||
public static function isValidPort(string $port): bool
|
||||
{
|
||||
if ($port === '' || !ctype_digit($port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalized = ltrim($port, '0');
|
||||
if ($normalized === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return strlen($normalized) <= 5 && (int) $normalized <= 0xFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RFC 5952 canonical form of a valid IPv6 address.
|
||||
*
|
||||
* The address must be a valid textual IPv6 address without brackets and
|
||||
* without a zone identifier, such as the inside of an IP-literal accepted
|
||||
* by `isValidHost()`. Canonicalization lowercases the hexadecimal fields,
|
||||
* suppresses leading zeros, and collapses the longest run of two or more
|
||||
* zero fields (the leftmost on a tie) with `::`. Embedded dotted-decimal
|
||||
* notation follows the rendering policy of BIND-derived `inet_ntop()`
|
||||
* implementations and curl: exactly the IPv4-mapped (`::ffff:0:0/96`) and
|
||||
* deprecated IPv4-compatible (`::/96`) layouts use it, while other
|
||||
* embedded-IPv4 forms, including translated (NAT64) well-known prefixes
|
||||
* such as `64:ff9b::/96` (RFC 6052), serialize in pure hexadecimal fields.
|
||||
*
|
||||
* Validation is strict and platform-independent: the address is checked
|
||||
* against the RFC 3986 `IPv6address` grammar with PHP's
|
||||
* `FILTER_VALIDATE_IP` filter and parsed in pure PHP, so spellings that
|
||||
* only some platform parsers accept, such as the zero-padded dotted octets
|
||||
* in `::ffff:192.168.001.001`, are rejected everywhere.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the address cannot be parsed.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc5952#section-4
|
||||
*/
|
||||
public static function canonicalizeIpv6(string $address): string
|
||||
{
|
||||
$canonical = self::tryCanonicalizeIpv6($address);
|
||||
if ($canonical === null) {
|
||||
throw new \InvalidArgumentException('Invalid IPv6 address');
|
||||
}
|
||||
|
||||
return $canonical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RFC 5952 canonical form of a valid IPv6 address, or null
|
||||
* when the address cannot be parsed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function tryCanonicalizeIpv6(string $address): ?string
|
||||
{
|
||||
// Platform parsers disagree on which spellings are valid: Apple libc
|
||||
// and OpenBSD inet_pton() accept zero-padded dotted octets such as
|
||||
// "::ffff:192.168.001.001", and macOS additionally accepts and silently
|
||||
// strips zone IDs ("fe80::1%eth0"), while glibc, musl, and PHP's own
|
||||
// filter reject both. Origin classification built on this helper must
|
||||
// fail closed and must not vary by operating system, so the address is
|
||||
// validated with the platform-independent FILTER_VALIDATE_IP filter and
|
||||
// parsed in pure PHP; no OS parser is consulted.
|
||||
if (\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$words = self::parseIpv6Words($address);
|
||||
if ($words === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the longest run of two or more zero fields; ties keep the
|
||||
// leftmost run per RFC 5952 section 4.2.3.
|
||||
$bestStart = 0;
|
||||
$bestLen = 0;
|
||||
$start = -1;
|
||||
foreach ($words as $i => $word) {
|
||||
if ($word !== 0) {
|
||||
$start = -1;
|
||||
continue;
|
||||
}
|
||||
if ($start === -1) {
|
||||
$start = $i;
|
||||
}
|
||||
if ($i - $start + 1 > $bestLen) {
|
||||
$bestStart = $start;
|
||||
$bestLen = $i - $start + 1;
|
||||
}
|
||||
}
|
||||
if ($bestLen < 2) {
|
||||
$bestLen = 0;
|
||||
}
|
||||
|
||||
// RFC 5952 section 5: embedded IPv4 notation for IPv4-mapped
|
||||
// (::ffff:0:0/96) and IPv4-compatible (::/96) addresses, the same
|
||||
// condition BIND-derived inet_ntop() and curl use. bestStart must be
|
||||
// zero: a five or six field zero run elsewhere is not an IPv4 prefix.
|
||||
$mixed = $bestStart === 0
|
||||
&& ($bestLen === 6 || ($bestLen === 5 && $words[5] === 0xFFFF));
|
||||
|
||||
$groups = [];
|
||||
for ($i = 0, $n = $mixed ? 6 : 8; $i < $n; ++$i) {
|
||||
$groups[] = dechex($words[$i]);
|
||||
}
|
||||
if ($mixed) {
|
||||
$groups[] = sprintf(
|
||||
'%d.%d.%d.%d',
|
||||
$words[6] >> 8,
|
||||
$words[6] & 0xFF,
|
||||
$words[7] >> 8,
|
||||
$words[7] & 0xFF
|
||||
);
|
||||
}
|
||||
|
||||
if ($bestLen === 0) {
|
||||
return implode(':', $groups);
|
||||
}
|
||||
|
||||
return implode(':', array_slice($groups, 0, $bestStart)).'::'.implode(':', array_slice($groups, $bestStart + $bestLen));
|
||||
}
|
||||
|
||||
private static function hasValidHostPercentEncoding(string $host): bool
|
||||
{
|
||||
// Mirror of the raw reg-name policy above for percent-encoded octets:
|
||||
// reject malformed sequences (RFC 3986 requires "%" HEXDIG HEXDIG) and
|
||||
// octets that decode to bytes the raw grammar rejects - C0 controls,
|
||||
// SP, DEL, the delimiters / ? # @ \ [ ], the port colon, and % itself.
|
||||
// Octets decoding to any other byte (unreserved, sub-delims, and
|
||||
// non-ASCII UTF-8 data) remain accepted.
|
||||
$invalidEncoding = preg_match(
|
||||
'/%(?!'.self::HEX_OCTET.')|%(?:[01][0-9A-Fa-f]|2[035F]|3[AF]|40|5[BCD]|7F)/i',
|
||||
$host
|
||||
);
|
||||
|
||||
return $invalidEncoding === 0;
|
||||
}
|
||||
|
||||
private static function isValidIpLiteralHost(string $host): bool
|
||||
{
|
||||
if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$address = substr($host, 1, -1);
|
||||
if (\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// RFC 6874 IPv6 zone identifiers are intentionally not supported here.
|
||||
// Bracketed hosts are validated as IPv6 or IPvFuture only.
|
||||
return preg_match('/^v[0-9a-f]+\.['.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a textual IPv6 address into its eight 16-bit words, or null
|
||||
* when the text is not a structurally valid address.
|
||||
*
|
||||
* The grammar enforced here is the RFC 3986 `IPv6address` rule: one to four
|
||||
* hexadecimal digits per field, at most one `::` eliding one or more zero
|
||||
* fields, and an optional dotted-decimal tail of four octets (0-255, no
|
||||
* leading zeros) as the final 32 bits. FILTER_VALIDATE_IP accepts exactly
|
||||
* this grammar, so the filter guard in tryCanonicalizeIpv6() and this
|
||||
* parser always agree and the null paths here can only fail closed.
|
||||
*
|
||||
* @return list<int>|null
|
||||
*/
|
||||
private static function parseIpv6Words(string $address): ?array
|
||||
{
|
||||
// A dotted-decimal tail is only valid as the final 32 bits, after the
|
||||
// final colon. Rewrite it into its two hexadecimal fields so the
|
||||
// remainder of the parse handles hexadecimal fields only.
|
||||
$dot = strpos($address, '.');
|
||||
if ($dot !== false) {
|
||||
$colon = strrpos($address, ':');
|
||||
if ($colon === false || $colon > $dot) {
|
||||
return null;
|
||||
}
|
||||
$octets = explode('.', substr($address, $colon + 1));
|
||||
if (count($octets) !== 4) {
|
||||
return null;
|
||||
}
|
||||
$bytes = [];
|
||||
foreach ($octets as $octet) {
|
||||
if ($octet === '' || strlen($octet) > 3 || !ctype_digit($octet)) {
|
||||
return null;
|
||||
}
|
||||
if ($octet[0] === '0' && $octet !== '0') {
|
||||
return null;
|
||||
}
|
||||
$byte = (int) $octet;
|
||||
if ($byte > 255) {
|
||||
return null;
|
||||
}
|
||||
$bytes[] = $byte;
|
||||
}
|
||||
$address = substr($address, 0, $colon + 1)
|
||||
.dechex(($bytes[0] << 8) | $bytes[1])
|
||||
.':'
|
||||
.dechex(($bytes[2] << 8) | $bytes[3]);
|
||||
}
|
||||
|
||||
$halves = explode('::', $address);
|
||||
if (count($halves) > 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$head = self::parseHexFields($halves[0]);
|
||||
if ($head === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count($halves) === 1) {
|
||||
return count($head) === 8 ? $head : null;
|
||||
}
|
||||
|
||||
$tail = self::parseHexFields($halves[1]);
|
||||
if ($tail === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The "::" must elide at least one zero field.
|
||||
$elided = 8 - count($head) - count($tail);
|
||||
if ($elided < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_merge($head, array_fill(0, $elided, 0), $tail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a colon-separated run of 16-bit hexadecimal fields, or null
|
||||
* when a field is empty, longer than four digits, or not hexadecimal.
|
||||
*
|
||||
* @return list<int>|null
|
||||
*/
|
||||
private static function parseHexFields(string $fields): ?array
|
||||
{
|
||||
if ($fields === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$words = [];
|
||||
foreach (explode(':', $fields) as $field) {
|
||||
if ($field === '' || strlen($field) > 4 || !ctype_xdigit($field)) {
|
||||
return null;
|
||||
}
|
||||
$words[] = intval($field, 16);
|
||||
}
|
||||
|
||||
return $words;
|
||||
}
|
||||
}
|
||||
|
||||
112
libs/vendor/guzzlehttp/psr7/src/Rfc7230.php
vendored
112
libs/vendor/guzzlehttp/psr7/src/Rfc7230.php
vendored
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Rfc7230
|
||||
{
|
||||
/**
|
||||
* Header related regular expressions (based on amphp/http package)
|
||||
*
|
||||
* Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
|
||||
*
|
||||
* @see https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
|
||||
*
|
||||
* @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
|
||||
*/
|
||||
public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
|
||||
public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int|null}|null
|
||||
*/
|
||||
public static function parseHostHeader(string $authority): ?array
|
||||
{
|
||||
if ($authority === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = $authority;
|
||||
$port = null;
|
||||
|
||||
if ($authority[0] === '[') {
|
||||
$closingBracket = strpos($authority, ']');
|
||||
if ($closingBracket === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = substr($authority, 0, $closingBracket + 1);
|
||||
$remainder = substr($authority, $closingBracket + 1);
|
||||
if ($remainder !== '') {
|
||||
if ($remainder[0] !== ':') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$port = self::parseAuthorityPort(substr($remainder, 1));
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} elseif (false !== ($colon = strpos($authority, ':'))) {
|
||||
$host = substr($authority, 0, $colon);
|
||||
$port = self::parseAuthorityPort(substr($authority, $colon + 1));
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($host === '' || !self::isValidHostHeaderHost($host)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$host, $port];
|
||||
}
|
||||
|
||||
private static function isValidHostHeaderHost(string $host): bool
|
||||
{
|
||||
$invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host);
|
||||
|
||||
if ($invalidHost === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($invalidHost === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($host, '[') !== false || strpos($host, ']') !== false) {
|
||||
if ($host[0] !== '[' || substr($host, -1) !== ']') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$address = substr($host, 1, -1);
|
||||
|
||||
return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false
|
||||
|| preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1;
|
||||
}
|
||||
|
||||
return strpos($host, ':') === false;
|
||||
}
|
||||
|
||||
private static function parseAuthorityPort(string $port): ?int
|
||||
{
|
||||
if ($port === '' || !ctype_digit($port)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = ltrim($port, '0');
|
||||
if ($normalized === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $normalized;
|
||||
}
|
||||
}
|
||||
41
libs/vendor/guzzlehttp/psr7/src/Rfc9110.php
vendored
Normal file
41
libs/vendor/guzzlehttp/psr7/src/Rfc9110.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Rfc9110
|
||||
{
|
||||
/**
|
||||
* A token for use in a regular expression.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2
|
||||
*/
|
||||
public const TOKEN_PATTERN = '[!#$%&\'*+.^_`|~0-9A-Za-z-]+';
|
||||
|
||||
/**
|
||||
* A field value for use in a regular expression.
|
||||
*
|
||||
* Obsolete line folding is intentionally excluded.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
|
||||
*/
|
||||
public const FIELD_VALUE_PATTERN = '[\x09\x20-\x7E\x80-\xFF]*';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function isToken(string $value): bool
|
||||
{
|
||||
return preg_match('/^'.self::TOKEN_PATTERN.'$/D', $value) === 1;
|
||||
}
|
||||
|
||||
public static function isFieldValue(string $value): bool
|
||||
{
|
||||
return preg_match('/^'.self::FIELD_VALUE_PATTERN.'$/D', $value) === 1;
|
||||
}
|
||||
}
|
||||
126
libs/vendor/guzzlehttp/psr7/src/Rfc9112.php
vendored
Normal file
126
libs/vendor/guzzlehttp/psr7/src/Rfc9112.php
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Rfc9112
|
||||
{
|
||||
/**
|
||||
* An HTTP protocol version for use in a regular expression.
|
||||
*/
|
||||
public const PROTOCOL_VERSION_PATTERN = '\d+(?:\.\d+)?';
|
||||
|
||||
/**
|
||||
* The request-target bytes accepted by the HTTP/1 start-line grammar for
|
||||
* use in a regular expression.
|
||||
*/
|
||||
public const REQUEST_TARGET_PATTERN = '[^\x00-\x20\x7F]+';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Header related regular expressions (based on amphp/http package)
|
||||
*
|
||||
* Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
|
||||
*
|
||||
* @see https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
|
||||
*
|
||||
* @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
|
||||
*/
|
||||
public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
|
||||
public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
|
||||
|
||||
public static function isValidProtocolVersion(string $version): bool
|
||||
{
|
||||
return preg_match('/^'.self::PROTOCOL_VERSION_PATTERN.'$/D', $version) === 1;
|
||||
}
|
||||
|
||||
public static function isValidRequestTarget(string $target): bool
|
||||
{
|
||||
return preg_match('/^'.self::REQUEST_TARGET_PATTERN.'$/D', $target) === 1;
|
||||
}
|
||||
|
||||
public static function isValidReasonPhrase(string $reasonPhrase): bool
|
||||
{
|
||||
return Rfc9110::isFieldValue($reasonPhrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int|null}|null
|
||||
*/
|
||||
public static function parseHostHeader(string $authority): ?array
|
||||
{
|
||||
if ($authority === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = $authority;
|
||||
$port = null;
|
||||
|
||||
if (str_starts_with($authority, '[')) {
|
||||
$closingBracket = strpos($authority, ']');
|
||||
if ($closingBracket === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$host = substr($authority, 0, $closingBracket + 1);
|
||||
$remainder = substr($authority, $closingBracket + 1);
|
||||
if ($remainder !== '') {
|
||||
if (!str_starts_with($remainder, ':')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$port = self::parsePort(substr($remainder, 1));
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} elseif (false !== ($colon = strpos($authority, ':'))) {
|
||||
$host = substr($authority, 0, $colon);
|
||||
$port = self::parsePort(substr($authority, $colon + 1));
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($host === '' || !Rfc3986::isValidHost($host)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$host, $port];
|
||||
}
|
||||
|
||||
public static function isAbsoluteFormRequestTarget(string $target): bool
|
||||
{
|
||||
return preg_match('/^[A-Za-z][A-Za-z0-9+.-]*:\/\//D', $target) === 1;
|
||||
}
|
||||
|
||||
public static function isAsteriskFormRequestTarget(string $method, string $target): bool
|
||||
{
|
||||
return $method === 'OPTIONS' && $target === '*';
|
||||
}
|
||||
|
||||
public static function isConnectAuthorityFormRequestTarget(string $method, string $target): bool
|
||||
{
|
||||
return $method === 'CONNECT' && strpbrk($target, '/?#') === false;
|
||||
}
|
||||
|
||||
public static function parsePort(string $port): ?int
|
||||
{
|
||||
if (!Rfc3986::isValidPort($port)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// A zero port is valid per RFC 3986 but meaningless for an HTTP
|
||||
// authority, so reject it on top of the generic syntax check.
|
||||
$parsed = (int) ltrim($port, '0');
|
||||
|
||||
return $parsed === 0 ? null : $parsed;
|
||||
}
|
||||
}
|
||||
302
libs/vendor/guzzlehttp/psr7/src/ServerRequest.php
vendored
302
libs/vendor/guzzlehttp/psr7/src/ServerRequest.php
vendored
@@ -26,35 +26,20 @@ use Psr\Http\Message\UriInterface;
|
||||
*/
|
||||
class ServerRequest extends Request implements ServerRequestInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributes = [];
|
||||
private array $attributes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cookieParams = [];
|
||||
private array $cookieParams = [];
|
||||
|
||||
/**
|
||||
* @var array|object|null
|
||||
*/
|
||||
private $parsedBody;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $queryParams = [];
|
||||
private array $queryParams = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $serverParams;
|
||||
private array $serverParams;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $uploadedFiles = [];
|
||||
private array $uploadedFiles = [];
|
||||
|
||||
/**
|
||||
* @param string $method HTTP method
|
||||
@@ -70,6 +55,7 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
array $headers = [],
|
||||
$body = null,
|
||||
string $version = '1.1',
|
||||
#[\SensitiveParameter]
|
||||
array $serverParams = []
|
||||
) {
|
||||
$this->serverParams = $serverParams;
|
||||
@@ -86,73 +72,7 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
*/
|
||||
public static function normalizeFiles(array $files): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($files as $key => $value) {
|
||||
if ($value instanceof UploadedFileInterface) {
|
||||
$normalized[$key] = $value;
|
||||
} elseif (is_array($value) && isset($value['tmp_name'])) {
|
||||
$normalized[$key] = self::createUploadedFileFromSpec($value);
|
||||
} elseif (is_array($value)) {
|
||||
$normalized[$key] = self::normalizeFiles($value);
|
||||
continue;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid value in files specification');
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an UploadedFile instance from a $_FILES specification.
|
||||
*
|
||||
* If the specification represents an array of values, this method will
|
||||
* delegate to normalizeNestedFileSpec() and return that return value.
|
||||
*
|
||||
* @param array $value $_FILES struct
|
||||
*
|
||||
* @return UploadedFileInterface|UploadedFileInterface[]
|
||||
*/
|
||||
private static function createUploadedFileFromSpec(array $value)
|
||||
{
|
||||
if (is_array($value['tmp_name'])) {
|
||||
return self::normalizeNestedFileSpec($value);
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
$value['tmp_name'],
|
||||
(int) $value['size'],
|
||||
(int) $value['error'],
|
||||
$value['name'],
|
||||
$value['type']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an array of file specifications.
|
||||
*
|
||||
* Loops through all nested files and returns a normalized array of
|
||||
* UploadedFileInterface instances.
|
||||
*
|
||||
* @return UploadedFileInterface[]
|
||||
*/
|
||||
private static function normalizeNestedFileSpec(array $files = []): array
|
||||
{
|
||||
$normalizedFiles = [];
|
||||
|
||||
foreach (array_keys($files['tmp_name']) as $key) {
|
||||
$spec = [
|
||||
'tmp_name' => $files['tmp_name'][$key],
|
||||
'size' => $files['size'][$key] ?? null,
|
||||
'error' => $files['error'][$key] ?? null,
|
||||
'name' => $files['name'][$key] ?? null,
|
||||
'type' => $files['type'][$key] ?? null,
|
||||
];
|
||||
$normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
|
||||
}
|
||||
|
||||
return $normalizedFiles;
|
||||
return UploadedFileNormalizer::normalize($files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,79 +85,20 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
*/
|
||||
public static function fromGlobals(): ServerRequestInterface
|
||||
{
|
||||
$method = Utils::asciiToUpper(self::getServerParam('REQUEST_METHOD') ?? 'GET');
|
||||
$headers = self::removeInvalidHostHeader(self::getAllHeaders());
|
||||
$uri = self::getUriFromGlobals();
|
||||
$body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
|
||||
$serverProtocol = self::getServerParam('SERVER_PROTOCOL');
|
||||
$protocol = $serverProtocol !== null ? str_replace('HTTP/', '', $serverProtocol) : '1.1';
|
||||
return ServerRequestGlobalsFactory::fromArrays(
|
||||
$_SERVER,
|
||||
$_GET,
|
||||
$_POST,
|
||||
$_COOKIE,
|
||||
$_FILES,
|
||||
static function () {
|
||||
if (!\function_exists('apache_request_headers')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
|
||||
|
||||
return $serverRequest
|
||||
->withCookieParams($_COOKIE)
|
||||
->withQueryParams($_GET)
|
||||
->withParsedBody($_POST)
|
||||
->withUploadedFiles(self::normalizeFiles($_FILES));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function getAllHeaders(): array
|
||||
{
|
||||
return self::normalizeHeaderValues(getallheaders());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $headers
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function normalizeHeaderValues(array $headers): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
|
||||
$normalized[$name] = (string) $value;
|
||||
return \apache_request_headers();
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function getServerParam(string $key): ?string
|
||||
{
|
||||
return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, string> $headers
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function removeInvalidHostHeader(array $headers): array
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
if (Utils::asciiToLower((string) $name) !== 'host') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Rfc7230::parseHostHeader($value) === null) {
|
||||
unset($headers[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string|null, 1: int|null}
|
||||
*/
|
||||
private static function extractHostAndPortFromAuthority(string $authority): array
|
||||
{
|
||||
return Rfc7230::parseHostHeader($authority) ?? [null, null];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,51 +106,7 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
*/
|
||||
public static function getUriFromGlobals(): UriInterface
|
||||
{
|
||||
$uri = new Uri('');
|
||||
|
||||
$https = self::getServerParam('HTTPS');
|
||||
$uri = $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http');
|
||||
|
||||
$hasPort = false;
|
||||
$authority = self::getServerParam('HTTP_HOST');
|
||||
if ($authority !== null) {
|
||||
[$host, $port] = self::extractHostAndPortFromAuthority($authority);
|
||||
if ($host !== null) {
|
||||
$uri = $uri->withHost($host);
|
||||
}
|
||||
|
||||
if ($port !== null) {
|
||||
$hasPort = true;
|
||||
$uri = $uri->withPort($port);
|
||||
}
|
||||
} elseif (($serverName = self::getServerParam('SERVER_NAME')) !== null) {
|
||||
$uri = $uri->withHost($serverName);
|
||||
} elseif (($serverAddr = self::getServerParam('SERVER_ADDR')) !== null) {
|
||||
$uri = $uri->withHost($serverAddr);
|
||||
}
|
||||
|
||||
$serverPort = self::getServerParam('SERVER_PORT');
|
||||
if (!$hasPort && $serverPort !== null && preg_match('/^[+-]?\d+$/D', $serverPort) === 1) {
|
||||
$uri = $uri->withPort((int) $serverPort);
|
||||
}
|
||||
|
||||
$hasQuery = false;
|
||||
$requestUri = self::getServerParam('REQUEST_URI');
|
||||
if ($requestUri !== null) {
|
||||
$requestUriParts = explode('?', $requestUri, 2);
|
||||
$uri = $uri->withPath($requestUriParts[0]);
|
||||
if (isset($requestUriParts[1])) {
|
||||
$hasQuery = true;
|
||||
$uri = $uri->withQuery($requestUriParts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$queryString = self::getServerParam('QUERY_STRING');
|
||||
if (!$hasQuery && $queryString !== null) {
|
||||
$uri = $uri->withQuery($queryString);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
return ServerRequestGlobalsFactory::getUriFromServerParams($_SERVER);
|
||||
}
|
||||
|
||||
public function getServerParams(): array
|
||||
@@ -304,12 +121,10 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
|
||||
public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
|
||||
{
|
||||
$invalidUploadedFileFound = false;
|
||||
$invalidUploadedFile = null;
|
||||
$stack = [$uploadedFiles];
|
||||
|
||||
while ($stack !== []) {
|
||||
foreach (\array_pop($stack) as $uploadedFile) {
|
||||
for ($i = 0; $i < \count($stack); ++$i) {
|
||||
foreach ($stack[$i] as $uploadedFile) {
|
||||
if ($uploadedFile instanceof UploadedFileInterface) {
|
||||
continue;
|
||||
}
|
||||
@@ -319,22 +134,13 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$invalidUploadedFileFound = true;
|
||||
$invalidUploadedFile = $uploadedFile;
|
||||
|
||||
break 2;
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid uploaded file tree; expected UploadedFileInterface instances but %s provided.',
|
||||
\get_debug_type($uploadedFile)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if ($invalidUploadedFileFound) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s inside ServerRequestInterface::withUploadedFiles() is deprecated; guzzlehttp/psr7 3.0 requires an UploadedFileInterface[] tree.',
|
||||
\get_debug_type($invalidUploadedFile)
|
||||
);
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->uploadedFiles = $uploadedFiles;
|
||||
|
||||
@@ -346,8 +152,10 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
return $this->cookieParams;
|
||||
}
|
||||
|
||||
public function withCookieParams(array $cookies): ServerRequestInterface
|
||||
{
|
||||
public function withCookieParams(
|
||||
#[\SensitiveParameter]
|
||||
array $cookies
|
||||
): ServerRequestInterface {
|
||||
$new = clone $this;
|
||||
$new->cookieParams = $cookies;
|
||||
|
||||
@@ -378,12 +186,7 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
public function withParsedBody($data): ServerRequestInterface
|
||||
{
|
||||
if ($data !== null && !\is_array($data) && !\is_object($data)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ServerRequestInterface::withParsedBody() is deprecated; guzzlehttp/psr7 3.0 requires array|object|null.',
|
||||
\get_debug_type($data)
|
||||
);
|
||||
throw new InvalidArgumentException('Parsed body must be an array, object, or null.');
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
@@ -400,58 +203,31 @@ class ServerRequest extends Request implements ServerRequestInterface
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($attribute, $default = null)
|
||||
public function getAttribute(string $name, $default = null)
|
||||
{
|
||||
if (!\is_string($attribute)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ServerRequestInterface::getAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.',
|
||||
\get_debug_type($attribute)
|
||||
);
|
||||
}
|
||||
|
||||
if (false === array_key_exists($attribute, $this->attributes)) {
|
||||
if (false === array_key_exists($name, $this->attributes)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->attributes[$attribute];
|
||||
return $this->attributes[$name];
|
||||
}
|
||||
|
||||
public function withAttribute($attribute, $value): ServerRequestInterface
|
||||
public function withAttribute(string $name, $value): ServerRequestInterface
|
||||
{
|
||||
if (!\is_string($attribute)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ServerRequestInterface::withAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.',
|
||||
\get_debug_type($attribute)
|
||||
);
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->attributes[$attribute] = $value;
|
||||
$new->attributes[$name] = $value;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withoutAttribute($attribute): ServerRequestInterface
|
||||
public function withoutAttribute(string $name): ServerRequestInterface
|
||||
{
|
||||
if (!\is_string($attribute)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to ServerRequestInterface::withoutAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.',
|
||||
\get_debug_type($attribute)
|
||||
);
|
||||
}
|
||||
|
||||
if (false === array_key_exists($attribute, $this->attributes)) {
|
||||
if (false === array_key_exists($name, $this->attributes)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
unset($new->attributes[$attribute]);
|
||||
unset($new->attributes[$name]);
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
504
libs/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php
vendored
Normal file
504
libs/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php
vendored
Normal file
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ServerRequestGlobalsFactory
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server Typically the $_SERVER superglobal
|
||||
* @param array<array-key, mixed> $query Typically the $_GET superglobal
|
||||
* @param array<array-key, mixed> $post Typically the $_POST superglobal
|
||||
* @param array<array-key, mixed> $cookies Typically the $_COOKIE superglobal
|
||||
* @param array<array-key, mixed> $files Typically the $_FILES superglobal
|
||||
* @param (callable(): mixed)|null $headerProvider
|
||||
*/
|
||||
public static function fromArrays(
|
||||
#[\SensitiveParameter]
|
||||
array $server,
|
||||
array $query,
|
||||
array $post,
|
||||
#[\SensitiveParameter]
|
||||
array $cookies,
|
||||
array $files,
|
||||
?callable $headerProvider = null
|
||||
): ServerRequestInterface {
|
||||
$method = self::getRequestMethodFromServer($server);
|
||||
$headers = self::removeInvalidHostHeader(self::getAllHeaders($server, $headerProvider));
|
||||
[$uri, $requestTarget] = self::getUriAndRequestTargetFromServer($server, $method);
|
||||
$body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
|
||||
|
||||
$serverRequest = new ServerRequest($method, $uri, $headers, $body, self::getProtocolFromServer($server), $server);
|
||||
if ($requestTarget !== null) {
|
||||
/** @var ServerRequestInterface $serverRequest */
|
||||
$serverRequest = $serverRequest->withRequestTarget($requestTarget);
|
||||
}
|
||||
|
||||
return $serverRequest
|
||||
->withCookieParams($cookies)
|
||||
->withQueryParams($query)
|
||||
->withParsedBody($post)
|
||||
->withUploadedFiles(UploadedFileNormalizer::normalize($files));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server Typically the $_SERVER superglobal
|
||||
*/
|
||||
public static function getUriFromServerParams(
|
||||
#[\SensitiveParameter]
|
||||
array $server
|
||||
): UriInterface {
|
||||
$method = self::getRequestMethodFromServer($server);
|
||||
|
||||
return self::getUriAndRequestTargetFromServer($server, $method)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
* @param (callable(): mixed)|null $headerProvider
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function getAllHeaders(
|
||||
#[\SensitiveParameter]
|
||||
array $server,
|
||||
?callable $headerProvider
|
||||
): array {
|
||||
$headers = $headerProvider !== null ? $headerProvider() : false;
|
||||
|
||||
if (!is_array($headers)) {
|
||||
$headers = self::getHeadersFromServer($server);
|
||||
}
|
||||
|
||||
return self::normalizeHeaderValues($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $headers
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function normalizeHeaderValues(
|
||||
#[\SensitiveParameter]
|
||||
array $headers
|
||||
): array {
|
||||
$normalized = [];
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
|
||||
$normalized[$name] = (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server Typically the $_SERVER superglobal
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function getHeadersFromServer(array $server): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
$copyServer = [
|
||||
'CONTENT_TYPE' => 'Content-Type',
|
||||
'CONTENT_LENGTH' => 'Content-Length',
|
||||
'CONTENT_MD5' => 'Content-Md5',
|
||||
];
|
||||
|
||||
foreach ($server as $key => $value) {
|
||||
if (!is_string($key) || !is_string($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($key, 'HTTP_')) {
|
||||
$header = substr($key, 5);
|
||||
|
||||
if (isset($copyServer[$header], $server[$header]) && is_string($server[$header])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode(' ', Utils::asciiToLower(str_replace('_', ' ', $header)));
|
||||
foreach ($parts as $i => $part) {
|
||||
$parts[$i] = Utils::asciiUcFirst($part);
|
||||
}
|
||||
$header = implode('-', $parts);
|
||||
$headers[$header] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($copyServer[$key])) {
|
||||
$headers[$copyServer[$key]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($headers['Authorization'])) {
|
||||
if (isset($server['REDIRECT_HTTP_AUTHORIZATION']) && is_string($server['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||
$headers['Authorization'] = $server['REDIRECT_HTTP_AUTHORIZATION'];
|
||||
} elseif (isset($server['PHP_AUTH_USER']) && is_string($server['PHP_AUTH_USER'])) {
|
||||
$password = isset($server['PHP_AUTH_PW']) && is_string($server['PHP_AUTH_PW'])
|
||||
? $server['PHP_AUTH_PW']
|
||||
: '';
|
||||
|
||||
$headers['Authorization'] = 'Basic '.base64_encode($server['PHP_AUTH_USER'].':'.$password);
|
||||
} elseif (isset($server['PHP_AUTH_DIGEST']) && is_string($server['PHP_AUTH_DIGEST'])) {
|
||||
$headers['Authorization'] = $server['PHP_AUTH_DIGEST'];
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, string> $headers
|
||||
*
|
||||
* @return array<array-key, string>
|
||||
*/
|
||||
private static function removeInvalidHostHeader(array $headers): array
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
if (Utils::asciiToLower((string) $name) !== 'host') {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$host] = self::extractHostAndPortFromAuthority($value);
|
||||
if ($host === null) {
|
||||
unset($headers[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*/
|
||||
private static function getServerParam(array $server, string $key): ?string
|
||||
{
|
||||
return isset($server[$key]) && is_string($server[$key]) ? $server[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*/
|
||||
private static function getRequestMethodFromServer(array $server): string
|
||||
{
|
||||
return Utils::asciiToUpper(self::getServerParam($server, 'REQUEST_METHOD') ?? 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*/
|
||||
private static function getProtocolFromServer(array $server): string
|
||||
{
|
||||
$serverProtocol = self::getServerParam($server, 'SERVER_PROTOCOL');
|
||||
if ($serverProtocol === null) {
|
||||
return '1.1';
|
||||
}
|
||||
|
||||
return str_starts_with($serverProtocol, 'HTTP/') ? substr($serverProtocol, 5) : $serverProtocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string|null, 1: int|null}
|
||||
*/
|
||||
private static function extractHostAndPortFromAuthority(string $authority): array
|
||||
{
|
||||
return Rfc9112::parseHostHeader($authority) ?? [null, null];
|
||||
}
|
||||
|
||||
private static function parseServerPort(string $port): int
|
||||
{
|
||||
$parsed = Rfc9112::parsePort($port);
|
||||
if ($parsed === null) {
|
||||
throw new InvalidArgumentException('Invalid SERVER_PORT; expected an integer between 1 and 65535.');
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
private static function withHostFromServer(UriInterface $uri, ?string $host): ?UriInterface
|
||||
{
|
||||
if ($host === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return $uri->withHost($host);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*/
|
||||
private static function getUriWithSchemeFromServer(array $server): UriInterface
|
||||
{
|
||||
$uri = new Uri('');
|
||||
|
||||
$https = self::getServerParam($server, 'HTTPS');
|
||||
|
||||
return $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*/
|
||||
private static function getAuthorityUriFromServer(
|
||||
#[\SensitiveParameter]
|
||||
array $server
|
||||
): UriInterface {
|
||||
$uri = self::getUriWithSchemeFromServer($server);
|
||||
|
||||
$hasPort = false;
|
||||
$hasHost = false;
|
||||
$authority = self::getServerParam($server, 'HTTP_HOST');
|
||||
if ($authority !== null) {
|
||||
[$host, $port] = self::extractHostAndPortFromAuthority($authority);
|
||||
if ($host !== null) {
|
||||
$hostUri = self::withHostFromServer($uri, $host);
|
||||
if ($hostUri !== null) {
|
||||
$uri = $hostUri;
|
||||
$hasHost = true;
|
||||
|
||||
if ($port !== null) {
|
||||
$hasPort = true;
|
||||
$uri = $uri->withPort($port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['SERVER_NAME', 'SERVER_ADDR'] as $serverParam) {
|
||||
if ($hasHost) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hostUri = self::withHostFromServer($uri, self::getServerParam($server, $serverParam));
|
||||
if ($hostUri !== null) {
|
||||
$uri = $hostUri;
|
||||
$hasHost = true;
|
||||
}
|
||||
}
|
||||
|
||||
$serverPort = self::getServerParam($server, 'SERVER_PORT');
|
||||
if (!$hasPort && $serverPort !== null) {
|
||||
$uri = $uri->withPort(self::parseServerPort($serverPort));
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $server
|
||||
*
|
||||
* @return array{0: UriInterface, 1: string|null}
|
||||
*/
|
||||
private static function getUriAndRequestTargetFromServer(
|
||||
#[\SensitiveParameter]
|
||||
array $server,
|
||||
string $method
|
||||
): array {
|
||||
$requestUri = self::getServerParam($server, 'REQUEST_URI');
|
||||
$queryString = self::getServerParam($server, 'QUERY_STRING');
|
||||
|
||||
if ($requestUri !== null) {
|
||||
$connectAuthority = self::parseConnectAuthorityFormRequestTarget($method, $requestUri);
|
||||
if ($connectAuthority !== null) {
|
||||
[$host, $port] = $connectAuthority;
|
||||
$uri = self::getUriWithSchemeFromServer($server);
|
||||
|
||||
return [
|
||||
$uri->withHost($host)->withPort($port)->withPath('')->withQuery(''),
|
||||
$requestUri,
|
||||
];
|
||||
}
|
||||
|
||||
$absoluteForm = self::getAbsoluteFormUriAndRequestTarget($requestUri, $queryString);
|
||||
if ($absoluteForm !== null) {
|
||||
return $absoluteForm;
|
||||
}
|
||||
}
|
||||
|
||||
$uri = self::getAuthorityUriFromServer($server);
|
||||
|
||||
if ($requestUri === null) {
|
||||
if ($queryString !== null) {
|
||||
$uri = $uri->withQuery($queryString);
|
||||
}
|
||||
|
||||
return [$uri, null];
|
||||
}
|
||||
|
||||
if (Rfc9112::isAsteriskFormRequestTarget($method, $requestUri)) {
|
||||
return [$uri->withPath('')->withQuery(''), '*'];
|
||||
}
|
||||
|
||||
[$path, $query, $hasQuery] = self::splitRequestTargetQuery($requestUri);
|
||||
$uri = $uri->withPath(self::normalizeOriginFormPathFromServer($path));
|
||||
|
||||
if ($hasQuery) {
|
||||
$uri = $uri->withQuery($query);
|
||||
} elseif ($queryString !== null) {
|
||||
$uri = $uri->withQuery($queryString);
|
||||
}
|
||||
|
||||
return [$uri, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: UriInterface, 1: string}|null
|
||||
*/
|
||||
private static function getAbsoluteFormUriAndRequestTarget(
|
||||
#[\SensitiveParameter]
|
||||
string $requestUri,
|
||||
?string $queryString
|
||||
): ?array {
|
||||
if (!Rfc9112::isAbsoluteFormRequestTarget($requestUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$targetUri = (new Uri($requestUri))->withFragment('');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($targetUri->getHost() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$requestTarget = self::removeRequestTargetFragment($requestUri);
|
||||
$requestTargetWithoutUserInfo = self::removeUserInfoFromAbsoluteFormRequestTarget($requestTarget);
|
||||
if ($requestTargetWithoutUserInfo !== $requestTarget) {
|
||||
$targetUri = $targetUri->withUserInfo('');
|
||||
$requestTarget = $requestTargetWithoutUserInfo;
|
||||
}
|
||||
|
||||
if (!str_contains($requestTarget, '?') && $queryString !== null && $queryString !== '') {
|
||||
$targetUri = $targetUri->withQuery($queryString);
|
||||
$requestTarget .= '?'.$queryString;
|
||||
}
|
||||
|
||||
// Preserve the received absolute-form target unless it cannot be used as
|
||||
// a PSR-7 request target without normalization.
|
||||
$normalizeRequestTarget = !Rfc9112::isValidRequestTarget($requestTarget)
|
||||
|| self::hasEmptyPortInAbsoluteFormRequestTarget($requestTarget);
|
||||
|
||||
return [$targetUri, $normalizeRequestTarget ? (string) $targetUri : $requestTarget];
|
||||
}
|
||||
|
||||
private static function removeUserInfoFromAbsoluteFormRequestTarget(string $target): string
|
||||
{
|
||||
$authorityStart = strpos($target, '://');
|
||||
if ($authorityStart === false) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
$authorityStart += 3;
|
||||
$authorityLength = strcspn($target, '/?#', $authorityStart);
|
||||
$authority = substr($target, $authorityStart, $authorityLength);
|
||||
if ($authority === '') {
|
||||
return $target;
|
||||
}
|
||||
|
||||
$lastAt = strrpos($authority, '@');
|
||||
if ($lastAt === false) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
$authorityEnd = $authorityStart + $authorityLength;
|
||||
|
||||
return substr($target, 0, $authorityStart)
|
||||
.substr($authority, $lastAt + 1)
|
||||
.substr($target, $authorityEnd);
|
||||
}
|
||||
|
||||
private static function hasEmptyPortInAbsoluteFormRequestTarget(string $target): bool
|
||||
{
|
||||
$authorityStart = strpos($target, '://');
|
||||
if ($authorityStart === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authorityStart += 3;
|
||||
$authority = substr($target, $authorityStart, strcspn($target, '/?#', $authorityStart));
|
||||
if ($authority === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastAt = strrpos($authority, '@');
|
||||
if ($lastAt !== false) {
|
||||
$authority = substr($authority, $lastAt + 1);
|
||||
}
|
||||
|
||||
if ($authority === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($authority, '[')) {
|
||||
$closingBracket = strpos($authority, ']');
|
||||
|
||||
return $closingBracket !== false && substr($authority, $closingBracket + 1) === ':';
|
||||
}
|
||||
|
||||
return str_ends_with($authority, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: int}|null
|
||||
*/
|
||||
private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?array
|
||||
{
|
||||
if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$host, $port] = self::extractHostAndPortFromAuthority($target);
|
||||
if ($host === null || $port === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$host, $port];
|
||||
}
|
||||
|
||||
private static function removeRequestTargetFragment(string $target): string
|
||||
{
|
||||
return explode('#', $target, 2)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string, 2: bool}
|
||||
*/
|
||||
private static function splitRequestTargetQuery(string $target): array
|
||||
{
|
||||
$parts = explode('?', $target, 2);
|
||||
|
||||
return [$parts[0], $parts[1] ?? '', isset($parts[1])];
|
||||
}
|
||||
|
||||
private static function normalizeOriginFormPathFromServer(string $path): string
|
||||
{
|
||||
if ($path === '' || str_starts_with($path, '/')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return '/'.$path;
|
||||
}
|
||||
}
|
||||
198
libs/vendor/guzzlehttp/psr7/src/Stream.php
vendored
198
libs/vendor/guzzlehttp/psr7/src/Stream.php
vendored
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use GuzzleHttp\Psr7\Exception\TimeoutException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
@@ -11,27 +12,17 @@ use Psr\Http\Message\StreamInterface;
|
||||
*/
|
||||
class Stream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/function.fopen.php
|
||||
* @see https://www.php.net/manual/en/function.gzopen.php
|
||||
*/
|
||||
private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
|
||||
private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
|
||||
use NonSerializableStreamTrait;
|
||||
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
/** @var int|null */
|
||||
private $size;
|
||||
/** @var bool */
|
||||
private $seekable;
|
||||
/** @var bool */
|
||||
private $readable;
|
||||
/** @var bool */
|
||||
private $writable;
|
||||
/** @var string|null */
|
||||
private $uri;
|
||||
private ?int $size = null;
|
||||
private bool $seekable;
|
||||
private bool $readable;
|
||||
private bool $writable;
|
||||
private ?string $uri = null;
|
||||
/** @var mixed[] */
|
||||
private $customMetadata;
|
||||
private array $customMetadata;
|
||||
|
||||
/**
|
||||
* This constructor accepts an associative array of options.
|
||||
@@ -53,16 +44,14 @@ class Stream implements StreamInterface
|
||||
throw new \InvalidArgumentException('Stream must be a resource');
|
||||
}
|
||||
|
||||
if (isset($options['size'])) {
|
||||
$this->size = $options['size'];
|
||||
}
|
||||
$this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size');
|
||||
|
||||
$this->customMetadata = $options['metadata'] ?? [];
|
||||
$this->stream = $stream;
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
$this->seekable = $meta['seekable'];
|
||||
$this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
|
||||
$this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
|
||||
$this->readable = self::isReadableMode($meta['mode']);
|
||||
$this->writable = self::isWritableMode($meta['mode']);
|
||||
$this->uri = $meta['uri'] ?? null;
|
||||
}
|
||||
|
||||
@@ -76,20 +65,11 @@ class Stream implements StreamInterface
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
if ($this->isSeekable()) {
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
return $this->getContents();
|
||||
} catch (\Throwable $e) {
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||
|
||||
return '';
|
||||
if ($this->isSeekable()) {
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function getContents(): string
|
||||
@@ -145,13 +125,13 @@ class Stream implements StreamInterface
|
||||
}
|
||||
|
||||
$stats = fstat($this->stream);
|
||||
if (is_array($stats) && isset($stats['size'])) {
|
||||
$this->size = $stats['size'];
|
||||
|
||||
return $this->size;
|
||||
if ($stats === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
$this->size = Integers::assertEngineInteger($stats['size'], 'Stream size');
|
||||
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
@@ -185,12 +165,16 @@ class Stream implements StreamInterface
|
||||
}
|
||||
|
||||
$result = ftell($this->stream);
|
||||
|
||||
if ($result === false) {
|
||||
throw new \RuntimeException('Unable to determine stream position');
|
||||
}
|
||||
|
||||
return $result;
|
||||
$position = Integers::assertEngineInteger($result, 'Stream position');
|
||||
if ($position === null) {
|
||||
throw new \RuntimeException('Unable to determine stream position');
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
@@ -198,28 +182,8 @@ class Stream implements StreamInterface
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
$whence = (int) $whence;
|
||||
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
@@ -232,17 +196,8 @@ class Stream implements StreamInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
@@ -259,28 +214,33 @@ class Stream implements StreamInterface
|
||||
|
||||
try {
|
||||
$string = fread($this->stream, $length);
|
||||
} catch (TimeoutException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->timedOut()) {
|
||||
throw new TimeoutException('Unable to read from stream: timed out', 0, $e);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to read from stream', 0, $e);
|
||||
}
|
||||
|
||||
if (false === $string) {
|
||||
if ($this->timedOut()) {
|
||||
throw new TimeoutException('Unable to read from stream: timed out');
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to read from stream');
|
||||
}
|
||||
|
||||
if ($string === '' && $this->timedOut()) {
|
||||
throw new TimeoutException('Unable to read from stream: timed out');
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
@@ -288,34 +248,48 @@ class Stream implements StreamInterface
|
||||
throw new \RuntimeException('Cannot write to a non-writable stream');
|
||||
}
|
||||
|
||||
if ($string === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We can't know the size after writing anything
|
||||
$this->size = null;
|
||||
$result = fwrite($this->stream, $string);
|
||||
|
||||
try {
|
||||
$result = fwrite($this->stream, $string);
|
||||
} catch (TimeoutException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->writeTimedOut()) {
|
||||
throw new TimeoutException('Unable to write to stream: timed out', 0, $e);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to write to stream', 0, $e);
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
if ($this->writeTimedOut()) {
|
||||
throw new TimeoutException('Unable to write to stream: timed out');
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to write to stream');
|
||||
}
|
||||
|
||||
if ($result === 0 && $this->writeTimedOut()) {
|
||||
throw new TimeoutException('Unable to write to stream: timed out');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null)
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($this->stream)) {
|
||||
return $key ? null : [];
|
||||
} elseif (!$key) {
|
||||
return $key === null ? [] : null;
|
||||
} elseif ($key === null) {
|
||||
return $this->customMetadata + stream_get_meta_data($this->stream);
|
||||
} elseif (isset($this->customMetadata[$key])) {
|
||||
return $this->customMetadata[$key];
|
||||
@@ -325,4 +299,36 @@ class Stream implements StreamInterface
|
||||
|
||||
return $meta[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/function.fopen.php
|
||||
* @see https://www.php.net/manual/en/function.gzopen.php
|
||||
*/
|
||||
private static function isReadableMode(string $mode): bool
|
||||
{
|
||||
return str_starts_with($mode, 'r') || str_contains($mode, '+');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/function.fopen.php
|
||||
* @see https://www.php.net/manual/en/function.gzopen.php
|
||||
*/
|
||||
private static function isWritableMode(string $mode): bool
|
||||
{
|
||||
return str_starts_with($mode, 'a')
|
||||
|| str_starts_with($mode, 'w')
|
||||
|| str_starts_with($mode, 'x')
|
||||
|| str_starts_with($mode, 'c')
|
||||
|| str_contains($mode, '+');
|
||||
}
|
||||
|
||||
private function timedOut(): bool
|
||||
{
|
||||
return StreamTimeout::isResourceReadTimedOut($this->stream);
|
||||
}
|
||||
|
||||
private function writeTimedOut(): bool
|
||||
{
|
||||
return StreamTimeout::isResourceWriteTimedOut($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,8 @@ trait StreamDecoratorTrait
|
||||
/**
|
||||
* Magic method used to create a new stream if streams are not added in
|
||||
* the constructor of a decorator (e.g., LazyOpenStream).
|
||||
*
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function __get(string $name)
|
||||
public function __get(string $name): StreamInterface
|
||||
{
|
||||
if ($name === 'stream') {
|
||||
$this->stream = $this->createStream();
|
||||
@@ -35,25 +33,16 @@ trait StreamDecoratorTrait
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException("$name not found on class");
|
||||
throw new \UnexpectedValueException(\sprintf('%s not found on class', DiagnosticValue::escape($name)));
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
if ($this->isSeekable()) {
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
return $this->getContents();
|
||||
} catch (\Throwable $e) {
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||
|
||||
return '';
|
||||
if ($this->isSeekable()) {
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function getContents(): string
|
||||
@@ -84,17 +73,8 @@ trait StreamDecoratorTrait
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
public function getMetadata(?string $key = null)
|
||||
{
|
||||
if ($key !== null && !\is_string($key)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.',
|
||||
\get_debug_type($key)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->stream->getMetadata($key);
|
||||
}
|
||||
|
||||
@@ -138,54 +118,22 @@ trait StreamDecoratorTrait
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!\is_int($offset)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.',
|
||||
\get_debug_type($offset)
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_int($whence)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.',
|
||||
\get_debug_type($whence)
|
||||
);
|
||||
}
|
||||
|
||||
$this->stream->seek($offset, $whence);
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (!\is_int($length)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.',
|
||||
\get_debug_type($length)
|
||||
);
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException('Length parameter cannot be negative');
|
||||
}
|
||||
|
||||
return $this->stream->read($length);
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!\is_string($string)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.',
|
||||
\get_debug_type($string)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->stream->write($string);
|
||||
}
|
||||
|
||||
|
||||
106
libs/vendor/guzzlehttp/psr7/src/StreamTimeout.php
vendored
Normal file
106
libs/vendor/guzzlehttp/psr7/src/StreamTimeout.php
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use GuzzleHttp\Psr7\Exception\TimeoutException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class StreamTimeout
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function read(StreamInterface $stream, int $length, string $timeoutMessage): string
|
||||
{
|
||||
try {
|
||||
$buffer = $stream->read($length);
|
||||
} catch (TimeoutException $e) {
|
||||
throw $e;
|
||||
} catch (\RuntimeException $e) {
|
||||
self::throwIfReadTimedOut($stream, $timeoutMessage, $e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($buffer === '') {
|
||||
self::throwIfReadTimedOut($stream, $timeoutMessage);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
public static function throwIfReadTimedOut(
|
||||
StreamInterface $stream,
|
||||
string $message,
|
||||
?\Throwable $previous = null
|
||||
): void {
|
||||
if (self::isReadTimedOut($stream)) {
|
||||
throw new TimeoutException($message, 0, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
public static function throwIfWriteTimedOut(StreamInterface $stream, ?\Throwable $previous = null): void
|
||||
{
|
||||
if (self::isWriteTimedOut($stream)) {
|
||||
throw new TimeoutException('Unable to write to stream: timed out', 0, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
public static function isReadTimedOut(StreamInterface $stream): bool
|
||||
{
|
||||
try {
|
||||
if ($stream->getMetadata('timed_out') !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !$stream->eof();
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function isWriteTimedOut(StreamInterface $stream): bool
|
||||
{
|
||||
try {
|
||||
return $stream->getMetadata('timed_out') === true;
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $resource
|
||||
*/
|
||||
public static function isResourceReadTimedOut($resource): bool
|
||||
{
|
||||
try {
|
||||
/** @var array<string, mixed> $metadata */
|
||||
$metadata = stream_get_meta_data($resource);
|
||||
|
||||
return ($metadata['timed_out'] ?? false) === true && !feof($resource);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $resource
|
||||
*/
|
||||
public static function isResourceWriteTimedOut($resource): bool
|
||||
{
|
||||
try {
|
||||
/** @var array<string, mixed> $metadata */
|
||||
$metadata = stream_get_meta_data($resource);
|
||||
|
||||
return ($metadata['timed_out'] ?? false) === true;
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,10 @@ final class StreamWrapper
|
||||
/** @var resource */
|
||||
public $context;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $stream;
|
||||
private StreamInterface $stream;
|
||||
|
||||
/** @var string r, r+, or w */
|
||||
private $mode;
|
||||
private string $mode;
|
||||
|
||||
/**
|
||||
* Returns a resource representing the stream.
|
||||
@@ -78,40 +77,67 @@ final class StreamWrapper
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
|
||||
{
|
||||
$options = stream_context_get_options($this->context);
|
||||
$stream = $options['guzzle']['stream'] ?? null;
|
||||
|
||||
if (!isset($options['guzzle']['stream'])) {
|
||||
if (!$stream instanceof StreamInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->mode = $mode;
|
||||
$this->stream = $options['guzzle']['stream'];
|
||||
$this->stream = $stream;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read(int $count): string
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
return $this->stream->read($count);
|
||||
try {
|
||||
return $this->stream->read($count);
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_write(string $data): int
|
||||
{
|
||||
return $this->stream->write($data);
|
||||
try {
|
||||
return $this->stream->write($data);
|
||||
} catch (\RuntimeException $e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_tell(): int
|
||||
/**
|
||||
* @return int|false
|
||||
*/
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->stream->tell();
|
||||
try {
|
||||
return $this->stream->tell();
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->stream->eof();
|
||||
try {
|
||||
return $this->stream->eof();
|
||||
} catch (\RuntimeException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_seek(int $offset, int $whence): bool
|
||||
{
|
||||
$this->stream->seek($offset, $whence);
|
||||
try {
|
||||
$this->stream->seek($offset, $whence);
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -121,8 +147,12 @@ final class StreamWrapper
|
||||
*/
|
||||
public function stream_cast(int $cast_as)
|
||||
{
|
||||
$stream = clone $this->stream;
|
||||
$resource = $stream->detach();
|
||||
try {
|
||||
$stream = clone $this->stream;
|
||||
$resource = $stream->detach();
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $resource ?? false;
|
||||
}
|
||||
@@ -146,7 +176,13 @@ final class StreamWrapper
|
||||
*/
|
||||
public function stream_stat()
|
||||
{
|
||||
if ($this->stream->getSize() === null) {
|
||||
try {
|
||||
$size = $this->stream->getSize();
|
||||
} catch (\RuntimeException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($size === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -161,12 +197,12 @@ final class StreamWrapper
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => $modeMap[$this->mode],
|
||||
'mode' => $modeMap[$this->mode] ?? 0,
|
||||
'nlink' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => $this->stream->getSize() ?: 0,
|
||||
'size' => $size,
|
||||
'atime' => 0,
|
||||
'mtime' => 0,
|
||||
'ctime' => 0,
|
||||
|
||||
57
libs/vendor/guzzlehttp/psr7/src/UploadedFile.php
vendored
57
libs/vendor/guzzlehttp/psr7/src/UploadedFile.php
vendored
@@ -22,40 +22,19 @@ class UploadedFile implements UploadedFileInterface
|
||||
UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $clientFilename;
|
||||
private ?string $clientFilename;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $clientMediaType;
|
||||
private ?string $clientMediaType;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $error;
|
||||
private int $error;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $file;
|
||||
private ?string $file = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $moved = false;
|
||||
private bool $moved = false;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $size;
|
||||
private ?int $size;
|
||||
|
||||
/**
|
||||
* @var StreamInterface|null
|
||||
*/
|
||||
private $stream;
|
||||
private ?StreamInterface $stream = null;
|
||||
|
||||
/**
|
||||
* @param StreamInterface|string|resource $streamOrFile
|
||||
@@ -68,7 +47,7 @@ class UploadedFile implements UploadedFileInterface
|
||||
?string $clientMediaType = null
|
||||
) {
|
||||
$this->setError($errorStatus);
|
||||
$this->size = $size;
|
||||
$this->size = Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size');
|
||||
$this->clientFilename = $clientFilename;
|
||||
$this->clientMediaType = $clientMediaType;
|
||||
|
||||
@@ -113,11 +92,6 @@ class UploadedFile implements UploadedFileInterface
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
private static function isStringNotEmpty($param): bool
|
||||
{
|
||||
return is_string($param) && false === empty($param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is no upload error
|
||||
*/
|
||||
@@ -159,11 +133,11 @@ class UploadedFile implements UploadedFileInterface
|
||||
return new LazyOpenStream($file, 'r+');
|
||||
}
|
||||
|
||||
public function moveTo($targetPath): void
|
||||
public function moveTo(string $targetPath): void
|
||||
{
|
||||
$this->validateActive();
|
||||
|
||||
if (false === self::isStringNotEmpty($targetPath)) {
|
||||
if ($targetPath === '') {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid path provided for move operation; must be a non-empty string'
|
||||
);
|
||||
@@ -174,8 +148,13 @@ class UploadedFile implements UploadedFileInterface
|
||||
? rename($this->file, $targetPath)
|
||||
: move_uploaded_file($this->file, $targetPath);
|
||||
} else {
|
||||
$stream = $this->getStream();
|
||||
if ($stream->isSeekable()) {
|
||||
$stream->rewind();
|
||||
}
|
||||
|
||||
Utils::copyToStream(
|
||||
$this->getStream(),
|
||||
$stream,
|
||||
new LazyOpenStream($targetPath, 'w')
|
||||
);
|
||||
|
||||
@@ -183,9 +162,7 @@ class UploadedFile implements UploadedFileInterface
|
||||
}
|
||||
|
||||
if (false === $this->moved) {
|
||||
throw new RuntimeException(
|
||||
sprintf('Uploaded file could not be moved to %s', $targetPath)
|
||||
);
|
||||
throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', DiagnosticValue::escape($targetPath)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
130
libs/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php
vendored
Normal file
130
libs/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @phpstan-type UploadedFileTree array<array-key, UploadedFileInterface|array>
|
||||
*/
|
||||
final class UploadedFileNormalizer
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an UploadedFile instance array.
|
||||
*
|
||||
* @param array $files An array which respect $_FILES structure
|
||||
*
|
||||
* @return UploadedFileTree
|
||||
*
|
||||
* @throws InvalidArgumentException for unrecognized values
|
||||
*/
|
||||
public static function normalize(array $files): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($files as $key => $value) {
|
||||
if ($value instanceof UploadedFileInterface) {
|
||||
$normalized[$key] = $value;
|
||||
} elseif (is_array($value) && array_key_exists('tmp_name', $value)) {
|
||||
$normalized[$key] = self::createUploadedFileFromSpec($value);
|
||||
} elseif (is_array($value)) {
|
||||
$normalized[$key] = self::normalize($value);
|
||||
continue;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid value in files specification');
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an UploadedFile instance from a $_FILES specification.
|
||||
*
|
||||
* If the specification represents an array of values, this method will
|
||||
* delegate to normalizeNestedFileSpec() and return that return value.
|
||||
*
|
||||
* @param array $value $_FILES struct
|
||||
*
|
||||
* @return UploadedFileInterface|UploadedFileTree
|
||||
*/
|
||||
private static function createUploadedFileFromSpec(array $value)
|
||||
{
|
||||
self::assertFileSpec($value);
|
||||
|
||||
if (is_array($value['tmp_name'])) {
|
||||
return self::normalizeNestedFileSpec($value);
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
$value['tmp_name'],
|
||||
Integers::assertNonNegativeInteger($value['size'], 'Uploaded file size'),
|
||||
Integers::assertNonNegativeInteger($value['error'], 'Uploaded file error'),
|
||||
$value['name'] ?? null,
|
||||
$value['type'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
private static function assertFileSpec(array $value): void
|
||||
{
|
||||
if (!isset($value['tmp_name'], $value['size'], $value['error'])) {
|
||||
throw new InvalidArgumentException('Invalid file specification; expected keys "tmp_name", "size", and "error".');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an array of file specifications.
|
||||
*
|
||||
* Loops through all nested files and returns a normalized array of
|
||||
* UploadedFileInterface instances.
|
||||
*
|
||||
* @return UploadedFileTree
|
||||
*/
|
||||
private static function normalizeNestedFileSpec(array $files = []): array
|
||||
{
|
||||
self::assertNestedFileSpec($files);
|
||||
|
||||
$normalizedFiles = [];
|
||||
|
||||
foreach (array_keys($files['tmp_name']) as $key) {
|
||||
if (!array_key_exists($key, $files['size']) || !array_key_exists($key, $files['error'])) {
|
||||
throw new InvalidArgumentException('Invalid nested file specification; expected "tmp_name", "size", and "error" arrays to have matching keys.');
|
||||
}
|
||||
|
||||
$spec = [
|
||||
'tmp_name' => $files['tmp_name'][$key],
|
||||
'size' => $files['size'][$key],
|
||||
'error' => $files['error'][$key],
|
||||
'name' => $files['name'][$key] ?? null,
|
||||
'type' => $files['type'][$key] ?? null,
|
||||
];
|
||||
$normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
|
||||
}
|
||||
|
||||
return $normalizedFiles;
|
||||
}
|
||||
|
||||
private static function assertNestedFileSpec(array $files): void
|
||||
{
|
||||
foreach (['tmp_name', 'size', 'error'] as $key) {
|
||||
if (!isset($files[$key]) || !is_array($files[$key])) {
|
||||
throw new InvalidArgumentException('Invalid nested file specification; expected keys "tmp_name", "size", and "error" to be arrays.');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['name', 'type'] as $key) {
|
||||
if (isset($files[$key]) && !is_array($files[$key])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid nested file specification; expected key "%s" to be an array when present.', $key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
523
libs/vendor/guzzlehttp/psr7/src/Uri.php
vendored
523
libs/vendor/guzzlehttp/psr7/src/Uri.php
vendored
@@ -17,10 +17,9 @@ use Psr\Http\Message\UriInterface;
|
||||
class Uri implements UriInterface, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Absolute http and https URIs require a host per RFC 7230 Section 2.7
|
||||
* but in generic URIs the host can be empty. So for http(s) URIs
|
||||
* we apply this default host when no host is given yet to form a
|
||||
* valid URI.
|
||||
* Absolute http and https URIs require a host per RFC 9110 Section 4.2.1
|
||||
* but in generic URIs the host can be empty. So for http(s) URIs we apply
|
||||
* this default host when no host is given yet to form a valid URI.
|
||||
*/
|
||||
private const HTTP_DEFAULT_HOST = 'localhost';
|
||||
|
||||
@@ -36,37 +35,41 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
'imap' => 143,
|
||||
'pop' => 110,
|
||||
'ldap' => 389,
|
||||
'ws' => 80,
|
||||
'wss' => 443,
|
||||
];
|
||||
|
||||
private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
|
||||
|
||||
/** @var string Uri scheme. */
|
||||
private $scheme = '';
|
||||
private string $scheme = '';
|
||||
|
||||
/** @var string Uri user info. */
|
||||
private $userInfo = '';
|
||||
private string $userInfo = '';
|
||||
|
||||
/** @var string Uri host. */
|
||||
private $host = '';
|
||||
private string $host = '';
|
||||
|
||||
/** @var int|null Uri port. */
|
||||
private $port;
|
||||
private ?int $port = null;
|
||||
|
||||
/** @var string Uri path. */
|
||||
private $path = '';
|
||||
private string $path = '';
|
||||
|
||||
/** @var string Uri query string. */
|
||||
private $query = '';
|
||||
private string $query = '';
|
||||
|
||||
/** @var string Uri fragment. */
|
||||
private $fragment = '';
|
||||
private string $fragment = '';
|
||||
|
||||
public function __construct(string $uri = '')
|
||||
{
|
||||
public function __construct(
|
||||
#[\SensitiveParameter]
|
||||
string $uri = ''
|
||||
) {
|
||||
if ($uri !== '') {
|
||||
$parts = self::parse($uri);
|
||||
$parts = UriParser::parse($uri);
|
||||
if ($parts === false) {
|
||||
throw new MalformedUriException("Unable to parse URI: $uri");
|
||||
throw new MalformedUriException(\sprintf('Unable to parse URI: %s', DiagnosticValue::escape($uri)));
|
||||
}
|
||||
try {
|
||||
$this->applyParts($parts);
|
||||
@@ -78,108 +81,6 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 aware \parse_url() replacement.
|
||||
*
|
||||
* The internal function produces broken output for non ASCII domain names
|
||||
* (IDN) when used with locales other than "C".
|
||||
*
|
||||
* On the other hand, cURL understands IDN correctly only when UTF-8 locale
|
||||
* is configured ("C.UTF-8", "en_US.UTF-8", etc.).
|
||||
*
|
||||
* @see https://bugs.php.net/bug.php?id=52923
|
||||
* @see https://www.php.net/manual/en/function.parse-url.php#114817
|
||||
* @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
private static function parse(string $url)
|
||||
{
|
||||
if (self::isPathNoSchemeReference($url)) {
|
||||
return self::parsePathNoSchemeReference($url);
|
||||
}
|
||||
|
||||
// Preserve bracketed IPv6 literals before encoding, including dotted IPv4
|
||||
// tails. DEL (\x7F) is excluded so a raw-DEL host falls through to the
|
||||
// general path and is rejected rather than silently mutated by parse_url().
|
||||
$prefix = '';
|
||||
$ipv6Prefix = preg_match('%\A([0-9A-Za-z+.-]+://\[[^\]\x00-\x20\x7F/?#@]+\])(.*)\z%s', $url, $matches);
|
||||
|
||||
if ($ipv6Prefix === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ipv6Prefix === 1) {
|
||||
/** @var array{0:string, 1:string, 2:string} $matches */
|
||||
$suffix = $matches[2];
|
||||
|
||||
// After the bracketed host only an optional numeric port and/or a
|
||||
// path, query, or fragment may follow. Anything else (for example
|
||||
// `:80@evil` or `:80x`) would let parse_url() reinterpret a
|
||||
// different host.
|
||||
if (preg_match('%\A(?::[0-9]*)?(?:[/?#].*)?\z%s', $suffix) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefix = $matches[1];
|
||||
$url = $suffix;
|
||||
}
|
||||
|
||||
/** @var string|null */
|
||||
$encodedUrl = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
static function ($matches) {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$url
|
||||
);
|
||||
|
||||
if ($encodedUrl === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = parse_url($prefix.$encodedUrl);
|
||||
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_map('urldecode', $result);
|
||||
}
|
||||
|
||||
private static function isPathNoSchemeReference(string $url): bool
|
||||
{
|
||||
if ($url === '' || $url[0] === '/' || $url[0] === '?' || $url[0] === '#') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstSegment = substr($url, 0, strcspn($url, '/?#'));
|
||||
|
||||
return strpos($firstSegment, ':') === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{path: string, query?: string, fragment?: string}
|
||||
*/
|
||||
private static function parsePathNoSchemeReference(string $url): array
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if (false !== ($fragmentPosition = strpos($url, '#'))) {
|
||||
$parts['fragment'] = substr($url, $fragmentPosition + 1);
|
||||
$url = substr($url, 0, $fragmentPosition);
|
||||
}
|
||||
|
||||
if (false !== ($queryPosition = strpos($url, '?'))) {
|
||||
$parts['query'] = substr($url, $queryPosition + 1);
|
||||
$url = substr($url, 0, $queryPosition);
|
||||
}
|
||||
|
||||
$parts['path'] = $url;
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return self::composeComponents(
|
||||
@@ -192,20 +93,25 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a URI reference string from its various components.
|
||||
* Composes a URI reference string from its various components according to
|
||||
* RFC 3986 Section 5.3.
|
||||
*
|
||||
* Usually this method does not need to be called manually but instead is used indirectly via
|
||||
* `Psr\Http\Message\UriInterface::__toString`.
|
||||
* Usually this method does not need to be called manually but instead is
|
||||
* used indirectly via `Psr\Http\Message\UriInterface::__toString`.
|
||||
*
|
||||
* PSR-7 UriInterface treats an empty component the same as a missing component as
|
||||
* getQuery(), getFragment() etc. always return a string. This explains the slight
|
||||
* difference to RFC 3986 Section 5.3.
|
||||
* PSR-7 UriInterface treats an empty component the same as a missing
|
||||
* component as `getQuery()`, `getFragment()` etc. always return a string.
|
||||
* This explains the slight difference to RFC 3986 Section 5.3.
|
||||
*
|
||||
* Another adjustment is that the authority separator is added even when the authority is missing/empty
|
||||
* for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
|
||||
* `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
|
||||
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
|
||||
* that format).
|
||||
* Another adjustment is that the authority separator is added even when the
|
||||
* authority is missing/empty for the "file" scheme. This is because PHP
|
||||
* stream functions like `file_get_contents` only work with `file:///myfile`
|
||||
* but not with `file:/myfile` although they are equivalent according to RFC
|
||||
* 3986. But `file:///` is the more common syntax for the file scheme anyway
|
||||
* (Chrome for example redirects to that format). The separator is omitted
|
||||
* when such a URI has a rootless or empty path: adding it would turn the
|
||||
* first path segment into the authority of the composed URI, or compose the
|
||||
* string `file://`, which cannot be parsed back into a URI.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
|
||||
*/
|
||||
@@ -218,11 +124,11 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
$uri .= $scheme.':';
|
||||
}
|
||||
|
||||
if ($authority != '' || $scheme === 'file') {
|
||||
if ($authority != '' || ($scheme === 'file' && str_starts_with($path, '/'))) {
|
||||
$uri .= '//'.$authority;
|
||||
}
|
||||
|
||||
if ($authority != '' && $path != '' && $path[0] != '/') {
|
||||
if ($authority != '' && $path != '' && !str_starts_with($path, '/')) {
|
||||
$path = '/'.$path;
|
||||
}
|
||||
|
||||
@@ -242,8 +148,8 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Whether the URI has the default port of the current scheme.
|
||||
*
|
||||
* `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
|
||||
* independently of the implementation.
|
||||
* `Psr\Http\Message\UriInterface::getPort` may return null or the standard
|
||||
* port. This method can be used independently of the implementation.
|
||||
*/
|
||||
public static function isDefaultPort(UriInterface $uri): bool
|
||||
{
|
||||
@@ -254,17 +160,18 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Whether the URI is absolute, i.e. it has a scheme.
|
||||
*
|
||||
* An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
|
||||
* if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
|
||||
* to another URI, the base URI. Relative references can be divided into several forms:
|
||||
* - network-path references, e.g. '//example.com/path'
|
||||
* - absolute-path references, e.g. '/path'
|
||||
* - relative-path references, e.g. 'subpath'
|
||||
* An instance of UriInterface can either be an absolute URI or a relative
|
||||
* reference. An absolute URI has a scheme. A relative reference is used to
|
||||
* express a URI relative to another URI, the base URI. Relative references
|
||||
* can be divided into several forms according to RFC 3986 Section 4.2:
|
||||
* - network-path references, e.g. `//example.com/path`
|
||||
* - absolute-path references, e.g. `/path`
|
||||
* - relative-path references, e.g. `subpath`
|
||||
*
|
||||
* @see Uri::isNetworkPathReference
|
||||
* @see Uri::isAbsolutePathReference
|
||||
* @see Uri::isRelativePathReference
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
|
||||
*/
|
||||
public static function isAbsolute(UriInterface $uri): bool
|
||||
{
|
||||
@@ -274,7 +181,8 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Whether the URI is a network-path reference.
|
||||
*
|
||||
* A relative reference that begins with two slash characters is termed an network-path reference.
|
||||
* A relative reference that begins with two slash characters is termed a
|
||||
* network-path reference.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
|
||||
*/
|
||||
@@ -284,9 +192,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is a absolute-path reference.
|
||||
* Whether the URI is an absolute-path reference.
|
||||
*
|
||||
* A relative reference that begins with a single slash character is termed an absolute-path reference.
|
||||
* A relative reference that begins with a single slash character is termed
|
||||
* an absolute-path reference.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
|
||||
*/
|
||||
@@ -301,7 +210,8 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Whether the URI is a relative-path reference.
|
||||
*
|
||||
* A relative reference that does not begin with a slash character is termed a relative-path reference.
|
||||
* A relative reference that does not begin with a slash character is termed
|
||||
* a relative-path reference.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
|
||||
*/
|
||||
@@ -315,9 +225,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Whether the URI is a same-document reference.
|
||||
*
|
||||
* A same-document reference refers to a URI that is, aside from its fragment
|
||||
* component, identical to the base URI. When no base URI is given, only an empty
|
||||
* URI reference (apart from its fragment) is considered a same-document reference.
|
||||
* A same-document reference refers to a URI that is, aside from its
|
||||
* fragment component, identical to the base URI. When no base URI is given,
|
||||
* only an empty URI reference (apart from its fragment) is considered a
|
||||
* same-document reference.
|
||||
*
|
||||
* @param UriInterface $uri The URI to check
|
||||
* @param UriInterface|null $base An optional base URI to compare against
|
||||
@@ -331,7 +242,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
|
||||
return ($uri->getScheme() === $base->getScheme())
|
||||
&& ($uri->getAuthority() === $base->getAuthority())
|
||||
&& ($uri->getPath() === $base->getPath())
|
||||
&& (self::rawPath($uri) === self::rawPath($base))
|
||||
&& ($uri->getQuery() === $base->getQuery());
|
||||
}
|
||||
|
||||
@@ -358,10 +269,9 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
* Creates a new URI with a specific query string value.
|
||||
*
|
||||
* Any existing query string values that exactly match the provided key are
|
||||
* removed and replaced with the given key value pair.
|
||||
*
|
||||
* A value of null will set the query string key without a value, e.g. "key"
|
||||
* instead of "key=value".
|
||||
* removed and replaced with the given key value pair. A value of null will
|
||||
* set the query string key without a value, e.g. "key" instead of
|
||||
* "key=value".
|
||||
*
|
||||
* @param UriInterface $uri URI to use as a base.
|
||||
* @param string $key Key to set.
|
||||
@@ -377,9 +287,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new URI with multiple specific query string values.
|
||||
* Creates a new URI with multiple query string values.
|
||||
*
|
||||
* It has the same behavior as withQueryValue() but for an associative array of key => value.
|
||||
* It has the same behavior as `withQueryValue()` but for an associative
|
||||
* array of key => value.
|
||||
*
|
||||
* @param UriInterface $uri URI to use as a base.
|
||||
* @param (string|null)[] $keyValueArray Associative array of key and values
|
||||
@@ -389,36 +300,24 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
$result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
|
||||
|
||||
foreach ($keyValueArray as $key => $value) {
|
||||
$result[] = self::generateQueryString((string) $key, $value !== null ? self::stringifyQueryValue($value) : null);
|
||||
self::assertStringOrNullQueryValue($value);
|
||||
$result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
|
||||
}
|
||||
|
||||
return $uri->withQuery(implode('&', $result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringifies a non-null query value, deprecating non-string values that
|
||||
* guzzlehttp/psr7 3.0 will reject. Non-finite floats are normalized to the
|
||||
* strings PHP coerces them to, as implicit coercion of NAN emits a warning
|
||||
* on PHP 8.5.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function stringifyQueryValue($value): string
|
||||
private static function assertStringOrNullQueryValue($value): void
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.12',
|
||||
'Passing %s to Uri::withQueryValues() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string or null query values.',
|
||||
\gettype($value)
|
||||
);
|
||||
|
||||
if (is_float($value) && !is_finite($value)) {
|
||||
return is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
|
||||
}
|
||||
if ($value !== null && !is_string($value)) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Query string values must be a string or null, %s given.',
|
||||
\get_debug_type($value)
|
||||
));
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,8 +327,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
*
|
||||
* @throws MalformedUriException If the components do not form a valid URI.
|
||||
*/
|
||||
public static function fromParts(array $parts): UriInterface
|
||||
{
|
||||
public static function fromParts(
|
||||
#[\SensitiveParameter]
|
||||
array $parts
|
||||
): UriInterface {
|
||||
$uri = new self();
|
||||
try {
|
||||
$uri->applyParts($parts);
|
||||
@@ -450,32 +351,8 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
*/
|
||||
public static function assertValidHost(string $host): void
|
||||
{
|
||||
if ($host === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject control characters and URI authority delimiters so getHost()
|
||||
// cannot disagree with the on-wire authority.
|
||||
$invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host);
|
||||
|
||||
if ($invalidHost === false) {
|
||||
throw new \RuntimeException('Unable to validate URI host: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($invalidHost === 1) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
|
||||
}
|
||||
|
||||
if (strpos($host, '[') !== false || strpos($host, ']') !== false) {
|
||||
if ($host[0] !== '[' || substr($host, -1) !== ']') {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strpos($host, ':') !== false) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
|
||||
if (!Rfc3986::isValidHost($host)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid host: %s', DiagnosticValue::escape($host)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,9 +392,52 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
if (str_starts_with($this->path, '//')) {
|
||||
return '/'.ltrim($this->path, '/');
|
||||
}
|
||||
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path as it appears within a URI's string form.
|
||||
*
|
||||
* getPath() collapses multiple leading slashes so that a path used in
|
||||
* isolation cannot be mistaken for a protocol-relative URL. Whole-URI
|
||||
* operations like reference resolution and normalization (RFC 3986
|
||||
* Sections 5 and 6) are defined on the URI string form, where the path
|
||||
* stays verbatim, so they must read the path through this method instead.
|
||||
* For direct instances of this class the path is derived from the stored
|
||||
* components, including the leading slash the string form adds to a
|
||||
* rootless path when an authority is present; for subclasses and other
|
||||
* implementations the path is split from the string form per RFC 3986
|
||||
* Appendix B, without validating or decoding any other component.
|
||||
*
|
||||
* @throws \RuntimeException If the path cannot be split from the string form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function rawPath(UriInterface $uri): string
|
||||
{
|
||||
if (get_class($uri) === self::class) {
|
||||
if ($uri->path !== '' && !str_starts_with($uri->path, '/') && $uri->getAuthority() !== '') {
|
||||
// composeComponents() prepends a slash to a rootless path when
|
||||
// an authority is present, so the string form uses this path.
|
||||
return '/'.$uri->path;
|
||||
}
|
||||
|
||||
return $uri->path;
|
||||
}
|
||||
|
||||
$count = preg_match('%^(?:[^:/?#]+:)?(?://[^/?#]*)?([^?#]*)%', (string) $uri, $matches);
|
||||
|
||||
if ($count === false) {
|
||||
throw new \RuntimeException('Unable to read the URI path: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
return $matches[1] ?? '';
|
||||
}
|
||||
|
||||
public function getQuery(): string
|
||||
{
|
||||
return $this->query;
|
||||
@@ -528,7 +448,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $this->fragment;
|
||||
}
|
||||
|
||||
public function withScheme($scheme): UriInterface
|
||||
public function withScheme(string $scheme): UriInterface
|
||||
{
|
||||
$scheme = $this->filterScheme($scheme);
|
||||
|
||||
@@ -544,8 +464,11 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withUserInfo($user, $password = null): UriInterface
|
||||
{
|
||||
public function withUserInfo(
|
||||
string $user,
|
||||
#[\SensitiveParameter]
|
||||
?string $password = null
|
||||
): UriInterface {
|
||||
$info = $this->filterUserInfoComponent($user);
|
||||
if ($password !== null) {
|
||||
$info .= ':'.$this->filterUserInfoComponent($password);
|
||||
@@ -562,7 +485,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withHost($host): UriInterface
|
||||
public function withHost(string $host): UriInterface
|
||||
{
|
||||
$host = $this->filterHost($host);
|
||||
|
||||
@@ -577,17 +500,8 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withPort($port): UriInterface
|
||||
public function withPort(?int $port): UriInterface
|
||||
{
|
||||
if ($port !== null && !\is_int($port)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to UriInterface::withPort() is deprecated; guzzlehttp/psr7 3.0 requires int|null.',
|
||||
\get_debug_type($port)
|
||||
);
|
||||
}
|
||||
|
||||
$port = $this->filterPort($port);
|
||||
|
||||
if ($this->port === $port) {
|
||||
@@ -602,7 +516,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withPath($path): UriInterface
|
||||
public function withPath(string $path): UriInterface
|
||||
{
|
||||
$path = $this->filterPath($path);
|
||||
|
||||
@@ -617,7 +531,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withQuery($query): UriInterface
|
||||
public function withQuery(string $query): UriInterface
|
||||
{
|
||||
$query = $this->filterQueryAndFragment($query);
|
||||
|
||||
@@ -631,7 +545,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withFragment($fragment): UriInterface
|
||||
public function withFragment(string $fragment): UriInterface
|
||||
{
|
||||
$fragment = $this->filterQueryAndFragment($fragment);
|
||||
|
||||
@@ -655,8 +569,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
*
|
||||
* @param array $parts Array of parse_url parts to apply.
|
||||
*/
|
||||
private function applyParts(array $parts): void
|
||||
{
|
||||
private function applyParts(
|
||||
#[\SensitiveParameter]
|
||||
array $parts
|
||||
): void {
|
||||
$this->scheme = isset($parts['scheme'])
|
||||
? $this->filterScheme($parts['scheme'])
|
||||
: '';
|
||||
@@ -667,7 +583,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
? $this->filterHost($parts['host'])
|
||||
: '';
|
||||
$this->port = isset($parts['port'])
|
||||
? $this->filterPort($parts['port'])
|
||||
? $this->filterPortPart($parts['port'])
|
||||
: null;
|
||||
$this->path = isset($parts['path'])
|
||||
? $this->filterPath($parts['path'])
|
||||
@@ -686,77 +602,70 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $scheme
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scheme is invalid.
|
||||
*/
|
||||
private function filterScheme($scheme): string
|
||||
private function filterScheme(string $scheme): string
|
||||
{
|
||||
if (!is_string($scheme)) {
|
||||
throw new \InvalidArgumentException('Scheme must be a string');
|
||||
}
|
||||
|
||||
$scheme = Utils::asciiToLower($scheme);
|
||||
|
||||
if ($scheme !== '' && !preg_match('/^[a-z][a-z0-9.+-]*$/D', $scheme)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing "%s" as a URI scheme is deprecated; guzzlehttp/psr7 3.0 requires URI schemes to match RFC 3986 syntax and begin with a letter.',
|
||||
$scheme
|
||||
);
|
||||
if (!Rfc3986::isValidScheme($scheme)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid scheme: %s', DiagnosticValue::escape($scheme)));
|
||||
}
|
||||
|
||||
return $scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $component
|
||||
*
|
||||
* @throws \InvalidArgumentException If the user info is invalid.
|
||||
*/
|
||||
private function filterUserInfoComponent($component): string
|
||||
{
|
||||
if (!is_string($component)) {
|
||||
throw new \InvalidArgumentException('User info must be a string');
|
||||
}
|
||||
|
||||
private function filterUserInfoComponent(
|
||||
#[\SensitiveParameter]
|
||||
string $component
|
||||
): string {
|
||||
return $this->filterComponent(
|
||||
'/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
|
||||
'/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']++|%(?!'.Rfc3986::HEX_OCTET.'))/',
|
||||
$component,
|
||||
'Unable to filter URI user info'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $host
|
||||
*
|
||||
* @throws \InvalidArgumentException If the host is invalid.
|
||||
*/
|
||||
private function filterHost($host): string
|
||||
private function filterHost(string $host): string
|
||||
{
|
||||
if (!is_string($host)) {
|
||||
throw new \InvalidArgumentException('Host must be a string');
|
||||
$host = Utils::asciiToLower($host);
|
||||
$filtered = \preg_replace_callback('/%'.Rfc3986::HEX_OCTET.'/', static function (array $m): string {
|
||||
return Utils::asciiToUpper($m[0]);
|
||||
}, $host);
|
||||
if ($filtered === null) {
|
||||
throw new \RuntimeException('Unable to normalize URI host percent-encoding: '.\preg_last_error_msg());
|
||||
}
|
||||
self::assertValidHost($filtered);
|
||||
|
||||
if (str_starts_with($filtered, '[') && !str_starts_with($filtered, '[v')) {
|
||||
// assertValidHost() accepted this bracketed value with the same
|
||||
// filter_var() predicate tryCanonicalizeIpv6() validates with, and
|
||||
// its pure-PHP parse cannot fail on filter-accepted text, so the
|
||||
// null guard is defense in depth only.
|
||||
$canonical = Rfc3986::tryCanonicalizeIpv6(substr($filtered, 1, -1));
|
||||
if ($canonical !== null) {
|
||||
$filtered = '['.$canonical.']';
|
||||
}
|
||||
}
|
||||
|
||||
$host = Utils::asciiToLower($host);
|
||||
self::assertValidHost($host);
|
||||
|
||||
return $host;
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $port
|
||||
*
|
||||
* @throws \InvalidArgumentException If the port is invalid.
|
||||
*/
|
||||
private function filterPort($port): ?int
|
||||
private function filterPort(?int $port): ?int
|
||||
{
|
||||
if ($port === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$port = (int) $port;
|
||||
if (0 > $port || 0xFFFF < $port) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
|
||||
@@ -766,6 +675,72 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $port
|
||||
*
|
||||
* @throws \InvalidArgumentException If the port is invalid.
|
||||
*/
|
||||
private function filterPortPart($port): ?int
|
||||
{
|
||||
if (\is_int($port)) {
|
||||
return $this->filterPort($port);
|
||||
}
|
||||
|
||||
if (\is_string($port) && \ctype_digit($port)) {
|
||||
// A zero port is accepted here; only Rfc9112::parsePort() rejects
|
||||
// it for HTTP Host/authority parsing.
|
||||
if (Rfc3986::isValidPort($port)) {
|
||||
return (int) \ltrim($port, '0');
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid port: %s. Must be between 0 and 65535',
|
||||
\ltrim($port, '0')
|
||||
));
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid port: %s. Must be between 0 and 65535',
|
||||
self::describeInvalidPort($port)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $port
|
||||
*/
|
||||
private static function describeInvalidPort($port): string
|
||||
{
|
||||
if (\is_string($port)) {
|
||||
return DiagnosticValue::escape($port);
|
||||
}
|
||||
|
||||
if (\is_int($port)) {
|
||||
return (string) $port;
|
||||
}
|
||||
|
||||
if (\is_bool($port)) {
|
||||
return $port ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if ($port === null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if (\is_float($port)) {
|
||||
if (\is_nan($port)) {
|
||||
return 'NAN';
|
||||
}
|
||||
|
||||
if (\is_infinite($port)) {
|
||||
return $port > 0 ? 'INF' : '-INF';
|
||||
}
|
||||
|
||||
return \sprintf('%.14G', $port);
|
||||
}
|
||||
|
||||
return \get_debug_type($port);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param (string|int)[] $keys
|
||||
*
|
||||
@@ -783,7 +758,7 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
return rawurldecode((string) $k);
|
||||
}, $keys);
|
||||
|
||||
return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
|
||||
return array_filter(explode('&', $current), static function (string $part) use ($decodedKeys): bool {
|
||||
return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
|
||||
});
|
||||
}
|
||||
@@ -813,18 +788,12 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Filters the path of a URI
|
||||
*
|
||||
* @param mixed $path
|
||||
*
|
||||
* @throws \InvalidArgumentException If the path is invalid.
|
||||
*/
|
||||
private function filterPath($path): string
|
||||
private function filterPath(string $path): string
|
||||
{
|
||||
if (!is_string($path)) {
|
||||
throw new \InvalidArgumentException('Path must be a string');
|
||||
}
|
||||
|
||||
return $this->filterComponent(
|
||||
'/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
|
||||
'/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?!'.Rfc3986::HEX_OCTET.'))/',
|
||||
$path,
|
||||
'Unable to filter URI path'
|
||||
);
|
||||
@@ -833,25 +802,23 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
/**
|
||||
* Filters the query string or fragment of a URI.
|
||||
*
|
||||
* @param mixed $str
|
||||
*
|
||||
* @throws \InvalidArgumentException If the query or fragment is invalid.
|
||||
*/
|
||||
private function filterQueryAndFragment($str): string
|
||||
private function filterQueryAndFragment(string $str): string
|
||||
{
|
||||
if (!is_string($str)) {
|
||||
throw new \InvalidArgumentException('Query and fragment must be a string');
|
||||
}
|
||||
|
||||
return $this->filterComponent(
|
||||
'/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
|
||||
'/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?!'.Rfc3986::HEX_OCTET.'))/',
|
||||
$str,
|
||||
'Unable to filter URI query or fragment'
|
||||
);
|
||||
}
|
||||
|
||||
private function filterComponent(string $pattern, string $component, string $context): string
|
||||
{
|
||||
private function filterComponent(
|
||||
string $pattern,
|
||||
#[\SensitiveParameter]
|
||||
string $component,
|
||||
string $context
|
||||
): string {
|
||||
$filtered = preg_replace_callback($pattern, [$this, 'rawurlencodeMatchZero'], $component);
|
||||
|
||||
if ($filtered === null) {
|
||||
@@ -873,10 +840,10 @@ class Uri implements UriInterface, \JsonSerializable
|
||||
}
|
||||
|
||||
if ($this->getAuthority() === '') {
|
||||
if (0 === strpos($this->path, '//')) {
|
||||
if (str_starts_with($this->path, '//')) {
|
||||
throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
|
||||
}
|
||||
if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
|
||||
if ($this->scheme === '' && str_contains(explode('/', $this->path, 2)[0], ':')) {
|
||||
throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,33 @@ namespace GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Provides methods to determine if a modified URL should be considered cross-origin.
|
||||
* Provides methods to determine if a modified URI should be considered
|
||||
* cross-origin.
|
||||
*
|
||||
* @author Graham Campbell
|
||||
*/
|
||||
final class UriComparator
|
||||
{
|
||||
/**
|
||||
* Determines if a modified URL should be considered cross-origin with
|
||||
* respect to an original URL.
|
||||
* Determines if a modified URI should be considered cross-origin with
|
||||
* respect to an original URI.
|
||||
*
|
||||
* Two URIs are cross-origin when their scheme, host, or effective port
|
||||
* differ. Host comparison is case-insensitive, and bracketed IPv6 literals
|
||||
* are canonicalized to their RFC 5952 form from any PSR-7 implementation
|
||||
* before comparison, so equivalent spellings of the same address are
|
||||
* same-origin. IPvFuture literals and bracketed values that cannot be
|
||||
* parsed as an IPv6 address, such as those carrying zone identifiers,
|
||||
* still compare as case-insensitive text. Missing ports use the default
|
||||
* port for `http`, `https`, `ws`, or `wss`. Other schemes do not receive
|
||||
* implicit default ports.
|
||||
*
|
||||
* This helper only compares URI origins. It does not implement redirect
|
||||
* handling or credential policy.
|
||||
*/
|
||||
public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool
|
||||
{
|
||||
if (!Utils::caselessEquals($original->getHost(), $modified->getHost())) {
|
||||
if (!Utils::caselessEquals(self::normalizeHost($original), self::normalizeHost($modified))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,6 +48,28 @@ final class UriComparator
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function normalizeHost(UriInterface $uri): string
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
// Foreign UriInterface implementations may carry non-canonical IPv6
|
||||
// spellings; canonicalize what is unambiguously an IPv6 address so
|
||||
// equivalent literals compare as same-origin, and leave IPvFuture,
|
||||
// zone-identifier, and invalid text to the caseless textual
|
||||
// comparison. Validation is platform-independent, so a spelling only
|
||||
// some OS parsers accept, such as zero-padded dotted octets, is
|
||||
// cross-origin everywhere instead of same-origin on some systems.
|
||||
$canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1));
|
||||
if ($canonical === null) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
return '['.$canonical.']';
|
||||
}
|
||||
|
||||
private static function computePort(UriInterface $uri): ?int
|
||||
{
|
||||
$port = $uri->getPort();
|
||||
@@ -42,11 +78,11 @@ final class UriComparator
|
||||
return $port;
|
||||
}
|
||||
|
||||
if ('http' === $uri->getScheme()) {
|
||||
if (\in_array($uri->getScheme(), ['http', 'ws'], true)) {
|
||||
return 80;
|
||||
}
|
||||
|
||||
if ('https' === $uri->getScheme()) {
|
||||
if (\in_array($uri->getScheme(), ['https', 'wss'], true)) {
|
||||
return 443;
|
||||
}
|
||||
|
||||
|
||||
268
libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php
vendored
268
libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php
vendored
@@ -16,7 +16,8 @@ use Psr\Http\Message\UriInterface;
|
||||
final class UriNormalizer
|
||||
{
|
||||
/**
|
||||
* Default normalizations which only include the ones that preserve semantics.
|
||||
* Default normalizations which only include the ones that preserve
|
||||
* semantics.
|
||||
*/
|
||||
public const PRESERVING_NORMALIZATIONS =
|
||||
self::CAPITALIZE_PERCENT_ENCODING |
|
||||
@@ -24,10 +25,21 @@ final class UriNormalizer
|
||||
self::CONVERT_EMPTY_PATH |
|
||||
self::REMOVE_DEFAULT_HOST |
|
||||
self::REMOVE_DEFAULT_PORT |
|
||||
self::REMOVE_DOT_SEGMENTS;
|
||||
self::REMOVE_DOT_SEGMENTS |
|
||||
self::CANONICALIZE_IPV6_HOST;
|
||||
|
||||
/**
|
||||
* All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
|
||||
* All letters within a percent-encoding triplet (e.g., "%3A") are
|
||||
* case-insensitive, and should be capitalized. This applies to the
|
||||
* userinfo, host, path, query, and fragment components. Bracketed
|
||||
* IP-literal hosts are skipped as a legacy tolerance for nonstandard values
|
||||
* other implementations may carry; zone-identifier text was briefly valid
|
||||
* URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The
|
||||
* userinfo and host are only rewritten when the value returned by the
|
||||
* implementation matches the normalized form, and a userinfo with an empty
|
||||
* user segment is never rewritten. No percent-encoding normalization is
|
||||
* applied to a component that contains malformed percent syntax, such as a
|
||||
* `%` not followed by two hexadecimal digits.
|
||||
*
|
||||
* Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b
|
||||
*/
|
||||
@@ -36,9 +48,22 @@ final class UriNormalizer
|
||||
/**
|
||||
* Decodes percent-encoded octets of unreserved characters.
|
||||
*
|
||||
* For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39),
|
||||
* hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
|
||||
* when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
|
||||
* For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A
|
||||
* and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore
|
||||
* (%5F), or tilde (%7E) should not be created by URI producers and, when
|
||||
* found in a URI, should be decoded to their corresponding unreserved
|
||||
* characters by URI normalizers. This applies to the userinfo, host, path,
|
||||
* query, and fragment components. Since the host is case-insensitive and
|
||||
* PSR-7 requires it to be lowercase, octets decoded in the host are
|
||||
* lowercased (e.g., "%41" becomes "a"). Bracketed IP-literal hosts are
|
||||
* skipped as a legacy tolerance for nonstandard values other
|
||||
* implementations may carry; zone-identifier text was briefly valid URI
|
||||
* syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The
|
||||
* userinfo and host are only rewritten when the value returned by the
|
||||
* implementation matches the normalized form, and a userinfo with an empty
|
||||
* user segment is never rewritten. No percent-encoding normalization is
|
||||
* applied to a component that contains malformed percent syntax, such as a
|
||||
* `%` not followed by two hexadecimal digits.
|
||||
*
|
||||
* Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/
|
||||
*/
|
||||
@@ -54,11 +79,12 @@ final class UriNormalizer
|
||||
/**
|
||||
* Removes the default host of the given URI scheme from the URI.
|
||||
*
|
||||
* Only the "file" scheme defines the default host "localhost".
|
||||
* All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
|
||||
* are equivalent according to RFC 3986. The first format is not accepted
|
||||
* by PHPs stream functions and thus already normalized implicitly to the
|
||||
* second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
|
||||
* Only the "file" scheme defines the default host "localhost". All of
|
||||
* `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are
|
||||
* equivalent according to RFC 3986. The first format is not accepted by
|
||||
* PHPs stream functions and thus already normalized implicitly to the
|
||||
* second format in the Uri class. See
|
||||
* `GuzzleHttp\Psr7\Uri::composeComponents`.
|
||||
*
|
||||
* Example: file://localhost/myfile → file:///myfile
|
||||
*/
|
||||
@@ -84,9 +110,10 @@ final class UriNormalizer
|
||||
/**
|
||||
* Paths which include two or more adjacent slashes are converted to one.
|
||||
*
|
||||
* Webservers usually ignore duplicate slashes and treat those URIs equivalent.
|
||||
* But in theory those URIs do not need to be equivalent. So this normalization
|
||||
* may change the semantics. Encoded slashes (%2F) are not removed.
|
||||
* Webservers usually ignore duplicate slashes and treat those URIs
|
||||
* equivalent. But in theory those URIs do not need to be equivalent. So
|
||||
* this normalization may change the semantics. Encoded slashes (%2F) are
|
||||
* not removed.
|
||||
*
|
||||
* Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
|
||||
*/
|
||||
@@ -95,26 +122,49 @@ final class UriNormalizer
|
||||
/**
|
||||
* Sort query parameters with their values in alphabetical order.
|
||||
*
|
||||
* However, the order of parameters in a URI may be significant (this is not defined by the standard).
|
||||
* So this normalization is not safe and may change the semantics of the URI.
|
||||
* However, the order of parameters in a URI may be significant (this is not
|
||||
* defined by the standard). So this normalization is not safe and may
|
||||
* change the semantics of the URI.
|
||||
*
|
||||
* Example: ?lang=en&article=fred → ?article=fred&lang=en
|
||||
*
|
||||
* Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
|
||||
* purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
|
||||
* Note: The sorting is neither locale nor Unicode aware (the URI query does
|
||||
* not get decoded at all) as the purpose is to be able to compare URIs in a
|
||||
* reproducible way, not to have the params sorted perfectly.
|
||||
*/
|
||||
public const SORT_QUERY_PARAMETERS = 128;
|
||||
|
||||
/**
|
||||
* Canonicalizes IPv6 hosts to their RFC 5952 form.
|
||||
*
|
||||
* IPv6 addresses allow leading zeros and multiple placements of the `::`
|
||||
* elision, so the same address has many textual spellings. The canonical
|
||||
* form is required for IPv6 literals in URIs by RFC 5952 Section 6 and
|
||||
* never changes what the URI refers to. Native `Uri` instances already
|
||||
* guarantee canonical output; for other implementations, the canonical
|
||||
* host is requested through `withHost()` and the result is kept only when
|
||||
* the returned `getHost()` exactly matches the requested spelling,
|
||||
* otherwise this step leaves the URI unchanged while other selected
|
||||
* normalizations still apply, and setter exceptions propagate.
|
||||
*
|
||||
* Example: http://[::0:0a]/ → http://[::a]/
|
||||
*/
|
||||
public const CANONICALIZE_IPV6_HOST = 256;
|
||||
|
||||
/**
|
||||
* Returns a normalized URI.
|
||||
*
|
||||
* The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
|
||||
* This methods adds additional normalizations that can be configured with the $flags parameter.
|
||||
* The scheme and host component are already normalized to lowercase per
|
||||
* PSR-7 UriInterface. This method adds additional normalizations that can
|
||||
* be configured with the `$flags` parameter, which is a bitmask of
|
||||
* normalizations to apply.
|
||||
*
|
||||
* PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
|
||||
* getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
|
||||
* treated equivalent which is not necessarily true according to RFC 3986. But that difference
|
||||
* is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
|
||||
* PSR-7 UriInterface cannot distinguish between an empty component and a
|
||||
* missing component as `getQuery()`, `getFragment()` etc. always return a
|
||||
* string. This means the URIs `/?#` and `/` are treated equivalent which is
|
||||
* not necessarily true according to RFC 3986. But that difference is highly
|
||||
* uncommon in reality. So this potential normalization is implied in PSR-7
|
||||
* as well.
|
||||
*
|
||||
* @param UriInterface $uri The URI to normalize
|
||||
* @param int $flags A bitmask of normalizations to apply, see constants
|
||||
@@ -145,18 +195,24 @@ final class UriNormalizer
|
||||
$uri = $uri->withPort(null);
|
||||
}
|
||||
|
||||
if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
|
||||
$uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
|
||||
}
|
||||
$removeDotSegments = ($flags & self::REMOVE_DOT_SEGMENTS) && !Uri::isRelativePathReference($uri);
|
||||
|
||||
if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
|
||||
$path = preg_replace('#//++#', '/', $uri->getPath());
|
||||
if ($removeDotSegments || $flags & self::REMOVE_DUPLICATE_SLASHES) {
|
||||
$path = Uri::rawPath($uri);
|
||||
|
||||
if ($path === null) {
|
||||
throw new \RuntimeException('Unable to remove duplicate slashes from URI path: '.preg_last_error_msg());
|
||||
if ($removeDotSegments) {
|
||||
$path = UriResolver::removeDotSegments($path);
|
||||
}
|
||||
|
||||
$uri = $uri->withPath($path);
|
||||
if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
|
||||
$path = preg_replace('#//++#', '/', $path);
|
||||
|
||||
if ($path === null) {
|
||||
throw new \RuntimeException('Unable to remove duplicate slashes from URI path: '.preg_last_error_msg());
|
||||
}
|
||||
}
|
||||
|
||||
$uri = $uri->withPath(UriResolver::guardedPath($uri, $path));
|
||||
}
|
||||
|
||||
if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
|
||||
@@ -165,16 +221,22 @@ final class UriNormalizer
|
||||
$uri = $uri->withQuery(implode('&', $queryKeyValues));
|
||||
}
|
||||
|
||||
if ($flags & self::CANONICALIZE_IPV6_HOST) {
|
||||
$uri = self::canonicalizeIpv6Host($uri);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether two URIs can be considered equivalent.
|
||||
*
|
||||
* Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
|
||||
* accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
|
||||
* resolved against the same base URI. If this is not the case, determination of equivalence or difference of
|
||||
* relative references does not mean anything.
|
||||
* Both URIs are normalized automatically before comparison with the given
|
||||
* `$normalizations` bitmask. The method also accepts relative URI
|
||||
* references and returns true when they are equivalent. This of course
|
||||
* assumes they will be resolved against the same base URI. If this is not
|
||||
* the case, determination of equivalence or difference of relative
|
||||
* references does not mean anything.
|
||||
*
|
||||
* @param UriInterface $uri1 An URI to compare
|
||||
* @param UriInterface $uri2 An URI to compare
|
||||
@@ -189,14 +251,17 @@ final class UriNormalizer
|
||||
|
||||
private static function capitalizePercentEncoding(UriInterface $uri): UriInterface
|
||||
{
|
||||
$regex = '/(?:%[A-Fa-f0-9]{2})++/';
|
||||
$regex = '/(?:%'.Rfc3986::HEX_OCTET.')++/';
|
||||
|
||||
$callback = function (array $match): string {
|
||||
return Utils::asciiToUpper($match[0]);
|
||||
};
|
||||
|
||||
$uri = self::withNormalizedUserInfo($uri, $regex, $callback);
|
||||
$uri = self::withNormalizedHost($uri, $regex, $callback);
|
||||
|
||||
return $uri
|
||||
->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback))
|
||||
->withPath(self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback))
|
||||
->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))
|
||||
->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback));
|
||||
}
|
||||
@@ -209,17 +274,116 @@ final class UriNormalizer
|
||||
return rawurldecode($match[0]);
|
||||
};
|
||||
|
||||
// The host is case-insensitive and PSR-7 requires it to be lowercase,
|
||||
// so decoded ALPHA octets (e.g. "%41") must land lowercase even for
|
||||
// implementations whose withHost() does not normalize the case.
|
||||
$hostCallback = function (array $match): string {
|
||||
return Utils::asciiToLower(rawurldecode($match[0]));
|
||||
};
|
||||
|
||||
$uri = self::withNormalizedUserInfo($uri, $regex, $callback);
|
||||
$uri = self::withNormalizedHost($uri, $regex, $hostCallback);
|
||||
|
||||
return $uri
|
||||
->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback))
|
||||
->withPath(self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback))
|
||||
->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))
|
||||
->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array): string $callback
|
||||
*/
|
||||
private static function withNormalizedUserInfo(UriInterface $uri, string $regex, callable $callback): UriInterface
|
||||
{
|
||||
$userInfo = $uri->getUserInfo();
|
||||
|
||||
if (!str_contains($userInfo, '%')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$normalized = self::normalizePercentEncodingInComponent($userInfo, $regex, $callback);
|
||||
|
||||
if ($normalized === $userInfo) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
// Normalization cannot create a colon: decoding is confined to
|
||||
// unreserved characters and capitalization keeps octets encoded. So
|
||||
// splitting on the first colon preserves the user/password boundary.
|
||||
$parts = explode(':', $normalized, 2);
|
||||
|
||||
// PSR-7 defines withUserInfo('') as removing the userinfo, so a
|
||||
// userinfo with an empty user segment (e.g. ":pass") cannot be
|
||||
// expressed through the setter and is preserved as-is instead.
|
||||
if ($parts[0] === '') {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$candidate = $uri->withUserInfo($parts[0], $parts[1] ?? null);
|
||||
|
||||
// Normalization must never lose or corrupt information, so verify the
|
||||
// representation the setter returned and leave the component untouched
|
||||
// when the implementation cannot represent the normalized form.
|
||||
if ($candidate->getUserInfo() !== $normalized) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array): string $callback
|
||||
*/
|
||||
private static function withNormalizedHost(UriInterface $uri, string $regex, callable $callback): UriInterface
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
|
||||
// Bracketed IP-literal hosts are skipped as a legacy tolerance for
|
||||
// nonstandard values other implementations may carry, such as a zone
|
||||
// identifier in "[fe80::1%25eth0]"; that text was briefly valid URI
|
||||
// syntax under RFC 6874, which RFC 9844 obsoleted and reverted.
|
||||
if (str_starts_with($host, '[') || !str_contains($host, '%')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$normalized = self::normalizePercentEncodingInComponent($host, $regex, $callback);
|
||||
|
||||
if ($normalized === $host) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$candidate = $uri->withHost($normalized);
|
||||
|
||||
// Normalization must never lose or corrupt information, so verify the
|
||||
// representation the setter returned and leave the component untouched
|
||||
// when the implementation cannot represent the normalized form.
|
||||
if ($candidate->getHost() !== $normalized) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array): string $callback
|
||||
*/
|
||||
private static function normalizePercentEncodingInComponent(string $component, string $regex, callable $callback): string
|
||||
{
|
||||
// Decoding a valid triplet that follows a dangling "%" would complete
|
||||
// the malformed sequence into a new valid triplet ("example%6%31com"
|
||||
// becomes "example%61com"), turning malformed text valid and breaking
|
||||
// idempotence, so a component containing malformed percent syntax is
|
||||
// returned unchanged.
|
||||
$malformed = preg_match('/%(?!'.Rfc3986::HEX_OCTET.')/', $component);
|
||||
|
||||
if ($malformed === false) {
|
||||
throw new \RuntimeException('Unable to scan URI component percent-encoding: '.preg_last_error_msg());
|
||||
}
|
||||
|
||||
if ($malformed === 1) {
|
||||
return $component;
|
||||
}
|
||||
|
||||
$normalized = preg_replace_callback($regex, $callback, $component);
|
||||
|
||||
if ($normalized === null) {
|
||||
@@ -229,6 +393,32 @@ final class UriNormalizer
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function canonicalizeIpv6Host(UriInterface $uri): UriInterface
|
||||
{
|
||||
$host = $uri->getHost();
|
||||
if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
// Foreign UriInterface implementations may carry IPvFuture literals,
|
||||
// IPv6 zone identifiers, uppercase text, or invalid spellings;
|
||||
// tryCanonicalizeIpv6() canonicalizes only what is unambiguously an
|
||||
// IPv6 address and leaves everything else untouched.
|
||||
$canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1));
|
||||
if ($canonical === null || '['.$canonical.']' === $host) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$candidate = $uri->withHost('['.$canonical.']');
|
||||
// Normalization must never corrupt a component, so keep the original
|
||||
// host when the implementation does not retain the canonical form.
|
||||
if ($candidate->getHost() !== '['.$canonical.']') {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
// cannot be instantiated
|
||||
|
||||
145
libs/vendor/guzzlehttp/psr7/src/UriParser.php
vendored
Normal file
145
libs/vendor/guzzlehttp/psr7/src/UriParser.php
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class UriParser
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 aware \parse_url() replacement.
|
||||
*
|
||||
* The internal function produces broken output for non ASCII domain names
|
||||
* (IDN) when used with locales other than "C".
|
||||
*
|
||||
* On the other hand, cURL understands IDN correctly only when UTF-8 locale
|
||||
* is configured ("C.UTF-8", "en_US.UTF-8", etc.).
|
||||
*
|
||||
* @see https://bugs.php.net/bug.php?id=52923
|
||||
* @see https://www.php.net/manual/en/function.parse-url.php#114817
|
||||
* @see https://curl.se/libcurl/c/CURLOPT_URL.html#ENCODING
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public static function parse(string $url)
|
||||
{
|
||||
if (self::isPathNoSchemeReference($url)) {
|
||||
return self::parsePathNoSchemeReference($url);
|
||||
}
|
||||
|
||||
// Preserve bracketed IP-literals (IPv6 or IPvFuture) in scheme, userinfo,
|
||||
// and network-path authorities before encoding. Userinfo is encoded
|
||||
// separately so raw bytes cannot reach parse_url(), which mutates
|
||||
// control characters instead of failing.
|
||||
$prefix = '';
|
||||
$ipv6Prefix = preg_match('%\A((?:[0-9A-Za-z+.-]+:)?//)(?:([^/?#@]*)(@))?(\[[^\]\x00-\x20\x7F/?#@]+\])(.*)\z%s', $url, $matches);
|
||||
|
||||
if ($ipv6Prefix === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ipv6Prefix === 1) {
|
||||
/** @var array{0:string, 1:string, 2:string, 3:string, 4:string, 5:string} $matches */
|
||||
$suffix = $matches[5];
|
||||
|
||||
// After the bracketed host only an optional numeric port and/or a
|
||||
// path, query, or fragment may follow. Anything else (for example
|
||||
// `:80@evil` or `:80x`) would let parse_url() reinterpret a
|
||||
// different host.
|
||||
if (preg_match('%\A(?::[0-9]*)?(?:[/?#].*)?\z%s', $suffix) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 3986 IP-literals contain no percent-encoding, so reject any
|
||||
// "%" in the bracketed host rather than letting the urldecode()
|
||||
// below turn an encoded octet into a different literal. This keeps
|
||||
// parsing aligned with withHost()/Rfc3986::isValidHost().
|
||||
if (str_contains($matches[4], '%')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefix = $matches[1];
|
||||
|
||||
if ($matches[3] === '@') {
|
||||
/** @var string|null */
|
||||
$encodedUserInfo = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
static function (array $matches): string {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$matches[2]
|
||||
);
|
||||
|
||||
if ($encodedUserInfo === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefix .= $encodedUserInfo.'@';
|
||||
}
|
||||
|
||||
$prefix .= $matches[4];
|
||||
$url = $suffix;
|
||||
}
|
||||
|
||||
/** @var string|null */
|
||||
$encodedUrl = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
static function (array $matches): string {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$url
|
||||
);
|
||||
|
||||
if ($encodedUrl === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = parse_url($prefix.$encodedUrl);
|
||||
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_map('urldecode', $result);
|
||||
}
|
||||
|
||||
private static function isPathNoSchemeReference(string $url): bool
|
||||
{
|
||||
if ($url === '' || str_starts_with($url, '/') || str_starts_with($url, '?') || str_starts_with($url, '#')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstSegment = substr($url, 0, strcspn($url, '/?#'));
|
||||
|
||||
return !str_contains($firstSegment, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{path: string, query?: string, fragment?: string}
|
||||
*/
|
||||
private static function parsePathNoSchemeReference(string $url): array
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if (false !== ($fragmentPosition = strpos($url, '#'))) {
|
||||
$parts['fragment'] = substr($url, $fragmentPosition + 1);
|
||||
$url = substr($url, 0, $fragmentPosition);
|
||||
}
|
||||
|
||||
if (false !== ($queryPosition = strpos($url, '?'))) {
|
||||
$parts['query'] = substr($url, $queryPosition + 1);
|
||||
$url = substr($url, 0, $queryPosition);
|
||||
}
|
||||
|
||||
$parts['path'] = $url;
|
||||
|
||||
return $parts;
|
||||
}
|
||||
}
|
||||
161
libs/vendor/guzzlehttp/psr7/src/UriResolver.php
vendored
161
libs/vendor/guzzlehttp/psr7/src/UriResolver.php
vendored
@@ -16,7 +16,15 @@ use Psr\Http\Message\UriInterface;
|
||||
final class UriResolver
|
||||
{
|
||||
/**
|
||||
* Removes dot segments from a path and returns the new path.
|
||||
* Removes dot segments from a path and returns the new path according to
|
||||
* RFC 3986 Section 5.2.4.
|
||||
*
|
||||
* Excess `..` segments above the root of an absolute path are dropped
|
||||
* without consuming the root, so the result can begin with `//` (e.g.
|
||||
* `/..//a` becomes `//a`). Such a path is not valid for a URI without an
|
||||
* authority (RFC 3986 Section 3.3); `resolve()` and
|
||||
* `UriNormalizer::normalize()` serialize it with a `/.` prefix in that
|
||||
* case, like the WHATWG URL Standard.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
|
||||
*/
|
||||
@@ -28,9 +36,15 @@ final class UriResolver
|
||||
|
||||
$results = [];
|
||||
$segments = explode('/', $path);
|
||||
// The first segment of an absolute path is the empty root marker producing the
|
||||
// leading slash. RFC 3986 Section 5.2.4 (2C) drops ".." segments in excess of
|
||||
// the path hierarchy without consuming the root, so it must never be popped.
|
||||
$floor = $segments[0] === '' ? 1 : 0;
|
||||
foreach ($segments as $segment) {
|
||||
if ($segment === '..') {
|
||||
array_pop($results);
|
||||
if (count($results) > $floor) {
|
||||
array_pop($results);
|
||||
}
|
||||
} elseif ($segment !== '.') {
|
||||
$results[] = $segment;
|
||||
}
|
||||
@@ -38,7 +52,7 @@ final class UriResolver
|
||||
|
||||
$newPath = implode('/', $results);
|
||||
|
||||
if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
|
||||
if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) {
|
||||
// Re-add the leading slash if necessary for cases like "/.."
|
||||
$newPath = '/'.$newPath;
|
||||
} elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
|
||||
@@ -51,7 +65,37 @@ final class UriResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the relative URI into a new URI that is resolved against the base URI.
|
||||
* Returns the path, prefixed with "/." when it would otherwise start the
|
||||
* URI's string form with an authority-like "//".
|
||||
*
|
||||
* A URI without an authority cannot hold a path beginning with "//" (RFC
|
||||
* 3986 Section 3.3), but removeDotSegments() can produce one. The "/."
|
||||
* prefix serializes such a path unambiguously, the same way the WHATWG URL
|
||||
* Standard does, and resolves back to the same path. Hostless http and
|
||||
* https Uri instances gain the default localhost host when the path is
|
||||
* written, so the path cannot be mistaken for an authority and the prefix
|
||||
* is not added.
|
||||
*
|
||||
* @see https://url.spec.whatwg.org/#url-serializing
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function guardedPath(UriInterface $uri, string $path): string
|
||||
{
|
||||
if (!str_starts_with($path, '//') || $uri->getAuthority() !== '') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if ($uri instanceof Uri && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return '/.'.$path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the relative URI into a new URI that is resolved against the
|
||||
* base URI.
|
||||
*
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
|
||||
*/
|
||||
@@ -63,61 +107,69 @@ final class UriResolver
|
||||
}
|
||||
|
||||
if ($rel->getScheme() != '') {
|
||||
return $rel->withPath(self::removeDotSegments($rel->getPath()));
|
||||
return $rel->withPath(self::guardedPath($rel, self::removeDotSegments(Uri::rawPath($rel))));
|
||||
}
|
||||
|
||||
if ($rel->getAuthority() != '') {
|
||||
return $rel
|
||||
->withScheme($base->getScheme())
|
||||
->withPath(self::removeDotSegments($rel->getPath()));
|
||||
->withPath(self::removeDotSegments(Uri::rawPath($rel)));
|
||||
}
|
||||
|
||||
if ($rel->getPath() === '') {
|
||||
$targetPath = $base->getPath();
|
||||
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
|
||||
$relPath = Uri::rawPath($rel);
|
||||
|
||||
if ($relPath === '') {
|
||||
// the base path is used as-is per RFC 3986 Section 5.2.2, so it must not be
|
||||
// rewritten through a getPath()/withPath() round-trip
|
||||
return $base
|
||||
->withQuery($rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery())
|
||||
->withFragment($rel->getFragment());
|
||||
}
|
||||
|
||||
if (str_starts_with($relPath, '/')) {
|
||||
$targetPath = $relPath;
|
||||
} else {
|
||||
if ($rel->getPath()[0] === '/') {
|
||||
$targetPath = $rel->getPath();
|
||||
$basePath = Uri::rawPath($base);
|
||||
if ($base->getAuthority() != '' && $basePath === '') {
|
||||
$targetPath = '/'.$relPath;
|
||||
} else {
|
||||
if ($base->getAuthority() != '' && $base->getPath() === '') {
|
||||
$targetPath = '/'.$rel->getPath();
|
||||
$lastSlashPos = strrpos($basePath, '/');
|
||||
if ($lastSlashPos === false) {
|
||||
$targetPath = $relPath;
|
||||
} else {
|
||||
$lastSlashPos = strrpos($base->getPath(), '/');
|
||||
if ($lastSlashPos === false) {
|
||||
$targetPath = $rel->getPath();
|
||||
} else {
|
||||
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
|
||||
}
|
||||
$targetPath = substr($basePath, 0, $lastSlashPos + 1).$relPath;
|
||||
}
|
||||
}
|
||||
$targetPath = self::removeDotSegments($targetPath);
|
||||
$targetQuery = $rel->getQuery();
|
||||
}
|
||||
$targetPath = self::removeDotSegments($targetPath);
|
||||
|
||||
return $base
|
||||
->withPath($targetPath)
|
||||
->withQuery($targetQuery)
|
||||
->withPath(self::guardedPath($base, $targetPath))
|
||||
->withQuery($rel->getQuery())
|
||||
->withFragment($rel->getFragment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target URI as a relative reference from the base URI.
|
||||
*
|
||||
* This method is the counterpart to resolve():
|
||||
* This method is the counterpart to `resolve()`:
|
||||
*
|
||||
* (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
|
||||
*
|
||||
* One use-case is to use the current request URI as base URI and then generate relative links in your documents
|
||||
* to reduce the document size or offer self-contained downloadable document archives.
|
||||
* One use case is to use the current request URI as the base URI and then
|
||||
* generate relative links in your documents to reduce the document size or
|
||||
* offer self-contained downloadable document archives.
|
||||
*
|
||||
* $base = new Uri('http://example.com/a/b/');
|
||||
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
|
||||
* echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
|
||||
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
|
||||
* echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
|
||||
* echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'.
|
||||
*
|
||||
* This method also accepts a target that is already relative and will try to relativize it further. Only a
|
||||
* relative-path reference will be returned as-is.
|
||||
* This method also accepts a target that is already relative and will try
|
||||
* to relativize it further. Only a relative-path reference will be returned
|
||||
* as-is.
|
||||
*
|
||||
* echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
|
||||
*/
|
||||
@@ -140,37 +192,68 @@ final class UriResolver
|
||||
return $target->withScheme('');
|
||||
}
|
||||
|
||||
// A same-authority target with an empty path can only be expressed by a
|
||||
// network-path reference (RFC 3986 Section 5.2.2).
|
||||
if (self::needsNetworkPathReference($base, $target)) {
|
||||
return $target->withScheme('');
|
||||
}
|
||||
|
||||
// We must remove the path before removing the authority because if the path starts with two slashes, the URI
|
||||
// would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
|
||||
// invalid.
|
||||
$emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
|
||||
|
||||
if ($base->getPath() !== $target->getPath()) {
|
||||
if (Uri::rawPath($base) !== Uri::rawPath($target)) {
|
||||
return $emptyPathUri->withPath(self::getRelativePath($base, $target));
|
||||
}
|
||||
|
||||
if ($base->getQuery() === $target->getQuery()) {
|
||||
if ($base->getQuery() === $target->getQuery() && ($target->getFragment() !== '' || $base->getFragment() === '')) {
|
||||
// Only the target fragment is left. And it must be returned even if base and target fragment are the same.
|
||||
return $emptyPathUri->withQuery('');
|
||||
}
|
||||
|
||||
// If the base URI has a query but the target has none, we cannot return an empty path reference as it would
|
||||
// inherit the base query component when resolving.
|
||||
// If the base URI has a query or fragment that the target lacks, we cannot return an empty path
|
||||
// reference as it would inherit that base component when resolving.
|
||||
if ($target->getQuery() === '') {
|
||||
$segments = explode('/', $target->getPath());
|
||||
$segments = explode('/', Uri::rawPath($target));
|
||||
/** @var string $lastSegment */
|
||||
$lastSegment = end($segments);
|
||||
|
||||
return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
|
||||
// A reference to an empty last segment must be prefixed with "./". The same applies
|
||||
// to a segment with a colon character, which would be mistaken for a scheme name.
|
||||
if ($lastSegment === '' || str_contains($lastSegment, ':')) {
|
||||
$lastSegment = "./$lastSegment";
|
||||
}
|
||||
|
||||
return $emptyPathUri->withPath($lastSegment);
|
||||
}
|
||||
|
||||
return $emptyPathUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether relativizing to $target requires a network-path reference.
|
||||
*
|
||||
* A same-authority target with an empty path is expressible by a shorter
|
||||
* relative reference unless resolving one would inherit a base component
|
||||
* the target lacks: the base path (kept by any empty-path reference), or
|
||||
* the base query or fragment (inherited by the empty reference).
|
||||
*/
|
||||
private static function needsNetworkPathReference(UriInterface $base, UriInterface $target): bool
|
||||
{
|
||||
if ($target->getAuthority() === '' || Uri::rawPath($target) !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Uri::rawPath($base) !== ''
|
||||
|| ($base->getQuery() !== '' && $target->getQuery() === '')
|
||||
|| ($base->getFragment() !== '' && $target->getFragment() === '' && $base->getQuery() === $target->getQuery());
|
||||
}
|
||||
|
||||
private static function getRelativePath(UriInterface $base, UriInterface $target): string
|
||||
{
|
||||
$sourceSegments = explode('/', $base->getPath());
|
||||
$targetSegments = explode('/', $target->getPath());
|
||||
$sourceSegments = explode('/', Uri::rawPath($base));
|
||||
$targetSegments = explode('/', Uri::rawPath($target));
|
||||
array_pop($sourceSegments);
|
||||
$targetLastSegment = array_pop($targetSegments);
|
||||
foreach ($sourceSegments as $i => $segment) {
|
||||
@@ -186,10 +269,10 @@ final class UriResolver
|
||||
// A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
|
||||
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
|
||||
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
|
||||
if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
|
||||
if ($relativePath === '' || str_contains(explode('/', $relativePath, 2)[0], ':')) {
|
||||
$relativePath = "./$relativePath";
|
||||
} elseif ('/' === $relativePath[0]) {
|
||||
if ($base->getAuthority() != '' && $base->getPath() === '') {
|
||||
} elseif (str_starts_with($relativePath, '/')) {
|
||||
if ($base->getAuthority() != '' && Uri::rawPath($base) === '') {
|
||||
// In this case an extra slash is added by resolve() automatically. So we must not add one here.
|
||||
$relativePath = ".$relativePath";
|
||||
} else {
|
||||
|
||||
391
libs/vendor/guzzlehttp/psr7/src/Utils.php
vendored
391
libs/vendor/guzzlehttp/psr7/src/Utils.php
vendored
@@ -4,12 +4,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
use GuzzleHttp\Psr7\Exception\TimeoutException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class Utils
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ASCII uppercase letters in a string to lowercase.
|
||||
*
|
||||
@@ -70,9 +75,9 @@ final class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the items given by the keys, case insensitively from the data.
|
||||
* Remove the items given by the keys from the data, case-insensitively.
|
||||
*
|
||||
* @param (string|int)[] $keys
|
||||
* @param array<array-key, string|int> $keys
|
||||
*/
|
||||
public static function caselessRemove(array $keys, array $data): array
|
||||
{
|
||||
@@ -93,12 +98,18 @@ final class Utils
|
||||
|
||||
/**
|
||||
* Copy the contents of a stream into another stream until the given number
|
||||
* of bytes have been read.
|
||||
* of bytes have been read, returning the number of bytes copied as an
|
||||
* `int`. On 32-bit PHP, an unbounded copy larger than `PHP_INT_MAX` bytes
|
||||
* cannot be represented by that return type. 64-bit PHP is not affected.
|
||||
*
|
||||
* The copy stops if the destination write returns 0, for example a
|
||||
* BufferStream at its high water mark or a full DroppingStream. For a
|
||||
* guaranteed full copy use a normal writable stream such as a file or
|
||||
* php://temp stream.
|
||||
* The destination must accept writes that make positive progress. Streams
|
||||
* that return 0 as a backpressure or drop signal (a `BufferStream` at its
|
||||
* high water mark, or a full `DroppingStream`) will cause this method to
|
||||
* throw. For full copies, use a normal writable stream such as a file or
|
||||
* `php://temp` stream.
|
||||
*
|
||||
* Throws `TimeoutException` when PHP-style timeout metadata can be detected
|
||||
* after a source read or destination write cannot make progress.
|
||||
*
|
||||
* @param StreamInterface $source Stream to read from
|
||||
* @param StreamInterface $dest Stream to write to
|
||||
@@ -107,63 +118,71 @@ final class Utils
|
||||
*
|
||||
* @throws \RuntimeException on error.
|
||||
*/
|
||||
public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
|
||||
public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): int
|
||||
{
|
||||
$bufferSize = 8192;
|
||||
$copied = 0;
|
||||
|
||||
if ($maxLen === -1) {
|
||||
while (!$source->eof()) {
|
||||
$buf = $source->read($bufferSize);
|
||||
$buf = StreamTimeout::read($source, $bufferSize, 'Unable to read from stream: timed out');
|
||||
if ($buf === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!self::writeAll($dest, $buf)) {
|
||||
break;
|
||||
}
|
||||
self::writeAll($dest, $buf);
|
||||
$copied = Integers::add($copied, strlen($buf));
|
||||
}
|
||||
} else {
|
||||
$remaining = $maxLen;
|
||||
while ($remaining > 0 && !$source->eof()) {
|
||||
$buf = $source->read(min($bufferSize, $remaining));
|
||||
$buf = StreamTimeout::read($source, min($bufferSize, $remaining), 'Unable to read from stream: timed out');
|
||||
$len = strlen($buf);
|
||||
if (!$len) {
|
||||
break;
|
||||
}
|
||||
$remaining -= $len;
|
||||
if (!self::writeAll($dest, $buf)) {
|
||||
break;
|
||||
}
|
||||
self::writeAll($dest, $buf);
|
||||
$copied = Integers::add($copied, $len);
|
||||
}
|
||||
}
|
||||
|
||||
return $copied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the full buffer to the destination, retrying short writes.
|
||||
*
|
||||
* Returns false when the destination write returns 0 or less.
|
||||
*/
|
||||
private static function writeAll(StreamInterface $dest, string $buf): bool
|
||||
private static function writeAll(StreamInterface $dest, string $buf): void
|
||||
{
|
||||
$written = 0;
|
||||
$len = strlen($buf);
|
||||
|
||||
while ($written < $len) {
|
||||
$result = $dest->write(substr($buf, $written));
|
||||
try {
|
||||
$result = $dest->write(substr($buf, $written));
|
||||
} catch (TimeoutException $e) {
|
||||
throw $e;
|
||||
} catch (\RuntimeException $e) {
|
||||
StreamTimeout::throwIfWriteTimedOut($dest, $e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($result <= 0) {
|
||||
return false;
|
||||
StreamTimeout::throwIfWriteTimedOut($dest);
|
||||
|
||||
throw new \RuntimeException('Unable to write to stream');
|
||||
}
|
||||
|
||||
$written += $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the contents of a stream into a string until the given number of
|
||||
* bytes have been read.
|
||||
*
|
||||
* Throws `TimeoutException` when PHP-style timeout metadata can be detected
|
||||
* after a stream read cannot make progress.
|
||||
*
|
||||
* @param StreamInterface $stream Stream to read
|
||||
* @param int $maxLen Maximum number of bytes to read. Pass -1
|
||||
* to read the entire stream.
|
||||
@@ -176,7 +195,7 @@ final class Utils
|
||||
|
||||
if ($maxLen === -1) {
|
||||
while (!$stream->eof()) {
|
||||
$buf = $stream->read(1048576);
|
||||
$buf = StreamTimeout::read($stream, 1048576, 'Unable to read from stream: timed out');
|
||||
if ($buf === '') {
|
||||
break;
|
||||
}
|
||||
@@ -188,7 +207,7 @@ final class Utils
|
||||
|
||||
$len = 0;
|
||||
while (!$stream->eof() && $len < $maxLen) {
|
||||
$buf = $stream->read($maxLen - $len);
|
||||
$buf = StreamTimeout::read($stream, $maxLen - $len, 'Unable to read from stream: timed out');
|
||||
if ($buf === '') {
|
||||
break;
|
||||
}
|
||||
@@ -202,8 +221,11 @@ final class Utils
|
||||
/**
|
||||
* Calculate a hash of a stream.
|
||||
*
|
||||
* This method reads the entire stream to calculate a rolling hash, based
|
||||
* on PHP's `hash_init` functions.
|
||||
* This method reads the entire stream to calculate a rolling hash, based on
|
||||
* PHP's `hash_init` functions.
|
||||
*
|
||||
* Throws `TimeoutException` when PHP-style timeout metadata can be detected
|
||||
* after a stream read cannot make progress.
|
||||
*
|
||||
* @param StreamInterface $stream Stream to calculate the hash for
|
||||
* @param string $algo Hash algorithm (e.g. md5, crc32, etc)
|
||||
@@ -221,7 +243,12 @@ final class Utils
|
||||
|
||||
$ctx = hash_init($algo);
|
||||
while (!$stream->eof()) {
|
||||
hash_update($ctx, $stream->read(1048576));
|
||||
$buf = StreamTimeout::read($stream, 1048576, 'Unable to calculate stream hash: timed out');
|
||||
if ($buf === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
hash_update($ctx, $buf);
|
||||
}
|
||||
|
||||
$out = hash_final($ctx, $rawOutput);
|
||||
@@ -242,16 +269,27 @@ final class Utils
|
||||
* or non-empty arrays of strings.
|
||||
* - remove_headers: (array) Remove the given headers. Values may be
|
||||
* strings or integers.
|
||||
* - body: (mixed) Sets the given body. Present non-null values are converted
|
||||
* with self::streamFor(), including scalar values, resources, streams,
|
||||
* iterators, callable arrays, closures, invokable objects, and objects
|
||||
* with __toString(). String inputs remain literal bodies.
|
||||
* - uri: (UriInterface) Set the URI.
|
||||
* - body: (mixed) Sets the given body. Present non-null values are
|
||||
* converted with self::streamFor(), including resources, streams,
|
||||
* iterators, callable arrays, closures, invokable objects, and stringable
|
||||
* objects. String inputs remain literal bodies.
|
||||
* - uri: (UriInterface) Set the URI. When the URI contains a host, the
|
||||
* Host header is updated from it, and combining this with an explicit
|
||||
* Host entry in set_headers throws an InvalidArgumentException. Apply
|
||||
* an intentional Host override separately with withHeader() afterwards.
|
||||
* - query: (string) Set the query string value of the URI.
|
||||
* - version: (string) Set the protocol version.
|
||||
*
|
||||
* @param RequestInterface $request Request to clone and modify.
|
||||
* @param array $changes Changes to apply.
|
||||
* @param array{
|
||||
* method?: string,
|
||||
* set_headers?: array<array-key, string|non-empty-array<array-key, string>>,
|
||||
* remove_headers?: array<array-key, string|int>,
|
||||
* body?: resource|string|StreamInterface|callable|\Iterator|\Stringable,
|
||||
* uri?: UriInterface,
|
||||
* query?: string,
|
||||
* version?: string
|
||||
* } $changes Changes to apply.
|
||||
*/
|
||||
public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
|
||||
{
|
||||
@@ -259,16 +297,20 @@ final class Utils
|
||||
return $request;
|
||||
}
|
||||
|
||||
self::warnOnInvalidModifyRequestChanges($changes);
|
||||
self::assertValidModifyRequestChanges($changes);
|
||||
|
||||
$headers = $request->getHeaders();
|
||||
|
||||
if (!isset($changes['uri'])) {
|
||||
$uri = $request->getUri();
|
||||
} else {
|
||||
// Remove the host header if one is on the URI
|
||||
$host = $changes['uri']->getHost();
|
||||
/** @var UriInterface */
|
||||
$uri = $changes['uri'];
|
||||
|
||||
$host = $uri->getHost();
|
||||
if ($host !== '') {
|
||||
Uri::assertValidHost($host);
|
||||
|
||||
if (isset($changes['set_headers']) && is_array($changes['set_headers'])) {
|
||||
foreach (array_keys($changes['set_headers']) as $header) {
|
||||
if (self::asciiToLower((string) $header) === 'host') {
|
||||
@@ -281,15 +323,15 @@ final class Utils
|
||||
|
||||
$changes['set_headers']['Host'] = $host;
|
||||
|
||||
if ($port = $changes['uri']->getPort()) {
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null) {
|
||||
$standardPorts = ['http' => 80, 'https' => 443];
|
||||
$scheme = $changes['uri']->getScheme();
|
||||
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
|
||||
$scheme = $uri->getScheme();
|
||||
if (!isset($standardPorts[$scheme]) || $port != $standardPorts[$scheme]) {
|
||||
$changes['set_headers']['Host'] .= ':'.$port;
|
||||
}
|
||||
}
|
||||
}
|
||||
$uri = $changes['uri'];
|
||||
}
|
||||
|
||||
if (!empty($changes['remove_headers'])) {
|
||||
@@ -316,6 +358,7 @@ final class Utils
|
||||
// Match Request::__construct() by adding a Host header when one is not provided.
|
||||
if (!$hasHost && $uri->getHost() !== '') {
|
||||
$host = $uri->getHost();
|
||||
Uri::assertValidHost($host);
|
||||
|
||||
if (($port = $uri->getPort()) !== null) {
|
||||
$host .= ':'.$port;
|
||||
@@ -372,45 +415,45 @@ final class Utils
|
||||
/**
|
||||
* @param array<array-key, mixed> $changes
|
||||
*/
|
||||
private static function warnOnInvalidModifyRequestChanges(array $changes): void
|
||||
private static function assertValidModifyRequestChanges(array $changes): void
|
||||
{
|
||||
foreach (['method', 'query', 'version'] as $key) {
|
||||
if (\array_key_exists($key, $changes) && !\is_string($changes[$key])) {
|
||||
self::warnOnInvalidModifyRequestChange($key, 'string', $changes[$key]);
|
||||
self::assertValidModifyRequestChange($key, 'string', $changes[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (\array_key_exists('uri', $changes) && !$changes['uri'] instanceof UriInterface) {
|
||||
self::warnOnInvalidModifyRequestChange('uri', 'UriInterface', $changes['uri']);
|
||||
self::assertValidModifyRequestChange('uri', 'UriInterface', $changes['uri']);
|
||||
}
|
||||
|
||||
if (\array_key_exists('body', $changes) && $changes['body'] === null) {
|
||||
self::warnOnInvalidModifyRequestChange('body', 'resource|string|int|float|bool|StreamInterface|callable|\Iterator|\Stringable', $changes['body']);
|
||||
self::assertValidModifyRequestChange('body', 'resource|string|StreamInterface|callable|\Iterator|\Stringable', $changes['body']);
|
||||
}
|
||||
|
||||
if (\array_key_exists('set_headers', $changes)) {
|
||||
if (!\is_array($changes['set_headers'])) {
|
||||
self::warnOnInvalidModifyRequestChange('set_headers', 'array<array-key, string|non-empty-array<array-key, string>>', $changes['set_headers']);
|
||||
self::assertValidModifyRequestChange('set_headers', 'array<array-key, string|non-empty-array<array-key, string>>', $changes['set_headers']);
|
||||
} else {
|
||||
foreach ($changes['set_headers'] as $header => $value) {
|
||||
$headerPath = \sprintf('set_headers.%s', (string) $header);
|
||||
|
||||
if (\is_array($value)) {
|
||||
if ($value === []) {
|
||||
self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array<array-key, string>', $value);
|
||||
self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array<array-key, string>', $value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($value as $index => $item) {
|
||||
if (!\is_string($item)) {
|
||||
self::warnOnInvalidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item);
|
||||
self::assertValidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item);
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
} elseif (!\is_string($value)) {
|
||||
self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array<array-key, string>', $value);
|
||||
self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array<array-key, string>', $value);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -423,14 +466,14 @@ final class Utils
|
||||
}
|
||||
|
||||
if (!\is_array($changes['remove_headers'])) {
|
||||
self::warnOnInvalidModifyRequestChange('remove_headers', 'array<array-key, string|int>', $changes['remove_headers']);
|
||||
self::assertValidModifyRequestChange('remove_headers', 'array<array-key, string|int>', $changes['remove_headers']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($changes['remove_headers'] as $index => $header) {
|
||||
if (!\is_string($header) && !\is_int($header)) {
|
||||
self::warnOnInvalidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header);
|
||||
self::assertValidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -440,21 +483,17 @@ final class Utils
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function warnOnInvalidModifyRequestChange(string $key, string $expected, $value): void
|
||||
private static function assertValidModifyRequestChange(string $key, string $expected, $value): void
|
||||
{
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.11',
|
||||
'Passing %s to Utils::modifyRequest() change "%s" is deprecated; guzzlehttp/psr7 3.0 requires %s.',
|
||||
\get_debug_type($value),
|
||||
$key,
|
||||
$expected
|
||||
);
|
||||
throw new \InvalidArgumentException(\sprintf('Utils::modifyRequest() change "%s" must be %s; %s provided.', DiagnosticValue::escape($key), $expected, \get_debug_type($value)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a line from the stream up to the maximum allowed buffer length.
|
||||
*
|
||||
* Throws `TimeoutException` when PHP-style timeout metadata can be detected
|
||||
* after a stream read cannot make progress.
|
||||
*
|
||||
* @param StreamInterface $stream Stream to read from
|
||||
* @param int|null $maxLength Maximum buffer length
|
||||
*/
|
||||
@@ -464,7 +503,7 @@ final class Utils
|
||||
$size = 0;
|
||||
|
||||
while (!$stream->eof()) {
|
||||
if ('' === ($byte = $stream->read(1))) {
|
||||
if ('' === ($byte = StreamTimeout::read($stream, 1, 'Unable to read line from stream: timed out'))) {
|
||||
return $buffer;
|
||||
}
|
||||
$buffer .= $byte;
|
||||
@@ -478,54 +517,113 @@ final class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact the password in the user info part of a URI.
|
||||
* Redact the user info part of a URI.
|
||||
*
|
||||
* Returns the URI with the whole userinfo component replaced by "***"
|
||||
* when one is present, so neither the username nor the password survives
|
||||
* into logs and diagnostics. A URI without userinfo is returned
|
||||
* unchanged.
|
||||
*/
|
||||
public static function redactUserInfo(UriInterface $uri): UriInterface
|
||||
{
|
||||
$userInfo = $uri->getUserInfo();
|
||||
public static function redactUserInfo(
|
||||
#[\SensitiveParameter]
|
||||
UriInterface $uri
|
||||
): UriInterface {
|
||||
return $uri->getUserInfo() === '' ? $uri : $uri->withUserInfo('***');
|
||||
}
|
||||
|
||||
if (false !== ($pos = \strpos($userInfo, ':'))) {
|
||||
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
|
||||
/**
|
||||
* Redacts the userinfo of a raw URI string wherever it appears in a
|
||||
* subject string.
|
||||
*
|
||||
* The needle is taken verbatim from the raw URI rather than from parsed
|
||||
* components, so credentials that URI normalization would rewrite, such
|
||||
* as raw control bytes or unencoded reserved characters, are still found
|
||||
* in text that embeds the URI exactly as given, for example transport
|
||||
* error messages. A URI without "://" is treated as authority-form: a
|
||||
* host and port with optional userinfo.
|
||||
*
|
||||
* A URI that does not parse has no trustworthy authority boundary, so
|
||||
* everything between any scheme and its last "@" is redacted as a safe-side
|
||||
* fallback.
|
||||
*
|
||||
* @param string $subject Text that may embed the URI
|
||||
* @param string $uri Raw URI whose userinfo is redacted in the text
|
||||
*/
|
||||
public static function redactUserInfoInString(string $subject, string $uri): string
|
||||
{
|
||||
if (\strpos($uri, '@') === false) {
|
||||
return $subject;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
$schemePosition = \strpos($uri, '://');
|
||||
$remainder = $schemePosition === false ? $uri : \substr($uri, $schemePosition + 3);
|
||||
|
||||
if (\parse_url($schemePosition === false ? 'http://'.$uri : $uri) === false) {
|
||||
// Raw '/', '?', or '#' separators may sit inside the credentials
|
||||
// of a URI that defeats parse_url(), so the redaction cannot stop
|
||||
// at the apparent authority.
|
||||
$atPosition = \strrpos($remainder, '@');
|
||||
|
||||
if ($atPosition === false || $atPosition === 0) {
|
||||
return $subject;
|
||||
}
|
||||
|
||||
return \str_replace(\substr($remainder, 0, $atPosition).'@', '***@', $subject);
|
||||
}
|
||||
|
||||
$authority = \substr($remainder, 0, \strcspn($remainder, '/?#'));
|
||||
$atPosition = \strrpos($authority, '@');
|
||||
|
||||
if ($atPosition === false || $atPosition === 0) {
|
||||
// A parseable URI with '@' only past its authority, or with an
|
||||
// empty userinfo, carries no credentials to redact.
|
||||
return $subject;
|
||||
}
|
||||
|
||||
return \str_replace(\substr($authority, 0, $atPosition).'@', '***@', $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stream based on the input type.
|
||||
*
|
||||
* Options is an associative array that can contain the following keys:
|
||||
* Options are provided as an associative array that can contain the
|
||||
* following keys:
|
||||
* - metadata: Array of custom metadata.
|
||||
* - size: Size of the stream.
|
||||
*
|
||||
* This method accepts the following `$resource` types:
|
||||
* - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
|
||||
* - `string`: Creates a stream object that uses the given string as the contents.
|
||||
* - `resource`: Creates a stream object that wraps the given PHP stream resource.
|
||||
* - `Iterator`: If the provided value implements `Iterator`, then a read-only
|
||||
* stream object will be created that wraps the given iterable. Each time the
|
||||
* stream is read from, data from the iterator will fill a buffer and will be
|
||||
* continuously called until the buffer is equal to the requested read size.
|
||||
* Subsequent read calls will first read from the buffer and then call `next`
|
||||
* on the underlying iterator until it is exhausted.
|
||||
* - `object` with `__toString()`: If the object has the `__toString()` method,
|
||||
* the object will be cast to a string and then a stream will be returned that
|
||||
* uses the string value.
|
||||
* - `string`: Creates a stream object that uses the given string as the
|
||||
* contents.
|
||||
* - `resource`: Creates a stream object that wraps the given PHP stream
|
||||
* resource.
|
||||
* - `Iterator`: If the provided value implements `Iterator`, then a
|
||||
* read-only stream object will be created that wraps the given iterable.
|
||||
* Each time the stream is read from, data from the iterator will fill a
|
||||
* buffer and will be continuously called until the buffer is equal to the
|
||||
* requested read size. Yielded strings, integers, finite floats,
|
||||
* booleans, `null`, and stringable objects are converted to string
|
||||
* chunks; non-finite floats and other values throw
|
||||
* `UnexpectedValueException` when the stream is read. Values that
|
||||
* stringify to an empty string are skipped while the iterator advances.
|
||||
* Subsequent read calls will first read from the buffer and then call
|
||||
* `next` on the underlying iterator until it is exhausted.
|
||||
* - `object` with `__toString()`: If the object has the `__toString()`
|
||||
* method, the object will be cast to a string and then a stream will be
|
||||
* returned that uses the string value.
|
||||
* - `NULL`: When `null` is passed, an empty stream object is returned.
|
||||
* - `callable`: When a callable array, closure, or invokable object is passed
|
||||
* and no earlier resource or object rule applies, a read-only stream object
|
||||
* will be created that invokes the given callable. The callable is invoked
|
||||
* with the suggested number of bytes to read. The callable can return fewer
|
||||
* or more bytes than requested, but MUST return `false` or `null` when there
|
||||
* is no more data to return. Any additional bytes will be buffered and used
|
||||
* in subsequent reads. String inputs are always treated as string bodies,
|
||||
* even when they name callable functions.
|
||||
* - `callable`: When a callable array, closure, or invokable object is
|
||||
* passed and no earlier resource or object rule applies, a read-only
|
||||
* stream object will be created that invokes the given callable. The
|
||||
* callable is invoked with the suggested number of bytes to read. The
|
||||
* callable can return fewer or more bytes than requested, but MUST return
|
||||
* a non-empty string to provide data and MUST return `false` or `null`
|
||||
* when there is no more data to return. Any additional bytes will be
|
||||
* buffered and used in subsequent reads. String inputs are always treated
|
||||
* as string bodies, even when they name callable functions.
|
||||
*
|
||||
* Passing a non-string scalar (`int`, `float`, or `bool`) is deprecated; cast
|
||||
* it to a string instead. guzzlehttp/psr7 3.0 will reject non-string scalars.
|
||||
*
|
||||
* @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
|
||||
* @param array{size?: int, metadata?: array} $options Additional options
|
||||
* @param resource|string|StreamInterface|callable|\Iterator|\Stringable|null $resource Entity body data
|
||||
* @param array{size?: int, metadata?: array} $options Additional options
|
||||
*
|
||||
* @throws \InvalidArgumentException if the $resource arg is not valid.
|
||||
*/
|
||||
@@ -533,24 +631,15 @@ final class Utils
|
||||
{
|
||||
if (is_scalar($resource)) {
|
||||
if (!is_string($resource)) {
|
||||
\trigger_deprecation(
|
||||
'guzzlehttp/psr7',
|
||||
'2.12',
|
||||
'Passing %s to Utils::streamFor() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string, resource, StreamInterface, Stringable, Iterator, callable, or null.',
|
||||
\gettype($resource)
|
||||
);
|
||||
|
||||
if (is_float($resource) && !is_finite($resource)) {
|
||||
// Normalized only to avoid PHP 8.5's (string) NAN warning
|
||||
// while deprecated; 3.0 rejects non-finite floats with every
|
||||
// other non-string scalar.
|
||||
$resource = is_nan($resource) ? 'NAN' : ($resource > 0 ? 'INF' : '-INF');
|
||||
}
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Cannot create a stream from %s; pass a string, resource, StreamInterface, Stringable, Iterator, callable, or null.',
|
||||
\get_debug_type($resource)
|
||||
));
|
||||
}
|
||||
|
||||
$stream = self::tryFopen('php://temp', 'r+');
|
||||
if ($resource !== '') {
|
||||
fwrite($stream, (string) $resource);
|
||||
fwrite($stream, $resource);
|
||||
fseek($stream, 0);
|
||||
}
|
||||
|
||||
@@ -578,14 +667,29 @@ final class Utils
|
||||
if ($resource instanceof StreamInterface) {
|
||||
return $resource;
|
||||
} elseif ($resource instanceof \Iterator) {
|
||||
return new PumpStream(function () use ($resource) {
|
||||
if (!$resource->valid()) {
|
||||
return false;
|
||||
}
|
||||
$result = $resource->current();
|
||||
$resource->next();
|
||||
return new PumpStream(function (int $length) use ($resource) {
|
||||
while ($resource->valid()) {
|
||||
$result = $resource->current();
|
||||
$resource->next();
|
||||
|
||||
return $result;
|
||||
if (is_float($result) && !is_finite($result)) {
|
||||
throw new \UnexpectedValueException('Iterator must not yield non-finite float values');
|
||||
}
|
||||
|
||||
if ($result === null || is_scalar($result)) {
|
||||
$data = (string) $result;
|
||||
} elseif (is_object($result) && method_exists($result, '__toString')) {
|
||||
$data = (string) $result;
|
||||
} else {
|
||||
throw new \UnexpectedValueException('Iterator must yield scalar, null, or stringable values');
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}, $options);
|
||||
} elseif (method_exists($resource, '__toString')) {
|
||||
return self::streamFor((string) $resource, $options);
|
||||
@@ -599,14 +703,14 @@ final class Utils
|
||||
return new PumpStream($resource, $options);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
|
||||
throw new \InvalidArgumentException('Invalid resource type: '.\get_debug_type($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely opens a PHP stream resource using a filename.
|
||||
*
|
||||
* When fopen fails, PHP normally raises a warning. This function adds an
|
||||
* error handler that checks for errors and throws an exception instead.
|
||||
* When `fopen()` fails, PHP normally raises a warning. This function adds
|
||||
* an error handler that checks for errors and throws an exception instead.
|
||||
*
|
||||
* @param string $filename File to open
|
||||
* @param string $mode Mode used to open the file
|
||||
@@ -619,12 +723,7 @@ final class Utils
|
||||
{
|
||||
$ex = null;
|
||||
set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
|
||||
$ex = new \RuntimeException(sprintf(
|
||||
'Unable to open "%s" using mode "%s": %s',
|
||||
$filename,
|
||||
$mode,
|
||||
$errstr
|
||||
));
|
||||
$ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), DiagnosticValue::escape($errstr)));
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -633,12 +732,7 @@ final class Utils
|
||||
/** @var resource $handle */
|
||||
$handle = fopen($filename, $mode);
|
||||
} catch (\Throwable $e) {
|
||||
$ex = new \RuntimeException(sprintf(
|
||||
'Unable to open "%s" using mode "%s": %s',
|
||||
$filename,
|
||||
$mode,
|
||||
$e->getMessage()
|
||||
), 0, $e);
|
||||
$ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), $e->getMessage()), 0, $e);
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
@@ -654,10 +748,13 @@ final class Utils
|
||||
/**
|
||||
* Safely gets the contents of a given stream.
|
||||
*
|
||||
* When stream_get_contents fails, PHP normally raises a warning. This
|
||||
* When `stream_get_contents()` fails, PHP normally raises a warning. This
|
||||
* function adds an error handler that checks for errors and throws an
|
||||
* exception instead.
|
||||
*
|
||||
* Throws `TimeoutException` when PHP-style timeout metadata can be detected
|
||||
* after a stream read cannot make progress.
|
||||
*
|
||||
* @param resource $stream
|
||||
*
|
||||
* @throws \RuntimeException if the stream cannot be read
|
||||
@@ -666,10 +763,7 @@ final class Utils
|
||||
{
|
||||
$ex = null;
|
||||
set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool {
|
||||
$ex = new \RuntimeException(sprintf(
|
||||
'Unable to read stream contents: %s',
|
||||
$errstr
|
||||
));
|
||||
$ex = new \RuntimeException(sprintf('Unable to read stream contents: %s', DiagnosticValue::escape($errstr)));
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -679,13 +773,18 @@ final class Utils
|
||||
$contents = stream_get_contents($stream);
|
||||
|
||||
if ($contents === false) {
|
||||
$ex = new \RuntimeException('Unable to read stream contents');
|
||||
$ex = StreamTimeout::isResourceReadTimedOut($stream)
|
||||
? new TimeoutException('Unable to read stream contents: timed out')
|
||||
: new \RuntimeException('Unable to read stream contents');
|
||||
} elseif (StreamTimeout::isResourceReadTimedOut($stream)) {
|
||||
$ex = new TimeoutException('Unable to read stream contents: timed out');
|
||||
}
|
||||
} catch (TimeoutException $e) {
|
||||
$ex = $e;
|
||||
} catch (\Throwable $e) {
|
||||
$ex = new \RuntimeException(sprintf(
|
||||
'Unable to read stream contents: %s',
|
||||
$e->getMessage()
|
||||
), 0, $e);
|
||||
$ex = StreamTimeout::isResourceReadTimedOut($stream)
|
||||
? new TimeoutException('Unable to read stream contents: timed out', 0, $e)
|
||||
: new \RuntimeException(sprintf('Unable to read stream contents: %s', $e->getMessage()), 0, $e);
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
@@ -699,11 +798,11 @@ final class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UriInterface for the given value.
|
||||
* Returns a `UriInterface` for the given value.
|
||||
*
|
||||
* This function accepts a string or UriInterface and returns a
|
||||
* UriInterface for the given value. If the value is already a
|
||||
* UriInterface, it is returned as-is.
|
||||
* This function accepts a string or `UriInterface` and returns a
|
||||
* `UriInterface` for the given value. If the value is already a
|
||||
* `UriInterface`, it is returned as-is.
|
||||
*
|
||||
* @param string|UriInterface $uri
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user