changes for filter and print

This commit is contained in:
2024-09-29 16:59:27 +05:45
parent 497f567cba
commit 684e01bf48
1335 changed files with 38709 additions and 74987 deletions

View File

@ -5,7 +5,76 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 2.7.0 - 2024-07-18
### Added
- Add `Utils::redactUserInfo()` method
- Add ability to encode bools as ints in `Query::build`
## 2.6.3 - 2024-07-18
### Fixed
- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null`
### Changed
- PHP 8.4 support
## 2.6.2 - 2023-12-03
### Fixed
- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints
### Changed
- Updated links in docs to their canonical versions
- Replaced `call_user_func*` with native calls
## 2.6.1 - 2023-08-27
### Fixed
- Properly handle the fact that PHP transforms numeric strings in array keys to ints
## 2.6.0 - 2023-08-03
### Changed
- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry
- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload
## 2.5.1 - 2023-08-03
### Fixed
- Corrected mime type for `.acc` files to `audio/aac`
### Changed
- PHP 8.3 support
## 2.5.0 - 2023-04-17
### Changed
- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0`
## 2.4.5 - 2023-04-17
### Fixed
- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec`
- Fixed `Message::bodySummary` when `preg_match` fails
- Fixed header validation issue
## 2.4.4 - 2023-03-09
### Changed
- Removed the need for `AllowDynamicProperties` in `LazyOpenStream`
## 2.4.3 - 2022-10-26

View File

@ -8,12 +8,26 @@ functionality like query string parsing.
![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg)
# Stream implementation
## Features
This package comes with a number of stream implementations and stream
decorators.
## Installation
```shell
composer require guzzlehttp/psr7
```
## Version Guidance
| Version | Status | PHP Version |
|---------|---------------------|--------------|
| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.5 |
## AppendStream
`GuzzleHttp\Psr7\AppendStream`
@ -245,6 +259,8 @@ class EofCallbackStream implements StreamInterface
private $callback;
private $stream;
public function __construct(StreamInterface $stream, callable $cb)
{
$this->stream = $stream;
@ -257,7 +273,7 @@ class EofCallbackStream implements StreamInterface
// Invoke the callback when EOF is hit.
if ($this->eof()) {
call_user_func($this->callback);
($this->callback)();
}
return $result;
@ -420,7 +436,7 @@ will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
## `GuzzleHttp\Psr7\Query::build`
`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string`
`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string`
Build a query string from an array of key value pairs.
@ -482,11 +498,18 @@ a message.
## `GuzzleHttp\Psr7\Utils::readLine`
`public static function readLine(StreamInterface $stream, int $maxLength = null): string`
`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string`
Read a line from the stream up to the maximum allowed buffer length.
## `GuzzleHttp\Psr7\Utils::redactUserInfo`
`public static function redactUserInfo(UriInterface $uri): UriInterface`
Redact the password in the user info part of a URI.
## `GuzzleHttp\Psr7\Utils::streamFor`
`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`
@ -621,7 +644,7 @@ this library also provides additional functionality when working with URIs as st
An instance of `Psr\Http\Message\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](https://tools.ietf.org/html/rfc3986#section-4.2):
[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2):
- network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path`
@ -658,7 +681,7 @@ termed a relative-path reference.
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
`public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool`
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
@ -680,8 +703,8 @@ or the standard port. This method can be used independently of the implementatio
`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need
to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
### `GuzzleHttp\Psr7\Uri::fromParts`
@ -725,8 +748,8 @@ Determines if a modified URL should be considered cross-origin with respect to a
## Reference Resolution
`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
do when resolving a link in a website based on the current request URI.
to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web
browsers do when resolving a link in a website based on the current request URI.
### `GuzzleHttp\Psr7\UriResolver::resolve`
@ -739,7 +762,7 @@ Converts the relative URI into a new URI that is resolved against the base URI.
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4).
### `GuzzleHttp\Psr7\UriResolver::relativize`
@ -765,7 +788,7 @@ echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // pr
## Normalization and Comparison
`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6).
### `GuzzleHttp\Psr7\UriNormalizer::normalize`
@ -847,14 +870,6 @@ This of course assumes they will be resolved against the same base URI. If this
equivalence or difference of relative references does not mean anything.
## Version Guidance
| Version | Status | PHP Version |
|---------|----------------|------------------|
| 1.x | Security fixes | >=5.4,<8.1 |
| 2.x | Latest | ^7.2.5 \|\| ^8.0 |
## Security
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information.

View File

@ -52,7 +52,7 @@
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
@ -60,9 +60,9 @@
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@ -81,9 +81,6 @@
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"config": {

View File

@ -40,12 +40,14 @@ final class AppendStream implements StreamInterface
{
try {
$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 '';
}
}
@ -138,9 +140,9 @@ final class AppendStream implements StreamInterface
public function eof(): bool
{
return !$this->streams ||
($this->current >= count($this->streams) - 1 &&
$this->streams[$this->current]->eof());
return !$this->streams
|| ($this->current >= count($this->streams) - 1
&& $this->streams[$this->current]->eof());
}
public function rewind(): void
@ -167,7 +169,7 @@ final class AppendStream implements StreamInterface
$stream->rewind();
} catch (\Exception $e) {
throw new \RuntimeException('Unable to seek stream '
. $i . ' of the AppendStream', 0, $e);
.$i.' of the AppendStream', 0, $e);
}
}
@ -197,7 +199,7 @@ final class AppendStream implements StreamInterface
if ($this->current === $total) {
break;
}
$this->current++;
++$this->current;
}
$result = $this->streams[$this->current]->read($remaining);
@ -237,8 +239,6 @@ final class AppendStream implements StreamInterface
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)

View File

@ -134,8 +134,6 @@ final class BufferStream implements StreamInterface
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)

View File

@ -33,7 +33,7 @@ final class CachingStream implements StreamInterface
*/
public function __construct(
StreamInterface $stream,
StreamInterface $target = null
?StreamInterface $target = null
) {
$this->remoteStream = $stream;
$this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));

