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

@ -1,3 +1,2 @@
coverage_clover: clover.xml
json_path: coveralls-upload.json
src_dir: src

View file

@ -2,11 +2,464 @@
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.4.0 - 2017-04-06
### Added
- [#219](https://github.com/zendframework/zend-diactoros/pull/219) adds two new
classes, `Zend\Diactoros\Request\ArraySerializer` and
`Zend\Diactoros\Response\ArraySerializer`. Each exposes the static methods
`toArray()` and `fromArray()`, allowing de/serialization of messages from and
to arrays.
- [#236](https://github.com/zendframework/zend-diactoros/pull/236) adds two new
constants to the `Response` class: `MIN_STATUS_CODE_VALUE` and
`MAX_STATUS_CODE_VALUE`.
### Changes
- [#240](https://github.com/zendframework/zend-diactoros/pull/240) changes the
behavior of `ServerRequestFactory::fromGlobals()` when no `$cookies` argument
is present. Previously, it would use `$_COOKIES`; now, if a `Cookie` header is
present, it will parse and use that to populate the instance instead.
This change allows utilizing cookies that contain period characters (`.`) in
their names (PHP's built-in cookie handling renames these to replace `.` with
`_`, which can lead to synchronization issues with clients).
- [#235](https://github.com/zendframework/zend-diactoros/pull/235) changes the
behavior of `Uri::__toString()` to better follow proscribed behavior in PSR-7.
In particular, prior to this release, if a scheme was missing but an authority
was present, the class was incorrectly returning a value that did not include
a `//` prefix. As of this release, it now does this correctly.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.3.11 - 2017-04-06
### Added
- Nothing.
### Changes
- [#241](https://github.com/zendframework/zend-diactoros/pull/241) changes the
constraint by which the package provides `psr/http-message-implementation` to
simply `1.0` instead of `~1.0.0`, to follow how other implementations provide
PSR-7.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#161](https://github.com/zendframework/zend-diactoros/pull/161) adds
additional validations to header names and values to ensure no malformed values
are provided.
- [#234](https://github.com/zendframework/zend-diactoros/pull/234) fixes a
number of reason phrases in the `Response` instance, and adds automation from
the canonical IANA sources to ensure any new phrases added are correct.
## 1.3.10 - 2017-01-23
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#226](https://github.com/zendframework/zend-diactoros/pull/226) fixed an
issue with the `SapiStreamEmitter` causing the response body to be cast
to `(string)` and also be read as a readable stream, potentially producing
double output.
## 1.3.9 - 2017-01-17
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#223](https://github.com/zendframework/zend-diactoros/issues/223)
[#224](https://github.com/zendframework/zend-diactoros/pull/224) fixed an issue
with the `SapiStreamEmitter` consuming too much memory when producing output
for readable bodies.
## 1.3.8 - 2017-01-05
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#222](https://github.com/zendframework/zend-diactoros/pull/222) fixes the
`SapiStreamEmitter`'s handling of the `Content-Range` header to properly only
emit a range of bytes if the header value is in the form `bytes {first-last}/length`.
This allows using other range units, such as `items`, without incorrectly
emitting truncated content.
## 1.3.7 - 2016-10-11
### Added
- [#208](https://github.com/zendframework/zend-diactoros/pull/208) adds several
missing response codes to `Zend\Diactoros\Response`, including:
- 226 ('IM used')
- 308 ('Permanent Redirect')
- 444 ('Connection Closed Without Response')
- 499 ('Client Closed Request')
- 510 ('Not Extended')
- 599 ('Network Connect Timeout Error')
- [#211](https://github.com/zendframework/zend-diactoros/pull/211) adds support
for UTF-8 characters in query strings handled by `Zend\Diactoros\Uri`.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.3.6 - 2016-09-07
### Added
- [#170](https://github.com/zendframework/zend-diactoros/pull/170) prepared
documentation for publication at https://zendframework.github.io/zend-diactoros/
- [#165](https://github.com/zendframework/zend-diactoros/pull/165) adds support
for Apache `REDIRECT_HTTP_*` header detection in the `ServerRequestFactory`.
- [#166](https://github.com/zendframework/zend-diactoros/pull/166) adds support
for UTF-8 characters in URI paths.
- [#204](https://github.com/zendframework/zend-diactoros/pull/204) adds testing
against PHP 7.1 release-candidate builds.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#186](https://github.com/zendframework/zend-diactoros/pull/186) fixes a typo
in a variable name within the `SapiStreamEmitter`.
- [#200](https://github.com/zendframework/zend-diactoros/pull/200) updates the
`SapiStreamEmitter` to implement a check for `isSeekable()` prior to attempts
to rewind; this allows it to work with non-seekable streams such as the
`CallbackStream`.
- [#169](https://github.com/zendframework/zend-diactoros/pull/169) ensures that
response serialization always provides a `\r\n\r\n` sequence following the
headers, even when no message body is present, to ensure it conforms with RFC
7230.
- [#175](https://github.com/zendframework/zend-diactoros/pull/175) updates the
`Request` class to set the `Host` header from the URI host if no header is
already present. (Ensures conformity with PSR-7 specification.)
- [#197](https://github.com/zendframework/zend-diactoros/pull/197) updates the
`Uri` class to ensure that string serialization does not include a colon after
the host name if no port is present in the instance.
## 1.3.5 - 2016-03-17
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#160](https://github.com/zendframework/zend-diactoros/pull/160) fixes HTTP
protocol detection in the `ServerRequestFactory` to work correctly with HTTP/2.
## 1.3.4 - 2016-03-17
### Added
- [#119](https://github.com/zendframework/zend-diactoros/pull/119) adds the 451
(Unavailable for Legal Reasons) status code to the `Response` class.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#117](https://github.com/zendframework/zend-diactoros/pull/117) provides
validation of the HTTP protocol version.
- [#127](https://github.com/zendframework/zend-diactoros/pull/127) now properly
removes attributes with `null` values when calling `withoutAttribute()`.
- [#132](https://github.com/zendframework/zend-diactoros/pull/132) updates the
`ServerRequestFactory` to marshal the request path fragment, if present.
- [#142](https://github.com/zendframework/zend-diactoros/pull/142) updates the
exceptions thrown by `HeaderSecurity` to include the header name and/or
value.
- [#148](https://github.com/zendframework/zend-diactoros/pull/148) fixes several
stream operations to ensure they raise exceptions when the internal pointer
is at an invalid position.
- [#151](https://github.com/zendframework/zend-diactoros/pull/151) ensures
URI fragments are properly encoded.
## 1.3.3 - 2016-01-04
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#135](https://github.com/zendframework/zend-diactoros/pull/135) fixes the
behavior of `ServerRequestFactory::marshalHeaders()` to no longer omit
`Cookie` headers from the aggregated headers. While the values are parsed and
injected into the cookie params, it's useful to have access to the raw headers
as well.
## 1.3.2 - 2015-12-22
### Added
- [#124](https://github.com/zendframework/zend-diactoros/pull/124) adds four
more optional arguments to the `ServerRequest` constructor:
- `array $cookies`
- `array $queryParams`
- `null|array|object $parsedBody`
- `string $protocolVersion`
`ServerRequestFactory` was updated to pass values for each of these parameters
when creating an instance, instead of using the related `with*()` methods on
an instance.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#122](https://github.com/zendframework/zend-diactoros/pull/122) updates the
`ServerRequestFactory` to retrieve the HTTP protocol version and inject it in
the generated `ServerRequest`, which previously was not performed.
## 1.3.1 - 2015-12-16
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#113](https://github.com/zendframework/zend-diactoros/pull/113) fixes an
issue in the response serializer, ensuring that the status code in the
deserialized response is an integer.
- [#115](https://github.com/zendframework/zend-diactoros/pull/115) fixes an
issue in the various text-basd response types (`TextResponse`, `HtmlResponse`,
and `JsonResponse`); due to the fact that the constructor was not
rewinding the message body stream, `getContents()` was thus returning `null`,
as the pointer was at the end of the stream. The constructor now rewinds the
stream after populating it in the constructor.
## 1.3.0 - 2015-12-15
### Added
- [#110](https://github.com/zendframework/zend-diactoros/pull/110) adds
`Zend\Diactoros\Response\SapiEmitterTrait`, which provides the following
private method definitions:
- `injectContentLength()`
- `emitStatusLine()`
- `emitHeaders()`
- `flush()`
- `filterHeader()`
The `SapiEmitter` implementation has been updated to remove those methods and
instead compose the trait.
- [#111](https://github.com/zendframework/zend-diactoros/pull/111) adds
a new emitter implementation, `SapiStreamEmitter`; this emitter type will
loop through the stream instead of emitting it in one go, and supports content
ranges.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.2.1 - 2015-12-15
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#101](https://github.com/zendframework/zend-diactoros/pull/101) fixes the
`withHeader()` implementation to ensure that if the header existed previously
but using a different casing strategy, the previous version will be removed
in the cloned instance.
- [#103](https://github.com/zendframework/zend-diactoros/pull/103) fixes the
constructor of `Response` to ensure that null status codes are not possible.
- [#99](https://github.com/zendframework/zend-diactoros/pull/99) fixes
validation of header values submitted via request and response constructors as
follows:
- numeric (integer and float) values are now properly allowed (this solves
some reported issues with setting Content-Length headers)
- invalid header names (non-string values or empty strings) now raise an
exception.
- invalid individual header values (non-string, non-numeric) now raise an
exception.
## 1.2.0 - 2015-11-24
### Added
- [#88](https://github.com/zendframework/zend-diactoros/pull/88) updates the
`SapiEmitter` to emit a `Content-Length` header with the content length as
reported by the response body stream, assuming that
`StreamInterface::getSize()` returns an integer.
- [#77](https://github.com/zendframework/zend-diactoros/pull/77) adds a new
response type, `Zend\Diactoros\Response\TextResponse`, for returning plain
text responses. By default, it sets the content type to `text/plain;
charset=utf-8`; per the other response types, the signature is `new
TextResponse($text, $status = 200, array $headers = [])`.
- [#90](https://github.com/zendframework/zend-diactoros/pull/90) adds a new
`Zend\Diactoros\CallbackStream`, allowing you to back a stream with a PHP
callable (such as a generator) to generate the message content. Its
constructor accepts the callable: `$stream = new CallbackStream($callable);`
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#77](https://github.com/zendframework/zend-diactoros/pull/77) updates the
`HtmlResponse` to set the charset to utf-8 by default (if no content type
header is provided at instantiation).
## 1.1.4 - 2015-10-16
### Added
- [#98](https://github.com/zendframework/zend-diactoros/pull/98) adds
`JSON_UNESCAPED_SLASHES` to the default `json_encode` flags used by
`Zend\Diactoros\Response\JsonResponse`.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#96](https://github.com/zendframework/zend-diactoros/pull/96) updates
`withPort()` to allow `null` port values (indicating usage of default for
the given scheme).
- [#91](https://github.com/zendframework/zend-diactoros/pull/91) fixes the
logic of `withUri()` to do a case-insensitive check for an existing `Host`
header, replacing it with the new one.
## 1.1.3 - 2015-08-10
### Added
- Nothing.
- [#73](https://github.com/zendframework/zend-diactoros/pull/73) adds caching of
the vendor directory to the Travis-CI configuration, to speed up builds.
### Deprecated
@ -181,7 +634,7 @@ immediately.
- [#57](https://github.com/zendframework/zend-diactoros/pull/57) fixes the
behavior of how the `ServerRequestFactory` marshals upload files when they are
represented as a nested associative array.
- [#49](https://github.com/zendframework/zend-diactoros/pull/49) provides several
- [#49](https://github.com/zendframework/zend-diactoros/pull/49) provides several
fixes that ensure that Diactoros complies with the PSR-7 specification:
- `MessageInterface::getHeaderLine()` MUST return a string (that string CAN be
empty). Previously, Diactoros would return `null`.

View file

@ -0,0 +1,43 @@
# Contributor Code of Conduct
The Zend Framework project adheres to [The Code Manifesto](http://codemanifesto.com)
as its guidelines for contributor interactions.
## The Code Manifesto
We want to work in an ecosystem that empowers developers to reach their
potential — one that encourages growth and effective collaboration. A space that
is safe for all.
A space such as this benefits everyone that participates in it. It encourages
new developers to enter our field. It is through discussion and collaboration
that we grow, and through growth that we improve.
In the effort to create such a place, we hold to these values:
1. **Discrimination limits us.** This includes discrimination on the basis of
race, gender, sexual orientation, gender identity, age, nationality, technology
and any other arbitrary exclusion of a group of people.
2. **Boundaries honor us.** Your comfort levels are not everyones comfort
levels. Remember that, and if brought to your attention, heed it.
3. **We are our biggest assets.** None of us were born masters of our trade.
Each of us has been helped along the way. Return that favor, when and where
you can.
4. **We are resources for the future.** As an extension of #3, share what you
know. Make yourself a resource to help those that come after you.
5. **Respect defines us.** Treat others as you wish to be treated. Make your
discussions, criticisms and debates from a position of respectfulness. Ask
yourself, is it true? Is it necessary? Is it constructive? Anything less is
unacceptable.
6. **Reactions require grace.** Angry responses are valid, but abusive language
and vindictive actions are toxic. When something happens that offends you,
handle it assertively, but be respectful. Escalate reasonably, and try to
allow the offender an opportunity to explain themselves, and possibly correct
the issue.
7. **Opinions are just that: opinions.** Each and every one of us, due to our
background and upbringing, have varying opinions. The fact of the matter, is
that is perfectly acceptable. Remember this: if you respect your own
opinions, you should respect the opinions of others.
8. **To err is human.** You might not intend it, but mistakes do happen and
contribute to build experience. Tolerate honest mistakes, and don't hesitate
to apologize if you make one yourself.

View file

@ -78,14 +78,14 @@ standards checks, and provides configuration for our selected checks.
To run checks only:
```console
$ ./vendor/bin/phpcs --standard=PSR2 src test
$ composer cs-check
```
`phpcs` also installs a tool named `phpcbf` which can attempt to fix problems
for you:
```console
$ ./vendor/bin/phpcbf --standard=PSR2 src test
$ composer cs-fix
```
If you allow phpcbf to fix CS issues, please re-run the tests to ensure
@ -102,7 +102,7 @@ pull your work into the master repository. We recommend using
3. Clone the canonical repository locally and enter it.
```console
$ git clone git://github.com:zendframework/zend-diactoros.git
$ git clone git://github.com/zendframework/zend-diactoros.git
$ cd zend-diactoros
```
@ -221,3 +221,8 @@ repository, we suggest doing some cleanup of these branches.
```console
$ git push {username} :<branchname>
```
## Conduct
Please see our [CONDUCT.md](CONDUCT.md) to understand expected behavior when interacting with others in the project.

View file

@ -1,4 +1,4 @@
Copyright (c) 2015, Zend Technologies USA, Inc.
Copyright (c) 2015-2016, Zend Technologies USA, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View file

@ -18,17 +18,11 @@ This package supercedes and replaces [phly/http](https://github.com/phly/http).
## Documentation
Documentation is [in the doc tree](doc/), and can be compiled using [bookdown](http://bookdown.io):
Documentation is available at:
```console
$ bookdown doc/bookdown.json
$ php -S 0.0.0.0:8080 -t doc/html/ # then browse to http://localhost:8080/
```
- https://zendframework.github.io/zend-diactoros/
> ### Bookdown
>
> You can install bookdown globally using `composer global require bookdown/bookdown`. If you do
> this, make sure that `$HOME/.composer/vendor/bin` is on your `$PATH`.
Source files for documentation are [in the doc/ tree](doc/).
[Master]: https://travis-ci.org/zendframework/zend-diactoros
[Master image]: https://secure.travis-ci.org/zendframework/zend-diactoros.svg?branch=master

View file

@ -15,20 +15,22 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev",
"dev-develop": "1.1-dev"
"dev-master": "1.4-dev",
"dev-develop": "1.5-dev"
}
},
"require": {
"php": ">=5.4",
"php": "^5.4 || ^7.0",
"psr/http-message": "~1.0"
},
"require-dev": {
"phpunit/PHPUnit": "~4.6",
"squizlabs/php_codesniffer": "^2.3.1"
"phpunit/phpunit": "^4.6 || ^5.5",
"zendframework/zend-coding-standard": "~1.0.0",
"ext-dom": "*",
"ext-libxml": "*"
},
"provide": {
"psr/http-message-implementation": "~1.0.0"
"psr/http-message-implementation": "1.0"
},
"autoload": {
"psr-4": {
@ -43,5 +45,16 @@
"test/TestAsset/Functions.php",
"test/TestAsset/SapiResponse.php"
]
},
"scripts": {
"check": [
"@cs-check",
"@test"
],
"upload-coverage": "coveralls -v",
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
}
}

1494
web/vendor/zendframework/zend-diactoros/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
docs_dir: doc/book
site_dir: doc/html
pages:
- index.md
- Overview: overview.md
- Installation: install.md
- Usage: usage.md
- Reference:
- "Custom Responses": custom-responses.md
- "Emitting Responses": emitting-responses.md
- Serialization: serialization.md
- API: api.md
site_name: zend-diactoros
site_description: 'zend-diactoros: PSR-7 HTTP message implementation'
repo_url: 'https://github.com/zendframework/zend-diactoros'
copyright: 'Copyright (c) 2016 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'

View file

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<ruleset name="Zend Framework coding standard">
<description>Zend Framework coding standard</description>
<!-- display progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="false"/>
</properties>
</rule>
<!-- Paths to check -->
<file>src</file>
<file>test</file>
</ruleset>

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
);