modified file plugins

This commit is contained in:
2024-10-09 12:44:43 +00:00
committed by Gitium
parent cd379e1d95
commit e13bab0b76
1784 changed files with 325846 additions and 0 deletions

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests');
$config = new PhpCsFixer\Config();
return $config->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'native_function_invocation' => ['include'=> ['@all']],
'native_constant_invocation' => true,
'ordered_imports' => true,
'declare_strict_types' => false,
'linebreak_after_opening_tag' => false,
'single_import_per_statement' => false,
'blank_line_after_opening_tag' => false,
'concat_space' => ['spacing'=>'one'],
'phpdoc_align' => ['align'=>'left'],
])
->setRiskyAllowed(true)
->setFinder($finder);

View File

@ -0,0 +1,164 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.8.0
- Deprecate HttplugFactory, use Psr17Factory instead
- Make depencendy on php-http/message-factory optional
## 1.7.0
- Bump to PHP 7.2 minimum
- Allow psr/http-message v2
- Use copy-on-write for streams created from strings
## 1.6.1
- Security fix: CVE-2023-29197
## 1.6.0
### Changed
- Seek to the begining of the string when using Stream::create()
- Populate ServerRequest::getQueryParams() on instantiation
- Encode [reserved characters](https://www.rfc-editor.org/rfc/rfc3986#appendix-A) in userinfo in Uri
- Normalize leading slashes for Uri::getPath()
- Make Stream's constructor public
- Add some missing type checks on arguments
## 1.5.1
### Fixed
- Fixed deprecations on PHP 8.1
## 1.5.0
### Added
- Add explicit `@return mixed`
- Add explicit return types to HttplugFactory
### Fixed
- Improve error handling with streams
## 1.4.1
### Fixed
- `Psr17Factory::createStreamFromFile`, `UploadedFile::moveTo`, and
`UploadedFile::getStream` no longer throw `ValueError` in PHP 8.
## 1.4.0
### Removed
The `final` keyword was replaced by `@final` annotation.
## 1.3.2
### Fixed
- `Stream::read()` must not return boolean.
- Improved exception message when using wrong HTTP status code.
## 1.3.1
### Fixed
- Allow installation on PHP8
## 1.3.0
### Added
- Make Stream::__toString() compatible with throwing exceptions on PHP 7.4.
### Fixed
- Support for UTF-8 hostnames
- Support for numeric header names
## 1.2.1
### Changed
- Added `.github` and `phpstan.neon.dist` to `.gitattributes`.
## 1.2.0
### Changed
- Change minimal port number to 0 (unix socket)
- Updated `Psr17Factory::createResponse` to respect the specification. If second
argument is not used, a standard reason phrase. If an empty string is passed,
then the reason phrase will be empty.
### Fixed
- Check for seekable on the stream resource.
- Fixed the `Response::$reason` should never be null.
## 1.1.0
### Added
- Improved performance
- More tests for `UploadedFile` and `HttplugFactory`
### Removed
- Dead code
## 1.0.1
### Fixed
- Handle `fopen` failing in createStreamFromFile according to PSR-7.
- Reduce execution path to speed up performance.
- Fixed typos.
- Code style.
## 1.0.0
### Added
- Support for final PSR-17 (HTTP factories). (`Psr17Factory`)
- Support for numeric header values.
- Support for empty header values.
- All classes are final
- `HttplugFactory` that implements factory interfaces from HTTPlug.
### Changed
- `ServerRequest` does not extend `Request`.
### Removed
- The HTTPlug discovery strategy was removed since it is included in php-http/discovery 1.4.
- `UploadedFileFactory()` was removed in favor for `Psr17Factory`.
- `ServerRequestFactory()` was removed in favor for `Psr17Factory`.
- `StreamFactory`, `UriFactory`, abd `MessageFactory`. Use `HttplugFactory` instead.
- `ServerRequestFactory::createServerRequestFromArray`, `ServerRequestFactory::createServerRequestFromArrays` and
`ServerRequestFactory::createServerRequestFromGlobals`. Please use the new `nyholm/psr7-server` instead.
## 0.3.0
### Added
- Return types.
- Many `InvalidArgumentException`s are thrown when you use invalid arguments.
- Integration tests for `UploadedFile` and `ServerRequest`.
### Changed
- We dropped PHP7.0 support.
- PSR-17 factories have been marked as internal. They do not fall under our BC promise until PSR-17 is accepted.
- `UploadedFileFactory::createUploadedFile` does not accept a string file path.
## 0.2.3
No changelog before this release

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Tobias Nyholm
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,108 @@
# PSR-7 implementation
[![Latest Version](https://img.shields.io/github/release/Nyholm/psr7.svg?style=flat-square)](https://github.com/Nyholm/psr7/releases)
[![Total Downloads](https://poser.pugx.org/nyholm/psr7/downloads)](https://packagist.org/packages/nyholm/psr7)
[![Monthly Downloads](https://poser.pugx.org/nyholm/psr7/d/monthly.png)](https://packagist.org/packages/nyholm/psr7)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Static analysis](https://github.com/Nyholm/psr7/actions/workflows/static.yml/badge.svg?branch=master)](https://github.com/Nyholm/psr7/actions/workflows/static.yml?query=branch%3Amaster)
[![Tests](https://github.com/Nyholm/psr7/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/Nyholm/psr7/actions/workflows/tests.yml?query=branch%3Amaster)
A super lightweight PSR-7 implementation. Very strict and very fast.
| Description | Guzzle | Laminas | Slim | Nyholm |
| ---- | ------ | ---- | ---- | ------ |
| Lines of code | 3.300 | 3.100 | 1.900 | 1.000 |
| PSR-7* | 66% | 100% | 75% | 100% |
| PSR-17 | No | Yes | Yes | Yes |
| HTTPlug | No | No | No | Yes |
| Performance (runs per second)** | 14.553 | 14.703 | 13.416 | 17.734 |
\* Percent of completed tests in https://github.com/php-http/psr7-integration-tests
\** Benchmark with 50.000 runs. See https://github.com/devanych/psr-http-benchmark (higher is better)
## Installation
```bash
composer require nyholm/psr7
```
If you are using Symfony Flex then you get all message factories registered as services.
## Usage
The PSR-7 objects do not contain any other public methods than those defined in
the [PSR-7 specification](https://www.php-fig.org/psr/psr-7/).
### Create objects
Use the PSR-17 factory to create requests, streams, URIs etc.
```php
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se');
$stream = $psr17Factory->createStream('foobar');
```
### Sending a request
With [HTTPlug](http://httplug.io/) or any other PSR-18 (HTTP client) you may send
requests like:
```bash
composer require kriswallsmith/buzz
```
```php
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$psr18Client = new \Buzz\Client\Curl($psr17Factory);
$request = $psr17Factory->createRequest('GET', 'http://tnyholm.se');
$response = $psr18Client->sendRequest($request);
```
### Create server requests
The [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) package can be used
to create server requests from PHP superglobals.
```bash
composer require nyholm/psr7-server
```
```php
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$creator = new \Nyholm\Psr7Server\ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory // StreamFactory
);
$serverRequest = $creator->fromGlobals();
```
### Emitting a response
```bash
composer require laminas/laminas-httphandlerrunner
```
```php
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$responseBody = $psr17Factory->createStream('Hello world');
$response = $psr17Factory->createResponse(200)->withBody($responseBody);
(new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response);
```
## Our goal
This package is currently maintained by [Tobias Nyholm](http://nyholm.se) and
[Martijn van der Ven](https://vanderven.se/martijn/). They have decided that the
goal of this library should be to provide a super strict implementation of
[PSR-7](https://www.php-fig.org/psr/psr-7/) that is blazing fast.
The package will never include any extra features nor helper methods. All our classes
and functions exist because they are required to fulfill the PSR-7 specification.

View File

@ -0,0 +1,49 @@
{
"name": "nyholm/psr7",
"description": "A fast PHP7 implementation of PSR-7",
"license": "MIT",
"keywords": ["psr-7", "psr-17"],
"homepage": "https://tnyholm.se",
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
},
{
"name": "Martijn van der Ven",
"email": "martijn@vanderven.se"
}
],
"require": {
"php": ">=7.2",
"psr/http-message": "^1.1 || ^2.0",
"psr/http-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"http-interop/http-factory-tests": "^0.9",
"symfony/error-handler": "^4.4"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0",
"psr/http-factory-implementation": "1.0"
},
"autoload": {
"psr-4": {
"Nyholm\\Psr7\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\Nyholm\\Psr7\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
}
}

View File

@ -0,0 +1,36 @@
parameters:
ignoreErrors:
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/Response.php
-
message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
count: 1
path: src/Response.php
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/ServerRequest.php
-
message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#"
count: 1
path: src/ServerRequest.php
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/Stream.php
-
message: "#^Result of && is always false\\.$#"
count: 2
path: src/UploadedFile.php
-
message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#"
count: 2
path: src/UploadedFile.php

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.30.0@d0bc6e25d89f649e4f36a534f330f8bb4643dd69">
<file src="src/Stream.php">
<NoValue occurrences="1">
<code>return \trigger_error((string) $e, \E_USER_ERROR);</code>
</NoValue>
</file>
</files>

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7\Factory;
use Http\Message\{MessageFactory, StreamFactory, UriFactory};
use Nyholm\Psr7\{Request, Response, Stream, Uri};
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
if (!\interface_exists(MessageFactory::class)) {
throw new \LogicException('You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead');
}
@\trigger_error('Class "Nyholm\Psr7\Factory\HttplugFactory" is deprecated since version 1.8, use "Nyholm\Psr7\Factory\Psr17Factory" instead.', \E_USER_DEPRECATED);
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*
* @deprecated since version 1.8, use Psr17Factory instead
*/
class HttplugFactory implements MessageFactory, StreamFactory, UriFactory
{
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
{
return new Request($method, $uri, $headers, $body, $protocolVersion);
}
public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1'): ResponseInterface
{
return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase);
}
public function createStream($body = null): StreamInterface
{
return Stream::create($body ?? '');
}
public function createUri($uri = ''): UriInterface
{
if ($uri instanceof UriInterface) {
return $uri;
}
return new Uri($uri);
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7\Factory;
use Nyholm\Psr7\{Request, Response, ServerRequest, Stream, UploadedFile, Uri};
use Psr\Http\Message\{RequestFactoryInterface, RequestInterface, ResponseFactoryInterface, ResponseInterface, ServerRequestFactoryInterface, ServerRequestInterface, StreamFactoryInterface, StreamInterface, UploadedFileFactoryInterface, UploadedFileInterface, UriFactoryInterface, UriInterface};
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
{
public function createRequest(string $method, $uri): RequestInterface
{
return new Request($method, $uri);
}
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
{
if (2 > \func_num_args()) {
// This will make the Response class to use a custom reasonPhrase
$reasonPhrase = null;
}
return new Response($code, [], null, '1.1', $reasonPhrase);
}
public function createStream(string $content = ''): StreamInterface
{
return Stream::create($content);
}
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
if ('' === $filename) {
throw new \RuntimeException('Path cannot be empty');
}
if (false === $resource = @\fopen($filename, $mode)) {
if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode));
}
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? ''));
}
return Stream::create($resource);
}
public function createStreamFromResource($resource): StreamInterface
{
return Stream::create($resource);
}
public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface
{
if (null === $size) {
$size = $stream->getSize();
}
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
}
public function createUri(string $uri = ''): UriInterface
{
return new Uri($uri);
}
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
{
return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
}
}

View File

@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
/**
* Trait implementing functionality common to requests and responses.
*
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
*/
trait MessageTrait
{
/** @var array Map of all registered headers, as original name => array of values */
private $headers = [];
/** @var array Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string */
private $protocol = '1.1';
/** @var StreamInterface|null */
private $stream;
public function getProtocolVersion(): string
{
return $this->protocol;
}
/**
* @return static
*/
public function withProtocolVersion($version): MessageInterface
{
if (!\is_scalar($version)) {
throw new \InvalidArgumentException('Protocol version must be a string');
}
if ($this->protocol === $version) {
return $this;
}
$new = clone $this;
$new->protocol = (string) $version;
return $new;
}
public function getHeaders(): array
{
return $this->headers;
}
public function hasHeader($header): bool
{
return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]);
}
public function getHeader($header): array
{
if (!\is_string($header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (!isset($this->headerNames[$header])) {
return [];
}
$header = $this->headerNames[$header];
return $this->headers[$header];
}
public function getHeaderLine($header): string
{
return \implode(', ', $this->getHeader($header));
}
/**
* @return static
*/
public function withHeader($header, $value): MessageInterface
{
$value = $this->validateAndTrimHeader($header, $value);
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
$new = clone $this;
if (isset($new->headerNames[$normalized])) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
return $new;
}
/**
* @return static
*/
public function withAddedHeader($header, $value): MessageInterface
{
if (!\is_string($header) || '' === $header) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$new = clone $this;
$new->setHeaders([$header => $value]);
return $new;
}
/**
* @return static
*/
public function withoutHeader($header): MessageInterface
{
if (!\is_string($header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (!isset($this->headerNames[$normalized])) {
return $this;
}
$header = $this->headerNames[$normalized];
$new = clone $this;
unset($new->headers[$header], $new->headerNames[$normalized]);
return $new;
}
public function getBody(): StreamInterface
{
if (null === $this->stream) {
$this->stream = Stream::create('');
}
return $this->stream;
}
/**
* @return static
*/
public function withBody(StreamInterface $body): MessageInterface
{
if ($body === $this->stream) {
return $this;
}
$new = clone $this;
$new->stream = $body;
return $new;
}
private function setHeaders(array $headers): void
{
foreach ($headers as $header => $value) {
if (\is_int($header)) {
// If a header name was set to a numeric string, PHP will cast the key to an int.
// We must cast it back to a string in order to comply with validation.
$header = (string) $header;
}
$value = $this->validateAndTrimHeader($header, $value);
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (isset($this->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$this->headers[$header] = \array_merge($this->headers[$header], $value);
} else {
$this->headerNames[$normalized] = $header;
$this->headers[$header] = $value;
}
}
}
/**
* Make sure the header complies with RFC 7230.
*
* Header names must be a non-empty string consisting of token characters.
*
* Header values must be strings consisting of visible characters with all optional
* leading and trailing whitespace stripped. This method will always strip such
* optional whitespace. Note that the method does not allow folding whitespace within
* the values as this was deprecated for almost all instances by the RFC.
*
* header-field = field-name ":" OWS field-value OWS
* field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
* / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
* OWS = *( SP / HTAB )
* field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
*
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
*/
private function validateAndTrimHeader($header, $values): array
{
if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
if (!\is_array($values)) {
// This is simple, just one value.
if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) {
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
}
return [\trim((string) $values, " \t")];
}
if (empty($values)) {
throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given');
}
// Assert Non empty array
$returnValues = [];
foreach ($values as $v) {
if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", (string) $v)) {
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
}
$returnValues[] = \trim((string) $v, " \t");
}
return $returnValues;
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\{RequestInterface, StreamInterface, UriInterface};
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Request implements RequestInterface
{
use MessageTrait;
use RequestTrait;
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
*/
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
{
if (!($uri instanceof UriInterface)) {
$uri = new Uri($uri);
}
$this->method = $method;
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $version;
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
// If we got no body, defer initialization of the stream until Request::getBody()
if ('' !== $body && null !== $body) {
$this->stream = Stream::create($body);
}
}
}

View File

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
*/
trait RequestTrait
{
/** @var string */
private $method;
/** @var string|null */
private $requestTarget;
/** @var UriInterface|null */
private $uri;
public function getRequestTarget(): string
{
if (null !== $this->requestTarget) {
return $this->requestTarget;
}
if ('' === $target = $this->uri->getPath()) {
$target = '/';
}
if ('' !== $this->uri->getQuery()) {
$target .= '?' . $this->uri->getQuery();
}
return $target;
}
/**
* @return static
*/
public function withRequestTarget($requestTarget): RequestInterface
{
if (!\is_string($requestTarget)) {
throw new \InvalidArgumentException('Request target must be a string');
}
if (\preg_match('#\s#', $requestTarget)) {
throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
}
$new = clone $this;
$new->requestTarget = $requestTarget;
return $new;
}
public function getMethod(): string
{
return $this->method;
}
/**
* @return static
*/
public function withMethod($method): RequestInterface
{
if (!\is_string($method)) {
throw new \InvalidArgumentException('Method must be a string');
}
$new = clone $this;
$new->method = $method;
return $new;
}
public function getUri(): UriInterface
{
return $this->uri;
}
/**
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
{
if ($uri === $this->uri) {
return $this;
}
$new = clone $this;
$new->uri = $uri;
if (!$preserveHost || !$this->hasHeader('Host')) {
$new->updateHostFromUri();
}
return $new;
}
private function updateHostFromUri(): void
{
if ('' === $host = $this->uri->getHost()) {
return;
}
if (null !== ($port = $this->uri->getPort())) {
$host .= ':' . $port;
}
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
$this->headerNames['host'] = $header = 'Host';
}
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\{ResponseInterface, StreamInterface};
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Response implements ResponseInterface
{
use MessageTrait;
/** @var array Map of standard HTTP status code/reason phrases */
private const PHRASES = [
100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported',
300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required',
];
/** @var string */
private $reasonPhrase = '';
/** @var int */
private $statusCode;
/**
* @param int $status Status code
* @param array $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)
*/
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null)
{
// If we got no body, defer initialization of the stream until Response::getBody()
if ('' !== $body && null !== $body) {
$this->stream = Stream::create($body);
}
$this->statusCode = $status;
$this->setHeaders($headers);
if (null === $reason && isset(self::PHRASES[$this->statusCode])) {
$this->reasonPhrase = self::PHRASES[$status];
} else {
$this->reasonPhrase = $reason ?? '';
}
$this->protocol = $version;
}
public function getStatusCode(): int
{
return $this->statusCode;
}
public function getReasonPhrase(): string
{
return $this->reasonPhrase;
}
/**
* @return static
*/
public function withStatus($code, $reasonPhrase = ''): ResponseInterface
{
if (!\is_int($code) && !\is_string($code)) {
throw new \InvalidArgumentException('Status code has to be an integer');
}
$code = (int) $code;
if ($code < 100 || $code > 599) {
throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code));
}
$new = clone $this;
$new->statusCode = $code;
if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) {
$reasonPhrase = self::PHRASES[$new->statusCode];
}
$new->reasonPhrase = $reasonPhrase;
return $new;
}
}

View File

@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\{ServerRequestInterface, StreamInterface, UploadedFileInterface, UriInterface};
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class ServerRequest implements ServerRequestInterface
{
use MessageTrait;
use RequestTrait;
/** @var array */
private $attributes = [];
/** @var array */
private $cookieParams = [];
/** @var array|object|null */
private $parsedBody;
/** @var array */
private $queryParams = [];
/** @var array */
private $serverParams;
/** @var UploadedFileInterface[] */
private $uploadedFiles = [];
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
*/
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
{
$this->serverParams = $serverParams;
if (!($uri instanceof UriInterface)) {
$uri = new Uri($uri);
}
$this->method = $method;
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $version;
\parse_str($uri->getQuery(), $this->queryParams);
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
// If we got no body, defer initialization of the stream until ServerRequest::getBody()
if ('' !== $body && null !== $body) {
$this->stream = Stream::create($body);
}
}
public function getServerParams(): array
{
return $this->serverParams;
}
public function getUploadedFiles(): array
{
return $this->uploadedFiles;
}
/**
* @return static
*/
public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
{
$new = clone $this;
$new->uploadedFiles = $uploadedFiles;
return $new;
}
public function getCookieParams(): array
{
return $this->cookieParams;
}
/**
* @return static
*/
public function withCookieParams(array $cookies): ServerRequestInterface
{
$new = clone $this;
$new->cookieParams = $cookies;
return $new;
}
public function getQueryParams(): array
{
return $this->queryParams;
}
/**
* @return static
*/
public function withQueryParams(array $query): ServerRequestInterface
{
$new = clone $this;
$new->queryParams = $query;
return $new;
}
/**
* @return array|object|null
*/
public function getParsedBody()
{
return $this->parsedBody;
}
/**
* @return static
*/
public function withParsedBody($data): ServerRequestInterface
{
if (!\is_array($data) && !\is_object($data) && null !== $data) {
throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
}
$new = clone $this;
$new->parsedBody = $data;
return $new;
}
public function getAttributes(): array
{
return $this->attributes;
}
/**
* @return mixed
*/
public function getAttribute($attribute, $default = null)
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
if (false === \array_key_exists($attribute, $this->attributes)) {
return $default;
}
return $this->attributes[$attribute];
}
/**
* @return static
*/
public function withAttribute($attribute, $value): ServerRequestInterface
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
$new = clone $this;
$new->attributes[$attribute] = $value;
return $new;
}
/**
* @return static
*/
public function withoutAttribute($attribute): ServerRequestInterface
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
if (false === \array_key_exists($attribute, $this->attributes)) {
return $this;
}
$new = clone $this;
unset($new->attributes[$attribute]);
return $new;
}
}

View File

@ -0,0 +1,391 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Stream implements StreamInterface
{
use StreamTrait;
/** @var resource|null A resource reference */
private $stream;
/** @var bool */
private $seekable;
/** @var bool */
private $readable;
/** @var bool */
private $writable;
/** @var array|mixed|void|bool|null */
private $uri;
/** @var int|null */
private $size;
/** @var array Hash of readable and writable stream types */
private const READ_WRITE_HASH = [
'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,
],
];
/**
* @param resource $body
*/
public function __construct($body)
{
if (!\is_resource($body)) {
throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource');
}
$this->stream = $body;
$meta = \stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR);
$this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
$this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
}
/**
* Creates a new PSR-7 stream.
*
* @param string|resource|StreamInterface $body
*
* @throws \InvalidArgumentException
*/
public static function create($body = ''): StreamInterface
{
if ($body instanceof StreamInterface) {
return $body;
}
if (\is_string($body)) {
if (200000 <= \strlen($body)) {
$body = self::openZvalStream($body);
} else {
$resource = \fopen('php://memory', 'r+');
\fwrite($resource, $body);
\fseek($resource, 0);
$body = $resource;
}
}
if (!\is_resource($body)) {
throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface');
}
return new self($body);
}
/**
* Closes the stream when the destructed.
*/
public function __destruct()
{
$this->close();
}
public function close(): void
{
if (isset($this->stream)) {
if (\is_resource($this->stream)) {
\fclose($this->stream);
}
$this->detach();
}
}
public function detach()
{
if (!isset($this->stream)) {
return null;
}
$result = $this->stream;
unset($this->stream);
$this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
return $result;
}
private function getUri()
{
if (false !== $this->uri) {
$this->uri = $this->getMetadata('uri') ?? false;
}
return $this->uri;
}
public function getSize(): ?int
{
if (null !== $this->size) {
return $this->size;
}
if (!isset($this->stream)) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($uri = $this->getUri()) {
\clearstatcache(true, $uri);
}
$stats = \fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
public function tell(): int
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (false === $result = @\ftell($this->stream)) {
throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function eof(): bool
{
return !isset($this->stream) || \feof($this->stream);
}
public function isSeekable(): bool
{
return $this->seekable;
}
public function seek($offset, $whence = \SEEK_SET): void
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->seekable) {
throw new \RuntimeException('Stream is not seekable');
}
if (-1 === \fseek($this->stream, $offset, $whence)) {
throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, true));
}
}
public function rewind(): void
{
$this->seek(0);
}
public function isWritable(): bool
{
return $this->writable;
}
public function write($string): int
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->writable) {
throw new \RuntimeException('Cannot write to a non-writable stream');
}
// We can't know the size after writing anything
$this->size = null;
if (false === $result = @\fwrite($this->stream, $string)) {
throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function isReadable(): bool
{
return $this->readable;
}
public function read($length): string
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->readable) {
throw new \RuntimeException('Cannot read from non-readable stream');
}
if (false === $result = @\fread($this->stream, $length)) {
throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function getContents(): string
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (false === $contents = @\stream_get_contents($this->stream)) {
throw new \RuntimeException('Unable to read stream contents: ' . (\error_get_last()['message'] ?? ''));
}
return $contents;
}
/**
* @return mixed
*/
public function getMetadata($key = null)
{
if (null !== $key && !\is_string($key)) {
throw new \InvalidArgumentException('Metadata key must be a string');
}
if (!isset($this->stream)) {
return $key ? null : [];
}
$meta = \stream_get_meta_data($this->stream);
if (null === $key) {
return $meta;
}
return $meta[$key] ?? null;
}
private static function openZvalStream(string $body)
{
static $wrapper;
$wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class() {
public $context;
private $data;
private $position = 0;
public function stream_open(): bool
{
$this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data'];
\stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null);
return true;
}
public function stream_read(int $count): string
{
$result = \substr($this->data, $this->position, $count);
$this->position += \strlen($result);
return $result;
}
public function stream_write(string $data): int
{
$this->data = \substr_replace($this->data, $data, $this->position, \strlen($data));
$this->position += \strlen($data);
return \strlen($data);
}
public function stream_tell(): int
{
return $this->position;
}
public function stream_eof(): bool
{
return \strlen($this->data) <= $this->position;
}
public function stream_stat(): array
{
return [
'mode' => 33206, // POSIX_S_IFREG | 0666
'nlink' => 1,
'rdev' => -1,
'size' => \strlen($this->data),
'blksize' => -1,
'blocks' => -1,
];
}
public function stream_seek(int $offset, int $whence): bool
{
if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) {
$this->position = $offset;
} elseif (\SEEK_CUR === $whence && 0 <= $offset) {
$this->position += $offset;
} elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) {
$this->position = $offset;
} else {
return false;
}
return true;
}
public function stream_set_option(): bool
{
return true;
}
public function stream_truncate(int $new_size): bool
{
if ($new_size) {
$this->data = \substr($this->data, 0, $new_size);
$this->position = \min($this->position, $new_size);
} else {
$this->data = '';
$this->position = 0;
}
return true;
}
}));
$context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]);
if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', false, $context)) {
\stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper);
$stream = \fopen('Nyholm-Psr7-Zval://', 'r+', false, $context);
}
return $stream;
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
use Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler;
use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler;
if (\PHP_VERSION_ID >= 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) {
/**
* @internal
*/
trait StreamTrait
{
public function __toString(): string
{
if ($this->isSeekable()) {
$this->seek(0);
}
return $this->getContents();
}
}
} else {
/**
* @internal
*/
trait StreamTrait
{
/**
* @return string
*/
public function __toString()
{
try {
if ($this->isSeekable()) {
$this->seek(0);
}
return $this->getContents();
} catch (\Throwable $e) {
if (\is_array($errorHandler = \set_error_handler('var_dump'))) {
$errorHandler = $errorHandler[0] ?? null;
}
\restore_error_handler();
if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) {
return \trigger_error((string) $e, \E_USER_ERROR);
}
return '';
}
}
}
}