View File

@ -18,7 +18,7 @@ final class FnStream implements StreamInterface
private const SLOTS = [
'__toString', 'close', 'detach', 'rewind',
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
'isReadable', 'read', 'getContents', 'getMetadata'
'isReadable', 'read', 'getContents', 'getMetadata',
];
/** @var array<string, callable> */
@ -33,7 +33,7 @@ final class FnStream implements StreamInterface
// Create the functions on the class
foreach ($methods as $name => $fn) {
$this->{'_fn_' . $name} = $fn;
$this->{'_fn_'.$name} = $fn;
}
}
@ -45,7 +45,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');
.'() is not implemented in the FnStream');
}
/**
@ -54,7 +54,7 @@ final class FnStream implements StreamInterface
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
($this->_fn_close)();
}
}
@ -93,88 +93,88 @@ final class FnStream implements StreamInterface
public function __toString(): string
{
try {
return call_user_func($this->_fn___toString);
/** @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);
return '';
}
}
public function close(): void
{
call_user_func($this->_fn_close);
($this->_fn_close)();
}
public function detach()
{
return call_user_func($this->_fn_detach);
return ($this->_fn_detach)();
}
public function getSize(): ?int
{
return call_user_func($this->_fn_getSize);
return ($this->_fn_getSize)();
}
public function tell(): int
{
return call_user_func($this->_fn_tell);
return ($this->_fn_tell)();
}
public function eof(): bool
{
return call_user_func($this->_fn_eof);
return ($this->_fn_eof)();
}
public function isSeekable(): bool
{
return call_user_func($this->_fn_isSeekable);
return ($this->_fn_isSeekable)();
}
public function rewind(): void
{
call_user_func($this->_fn_rewind);
($this->_fn_rewind)();
}
public function seek($offset, $whence = SEEK_SET): void
{
call_user_func($this->_fn_seek, $offset, $whence);
($this->_fn_seek)($offset, $whence);
}
public function isWritable(): bool
{
return call_user_func($this->_fn_isWritable);
return ($this->_fn_isWritable)();
}
public function write($string): int
{
return call_user_func($this->_fn_write, $string);
return ($this->_fn_write)($string);
}
public function isReadable(): bool
{
return call_user_func($this->_fn_isReadable);
return ($this->_fn_isReadable)();
}
public function read($length): string
{
return call_user_func($this->_fn_read, $length);
return ($this->_fn_read)($length);
}
public function getContents(): string
{
return call_user_func($this->_fn_getContents);
return ($this->_fn_getContents)();
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)
{
return call_user_func($this->_fn_getMetadata, $key);
return ($this->_fn_getMetadata)($key);
}
}

View File

@ -22,7 +22,7 @@ final class Header
foreach ((array) $header as $value) {
foreach (self::splitList($value) as $val) {
$part = [];
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) ?: [] as $kvp) {
if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
$m = $matches[0];
if (isset($m[1])) {
@ -89,7 +89,7 @@ final class Header
$v = '';
$isQuoted = false;
$isEscaped = false;
for ($i = 0, $max = \strlen($value); $i < $max; $i++) {
for ($i = 0, $max = \strlen($value); $i < $max; ++$i) {
if ($isEscaped) {
$v .= $value[$i];
$isEscaped = false;

View File

@ -23,20 +23,14 @@ use Psr\Http\Message\UriInterface;
* Note: in consuming code it is recommended to require the implemented interfaces
* and inject the instance of this class multiple times.
*/
final class HttpFactory implements
RequestFactoryInterface,
ResponseFactoryInterface,
ServerRequestFactoryInterface,
StreamFactoryInterface,
UploadedFileFactoryInterface,
UriFactoryInterface
final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
{
public function createUploadedFile(
StreamInterface $stream,
int $size = null,
?int $size = null,
int $error = \UPLOAD_ERR_OK,
string $clientFilename = null,
string $clientMediaType = null
?string $clientFilename = null,
?string $clientMediaType = null
): UploadedFileInterface {
if ($size === null) {
$size = $stream->getSize();

View File

@ -13,9 +13,9 @@ use Psr\Http\Message\StreamInterface;
* then appends the zlib.inflate filter. The stream is then converted back
* to a Guzzle stream resource to be used as a Guzzle stream.
*
* @link http://tools.ietf.org/html/rfc1950
* @link http://tools.ietf.org/html/rfc1952
* @link http://php.net/manual/en/filters.compression.php
* @see https://datatracker.ietf.org/doc/html/rfc1950
* @see https://datatracker.ietf.org/doc/html/rfc1952
* @see https://www.php.net/manual/en/filters.compression.php
*/
final class InflateStream implements StreamInterface
{
@ -28,7 +28,7 @@ final class InflateStream implements StreamInterface
{
$resource = StreamWrapper::getResource($stream);
// Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data
// See http://www.zlib.net/manual.html#Advanced definition of inflateInit2
// See https://www.zlib.net/manual.html#Advanced definition of inflateInit2
// "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
// Default window size is 15.
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]);

View File

@ -10,7 +10,6 @@ use Psr\Http\Message\StreamInterface;
* Lazily reads or writes to a file that is opened only after an IO operation
* take place on the stream.
*/
#[\AllowDynamicProperties]
final class LazyOpenStream implements StreamInterface
{
use StreamDecoratorTrait;
@ -21,6 +20,11 @@ final class LazyOpenStream implements StreamInterface
/** @var string */
private $mode;
/**
* @var StreamInterface
*/
private $stream;
/**
* @param string $filename File to lazily open
* @param string $mode fopen mode to use when opening the stream
@ -29,6 +33,10 @@ final class LazyOpenStream implements StreamInterface
{
$this->filename = $filename;
$this->mode = $mode;
// unsetting the property forces the first access to go through
// __get().
unset($this->stream);
}
/**

View File

@ -18,31 +18,31 @@ final class Message
public static function toString(MessageInterface $message): string
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod() . ' '
. $message->getRequestTarget())
. ' HTTP/' . $message->getProtocolVersion();
$msg = trim($message->getMethod().' '
.$message->getRequestTarget())
.' HTTP/'.$message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: " . $message->getUri()->getHost();
$msg .= "\r\nHost: ".$message->getUri()->getHost();
}
} elseif ($message instanceof ResponseInterface) {
$msg = 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
$msg = 'HTTP/'.$message->getProtocolVersion().' '
.$message->getStatusCode().' '
.$message->getReasonPhrase();
} else {
throw new \InvalidArgumentException('Unknown message type');
}
foreach ($message->getHeaders() as $name => $values) {
if (strtolower($name) === 'set-cookie') {
if (is_string($name) && strtolower($name) === 'set-cookie') {
foreach ($values as $value) {
$msg .= "\r\n{$name}: " . $value;
$msg .= "\r\n{$name}: ".$value;
}
} else {
$msg .= "\r\n{$name}: " . implode(', ', $values);
$msg .= "\r\n{$name}: ".implode(', ', $values);
}
}
return "{$msg}\r\n\r\n" . $message->getBody();
return "{$msg}\r\n\r\n".$message->getBody();
}
/**
@ -77,7 +77,7 @@ final class Message
// 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)) {
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
return null;
}
@ -146,7 +146,7 @@ final class Message
// 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
// Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
}
@ -190,7 +190,7 @@ final class Message
$host = $headers[reset($hostKey)][0];
$scheme = substr($host, -4) === ':443' ? 'https' : 'http';
return $scheme . '://' . $host . '/' . ltrim($path, '/');
return $scheme.'://'.$host.'/'.ltrim($path, '/');
}
/**
@ -227,11 +227,11 @@ final class Message
public static function parseResponse(string $message): ResponseInterface
{
$data = self::parseMessage($message);
// According to https://tools.ietf.org/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.
// 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.
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
}
$parts = explode(' ', $data['start-line'], 3);

View File

@ -12,11 +12,11 @@ use Psr\Http\Message\StreamInterface;
*/
trait MessageTrait
{
/** @var array<string, string[]> Map of all registered headers, as original name => array of values */
/** @var string[][] Map of all registered headers, as original name => array of values */
private $headers = [];
/** @var array<string, string> Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string[] Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string */
private $protocol = '1.1';
@ -37,6 +37,7 @@ trait MessageTrait
$new = clone $this;
$new->protocol = $version;
return $new;
}
@ -135,11 +136,12 @@ trait MessageTrait
$new = clone $this;
$new->stream = $body;
return $new;
}
/**
* @param array<string|int, string|string[]> $headers
* @param (string|string[])[] $headers
*/
private function setHeaders(array $headers): void
{
@ -191,7 +193,7 @@ trait MessageTrait
*
* @return string[] Trimmed header values
*
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
*/
private function trimAndValidateHeaderValues(array $values): array
{
@ -211,7 +213,7 @@ trait MessageTrait
}
/**
* @see https://tools.ietf.org/html/rfc7230#section-3.2
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
*
* @param mixed $header
*/
@ -224,18 +226,15 @@ trait MessageTrait
));
}
if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) {
if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
throw new \InvalidArgumentException(
sprintf(
'"%s" is not valid header name',
$header
)
sprintf('"%s" is not valid header name.', $header)
);
}
}
/**
* @see https://tools.ietf.org/html/rfc7230#section-3.2
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
*
* field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
@ -257,8 +256,10 @@ trait MessageTrait
// 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]*$/', $value)) {
throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value));
if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
throw new \InvalidArgumentException(
sprintf('"%s" is not valid header value.', $value)
);
}
}
}

View File

@ -18,7 +18,7 @@ final class MimeType
'7zip' => 'application/x-7z-compressed',
'123' => 'application/vnd.lotus-1-2-3',
'aab' => 'application/x-authorware-bin',
'aac' => 'audio/x-acc',
'aac' => 'audio/aac',
'aam' => 'application/x-authorware-map',
'aas' => 'application/x-authorware-seg',
'abw' => 'application/x-abiword',
@ -29,6 +29,7 @@ final class MimeType
'acu' => 'application/vnd.acucobol',
'acutc' => 'application/vnd.acucorp',
'adp' => 'audio/adpcm',
'adts' => 'audio/aac',
'aep' => 'application/vnd.audiograph',
'afm' => 'application/x-font-type1',
'afp' => 'application/vnd.ibm.modcap',
@ -41,11 +42,16 @@ final class MimeType
'air' => 'application/vnd.adobe.air-application-installer-package+zip',
'ait' => 'application/vnd.dvb.ait',
'ami' => 'application/vnd.amiga.ami',
'aml' => 'application/automationml-aml+xml',
'amlx' => 'application/automationml-amlx+zip',
'amr' => 'audio/amr',
'apk' => 'application/vnd.android.package-archive',
'apng' => 'image/apng',
'appcache' => 'text/cache-manifest',
'appinstaller' => 'application/appinstaller',
'application' => 'application/x-ms-application',
'appx' => 'application/appx',
'appxbundle' => 'application/appxbundle',
'apr' => 'application/vnd.lotus-approach',
'arc' => 'application/x-freearc',
'arj' => 'application/x-arj',
@ -90,6 +96,7 @@ final class MimeType
'bpk' => 'application/octet-stream',
'bpmn' => 'application/octet-stream',
'bsp' => 'model/vnd.valve.source.compiled-map',
'btf' => 'image/prs.btif',
'btif' => 'image/prs.btif',
'buffer' => 'application/octet-stream',
'bz' => 'application/x-bzip',
@ -141,6 +148,7 @@ final class MimeType
'cjs' => 'application/node',
'cla' => 'application/vnd.claymore',
'class' => 'application/octet-stream',
'cld' => 'model/vnd.cld',
'clkk' => 'application/vnd.crick.clicker.keyboard',
'clkp' => 'application/vnd.crick.clicker.palette',
'clkt' => 'application/vnd.crick.clicker.template',
@ -175,6 +183,7 @@ final class MimeType
'csv' => 'text/csv',
'cu' => 'application/cu-seeme',
'curl' => 'text/vnd.curl',
'cwl' => 'application/cwl',
'cww' => 'application/prs.cww',
'cxt' => 'application/x-director',
'cxx' => 'text/x-c',
@ -197,6 +206,7 @@ final class MimeType
'der' => 'application/x-x509-ca-cert',
'dfac' => 'application/vnd.dreamfactory',
'dgc' => 'application/x-dgc-compressed',
'dib' => 'image/bmp',
'dic' => 'text/x-c',
'dir' => 'application/x-director',
'dis' => 'application/vnd.mobius.dis',
@ -219,6 +229,7 @@ final class MimeType
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'dp' => 'application/vnd.osgi.dp',
'dpg' => 'application/vnd.dpgraph',
'dpx' => 'image/dpx',
'dra' => 'audio/vnd.dra',
'drle' => 'image/dicom-rle',
'dsc' => 'text/prs.lines.tag',
@ -255,7 +266,6 @@ final class MimeType
'eot' => 'application/vnd.ms-fontobject',
'eps' => 'application/postscript',
'epub' => 'application/epub+zip',
'es' => 'application/ecmascript',
'es3' => 'application/vnd.eszigno3+xml',
'esa' => 'application/vnd.osgi.subsystem',
'esf' => 'application/vnd.epson.esf',
@ -448,6 +458,7 @@ final class MimeType
'jsonld' => 'application/ld+json',
'jsonml' => 'application/jsonml+json',
'jsx' => 'text/jsx',
'jt' => 'model/jt',
'jxr' => 'image/jxr',
'jxra' => 'image/jxra',
'jxrs' => 'image/jxrs',
@ -552,7 +563,7 @@ final class MimeType
'mime' => 'message/rfc822',
'mj2' => 'video/mj2',
'mjp2' => 'video/mj2',
'mjs' => 'application/javascript',
'mjs' => 'text/javascript',
'mk3d' => 'video/x-matroska',
'mka' => 'audio/x-matroska',
'mkd' => 'text/x-markdown',
@ -602,6 +613,8 @@ final class MimeType
'msg' => 'application/vnd.ms-outlook',
'msh' => 'model/mesh',
'msi' => 'application/x-msdownload',
'msix' => 'application/msix',
'msixbundle' => 'application/msixbundle',
'msl' => 'application/vnd.mobius.msl',
'msm' => 'application/octet-stream',
'msp' => 'application/octet-stream',
@ -775,6 +788,8 @@ final class MimeType
'pvb' => 'application/vnd.3gpp.pic-bw-var',
'pwn' => 'application/vnd.3m.post-it-notes',
'pya' => 'audio/vnd.ms-playready.media.pya',
'pyo' => 'model/vnd.pytha.pyox',
'pyox' => 'model/vnd.pytha.pyox',
'pyv' => 'video/vnd.ms-playready.media.pyv',
'qam' => 'application/vnd.epson.quickanime',
'qbo' => 'application/vnd.intu.qbo',
@ -923,10 +938,12 @@ final class MimeType
'st' => 'application/vnd.sailingtracker.track',
'stc' => 'application/vnd.sun.xml.calc.template',
'std' => 'application/vnd.sun.xml.draw.template',
'step' => 'application/STEP',
'stf' => 'application/vnd.wt.stf',
'sti' => 'application/vnd.sun.xml.impress.template',
'stk' => 'application/hyperstudio',
'stl' => 'model/stl',
'stp' => 'application/STEP',
'stpx' => 'model/step+xml',
'stpxz' => 'model/step-xml+zip',
'stpz' => 'model/step+zip',
@ -1013,10 +1030,12 @@ final class MimeType
'ulx' => 'application/x-glulx',
'umj' => 'application/vnd.umajin',
'unityweb' => 'application/vnd.unity',
'uo' => 'application/vnd.uoml+xml',
'uoml' => 'application/vnd.uoml+xml',
'uri' => 'text/uri-list',
'uris' => 'text/uri-list',
'urls' => 'text/uri-list',
'usda' => 'model/vnd.usda',
'usdz' => 'model/vnd.usdz+zip',
'ustar' => 'application/x-ustar',
'utz' => 'application/vnd.uiq.theme',
@ -1096,6 +1115,7 @@ final class MimeType
'webmanifest' => 'application/manifest+json',
'webp' => 'image/webp',
'wg' => 'application/vnd.pmi.widget',
'wgsl' => 'text/wgsl',
'wgt' => 'application/widget',
'wif' => 'application/watcherinfo+xml',
'wks' => 'application/vnd.ms-works',
@ -1150,9 +1170,10 @@ final class MimeType
'xel' => 'application/xcap-el+xml',
'xenc' => 'application/xenc+xml',
'xer' => 'application/patch-ops-error+xml',
'xfdf' => 'application/vnd.adobe.xfdf',
'xfdf' => 'application/xfdf',
'xfdl' => 'application/vnd.xfdl',
'xht' => 'application/xhtml+xml',
'xhtm' => 'application/vnd.pwg-xhtml-print+xml',
'xhtml' => 'application/xhtml+xml',
'xhvml' => 'application/xv+xml',
'xif' => 'image/vnd.xiff',
@ -1183,6 +1204,7 @@ final class MimeType
'xpw' => 'application/vnd.intercon.formnet',
'xpx' => 'application/vnd.intercon.formnet',
'xsd' => 'application/xml',
'xsf' => 'application/prs.xsf+xml',
'xsl' => 'application/xml',
'xslt' => 'application/xslt+xml',
'xsm' => 'application/vnd.syncml+xml',
@ -1218,7 +1240,7 @@ final class MimeType
/**
* Determines the mimetype of a file by looking at its extension.
*
* @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
* @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
*/
public static function fromFilename(string $filename): ?string
{
@ -1228,7 +1250,7 @@ final class MimeType
/**
* Maps a file extensions to a mimetype.
*
* @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
* @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
*/
public static function fromExtension(string $extension): ?string
{

View File

@ -32,7 +32,7 @@ final class MultipartStream implements StreamInterface
*
* @throws \InvalidArgumentException
*/
public function __construct(array $elements = [], string $boundary = null)
public function __construct(array $elements = [], ?string $boundary = null)
{
$this->boundary = $boundary ?: bin2hex(random_bytes(20));
$this->stream = $this->createStream($elements);
@ -51,7 +51,7 @@ final class MultipartStream implements StreamInterface
/**
* Get the headers needed before transferring the content of a POST file
*
* @param array<string, string> $headers
* @param string[] $headers
*/
private function getHeaders(array $headers): string
{
@ -60,7 +60,7 @@ final class MultipartStream implements StreamInterface
$str .= "{$key}: {$value}\r\n";
}
return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
}
/**
@ -72,7 +72,7 @@ final class MultipartStream implements StreamInterface
foreach ($elements as $element) {
if (!is_array($element)) {
throw new \UnexpectedValueException("An array is expected");
throw new \UnexpectedValueException('An array is expected');
}
$this->addElement($stream, $element);
}
@ -112,10 +112,15 @@ final class MultipartStream implements StreamInterface
$stream->addStream(Utils::streamFor("\r\n"));
}
/**
* @param string[] $headers
*
* @return array{0: StreamInterface, 1: string[]}
*/
private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
{
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');
$disposition = self::getHeader($headers, 'content-disposition');
if (!$disposition) {
$headers['Content-Disposition'] = ($filename === '0' || $filename)
? sprintf(
@ -127,7 +132,7 @@ final class MultipartStream implements StreamInterface
}
// Set a default content-length header if one was no provided
$length = $this->getHeader($headers, 'content-length');
$length = self::getHeader($headers, 'content-length');
if (!$length) {
if ($length = $stream->getSize()) {
$headers['Content-Length'] = (string) $length;
@ -135,21 +140,22 @@ final class MultipartStream implements StreamInterface
}
// Set a default Content-Type if one was not supplied
$type = $this->getHeader($headers, 'content-type');
$type = self::getHeader($headers, 'content-type');
if (!$type && ($filename === '0' || $filename)) {
if ($type = MimeType::fromFilename($filename)) {
$headers['Content-Type'] = $type;
}
$headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
}
return [$stream, $headers];
}
private function getHeader(array $headers, string $key)
/**
* @param string[] $headers
*/
private static function getHeader(array $headers, string $key): ?string
{
$lowercaseHeader = strtolower($key);
foreach ($headers as $k => $v) {
if (strtolower($k) === $lowercaseHeader) {
if (strtolower((string) $k) === $lowercaseHeader) {
return $v;
}
}

View File

@ -18,7 +18,7 @@ use Psr\Http\Message\StreamInterface;
*/
final class PumpStream implements StreamInterface
{
/** @var callable|null */
/** @var callable(int): (string|false|null)|null */
private $source;
/** @var int|null */
@ -34,7 +34,7 @@ final class PumpStream implements StreamInterface
private $buffer;
/**
* @param callable(int): (string|null|false) $source Source of the stream data. The callable MAY
* @param callable(int): (string|false|null) $source Source of the stream data. The callable MAY
* accept an integer argument used to control the
* amount of data to return. The callable MUST
* return a string when called, or false|null on error
@ -60,6 +60,7 @@ final class PumpStream implements StreamInterface
throw $e;
}
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
return '';
}
}
@ -149,8 +150,6 @@ final class PumpStream implements StreamInterface
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)
@ -164,11 +163,12 @@ final class PumpStream implements StreamInterface
private function pump(int $length): void
{
if ($this->source) {
if ($this->source !== null) {
do {
$data = call_user_func($this->source, $length);
$data = ($this->source)($length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);

View File

@ -63,12 +63,15 @@ final class Query
* string. This function does not modify the provided keys when an array is
* encountered (like `http_build_query()` would).
*
* @param array $params Query string parameters.
* @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
* to encode using RFC3986, or PHP_QUERY_RFC1738
* to encode using RFC1738.
* @param array $params Query string parameters.
* @param int|false $encoding Set to false to not encode,
* PHP_QUERY_RFC3986 to encode using
* RFC3986, or PHP_QUERY_RFC1738 to
* encode using RFC1738.
* @param bool $treatBoolsAsInts Set to true to encode as 0/1, and
* false as false/true.
*/
public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string
public static function build(array $params, $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string
{
if (!$params) {
return '';
@ -86,22 +89,24 @@ final class Query
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) ? (int) $v : $v;
$v = is_bool($v) ? $castBool($v) : $v;
if ($v !== null) {
$qs .= '=' . $encoder((string) $v);
$qs .= '='.$encoder((string) $v);
}
$qs .= '&';
} else {
foreach ($v as $vv) {
$qs .= $k;
$vv = is_bool($vv) ? (int) $vv : $vv;
$vv = is_bool($vv) ? $castBool($vv) : $vv;
if ($vv !== null) {
$qs .= '=' . $encoder((string) $vv);
$qs .= '='.$encoder((string) $vv);
}
$qs .= '&';
}

View File

@ -28,7 +28,7 @@ class Request implements RequestInterface
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array<string, string|string[]> $headers Request headers
* @param (string|string[])[] $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
*/
@ -69,7 +69,7 @@ class Request implements RequestInterface
$target = '/';
}
if ($this->uri->getQuery() != '') {
$target .= '?' . $this->uri->getQuery();
$target .= '?'.$this->uri->getQuery();
}
return $target;
@ -85,6 +85,7 @@ class Request implements RequestInterface
$new = clone $this;
$new->requestTarget = $requestTarget;
return $new;
}
@ -98,6 +99,7 @@ class Request implements RequestInterface
$this->assertMethod($method);
$new = clone $this;
$new->method = strtoupper($method);
return $new;
}
@ -131,7 +133,7 @@ class Request implements RequestInterface
}
if (($port = $this->uri->getPort()) !== null) {
$host .= ':' . $port;
$host .= ':'.$port;
}
if (isset($this->headerNames['host'])) {
@ -141,7 +143,7 @@ class Request implements RequestInterface
$this->headerNames['host'] = 'Host';
}
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
// See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
}

View File

@ -86,7 +86,7 @@ class Response implements ResponseInterface
/**
* @param int $status Status code
* @param array<string, string|string[]> $headers Response headers
* @param (string|string[])[] $headers Response headers
* @param string|resource|StreamInterface|null $body Response body
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
@ -96,7 +96,7 @@ class Response implements ResponseInterface
array $headers = [],
$body = null,
string $version = '1.1',
string $reason = null
?string $reason = null
) {
$this->assertStatusCodeRange($status);
@ -138,6 +138,7 @@ class Response implements ResponseInterface
$reasonPhrase = self::PHRASES[$new->statusCode];
}
$new->reasonPhrase = (string) $reasonPhrase;
return $new;
}

View File

@ -14,7 +14,7 @@ final class Rfc7230
*
* 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
* @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
*/

View File

@ -59,7 +59,7 @@ class ServerRequest extends Request implements ServerRequestInterface
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array<string, string|string[]> $headers Request headers
* @param (string|string[])[] $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
@ -144,10 +144,10 @@ class ServerRequest extends Request implements ServerRequestInterface
foreach (array_keys($files['tmp_name']) as $key) {
$spec = [
'tmp_name' => $files['tmp_name'][$key],
'size' => $files['size'][$key],
'error' => $files['error'][$key],
'name' => $files['name'][$key],
'type' => $files['type'][$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);
}
@ -182,7 +182,7 @@ class ServerRequest extends Request implements ServerRequestInterface
private static function extractHostAndPortFromAuthority(string $authority): array
{
$uri = 'http://' . $authority;
$uri = 'http://'.$authority;
$parts = parse_url($uri);
if (false === $parts) {
return [null, null];
@ -286,8 +286,6 @@ class ServerRequest extends Request implements ServerRequestInterface
}
/**
* {@inheritdoc}
*
* @return array|object|null
*/
public function getParsedBody()
@ -309,8 +307,6 @@ class ServerRequest extends Request implements ServerRequestInterface
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getAttribute($attribute, $default = null)

View File

@ -12,8 +12,8 @@ use Psr\Http\Message\StreamInterface;
class Stream implements StreamInterface
{
/**
* @see http://php.net/manual/function.fopen.php
* @see http://php.net/manual/en/function.gzopen.php
* @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/';
@ -61,8 +61,8 @@ class Stream implements StreamInterface
$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 = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
$this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
$this->uri = $this->getMetadata('uri');
}
@ -80,12 +80,14 @@ class Stream implements StreamInterface
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 '';
}
}
@ -145,6 +147,7 @@ class Stream implements StreamInterface
$stats = fstat($this->stream);
if (is_array($stats) && isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
@ -207,7 +210,7 @@ class Stream implements StreamInterface
}
if (fseek($this->stream, $offset, $whence) === -1) {
throw new \RuntimeException('Unable to seek to stream position '
. $offset . ' with whence ' . var_export($whence, true));
.$offset.' with whence '.var_export($whence, true));
}
}
@ -261,8 +264,6 @@ class Stream implements StreamInterface
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)

View File

@ -31,6 +31,7 @@ trait StreamDecoratorTrait
{
if ($name === 'stream') {
$this->stream = $this->createStream();
return $this->stream;
}
@ -43,12 +44,14 @@ trait StreamDecoratorTrait
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 '';
}
}
@ -67,7 +70,7 @@ trait StreamDecoratorTrait
{
/** @var callable $callable */
$callable = [$this->stream, $method];
$result = call_user_func_array($callable, $args);
$result = ($callable)(...$args);
// Always return the wrapped object if the result is a return $this
return $result === $this->stream ? $this : $result;
@ -79,8 +82,6 @@ trait StreamDecoratorTrait
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function getMetadata($key = null)

View File

@ -41,7 +41,7 @@ final class StreamWrapper
$mode = 'w';
} else {
throw new \InvalidArgumentException('The stream must be readable, '
. 'writable, or both.');
.'writable, or both.');
}
return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
@ -55,7 +55,7 @@ final class StreamWrapper
public static function createStreamContext(StreamInterface $stream)
{
return stream_context_create([
'guzzle' => ['stream' => $stream]
'guzzle' => ['stream' => $stream],
]);
}
@ -69,7 +69,7 @@ final class StreamWrapper
}
}
public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
{
$options = stream_context_get_options($this->context);
@ -115,61 +115,93 @@ final class StreamWrapper
*/
public function stream_cast(int $cast_as)
{
$stream = clone($this->stream);
$stream = clone $this->stream;
$resource = $stream->detach();
return $resource ?? false;
}
/**
* @return array<int|string, int>
* @return array{
* dev: int,
* ino: int,
* mode: int,
* nlink: int,
* uid: int,
* gid: int,
* rdev: int,
* size: int,
* atime: int,
* mtime: int,
* ctime: int,
* blksize: int,
* blocks: int
* }|false
*/
public function stream_stat(): array
public function stream_stat()
{
if ($this->stream->getSize() === null) {
return false;
}
static $modeMap = [
'r' => 33060,
'r' => 33060,
'rb' => 33060,
'r+' => 33206,
'w' => 33188,
'wb' => 33188
'w' => 33188,
'wb' => 33188,
];
return [
'dev' => 0,
'ino' => 0,
'mode' => $modeMap[$this->mode],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $this->stream->getSize() ?: 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'dev' => 0,
'ino' => 0,
'mode' => $modeMap[$this->mode],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $this->stream->getSize() ?: 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0
'blocks' => 0,
];
}
/**
* @return array<int|string, int>
* @return array{
* dev: int,
* ino: int,
* mode: int,
* nlink: int,
* uid: int,
* gid: int,
* rdev: int,
* size: int,
* atime: int,
* mtime: int,
* ctime: int,
* blksize: int,
* blocks: int
* }
*/
public function url_stat(string $path, int $flags): array
{
return [
'dev' => 0,
'ino' => 0,
'mode' => 0,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'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
'blocks' => 0,
];
}
}

View File

@ -64,8 +64,8 @@ class UploadedFile implements UploadedFileInterface
$streamOrFile,
?int $size,
int $errorStatus,
string $clientFilename = null,
string $clientMediaType = null
?string $clientFilename = null,
?string $clientMediaType = null
) {
$this->setError($errorStatus);
$this->size = $size;
@ -113,7 +113,7 @@ class UploadedFile implements UploadedFileInterface
$this->error = $error;
}
private function isStringNotEmpty($param): bool
private static function isStringNotEmpty($param): bool
{
return is_string($param) && false === empty($param);
}
@ -163,7 +163,7 @@ class UploadedFile implements UploadedFileInterface
{
$this->validateActive();
if (false === $this->isStringNotEmpty($targetPath)) {
if (false === self::isStringNotEmpty($targetPath)) {
throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a non-empty string'
);

View File

@ -25,7 +25,7 @@ class Uri implements UriInterface, \JsonSerializable
private const HTTP_DEFAULT_HOST = 'localhost';
private const DEFAULT_PORTS = [
'http' => 80,
'http' => 80,
'https' => 443,
'ftp' => 21,
'gopher' => 70,
@ -41,14 +41,14 @@ class Uri implements UriInterface, \JsonSerializable
/**
* Unreserved characters for use in a regex.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.3
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
*/
private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
/**
* Sub-delims for use in a regex.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
*/
private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
@ -87,6 +87,7 @@ class Uri implements UriInterface, \JsonSerializable
$this->applyParts($parts);
}
}
/**
* UTF-8 aware \parse_url() replacement.
*
@ -121,7 +122,7 @@ class Uri implements UriInterface, \JsonSerializable
$url
);
$result = parse_url($prefix . $encodedUrl);
$result = parse_url($prefix.$encodedUrl);
if ($result === false) {
return false;
@ -161,7 +162,7 @@ class Uri implements UriInterface, \JsonSerializable
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
* that format).
*
* @link https://tools.ietf.org/html/rfc3986#section-5.3
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
*/
public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
{
@ -169,25 +170,25 @@ class Uri implements UriInterface, \JsonSerializable
// weak type checks to also accept null until we can add scalar type hints
if ($scheme != '') {
$uri .= $scheme . ':';
$uri .= $scheme.':';
}
if ($authority != '' || $scheme === 'file') {
$uri .= '//' . $authority;
$uri .= '//'.$authority;
}
if ($authority != '' && $path != '' && $path[0] != '/') {
$path = '/' . $path;
$path = '/'.$path;
}
$uri .= $path;
if ($query != '') {
$uri .= '?' . $query;
$uri .= '?'.$query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
$uri .= '#'.$fragment;
}
return $uri;
@ -218,7 +219,7 @@ class Uri implements UriInterface, \JsonSerializable
* @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference
* @link https://tools.ietf.org/html/rfc3986#section-4
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
*/
public static function isAbsolute(UriInterface $uri): bool
{
@ -230,7 +231,7 @@ class Uri implements UriInterface, \JsonSerializable
*
* A relative reference that begins with two slash characters is termed an network-path reference.
*
* @link https://tools.ietf.org/html/rfc3986#section-4.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/
public static function isNetworkPathReference(UriInterface $uri): bool
{
@ -242,7 +243,7 @@ class Uri implements UriInterface, \JsonSerializable
*
* A relative reference that begins with a single slash character is termed an absolute-path reference.
*
* @link https://tools.ietf.org/html/rfc3986#section-4.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/
public static function isAbsolutePathReference(UriInterface $uri): bool
{
@ -257,7 +258,7 @@ class Uri implements UriInterface, \JsonSerializable
*
* A relative reference that does not begin with a slash character is termed a relative-path reference.
*
* @link https://tools.ietf.org/html/rfc3986#section-4.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/
public static function isRelativePathReference(UriInterface $uri): bool
{
@ -276,9 +277,9 @@ class Uri implements UriInterface, \JsonSerializable
* @param UriInterface $uri The URI to check
* @param UriInterface|null $base An optional base URI to compare against
*
* @link https://tools.ietf.org/html/rfc3986#section-4.4
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
*/
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool
public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
{
if ($base !== null) {
$uri = UriResolver::resolve($base, $uri);
@ -335,8 +336,8 @@ class Uri implements UriInterface, \JsonSerializable
*
* 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<string, string|null> $keyValueArray Associative array of key and values
* @param UriInterface $uri URI to use as a base.
* @param (string|null)[] $keyValueArray Associative array of key and values
*/
public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
{
@ -352,7 +353,7 @@ class Uri implements UriInterface, \JsonSerializable
/**
* Creates a URI from a hash of `parse_url` components.
*
* @link http://php.net/manual/en/function.parse-url.php
* @see https://www.php.net/manual/en/function.parse-url.php
*
* @throws MalformedUriException If the components do not form a valid URI.
*/
@ -374,11 +375,11 @@ class Uri implements UriInterface, \JsonSerializable
{
$authority = $this->host;
if ($this->userInfo !== '') {
$authority = $this->userInfo . '@' . $authority;
$authority = $this->userInfo.'@'.$authority;
}
if ($this->port !== null) {
$authority .= ':' . $this->port;
$authority .= ':'.$this->port;
}
return $authority;
@ -435,7 +436,7 @@ class Uri implements UriInterface, \JsonSerializable
{
$info = $this->filterUserInfoComponent($user);
if ($password !== null) {
$info .= ':' . $this->filterUserInfoComponent($password);
$info .= ':'.$this->filterUserInfoComponent($password);
}
if ($this->userInfo === $info) {
@ -563,7 +564,7 @@ class Uri implements UriInterface, \JsonSerializable
? $this->filterQueryAndFragment($parts['fragment'])
: '';
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
$this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']);
}
$this->removeDefaultPort();
@ -595,7 +596,7 @@ class Uri implements UriInterface, \JsonSerializable
}
return preg_replace_callback(
'/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$component
);
@ -627,7 +628,7 @@ class Uri implements UriInterface, \JsonSerializable
}
$port = (int) $port;
if (0 > $port || 0xffff < $port) {
if (0 > $port || 0xFFFF < $port) {
throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
);
@ -637,7 +638,7 @@ class Uri implements UriInterface, \JsonSerializable
}
/**
* @param string[] $keys
* @param (string|int)[] $keys
*
* @return string[]
*/
@ -649,7 +650,9 @@ class Uri implements UriInterface, \JsonSerializable
return [];
}
$decodedKeys = array_map('rawurldecode', $keys);
$decodedKeys = array_map(function ($k): string {
return rawurldecode((string) $k);
}, $keys);
return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
@ -664,7 +667,7 @@ class Uri implements UriInterface, \JsonSerializable
$queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
if ($value !== null) {
$queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
$queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
}
return $queryString;
@ -691,7 +694,7 @@ class Uri implements UriInterface, \JsonSerializable
}
return preg_replace_callback(
'/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$path
);
@ -711,7 +714,7 @@ class Uri implements UriInterface, \JsonSerializable
}
return preg_replace_callback(
'/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$str
);

View File

@ -11,7 +11,7 @@ use Psr\Http\Message\UriInterface;
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-6
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-6
*/
final class UriNormalizer
{
@ -119,7 +119,7 @@ final class UriNormalizer
* @param UriInterface $uri The URI to normalize
* @param int $flags A bitmask of normalizations to apply, see constants
*
* @link https://tools.ietf.org/html/rfc3986#section-6.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2
*/
public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
{
@ -131,8 +131,8 @@ final class UriNormalizer
$uri = self::decodeUnreservedCharacters($uri);
}
if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' &&
($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === ''
&& ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
) {
$uri = $uri->withPath('/');
}
@ -174,7 +174,7 @@ final class UriNormalizer
* @param UriInterface $uri2 An URI to compare
* @param int $normalizations A bitmask of normalizations to apply, see constants
*
* @link https://tools.ietf.org/html/rfc3986#section-6.1
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
*/
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
{
@ -185,7 +185,7 @@ final class UriNormalizer
{
$regex = '/(?:%[A-Fa-f0-9]{2})++/';
$callback = function (array $match) {
$callback = function (array $match): string {
return strtoupper($match[0]);
};
@ -201,7 +201,7 @@ final class UriNormalizer
{
$regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
$callback = function (array $match) {
$callback = function (array $match): string {
return rawurldecode($match[0]);
};

View File

@ -11,14 +11,14 @@ use Psr\Http\Message\UriInterface;
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-5
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5
*/
final class UriResolver
{
/**
* Removes dot segments from a path and returns the new path.
*
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
*/
public static function removeDotSegments(string $path): string
{
@ -40,7 +40,7 @@ final class UriResolver
if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
// Re-add the leading slash if necessary for cases like "/.."
$newPath = '/' . $newPath;
$newPath = '/'.$newPath;
} elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
// Add the trailing slash if necessary
// If newPath is not empty, then $segment must be set and is the last segment from the foreach
@ -53,7 +53,7 @@ final class UriResolver
/**
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @link http://tools.ietf.org/html/rfc3986#section-5.2
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
{
@ -80,13 +80,13 @@ final class UriResolver
$targetPath = $rel->getPath();
} else {
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
$targetPath = '/'.$rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
}
}
}
@ -127,8 +127,8 @@ final class UriResolver
*/
public static function relativize(UriInterface $base, UriInterface $target): UriInterface
{
if ($target->getScheme() !== '' &&
($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
if ($target->getScheme() !== ''
&& ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
) {
return $target;
}
@ -185,7 +185,7 @@ final class UriResolver
}
}
$targetSegments[] = $targetLastSegment;
$relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
$relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments);
// 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

View File

@ -14,18 +14,18 @@ final class Utils
/**
* Remove the items given by the keys, case insensitively from the data.
*
* @param string[] $keys
* @param (string|int)[] $keys
*/
public static function caselessRemove(array $keys, array $data): array
{
$result = [];
foreach ($keys as &$key) {
$key = strtolower($key);
$key = strtolower((string) $key);
}
foreach ($data as $k => $v) {
if (!is_string($k) || !in_array(strtolower($k), $keys)) {
if (!in_array(strtolower((string) $k), $keys)) {
$result[$k] = $v;
}
}
@ -90,6 +90,7 @@ final class Utils
}
$buffer .= $buf;
}
return $buffer;
}
@ -174,7 +175,7 @@ final class Utils
$standardPorts = ['http' => 80, 'https' => 443];
$scheme = $changes['uri']->getScheme();
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
$changes['set_headers']['Host'] .= ':' . $port;
$changes['set_headers']['Host'] .= ':'.$port;
}
}
}
@ -249,6 +250,20 @@ final class Utils
return $buffer;
}
/**
* Redact the password in the user info part of a URI.
*/
public static function redactUserInfo(UriInterface $uri): UriInterface
{
$userInfo = $uri->getUserInfo();
if (false !== ($pos = \strpos($userInfo, ':'))) {
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
}
return $uri;
}
/**
* Create a new stream based on the input type.
*
@ -291,6 +306,7 @@ final class Utils
fwrite($stream, (string) $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
}
@ -308,6 +324,7 @@ final class Utils
fseek($stream, 0);
$resource = $stream;
}
return new Stream($resource, $options);
case 'object':
/** @var object $resource */
@ -320,6 +337,7 @@ final class Utils
}
$result = $resource->current();
$resource->next();
return $result;
}, $options);
} elseif (method_exists($resource, '__toString')) {
@ -334,7 +352,7 @@ final class Utils
return new PumpStream($resource, $options);
}
throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
}
/**