Server IP : 213.176.29.180 / Your IP : 18.217.122.223 Web Server : Apache System : Linux 213.176.29.180.hostiran.name 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64 User : webtaragh ( 1001) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON Directory (0750) : /home/webtaragh/public_html/.tmb/../ |
[ Home ] | [ C0mmand ] | [ Upload File ] |
---|
zend-diactoros/CONTRIBUTING.md 0000644 00000016325 14736103054 0011734 0 ustar 00 # CONTRIBUTING ## RESOURCES If you wish to contribute to Zend Framework, please be sure to read/subscribe to the following resources: - [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards) - [Contributor's Guide](http://framework.zend.com/participate/contributor-guide) - ZF Contributor's mailing list: Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html Subscribe: zf-contributors-subscribe@lists.zend.com - ZF Contributor's IRC channel: #zftalk.dev on Freenode.net If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-diactoros/issues/new). ## Reporting Potential Security Issues If you have encountered a potential security vulnerability, please **DO NOT** report it on the public issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead. We will work with you to verify the vulnerability and patch it as soon as possible. When reporting issues, please provide the following information: - Component(s) affected - A description indicating how to reproduce the issue - A summary of the security vulnerability and impact We request that you contact us via the email address above and give the project contributors a chance to resolve the vulnerability and issue a new release prior to any public exposure; this helps protect users and provides them with a chance to upgrade and/or update in order to protect their applications. For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc). ## Documentation Documentation is in [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/), and rendered using [bookdown](http://bookdown.io). Please read and follow the [general documentation guidelines](https://github.com/zendframework/documentation/blob/master/CONTRIBUTING.md) when providing documentation. All new features **must** include documentation before they may be accepted and merged. ## RUNNING TESTS To run tests: - Clone the repository: ```console $ git clone git@github.com:zendframework/zend-diactoros.git $ cd ``` - Install dependencies via composer: ```console $ curl -sS https://getcomposer.org/installer | php -- $ ./composer.phar install ``` If you don't have `curl` installed, you can also download `composer.phar` from https://getcomposer.org/ - Run the tests via `phpunit` and the provided PHPUnit config, like in this example: ```console $ ./vendor/bin/phpunit ``` ## Running Coding Standards Checks This component uses [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) for coding standards checks, and provides configuration for our selected checks. `phpcs` is installed by default via Composer. To run checks only: ```console $ composer cs-check ``` `phpcs` also installs a tool named `phpcbf` which can attempt to fix problems for you: ```console $ composer cs-fix ``` If you allow phpcbf to fix CS issues, please re-run the tests to ensure they pass, and make sure you add and commit the changes after verification. ## Recommended Workflow for Contributions Your first step is to establish a public repository from which we can pull your work into the master repository. We recommend using [GitHub](https://github.com), as that is where the component is already hosted. 1. Setup a [GitHub account](http://github.com/), if you haven't yet 2. Fork the repository (http://github.com/zendframework/zend-diactoros) 3. Clone the canonical repository locally and enter it. ```console $ git clone git://github.com/zendframework/zend-diactoros.git $ cd zend-diactoros ``` 4. Add a remote to your fork; substitute your GitHub username in the command below. ```console $ git remote add {username} git@github.com:{username}/zend-diactoros.git $ git fetch {username} ``` ### Keeping Up-to-Date Periodically, you should update your fork or personal repository to match the canonical repository. Assuming you have setup your local repository per the instructions above, you can do the following: ```console $ git checkout master $ git fetch origin $ git rebase origin/master # OPTIONALLY, to keep your remote up-to-date - $ git push {username} master:master ``` If you're tracking other branches -- for example, the "develop" branch, where new feature development occurs -- you'll want to do the same operations for that branch; simply substitute "develop" for "master". ### Working on a patch We recommend you do each new feature or bugfix in a new branch. This simplifies the task of code review as well as the task of merging your changes into the canonical repository. A typical workflow will then consist of the following: 1. Create a new local branch based off either your master or develop branch. 2. Switch to your new local branch. (This step can be combined with the previous step with the use of `git checkout -b`.) 3. Do some work, commit, repeat as necessary. 4. Push the local branch to your remote repository. 5. Send a pull request. The mechanics of this process are actually quite trivial. Below, we will create a branch for fixing an issue in the tracker. ```console $ git checkout -b hotfix/9295 Switched to a new branch 'hotfix/9295' ``` ... do some work ... ```console $ git commit ``` ... write your log message ... ```console $ git push {username} hotfix/9295:hotfix/9295 Counting objects: 38, done. Delta compression using up to 2 threads. Compression objects: 100% (18/18), done. Writing objects: 100% (20/20), 8.19KiB, done. Total 20 (delta 12), reused 0 (delta 0) To ssh://git@github.com/{username}/zend-diactoros.git b5583aa..4f51698 HEAD -> master ``` To send a pull request, you have two options. If using GitHub, you can do the pull request from there. Navigate to your repository, select the branch you just created, and then select the "Pull Request" button in the upper right. Select the user/organization "zendframework" as the recipient. If using your own repository - or even if using GitHub - you can use `git format-patch` to create a patchset for us to apply; in fact, this is **recommended** for security-related patches. If you use `format-patch`, please send the patches as attachments to: - zf-devteam@zend.com for patches without security implications - zf-security@zend.com for security patches #### What branch to issue the pull request against? Which branch should you issue a pull request against? - For fixes against the stable release, issue the pull request against the "master" branch. - For new features, or fixes that introduce new elements to the public API (such as new public methods or properties), issue the pull request against the "develop" branch. ### Branch Cleanup As you might imagine, if you are a frequent contributor, you'll start to get a ton of branches both locally and on your remote. Once you know that your changes have been accepted to the master repository, we suggest doing some cleanup of these branches. - Local branch cleanup ```console $ git branch -d <branchname> ``` - Remote branch removal ```console $ git push {username} :<branchname> ``` ## Conduct Please see our [CONDUCT.md](CONDUCT.md) to understand expected behavior when interacting with others in the project. zend-diactoros/phpcs.xml 0000644 00000000343 14736103054 0011333 0 ustar 00 <?xml version="1.0"?> <ruleset name="Zend Framework coding standard"> <rule ref="./vendor/zendframework/zend-coding-standard/ruleset.xml"/> <!-- Paths to check --> <file>src</file> <file>test</file> </ruleset> zend-diactoros/CONDUCT.md 0000644 00000004512 14736103054 0011117 0 ustar 00 # 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 everyone’s 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. zend-diactoros/mkdocs.yml 0000644 00000001100 14736103054 0011467 0 ustar 00 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>' zend-diactoros/src/Response/SapiEmitterTrait.php 0000644 00000006252 14736103054 0016031 0 ustar 00 <?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); } } zend-diactoros/src/Response/Serializer.php 0000644 00000006665 14736103054 0014720 0 ustar 00 <?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\ResponseInterface; use Psr\Http\Message\StreamInterface; use UnexpectedValueException; use Zend\Diactoros\AbstractSerializer; use Zend\Diactoros\Response; use Zend\Diactoros\Stream; final class Serializer extends AbstractSerializer { /** * Deserialize a response string to a response instance. * * @param string $message * @return Response * @throws UnexpectedValueException when errors occur parsing the message. */ public static function fromString($message) { $stream = new Stream('php://temp', 'wb+'); $stream->write($message); return static::fromStream($stream); } /** * Parse a response from a stream. * * @param StreamInterface $stream * @return ResponseInterface * @throws InvalidArgumentException when the stream is not readable. * @throws UnexpectedValueException when errors occur parsing the message. */ public static function fromStream(StreamInterface $stream) { if (! $stream->isReadable() || ! $stream->isSeekable()) { throw new InvalidArgumentException('Message stream must be both readable and seekable'); } $stream->rewind(); list($version, $status, $reasonPhrase) = self::getStatusLine($stream); list($headers, $body) = self::splitStream($stream); return (new Response($body, $status, $headers)) ->withProtocolVersion($version) ->withStatus((int) $status, $reasonPhrase); } /** * Create a string representation of a response. * * @param ResponseInterface $response * @return string */ public static function toString(ResponseInterface $response) { $reasonPhrase = $response->getReasonPhrase(); $headers = self::serializeHeaders($response->getHeaders()); $body = (string) $response->getBody(); $format = 'HTTP/%s %d%s%s%s'; if (! empty($headers)) { $headers = "\r\n" . $headers; } $headers .= "\r\n\r\n"; return sprintf( $format, $response->getProtocolVersion(), $response->getStatusCode(), ($reasonPhrase ? ' ' . $reasonPhrase : ''), $headers, $body ); } /** * Retrieve the status line for the message. * * @param StreamInterface $stream * @return array Array with three elements: 0 => version, 1 => status, 2 => reason * @throws UnexpectedValueException if line is malformed */ private static function getStatusLine(StreamInterface $stream) { $line = self::getLine($stream); if (! preg_match( '#^HTTP/(?P<version>[1-9]\d*\.\d) (?P<status>[1-5]\d{2})(\s+(?P<reason>.+))?$#', $line, $matches )) { throw new UnexpectedValueException('No status line detected'); } return [$matches['version'], $matches['status'], isset($matches['reason']) ? $matches['reason'] : '']; } } zend-diactoros/src/Response/TextResponse.php 0000644 00000004333 14736103054 0015240 0 ustar 00 <?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; } } zend-diactoros/src/Response/EmptyResponse.php 0000644 00000002263 14736103054 0015412 0 ustar 00 <?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 Zend\Diactoros\Response; use Zend\Diactoros\Stream; /** * A class representing empty HTTP responses. */ class EmptyResponse extends Response { /** * Create an empty response with the given status code. * * @param int $status Status code for the response, if any. * @param array $headers Headers for the response, if any. */ public function __construct($status = 204, array $headers = []) { $body = new Stream('php://temp', 'r'); parent::__construct($body, $status, $headers); } /** * Create an empty response with the given headers. * * @param array $headers Headers for the response. * @return EmptyResponse */ public static function withHeaders(array $headers) { return new static(204, $headers); } } zend-diactoros/src/Response/SapiEmitter.php 0000644 00000002720 14736103054 0015021 0 ustar 00 <?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; class SapiEmitter 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 null|int $maxBufferLevel Maximum output buffering level to unwrap. */ public function emit(ResponseInterface $response, $maxBufferLevel = null) { 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($maxBufferLevel); $this->emitBody($response); } /** * Emit the message body. * * @param ResponseInterface $response */ private function emitBody(ResponseInterface $response) { echo $response->getBody(); } } zend-diactoros/src/Response/HtmlResponse.php 0000644 00000004322 14736103054 0015216 0 ustar 00 <?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; /** * HTML response. * * Allows creating a response by passing an HTML string to the constructor; * by default, sets a status code of 200 and sets the Content-Type header to * text/html. */ class HtmlResponse extends Response { use InjectContentTypeTrait; /** * Create an HTML response. * * Produces an HTML response with a Content-Type of text/html and a default * status of 200. * * @param string|StreamInterface $html HTML 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 $html is neither a string or stream. */ public function __construct($html, $status = 200, array $headers = []) { parent::__construct( $this->createBody($html), $status, $this->injectContentType('text/html; charset=utf-8', $headers) ); } /** * Create the message body. * * @param string|StreamInterface $html * @return StreamInterface * @throws InvalidArgumentException if $html is neither a string or stream. */ private function createBody($html) { if ($html instanceof StreamInterface) { return $html; } if (! is_string($html)) { throw new InvalidArgumentException(sprintf( 'Invalid content (%s) provided to %s', (is_object($html) ? get_class($html) : gettype($html)), __CLASS__ )); } $body = new Stream('php://temp', 'wb+'); $body->write($html); $body->rewind(); return $body; } } zend-diactoros/src/Response/SapiStreamEmitter.php 0000644 00000007354 14736103054 0016205 0 ustar 00 <?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; } } zend-diactoros/src/Response/RedirectResponse.php 0000644 00000003056 14736103054 0016056 0 ustar 00 <?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\UriInterface; use Zend\Diactoros\Response; /** * Produce a redirect response. */ class RedirectResponse extends Response { /** * Create a redirect response. * * Produces a redirect response with a Location header and the given status * (302 by default). * * Note: this method overwrites the `location` $headers value. * * @param string|UriInterface $uri URI for the Location header. * @param int $status Integer status code for the redirect; 302 by default. * @param array $headers Array of headers to use at initialization. */ public function __construct($uri, $status = 302, array $headers = []) { if (! is_string($uri) && ! $uri instanceof UriInterface) { throw new InvalidArgumentException(sprintf( 'Uri provided to %s MUST be a string or Psr\Http\Message\UriInterface instance; received "%s"', __CLASS__, (is_object($uri) ? get_class($uri) : gettype($uri)) )); } $headers['location'] = [(string) $uri]; parent::__construct('php://temp', $status, $headers); } } zend-diactoros/src/Response/InjectContentTypeTrait.php 0000644 00000001764 14736103054 0017217 0 ustar 00 <?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; trait InjectContentTypeTrait { /** * Inject the provided Content-Type, if none is already present. * * @param string $contentType * @param array $headers * @return array Headers with injected Content-Type */ private function injectContentType($contentType, array $headers) { $hasContentType = array_reduce(array_keys($headers), function ($carry, $item) { return $carry ?: (strtolower($item) === 'content-type'); }, false); if (! $hasContentType) { $headers['content-type'] = [$contentType]; } return $headers; } } zend-diactoros/src/Response/JsonResponse.php 0000644 00000005651 14736103054 0015231 0 ustar 00 <?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 Zend\Diactoros\Response; use Zend\Diactoros\Stream; /** * JSON response. * * Allows creating a response by passing data to the constructor; by default, * serializes the data to JSON, sets a status code of 200 and sets the * Content-Type header to application/json. */ 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. * * Default JSON encoding is performed with the following options, which * produces RFC4627-compliant JSON, capable of embedding into HTML. * * - JSON_HEX_TAG * - 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. * @param array $headers Array of headers to use at initialization. * @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 = self::DEFAULT_JSON_FLAGS ) { $body = new Stream('php://temp', 'wb+'); $body->write($this->jsonEncode($data, $encodingOptions)); $body->rewind(); $headers = $this->injectContentType('application/json', $headers); parent::__construct($body, $status, $headers); } /** * Encode the provided data to JSON. * * @param mixed $data * @param int $encodingOptions * @return string * @throws InvalidArgumentException if unable to encode the $data to JSON. */ private function jsonEncode($data, $encodingOptions) { if (is_resource($data)) { throw new InvalidArgumentException('Cannot JSON encode resources'); } // Clear json_last_error() json_encode(null); $json = json_encode($data, $encodingOptions); if (JSON_ERROR_NONE !== json_last_error()) { throw new InvalidArgumentException(sprintf( 'Unable to encode data to JSON in %s: %s', __CLASS__, json_last_error_msg() )); } return $json; } } zend-diactoros/src/Response/EmitterInterface.php 0000644 00000002024 14736103054 0016022 0 ustar 00 <?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; interface EmitterInterface { /** * Emit a response. * * Emits a response, including status line, headers, and the message body, * according to the environment. * * Implementations of this method may be written in such a way as to have * side effects, such as usage of header() or pushing output to the * output buffer. * * Implementations MAY raise exceptions if they are unable to emit the * response; e.g., if headers have already been sent. * * @param ResponseInterface $response */ public function emit(ResponseInterface $response); } zend-diactoros/src/AbstractSerializer.php 0000644 00000011056 14736103054 0014574 0 ustar 00 <?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 Psr\Http\Message\StreamInterface; use UnexpectedValueException; /** * Provides base functionality for request and response de/serialization * strategies, including functionality for retrieving a line at a time from * the message, splitting headers from the body, and serializing headers. */ abstract class AbstractSerializer { const CR = "\r"; const EOL = "\r\n"; const LF = "\n"; /** * Retrieve a single line from the stream. * * Retrieves a line from the stream; a line is defined as a sequence of * characters ending in a CRLF sequence. * * @param StreamInterface $stream * @return string * @throws UnexpectedValueException if the sequence contains a CR or LF in * isolation, or ends in a CR. */ protected static function getLine(StreamInterface $stream) { $line = ''; $crFound = false; while (! $stream->eof()) { $char = $stream->read(1); if ($crFound && $char === self::LF) { $crFound = false; break; } // CR NOT followed by LF if ($crFound && $char !== self::LF) { throw new UnexpectedValueException('Unexpected carriage return detected'); } // LF in isolation if (! $crFound && $char === self::LF) { throw new UnexpectedValueException('Unexpected line feed detected'); } // CR found; do not append if ($char === self::CR) { $crFound = true; continue; } // Any other character: append $line .= $char; } // CR found at end of stream if ($crFound) { throw new UnexpectedValueException("Unexpected end of headers"); } return $line; } /** * Split the stream into headers and body content. * * Returns an array containing two elements * * - The first is an array of headers * - The second is a StreamInterface containing the body content * * @param StreamInterface $stream * @return array * @throws UnexpectedValueException For invalid headers. */ protected static function splitStream(StreamInterface $stream) { $headers = []; $currentHeader = false; while ($line = self::getLine($stream)) { if (preg_match(';^(?P<name>[!#$%&\'*+.^_`\|~0-9a-zA-Z-]+):(?P<value>.*)$;', $line, $matches)) { $currentHeader = $matches['name']; if (! isset($headers[$currentHeader])) { $headers[$currentHeader] = []; } $headers[$currentHeader][] = ltrim($matches['value']); continue; } if (! $currentHeader) { throw new UnexpectedValueException('Invalid header detected'); } if (! preg_match('#^[ \t]#', $line)) { throw new UnexpectedValueException('Invalid header continuation'); } // Append continuation to last header value found $value = array_pop($headers[$currentHeader]); $headers[$currentHeader][] = $value . ltrim($line); } // use RelativeStream to avoid copying initial stream into memory return [$headers, new RelativeStream($stream, $stream->tell())]; } /** * Serialize headers to string values. * * @param array $headers * @return string */ protected static function serializeHeaders(array $headers) { $lines = []; foreach ($headers as $header => $values) { $normalized = self::filterHeader($header); foreach ($values as $value) { $lines[] = sprintf('%s: %s', $normalized, $value); } } return implode("\r\n", $lines); } /** * Filter a header name to wordcase * * @param string $header * @return string */ protected static function filterHeader($header) { $filtered = str_replace('-', ' ', $header); $filtered = ucwords($filtered); return str_replace(' ', '-', $filtered); } } zend-diactoros/src/HeaderSecurity.php 0000644 00000011042 14736103054 0013712 0 ustar 00 <?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; /** * Provide security tools around HTTP headers to prevent common injection vectors. * * Code is largely lifted from the Zend\Http\Header\HeaderValue implementation in * Zend Framework, released with the copyright and license below. * * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ final class HeaderSecurity { /** * Private constructor; non-instantiable. * @codeCoverageIgnore */ private function __construct() { } /** * Filter a header value * * Ensures CRLF header injection vectors are filtered. * * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal * tabs are allowed in values; header continuations MUST consist of * a single CRLF sequence followed by a space or horizontal tab. * * This method filters any values not allowed from the string, and is * lossy. * * @see http://en.wikipedia.org/wiki/HTTP_response_splitting * @param string $value * @return string */ public static function filter($value) { $value = (string) $value; $length = strlen($value); $string = ''; for ($i = 0; $i < $length; $i += 1) { $ascii = ord($value[$i]); // Detect continuation sequences if ($ascii === 13) { $lf = ord($value[$i + 1]); $ws = ord($value[$i + 2]); if ($lf === 10 && in_array($ws, [9, 32], true)) { $string .= $value[$i] . $value[$i + 1]; $i += 1; } continue; } // Non-visible, non-whitespace characters // 9 === horizontal tab // 32-126, 128-254 === visible // 127 === DEL // 255 === null byte if (($ascii < 32 && $ascii !== 9) || $ascii === 127 || $ascii > 254 ) { continue; } $string .= $value[$i]; } return $string; } /** * Validate a header value. * * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal * tabs are allowed in values; header continuations MUST consist of * a single CRLF sequence followed by a space or horizontal tab. * * @see http://en.wikipedia.org/wiki/HTTP_response_splitting * @param string $value * @return bool */ public static function isValid($value) { $value = (string) $value; // Look for: // \n not preceded by \r, OR // \r not followed by \n, OR // \r\n not followed by space or horizontal tab; these are all CRLF attacks if (preg_match("#(?:(?:(?<!\r)\n)|(?:\r(?!\n))|(?:\r\n(?![ \t])))#", $value)) { return false; } // Non-visible, non-whitespace characters // 9 === horizontal tab // 10 === line feed // 13 === carriage return // 32-126, 128-254 === visible // 127 === DEL (disallowed) // 255 === null byte (disallowed) if (preg_match('/[^\x09\x0a\x0d\x20-\x7E\x80-\xFE]/', $value)) { return false; } return true; } /** * Assert a header value is valid. * * @param string $value * @throws InvalidArgumentException for invalid values */ public static function assertValid($value) { if (! self::isValid($value)) { throw new InvalidArgumentException(sprintf( '"%s" is not valid header value', $value )); } } /** * Assert whether or not a header name is valid. * * @see http://tools.ietf.org/html/rfc7230#section-3.2 * @param mixed $name * @throws InvalidArgumentException */ public static function assertValidName($name) { if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) { throw new InvalidArgumentException(sprintf( '"%s" is not valid header name', $name )); } } } zend-diactoros/src/RequestTrait.php 0000644 00000024601 14736103054 0013433 0 ustar 00 <?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 Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; /** * Trait with common request behaviors. * * Server and client-side requests differ slightly in how the Host header is * handled; on client-side, it should be calculated on-the-fly from the * composed URI (if present), while on server-side, it will be calculated from * 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 { /** * @var string */ private $method = ''; /** * The request-target, if it has been provided or calculated. * * @var null|string */ private $requestTarget; /** * @var UriInterface */ private $uri; /** * Initialize request state. * * Used by constructors. * * @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. * @throws InvalidArgumentException for any invalid value. */ private function initialize($uri = null, $method = null, $body = 'php://memory', array $headers = []) { $this->validateMethod($method); $this->method = $method ?: ''; $this->uri = $this->createUri($uri); $this->stream = $this->getStream($body, 'wb+'); list($this->headerNames, $headers) = $this->filterHeaders($headers); $this->assertHeaders($headers); $this->headers = $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' ); } /** * Retrieves the message's request target. * * Retrieves the message's request-target either as it will appear (for * clients), as it appeared at request (for servers), or as it was * specified for the instance (see withRequestTarget()). * * In most cases, this will be the origin-form of the composed URI, * unless a value was provided to the concrete implementation (see * withRequestTarget() below). * * If no URI is available, and no request-target has been specifically * provided, this method MUST return the string "/". * * @return string */ public function getRequestTarget() { if (null !== $this->requestTarget) { return $this->requestTarget; } $target = $this->uri->getPath(); if ($this->uri->getQuery()) { $target .= '?' . $this->uri->getQuery(); } if (empty($target)) { $target = '/'; } return $target; } /** * Create a new instance with a specific request-target. * * If the request needs a non-origin-form request-target — e.g., for * specifying an absolute-form, authority-form, or asterisk-form — * this method may be used to create an instance with the specified * request-target, verbatim. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * changed request target. * * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various * request-target forms allowed in request messages) * @param mixed $requestTarget * @return static * @throws InvalidArgumentException if the request target is invalid. */ public function withRequestTarget($requestTarget) { if (preg_match('#\s#', $requestTarget)) { throw new InvalidArgumentException( 'Invalid request target provided; cannot contain whitespace' ); } $new = clone $this; $new->requestTarget = $requestTarget; return $new; } /** * Retrieves the HTTP method of the request. * * @return string Returns the request method. */ public function getMethod() { return $this->method; } /** * Return an instance with the provided HTTP method. * * While HTTP method names are typically all uppercase characters, HTTP * method names are case-sensitive and thus implementations SHOULD NOT * modify the given string. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * changed request method. * * @param string $method Case-insensitive method. * @return static * @throws InvalidArgumentException for invalid HTTP methods. */ public function withMethod($method) { $this->validateMethod($method); $new = clone $this; $new->method = $method; return $new; } /** * Retrieves the URI instance. * * This method MUST return a UriInterface instance. * * @link http://tools.ietf.org/html/rfc3986#section-4.3 * @return UriInterface Returns a UriInterface instance * representing the URI of the request, if any. */ public function getUri() { return $this->uri; } /** * Returns an instance with the provided URI. * * This method will update the Host header of the returned request by * default if the URI contains a host component. If the URI does not * contain a host component, any pre-existing Host header will be carried * over to the returned request. * * You can opt-in to preserving the original state of the Host header by * setting `$preserveHost` to `true`. When `$preserveHost` is set to * `true`, the returned request will not update the Host header of the * returned message -- even if the message contains no Host header. This * means that a call to `getHeader('Host')` on the original request MUST * equal the return value of a call to `getHeader('Host')` on the returned * request. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new UriInterface instance. * * @link http://tools.ietf.org/html/rfc3986#section-4.3 * @param UriInterface $uri New request URI to use. * @param bool $preserveHost Preserve the original state of the Host header. * @return static */ public function withUri(UriInterface $uri, $preserveHost = false) { $new = clone $this; $new->uri = $uri; if ($preserveHost && $this->hasHeader('Host')) { return $new; } if (! $uri->getHost()) { return $new; } $host = $uri->getHost(); if ($uri->getPort()) { $host .= ':' . $uri->getPort(); } $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; } /** * Validate the HTTP method * * @param null|string $method * @throws InvalidArgumentException on invalid HTTP method. */ private function validateMethod($method) { if (null === $method) { return; } if (! is_string($method)) { throw new InvalidArgumentException(sprintf( 'Unsupported HTTP method; must be a string, received %s', (is_object($method) ? get_class($method) : gettype($method)) )); } if (! preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) { throw new InvalidArgumentException(sprintf( 'Unsupported HTTP method "%s" provided', $method )); } } /** * Retrieve the host from the URI instance * * @return string */ private function getHostFromUri() { $host = $this->uri->getHost(); $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'); } } } zend-diactoros/src/Server.php 0000644 00000012102 14736103054 0012236 0 ustar 00 <?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 OutOfBoundsException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; /** * "Serve" incoming HTTP requests * * Given a callback, takes an incoming request, dispatches it to the * callback, and then sends a response. */ class Server { /** * @var callable */ private $callback; /** * Response emitter to use; by default, uses Response\SapiEmitter. * * @var Response\EmitterInterface */ private $emitter; /** * @var ServerRequestInterface */ private $request; /** * @var ResponseInterface */ private $response; /** * Constructor * * Given a callback, a request, and a response, we can create a server. * * @param callable $callback * @param ServerRequestInterface $request * @param ResponseInterface $response */ public function __construct( callable $callback, ServerRequestInterface $request, ResponseInterface $response ) { $this->callback = $callback; $this->request = $request; $this->response = $response; } /** * Allow retrieving the request, response and callback as properties * * @param string $name * @return mixed * @throws OutOfBoundsException for invalid properties */ public function __get($name) { if (! property_exists($this, $name)) { throw new OutOfBoundsException('Cannot retrieve arbitrary properties from server'); } return $this->{$name}; } /** * Set alternate response emitter to use. * * @param Response\EmitterInterface $emitter */ public function setEmitter(Response\EmitterInterface $emitter) { $this->emitter = $emitter; } /** * Create a Server instance * * Creates a server instance from the callback and the following * PHP environmental values: * * - server; typically this will be the $_SERVER superglobal * - query; typically this will be the $_GET superglobal * - body; typically this will be the $_POST superglobal * - cookies; typically this will be the $_COOKIE superglobal * - files; typically this will be the $_FILES superglobal * * @param callable $callback * @param array $server * @param array $query * @param array $body * @param array $cookies * @param array $files * @return static */ public static function createServer( callable $callback, array $server, array $query, array $body, array $cookies, array $files ) { $request = ServerRequestFactory::fromGlobals($server, $query, $body, $cookies, $files); $response = new Response(); return new static($callback, $request, $response); } /** * Create a Server instance from an existing request object * * Provided a callback, an existing request object, and optionally an * existing response object, create and return the Server instance. * * If no Response object is provided, one will be created. * * @param callable $callback * @param ServerRequestInterface $request * @param null|ResponseInterface $response * @return static */ public static function createServerFromRequest( callable $callback, ServerRequestInterface $request, ResponseInterface $response = null ) { if (! $response) { $response = new Response(); } return new static($callback, $request, $response); } /** * "Listen" to an incoming request * * If provided a $finalHandler, that callable will be used for * incomplete requests. * * Output buffering is enabled prior to invoking the attached * callback; any output buffered will be sent prior to any * response body content. * * @param null|callable $finalHandler */ public function listen(callable $finalHandler = null) { $callback = $this->callback; ob_start(); $bufferLevel = ob_get_level(); $response = $callback($this->request, $this->response, $finalHandler); if (! $response instanceof ResponseInterface) { $response = $this->response; } $this->getEmitter()->emit($response, $bufferLevel); } /** * Retrieve the current response emitter. * * If none has been registered, lazy-loads a Response\SapiEmitter. * * @return Response\EmitterInterface */ private function getEmitter() { if (! $this->emitter) { $this->emitter = new Response\SapiEmitter(); } return $this->emitter; } } zend-diactoros/src/UploadedFile.php 0000644 00000015270 14736103054 0013336 0 ustar 00 <?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 Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use RuntimeException; class UploadedFile implements UploadedFileInterface { /** * @var string */ private $clientFilename; /** * @var string */ private $clientMediaType; /** * @var int */ private $error; /** * @var null|string */ private $file; /** * @var bool */ private $moved = false; /** * @var int */ private $size; /** * @var null|StreamInterface */ 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) { if (is_string($streamOrFile)) { $this->file = $streamOrFile; } if (is_resource($streamOrFile)) { $this->stream = new Stream($streamOrFile); } if (! $this->file && ! $this->stream) { if (! $streamOrFile instanceof StreamInterface) { throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile'); } $this->stream = $streamOrFile; } } if (! is_int($size)) { throw new InvalidArgumentException('Invalid size provided for UploadedFile; must be an int'); } $this->size = $size; if (! is_int($errorStatus) || 0 > $errorStatus || 8 < $errorStatus ) { throw new InvalidArgumentException( 'Invalid error status for UploadedFile; must be an UPLOAD_ERR_* constant' ); } $this->error = $errorStatus; if (null !== $clientFilename && ! is_string($clientFilename)) { throw new InvalidArgumentException( 'Invalid client filename provided for UploadedFile; must be null or a string' ); } $this->clientFilename = $clientFilename; if (null !== $clientMediaType && ! is_string($clientMediaType)) { throw new InvalidArgumentException( 'Invalid client media type provided for UploadedFile; must be null or a string' ); } $this->clientMediaType = $clientMediaType; } /** * {@inheritdoc} * @throws \RuntimeException if the upload was not successful. */ public function getStream() { if ($this->error !== UPLOAD_ERR_OK) { throw new RuntimeException('Cannot retrieve stream due to upload error'); } if ($this->moved) { throw new RuntimeException('Cannot retrieve stream after it has already been moved'); } if ($this->stream instanceof StreamInterface) { return $this->stream; } $this->stream = new Stream($this->file); return $this->stream; } /** * {@inheritdoc} * * @see http://php.net/is_uploaded_file * @see http://php.net/move_uploaded_file * @param string $targetPath Path to which to move the uploaded file. * @throws \RuntimeException if the upload was not successful. * @throws \InvalidArgumentException if the $path specified is invalid. * @throws \RuntimeException on any error during the move operation, or on * the second or subsequent call to the method. */ 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) || empty($targetPath)) { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); } $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; switch (true) { case (empty($sapi) || 0 === strpos($sapi, 'cli') || ! $this->file): // Non-SAPI environment, or no filename present $this->writeFile($targetPath); break; default: // SAPI environment, with file present if (false === move_uploaded_file($this->file, $targetPath)) { throw new RuntimeException('Error occurred while moving uploaded file'); } break; } $this->moved = true; } /** * {@inheritdoc} * * @return int|null The file size in bytes or null if unknown. */ public function getSize() { return $this->size; } /** * {@inheritdoc} * * @see http://php.net/manual/en/features.file-upload.errors.php * @return int One of PHP's UPLOAD_ERR_XXX constants. */ public function getError() { return $this->error; } /** * {@inheritdoc} * * @return string|null The filename sent by the client or null if none * was provided. */ public function getClientFilename() { return $this->clientFilename; } /** * {@inheritdoc} */ public function getClientMediaType() { return $this->clientMediaType; } /** * Write internal stream to given path * * @param string $path */ private function writeFile($path) { $handle = fopen($path, 'wb+'); if (false === $handle) { throw new RuntimeException('Unable to write to designated path'); } $stream = $this->getStream(); $stream->rewind(); while (! $stream->eof()) { fwrite($handle, $stream->read(4096)); } fclose($handle); } } zend-diactoros/src/Response.php 0000644 00000013375 14736103054 0012603 0 ustar 00 <?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 Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; /** * HTTP response encapsulation. * * Responses are considered immutable; all methods that might change state are * implemented such that they retain the internal state of the current * message and return a new instance that contains the changed state. */ class Response implements ResponseInterface { use MessageTrait; /** * Map of standard HTTP status code/reason phrases * * @var array */ private $phrases = [ // INFORMATIONAL CODES 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', // SUCCESS CODES 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 226 => 'IM used', // REDIRECTION CODES 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', // Deprecated to 306 => '(Unused)' 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', // CLIENT ERROR 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 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', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required', 599 => 'Network Connect Timeout Error', ]; /** * @var string */ private $reasonPhrase = ''; /** * @var int */ private $statusCode; /** * @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 = []) { $this->setStatusCode($status); $this->stream = $this->getStream($body, 'wb+'); list($this->headerNames, $headers) = $this->filterHeaders($headers); $this->assertHeaders($headers); $this->headers = $headers; } /** * {@inheritdoc} */ public function getStatusCode() { return $this->statusCode; } /** * {@inheritdoc} */ public function getReasonPhrase() { if (! $this->reasonPhrase && isset($this->phrases[$this->statusCode]) ) { $this->reasonPhrase = $this->phrases[$this->statusCode]; } return $this->reasonPhrase; } /** * {@inheritdoc} */ public function withStatus($code, $reasonPhrase = '') { $new = clone $this; $new->setStatusCode($code); $new->reasonPhrase = $reasonPhrase; return $new; } /** * Validate a status code. * * @param int|string $code * @throws InvalidArgumentException on an invalid status code. */ private function setStatusCode($code) { if (! is_numeric($code) || is_float($code) || $code < 100 || $code >= 600 ) { throw new InvalidArgumentException(sprintf( 'Invalid status code "%s"; must be an integer between 100 and 599, inclusive', (is_scalar($code) ? $code : gettype($code)) )); } $this->statusCode = $code; } /** * 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'); } } } zend-diactoros/src/Stream.php 0000644 00000016365 14736103054 0012242 0 ustar 00 <?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 Stream implements StreamInterface { /** * @var resource */ protected $resource; /** * @var string|resource */ protected $stream; /** * @param string|resource $stream * @param string $mode Mode with which to open stream * @throws InvalidArgumentException */ public function __construct($stream, $mode = 'r') { $this->setStream($stream, $mode); } /** * {@inheritdoc} */ public function __toString() { if (! $this->isReadable()) { return ''; } try { $this->rewind(); return $this->getContents(); } catch (RuntimeException $e) { return ''; } } /** * {@inheritdoc} */ public function close() { if (! $this->resource) { return; } $resource = $this->detach(); fclose($resource); } /** * {@inheritdoc} */ public function detach() { $resource = $this->resource; $this->resource = null; return $resource; } /** * Attach a new stream/resource to the instance. * * @param string|resource $resource * @param string $mode * @throws InvalidArgumentException for stream identifier that cannot be * cast to a resource * @throws InvalidArgumentException for non-resource stream */ public function attach($resource, $mode = 'r') { $this->setStream($resource, $mode); } /** * {@inheritdoc} */ public function getSize() { if (null === $this->resource) { return null; } $stats = fstat($this->resource); return $stats['size']; } /** * {@inheritdoc} */ public function tell() { if (! $this->resource) { throw new RuntimeException('No resource available; cannot tell position'); } $result = ftell($this->resource); if (! is_int($result)) { throw new RuntimeException('Error occurred during tell operation'); } return $result; } /** * {@inheritdoc} */ public function eof() { if (! $this->resource) { return true; } return feof($this->resource); } /** * {@inheritdoc} */ public function isSeekable() { if (! $this->resource) { return false; } $meta = stream_get_meta_data($this->resource); return $meta['seekable']; } /** * {@inheritdoc} */ public function seek($offset, $whence = SEEK_SET) { if (! $this->resource) { throw new RuntimeException('No resource available; cannot seek position'); } if (! $this->isSeekable()) { throw new RuntimeException('Stream is not seekable'); } $result = fseek($this->resource, $offset, $whence); if (0 !== $result) { throw new RuntimeException('Error seeking within stream'); } return true; } /** * {@inheritdoc} */ public function rewind() { return $this->seek(0); } /** * {@inheritdoc} */ public function isWritable() { if (! $this->resource) { return false; } $meta = stream_get_meta_data($this->resource); $mode = $meta['mode']; return ( strstr($mode, 'x') || strstr($mode, 'w') || strstr($mode, 'c') || strstr($mode, 'a') || strstr($mode, '+') ); } /** * {@inheritdoc} */ public function write($string) { if (! $this->resource) { throw new RuntimeException('No resource available; cannot write'); } if (! $this->isWritable()) { throw new RuntimeException('Stream is not writable'); } $result = fwrite($this->resource, $string); if (false === $result) { throw new RuntimeException('Error writing to stream'); } return $result; } /** * {@inheritdoc} */ public function isReadable() { if (! $this->resource) { return false; } $meta = stream_get_meta_data($this->resource); $mode = $meta['mode']; return (strstr($mode, 'r') || strstr($mode, '+')); } /** * {@inheritdoc} */ public function read($length) { if (! $this->resource) { throw new RuntimeException('No resource available; cannot read'); } if (! $this->isReadable()) { throw new RuntimeException('Stream is not readable'); } $result = fread($this->resource, $length); if (false === $result) { throw new RuntimeException('Error reading stream'); } return $result; } /** * {@inheritdoc} */ public function getContents() { if (! $this->isReadable()) { throw new RuntimeException('Stream is not readable'); } $result = stream_get_contents($this->resource); if (false === $result) { throw new RuntimeException('Error reading from stream'); } return $result; } /** * {@inheritdoc} */ public function getMetadata($key = null) { if (null === $key) { return stream_get_meta_data($this->resource); } $metadata = stream_get_meta_data($this->resource); if (! array_key_exists($key, $metadata)) { return null; } return $metadata[$key]; } /** * Set the internal stream resource. * * @param string|resource $stream String stream target or stream resource. * @param string $mode Resource mode for stream target. * @throws InvalidArgumentException for invalid streams or resources. */ private function setStream($stream, $mode = 'r') { $error = null; $resource = $stream; if (is_string($stream)) { set_error_handler(function ($e) use (&$error) { $error = $e; }, E_WARNING); $resource = fopen($stream, $mode); restore_error_handler(); } if ($error) { throw new InvalidArgumentException('Invalid stream reference provided'); } if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) { throw new InvalidArgumentException( 'Invalid stream provided; must be a string stream identifier or stream resource' ); } if ($stream !== $resource) { $this->stream = $stream; } $this->resource = $resource; } } zend-diactoros/src/RelativeStream.php 0000644 00000007175 14736103054 0013735 0 ustar 00 <?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 Psr\Http\Message\StreamInterface; use RuntimeException; /** * Class RelativeStream * * Wrapper for default Stream class, representing subpart (starting from given offset) of initial stream. * It can be used to avoid copying full stream, conserving memory. * @example see Zend\Diactoros\AbstractSerializer::splitStream() */ final class RelativeStream implements StreamInterface { /** * @var StreamInterface */ private $decoratedStream; /** * @var int */ private $offset; /** * Class constructor * * @param StreamInterface $decoratedStream * @param int $offset */ public function __construct(StreamInterface $decoratedStream, $offset) { $this->decoratedStream = $decoratedStream; $this->offset = (int)$offset; } /** * {@inheritdoc} */ public function __toString() { $this->seek(0); return $this->getContents(); } /** * {@inheritdoc} */ public function close() { $this->decoratedStream->close(); } /** * {@inheritdoc} */ public function detach() { return $this->decoratedStream->detach(); } /** * {@inheritdoc} */ public function getSize() { return $this->decoratedStream->getSize() - $this->offset; } /** * {@inheritdoc} */ public function tell() { return $this->decoratedStream->tell() - $this->offset; } /** * {@inheritdoc} */ public function eof() { return $this->decoratedStream->eof(); } /** * {@inheritdoc} */ public function isSeekable() { return $this->decoratedStream->isSeekable(); } /** * {@inheritdoc} */ public function seek($offset, $whence = SEEK_SET) { if ($whence == SEEK_SET) { return $this->decoratedStream->seek($offset + $this->offset, $whence); } return $this->decoratedStream->seek($offset, $whence); } /** * {@inheritdoc} */ public function rewind() { return $this->seek(0); } /** * {@inheritdoc} */ public function isWritable() { return $this->decoratedStream->isWritable(); } /** * {@inheritdoc} */ public function write($string) { if ($this->tell() < 0) { throw new RuntimeException('Invalid pointer position'); } return $this->decoratedStream->write($string); } /** * {@inheritdoc} */ public function isReadable() { return $this->decoratedStream->isReadable(); } /** * {@inheritdoc} */ public function read($length) { if ($this->tell() < 0) { throw new RuntimeException('Invalid pointer position'); } return $this->decoratedStream->read($length); } /** * {@inheritdoc} */ public function getContents() { if ($this->tell() < 0) { throw new RuntimeException('Invalid pointer position'); } return $this->decoratedStream->getContents(); } /** * {@inheritdoc} */ public function getMetadata($key = null) { return $this->decoratedStream->getMetadata($key); } } zend-diactoros/src/Request.php 0000644 00000004211 14736103054 0012422 0 ustar 00 <?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 Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; /** * HTTP Request encapsulation * * Requests are considered immutable; all methods that might change state are * implemented such that they retain the internal state of the current * message and return a new instance that contains the changed state. */ class Request implements RequestInterface { use MessageTrait, RequestTrait; /** * @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. * @throws \InvalidArgumentException for any invalid value. */ public function __construct($uri = null, $method = null, $body = 'php://temp', array $headers = []) { $this->initialize($uri, $method, $body, $headers); } /** * {@inheritdoc} */ public function getHeaders() { $headers = $this->headers; if (! $this->hasHeader('host') && $this->uri->getHost() ) { $headers['Host'] = [$this->getHostFromUri()]; } return $headers; } /** * {@inheritdoc} */ public function getHeader($header) { if (! $this->hasHeader($header)) { if (strtolower($header) === 'host' && $this->uri->getHost() ) { return [$this->getHostFromUri()]; } return []; } $header = $this->headerNames[strtolower($header)]; $value = $this->headers[$header]; $value = is_array($value) ? $value : [$value]; return $value; } } zend-diactoros/src/MessageTrait.php 0000644 00000034022 14736103054 0013365 0 ustar 00 <?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 Psr\Http\Message\StreamInterface; /** * Trait implementing the various methods defined in MessageInterface. * * @see https://github.com/php-fig/http-message/tree/master/src/MessageInterface.php */ trait MessageTrait { /** * List of all registered headers, as key => array of values. * * @var array */ protected $headers = []; /** * Map of normalized header name to original name used to register header. * * @var array */ protected $headerNames = []; /** * @var string */ private $protocol = '1.1'; /** * @var StreamInterface */ private $stream; /** * Retrieves the HTTP protocol version as a string. * * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). * * @return string HTTP protocol version. */ public function getProtocolVersion() { return $this->protocol; } /** * Return an instance with the specified HTTP protocol version. * * The version string MUST contain only the HTTP version number (e.g., * "1.1", "1.0"). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new protocol version. * * @param string $version HTTP protocol version * @return static */ public function withProtocolVersion($version) { $this->validateProtocolVersion($version); $new = clone $this; $new->protocol = $version; return $new; } /** * Retrieves all message headers. * * The keys represent the header name as it will be sent over the wire, and * each value is an array of strings associated with the header. * * // Represent the headers as a string * foreach ($message->getHeaders() as $name => $values) { * echo $name . ": " . implode(", ", $values); * } * * // Emit headers iteratively: * foreach ($message->getHeaders() as $name => $values) { * foreach ($values as $value) { * header(sprintf('%s: %s', $name, $value), false); * } * } * * @return array Returns an associative array of the message's headers. Each * key MUST be a header name, and each value MUST be an array of strings. */ public function getHeaders() { return $this->headers; } /** * Checks if a header exists by the given case-insensitive name. * * @param string $header Case-insensitive header name. * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function hasHeader($header) { return array_key_exists(strtolower($header), $this->headerNames); } /** * Retrieves a message header value by the given case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $header Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function getHeader($header) { if (! $this->hasHeader($header)) { return []; } $header = $this->headerNames[strtolower($header)]; $value = $this->headers[$header]; $value = is_array($value) ? $value : [$value]; return $value; } /** * Retrieves a comma-separated string of the values for a single header. * * This method returns all of the header values of the given * case-insensitive header name as a string concatenated together using * a comma. * * NOTE: Not all header values may be appropriately represented using * comma concatenation. For such headers, use getHeader() instead * and supply your own delimiter when concatenating. * * If the header does not appear in the message, this method MUST return * an empty string. * * @param string $name Case-insensitive header field name. * @return string A string of values as provided for the given header * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ public function getHeaderLine($name) { $value = $this->getHeader($name); if (empty($value)) { return ''; } return implode(',', $value); } /** * Return an instance with the provided header, replacing any existing * values of any headers with the same case-insensitive name. * * While header names are case-insensitive, the casing of the header will * be preserved by this function, and returned from getHeaders(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new and/or updated header and value. * * @param string $header Case-insensitive header field name. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ 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); $normalized = strtolower($header); $new = clone $this; if ($new->hasHeader($header)) { unset($new->headers[$new->headerNames[$normalized]]); } $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; return $new; } /** * Return an instance with the specified header appended with the * given value. * * Existing values for the specified header will be maintained. The new * value(s) will be appended to the existing list. If the header did not * exist previously, it will be added. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new header and/or value. * * @param string $header Case-insensitive header field name to add. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ 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); if (! $this->hasHeader($header)) { return $this->withHeader($header, $value); } $normalized = strtolower($header); $header = $this->headerNames[$normalized]; $new = clone $this; $new->headers[$header] = array_merge($this->headers[$header], $value); return $new; } /** * Return an instance without the specified header. * * Header resolution MUST be done without case-sensitivity. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the named header. * * @param string $header Case-insensitive header field name to remove. * @return static */ public function withoutHeader($header) { if (! $this->hasHeader($header)) { return clone $this; } $normalized = strtolower($header); $original = $this->headerNames[$normalized]; $new = clone $this; unset($new->headers[$original], $new->headerNames[$normalized]); return $new; } /** * Gets the body of the message. * * @return StreamInterface Returns the body as a stream. */ public function getBody() { return $this->stream; } /** * Return an instance with the specified message body. * * The body MUST be a StreamInterface object. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. * * @param StreamInterface $body Body. * @return static * @throws \InvalidArgumentException When the body is not valid. */ public function withBody(StreamInterface $body) { $new = clone $this; $new->stream = $body; return $new; } private function getStream($stream, $modeIfNotInstance) { 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); } /** * Test that an array contains only strings * * @param array $array * @return bool */ private function arrayContainsOnlyStrings(array $array) { return array_reduce($array, [__CLASS__, 'filterStringValue'], true); } /** * Filter a set of headers to ensure they are in the correct internal format. * * 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) { $headerNames = $headers = []; foreach ($originalHeaders as $header => $value) { if (! is_string($header)) { throw new InvalidArgumentException(sprintf( 'Invalid header name; expected non-empty string, received %s', gettype($header) )); } if (! is_array($value) && ! is_string($value) && ! is_numeric($value)) { throw new InvalidArgumentException(sprintf( 'Invalid header value type; expected number, string, or array; received %s', (is_object($value) ? get_class($value) : gettype($value)) )); } if (is_array($value)) { array_walk($value, function ($item) { if (! is_string($item) && ! is_numeric($item)) { throw new InvalidArgumentException(sprintf( 'Invalid header value type; expected number, string, or array; received %s', (is_object($item) ? get_class($item) : gettype($item)) )); } }); } if (! is_array($value)) { $value = [ $value ]; } $headerNames[strtolower($header)] = $header; $headers[$header] = $value; } return [$headerNames, $headers]; } /** * Test if a value is a string * * Used with array_reduce. * * @param bool $carry * @param mixed $item * @return bool */ private static function filterStringValue($carry, $item) { if (! is_string($item)) { return false; } return $carry; } /** * Assert that the provided header values are valid. * * @see http://tools.ietf.org/html/rfc7230#section-3.2 * @param string[] $values * @throws InvalidArgumentException */ private static function assertValidHeaderValue(array $values) { array_walk($values, __NAMESPACE__ . '\HeaderSecurity::assertValid'); } /** * Validate the HTTP protocol version * * @param string $version * @throws InvalidArgumentException on invalid HTTP protocol version */ private function validateProtocolVersion($version) { 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 )); } } } zend-diactoros/src/Exception/ExceptionInterface.php 0000644 00000000741 14736103054 0016513 0 ustar 00 <?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\Exception; /** * Marker interface for package-specific exceptions. */ interface ExceptionInterface { } zend-diactoros/src/Exception/DeprecatedMethodException.php 0000644 00000001066 14736103054 0020015 0 ustar 00 <?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\Exception; use BadMethodCallException; /** * Exception indicating a deprecated method. */ class DeprecatedMethodException extends BadMethodCallException implements ExceptionInterface { } zend-diactoros/src/CallbackStream.php 0000644 00000006473 14736103054 0013656 0 ustar 00 <?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]; } } zend-diactoros/src/Request/Serializer.php 0000644 00000011310 14736103054 0014531 0 ustar 00 <?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\Request; use InvalidArgumentException; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use UnexpectedValueException; use Zend\Diactoros\AbstractSerializer; use Zend\Diactoros\Request; use Zend\Diactoros\Stream; use Zend\Diactoros\Uri; /** * Serialize (cast to string) or deserialize (cast string to Request) messages. * * This class provides functionality for serializing a RequestInterface instance * to a string, as well as the reverse operation of creating a Request instance * from a string/stream representing a message. */ final class Serializer extends AbstractSerializer { /** * Deserialize a request string to a request instance. * * Internally, casts the message to a stream and invokes fromStream(). * * @param string $message * @return Request * @throws UnexpectedValueException when errors occur parsing the message. */ public static function fromString($message) { $stream = new Stream('php://temp', 'wb+'); $stream->write($message); return self::fromStream($stream); } /** * Deserialize a request stream to a request instance. * * @param StreamInterface $stream * @return Request * @throws UnexpectedValueException when errors occur parsing the message. */ public static function fromStream(StreamInterface $stream) { if (! $stream->isReadable() || ! $stream->isSeekable()) { throw new InvalidArgumentException('Message stream must be both readable and seekable'); } $stream->rewind(); list($method, $requestTarget, $version) = self::getRequestLine($stream); $uri = self::createUriFromRequestTarget($requestTarget); list($headers, $body) = self::splitStream($stream); return (new Request($uri, $method, $body, $headers)) ->withProtocolVersion($version) ->withRequestTarget($requestTarget); } /** * Serialize a request message to a string. * * @param RequestInterface $request * @return string */ 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'; if (! empty($headers)) { $headers = "\r\n" . $headers; } if (! empty($body)) { $headers .= "\r\n\r\n"; } return sprintf( $format, $httpMethod, $request->getRequestTarget(), $request->getProtocolVersion(), $headers, $body ); } /** * Retrieve the components of the request line. * * Retrieves the first line of the stream and parses it, raising an * exception if it does not follow specifications; if valid, returns a list * with the method, target, and version, in that order. * * @param StreamInterface $stream * @return array */ private static function getRequestLine(StreamInterface $stream) { $requestLine = self::getLine($stream); if (! preg_match( '#^(?P<method>[!\#$%&\'*+.^_`|~a-zA-Z0-9-]+) (?P<target>[^\s]+) HTTP/(?P<version>[1-9]\d*\.\d+)$#', $requestLine, $matches )) { throw new UnexpectedValueException('Invalid request line detected'); } return [$matches['method'], $matches['target'], $matches['version']]; } /** * Create and return a Uri instance based on the provided request target. * * If the request target is of authority or asterisk form, an empty Uri * instance is returned; otherwise, the value is used to create and return * a new Uri instance. * * @param string $requestTarget * @return Uri */ private static function createUriFromRequestTarget($requestTarget) { if (preg_match('#^https?://#', $requestTarget)) { return new Uri($requestTarget); } if (preg_match('#^(\*|[^/])#', $requestTarget)) { return new Uri(); } return new Uri($requestTarget); } } zend-diactoros/src/PhpInputStream.php 0000644 00000003424 14736103054 0013722 0 ustar 00 <?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; /** * Caching version of php://input */ class PhpInputStream extends Stream { /** * @var string */ private $cache = ''; /** * @var bool */ private $reachedEof = false; /** * @param string|resource $stream */ public function __construct($stream = 'php://input') { parent::__construct($stream, 'r'); } /** * {@inheritdoc} */ public function __toString() { if ($this->reachedEof) { return $this->cache; } $this->getContents(); return $this->cache; } /** * {@inheritdoc} */ public function isWritable() { return false; } /** * {@inheritdoc} */ public function read($length) { $content = parent::read($length); if ($content && ! $this->reachedEof) { $this->cache .= $content; } if ($this->eof()) { $this->reachedEof = true; } return $content; } /** * {@inheritdoc} */ public function getContents($maxLength = -1) { if ($this->reachedEof) { return $this->cache; } $contents = stream_get_contents($this->resource, $maxLength); $this->cache .= $contents; if ($maxLength === -1 || $this->eof()) { $this->reachedEof = true; } return $contents; } } zend-diactoros/src/ServerRequest.php 0000644 00000015601 14736103054 0013616 0 ustar 00 <?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 Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; /** * Server-side HTTP request * * Extends the Request definition to add methods for accessing incoming data, * specifically server parameters, cookies, matched path parameters, query * string arguments, body parameters, and upload file information. * * "Attributes" are discovered via decomposing the request (and usually * specifically the URI path), and typically will be injected by the application. * * Requests are considered immutable; all methods that might change state are * implemented such that they retain the internal state of the current * message and return a new instance that contains the changed state. */ class ServerRequest implements ServerRequestInterface { use MessageTrait, RequestTrait; /** * @var array */ private $attributes = []; /** * @var array */ private $cookieParams = []; /** * @var null|array|object */ private $parsedBody; /** * @var array */ private $queryParams = []; /** * @var array */ private $serverParams; /** * @var array */ private $uploadedFiles; /** * @param array $serverParams Server parameters, typically from $_SERVER * @param array $uploadedFiles Upload file information, a tree of UploadedFiles * @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( array $serverParams = [], array $uploadedFiles = [], $uri = null, $method = null, $body = 'php://input', array $headers = [], array $cookies = [], array $queryParams = [], $parsedBody = null, $protocol = '1.1' ) { $this->validateUploadedFiles($uploadedFiles); 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; } /** * {@inheritdoc} */ public function getServerParams() { return $this->serverParams; } /** * {@inheritdoc} */ public function getUploadedFiles() { return $this->uploadedFiles; } /** * {@inheritdoc} */ public function withUploadedFiles(array $uploadedFiles) { $this->validateUploadedFiles($uploadedFiles); $new = clone $this; $new->uploadedFiles = $uploadedFiles; return $new; } /** * {@inheritdoc} */ public function getCookieParams() { return $this->cookieParams; } /** * {@inheritdoc} */ public function withCookieParams(array $cookies) { $new = clone $this; $new->cookieParams = $cookies; return $new; } /** * {@inheritdoc} */ public function getQueryParams() { return $this->queryParams; } /** * {@inheritdoc} */ public function withQueryParams(array $query) { $new = clone $this; $new->queryParams = $query; return $new; } /** * {@inheritdoc} */ public function getParsedBody() { return $this->parsedBody; } /** * {@inheritdoc} */ public function withParsedBody($data) { $new = clone $this; $new->parsedBody = $data; return $new; } /** * {@inheritdoc} */ public function getAttributes() { return $this->attributes; } /** * {@inheritdoc} */ public function getAttribute($attribute, $default = null) { if (! array_key_exists($attribute, $this->attributes)) { return $default; } return $this->attributes[$attribute]; } /** * {@inheritdoc} */ public function withAttribute($attribute, $value) { $new = clone $this; $new->attributes[$attribute] = $value; return $new; } /** * {@inheritdoc} */ public function withoutAttribute($attribute) { $new = clone $this; unset($new->attributes[$attribute]); return $new; } /** * Proxy to receive the request method. * * This overrides the parent functionality to ensure the method is never * empty; if no method is present, it returns 'GET'. * * @return string */ public function getMethod() { if (empty($this->method)) { return 'GET'; } return $this->method; } /** * Set the request method. * * Unlike the regular Request implementation, the server-side * normalizes the method to uppercase to ensure consistency * and make checking the method simpler. * * This methods returns a new instance. * * @param string $method * @return self */ public function withMethod($method) { $this->validateMethod($method); $new = clone $this; $new->method = $method; return $new; } /** * Recursively validate the structure in an uploaded files array. * * @param array $uploadedFiles * @throws InvalidArgumentException if any leaf is not an UploadedFileInterface instance. */ private function validateUploadedFiles(array $uploadedFiles) { foreach ($uploadedFiles as $file) { if (is_array($file)) { $this->validateUploadedFiles($file); continue; } if (! $file instanceof UploadedFileInterface) { throw new InvalidArgumentException('Invalid leaf in uploaded files structure'); } } } } zend-diactoros/src/ServerRequestFactory.php 0000644 00000035453 14736103054 0015155 0 ustar 00 <?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 Psr\Http\Message\UploadedFileInterface; use stdClass; use UnexpectedValueException; /** * Class for marshaling a request object from the current PHP environment. * * Logic largely refactored from the ZF2 Zend\Http\PhpEnvironment\Request class. * * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ abstract class ServerRequestFactory { /** * Function to use to get apache request headers; present only to simplify mocking. * * @var callable */ private static $apacheRequestHeaders = 'apache_request_headers'; /** * Create a request from the supplied superglobal values. * * If any argument is not supplied, the corresponding superglobal value will * be used. * * The ServerRequest created is then passed to the fromServer() method in * order to marshal the request URI and headers. * * @see fromServer() * @param array $server $_SERVER superglobal * @param array $query $_GET superglobal * @param array $body $_POST superglobal * @param array $cookies $_COOKIE superglobal * @param array $files $_FILES superglobal * @return ServerRequest * @throws InvalidArgumentException for invalid file values */ public static function fromGlobals( array $server = null, array $query = null, array $body = null, array $cookies = null, array $files = null ) { $server = static::normalizeServer($server ?: $_SERVER); $files = static::normalizeFiles($files ?: $_FILES); $headers = static::marshalHeaders($server); return new ServerRequest( $server, $files, static::marshalUriFromServer($server, $headers), static::get('REQUEST_METHOD', $server, 'GET'), 'php://input', $headers, $cookies ?: $_COOKIE, $query ?: $_GET, $body ?: $_POST, static::marshalProtocolVersion($server) ); } /** * Access a value in an array, returning a default value if not found * * Will also do a case-insensitive search if a case sensitive search fails. * * @param string $key * @param array $values * @param mixed $default * @return mixed */ public static function get($key, array $values, $default = null) { if (array_key_exists($key, $values)) { return $values[$key]; } return $default; } /** * Search for a header value. * * Does a case-insensitive search for a matching header. * * If found, it is returned as a string, using comma concatenation. * * If not, the $default is returned. * * @param string $header * @param array $headers * @param mixed $default * @return string */ public static function getHeader($header, array $headers, $default = null) { $header = strtolower($header); $headers = array_change_key_case($headers, CASE_LOWER); if (array_key_exists($header, $headers)) { $value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header]; return $value; } return $default; } /** * Marshal the $_SERVER array * * Pre-processes and returns the $_SERVER superglobal. * * @param array $server * @return array */ public static function normalizeServer(array $server) { // This seems to be the only way to get the Authorization header on Apache $apacheRequestHeaders = self::$apacheRequestHeaders; if (isset($server['HTTP_AUTHORIZATION']) || ! is_callable($apacheRequestHeaders) ) { return $server; } $apacheRequestHeaders = $apacheRequestHeaders(); if (isset($apacheRequestHeaders['Authorization'])) { $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['Authorization']; return $server; } if (isset($apacheRequestHeaders['authorization'])) { $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['authorization']; return $server; } return $server; } /** * Normalize uploaded files * * Transforms each value into an UploadedFileInterface instance, and ensures * that nested arrays are normalized. * * @param array $files * @return array * @throws InvalidArgumentException for unrecognized values */ public static function normalizeFiles(array $files) { $normalized = []; foreach ($files as $key => $value) { if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; continue; } if (is_array($value) && isset($value['tmp_name'])) { $normalized[$key] = self::createUploadedFileFromSpec($value); continue; } if (is_array($value)) { $normalized[$key] = self::normalizeFiles($value); continue; } throw new InvalidArgumentException('Invalid value in files specification'); } return $normalized; } /** * Marshal headers from $_SERVER * * @param array $server * @return array */ public static function marshalHeaders(array $server) { $headers = []; foreach ($server as $key => $value) { // 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(strtolower(substr($key, 5)), '_', '-'); $headers[$name] = $value; continue; } if ($value && strpos($key, 'CONTENT_') === 0) { $name = 'content-' . strtolower(substr($key, 8)); $headers[$name] = $value; continue; } } return $headers; } /** * Marshal the URI from the $_SERVER array and headers * * @param array $server * @param array $headers * @return Uri */ public static function marshalUriFromServer(array $server, array $headers) { $uri = new Uri(''); // URI scheme $scheme = 'http'; $https = self::get('HTTPS', $server); if (($https && 'off' !== $https) || self::getHeader('x-forwarded-proto', $headers, false) === 'https' ) { $scheme = 'https'; } if (! empty($scheme)) { $uri = $uri->withScheme($scheme); } // Set the host $accumulator = (object) ['host' => '', 'port' => null]; self::marshalHostAndPortFromHeaders($accumulator, $server, $headers); $host = $accumulator->host; $port = $accumulator->port; if (! empty($host)) { $uri = $uri->withHost($host); if (! empty($port)) { $uri = $uri->withPort($port); } } // URI path $path = self::marshalRequestUri($server); $path = self::stripQueryString($path); // URI query $query = ''; if (isset($server['QUERY_STRING'])) { $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); } /** * Marshal the host and port from HTTP headers and/or the PHP environment * * @param stdClass $accumulator * @param array $server * @param array $headers */ public static function marshalHostAndPortFromHeaders(stdClass $accumulator, array $server, array $headers) { if (self::getHeader('host', $headers, false)) { self::marshalHostAndPortFromHeader($accumulator, self::getHeader('host', $headers)); return; } if (! isset($server['SERVER_NAME'])) { return; } $accumulator->host = $server['SERVER_NAME']; if (isset($server['SERVER_PORT'])) { $accumulator->port = (int) $server['SERVER_PORT']; } if (! isset($server['SERVER_ADDR']) || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $accumulator->host)) { return; } // Misinterpreted IPv6-Address // Reported for Safari on Windows self::marshalIpv6HostAndPort($accumulator, $server); } /** * Detect the base URI for the request * * Looks at a variety of criteria in order to attempt to autodetect a base * URI, including rewrite URIs, proxy URIs, etc. * * From ZF2's Zend\Http\PhpEnvironment\Request class * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * * @param array $server * @return string */ public static function marshalRequestUri(array $server) { // IIS7 with URL Rewrite: make sure we get the unencoded url // (double slash problem). $iisUrlRewritten = self::get('IIS_WasUrlRewritten', $server); $unencodedUrl = self::get('UNENCODED_URL', $server, ''); if ('1' == $iisUrlRewritten && ! empty($unencodedUrl)) { return $unencodedUrl; } $requestUri = self::get('REQUEST_URI', $server); // Check this first so IIS will catch. $httpXRewriteUrl = self::get('HTTP_X_REWRITE_URL', $server); if ($httpXRewriteUrl !== null) { $requestUri = $httpXRewriteUrl; } // Check for IIS 7.0 or later with ISAPI_Rewrite $httpXOriginalUrl = self::get('HTTP_X_ORIGINAL_URL', $server); if ($httpXOriginalUrl !== null) { $requestUri = $httpXOriginalUrl; } if ($requestUri !== null) { return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri); } $origPathInfo = self::get('ORIG_PATH_INFO', $server); if (empty($origPathInfo)) { return '/'; } return $origPathInfo; } /** * Strip the query string from a path * * @param mixed $path * @return string */ public static function stripQueryString($path) { if (($qpos = strpos($path, '?')) !== false) { return substr($path, 0, $qpos); } return $path; } /** * Marshal the host and port from the request header * * @param stdClass $accumulator * @param string|array $host * @return void */ private static function marshalHostAndPortFromHeader(stdClass $accumulator, $host) { if (is_array($host)) { $host = implode(', ', $host); } $accumulator->host = $host; $accumulator->port = null; // works for regname, IPv4 & IPv6 if (preg_match('|\:(\d+)$|', $accumulator->host, $matches)) { $accumulator->host = substr($accumulator->host, 0, -1 * (strlen($matches[1]) + 1)); $accumulator->port = (int) $matches[1]; } } /** * Marshal host/port from misinterpreted IPv6 address * * @param stdClass $accumulator * @param array $server */ private static function marshalIpv6HostAndPort(stdClass $accumulator, array $server) { $accumulator->host = '[' . $server['SERVER_ADDR'] . ']'; $accumulator->port = $accumulator->port ?: 80; if ($accumulator->port . ']' === substr($accumulator->host, strrpos($accumulator->host, ':') + 1)) { // The last digit of the IPv6-Address has been taken as port // Unset the port so the default port can be used $accumulator->port = null; } } /** * Create and return an UploadedFile instance from a $_FILES specification. * * If the specification represents an array of values, this method will * delegate to normalizeNestedFileSpec() and return that return value. * * @param array $value $_FILES struct * @return array|UploadedFileInterface */ private static function createUploadedFileFromSpec(array $value) { if (is_array($value['tmp_name'])) { return self::normalizeNestedFileSpec($value); } return new UploadedFile( $value['tmp_name'], $value['size'], $value['error'], $value['name'], $value['type'] ); } /** * Normalize an array of file specifications. * * Loops through all nested files and returns a normalized array of * UploadedFileInterface instances. * * @param array $files * @return UploadedFileInterface[] */ private static function normalizeNestedFileSpec(array $files = []) { $normalizedFiles = []; foreach (array_keys($files['tmp_name']) as $key) { $spec = [ 'tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key], 'type' => $files['type'][$key], ]; $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); } 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']; } } zend-diactoros/src/Uri.php 0000644 00000037313 14736103054 0011542 0 ustar 00 <?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 Psr\Http\Message\UriInterface; /** * Implementation of Psr\Http\UriInterface. * * Provides a value object representing a URI for HTTP requests. * * Instances of this class are considered immutable; all methods that * might change state are implemented such that they retain the internal * state of the current instance and return a new instance that contains the * changed state. */ class Uri implements UriInterface { /** * Sub-delimiters used in query strings and fragments. * * @const string */ const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; /** * Unreserved characters used in paths, query strings, and fragments. * * @const string */ const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~\pL'; /** * @var int[] Array indexed by valid scheme names to their corresponding ports. */ protected $allowedSchemes = [ 'http' => 80, 'https' => 443, ]; /** * @var string */ private $scheme = ''; /** * @var string */ private $userInfo = ''; /** * @var string */ private $host = ''; /** * @var int */ private $port; /** * @var string */ private $path = ''; /** * @var string */ private $query = ''; /** * @var string */ private $fragment = ''; /** * generated uri string cache * @var string|null */ private $uriString; /** * @param string $uri * @throws InvalidArgumentException on non-string $uri argument */ public function __construct($uri = '') { if (! is_string($uri)) { throw new InvalidArgumentException(sprintf( 'URI passed to constructor must be a string; received "%s"', (is_object($uri) ? get_class($uri) : gettype($uri)) )); } if (! empty($uri)) { $this->parseUri($uri); } } /** * Operations to perform on clone. * * Since cloning usually is for purposes of mutation, we reset the * $uriString property so it will be re-calculated. */ public function __clone() { $this->uriString = null; } /** * {@inheritdoc} */ public function __toString() { if (null !== $this->uriString) { return $this->uriString; } $this->uriString = static::createUriString( $this->scheme, $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path $this->query, $this->fragment ); return $this->uriString; } /** * {@inheritdoc} */ public function getScheme() { return $this->scheme; } /** * {@inheritdoc} */ public function getAuthority() { if (empty($this->host)) { return ''; } $authority = $this->host; if (! empty($this->userInfo)) { $authority = $this->userInfo . '@' . $authority; } if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) { $authority .= ':' . $this->port; } return $authority; } /** * {@inheritdoc} */ public function getUserInfo() { return $this->userInfo; } /** * {@inheritdoc} */ public function getHost() { return $this->host; } /** * {@inheritdoc} */ public function getPort() { return $this->isNonStandardPort($this->scheme, $this->host, $this->port) ? $this->port : null; } /** * {@inheritdoc} */ public function getPath() { return $this->path; } /** * {@inheritdoc} */ public function getQuery() { return $this->query; } /** * {@inheritdoc} */ public function getFragment() { return $this->fragment; } /** * {@inheritdoc} */ public function withScheme($scheme) { if (! is_string($scheme)) { throw new InvalidArgumentException(sprintf( '%s expects a string argument; received %s', __METHOD__, (is_object($scheme) ? get_class($scheme) : gettype($scheme)) )); } $scheme = $this->filterScheme($scheme); if ($scheme === $this->scheme) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->scheme = $scheme; return $new; } /** * {@inheritdoc} */ public function withUserInfo($user, $password = null) { if (! is_string($user)) { throw new InvalidArgumentException(sprintf( '%s expects a string user argument; received %s', __METHOD__, (is_object($user) ? get_class($user) : gettype($user)) )); } if (null !== $password && ! is_string($password)) { throw new InvalidArgumentException(sprintf( '%s expects a string password argument; received %s', __METHOD__, (is_object($password) ? get_class($password) : gettype($password)) )); } $info = $user; if ($password) { $info .= ':' . $password; } if ($info === $this->userInfo) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->userInfo = $info; return $new; } /** * {@inheritdoc} */ public function withHost($host) { if (! is_string($host)) { throw new InvalidArgumentException(sprintf( '%s expects a string argument; received %s', __METHOD__, (is_object($host) ? get_class($host) : gettype($host)) )); } if ($host === $this->host) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->host = $host; return $new; } /** * {@inheritdoc} */ public function withPort($port) { if (! is_numeric($port) && $port !== null) { throw new InvalidArgumentException(sprintf( 'Invalid port "%s" specified; must be an integer, an integer string, or null', (is_object($port) ? get_class($port) : gettype($port)) )); } if ($port !== null) { $port = (int) $port; } if ($port === $this->port) { // Do nothing if no change was made. return clone $this; } if ($port !== null && $port < 1 || $port > 65535) { throw new InvalidArgumentException(sprintf( 'Invalid port "%d" specified; must be a valid TCP/UDP port', $port )); } $new = clone $this; $new->port = $port; return $new; } /** * {@inheritdoc} */ public function withPath($path) { if (! is_string($path)) { throw new InvalidArgumentException( 'Invalid path provided; must be a string' ); } if (strpos($path, '?') !== false) { throw new InvalidArgumentException( 'Invalid path provided; must not contain a query string' ); } if (strpos($path, '#') !== false) { throw new InvalidArgumentException( 'Invalid path provided; must not contain a URI fragment' ); } $path = $this->filterPath($path); if ($path === $this->path) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->path = $path; return $new; } /** * {@inheritdoc} */ public function withQuery($query) { if (! is_string($query)) { throw new InvalidArgumentException( 'Query string must be a string' ); } if (strpos($query, '#') !== false) { throw new InvalidArgumentException( 'Query string must not include a URI fragment' ); } $query = $this->filterQuery($query); if ($query === $this->query) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->query = $query; return $new; } /** * {@inheritdoc} */ public function withFragment($fragment) { if (! is_string($fragment)) { throw new InvalidArgumentException(sprintf( '%s expects a string argument; received %s', __METHOD__, (is_object($fragment) ? get_class($fragment) : gettype($fragment)) )); } $fragment = $this->filterFragment($fragment); if ($fragment === $this->fragment) { // Do nothing if no change was made. return clone $this; } $new = clone $this; $new->fragment = $fragment; return $new; } /** * Parse a URI into its parts, and set the properties * * @param string $uri */ private function parseUri($uri) { $parts = parse_url($uri); if (false === $parts) { throw new \InvalidArgumentException( 'The source URI string appears to be malformed' ); } $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'])) { $this->userInfo .= ':' . $parts['pass']; } } /** * Create a URI string from its various parts * * @param string $scheme * @param string $authority * @param string $path * @param string $query * @param string $fragment * @return string */ private static function createUriString($scheme, $authority, $path, $query, $fragment) { $uri = ''; if (! empty($scheme)) { $uri .= sprintf('%s://', $scheme); } if (! empty($authority)) { $uri .= $authority; } if ($path) { if (empty($path) || '/' !== substr($path, 0, 1)) { $path = '/' . $path; } $uri .= $path; } if ($query) { $uri .= sprintf('?%s', $query); } if ($fragment) { $uri .= sprintf('#%s', $fragment); } return $uri; } /** * Is a given port non-standard for the current scheme? * * @param string $scheme * @param string $host * @param int $port * @return bool */ private function isNonStandardPort($scheme, $host, $port) { if (! $scheme) { if ($host && ! $port) { return false; } return true; } if (! $host || ! $port) { return false; } return ! isset($this->allowedSchemes[$scheme]) || $port !== $this->allowedSchemes[$scheme]; } /** * Filters the scheme to ensure it is a valid scheme. * * @param string $scheme Scheme name. * * @return string Filtered scheme. */ private function filterScheme($scheme) { $scheme = strtolower($scheme); $scheme = preg_replace('#:(//)?$#', '', $scheme); if (empty($scheme)) { return ''; } if (! array_key_exists($scheme, $this->allowedSchemes)) { throw new InvalidArgumentException(sprintf( 'Unsupported scheme "%s"; must be any empty string or in the set (%s)', $scheme, implode(', ', array_keys($this->allowedSchemes)) )); } return $scheme; } /** * Filters the path of a URI to ensure it is properly encoded. * * @param string $path * @return string */ private function filterPath($path) { $path = preg_replace_callback( '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u', [$this, 'urlEncodeChar'], $path ); if (empty($path)) { // No path return $path; } if ($path[0] !== '/') { // Relative path return $path; } // Ensure only one leading slash, to prevent XSS attempts. return '/' . ltrim($path, '/'); } /** * Filter a query string to ensure it is propertly encoded. * * Ensures that the values in the query string are properly urlencoded. * * @param string $query * @return string */ private function filterQuery($query) { if (! empty($query) && strpos($query, '?') === 0) { $query = substr($query, 1); } $parts = explode('&', $query); foreach ($parts as $index => $part) { list($key, $value) = $this->splitQueryValue($part); if ($value === null) { $parts[$index] = $this->filterQueryOrFragment($key); continue; } $parts[$index] = sprintf( '%s=%s', $this->filterQueryOrFragment($key), $this->filterQueryOrFragment($value) ); } return implode('&', $parts); } /** * Split a query value into a key/value tuple. * * @param string $value * @return array A value with exactly two elements, key and value */ private function splitQueryValue($value) { $data = explode('=', $value, 2); if (1 === count($data)) { $data[] = null; } return $data; } /** * Filter a fragment value to ensure it is properly encoded. * * @param null|string $fragment * @return string */ private function filterFragment($fragment) { if (! empty($fragment) && strpos($fragment, '#') === 0) { $fragment = '%23' . substr($fragment, 1); } return $this->filterQueryOrFragment($fragment); } /** * Filter a query string key or value, or a fragment. * * @param string $value * @return string */ private function filterQueryOrFragment($value) { return preg_replace_callback( '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u', [$this, 'urlEncodeChar'], $value ); } /** * URL encode a character returned by a regex. * * @param array $matches * @return string */ private function urlEncodeChar(array $matches) { return rawurlencode($matches[0]); } } zend-diactoros/README.md 0000644 00000003330 14736103054 0010752 0 ustar 00 # zend-diactoros Master: [![Build status][Master image]][Master] [![Coverage Status][Master coverage image]][Master coverage] Develop: [![Build status][Develop image]][Develop] [![Coverage Status][Develop coverage image]][Develop coverage] > Diactoros (pronunciation: `/dɪʌktɒrɒs/`): an epithet for Hermes, meaning literally, "the messenger." This package supercedes and replaces [phly/http](https://github.com/phly/http). `zend-diactoros` is a PHP package containing implementations of the [accepted PSR-7 HTTP message interfaces](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md), as well as a "server" implementation similar to [node's http.Server](http://nodejs.org/api/http.html). * File issues at https://github.com/zendframework/zend-diactoros/issues * Issue patches to https://github.com/zendframework/zend-diactoros/pulls ## Documentation Documentation is available at: - https://zendframework.github.io/zend-diactoros/ 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 [Master coverage image]: https://img.shields.io/coveralls/zendframework/zend-diactoros/master.svg [Master coverage]: https://coveralls.io/r/zendframework/zend-diactoros?branch=master [Develop]: https://github.com/zendframework/zend-diactoros/tree/develop [Develop image]: https://secure.travis-ci.org/zendframework/zend-diactoros.svg?branch=develop [Develop coverage image]: https://coveralls.io/repos/zendframework/zend-diactoros/badge.svg?branch=develop [Develop coverage]: https://coveralls.io/r/zendframework/zend-diactoros?branch=develop zend-diactoros/CHANGELOG.md 0000644 00000047733 14736103054 0011323 0 ustar 00 # Changelog All notable changes to this project will be documented in this file, in reverse chronological order by release. ## 1.3.11 - TBD ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. ## 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 - [#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 - Nothing. ### Removed - Nothing. ### Fixed - [#71](https://github.com/zendframework/zend-diactoros/pull/71) fixes the docblock of the `JsonResponse` constructor to typehint the `$data` argument as `mixed`. - [#73](https://github.com/zendframework/zend-diactoros/pull/73) changes the behavior in `Request` such that if it marshals a stream during instantiation, the stream is marked as writeable (specifically, mode `wb+`). - [#85](https://github.com/zendframework/zend-diactoros/pull/85) updates the behavior of `Zend\Diactoros\Uri`'s various `with*()` methods that are documented as accepting strings to raise exceptions on non-string input. Previously, several simply passed non-string input on verbatim, others normalized the input, and a few correctly raised the exceptions. Behavior is now consistent across each. - [#87](https://github.com/zendframework/zend-diactoros/pull/87) fixes `UploadedFile` to ensure that `moveTo()` works correctly in non-SAPI environments when the file provided to the constructor is a path. ## 1.1.2 - 2015-07-12 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#67](https://github.com/zendframework/zend-diactoros/pull/67) ensures that the `Stream` class only accepts `stream` resources, not any resource. ## 1.1.1 - 2015-06-25 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#64](https://github.com/zendframework/zend-diactoros/pull/64) fixes the behavior of `JsonResponse` with regards to serialization of `null` and scalar values; the new behavior is to serialize them verbatim, without any casting. ## 1.1.0 - 2015-06-24 ### Added - [#52](https://github.com/zendframework/zend-diactoros/pull/52), [#58](https://github.com/zendframework/zend-diactoros/pull/58), [#59](https://github.com/zendframework/zend-diactoros/pull/59), and [#61](https://github.com/zendframework/zend-diactoros/pull/61) create several custom response types for simplifying response creation: - `Zend\Diactoros\Response\HtmlResponse` accepts HTML content via its constructor, and sets the `Content-Type` to `text/html`. - `Zend\Diactoros\Response\JsonResponse` accepts data to serialize to JSON via its constructor, and sets the `Content-Type` to `application/json`. - `Zend\Diactoros\Response\EmptyResponse` allows creating empty, read-only responses, with a default status code of 204. - `Zend\Diactoros\Response\RedirectResponse` allows specifying a URI for the `Location` header in the constructor, with a default status code of 302. Each also accepts an optional status code, and optional headers (which can also be used to provide an alternate `Content-Type` in the case of the HTML and JSON responses). ### Deprecated - Nothing. ### Removed - [#43](https://github.com/zendframework/zend-diactoros/pull/43) removed both `ServerRequestFactory::marshalUri()` and `ServerRequestFactory::marshalHostAndPort()`, which were deprecated prior to the 1.0 release. ### Fixed - [#29](https://github.com/zendframework/zend-diactoros/pull/29) fixes request method validation to allow any valid token as defined by [RFC 7230](http://tools.ietf.org/html/rfc7230#appendix-B). This allows usage of custom request methods, vs a static, hard-coded list. ## 1.0.5 - 2015-06-24 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#60](https://github.com/zendframework/zend-diactoros/pull/60) fixes the behavior of `UploadedFile` when the `$errorStatus` provided at instantiation is not `UPLOAD_ERR_OK`. Prior to the fix, an `InvalidArgumentException` would occur at instantiation due to the fact that the upload file was missing or invalid. With the fix, no exception is raised until a call to `moveTo()` or `getStream()` is made. ## 1.0.4 - 2015-06-23 This is a security release. A patch has been applied to `Zend\Diactoros\Uri::filterPath()` that ensures that paths can only begin with a single leading slash. This prevents the following potential security issues: - XSS vectors. If the URI path is used for links or form targets, this prevents cases where the first segment of the path resembles a domain name, thus creating scheme-relative links such as `//example.com/foo`. With the patch, the leading double slash is reduced to a single slash, preventing the XSS vector. - Open redirects. If the URI path is used for `Location` or `Link` headers, without a scheme and authority, potential for open redirects exist if clients do not prepend the scheme and authority. Again, preventing a double slash corrects the vector. If you are using `Zend\Diactoros\Uri` for creating links, form targets, or redirect paths, and only using the path segment, we recommend upgrading immediately. ### Added - [#25](https://github.com/zendframework/zend-diactoros/pull/25) adds documentation. Documentation is written in markdown, and can be converted to HTML using [bookdown](http://bookdown.io). New features now MUST include documentation for acceptance. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#51](https://github.com/zendframework/zend-diactoros/pull/51) fixes `MessageTrait::getHeaderLine()` to return an empty string instead of `null` if the header is undefined (which is the behavior specified in PSR-7). - [#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 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`. - If no `Host` header is set, the `$preserveHost` flag MUST be ignored when calling `withUri()` (previously, Diactoros would not set the `Host` header if `$preserveHost` was `true`, but no `Host` header was present). - The request method MUST be a string; it CAN be empty. Previously, Diactoros would return `null`. - The request MUST return a `UriInterface` instance from `getUri()`; that instance CAN be empty. Previously, Diactoros would return `null`; now it lazy-instantiates an empty `Uri` instance on initialization. - [ZF2015-05](http://framework.zend.com/security/advisory/ZF2015-05) was addressed by altering `Uri::filterPath()` to prevent emitting a path prepended with multiple slashes. ## 1.0.3 - 2015-06-04 ### Added - [#48](https://github.com/zendframework/zend-diactoros/pull/48) drops the minimum supported PHP version to 5.4, to allow an easier upgrade path for Symfony 2.7 users, and potential Drupal 8 usage. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. ## 1.0.2 - 2015-06-04 ### Added - [#27](https://github.com/zendframework/zend-diactoros/pull/27) adds phonetic pronunciation of "Diactoros" to the README file. - [#36](https://github.com/zendframework/zend-diactoros/pull/36) adds property annotations to the class-level docblock of `Zend\Diactoros\RequestTrait` to ensure properties inherited from the `MessageTrait` are inherited by implementations. ### Deprecated - Nothing. ### Removed - Nothing. - ### Fixed - [#41](https://github.com/zendframework/zend-diactoros/pull/41) fixes the namespace for test files to begin with `ZendTest` instead of `Zend`. - [#46](https://github.com/zendframework/zend-diactoros/pull/46) ensures that the cookie and query params for the `ServerRequest` implementation are initialized as arrays. - [#47](https://github.com/zendframework/zend-diactoros/pull/47) modifies the internal logic in `HeaderSecurity::isValid()` to use a regular expression instead of character-by-character comparisons, improving performance. ## 1.0.1 - 2015-05-26 ### Added - [#10](https://github.com/zendframework/zend-diactoros/pull/10) adds `Zend\Diactoros\RelativeStream`, which will return stream contents relative to a given offset (i.e., a subset of the stream). `AbstractSerializer` was updated to create a `RelativeStream` when creating the body of a message, which will prevent duplication of the stream in-memory. - [#21](https://github.com/zendframework/zend-diactoros/pull/21) adds a `.gitattributes` file that excludes directories and files not needed for production; this will further minify the package for production use cases. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#9](https://github.com/zendframework/zend-diactoros/pull/9) ensures that attributes are initialized to an empty array, ensuring that attempts to retrieve single attributes when none are defined will not produce errors. - [#14](https://github.com/zendframework/zend-diactoros/pull/14) updates `Zend\Diactoros\Request` to use a `php://temp` stream by default instead of `php://memory`, to ensure requests do not create an out-of-memory condition. - [#15](https://github.com/zendframework/zend-diactoros/pull/15) updates `Zend\Diactoros\Stream` to ensure that write operations trigger an exception if the stream is not writeable. Additionally, it adds more robust logic for determining if a stream is writeable. ## 1.0.0 - 2015-05-21 First stable release, and first release as `zend-diactoros`. ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - Nothing. zend-diactoros/LICENSE.md 0000644 00000002742 14736103054 0011105 0 ustar 00 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: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Zend Technologies USA, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zend-diactoros/.coveralls.yml 0000644 00000000075 14736103054 0012271 0 ustar 00 coverage_clover: clover.xml json_path: coveralls-upload.json