View File

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class UploadedFile implements UploadedFileInterface
{
/** @var array */
private const ERRORS = [
\UPLOAD_ERR_OK => 1,
\UPLOAD_ERR_INI_SIZE => 1,
\UPLOAD_ERR_FORM_SIZE => 1,
\UPLOAD_ERR_PARTIAL => 1,
\UPLOAD_ERR_NO_FILE => 1,
\UPLOAD_ERR_NO_TMP_DIR => 1,
\UPLOAD_ERR_CANT_WRITE => 1,
\UPLOAD_ERR_EXTENSION => 1,
];
/** @var string */
private $clientFilename;
/** @var string */
private $clientMediaType;
/** @var int */
private $error;
/** @var string|null */
private $file;
/** @var bool */
private $moved = false;
/** @var int */
private $size;
/** @var StreamInterface|null */
private $stream;
/**
* @param StreamInterface|string|resource $streamOrFile
* @param int $size
* @param int $errorStatus
* @param string|null $clientFilename
* @param string|null $clientMediaType
*/
public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
{
if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants');
}
if (false === \is_int($size)) {
throw new \InvalidArgumentException('Upload file size must be an integer');
}
if (null !== $clientFilename && !\is_string($clientFilename)) {
throw new \InvalidArgumentException('Upload file client filename must be a string or null');
}
if (null !== $clientMediaType && !\is_string($clientMediaType)) {
throw new \InvalidArgumentException('Upload file client media type must be a string or null');
}
$this->error = $errorStatus;
$this->size = $size;
$this->clientFilename = $clientFilename;
$this->clientMediaType = $clientMediaType;
if (\UPLOAD_ERR_OK === $this->error) {
// Depending on the value set file or stream variable.
if (\is_string($streamOrFile) && '' !== $streamOrFile) {
$this->file = $streamOrFile;
} elseif (\is_resource($streamOrFile)) {
$this->stream = Stream::create($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
}
}
}
/**
* @throws \RuntimeException if is moved or not ok
*/
private function validateActive(): void
{
if (\UPLOAD_ERR_OK !== $this->error) {
throw new \RuntimeException('Cannot retrieve stream due to upload error');
}
if ($this->moved) {
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
public function getStream(): StreamInterface
{
$this->validateActive();
if ($this->stream instanceof StreamInterface) {
return $this->stream;
}
if (false === $resource = @\fopen($this->file, 'r')) {
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? ''));
}
return Stream::create($resource);
}
public function moveTo($targetPath): void
{
$this->validateActive();
if (!\is_string($targetPath) || '' === $targetPath) {
throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
}
if (null !== $this->file) {
$this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath);
if (false === $this->moved) {
throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? ''));
}
} else {
$stream = $this->getStream();
if ($stream->isSeekable()) {
$stream->rewind();
}
if (false === $resource = @\fopen($targetPath, 'w')) {
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? ''));
}
$dest = Stream::create($resource);
while (!$stream->eof()) {
if (!$dest->write($stream->read(1048576))) {
break;
}
}
$this->moved = true;
}
}
public function getSize(): int
{
return $this->size;
}
public function getError(): int
{
return $this->error;
}
public function getClientFilename(): ?string
{
return $this->clientFilename;
}
public function getClientMediaType(): ?string
{
return $this->clientMediaType;
}
}

