updated mailgun librarie

This commit is contained in:
2019-05-14 10:47:30 +02:00
parent c97e0f8ba1
commit dc39ddbbea
1588 changed files with 40009 additions and 143222 deletions

View File

@@ -0,0 +1,194 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## Unreleased
## [1.7.2] - 2018-10-30
### Fixed
- FilteredStream uses `@trigger_error` instead of throwing exceptions to not
break careless users. You still need to fix your stream code to respect
`isSeekable`. Seeking does not work as expected, and we will add exceptions
in version 2.
## [1.7.1] - 2018-10-29
### Fixed
- FilteredStream is not actually seekable
## [1.7.0] - 2018-08-15
### Fixed
- Fix CurlCommandFormatter for binary request payloads
- Fix QueryParam authentication to assemble proper URL regardless of PHP `arg_separator.output` directive
- Do not pass `null` parameters to `Clue\StreamFilter\fun`
### Changed
- Dropped tests on HHVM
## [1.6.0] - 2017-07-05
### Added
- CookieUtil::parseDate to create a date from cookie date string
### Fixed
- Fix curl command of CurlFormatter when there is an user-agent header
## [1.5.0] - 2017-02-14
### Added
- Check for empty string in Stream factories
- Cookie::createWithoutValidation Static constructor to create a cookie. Will not perform any attribute validation during instantiation.
- Cookie::isValid Method to check if cookie attributes are valid.
### Fixed
- FilteredStream::getSize returns null because the contents size is unknown.
- Stream factories does not rewinds streams. The previous behavior was not coherent between factories and inputs.
### Deprecated
- FilteredStream::getReadFilter The read filter is internal and should never be used by consuming code.
- FilteredStream::getWriteFilter We did not implement writing to the streams at all. And if we do, the filter is an internal information and should not be used by consuming code.
## [1.4.1] - 2016-12-16
### Fixed
- Cookie::matchPath Cookie with root path (`/`) will not match sub path (e.g. `/cookie`).
## [1.4.0] - 2016-10-20
### Added
- Message, stream and URI factories for [Slim Framework](https://github.com/slimphp/Slim)
- BufferedStream that allow you to decorate a non-seekable stream with a seekable one.
- cUrlFormatter to be able to redo the request with a cURL command
## [1.3.1] - 2016-07-15
### Fixed
- FullHttpMessageFormatter will not read from streams that you cannot rewind (non-seekable)
- FullHttpMessageFormatter will not read from the stream if $maxBodyLength is zero
- FullHttpMessageFormatter rewinds streams after they are read
## [1.3.0] - 2016-07-14
### Added
- FullHttpMessageFormatter to include headers and body in the formatted message
### Fixed
- #41: Response builder broke header value
## [1.2.0] - 2016-03-29
### Added
- The RequestMatcher is built after the Symfony RequestMatcher and separates
scheme, host and path expressions and provides an option to filter on the
method
- New RequestConditional authentication method using request matchers
- Add automatic basic auth info detection based on the URL
### Changed
- Improved ResponseBuilder
### Deprecated
- RegexRequestMatcher, use RequestMatcher instead
- Matching authenitcation method, use RequestConditional instead
## [1.1.0] - 2016-02-25
### Added
- Add a request matcher interface and regex implementation
- Add a callback request matcher implementation
- Add a ResponseBuilder, to create PSR7 Response from a string
### Fixed
- Fix casting string on a FilteredStream not filtering the output
## [1.0.0] - 2016-01-27
## [0.2.0] - 2015-12-29
### Added
- Autoregistration of stream filters using Composer autoload
- Cookie
- [Apigen](http://www.apigen.org/) configuration
## [0.1.2] - 2015-12-26
### Added
- Request and response factory bindings
### Fixed
- Chunk filter namespace in Dechunk stream
## [0.1.1] - 2015-12-25
### Added
- Formatter
## 0.1.0 - 2015-12-24
### Added
- Authentication
- Encoding
- Message decorator
- Message factory (Guzzle, Diactoros)
[Unreleased]: https://github.com/php-http/message/compare/v1.7.1...HEAD
[1.7.1]: https://github.com/php-http/message/compare/1.7.0...v1.7.1
[1.7.0]: https://github.com/php-http/message/compare/1.6.0...1.7.0
[1.6.0]: https://github.com/php-http/message/compare/1.5.0...1.6.0
[1.5.0]: https://github.com/php-http/message/compare/v1.4.1...1.5.0
[1.4.1]: https://github.com/php-http/message/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/php-http/message/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/php-http/message/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/php-http/message/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/php-http/message/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/php-http/message/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/php-http/message/compare/0.2.0...v1.0.0
[0.2.0]: https://github.com/php-http/message/compare/v0.1.2...0.2.0
[0.1.2]: https://github.com/php-http/message/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/php-http/message/compare/v0.1.0...v0.1.1

View File

@@ -0,0 +1,19 @@
Copyright (c) 2015-2016 PHP HTTP Team <team@php-http.org>
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,61 @@
# HTTP Message
[![Latest Version](https://img.shields.io/github/release/php-http/message.svg?style=flat-square)](https://github.com/php-http/message/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/php-http/message.svg?style=flat-square)](https://travis-ci.org/php-http/message)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message.svg?style=flat-square)](https://packagist.org/packages/php-http/message)
**HTTP Message related tools.**
## Install
Via Composer
``` bash
$ composer require php-http/message
```
## Intro
This package contains various PSR-7 tools which might be useful in an HTTP workflow:
- Authentication method implementations
- Various Stream encoding tools
- Message decorators
- Message factory implementations for Guzzle PSR-7 and Diactoros
- Cookie implementation
- Request matchers
## Documentation
Please see the [official documentation](http://docs.php-http.org/en/latest/message.html).
## Testing
``` bash
$ composer test
```
## Contributing
Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).
## Credits
Thanks to [Cuzzle](https://github.com/namshi/cuzzle) for inpiration for the `CurlCommandFormatter`.
## Security
If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).
## License
The MIT License (MIT). Please see [License File](LICENSE) for more information.

View File

@@ -0,0 +1,6 @@
source:
- src/
destination: build/api/
templateTheme: bootstrap

View File

@@ -0,0 +1,60 @@
{
"name": "php-http/message",
"description": "HTTP Message related tools",
"license": "MIT",
"keywords": ["message", "http", "psr-7"],
"homepage": "http://php-http.org",
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"require": {
"php": "^5.4 || ^7.0",
"psr/http-message": "^1.0",
"php-http/message-factory": "^1.0.2",
"clue/stream-filter": "^1.4"
},
"provide": {
"php-http/message-factory-implementation": "1.0"
},
"require-dev": {
"zendframework/zend-diactoros": "^1.0",
"guzzlehttp/psr7": "^1.0",
"ext-zlib": "*",
"phpspec/phpspec": "^2.4",
"henrikbjorn/phpspec-code-coverage" : "^1.0",
"coduo/phpspec-data-provider-extension": "^1.0",
"akeneo/phpspec-skip-example-extension": "^1.0",
"slim/slim": "^3.0"
},
"suggest": {
"zendframework/zend-diactoros": "Used with Diactoros Factories",
"guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
"slim/slim": "Used with Slim Framework PSR-7 implementation",
"ext-zlib": "Used with compressor/decompressor streams"
},
"autoload": {
"psr-4": {
"Http\\Message\\": "src/"
},
"files": [
"src/filters.php"
]
},
"autoload-dev": {
"psr-4": {
"spec\\Http\\Message\\": "spec/"
}
},
"scripts": {
"test": "vendor/bin/phpspec run",
"test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml"
},
"extra": {
"branch-alias": {
"dev-master": "1.6-dev"
}
}
}

View File

@@ -0,0 +1,111 @@
{
"version": "1.0",
"name": "php-http/message",
"bindings": {
"064d003d-78a1-48c4-8f3b-1f92ff25da69": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory",
"type": "Http\\Message\\MessageFactory",
"parameters": {
"depends": "Zend\\Diactoros\\Request"
}
},
"0836751e-6558-4d1b-8993-4a52012947c3": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\SlimMessageFactory",
"type": "Http\\Message\\ResponseFactory"
},
"1d127622-dc61-4bfa-b9da-d221548d72c3": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\SlimMessageFactory",
"type": "Http\\Message\\RequestFactory"
},
"2438c2d0-0658-441f-8855-ddaf0f87d54d": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory",
"type": "Http\\Message\\MessageFactory",
"parameters": {
"depends": "GuzzleHttp\\Psr7\\Request"
}
},
"253aa08c-d705-46e7-b1d2-e28c97eef792": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory",
"type": "Http\\Message\\RequestFactory",
"parameters": {
"depends": "GuzzleHttp\\Psr7\\Request"
}
},
"273a34f9-62f4-4ba1-9801-b1284d49ff89": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\StreamFactory\\GuzzleStreamFactory",
"type": "Http\\Message\\StreamFactory",
"parameters": {
"depends": "GuzzleHttp\\Psr7\\Stream"
}
},
"304b83db-b594-4d83-ae75-1f633adf92f7": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\UriFactory\\GuzzleUriFactory",
"type": "Http\\Message\\UriFactory",
"parameters": {
"depends": "GuzzleHttp\\Psr7\\Uri"
}
},
"3f4bc1cd-aa95-4702-9fa7-65408e471691": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\UriFactory\\DiactorosUriFactory",
"type": "Http\\Message\\UriFactory",
"parameters": {
"depends": "Zend\\Diactoros\\Uri"
}
},
"4672a6ee-ad9e-4109-a5d1-b7d46f26c7a1": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\SlimMessageFactory",
"type": "Http\\Message\\MessageFactory"
},
"6234e947-d3bd-43eb-97d5-7f9e22e6bb1b": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory",
"type": "Http\\Message\\ResponseFactory",
"parameters": {
"depends": "Zend\\Diactoros\\Response"
}
},
"6a9ad6ce-d82c-470f-8e30-60f21d9d95bf": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\UriFactory\\SlimUriFactory",
"type": "Http\\Message\\UriFactory"
},
"72c2afa0-ea56-4d03-adb6-a9f241a8a734": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\StreamFactory\\SlimStreamFactory",
"type": "Http\\Message\\StreamFactory"
},
"95c1be8f-39fe-4abd-8351-92cb14379a75": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\StreamFactory\\DiactorosStreamFactory",
"type": "Http\\Message\\StreamFactory",
"parameters": {
"depends": "Zend\\Diactoros\\Stream"
}
},
"a018af27-7590-4dcf-83a1-497f95604cd6": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory",
"type": "Http\\Message\\ResponseFactory",
"parameters": {
"depends": "GuzzleHttp\\Psr7\\Response"
}
},
"c07955b1-de46-43db-923b-d07fae9382cb": {
"_class": "Puli\\Discovery\\Binding\\ClassBinding",
"class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory",
"type": "Http\\Message\\RequestFactory",
"parameters": {
"depends": "Zend\\Diactoros\\Request"
}
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Http\Message;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
interface Authentication
{
/**
* Authenticates a request.
*
* @param RequestInterface $request
*
* @return RequestInterface
*/
public function authenticate(RequestInterface $request);
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request using Basic Auth based on credentials in the URI.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class AutoBasicAuth implements Authentication
{
/**
* Whether user info should be removed from the URI.
*
* @var bool
*/
private $shouldRemoveUserInfo;
/**
* @param bool|true $shouldRremoveUserInfo
*/
public function __construct($shouldRremoveUserInfo = true)
{
$this->shouldRemoveUserInfo = (bool) $shouldRremoveUserInfo;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
$uri = $request->getUri();
$userInfo = $uri->getUserInfo();
if (!empty($userInfo)) {
if ($this->shouldRemoveUserInfo) {
$request = $request->withUri($uri->withUserInfo(''));
}
$request = $request->withHeader('Authorization', sprintf('Basic %s', base64_encode($userInfo)));
}
return $request;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request using Basic Auth.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class BasicAuth implements Authentication
{
/**
* @var string
*/
private $username;
/**
* @var string
*/
private $password;
/**
* @param string $username
* @param string $password
*/
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
$header = sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->username, $this->password)));
return $request->withHeader('Authorization', $header);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request using a token.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class Bearer implements Authentication
{
/**
* @var string
*/
private $token;
/**
* @param string $token
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
$header = sprintf('Bearer %s', $this->token);
return $request->withHeader('Authorization', $header);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request with a multiple authentication methods.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class Chain implements Authentication
{
/**
* @var Authentication[]
*/
private $authenticationChain = [];
/**
* @param Authentication[] $authenticationChain
*/
public function __construct(array $authenticationChain = [])
{
foreach ($authenticationChain as $authentication) {
if (!$authentication instanceof Authentication) {
throw new \InvalidArgumentException(
'Members of the authentication chain must be of type Http\Message\Authentication'
);
}
}
$this->authenticationChain = $authenticationChain;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
foreach ($this->authenticationChain as $authentication) {
$request = $authentication->authenticate($request);
}
return $request;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Http\Message\RequestMatcher\CallbackRequestMatcher;
use Psr\Http\Message\RequestInterface;
@trigger_error('The '.__NAMESPACE__.'\Matching class is deprecated since version 1.2 and will be removed in 2.0. Use Http\Message\Authentication\RequestConditional instead.', E_USER_DEPRECATED);
/**
* Authenticate a PSR-7 Request if the request is matching.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*
* @deprecated since since version 1.2, and will be removed in 2.0. Use {@link RequestConditional} instead.
*/
final class Matching implements Authentication
{
/**
* @var Authentication
*/
private $authentication;
/**
* @var CallbackRequestMatcher
*/
private $matcher;
/**
* @param Authentication $authentication
* @param callable|null $matcher
*/
public function __construct(Authentication $authentication, callable $matcher = null)
{
if (is_null($matcher)) {
$matcher = function () {
return true;
};
}
$this->authentication = $authentication;
$this->matcher = new CallbackRequestMatcher($matcher);
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
if ($this->matcher->matches($request)) {
return $this->authentication->authenticate($request);
}
return $request;
}
/**
* Creates a matching authentication for an URL.
*
* @param Authentication $authentication
* @param string $url
*
* @return self
*/
public static function createUrlMatcher(Authentication $authentication, $url)
{
$matcher = function (RequestInterface $request) use ($url) {
return preg_match($url, $request->getRequestTarget());
};
return new static($authentication, $matcher);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request by adding parameters to its query.
*
* Note: Although in some cases it can be useful, we do not recommend using query parameters for authentication.
* Credentials in the URL is generally unsafe as they are not encrypted, anyone can see them.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class QueryParam implements Authentication
{
/**
* @var array
*/
private $params = [];
/**
* @param array $params
*/
public function __construct(array $params)
{
$this->params = $params;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
$uri = $request->getUri();
$query = $uri->getQuery();
$params = [];
parse_str($query, $params);
$params = array_merge($params, $this->params);
$query = http_build_query($params, null, '&');
$uri = $uri->withQuery($query);
return $request->withUri($uri);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Http\Message\RequestMatcher;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request if the request is matching the given request matcher.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class RequestConditional implements Authentication
{
/**
* @var RequestMatcher
*/
private $requestMatcher;
/**
* @var Authentication
*/
private $authentication;
/**
* @param RequestMatcher $requestMatcher
* @param Authentication $authentication
*/
public function __construct(RequestMatcher $requestMatcher, Authentication $authentication)
{
$this->requestMatcher = $requestMatcher;
$this->authentication = $authentication;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
if ($this->requestMatcher->matches($request)) {
return $this->authentication->authenticate($request);
}
return $request;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Http\Message\Authentication;
use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;
/**
* Authenticate a PSR-7 Request using WSSE.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class Wsse implements Authentication
{
/**
* @var string
*/
private $username;
/**
* @var string
*/
private $password;
/**
* @param string $username
* @param string $password
*/
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestInterface $request)
{
// TODO: generate better nonce?
$nonce = substr(md5(uniqid(uniqid().'_', true)), 0, 16);
$created = date('c');
$digest = base64_encode(sha1(base64_decode($nonce).$created.$this->password, true));
$wsse = sprintf(
'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
$this->username,
$digest,
$nonce,
$created
);
return $request
->withHeader('Authorization', 'WSSE profile="UsernameToken"')
->withHeader('X-WSSE', $wsse)
;
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace Http\Message\Builder;
use Psr\Http\Message\ResponseInterface;
/**
* Fills response object with values.
*/
class ResponseBuilder
{
/**
* The response to be built.
*
* @var ResponseInterface
*/
protected $response;
/**
* Create builder for the given response.
*
* @param ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
/**
* Return response.
*
* @return ResponseInterface
*/
public function getResponse()
{
return $this->response;
}
/**
* Add headers represented by an array of header lines.
*
* @param string[] $headers Response headers as array of header lines.
*
* @return $this
*
* @throws \UnexpectedValueException For invalid header values.
* @throws \InvalidArgumentException For invalid status code arguments.
*/
public function setHeadersFromArray(array $headers)
{
$status = array_shift($headers);
$this->setStatus($status);
foreach ($headers as $headerLine) {
$headerLine = trim($headerLine);
if ('' === $headerLine) {
continue;
}
$this->addHeader($headerLine);
}
return $this;
}
/**
* Add headers represented by a single string.
*
* @param string $headers Response headers as single string.
*
* @return $this
*
* @throws \InvalidArgumentException if $headers is not a string on object with __toString()
* @throws \UnexpectedValueException For invalid header values.
*/
public function setHeadersFromString($headers)
{
if (!(is_string($headers)
|| (is_object($headers) && method_exists($headers, '__toString')))
) {
throw new \InvalidArgumentException(
sprintf(
'%s expects parameter 1 to be a string, %s given',
__METHOD__,
is_object($headers) ? get_class($headers) : gettype($headers)
)
);
}
$this->setHeadersFromArray(explode("\r\n", $headers));
return $this;
}
/**
* Set response status from a status string.
*
* @param string $statusLine Response status as a string.
*
* @return $this
*
* @throws \InvalidArgumentException For invalid status line.
*/
public function setStatus($statusLine)
{
$parts = explode(' ', $statusLine, 3);
if (count($parts) < 2 || 0 !== strpos(strtolower($parts[0]), 'http/')) {
throw new \InvalidArgumentException(
sprintf('"%s" is not a valid HTTP status line', $statusLine)
);
}
$reasonPhrase = count($parts) > 2 ? $parts[2] : '';
$this->response = $this->response
->withStatus((int) $parts[1], $reasonPhrase)
->withProtocolVersion(substr($parts[0], 5));
return $this;
}
/**
* Add header represented by a string.
*
* @param string $headerLine Response header as a string.
*
* @return $this
*
* @throws \InvalidArgumentException For invalid header names or values.
*/
public function addHeader($headerLine)
{
$parts = explode(':', $headerLine, 2);
if (2 !== count($parts)) {
throw new \InvalidArgumentException(
sprintf('"%s" is not a valid HTTP header line', $headerLine)
);
}
$name = trim($parts[0]);
$value = trim($parts[1]);
if ($this->response->hasHeader($name)) {
$this->response = $this->response->withAddedHeader($name, $value);
} else {
$this->response = $this->response->withHeader($name, $value);
}
return $this;
}
}

View File

@@ -0,0 +1,526 @@
<?php
namespace Http\Message;
/**
* Cookie Value Object.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*
* @see http://tools.ietf.org/search/rfc6265
*/
final class Cookie
{
/**
* @var string
*/
private $name;
/**
* @var string|null
*/
private $value;
/**
* @var int|null
*/
private $maxAge;
/**
* @var string|null
*/
private $domain;
/**
* @var string
*/
private $path;
/**
* @var bool
*/
private $secure;
/**
* @var bool
*/
private $httpOnly;
/**
* Expires attribute is HTTP 1.0 only and should be avoided.
*
* @var \DateTime|null
*/
private $expires;
/**
* @param string $name
* @param string|null $value
* @param int|null $maxAge
* @param string|null $domain
* @param string|null $path
* @param bool $secure
* @param bool $httpOnly
* @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided.
*
* @throws \InvalidArgumentException If name, value or max age is not valid.
*/
public function __construct(
$name,
$value = null,
$maxAge = null,
$domain = null,
$path = null,
$secure = false,
$httpOnly = false,
\DateTime $expires = null
) {
$this->validateName($name);
$this->validateValue($value);
$this->validateMaxAge($maxAge);
$this->name = $name;
$this->value = $value;
$this->maxAge = $maxAge;
$this->expires = $expires;
$this->domain = $this->normalizeDomain($domain);
$this->path = $this->normalizePath($path);
$this->secure = (bool) $secure;
$this->httpOnly = (bool) $httpOnly;
}
/**
* Creates a new cookie without any attribute validation.
*
* @param string $name
* @param string|null $value
* @param int $maxAge
* @param string|null $domain
* @param string|null $path
* @param bool $secure
* @param bool $httpOnly
* @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided.
*/
public static function createWithoutValidation(
$name,
$value = null,
$maxAge = null,
$domain = null,
$path = null,
$secure = false,
$httpOnly = false,
\DateTime $expires = null
) {
$cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires);
$cookie->name = $name;
$cookie->value = $value;
$cookie->maxAge = $maxAge;
return $cookie;
}
/**
* Returns the name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the value.
*
* @return string|null
*/
public function getValue()
{
return $this->value;
}
/**
* Checks if there is a value.
*
* @return bool
*/
public function hasValue()
{
return isset($this->value);
}
/**
* Sets the value.
*
* @param string|null $value
*
* @return Cookie
*/
public function withValue($value)
{
$this->validateValue($value);
$new = clone $this;
$new->value = $value;
return $new;
}
/**
* Returns the max age.
*
* @return int|null
*/
public function getMaxAge()
{
return $this->maxAge;
}
/**
* Checks if there is a max age.
*
* @return bool
*/
public function hasMaxAge()
{
return isset($this->maxAge);
}
/**
* Sets the max age.
*
* @param int|null $maxAge
*
* @return Cookie
*/
public function withMaxAge($maxAge)
{
$this->validateMaxAge($maxAge);
$new = clone $this;
$new->maxAge = $maxAge;
return $new;
}
/**
* Returns the expiration time.
*
* @return \DateTime|null
*/
public function getExpires()
{
return $this->expires;
}
/**
* Checks if there is an expiration time.
*
* @return bool
*/
public function hasExpires()
{
return isset($this->expires);
}
/**
* Sets the expires.
*
* @param \DateTime|null $expires
*
* @return Cookie
*/
public function withExpires(\DateTime $expires = null)
{
$new = clone $this;
$new->expires = $expires;
return $new;
}
/**
* Checks if the cookie is expired.
*
* @return bool
*/
public function isExpired()
{
return isset($this->expires) and $this->expires < new \DateTime();
}
/**
* Returns the domain.
*
* @return string|null
*/
public function getDomain()
{
return $this->domain;
}
/**
* Checks if there is a domain.
*
* @return bool
*/
public function hasDomain()
{
return isset($this->domain);
}
/**
* Sets the domain.
*
* @param string|null $domain
*
* @return Cookie
*/
public function withDomain($domain)
{
$new = clone $this;
$new->domain = $this->normalizeDomain($domain);
return $new;
}
/**
* Checks whether this cookie is meant for this domain.
*
* @see http://tools.ietf.org/html/rfc6265#section-5.1.3
*
* @param string $domain
*
* @return bool
*/
public function matchDomain($domain)
{
// Domain is not set or exact match
if (!$this->hasDomain() || 0 === strcasecmp($domain, $this->domain)) {
return true;
}
// Domain is not an IP address
if (filter_var($domain, FILTER_VALIDATE_IP)) {
return false;
}
return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain);
}
/**
* Returns the path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Sets the path.
*
* @param string|null $path
*
* @return Cookie
*/
public function withPath($path)
{
$new = clone $this;
$new->path = $this->normalizePath($path);
return $new;
}
/**
* Checks whether this cookie is meant for this path.
*
* @see http://tools.ietf.org/html/rfc6265#section-5.1.4
*
* @param string $path
*
* @return bool
*/
public function matchPath($path)
{
return $this->path === $path || (0 === strpos($path, rtrim($this->path, '/').'/'));
}
/**
* Checks whether this cookie may only be sent over HTTPS.
*
* @return bool
*/
public function isSecure()
{
return $this->secure;
}
/**
* Sets whether this cookie should only be sent over HTTPS.
*
* @param bool $secure
*
* @return Cookie
*/
public function withSecure($secure)
{
$new = clone $this;
$new->secure = (bool) $secure;
return $new;
}
/**
* Check whether this cookie may not be accessed through Javascript.
*
* @return bool
*/
public function isHttpOnly()
{
return $this->httpOnly;
}
/**
* Sets whether this cookie may not be accessed through Javascript.
*
* @param bool $httpOnly
*
* @return Cookie
*/
public function withHttpOnly($httpOnly)
{
$new = clone $this;
$new->httpOnly = (bool) $httpOnly;
return $new;
}
/**
* Checks if this cookie represents the same cookie as $cookie.
*
* This does not compare the values, only name, domain and path.
*
* @param Cookie $cookie
*
* @return bool
*/
public function match(self $cookie)
{
return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path;
}
/**
* Validates cookie attributes.
*
* @return bool
*/
public function isValid()
{
try {
$this->validateName($this->name);
$this->validateValue($this->value);
$this->validateMaxAge($this->maxAge);
} catch (\InvalidArgumentException $e) {
return false;
}
return true;
}
/**
* Validates the name attribute.
*
* @see http://tools.ietf.org/search/rfc2616#section-2.2
*
* @param string $name
*
* @throws \InvalidArgumentException If the name is empty or contains invalid characters.
*/
private function validateName($name)
{
if (strlen($name) < 1) {
throw new \InvalidArgumentException('The name cannot be empty');
}
// Name attribute is a token as per spec in RFC 2616
if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
}
/**
* Validates a value.
*
* @see http://tools.ietf.org/html/rfc6265#section-4.1.1
*
* @param string|null $value
*
* @throws \InvalidArgumentException If the value contains invalid characters.
*/
private function validateValue($value)
{
if (isset($value)) {
if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
}
}
}
/**
* Validates a Max-Age attribute.
*
* @param int|null $maxAge
*
* @throws \InvalidArgumentException If the Max-Age is not an empty or integer value.
*/
private function validateMaxAge($maxAge)
{
if (isset($maxAge)) {
if (!is_int($maxAge)) {
throw new \InvalidArgumentException('Max-Age must be integer');
}
}
}
/**
* Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
*
* @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
* @see http://tools.ietf.org/html/rfc6265#section-5.1.3
* @see http://tools.ietf.org/html/rfc6265#section-5.2.3
*
* @param string|null $domain
*
* @return string
*/
private function normalizeDomain($domain)
{
if (isset($domain)) {
$domain = ltrim(strtolower($domain), '.');
}
return $domain;
}
/**
* Processes path as per spec in RFC 6265.
*
* @see http://tools.ietf.org/html/rfc6265#section-5.1.4
* @see http://tools.ietf.org/html/rfc6265#section-5.2.4
*
* @param string|null $path
*
* @return string
*/
private function normalizePath($path)
{
$path = rtrim($path, '/');
if (empty($path) or '/' !== substr($path, 0, 1)) {
$path = '/';
}
return $path;
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace Http\Message;
/**
* Cookie Jar holds a set of Cookies.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class CookieJar implements \Countable, \IteratorAggregate
{
/**
* @var \SplObjectStorage
*/
protected $cookies;
public function __construct()
{
$this->cookies = new \SplObjectStorage();
}
/**
* Checks if there is a cookie.
*
* @param Cookie $cookie
*
* @return bool
*/
public function hasCookie(Cookie $cookie)
{
return $this->cookies->contains($cookie);
}
/**
* Adds a cookie.
*
* @param Cookie $cookie
*/
public function addCookie(Cookie $cookie)
{
if (!$this->hasCookie($cookie)) {
$cookies = $this->getMatchingCookies($cookie);
foreach ($cookies as $matchingCookie) {
if ($cookie->getValue() !== $matchingCookie->getValue() || $cookie->getMaxAge() > $matchingCookie->getMaxAge()) {
$this->removeCookie($matchingCookie);
continue;
}
}
if ($cookie->hasValue()) {
$this->cookies->attach($cookie);
}
}
}
/**
* Removes a cookie.
*
* @param Cookie $cookie
*/
public function removeCookie(Cookie $cookie)
{
$this->cookies->detach($cookie);
}
/**
* Returns the cookies.
*
* @return Cookie[]
*/
public function getCookies()
{
$match = function ($matchCookie) {
return true;
};
return $this->findMatchingCookies($match);
}
/**
* Returns all matching cookies.
*
* @param Cookie $cookie
*
* @return Cookie[]
*/
public function getMatchingCookies(Cookie $cookie)
{
$match = function ($matchCookie) use ($cookie) {
return $matchCookie->match($cookie);
};
return $this->findMatchingCookies($match);
}
/**
* Finds matching cookies based on a callable.
*
* @param callable $match
*
* @return Cookie[]
*/
protected function findMatchingCookies(callable $match)
{
$cookies = [];
foreach ($this->cookies as $cookie) {
if ($match($cookie)) {
$cookies[] = $cookie;
}
}
return $cookies;
}
/**
* Checks if there are cookies.
*
* @return bool
*/
public function hasCookies()
{
return $this->cookies->count() > 0;
}
/**
* Sets the cookies and removes any previous one.
*
* @param Cookie[] $cookies
*/
public function setCookies(array $cookies)
{
$this->clear();
$this->addCookies($cookies);
}
/**
* Adds some cookies.
*
* @param Cookie[] $cookies
*/
public function addCookies(array $cookies)
{
foreach ($cookies as $cookie) {
$this->addCookie($cookie);
}
}
/**
* Removes some cookies.
*
* @param Cookie[] $cookies
*/
public function removeCookies(array $cookies)
{
foreach ($cookies as $cookie) {
$this->removeCookie($cookie);
}
}
/**
* Removes cookies which match the given parameters.
*
* Null means that parameter should not be matched
*
* @param string|null $name
* @param string|null $domain
* @param string|null $path
*/
public function removeMatchingCookies($name = null, $domain = null, $path = null)
{
$match = function ($cookie) use ($name, $domain, $path) {
$match = true;
if (isset($name)) {
$match = $match && ($cookie->getName() === $name);
}
if (isset($domain)) {
$match = $match && $cookie->matchDomain($domain);
}
if (isset($path)) {
$match = $match && $cookie->matchPath($path);
}
return $match;
};
$cookies = $this->findMatchingCookies($match);
$this->removeCookies($cookies);
}
/**
* Removes all cookies.
*/
public function clear()
{
$this->cookies = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function count()
{
return $this->cookies->count();
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return clone $this->cookies;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Http\Message;
use Http\Message\Exception\UnexpectedValueException;
final class CookieUtil
{
/**
* Handles dates as defined by RFC 2616 section 3.3.1, and also some other
* non-standard, but common formats.
*
* @var array
*/
private static $dateFormats = [
'D, d M y H:i:s T',
'D, d M Y H:i:s T',
'D, d-M-y H:i:s T',
'D, d-M-Y H:i:s T',
'D, d-m-y H:i:s T',
'D, d-m-Y H:i:s T',
'D M j G:i:s Y',
'D M d H:i:s Y T',
];
/**
* @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/BrowserKit/Cookie.php
*
* @param string $dateValue
*
* @return \DateTime
*
* @throws UnexpectedValueException if we cannot parse the cookie date string.
*/
public static function parseDate($dateValue)
{
foreach (self::$dateFormats as $dateFormat) {
if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) {
return $date;
}
}
// attempt a fallback for unusual formatting
if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) {
return $date;
}
throw new UnexpectedValueException(sprintf(
'Unparseable cookie date string "%s"',
$dateValue
));
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace Http\Message\Decorator;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
/**
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
trait MessageDecorator
{
/**
* @var MessageInterface
*/
private $message;
/**
* Returns the decorated message.
*
* Since the underlying Message is immutable as well
* exposing it is not an issue, because it's state cannot be altered
*
* @return MessageInterface
*/
public function getMessage()
{
return $this->message;
}
/**
* {@inheritdoc}
*/
public function getProtocolVersion()
{
return $this->message->getProtocolVersion();
}
/**
* {@inheritdoc}
*/
public function withProtocolVersion($version)
{
$new = clone $this;
$new->message = $this->message->withProtocolVersion($version);
return $new;
}
/**
* {@inheritdoc}
*/
public function getHeaders()
{
return $this->message->getHeaders();
}
/**
* {@inheritdoc}
*/
public function hasHeader($header)
{
return $this->message->hasHeader($header);
}
/**
* {@inheritdoc}
*/
public function getHeader($header)
{
return $this->message->getHeader($header);
}
/**
* {@inheritdoc}
*/
public function getHeaderLine($header)
{
return $this->message->getHeaderLine($header);
}
/**
* {@inheritdoc}
*/
public function withHeader($header, $value)
{
$new = clone $this;
$new->message = $this->message->withHeader($header, $value);
return $new;
}
/**
* {@inheritdoc}
*/
public function withAddedHeader($header, $value)
{
$new = clone $this;
$new->message = $this->message->withAddedHeader($header, $value);
return $new;
}
/**
* {@inheritdoc}
*/
public function withoutHeader($header)
{
$new = clone $this;
$new->message = $this->message->withoutHeader($header);
return $new;
}
/**
* {@inheritdoc}
*/
public function getBody()
{
return $this->message->getBody();
}
/**
* {@inheritdoc}
*/
public function withBody(StreamInterface $body)
{
$new = clone $this;
$new->message = $this->message->withBody($body);
return $new;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Http\Message\Decorator;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
trait RequestDecorator
{
use MessageDecorator {
getMessage as getRequest;
}
/**
* Exchanges the underlying request with another.
*
* @param RequestInterface $request
*
* @return self
*/
public function withRequest(RequestInterface $request)
{
$new = clone $this;
$new->message = $request;
return $new;
}
/**
* {@inheritdoc}
*/
public function getRequestTarget()
{
return $this->message->getRequestTarget();
}
/**
* {@inheritdoc}
*/
public function withRequestTarget($requestTarget)
{
$new = clone $this;
$new->message = $this->message->withRequestTarget($requestTarget);
return $new;
}
/**
* {@inheritdoc}
*/
public function getMethod()
{
return $this->message->getMethod();
}
/**
* {@inheritdoc}
*/
public function withMethod($method)
{
$new = clone $this;
$new->message = $this->message->withMethod($method);
return $new;
}
/**
* {@inheritdoc}
*/
public function getUri()
{
return $this->message->getUri();
}
/**
* {@inheritdoc}
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
$new = clone $this;
$new->message = $this->message->withUri($uri, $preserveHost);
return $new;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Http\Message\Decorator;
use Psr\Http\Message\ResponseInterface;
/**
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
trait ResponseDecorator
{
use MessageDecorator {
getMessage as getResponse;
}
/**
* Exchanges the underlying response with another.
*
* @param ResponseInterface $response
*
* @return self
*/
public function withResponse(ResponseInterface $response)
{
$new = clone $this;
$new->message = $response;
return $new;
}
/**
* {@inheritdoc}
*/
public function getStatusCode()
{
return $this->message->getStatusCode();
}
/**
* {@inheritdoc}
*/
public function withStatus($code, $reasonPhrase = '')
{
$new = clone $this;
$new->message = $this->message->withStatus($code, $reasonPhrase);
return $new;
}
/**
* {@inheritdoc}
*/
public function getReasonPhrase()
{
return $this->message->getReasonPhrase();
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace Http\Message\Decorator;
use Psr\Http\Message\StreamInterface;
/**
* Decorates a stream.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
trait StreamDecorator
{
/**
* @var StreamInterface
*/
protected $stream;
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->stream->__toString();
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->stream->close();
}
/**
* {@inheritdoc}
*/
public function detach()
{
return $this->stream->detach();
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return $this->stream->getSize();
}
/**
* {@inheritdoc}
*/
public function tell()
{
return $this->stream->tell();
}
/**
* {@inheritdoc}
*/
public function eof()
{
return $this->stream->eof();
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return $this->stream->isSeekable();
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
$this->stream->seek($offset, $whence);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->stream->rewind();
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
return $this->stream->isWritable();
}
/**
* {@inheritdoc}
*/
public function write($string)
{
return $this->stream->write($string);
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
return $this->stream->isReadable();
}
/**
* {@inheritdoc}
*/
public function read($length)
{
return $this->stream->read($length);
}
/**
* {@inheritdoc}
*/
public function getContents()
{
return $this->stream->getContents();
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
return $this->stream->getMetadata($key);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Http\Message\Encoding;
/**
* Transform a regular stream into a chunked one.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class ChunkStream extends FilteredStream
{
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'chunk';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'dechunk';
}
/**
* {@inheritdoc}
*/
protected function fill()
{
parent::fill();
if ($this->stream->eof()) {
$this->buffer .= "0\r\n\r\n";
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream compress (RFC 1950).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class CompressStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 15, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.deflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Http\Message\Encoding;
/**
* Decorate a stream which is chunked.
*
* Allow to decode a chunked stream
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DechunkStream extends FilteredStream
{
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'dechunk';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'chunk';
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream decompress (RFC 1950).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DecompressStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 15]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15, 'level' => $level]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.inflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.deflate';
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream deflate (RFC 1951).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class DeflateStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
parent::__construct($stream, ['window' => -15, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.deflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Http\Message\Encoding\Filter;
/**
* Userland implementation of the chunk stream filter.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class Chunk extends \php_user_filter
{
/**
* {@inheritdoc}
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n");
stream_bucket_append($out, $lenbucket);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
$lenbucket = stream_bucket_new($this->stream, "\r\n");
stream_bucket_append($out, $lenbucket);
}
return PSFS_PASS_ON;
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Http\Message\Decorator\StreamDecorator;
use Psr\Http\Message\StreamInterface;
/**
* A filtered stream has a filter for filtering output and a filter for filtering input made to a underlying stream.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
abstract class FilteredStream implements StreamInterface
{
const BUFFER_SIZE = 8192;
use StreamDecorator {
rewind as private doRewind;
seek as private doSeek;
}
/**
* @var callable
*/
protected $readFilterCallback;
/**
* @var resource
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $readFilter;
/**
* @var callable
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $writeFilterCallback;
/**
* @var resource
*
* @deprecated since version 1.5, will be removed in 2.0
*/
protected $writeFilter;
/**
* Internal buffer.
*
* @var string
*/
protected $buffer = '';
/**
* @param StreamInterface $stream
* @param mixed|null $readFilterOptions
* @param mixed|null $writeFilterOptions deprecated since 1.5, will be removed in 2.0
*/
public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null)
{
if (null !== $readFilterOptions) {
$this->readFilterCallback = Filter\fun($this->readFilter(), $readFilterOptions);
} else {
$this->readFilterCallback = Filter\fun($this->readFilter());
}
if (null !== $writeFilterOptions) {
$this->writeFilterCallback = Filter\fun($this->writeFilter(), $writeFilterOptions);
@trigger_error('The $writeFilterOptions argument is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
} else {
$this->writeFilterCallback = Filter\fun($this->writeFilter());
}
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
if (strlen($this->buffer) >= $length) {
$read = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);
return $read;
}
if ($this->stream->eof()) {
$buffer = $this->buffer;
$this->buffer = '';
return $buffer;
}
$read = $this->buffer;
$this->buffer = '';
$this->fill();
return $read.$this->read($length - strlen($read));
}
/**
* {@inheritdoc}
*/
public function eof()
{
return $this->stream->eof() && '' === $this->buffer;
}
/**
* Buffer is filled by reading underlying stream.
*
* Callback is reading once more even if the stream is ended.
* This allow to get last data in the PHP buffer otherwise this
* bug is present : https://bugs.php.net/bug.php?id=48725
*/
protected function fill()
{
$readFilterCallback = $this->readFilterCallback;
$this->buffer .= $readFilterCallback($this->stream->read(self::BUFFER_SIZE));
if ($this->stream->eof()) {
$this->buffer .= $readFilterCallback();
}
}
/**
* {@inheritdoc}
*/
public function getContents()
{
$buffer = '';
while (!$this->eof()) {
$buf = $this->read(self::BUFFER_SIZE);
// Using a loose equality here to match on '' and false.
if (null == $buf) {
break;
}
$buffer .= $buf;
}
return $buffer;
}
/**
* Always returns null because we can't tell the size of a stream when we filter.
*/
public function getSize()
{
return null;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->getContents();
}
/**
* Filtered streams are not seekable.
*
* We would need to buffer and process everything to allow seeking.
*/
public function isSeekable()
{
return false;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
@trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED);
$this->doRewind();
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
@trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED);
$this->doSeek($offset, $whence);
}
/**
* Returns the read filter name.
*
* @return string
*
* @deprecated since version 1.5, will be removed in 2.0
*/
public function getReadFilter()
{
@trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
return $this->readFilter();
}
/**
* Returns the write filter name.
*
* @return string
*/
abstract protected function readFilter();
/**
* Returns the write filter name.
*
* @return string
*
* @deprecated since version 1.5, will be removed in 2.0
*/
public function getWriteFilter()
{
@trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
return $this->writeFilter();
}
/**
* Returns the write filter name.
*
* @return string
*/
abstract protected function writeFilter();
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream for decoding from gzip format (RFC 1952).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class GzipDecodeStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 31]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31, 'level' => $level]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.inflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.deflate';
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream for encoding to gzip format (RFC 1952).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class GzipEncodeStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => 31, 'level' => $level]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.deflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.inflate';
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Http\Message\Encoding;
use Clue\StreamFilter as Filter;
use Psr\Http\Message\StreamInterface;
/**
* Stream inflate (RFC 1951).
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class InflateStream extends FilteredStream
{
/**
* @param StreamInterface $stream
* @param int $level
*/
public function __construct(StreamInterface $stream, $level = -1)
{
if (!extension_loaded('zlib')) {
throw new \RuntimeException('The zlib extension must be enabled to use this stream');
}
parent::__construct($stream, ['window' => -15]);
// @deprecated will be removed in 2.0
$this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15, 'level' => $level]);
}
/**
* {@inheritdoc}
*/
protected function readFilter()
{
return 'zlib.inflate';
}
/**
* {@inheritdoc}
*/
protected function writeFilter()
{
return 'zlib.deflate';
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Http\Message;
/**
* An interface implemented by all HTTP message related exceptions.
*/
interface Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Http\Message\Exception;
use Http\Message\Exception;
final class UnexpectedValueException extends \UnexpectedValueException implements Exception
{
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Http\Message;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Formats a request and/or a response as a string.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
interface Formatter
{
/**
* Formats a request.
*
* @param RequestInterface $request
*
* @return string
*/
public function formatRequest(RequestInterface $request);
/**
* Formats a response.
*
* @param ResponseInterface $response
*
* @return string
*/
public function formatResponse(ResponseInterface $response);
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Http\Message\Formatter;
use Http\Message\Formatter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A formatter that prints a cURL command for HTTP requests.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class CurlCommandFormatter implements Formatter
{
/**
* {@inheritdoc}
*/
public function formatRequest(RequestInterface $request)
{
$command = sprintf('curl %s', escapeshellarg((string) $request->getUri()->withFragment('')));
if ('1.0' === $request->getProtocolVersion()) {
$command .= ' --http1.0';
} elseif ('2.0' === $request->getProtocolVersion()) {
$command .= ' --http2';
}
$method = strtoupper($request->getMethod());
if ('HEAD' === $method) {
$command .= ' --head';
} elseif ('GET' !== $method) {
$command .= ' --request '.$method;
}
$command .= $this->getHeadersAsCommandOptions($request);
$body = $request->getBody();
if ($body->getSize() > 0) {
if ($body->isSeekable()) {
$data = $body->__toString();
$body->rewind();
if (preg_match('/[\x00-\x1F\x7F]/', $data)) {
$data = '[binary stream omitted]';
}
} else {
$data = '[non-seekable stream omitted]';
}
$escapedData = @escapeshellarg($data);
if (empty($escapedData)) {
$escapedData = 'We couldn\'t not escape the data properly';
}
$command .= sprintf(' --data %s', $escapedData);
}
return $command;
}
/**
* {@inheritdoc}
*/
public function formatResponse(ResponseInterface $response)
{
return '';
}
/**
* @param RequestInterface $request
*
* @return string
*/
private function getHeadersAsCommandOptions(RequestInterface $request)
{
$command = '';
foreach ($request->getHeaders() as $name => $values) {
if ('host' === strtolower($name) && $values[0] === $request->getUri()->getHost()) {
continue;
}
if ('user-agent' === strtolower($name)) {
$command .= sprintf(' -A %s', escapeshellarg($values[0]));
continue;
}
$command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name)));
}
return $command;
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Http\Message\Formatter;
use Http\Message\Formatter;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A formatter that prints the complete HTTP message.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class FullHttpMessageFormatter implements Formatter
{
/**
* The maximum length of the body.
*
* @var int
*/
private $maxBodyLength;
/**
* @param int $maxBodyLength
*/
public function __construct($maxBodyLength = 1000)
{
$this->maxBodyLength = $maxBodyLength;
}
/**
* {@inheritdoc}
*/
public function formatRequest(RequestInterface $request)
{
$message = sprintf(
"%s %s HTTP/%s\n",
$request->getMethod(),
$request->getRequestTarget(),
$request->getProtocolVersion()
);
foreach ($request->getHeaders() as $name => $values) {
$message .= $name.': '.implode(', ', $values)."\n";
}
return $this->addBody($request, $message);
}
/**
* {@inheritdoc}
*/
public function formatResponse(ResponseInterface $response)
{
$message = sprintf(
"HTTP/%s %s %s\n",
$response->getProtocolVersion(),
$response->getStatusCode(),
$response->getReasonPhrase()
);
foreach ($response->getHeaders() as $name => $values) {
$message .= $name.': '.implode(', ', $values)."\n";
}
return $this->addBody($response, $message);
}
/**
* Add the message body if the stream is seekable.
*
* @param MessageInterface $request
* @param string $message
*
* @return string
*/
private function addBody(MessageInterface $request, $message)
{
$stream = $request->getBody();
if (!$stream->isSeekable() || 0 === $this->maxBodyLength) {
// Do not read the stream
$message .= "\n";
} else {
$message .= "\n".mb_substr($stream->__toString(), 0, $this->maxBodyLength);
$stream->rewind();
}
return $message;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Http\Message\Formatter;
use Http\Message\Formatter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Normalize a request or a response into a string or an array.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
class SimpleFormatter implements Formatter
{
/**
* {@inheritdoc}
*/
public function formatRequest(RequestInterface $request)
{
return sprintf(
'%s %s %s',
$request->getMethod(),
$request->getUri()->__toString(),
$request->getProtocolVersion()
);
}
/**
* {@inheritdoc}
*/
public function formatResponse(ResponseInterface $response)
{
return sprintf(
'%s %s %s',
$response->getStatusCode(),
$response->getReasonPhrase(),
$response->getProtocolVersion()
);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Http\Message\MessageFactory;
use Http\Message\StreamFactory\DiactorosStreamFactory;
use Http\Message\MessageFactory;
use Zend\Diactoros\Request;
use Zend\Diactoros\Response;
/**
* Creates Diactoros messages.
*
* @author GeLo <geloen.eric@gmail.com>
*/
final class DiactorosMessageFactory implements MessageFactory
{
/**
* @var DiactorosStreamFactory
*/
private $streamFactory;
public function __construct()
{
$this->streamFactory = new DiactorosStreamFactory();
}
/**
* {@inheritdoc}
*/
public function createRequest(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return (new Request(
$uri,
$method,
$this->streamFactory->createStream($body),
$headers
))->withProtocolVersion($protocolVersion);
}
/**
* {@inheritdoc}
*/
public function createResponse(
$statusCode = 200,
$reasonPhrase = null,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return (new Response(
$this->streamFactory->createStream($body),
$statusCode,
$headers
))->withProtocolVersion($protocolVersion);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Http\Message\MessageFactory;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Http\Message\MessageFactory;
/**
* Creates Guzzle messages.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class GuzzleMessageFactory implements MessageFactory
{
/**
* {@inheritdoc}
*/
public function createRequest(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return new Request(
$method,
$uri,
$headers,
$body,
$protocolVersion
);
}
/**
* {@inheritdoc}
*/
public function createResponse(
$statusCode = 200,
$reasonPhrase = null,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return new Response(
$statusCode,
$headers,
$body,
$protocolVersion,
$reasonPhrase
);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Http\Message\MessageFactory;
use Http\Message\StreamFactory\SlimStreamFactory;
use Http\Message\UriFactory\SlimUriFactory;
use Http\Message\MessageFactory;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Headers;
/**
* Creates Slim 3 messages.
*
* @author Mika Tuupola <tuupola@appelsiini.net>
*/
final class SlimMessageFactory implements MessageFactory
{
/**
* @var SlimStreamFactory
*/
private $streamFactory;
/**
* @var SlimUriFactory
*/
private $uriFactory;
public function __construct()
{
$this->streamFactory = new SlimStreamFactory();
$this->uriFactory = new SlimUriFactory();
}
/**
* {@inheritdoc}
*/
public function createRequest(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return (new Request(
$method,
$this->uriFactory->createUri($uri),
new Headers($headers),
[],
[],
$this->streamFactory->createStream($body),
[]
))->withProtocolVersion($protocolVersion);
}
/**
* {@inheritdoc}
*/
public function createResponse(
$statusCode = 200,
$reasonPhrase = null,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return (new Response(
$statusCode,
new Headers($headers),
$this->streamFactory->createStream($body)
))->withProtocolVersion($protocolVersion);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Http\Message;
use Psr\Http\Message\RequestInterface;
/**
* Match a request.
*
* PSR-7 equivalent of Symfony's RequestMatcher
*
* @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/RequestMatcherInterface.php
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
interface RequestMatcher
{
/**
* Decides whether the rule(s) implemented by the strategy matches the supplied request.
*
* @param RequestInterface $request The PSR7 request to check for a match
*
* @return bool true if the request matches, false otherwise
*/
public function matches(RequestInterface $request);
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Http\Message\RequestMatcher;
use Http\Message\RequestMatcher;
use Psr\Http\Message\RequestInterface;
/**
* Match a request with a callback.
*
* @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
*/
final class CallbackRequestMatcher implements RequestMatcher
{
/**
* @var callable
*/
private $callback;
/**
* @param callable $callback
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* {@inheritdoc}
*/
public function matches(RequestInterface $request)
{
return (bool) call_user_func($this->callback, $request);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Http\Message\RequestMatcher;
use Http\Message\RequestMatcher;
use Psr\Http\Message\RequestInterface;
@trigger_error('The '.__NAMESPACE__.'\RegexRequestMatcher class is deprecated since version 1.2 and will be removed in 2.0. Use Http\Message\RequestMatcher\RequestMatcher instead.', E_USER_DEPRECATED);
/**
* Match a request with a regex on the uri.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*
* @deprecated since version 1.2 and will be removed in 2.0. Use {@link RequestMatcher} instead.
*/
final class RegexRequestMatcher implements RequestMatcher
{
/**
* Matching regex.
*
* @var string
*/
private $regex;
/**
* @param string $regex
*/
public function __construct($regex)
{
$this->regex = $regex;
}
/**
* {@inheritdoc}
*/
public function matches(RequestInterface $request)
{
return (bool) preg_match($this->regex, (string) $request->getUri());
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Http\Message\RequestMatcher;
use Http\Message\RequestMatcher as RequestMatcherInterface;
use Psr\Http\Message\RequestInterface;
/**
* A port of the Symfony RequestMatcher for PSR-7.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
final class RequestMatcher implements RequestMatcherInterface
{
/**
* @var string
*/
private $path;
/**
* @var string
*/
private $host;
/**
* @var array
*/
private $methods = [];
/**
* @var string[]
*/
private $schemes = [];
/**
* The regular expressions used for path or host must be specified without delimiter.
* You do not need to escape the forward slash / to match it.
*
* @param string|null $path Regular expression for the path
* @param string|null $host Regular expression for the hostname
* @param string|string[]|null $methods Method or list of methods to match
* @param string|string[]|null $schemes Scheme or list of schemes to match (e.g. http or https)
*/
public function __construct($path = null, $host = null, $methods = [], $schemes = [])
{
$this->path = $path;
$this->host = $host;
$this->methods = array_map('strtoupper', (array) $methods);
$this->schemes = array_map('strtolower', (array) $schemes);
}
/**
* {@inheritdoc}
*
* @api
*/
public function matches(RequestInterface $request)
{
if ($this->schemes && !in_array($request->getUri()->getScheme(), $this->schemes)) {
return false;
}
if ($this->methods && !in_array($request->getMethod(), $this->methods)) {
return false;
}
if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getUri()->getPath()))) {
return false;
}
if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getUri()->getHost())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,270 @@
<?php
namespace Http\Message\Stream;
use Psr\Http\Message\StreamInterface;
/**
* Decorator to make any stream seekable.
*
* Internally it buffers an existing StreamInterface into a php://temp resource (or memory). By default it will use
* 2 megabytes of memory before writing to a temporary disk file.
*
* Due to this, very large stream can suffer performance issue (i/o slowdown).
*/
class BufferedStream implements StreamInterface
{
/** @var resource The buffered resource used to seek previous data */
private $resource;
/** @var int size of the stream if available */
private $size;
/** @var StreamInterface The underlying stream decorated by this class */
private $stream;
/** @var int How many bytes were written */
private $written = 0;
/**
* @param StreamInterface $stream Decorated stream
* @param bool $useFileBuffer Whether to use a file buffer (write to a file, if data exceed a certain size)
* by default, set this to false to only use memory
* @param int $memoryBuffer In conjunction with using file buffer, limit (in bytes) from which it begins to buffer
* the data in a file
*/
public function __construct(StreamInterface $stream, $useFileBuffer = true, $memoryBuffer = 2097152)
{
$this->stream = $stream;
$this->size = $stream->getSize();
if ($useFileBuffer) {
$this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+');
} else {
$this->resource = fopen('php://memory', 'rw+');
}
if (false === $this->resource) {
throw new \RuntimeException('Cannot create a resource over temp or memory implementation');
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
try {
$this->rewind();
return $this->getContents();
} catch (\Throwable $throwable) {
return '';
} catch (\Exception $exception) { // Layer to be BC with PHP 5, remove this when we only support PHP 7+
return '';
}
}
/**
* {@inheritdoc}
*/
public function close()
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot close on a detached stream');
}
$this->stream->close();
fclose($this->resource);
}
/**
* {@inheritdoc}
*/
public function detach()
{
if (null === $this->resource) {
return;
}
// Force reading the remaining data of the stream
$this->getContents();
$resource = $this->resource;
$this->stream->close();
$this->stream = null;
$this->resource = null;
return $resource;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
if (null === $this->resource) {
return;
}
if (null === $this->size && $this->stream->eof()) {
return $this->written;
}
return $this->size;
}
/**
* {@inheritdoc}
*/
public function tell()
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot tell on a detached stream');
}
return ftell($this->resource);
}
/**
* {@inheritdoc}
*/
public function eof()
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot call eof on a detached stream');
}
// We are at the end only when both our resource and underlying stream are at eof
return $this->stream->eof() && (ftell($this->resource) === $this->written);
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return null !== $this->resource;
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot seek on a detached stream');
}
fseek($this->resource, $offset, $whence);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot rewind on a detached stream');
}
rewind($this->resource);
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
return false;
}
/**
* {@inheritdoc}
*/
public function write($string)
{
throw new \RuntimeException('Cannot write on this stream');
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
return null !== $this->resource;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot read on a detached stream');
}
$read = '';
// First read from the resource
if (ftell($this->resource) !== $this->written) {
$read = fread($this->resource, $length);
}
$bytesRead = strlen($read);
if ($bytesRead < $length) {
$streamRead = $this->stream->read($length - $bytesRead);
// Write on the underlying stream what we read
$this->written += fwrite($this->resource, $streamRead);
$read .= $streamRead;
}
return $read;
}
/**
* {@inheritdoc}
*/
public function getContents()
{
if (null === $this->resource) {
throw new \RuntimeException('Cannot read on a detached stream');
}
$read = '';
while (!$this->eof()) {
$read .= $this->read(8192);
}
return $read;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
if (null === $this->resource) {
if (null === $key) {
return [];
}
return;
}
$metadata = stream_get_meta_data($this->resource);
if (null === $key) {
return $metadata;
}
if (!array_key_exists($key, $metadata)) {
return;
}
return $metadata[$key];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Http\Message\StreamFactory;
use Http\Message\StreamFactory;
use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Stream;
/**
* Creates Diactoros streams.
*
* @author Михаил Красильников <m.krasilnikov@yandex.ru>
*/
final class DiactorosStreamFactory implements StreamFactory
{
/**
* {@inheritdoc}
*/
public function createStream($body = null)
{
if ($body instanceof StreamInterface) {
return $body;
}
if (is_resource($body)) {
return new Stream($body);
}
$stream = new Stream('php://memory', 'rw');
if (null !== $body && '' !== $body) {
$stream->write((string) $body);
}
return $stream;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Http\Message\StreamFactory;
use Http\Message\StreamFactory;
/**
* Creates Guzzle streams.
*
* @author Михаил Красильников <m.krasilnikov@yandex.ru>
*/
final class GuzzleStreamFactory implements StreamFactory
{
/**
* {@inheritdoc}
*/
public function createStream($body = null)
{
return \GuzzleHttp\Psr7\stream_for($body);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Http\Message\StreamFactory;
use Http\Message\StreamFactory;
use Psr\Http\Message\StreamInterface;
use Slim\Http\Stream;
/**
* Creates Slim 3 streams.
*
* @author Mika Tuupola <tuupola@appelsiini.net>
*/
final class SlimStreamFactory implements StreamFactory
{
/**
* {@inheritdoc}
*/
public function createStream($body = null)
{
if ($body instanceof StreamInterface) {
return $body;
}
if (is_resource($body)) {
return new Stream($body);
}
$resource = fopen('php://memory', 'r+');
$stream = new Stream($resource);
if (null !== $body && '' !== $body) {
$stream->write((string) $body);
}
return $stream;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Http\Message\UriFactory;
use Http\Message\UriFactory;
use Psr\Http\Message\UriInterface;
use Zend\Diactoros\Uri;
/**
* Creates Diactoros URI.
*
* @author David de Boer <david@ddeboer.nl>
*/
final class DiactorosUriFactory implements UriFactory
{
/**
* {@inheritdoc}
*/
public function createUri($uri)
{
if ($uri instanceof UriInterface) {
return $uri;
} elseif (is_string($uri)) {
return new Uri($uri);
}
throw new \InvalidArgumentException('URI must be a string or UriInterface');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Http\Message\UriFactory;
use GuzzleHttp\Psr7;
use Http\Message\UriFactory;
/**
* Creates Guzzle URI.
*
* @author David de Boer <david@ddeboer.nl>
*/
final class GuzzleUriFactory implements UriFactory
{
/**
* {@inheritdoc}
*/
public function createUri($uri)
{
return Psr7\uri_for($uri);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Http\Message\UriFactory;
use Http\Message\UriFactory;
use Psr\Http\Message\UriInterface;
use Slim\Http\Uri;
/**
* Creates Slim 3 URI.
*
* @author Mika Tuupola <tuupola@appelsiini.net>
*/
final class SlimUriFactory implements UriFactory
{
/**
* {@inheritdoc}
*/
public function createUri($uri)
{
if ($uri instanceof UriInterface) {
return $uri;
}
if (is_string($uri)) {
return Uri::createFromString($uri);
}
throw new \InvalidArgumentException('URI must be a string or UriInterface');
}
}

View File

@@ -0,0 +1,6 @@
<?php
// Register chunk filter if not found
if (!array_key_exists('chunk', stream_get_filters())) {
stream_filter_register('chunk', 'Http\Message\Encoding\Filter\Chunk');
}