mirror of
https://github.com/SociallyDev/Spaces-API.git
synced 2025-07-16 10:03:01 -07:00
v2: Updates
* Simplifies & beautifies everything * Introduces a new Class system. * Errors are defaulted to AWS's handler. * New function names & more efficient handling. * Should fix a majority of the errors. Please read the README for more!
This commit is contained in:
parent
ad0726e41e
commit
e6d7753dc8
1095 changed files with 45088 additions and 2911 deletions
|
@ -16,7 +16,6 @@ class AppendStream implements StreamInterface
|
|||
private $seekable = true;
|
||||
private $current = 0;
|
||||
private $pos = 0;
|
||||
private $detached = false;
|
||||
|
||||
/**
|
||||
* @param StreamInterface[] $streams Streams to decorate. Each stream must
|
||||
|
@ -73,6 +72,7 @@ class AppendStream implements StreamInterface
|
|||
public function close()
|
||||
{
|
||||
$this->pos = $this->current = 0;
|
||||
$this->seekable = true;
|
||||
|
||||
foreach ($this->streams as $stream) {
|
||||
$stream->close();
|
||||
|
@ -82,14 +82,22 @@ class AppendStream implements StreamInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* Detaches each attached stream
|
||||
* Detaches each attached stream.
|
||||
*
|
||||
* Returns null as it's not clear which underlying stream resource to return.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
$this->close();
|
||||
$this->detached = true;
|
||||
$this->pos = $this->current = 0;
|
||||
$this->seekable = true;
|
||||
|
||||
foreach ($this->streams as $stream) {
|
||||
$stream->detach();
|
||||
}
|
||||
|
||||
$this->streams = [];
|
||||
}
|
||||
|
||||
public function tell()
|
||||
|
|
|
@ -52,6 +52,15 @@ class FnStream implements StreamInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \LogicException('FnStream should never be unserialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom functionality to an underlying stream by intercepting
|
||||
* specific method calls.
|
||||
|
|
|
@ -27,7 +27,7 @@ class InflateStream implements StreamInterface
|
|||
$stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
|
||||
$resource = StreamWrapper::getResource($stream);
|
||||
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
|
||||
$this->stream = new Stream($resource);
|
||||
$this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,7 +72,7 @@ class LimitStream implements StreamInterface
|
|||
{
|
||||
if ($whence !== SEEK_SET || $offset < 0) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Cannot seek to offset % with whence %s',
|
||||
'Cannot seek to offset %s with whence %s',
|
||||
$offset,
|
||||
$whence
|
||||
));
|
||||
|
|
|
@ -66,11 +66,8 @@ trait MessageTrait
|
|||
|
||||
public function withHeader($header, $value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
$value = $this->trimHeaderValues($value);
|
||||
$this->assertHeader($header);
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = strtolower($header);
|
||||
|
||||
$new = clone $this;
|
||||
|
@ -85,11 +82,8 @@ trait MessageTrait
|
|||
|
||||
public function withAddedHeader($header, $value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
$value = $this->trimHeaderValues($value);
|
||||
$this->assertHeader($header);
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = strtolower($header);
|
||||
|
||||
$new = clone $this;
|
||||
|
@ -144,11 +138,13 @@ trait MessageTrait
|
|||
{
|
||||
$this->headerNames = $this->headers = [];
|
||||
foreach ($headers as $header => $value) {
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
if (is_int($header)) {
|
||||
// Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec
|
||||
// and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass.
|
||||
$header = (string) $header;
|
||||
}
|
||||
|
||||
$value = $this->trimHeaderValues($value);
|
||||
$this->assertHeader($header);
|
||||
$value = $this->normalizeHeaderValue($value);
|
||||
$normalized = strtolower($header);
|
||||
if (isset($this->headerNames[$normalized])) {
|
||||
$header = $this->headerNames[$normalized];
|
||||
|
@ -160,6 +156,19 @@ trait MessageTrait
|
|||
}
|
||||
}
|
||||
|
||||
private function normalizeHeaderValue($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return $this->trimHeaderValues([$value]);
|
||||
}
|
||||
|
||||
if (count($value) === 0) {
|
||||
throw new \InvalidArgumentException('Header value can not be an empty array.');
|
||||
}
|
||||
|
||||
return $this->trimHeaderValues($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims whitespace from the header values.
|
||||
*
|
||||
|
@ -177,7 +186,28 @@ trait MessageTrait
|
|||
private function trimHeaderValues(array $values)
|
||||
{
|
||||
return array_map(function ($value) {
|
||||
return trim($value, " \t");
|
||||
if (!is_scalar($value) && null !== $value) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Header value must be scalar or null but %s provided.',
|
||||
is_object($value) ? get_class($value) : gettype($value)
|
||||
));
|
||||
}
|
||||
|
||||
return trim((string) $value, " \t");
|
||||
}, $values);
|
||||
}
|
||||
|
||||
private function assertHeader($header)
|
||||
{
|
||||
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 ($header === '') {
|
||||
throw new \InvalidArgumentException('Header name can not be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class Request implements RequestInterface
|
|||
$body = null,
|
||||
$version = '1.1'
|
||||
) {
|
||||
$this->assertMethod($method);
|
||||
if (!($uri instanceof UriInterface)) {
|
||||
$uri = new Uri($uri);
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ class Request implements RequestInterface
|
|||
$this->setHeaders($headers);
|
||||
$this->protocol = $version;
|
||||
|
||||
if (!$this->hasHeader('Host')) {
|
||||
if (!isset($this->headerNames['host'])) {
|
||||
$this->updateHostFromUri();
|
||||
}
|
||||
|
||||
|
@ -91,6 +92,7 @@ class Request implements RequestInterface
|
|||
|
||||
public function withMethod($method)
|
||||
{
|
||||
$this->assertMethod($method);
|
||||
$new = clone $this;
|
||||
$new->method = strtoupper($method);
|
||||
return $new;
|
||||
|
@ -110,7 +112,7 @@ class Request implements RequestInterface
|
|||
$new = clone $this;
|
||||
$new->uri = $uri;
|
||||
|
||||
if (!$preserveHost) {
|
||||
if (!$preserveHost || !isset($this->headerNames['host'])) {
|
||||
$new->updateHostFromUri();
|
||||
}
|
||||
|
||||
|
@ -139,4 +141,11 @@ class Request implements RequestInterface
|
|||
// See: http://tools.ietf.org/html/rfc7230#section-5.4
|
||||
$this->headers = [$header => [$host]] + $this->headers;
|
||||
}
|
||||
|
||||
private function assertMethod($method)
|
||||
{
|
||||
if (!is_string($method) || $method === '') {
|
||||
throw new \InvalidArgumentException('Method must be a non-empty string.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,11 @@ class Response implements ResponseInterface
|
|||
$version = '1.1',
|
||||
$reason = null
|
||||
) {
|
||||
$this->statusCode = (int) $status;
|
||||
$this->assertStatusCodeIsInteger($status);
|
||||
$status = (int) $status;
|
||||
$this->assertStatusCodeRange($status);
|
||||
|
||||
$this->statusCode = $status;
|
||||
|
||||
if ($body !== '' && $body !== null) {
|
||||
$this->stream = stream_for($body);
|
||||
|
@ -121,12 +125,30 @@ class Response implements ResponseInterface
|
|||
|
||||
public function withStatus($code, $reasonPhrase = '')
|
||||
{
|
||||
$this->assertStatusCodeIsInteger($code);
|
||||
$code = (int) $code;
|
||||
$this->assertStatusCodeRange($code);
|
||||
|
||||
$new = clone $this;
|
||||
$new->statusCode = (int) $code;
|
||||
$new->statusCode = $code;
|
||||
if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
|
||||
$reasonPhrase = self::$phrases[$new->statusCode];
|
||||
}
|
||||
$new->reasonPhrase = $reasonPhrase;
|
||||
return $new;
|
||||
}
|
||||
|
||||
private function assertStatusCodeIsInteger($statusCode)
|
||||
{
|
||||
if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
|
||||
throw new \InvalidArgumentException('Status code must be an integer value.');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertStatusCodeRange($statusCode)
|
||||
{
|
||||
if ($statusCode < 100 || $statusCode >= 600) {
|
||||
throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
aws/GuzzleHttp/Psr7/Rfc7230.php
Normal file
18
aws/GuzzleHttp/Psr7/Rfc7230.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace GuzzleHttp\Psr7;
|
||||
|
||||
final class Rfc7230
|
||||
{
|
||||
/**
|
||||
* Header related regular expressions (copied from amphp/http package)
|
||||
* (Note: once we require PHP 7.x we could just depend on the upstream package)
|
||||
*
|
||||
* Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
|
||||
*
|
||||
* @link 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
|
||||
*/
|
||||
const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
|
||||
const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
|
||||
}
|
|
@ -166,9 +166,9 @@ class ServerRequest extends Request implements ServerRequestInterface
|
|||
public static function fromGlobals()
|
||||
{
|
||||
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
|
||||
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
||||
$headers = getallheaders();
|
||||
$uri = self::getUriFromGlobals();
|
||||
$body = new LazyOpenStream('php://input', 'r+');
|
||||
$body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
|
||||
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
|
||||
|
||||
$serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
|
||||
|
@ -180,23 +180,41 @@ class ServerRequest extends Request implements ServerRequestInterface
|
|||
->withUploadedFiles(self::normalizeFiles($_FILES));
|
||||
}
|
||||
|
||||
private static function extractHostAndPortFromAuthority($authority)
|
||||
{
|
||||
$uri = 'http://'.$authority;
|
||||
$parts = parse_url($uri);
|
||||
if (false === $parts) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
$host = isset($parts['host']) ? $parts['host'] : null;
|
||||
$port = isset($parts['port']) ? $parts['port'] : null;
|
||||
|
||||
return [$host, $port];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Uri populated with values from $_SERVER.
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
public static function getUriFromGlobals() {
|
||||
public static function getUriFromGlobals()
|
||||
{
|
||||
$uri = new Uri('');
|
||||
|
||||
$uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
|
||||
|
||||
$hasPort = false;
|
||||
if (isset($_SERVER['HTTP_HOST'])) {
|
||||
$hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']);
|
||||
$uri = $uri->withHost($hostHeaderParts[0]);
|
||||
if (isset($hostHeaderParts[1])) {
|
||||
list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
|
||||
if ($host !== null) {
|
||||
$uri = $uri->withHost($host);
|
||||
}
|
||||
|
||||
if ($port !== null) {
|
||||
$hasPort = true;
|
||||
$uri = $uri->withPort($hostHeaderParts[1]);
|
||||
$uri = $uri->withPort($port);
|
||||
}
|
||||
} elseif (isset($_SERVER['SERVER_NAME'])) {
|
||||
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
|
||||
|
@ -210,7 +228,7 @@ class ServerRequest extends Request implements ServerRequestInterface
|
|||
|
||||
$hasQuery = false;
|
||||
if (isset($_SERVER['REQUEST_URI'])) {
|
||||
$requestUriParts = explode('?', $_SERVER['REQUEST_URI']);
|
||||
$requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
|
||||
$uri = $uri->withPath($requestUriParts[0]);
|
||||
if (isset($requestUriParts[1])) {
|
||||
$hasQuery = true;
|
||||
|
|
|
@ -10,6 +10,17 @@ use Psr\Http\Message\StreamInterface;
|
|||
*/
|
||||
class Stream implements StreamInterface
|
||||
{
|
||||
/**
|
||||
* Resource modes.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see http://php.net/manual/function.fopen.php
|
||||
* @see http://php.net/manual/en/function.gzopen.php
|
||||
*/
|
||||
const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
|
||||
const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
|
||||
|
||||
private $stream;
|
||||
private $size;
|
||||
private $seekable;
|
||||
|
@ -18,22 +29,6 @@ class Stream implements StreamInterface
|
|||
private $uri;
|
||||
private $customMetadata;
|
||||
|
||||
/** @var array Hash of readable and writable stream types */
|
||||
private static $readWriteHash = [
|
||||
'read' => [
|
||||
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
|
||||
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
|
||||
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a+' => true
|
||||
],
|
||||
'write' => [
|
||||
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
||||
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
|
||||
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
|
||||
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* This constructor accepts an associative array of options.
|
||||
*
|
||||
|
@ -65,20 +60,11 @@ class Stream implements StreamInterface
|
|||
$this->stream = $stream;
|
||||
$meta = stream_get_meta_data($this->stream);
|
||||
$this->seekable = $meta['seekable'];
|
||||
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
|
||||
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
|
||||
$this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
|
||||
$this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
|
||||
$this->uri = $this->getMetadata('uri');
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name == 'stream') {
|
||||
throw new \RuntimeException('The stream is detached');
|
||||
}
|
||||
|
||||
throw new \BadMethodCallException('No value for ' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream when the destructed
|
||||
*/
|
||||
|
@ -99,6 +85,10 @@ class Stream implements StreamInterface
|
|||
|
||||
public function getContents()
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
|
||||
$contents = stream_get_contents($this->stream);
|
||||
|
||||
if ($contents === false) {
|
||||
|
@ -173,11 +163,19 @@ class Stream implements StreamInterface
|
|||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->stream || feof($this->stream);
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
|
||||
return feof($this->stream);
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
|
||||
$result = ftell($this->stream);
|
||||
|
||||
if ($result === false) {
|
||||
|
@ -194,9 +192,15 @@ class Stream implements StreamInterface
|
|||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
$whence = (int) $whence;
|
||||
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
if (!$this->seekable) {
|
||||
throw new \RuntimeException('Stream is not seekable');
|
||||
} elseif (fseek($this->stream, $offset, $whence) === -1) {
|
||||
}
|
||||
if (fseek($this->stream, $offset, $whence) === -1) {
|
||||
throw new \RuntimeException('Unable to seek to stream position '
|
||||
. $offset . ' with whence ' . var_export($whence, true));
|
||||
}
|
||||
|
@ -204,6 +208,9 @@ class Stream implements StreamInterface
|
|||
|
||||
public function read($length)
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
if (!$this->readable) {
|
||||
throw new \RuntimeException('Cannot read from non-readable stream');
|
||||
}
|
||||
|
@ -225,6 +232,9 @@ class Stream implements StreamInterface
|
|||
|
||||
public function write($string)
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException('Stream is detached');
|
||||
}
|
||||
if (!$this->writable) {
|
||||
throw new \RuntimeException('Cannot write to a non-writable stream');
|
||||
}
|
||||
|
|
|
@ -38,9 +38,21 @@ class StreamWrapper
|
|||
. 'writable, or both.');
|
||||
}
|
||||
|
||||
return fopen('guzzle://stream', $mode, null, stream_context_create([
|
||||
return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream context that can be used to open a stream as a php stream resource.
|
||||
*
|
||||
* @param StreamInterface $stream
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public static function createStreamContext(StreamInterface $stream)
|
||||
{
|
||||
return stream_context_create([
|
||||
'guzzle' => ['stream' => $stream]
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,12 +106,21 @@ class StreamWrapper
|
|||
return true;
|
||||
}
|
||||
|
||||
public function stream_cast($cast_as)
|
||||
{
|
||||
$stream = clone($this->stream);
|
||||
|
||||
return $stream->detach();
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
static $modeMap = [
|
||||
'r' => 33060,
|
||||
'rb' => 33060,
|
||||
'r+' => 33206,
|
||||
'w' => 33188
|
||||
'w' => 33188,
|
||||
'wb' => 33188
|
||||
];
|
||||
|
||||
return [
|
||||
|
@ -118,4 +139,23 @@ class StreamWrapper
|
|||
'blocks' => 0
|
||||
];
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 0,
|
||||
'nlink' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => 0,
|
||||
'atime' => 0,
|
||||
'mtime' => 0,
|
||||
'ctime' => 0,
|
||||
'blksize' => 0,
|
||||
'blocks' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,15 +301,7 @@ class Uri implements UriInterface
|
|||
*/
|
||||
public static function withoutQueryValue(UriInterface $uri, $key)
|
||||
{
|
||||
$current = $uri->getQuery();
|
||||
if ($current === '') {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$decodedKey = rawurldecode($key);
|
||||
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
|
||||
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
|
||||
});
|
||||
$result = self::getFilteredQueryString($uri, [$key]);
|
||||
|
||||
return $uri->withQuery(implode('&', $result));
|
||||
}
|
||||
|
@ -331,26 +323,29 @@ class Uri implements UriInterface
|
|||
*/
|
||||
public static function withQueryValue(UriInterface $uri, $key, $value)
|
||||
{
|
||||
$current = $uri->getQuery();
|
||||
$result = self::getFilteredQueryString($uri, [$key]);
|
||||
|
||||
if ($current === '') {
|
||||
$result = [];
|
||||
} else {
|
||||
$decodedKey = rawurldecode($key);
|
||||
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
|
||||
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
|
||||
});
|
||||
}
|
||||
$result[] = self::generateQueryString($key, $value);
|
||||
|
||||
// Query string separators ("=", "&") within the key or value need to be encoded
|
||||
// (while preventing double-encoding) before setting the query string. All other
|
||||
// chars that need percent-encoding will be encoded by withQuery().
|
||||
$key = strtr($key, self::$replaceQuery);
|
||||
return $uri->withQuery(implode('&', $result));
|
||||
}
|
||||
|
||||
if ($value !== null) {
|
||||
$result[] = $key . '=' . strtr($value, self::$replaceQuery);
|
||||
} else {
|
||||
$result[] = $key;
|
||||
/**
|
||||
* Creates a new URI with multiple specific query string values.
|
||||
*
|
||||
* 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 array $keyValueArray Associative array of key and values
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
public static function withQueryValues(UriInterface $uri, array $keyValueArray)
|
||||
{
|
||||
$result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
|
||||
|
||||
foreach ($keyValueArray as $key => $value) {
|
||||
$result[] = self::generateQueryString($key, $value);
|
||||
}
|
||||
|
||||
return $uri->withQuery(implode('&', $result));
|
||||
|
@ -442,9 +437,9 @@ class Uri implements UriInterface
|
|||
|
||||
public function withUserInfo($user, $password = null)
|
||||
{
|
||||
$info = $user;
|
||||
if ($password != '') {
|
||||
$info .= ':' . $password;
|
||||
$info = $this->filterUserInfoComponent($user);
|
||||
if ($password !== null) {
|
||||
$info .= ':' . $this->filterUserInfoComponent($password);
|
||||
}
|
||||
|
||||
if ($this->userInfo === $info) {
|
||||
|
@ -542,7 +537,9 @@ class Uri implements UriInterface
|
|||
$this->scheme = isset($parts['scheme'])
|
||||
? $this->filterScheme($parts['scheme'])
|
||||
: '';
|
||||
$this->userInfo = isset($parts['user']) ? $parts['user'] : '';
|
||||
$this->userInfo = isset($parts['user'])
|
||||
? $this->filterUserInfoComponent($parts['user'])
|
||||
: '';
|
||||
$this->host = isset($parts['host'])
|
||||
? $this->filterHost($parts['host'])
|
||||
: '';
|
||||
|
@ -559,7 +556,7 @@ class Uri implements UriInterface
|
|||
? $this->filterQueryAndFragment($parts['fragment'])
|
||||
: '';
|
||||
if (isset($parts['pass'])) {
|
||||
$this->userInfo .= ':' . $parts['pass'];
|
||||
$this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
|
||||
}
|
||||
|
||||
$this->removeDefaultPort();
|
||||
|
@ -581,6 +578,26 @@ class Uri implements UriInterface
|
|||
return strtolower($scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $component
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException If the user info is invalid.
|
||||
*/
|
||||
private function filterUserInfoComponent($component)
|
||||
{
|
||||
if (!is_string($component)) {
|
||||
throw new \InvalidArgumentException('User info must be a string');
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
|
||||
[$this, 'rawurlencodeMatchZero'],
|
||||
$component
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
*
|
||||
|
@ -611,15 +628,56 @@ class Uri implements UriInterface
|
|||
}
|
||||
|
||||
$port = (int) $port;
|
||||
if (1 > $port || 0xffff < $port) {
|
||||
if (0 > $port || 0xffff < $port) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
|
||||
sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
|
||||
);
|
||||
}
|
||||
|
||||
return $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UriInterface $uri
|
||||
* @param array $keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getFilteredQueryString(UriInterface $uri, array $keys)
|
||||
{
|
||||
$current = $uri->getQuery();
|
||||
|
||||
if ($current === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decodedKeys = array_map('rawurldecode', $keys);
|
||||
|
||||
return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
|
||||
return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|null $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function generateQueryString($key, $value)
|
||||
{
|
||||
// Query string separators ("=", "&") within the key or value need to be encoded
|
||||
// (while preventing double-encoding) before setting the query string. All other
|
||||
// chars that need percent-encoding will be encoded by withQuery().
|
||||
$queryString = strtr($key, self::$replaceQuery);
|
||||
|
||||
if ($value !== null) {
|
||||
$queryString .= '=' . strtr($value, self::$replaceQuery);
|
||||
}
|
||||
|
||||
return $queryString;
|
||||
}
|
||||
|
||||
private function removeDefaultPort()
|
||||
{
|
||||
if ($this->port !== null && self::isDefaultPort($this)) {
|
||||
|
|
|
@ -69,10 +69,10 @@ function uri_for($uri)
|
|||
* - metadata: Array of custom metadata.
|
||||
* - size: Size of the stream.
|
||||
*
|
||||
* @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
|
||||
* @param array $options Additional options
|
||||
* @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
|
||||
* @param array $options Additional options
|
||||
*
|
||||
* @return Stream
|
||||
* @return StreamInterface
|
||||
* @throws \InvalidArgumentException if the $resource arg is not valid.
|
||||
*/
|
||||
function stream_for($resource = '', array $options = [])
|
||||
|
@ -238,7 +238,7 @@ function modify_request(RequestInterface $request, array $changes)
|
|||
}
|
||||
|
||||
if ($request instanceof ServerRequestInterface) {
|
||||
return new ServerRequest(
|
||||
return (new ServerRequest(
|
||||
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
|
||||
$uri,
|
||||
$headers,
|
||||
|
@ -247,7 +247,11 @@ function modify_request(RequestInterface $request, array $changes)
|
|||
? $changes['version']
|
||||
: $request->getProtocolVersion(),
|
||||
$request->getServerParams()
|
||||
);
|
||||
))
|
||||
->withParsedBody($request->getParsedBody())
|
||||
->withQueryParams($request->getQueryParams())
|
||||
->withCookieParams($request->getCookieParams())
|
||||
->withUploadedFiles($request->getUploadedFiles());
|
||||
}
|
||||
|
||||
return new Request(
|
||||
|
@ -431,7 +435,7 @@ function hash(
|
|||
* @param StreamInterface $stream Stream to read from
|
||||
* @param int $maxLength Maximum buffer length
|
||||
*
|
||||
* @return string|bool
|
||||
* @return string
|
||||
*/
|
||||
function readline(StreamInterface $stream, $maxLength = null)
|
||||
{
|
||||
|
@ -495,7 +499,7 @@ function parse_response($message)
|
|||
// between status-code and reason-phrase is required. But browsers accept
|
||||
// responses without space and reason as well.
|
||||
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
|
||||
throw new \InvalidArgumentException('Invalid response string');
|
||||
throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
|
||||
}
|
||||
$parts = explode(' ', $data['start-line'], 3);
|
||||
|
||||
|
@ -516,8 +520,8 @@ function parse_response($message)
|
|||
* 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']).
|
||||
*
|
||||
* @param string $str Query string to parse
|
||||
* @param bool|string $urlEncoding How the query string is encoded
|
||||
* @param string $str Query string to parse
|
||||
* @param int|bool $urlEncoding How the query string is encoded
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -533,9 +537,9 @@ function parse_query($str, $urlEncoding = true)
|
|||
$decoder = function ($value) {
|
||||
return rawurldecode(str_replace('+', ' ', $value));
|
||||
};
|
||||
} elseif ($urlEncoding == PHP_QUERY_RFC3986) {
|
||||
} elseif ($urlEncoding === PHP_QUERY_RFC3986) {
|
||||
$decoder = 'rawurldecode';
|
||||
} elseif ($urlEncoding == PHP_QUERY_RFC1738) {
|
||||
} elseif ($urlEncoding === PHP_QUERY_RFC1738) {
|
||||
$decoder = 'urldecode';
|
||||
} else {
|
||||
$decoder = function ($str) { return $str; };
|
||||
|
@ -633,6 +637,7 @@ function mimetype_from_filename($filename)
|
|||
function mimetype_from_extension($extension)
|
||||
{
|
||||
static $mimetypes = [
|
||||
'3gp' => 'video/3gpp',
|
||||
'7z' => 'application/x-7z-compressed',
|
||||
'aac' => 'audio/x-aac',
|
||||
'ai' => 'application/postscript',
|
||||
|
@ -680,6 +685,7 @@ function mimetype_from_extension($extension)
|
|||
'mid' => 'audio/midi',
|
||||
'midi' => 'audio/midi',
|
||||
'mov' => 'video/quicktime',
|
||||
'mkv' => 'video/x-matroska',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'mp4' => 'video/mp4',
|
||||
'mp4a' => 'audio/mp4',
|
||||
|
@ -718,6 +724,7 @@ function mimetype_from_extension($extension)
|
|||
'txt' => 'text/plain',
|
||||
'wav' => 'audio/x-wav',
|
||||
'webm' => 'video/webm',
|
||||
'webp' => 'image/webp',
|
||||
'wma' => 'audio/x-ms-wma',
|
||||
'wmv' => 'video/x-ms-wmv',
|
||||
'woff' => 'application/x-font-woff',
|
||||
|
@ -758,29 +765,53 @@ function _parse_message($message)
|
|||
throw new \InvalidArgumentException('Invalid message');
|
||||
}
|
||||
|
||||
// Iterate over each line in the message, accounting for line endings
|
||||
$lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => ''];
|
||||
array_shift($lines);
|
||||
$message = ltrim($message, "\r\n");
|
||||
|
||||
for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
|
||||
$line = $lines[$i];
|
||||
// If two line breaks were encountered, then this is the end of body
|
||||
if (empty($line)) {
|
||||
if ($i < $totalLines - 1) {
|
||||
$result['body'] = implode('', array_slice($lines, $i + 2));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (strpos($line, ':')) {
|
||||
$parts = explode(':', $line, 2);
|
||||
$key = trim($parts[0]);
|
||||
$value = isset($parts[1]) ? trim($parts[1]) : '';
|
||||
$result['headers'][$key][] = $value;
|
||||
}
|
||||
$messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
|
||||
|
||||
if ($messageParts === false || count($messageParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
|
||||
}
|
||||
|
||||
return $result;
|
||||
list($rawHeaders, $body) = $messageParts;
|
||||
$rawHeaders .= "\r\n"; // Put back the delimiter we split previously
|
||||
$headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
|
||||
|
||||
if ($headerParts === false || count($headerParts) !== 2) {
|
||||
throw new \InvalidArgumentException('Invalid message: Missing status line');
|
||||
}
|
||||
|
||||
list($startLine, $rawHeaders) = $headerParts;
|
||||
|
||||
if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $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);
|
||||
}
|
||||
|
||||
/** @var array[] $headerLines */
|
||||
$count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
|
||||
|
||||
// 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://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -809,6 +840,46 @@ function _parse_request_uri($path, array $headers)
|
|||
return $scheme . '://' . $host . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short summary of the message body
|
||||
*
|
||||
* Will return `null` if the response is not printable.
|
||||
*
|
||||
* @param MessageInterface $message The message to get the body summary
|
||||
* @param int $truncateAt The maximum allowed size of the summary
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
|
||||
{
|
||||
$body = $message->getBody();
|
||||
|
||||
if (!$body->isSeekable() || !$body->isReadable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$summary = $body->read($truncateAt);
|
||||
$body->rewind();
|
||||
|
||||
if ($size > $truncateAt) {
|
||||
$summary .= ' (truncated...)';
|
||||
}
|
||||
|
||||
// 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]/', $summary)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function _caseless_remove($keys, array $data)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue