Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -0,0 +1,181 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros;
use InvalidArgumentException;
use RuntimeException;
use Psr\Http\Message\StreamInterface;
/**
* Implementation of PSR HTTP streams
*/
class CallbackStream implements StreamInterface
{
/**
* @var callable|null
*/
protected $callback;
/**
* @param callable $callback
* @throws InvalidArgumentException
*/
public function __construct(callable $callback)
{
$this->attach($callback);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->getContents();
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->callback = null;
}
/**
* {@inheritdoc}
*/
public function detach()
{
$callback = $this->callback;
$this->callback = null;
return $callback;
}
/**
* Attach a new callback to the instance.
*
* @param callable $callback
* @throws InvalidArgumentException for callable callback
*/
public function attach(callable $callback)
{
$this->callback = $callback;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
}
/**
* {@inheritdoc}
*/
public function tell()
{
throw new RuntimeException('Callback streams cannot tell position');
}
/**
* {@inheritdoc}
*/
public function eof()
{
return empty($this->callback);
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return false;
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
throw new RuntimeException('Callback streams cannot seek position');
}
/**
* {@inheritdoc}
*/
public function rewind()
{
throw new RuntimeException('Callback streams cannot rewind position');
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
return false;
}
/**
* {@inheritdoc}
*/
public function write($string)
{
throw new RuntimeException('Callback streams cannot write');
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
return false;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
throw new RuntimeException('Callback streams cannot read');
}
/**
* {@inheritdoc}
*/
public function getContents()
{
$callback = $this->detach();
return $callback ? $callback() : '';
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
$metadata = [
'eof' => $this->eof(),
'stream_type' => 'callback',
'seekable' => false
];
if (null === $key) {
return $metadata;
}
if (! array_key_exists($key, $metadata)) {
return null;
}
return $metadata[$key];
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -24,6 +24,7 @@ final class HeaderSecurity
{
/**
* Private constructor; non-instantiable.
* @codeCoverageIgnore
*/
private function __construct()
{
@ -128,8 +129,17 @@ final class HeaderSecurity
*/
public static function assertValid($value)
{
if (! is_string($value) && ! is_numeric($value)) {
throw new InvalidArgumentException(sprintf(
'Invalid header value type; must be a string or numeric; received %s',
(is_object($value) ? get_class($value) : gettype($value))
));
}
if (! self::isValid($value)) {
throw new InvalidArgumentException('Invalid header value');
throw new InvalidArgumentException(sprintf(
'"%s" is not valid header value',
$value
));
}
}
@ -142,8 +152,17 @@ final class HeaderSecurity
*/
public static function assertValidName($name)
{
if (! is_string($name)) {
throw new InvalidArgumentException(sprintf(
'Invalid header name type; expected string; received %s',
(is_object($name) ? get_class($name) : gettype($name))
));
}
if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) {
throw new InvalidArgumentException('Invalid header name');
throw new InvalidArgumentException(sprintf(
'"%s" is not valid header name',
$name
));
}
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -70,6 +70,7 @@ trait MessageTrait
*/
public function withProtocolVersion($version)
{
$this->validateProtocolVersion($version);
$new = clone $this;
$new->protocol = $version;
return $new;
@ -111,7 +112,7 @@ trait MessageTrait
*/
public function hasHeader($header)
{
return array_key_exists(strtolower($header), $this->headerNames);
return isset($this->headerNames[strtolower($header)]);
}
/**
@ -135,10 +136,8 @@ trait MessageTrait
}
$header = $this->headerNames[strtolower($header)];
$value = $this->headers[$header];
$value = is_array($value) ? $value : [$value];
return $value;
return $this->headers[$header];
}
/**
@ -188,22 +187,17 @@ trait MessageTrait
*/
public function withHeader($header, $value)
{
if (is_string($value)) {
$value = [$value];
}
if (! is_array($value) || ! $this->arrayContainsOnlyStrings($value)) {
throw new InvalidArgumentException(
'Invalid header value; must be a string or array of strings'
);
}
HeaderSecurity::assertValidName($header);
self::assertValidHeaderValue($value);
$this->assertHeader($header);
$normalized = strtolower($header);
$new = clone $this;
if ($new->hasHeader($header)) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$value = $this->filterHeaderValue($value);
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
@ -229,27 +223,16 @@ trait MessageTrait
*/
public function withAddedHeader($header, $value)
{
if (is_string($value)) {
$value = [ $value ];
}
if (! is_array($value) || ! $this->arrayContainsOnlyStrings($value)) {
throw new InvalidArgumentException(
'Invalid header value; must be a string or array of strings'
);
}
HeaderSecurity::assertValidName($header);
self::assertValidHeaderValue($value);
$this->assertHeader($header);
if (! $this->hasHeader($header)) {
return $this->withHeader($header, $value);
}
$normalized = strtolower($header);
$header = $this->headerNames[$normalized];
$header = $this->headerNames[strtolower($header)];
$new = clone $this;
$value = $this->filterHeaderValue($value);
$new->headers[$header] = array_merge($this->headers[$header], $value);
return $new;
}
@ -310,15 +293,21 @@ trait MessageTrait
return $new;
}
/**
* Test that an array contains only strings
*
* @param array $array
* @return bool
*/
private function arrayContainsOnlyStrings(array $array)
private function getStream($stream, $modeIfNotInstance)
{
return array_reduce($array, [__CLASS__, 'filterStringValue'], true);
if ($stream instanceof StreamInterface) {
return $stream;
}
if (! is_string($stream) && ! is_resource($stream)) {
throw new InvalidArgumentException(
'Stream must be a string stream resource identifier, '
. 'an actual stream resource, '
. 'or a Psr\Http\Message\StreamInterface implementation'
);
}
return new Stream($stream, $modeIfNotInstance);
}
/**
@ -327,57 +316,80 @@ trait MessageTrait
* Used by message constructors to allow setting all initial headers at once.
*
* @param array $originalHeaders Headers to filter.
* @return array Filtered headers and names.
*/
private function filterHeaders(array $originalHeaders)
private function setHeaders(array $originalHeaders)
{
$headerNames = $headers = [];
foreach ($originalHeaders as $header => $value) {
if (! is_string($header)) {
continue;
}
$value = $this->filterHeaderValue($value);
if (! is_array($value) && ! is_string($value)) {
continue;
}
if (! is_array($value)) {
$value = [ $value ];
}
$this->assertHeader($header);
$headerNames[strtolower($header)] = $header;
$headers[$header] = $value;
}
return [$headerNames, $headers];
$this->headerNames = $headerNames;
$this->headers = $headers;
}
/**
* Test if a value is a string
* Validate the HTTP protocol version
*
* Used with array_reduce.
*
* @param bool $carry
* @param mixed $item
* @return bool
* @param string $version
* @throws InvalidArgumentException on invalid HTTP protocol version
*/
private static function filterStringValue($carry, $item)
private function validateProtocolVersion($version)
{
if (! is_string($item)) {
return false;
if (empty($version)) {
throw new InvalidArgumentException(sprintf(
'HTTP protocol version can not be empty'
));
}
if (! is_string($version)) {
throw new InvalidArgumentException(sprintf(
'Unsupported HTTP protocol version; must be a string, received %s',
(is_object($version) ? get_class($version) : gettype($version))
));
}
// HTTP/1 uses a "<major>.<minor>" numbering scheme to indicate
// versions of the protocol, while HTTP/2 does not.
if (! preg_match('#^(1\.[01]|2)$#', $version)) {
throw new InvalidArgumentException(sprintf(
'Unsupported HTTP protocol version "%s" provided',
$version
));
}
return $carry;
}
/**
* Assert that the provided header values are valid.
* @param mixed $values
* @return string[]
*/
private function filterHeaderValue($values)
{
if (! is_array($values)) {
$values = [$values];
}
return array_map(function ($value) {
HeaderSecurity::assertValid($value);
return (string) $value;
}, $values);
}
/**
* Ensure header name and values are valid.
*
* @param string $name
*
* @see http://tools.ietf.org/html/rfc7230#section-3.2
* @param string[] $values
* @throws InvalidArgumentException
*/
private static function assertValidHeaderValue(array $values)
private function assertHeader($name)
{
array_walk($values, __NAMESPACE__ . '\HeaderSecurity::assertValid');
HeaderSecurity::assertValidName($name);
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -26,12 +26,10 @@ class PhpInputStream extends Stream
/**
* @param string|resource $stream
* @param string $mode
*/
public function __construct($stream = 'php://input', $mode = 'r')
public function __construct($stream = 'php://input')
{
$mode = 'r';
parent::__construct($stream, $mode);
parent::__construct($stream, 'r');
}
/**

View file

@ -3,13 +3,14 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
/**
* Class RelativeStream
@ -131,6 +132,9 @@ final class RelativeStream implements StreamInterface
*/
public function write($string)
{
if ($this->tell() < 0) {
throw new RuntimeException('Invalid pointer position');
}
return $this->decoratedStream->write($string);
}
@ -147,6 +151,9 @@ final class RelativeStream implements StreamInterface
*/
public function read($length)
{
if ($this->tell() < 0) {
throw new RuntimeException('Invalid pointer position');
}
return $this->decoratedStream->read($length);
}
@ -155,6 +162,9 @@ final class RelativeStream implements StreamInterface
*/
public function getContents()
{
if ($this->tell() < 0) {
throw new RuntimeException('Invalid pointer position');
}
return $this->decoratedStream->getContents();
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -11,6 +11,7 @@ namespace Zend\Diactoros;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
/**
* HTTP Request encapsulation
@ -21,10 +22,10 @@ use Psr\Http\Message\StreamInterface;
*/
class Request implements RequestInterface
{
use MessageTrait, RequestTrait;
use RequestTrait;
/**
* @param null|string $uri URI for the request, if any.
* @param null|string|UriInterface $uri URI for the request, if any.
* @param null|string $method HTTP method for the request, if any.
* @param string|resource|StreamInterface $body Message body, if any.
* @param array $headers Headers for the message, if any.
@ -42,7 +43,7 @@ class Request implements RequestInterface
{
$headers = $this->headers;
if (! $this->hasHeader('host')
&& ($this->uri && $this->uri->getHost())
&& $this->uri->getHost()
) {
$headers['Host'] = [$this->getHostFromUri()];
}
@ -57,7 +58,7 @@ class Request implements RequestInterface
{
if (! $this->hasHeader($header)) {
if (strtolower($header) === 'host'
&& ($this->uri && $this->uri->getHost())
&& $this->uri->getHost()
) {
return [$this->getHostFromUri()];
}
@ -66,9 +67,7 @@ class Request implements RequestInterface
}
$header = $this->headerNames[strtolower($header)];
$value = $this->headers[$header];
$value = is_array($value) ? $value : [$value];
return $value;
return $this->headers[$header];
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros\Request;
use Psr\Http\Message\RequestInterface;
use UnexpectedValueException;
use Zend\Diactoros\Request;
use Zend\Diactoros\Stream;
/**
* Serialize or deserialize request messages to/from arrays.
*
* This class provides functionality for serializing a RequestInterface instance
* to an array, as well as the reverse operation of creating a Request instance
* from an array representing a message.
*/
final class ArraySerializer
{
/**
* Serialize a request message to an array.
*
* @param RequestInterface $request
* @return array
*/
public static function toArray(RequestInterface $request)
{
return [
'method' => $request->getMethod(),
'request_target' => $request->getRequestTarget(),
'uri' => (string) $request->getUri(),
'protocol_version' => $request->getProtocolVersion(),
'headers' => $request->getHeaders(),
'body' => (string) $request->getBody(),
];
}
/**
* Deserialize a request array to a request instance.
*
* @param array $serializedRequest
* @return Request
* @throws UnexpectedValueException when cannot deserialize response
*/
public static function fromArray(array $serializedRequest)
{
try {
$uri = self::getValueFromKey($serializedRequest, 'uri');
$method = self::getValueFromKey($serializedRequest, 'method');
$body = new Stream('php://memory', 'wb+');
$body->write(self::getValueFromKey($serializedRequest, 'body'));
$headers = self::getValueFromKey($serializedRequest, 'headers');
$requestTarget = self::getValueFromKey($serializedRequest, 'request_target');
$protocolVersion = self::getValueFromKey($serializedRequest, 'protocol_version');
return (new Request($uri, $method, $body, $headers))
->withRequestTarget($requestTarget)
->withProtocolVersion($protocolVersion);
} catch (\Exception $exception) {
throw new UnexpectedValueException('Cannot deserialize request', null, $exception);
}
}
/**
* @param array $data
* @param string $key
* @param string $message
* @return mixed
* @throws UnexpectedValueException
*/
private static function getValueFromKey(array $data, $key, $message = null)
{
if (isset($data[$key])) {
return $data[$key];
}
if ($message === null) {
$message = sprintf('Missing "%s" key in serialized request', $key);
}
throw new UnexpectedValueException($message);
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -76,6 +76,10 @@ final class Serializer extends AbstractSerializer
*/
public static function toString(RequestInterface $request)
{
$httpMethod = $request->getMethod();
if (empty($httpMethod)) {
throw new UnexpectedValueException('Object can not be serialized because HTTP method is empty');
}
$headers = self::serializeHeaders($request->getHeaders());
$body = (string) $request->getBody();
$format = '%s %s HTTP/%s%s%s';
@ -89,7 +93,7 @@ final class Serializer extends AbstractSerializer
return sprintf(
$format,
$request->getMethod(),
$httpMethod,
$request->getRequestTarget(),
$request->getProtocolVersion(),
$headers,

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -22,14 +22,11 @@ use Psr\Http\Message\UriInterface;
* the environment. As such, this trait exists to provide the common code
* between both client-side and server-side requests, and each can then
* use the headers functionality required by their implementations.
*
* @property array $headers
* @property array $headerNames
* @property StreamInterface $stream
* @method bool hasHeader(string $header)
*/
trait RequestTrait
{
use MessageTrait;
/**
* @var string
*/
@ -43,7 +40,7 @@ trait RequestTrait
private $requestTarget;
/**
* @var null|UriInterface
* @var UriInterface
*/
private $uri;
@ -52,7 +49,7 @@ trait RequestTrait
*
* Used by constructors.
*
* @param null|string $uri URI for the request, if any.
* @param null|string|UriInterface $uri URI for the request, if any.
* @param null|string $method HTTP method for the request, if any.
* @param string|resource|StreamInterface $body Message body, if any.
* @param array $headers Headers for the message, if any.
@ -60,33 +57,52 @@ trait RequestTrait
*/
private function initialize($uri = null, $method = null, $body = 'php://memory', array $headers = [])
{
if (! $uri instanceof UriInterface && ! is_string($uri) && null !== $uri) {
throw new InvalidArgumentException(
'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance'
);
}
$this->validateMethod($method);
if (! is_string($body) && ! is_resource($body) && ! $body instanceof StreamInterface) {
throw new InvalidArgumentException(
'Body must be a string stream resource identifier, '
. 'an actual stream resource, '
. 'or a Psr\Http\Message\StreamInterface implementation'
);
}
if (is_string($uri)) {
$uri = new Uri($uri);
}
$this->method = $method ?: '';
$this->uri = $uri ?: new Uri();
$this->stream = ($body instanceof StreamInterface) ? $body : new Stream($body, 'wb+');
$this->uri = $this->createUri($uri);
$this->stream = $this->getStream($body, 'wb+');
list($this->headerNames, $headers) = $this->filterHeaders($headers);
$this->assertHeaders($headers);
$this->headers = $headers;
$this->setHeaders($headers);
// per PSR-7: attempt to set the Host header from a provided URI if no
// Host header is provided
if (! $this->hasHeader('Host') && $this->uri->getHost()) {
$this->headerNames['host'] = 'Host';
$this->headers['Host'] = [$this->getHostFromUri()];
}
}
/**
* Create and return a URI instance.
*
* If `$uri` is a already a `UriInterface` instance, returns it.
*
* If `$uri` is a string, passes it to the `Uri` constructor to return an
* instance.
*
* If `$uri is null, creates and returns an empty `Uri` instance.
*
* Otherwise, it raises an exception.
*
* @param null|string|UriInterface $uri
* @return UriInterface
* @throws InvalidArgumentException
*/
private function createUri($uri)
{
if ($uri instanceof UriInterface) {
return $uri;
}
if (is_string($uri)) {
return new Uri($uri);
}
if ($uri === null) {
return new Uri();
}
throw new InvalidArgumentException(
'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance'
);
}
/**
@ -111,10 +127,6 @@ trait RequestTrait
return $this->requestTarget;
}
if (! $this->uri) {
return '/';
}
$target = $this->uri->getPath();
if ($this->uri->getQuery()) {
$target .= '?' . $this->uri->getQuery();
@ -249,6 +261,16 @@ trait RequestTrait
}
$new->headerNames['host'] = 'Host';
// Remove an existing host header if present, regardless of current
// de-normalization of the header name.
// @see https://github.com/zendframework/zend-diactoros/issues/91
foreach (array_keys($new->headers) as $header) {
if (strtolower($header) === 'host') {
unset($new->headers[$header]);
}
}
$new->headers['Host'] = [$host];
return $new;
@ -292,18 +314,4 @@ trait RequestTrait
$host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
return $host;
}
/**
* Ensure header names and values are valid.
*
* @param array $headers
* @throws InvalidArgumentException
*/
private function assertHeaders(array $headers)
{
foreach ($headers as $name => $headerValues) {
HeaderSecurity::assertValidName($name);
array_walk($headerValues, __NAMESPACE__ . '\HeaderSecurity::assertValid');
}
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -24,6 +24,9 @@ class Response implements ResponseInterface
{
use MessageTrait;
const MIN_STATUS_CODE_VALUE = 100;
const MAX_STATUS_CODE_VALUE = 599;
/**
* Map of standard HTTP status code/reason phrases
*
@ -42,8 +45,9 @@ class Response implements ResponseInterface
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-status',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
// REDIRECTION CODES
300 => 'Multiple Choices',
301 => 'Moved Permanently',
@ -51,8 +55,9 @@ class Response implements ResponseInterface
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => 'Switch Proxy', // Deprecated
306 => 'Switch Proxy', // Deprecated to 306 => '(Unused)'
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
// CLIENT ERROR
400 => 'Bad Request',
401 => 'Unauthorized',
@ -62,17 +67,18 @@ class Response implements ResponseInterface
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
421 => 'Misdirected Request',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
@ -81,17 +87,22 @@ class Response implements ResponseInterface
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
444 => 'Connection Closed Without Response',
451 => 'Unavailable For Legal Reasons',
// SERVER ERROR
499 => 'Client Closed Request',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
510 => 'Not Extended',
511 => 'Network Authentication Required',
599 => 'Network Connect Timeout Error',
];
/**
@ -102,34 +113,19 @@ class Response implements ResponseInterface
/**
* @var int
*/
private $statusCode = 200;
private $statusCode;
/**
* @param string|resource|StreamInterface $stream Stream identifier and/or actual stream resource
* @param string|resource|StreamInterface $body Stream identifier and/or actual stream resource
* @param int $status Status code for the response, if any.
* @param array $headers Headers for the response, if any.
* @throws InvalidArgumentException on any invalid element.
*/
public function __construct($body = 'php://memory', $status = 200, array $headers = [])
{
if (! is_string($body) && ! is_resource($body) && ! $body instanceof StreamInterface) {
throw new InvalidArgumentException(
'Stream must be a string stream resource identifier, '
. 'an actual stream resource, '
. 'or a Psr\Http\Message\StreamInterface implementation'
);
}
if (null !== $status) {
$this->validateStatus($status);
}
$this->stream = ($body instanceof StreamInterface) ? $body : new Stream($body, 'wb+');
$this->statusCode = $status ? (int) $status : 200;
list($this->headerNames, $headers) = $this->filterHeaders($headers);
$this->assertHeaders($headers);
$this->headers = $headers;
$this->setStatusCode($status);
$this->stream = $this->getStream($body, 'wb+');
$this->setHeaders($headers);
}
/**
@ -159,44 +155,32 @@ class Response implements ResponseInterface
*/
public function withStatus($code, $reasonPhrase = '')
{
$this->validateStatus($code);
$new = clone $this;
$new->statusCode = (int) $code;
$new->setStatusCode($code);
$new->reasonPhrase = $reasonPhrase;
return $new;
}
/**
* Validate a status code.
* Set a valid status code.
*
* @param int|string $code
* @param int $code
* @throws InvalidArgumentException on an invalid status code.
*/
private function validateStatus($code)
private function setStatusCode($code)
{
if (! is_numeric($code)
|| is_float($code)
|| $code < 100
|| $code >= 600
|| $code < static::MIN_STATUS_CODE_VALUE
|| $code > static::MAX_STATUS_CODE_VALUE
) {
throw new InvalidArgumentException(sprintf(
'Invalid status code "%s"; must be an integer between 100 and 599, inclusive',
(is_scalar($code) ? $code : gettype($code))
'Invalid status code "%s"; must be an integer between %d and %d, inclusive',
(is_scalar($code) ? $code : gettype($code)),
static::MIN_STATUS_CODE_VALUE,
static::MAX_STATUS_CODE_VALUE
));
}
}
/**
* Ensure header names and values are valid.
*
* @param array $headers
* @throws InvalidArgumentException
*/
private function assertHeaders(array $headers)
{
foreach ($headers as $name => $headerValues) {
HeaderSecurity::assertValidName($name);
array_walk($headerValues, __NAMESPACE__ . '\HeaderSecurity::assertValid');
}
$this->statusCode = $code;
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros\Response;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
/**
* Serialize or deserialize response messages to/from arrays.
*
* This class provides functionality for serializing a ResponseInterface instance
* to an array, as well as the reverse operation of creating a Response instance
* from an array representing a message.
*/
final class ArraySerializer
{
/**
* Serialize a response message to an array.
*
* @param ResponseInterface $response
* @return array
*/
public static function toArray(ResponseInterface $response)
{
return [
'status_code' => $response->getStatusCode(),
'reason_phrase' => $response->getReasonPhrase(),
'protocol_version' => $response->getProtocolVersion(),
'headers' => $response->getHeaders(),
'body' => (string) $response->getBody(),
];
}
/**
* Deserialize a response array to a response instance.
*
* @param array $serializedResponse
* @return Response
* @throws UnexpectedValueException when cannot deserialize response
*/
public static function fromArray(array $serializedResponse)
{
try {
$body = new Stream('php://memory', 'wb+');
$body->write(self::getValueFromKey($serializedResponse, 'body'));
$statusCode = self::getValueFromKey($serializedResponse, 'status_code');
$headers = self::getValueFromKey($serializedResponse, 'headers');
$protocolVersion = self::getValueFromKey($serializedResponse, 'protocol_version');
$reasonPhrase = self::getValueFromKey($serializedResponse, 'reason_phrase');
return (new Response($body, $statusCode, $headers))
->withProtocolVersion($protocolVersion)
->withStatus($statusCode, $reasonPhrase);
} catch (\Exception $exception) {
throw new UnexpectedValueException('Cannot deserialize response', null, $exception);
}
}
/**
* @param array $data
* @param string $key
* @param string $message
* @return mixed
* @throws UnexpectedValueException
*/
private static function getValueFromKey(array $data, $key, $message = null)
{
if (isset($data[$key])) {
return $data[$key];
}
if ($message === null) {
$message = sprintf('Missing "%s" key in serialized request', $key);
}
throw new UnexpectedValueException($message);
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -41,7 +41,7 @@ class HtmlResponse extends Response
parent::__construct(
$this->createBody($html),
$status,
$this->injectContentType('text/html', $headers)
$this->injectContentType('text/html; charset=utf-8', $headers)
);
}
@ -68,6 +68,7 @@ class HtmlResponse extends Response
$body = new Stream('php://temp', 'wb+');
$body->write($html);
$body->rewind();
return $body;
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -24,6 +24,17 @@ class JsonResponse extends Response
{
use InjectContentTypeTrait;
/**
* Default flags for json_encode; value of:
*
* <code>
* JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES
* </code>
*
* @const int
*/
const DEFAULT_JSON_FLAGS = 79;
/**
* Create a JSON response with the given data.
*
@ -34,6 +45,7 @@ class JsonResponse extends Response
* - JSON_HEX_APOS
* - JSON_HEX_AMP
* - JSON_HEX_QUOT
* - JSON_UNESCAPED_SLASHES
*
* @param mixed $data Data to convert to JSON.
* @param int $status Integer status code for the response; 200 by default.
@ -41,10 +53,15 @@ class JsonResponse extends Response
* @param int $encodingOptions JSON encoding options to use.
* @throws InvalidArgumentException if unable to encode the $data to JSON.
*/
public function __construct($data, $status = 200, array $headers = [], $encodingOptions = 15)
{
public function __construct(
$data,
$status = 200,
array $headers = [],
$encodingOptions = self::DEFAULT_JSON_FLAGS
) {
$body = new Stream('php://temp', 'wb+');
$body->write($this->jsonEncode($data, $encodingOptions));
$body->rewind();
$headers = $this->injectContentType('application/json', $headers);

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -12,7 +12,6 @@ namespace Zend\Diactoros\Response;
use InvalidArgumentException;
use Psr\Http\Message\UriInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
/**
* Produce a redirect response.

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -14,6 +14,8 @@ use RuntimeException;
class SapiEmitter implements EmitterInterface
{
use SapiEmitterTrait;
/**
* Emits a response for a PHP SAPI environment.
*
@ -29,88 +31,21 @@ class SapiEmitter implements EmitterInterface
throw new RuntimeException('Unable to emit response; headers already sent');
}
$response = $this->injectContentLength($response);
$this->emitStatusLine($response);
$this->emitHeaders($response);
$this->emitBody($response, $maxBufferLevel);
}
/**
* Emit the status line.
*
* Emits the status line using the protocol version and status code from
* the response; if a reason phrase is availble, it, too, is emitted.
*
* @param ResponseInterface $response
*/
private function emitStatusLine(ResponseInterface $response)
{
$reasonPhrase = $response->getReasonPhrase();
header(sprintf(
'HTTP/%s %d%s',
$response->getProtocolVersion(),
$response->getStatusCode(),
($reasonPhrase ? ' ' . $reasonPhrase : '')
));
}
/**
* Emit response headers.
*
* Loops through each header, emitting each; if the header value
* is an array with multiple values, ensures that each is sent
* in such a way as to create aggregate headers (instead of replace
* the previous).
*
* @param ResponseInterface $response
*/
private function emitHeaders(ResponseInterface $response)
{
foreach ($response->getHeaders() as $header => $values) {
$name = $this->filterHeader($header);
$first = true;
foreach ($values as $value) {
header(sprintf(
'%s: %s',
$name,
$value
), $first);
$first = false;
}
}
$this->flush($maxBufferLevel);
$this->emitBody($response);
}
/**
* Emit the message body.
*
* Loops through the output buffer, flushing each, before emitting
* the response body using `echo()`.
*
* @param ResponseInterface $response
* @param int $maxBufferLevel Flush up to this buffer level.
*/
private function emitBody(ResponseInterface $response, $maxBufferLevel)
private function emitBody(ResponseInterface $response)
{
if (null === $maxBufferLevel) {
$maxBufferLevel = ob_get_level();
}
while (ob_get_level() > $maxBufferLevel) {
ob_end_flush();
}
echo $response->getBody();
}
/**
* Filter a header name to wordcase
*
* @param string $header
* @return string
*/
private function filterHeader($header)
{
$filtered = str_replace('-', ' ', $header);
$filtered = ucwords($filtered);
return str_replace(' ', '-', $filtered);
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros\Response;
use Psr\Http\Message\ResponseInterface;
trait SapiEmitterTrait
{
/**
* Inject the Content-Length header if is not already present.
*
* @param ResponseInterface $response
* @return ResponseInterface
*/
private function injectContentLength(ResponseInterface $response)
{
if (! $response->hasHeader('Content-Length')) {
// PSR-7 indicates int OR null for the stream size; for null values,
// we will not auto-inject the Content-Length.
if (null !== $response->getBody()->getSize()) {
return $response->withHeader('Content-Length', (string) $response->getBody()->getSize());
}
}
return $response;
}
/**
* Emit the status line.
*
* Emits the status line using the protocol version and status code from
* the response; if a reason phrase is available, it, too, is emitted.
*
* @param ResponseInterface $response
*/
private function emitStatusLine(ResponseInterface $response)
{
$reasonPhrase = $response->getReasonPhrase();
header(sprintf(
'HTTP/%s %d%s',
$response->getProtocolVersion(),
$response->getStatusCode(),
($reasonPhrase ? ' ' . $reasonPhrase : '')
));
}
/**
* Emit response headers.
*
* Loops through each header, emitting each; if the header value
* is an array with multiple values, ensures that each is sent
* in such a way as to create aggregate headers (instead of replace
* the previous).
*
* @param ResponseInterface $response
*/
private function emitHeaders(ResponseInterface $response)
{
foreach ($response->getHeaders() as $header => $values) {
$name = $this->filterHeader($header);
$first = true;
foreach ($values as $value) {
header(sprintf(
'%s: %s',
$name,
$value
), $first);
$first = false;
}
}
}
/**
* Loops through the output buffer, flushing each, before emitting
* the response.
*
* @param int|null $maxBufferLevel Flush up to this buffer level.
*/
private function flush($maxBufferLevel = null)
{
if (null === $maxBufferLevel) {
$maxBufferLevel = ob_get_level();
}
while (ob_get_level() > $maxBufferLevel) {
ob_end_flush();
}
}
/**
* Filter a header name to wordcase
*
* @param string $header
* @return string
*/
private function filterHeader($header)
{
$filtered = str_replace('-', ' ', $header);
$filtered = ucwords($filtered);
return str_replace(' ', '-', $filtered);
}
}

View file

@ -0,0 +1,135 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros\Response;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Zend\Diactoros\RelativeStream;
class SapiStreamEmitter implements EmitterInterface
{
use SapiEmitterTrait;
/**
* Emits a response for a PHP SAPI environment.
*
* Emits the status line and headers via the header() function, and the
* body content via the output buffer.
*
* @param ResponseInterface $response
* @param int $maxBufferLength Maximum output buffering size for each iteration
*/
public function emit(ResponseInterface $response, $maxBufferLength = 8192)
{
if (headers_sent()) {
throw new RuntimeException('Unable to emit response; headers already sent');
}
$response = $this->injectContentLength($response);
$this->emitStatusLine($response);
$this->emitHeaders($response);
$this->flush();
$range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
if (is_array($range) && $range[0] === 'bytes') {
$this->emitBodyRange($range, $response, $maxBufferLength);
return;
}
$this->emitBody($response, $maxBufferLength);
}
/**
* Emit the message body.
*
* @param ResponseInterface $response
* @param int $maxBufferLength
*/
private function emitBody(ResponseInterface $response, $maxBufferLength)
{
$body = $response->getBody();
if ($body->isSeekable()) {
$body->rewind();
}
if (! $body->isReadable()) {
echo $body;
return;
}
while (! $body->eof()) {
echo $body->read($maxBufferLength);
}
}
/**
* Emit a range of the message body.
*
* @param array $range
* @param ResponseInterface $response
* @param int $maxBufferLength
*/
private function emitBodyRange(array $range, ResponseInterface $response, $maxBufferLength)
{
list($unit, $first, $last, $length) = $range;
$body = $response->getBody();
$length = $last - $first + 1;
if ($body->isSeekable()) {
$body->seek($first);
$first = 0;
}
if (! $body->isReadable()) {
echo substr($body->getContents(), $first, $length);
return;
}
$remaining = $length;
while ($remaining >= $maxBufferLength && ! $body->eof()) {
$contents = $body->read($maxBufferLength);
$remaining -= strlen($contents);
echo $contents;
}
if ($remaining > 0 && ! $body->eof()) {
echo $body->read($remaining);
}
}
/**
* Parse content-range header
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
*
* @param string $header
* @return false|array [unit, first, last, length]; returns false if no
* content range or an invalid content range is provided
*/
private function parseContentRange($header)
{
if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
return [
$matches['unit'],
(int) $matches['first'],
(int) $matches['last'],
$matches['length'] === '*' ? '*' : (int) $matches['length'],
];
}
return false;
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -37,7 +37,7 @@ final class Serializer extends AbstractSerializer
* Parse a response from a stream.
*
* @param StreamInterface $stream
* @return ResponseInterface
* @return Response
* @throws InvalidArgumentException when the stream is not readable.
* @throws UnexpectedValueException when errors occur parsing the message.
*/
@ -54,7 +54,7 @@ final class Serializer extends AbstractSerializer
return (new Response($body, $status, $headers))
->withProtocolVersion($version)
->withStatus($status, $reasonPhrase);
->withStatus((int) $status, $reasonPhrase);
}
/**
@ -73,9 +73,8 @@ final class Serializer extends AbstractSerializer
if (! empty($headers)) {
$headers = "\r\n" . $headers;
}
if (! empty($body)) {
$headers .= "\r\n\r\n";
}
$headers .= "\r\n\r\n";
return sprintf(
$format,

View file

@ -0,0 +1,74 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros\Response;
use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
/**
* Plain text response.
*
* Allows creating a response by passing a string to the constructor;
* by default, sets a status code of 200 and sets the Content-Type header to
* text/plain.
*/
class TextResponse extends Response
{
use InjectContentTypeTrait;
/**
* Create a plain text response.
*
* Produces a text response with a Content-Type of text/plain and a default
* status of 200.
*
* @param string|StreamInterface $text String or stream for the message body.
* @param int $status Integer status code for the response; 200 by default.
* @param array $headers Array of headers to use at initialization.
* @throws InvalidArgumentException if $text is neither a string or stream.
*/
public function __construct($text, $status = 200, array $headers = [])
{
parent::__construct(
$this->createBody($text),
$status,
$this->injectContentType('text/plain; charset=utf-8', $headers)
);
}
/**
* Create the message body.
*
* @param string|StreamInterface $text
* @return StreamInterface
* @throws InvalidArgumentException if $html is neither a string or stream.
*/
private function createBody($text)
{
if ($text instanceof StreamInterface) {
return $text;
}
if (! is_string($text)) {
throw new InvalidArgumentException(sprintf(
'Invalid content (%s) provided to %s',
(is_object($text) ? get_class($text) : gettype($text)),
__CLASS__
));
}
$body = new Stream('php://temp', 'wb+');
$body->write($text);
$body->rewind();
return $body;
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -13,6 +13,7 @@ use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
/**
* Server-side HTTP request
@ -30,7 +31,7 @@ use Psr\Http\Message\UploadedFileInterface;
*/
class ServerRequest implements ServerRequestInterface
{
use MessageTrait, RequestTrait;
use RequestTrait;
/**
* @var array
@ -65,10 +66,14 @@ class ServerRequest implements ServerRequestInterface
/**
* @param array $serverParams Server parameters, typically from $_SERVER
* @param array $uploadedFiles Upload file information, a tree of UploadedFiles
* @param null|string $uri URI for the request, if any.
* @param null|string|UriInterface $uri URI for the request, if any.
* @param null|string $method HTTP method for the request, if any.
* @param string|resource|StreamInterface $body Message body, if any.
* @param array $headers Headers for the message, if any.
* @param array $cookies Cookies for the message, if any.
* @param array $queryParams Query params for the message, if any.
* @param null|array|object $parsedBody The deserialized body parameters, if any.
* @param string $protocol HTTP protocol version.
* @throws InvalidArgumentException for any invalid value.
*/
public function __construct(
@ -77,14 +82,25 @@ class ServerRequest implements ServerRequestInterface
$uri = null,
$method = null,
$body = 'php://input',
array $headers = []
array $headers = [],
array $cookies = [],
array $queryParams = [],
$parsedBody = null,
$protocol = '1.1'
) {
$this->validateUploadedFiles($uploadedFiles);
$body = $this->getStream($body);
if ($body === 'php://input') {
$body = new PhpInputStream();
}
$this->initialize($uri, $method, $body, $headers);
$this->serverParams = $serverParams;
$this->uploadedFiles = $uploadedFiles;
$this->cookieParams = $cookies;
$this->queryParams = $queryParams;
$this->parsedBody = $parsedBody;
$this->protocol = $protocol;
}
/**
@ -203,10 +219,6 @@ class ServerRequest implements ServerRequestInterface
*/
public function withoutAttribute($attribute)
{
if (! isset($this->attributes[$attribute])) {
return clone $this;
}
$new = clone $this;
unset($new->attributes[$attribute]);
return $new;
@ -248,33 +260,6 @@ class ServerRequest implements ServerRequestInterface
return $new;
}
/**
* Set the body stream
*
* @param string|resource|StreamInterface $stream
* @return StreamInterface
*/
private function getStream($stream)
{
if ($stream === 'php://input') {
return new PhpInputStream();
}
if (! is_string($stream) && ! is_resource($stream) && ! $stream instanceof StreamInterface) {
throw new InvalidArgumentException(
'Stream must be a string stream resource identifier, '
. 'an actual stream resource, '
. 'or a Psr\Http\Message\StreamInterface implementation'
);
}
if (! $stream instanceof StreamInterface) {
return new Stream($stream, 'r');
}
return $stream;
}
/**
* Recursively validate the structure in an uploaded files array.
*

View file

@ -3,17 +3,16 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\UploadedFileInterface;
use stdClass;
use UnexpectedValueException;
/**
* Class for marshaling a request object from the current PHP environment.
@ -60,19 +59,23 @@ abstract class ServerRequestFactory
$server = static::normalizeServer($server ?: $_SERVER);
$files = static::normalizeFiles($files ?: $_FILES);
$headers = static::marshalHeaders($server);
$request = new ServerRequest(
if (null === $cookies && array_key_exists('cookie', $headers)) {
$cookies = self::parseCookieHeader($headers['cookie']);
}
return new ServerRequest(
$server,
$files,
static::marshalUriFromServer($server, $headers),
static::get('REQUEST_METHOD', $server, 'GET'),
'php://input',
$headers
$headers,
$cookies ?: $_COOKIE,
$query ?: $_GET,
$body ?: $_POST,
static::marshalProtocolVersion($server)
);
return $request
->withCookieParams($cookies ?: $_COOKIE)
->withQueryParams($query ?: $_GET)
->withParsedBody($body ?: $_POST);
}
/**
@ -196,24 +199,26 @@ abstract class ServerRequestFactory
{
$headers = [];
foreach ($server as $key => $value) {
if (strpos($key, 'HTTP_COOKIE') === 0) {
// Cookies are handled using the $_COOKIE superglobal
continue;
// Apache prefixes environment variables with REDIRECT_
// if they are added by rewrite rules
if (strpos($key, 'REDIRECT_') === 0) {
$key = substr($key, 9);
// We will not overwrite existing variables with the
// prefixed versions, though
if (array_key_exists($key, $server)) {
continue;
}
}
if ($value && strpos($key, 'HTTP_') === 0) {
$name = strtr(substr($key, 5), '_', ' ');
$name = strtr(ucwords(strtolower($name)), ' ', '-');
$name = strtolower($name);
$name = strtr(strtolower(substr($key, 5)), '_', '-');
$headers[$name] = $value;
continue;
}
if ($value && strpos($key, 'CONTENT_') === 0) {
$name = substr($key, 8); // Content-
$name = 'Content-' . (($name == 'MD5') ? $name : ucfirst(strtolower($name)));
$name = strtolower($name);
$name = 'content-' . strtolower(substr($key, 8));
$headers[$name] = $value;
continue;
}
@ -267,8 +272,15 @@ abstract class ServerRequestFactory
$query = ltrim($server['QUERY_STRING'], '?');
}
// URI fragment
$fragment = '';
if (strpos($path, '#') !== false) {
list($path, $fragment) = explode('#', $path, 2);
}
return $uri
->withPath($path)
->withFragment($fragment)
->withQuery($query);
}
@ -455,4 +467,56 @@ abstract class ServerRequestFactory
}
return $normalizedFiles;
}
/**
* Return HTTP protocol version (X.Y)
*
* @param array $server
* @return string
*/
private static function marshalProtocolVersion(array $server)
{
if (! isset($server['SERVER_PROTOCOL'])) {
return '1.1';
}
if (! preg_match('#^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$#', $server['SERVER_PROTOCOL'], $matches)) {
throw new UnexpectedValueException(sprintf(
'Unrecognized protocol version (%s)',
$server['SERVER_PROTOCOL']
));
}
return $matches['version'];
}
/**
* Parse a cookie header according to RFC 6265.
*
* PHP will replace special characters in cookie names, which results in other cookies not being available due to
* overwriting. Thus, the server request should take the cookies from the request header instead.
*
* @param $cookieHeader
* @return array
*/
private static function parseCookieHeader($cookieHeader)
{
preg_match_all('(
(?:^\\n?[ \t]*|;[ ])
(?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
=
(?P<DQUOTE>"?)
(?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
(?P=DQUOTE)
(?=\\n?[ \t]*$|;[ ])
)x', $cookieHeader, $matches, PREG_SET_ORDER);
$cookies = [];
foreach ($matches as $match) {
$cookies[$match['name']] = urldecode($match['value']);
}
return $cookies;
}
}

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -19,7 +19,7 @@ use Psr\Http\Message\StreamInterface;
class Stream implements StreamInterface
{
/**
* @var resource
* @var resource|null
*/
protected $resource;

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -51,6 +51,14 @@ class UploadedFile implements UploadedFileInterface
*/
private $stream;
/**
* @param string|resource $streamOrFile
* @param int $size
* @param int $errorStatus
* @param string|null $clientFilename
* @param string|null $clientMediaType
* @throws InvalidArgumentException
*/
public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
{
if ($errorStatus === UPLOAD_ERR_OK) {
@ -134,24 +142,26 @@ class UploadedFile implements UploadedFileInterface
*/
public function moveTo($targetPath)
{
if ($this->moved) {
throw new RuntimeException('Cannot move file; already moved!');
}
if ($this->error !== UPLOAD_ERR_OK) {
throw new RuntimeException('Cannot retrieve stream due to upload error');
}
if (! is_string($targetPath)) {
throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a string'
);
}
if (empty($targetPath)) {
if (! is_string($targetPath) || empty($targetPath)) {
throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a non-empty string'
);
}
if ($this->moved) {
throw new RuntimeException('Cannot move file; already moved!');
$targetDirectory = dirname($targetPath);
if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
throw new RuntimeException(sprintf(
'The target directory `%s` does not exists or is not writable',
$targetDirectory
));
}
$sapi = PHP_SAPI;

View file

@ -3,7 +3,7 @@
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
@ -36,7 +36,7 @@ class Uri implements UriInterface
*
* @const string
*/
const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~\pL';
/**
* @var int[] Array indexed by valid scheme names to their corresponding ports.
@ -306,21 +306,23 @@ class Uri implements UriInterface
*/
public function withPort($port)
{
if (! is_numeric($port)) {
if (! is_numeric($port) && $port !== null) {
throw new InvalidArgumentException(sprintf(
'Invalid port "%s" specified; must be an integer or integer string',
'Invalid port "%s" specified; must be an integer, an integer string, or null',
(is_object($port) ? get_class($port) : gettype($port))
));
}
$port = (int) $port;
if ($port !== null) {
$port = (int) $port;
}
if ($port === $this->port) {
// Do nothing if no change was made.
return clone $this;
}
if ($port < 1 || $port > 65535) {
if ($port !== null && $port < 1 || $port > 65535) {
throw new InvalidArgumentException(sprintf(
'Invalid port "%d" specified; must be a valid TCP/UDP port',
$port
@ -440,12 +442,12 @@ class Uri implements UriInterface
);
}
$this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
$this->userInfo = isset($parts['user']) ? $parts['user'] : '';
$this->host = isset($parts['host']) ? $parts['host'] : '';
$this->port = isset($parts['port']) ? $parts['port'] : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQuery($parts['query']) : '';
$this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
$this->userInfo = isset($parts['user']) ? $parts['user'] : '';
$this->host = isset($parts['host']) ? $parts['host'] : '';
$this->port = isset($parts['port']) ? $parts['port'] : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQuery($parts['query']) : '';
$this->fragment = isset($parts['fragment']) ? $this->filterFragment($parts['fragment']) : '';
if (isset($parts['pass'])) {
@ -468,11 +470,11 @@ class Uri implements UriInterface
$uri = '';
if (! empty($scheme)) {
$uri .= sprintf('%s://', $scheme);
$uri .= sprintf('%s:', $scheme);
}
if (! empty($authority)) {
$uri .= $authority;
$uri .= '//' . $authority;
}
if ($path) {
@ -505,6 +507,9 @@ class Uri implements UriInterface
private function isNonStandardPort($scheme, $host, $port)
{
if (! $scheme) {
if ($host && ! $port) {
return false;
}
return true;
}
@ -551,7 +556,7 @@ class Uri implements UriInterface
private function filterPath($path)
{
$path = preg_replace_callback(
'/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
[$this, 'urlEncodeChar'],
$path
);
@ -625,7 +630,7 @@ class Uri implements UriInterface
private function filterFragment($fragment)
{
if (! empty($fragment) && strpos($fragment, '#') === 0) {
$fragment = substr($fragment, 1);
$fragment = '%23' . substr($fragment, 1);
}
return $this->filterQueryOrFragment($fragment);
@ -640,7 +645,7 @@ class Uri implements UriInterface
private function filterQueryOrFragment($value)
{
return preg_replace_callback(
'/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
[$this, 'urlEncodeChar'],
$value
);