View File

@ -0,0 +1,356 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\UriInterface;
/**
* PSR-7 URI implementation.
*
* @author Michael Dowling
* @author Tobias Schultze
* @author Matthew Weier O'Phinney
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Uri implements UriInterface
{
private const SCHEMES = ['http' => 80, 'https' => 443];
private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
private const CHAR_GEN_DELIMS = ':\/\?#\[\]@';
/** @var string Uri scheme. */
private $scheme = '';
/** @var string Uri user info. */
private $userInfo = '';
/** @var string Uri host. */
private $host = '';
/** @var int|null Uri port. */
private $port;
/** @var string Uri path. */
private $path = '';
/** @var string Uri query string. */
private $query = '';
/** @var string Uri fragment. */
private $fragment = '';
public function __construct(string $uri = '')
{
if ('' !== $uri) {
if (false === $parts = \parse_url($uri)) {
throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri));
}
// Apply parse_url parts to a URI.
$this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
$this->userInfo = $parts['user'] ?? '';
$this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
$this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
$this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
}
public function __toString(): string
{
return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
}
public function getScheme(): string
{
return $this->scheme;
}
public function getAuthority(): string
{
if ('' === $this->host) {
return '';
}
$authority = $this->host;
if ('' !== $this->userInfo) {
$authority = $this->userInfo . '@' . $authority;
}
if (null !== $this->port) {
$authority .= ':' . $this->port;
}
return $authority;
}
public function getUserInfo(): string
{
return $this->userInfo;
}
public function getHost(): string
{
return $this->host;
}
public function getPort(): ?int
{
return $this->port;
}
public function getPath(): string
{
$path = $this->path;
if ('' !== $path && '/' !== $path[0]) {
if ('' !== $this->host) {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && '/' === $path[1]) {
// If the path is starting with more than one "/", the
// starting slashes MUST be reduced to one.
$path = '/' . \ltrim($path, '/');
}
return $path;
}
public function getQuery(): string
{
return $this->query;
}
public function getFragment(): string
{
return $this->fragment;
}
/**
* @return static
*/
public function withScheme($scheme): UriInterface
{
if (!\is_string($scheme)) {
throw new \InvalidArgumentException('Scheme must be a string');
}
if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) {
return $this;
}
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->port);
return $new;
}
/**
* @return static
*/
public function withUserInfo($user, $password = null): UriInterface
{
if (!\is_string($user)) {
throw new \InvalidArgumentException('User must be a string');
}
$info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user);
if (null !== $password && '' !== $password) {
if (!\is_string($password)) {
throw new \InvalidArgumentException('Password must be a string');
}
$info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password);
}
if ($this->userInfo === $info) {
return $this;
}
$new = clone $this;
$new->userInfo = $info;
return $new;
}
/**
* @return static
*/
public function withHost($host): UriInterface
{
if (!\is_string($host)) {
throw new \InvalidArgumentException('Host must be a string');
}
if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) {
return $this;
}
$new = clone $this;
$new->host = $host;
return $new;
}
/**
* @return static
*/
public function withPort($port): UriInterface
{
if ($this->port === $port = $this->filterPort($port)) {
return $this;
}
$new = clone $this;
$new->port = $port;
return $new;
}
/**
* @return static
*/
public function withPath($path): UriInterface
{
if ($this->path === $path = $this->filterPath($path)) {
return $this;
}
$new = clone $this;
$new->path = $path;
return $new;
}
/**
* @return static
*/
public function withQuery($query): UriInterface
{
if ($this->query === $query = $this->filterQueryAndFragment($query)) {
return $this;
}
$new = clone $this;
$new->query = $query;
return $new;
}
/**
* @return static
*/
public function withFragment($fragment): UriInterface
{
if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
return $this;
}
$new = clone $this;
$new->fragment = $fragment;
return $new;
}
/**
* Create a URI string from its various parts.
*/
private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string
{
$uri = '';
if ('' !== $scheme) {
$uri .= $scheme . ':';
}
if ('' !== $authority) {
$uri .= '//' . $authority;
}
if ('' !== $path) {
if ('/' !== $path[0]) {
if ('' !== $authority) {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && '/' === $path[1]) {
if ('' === $authority) {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . \ltrim($path, '/');
}
}
$uri .= $path;
}
if ('' !== $query) {
$uri .= '?' . $query;
}
if ('' !== $fragment) {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Is a given port non-standard for the current scheme?
*/
private static function isNonStandardPort(string $scheme, int $port): bool
{
return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
}
private function filterPort($port): ?int
{
if (null === $port) {
return null;
}
$port = (int) $port;
if (0 > $port || 0xFFFF < $port) {
throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
}
private function filterPath($path): string
{
if (!\is_string($path)) {
throw new \InvalidArgumentException('Path must be a string');
}
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
}
private function filterQueryAndFragment($str): string
{
if (!\is_string($str)) {
throw new \InvalidArgumentException('Query and fragment must be a string');
}
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
}
private static function rawurlencodeMatchZero(array $match): string
{
return \rawurlencode($match[0]);
}
}