diff -Nru php-guzzlehttp-psr7-1.4.2/CHANGELOG.md php-guzzlehttp-psr7-1.7.0/CHANGELOG.md --- php-guzzlehttp-psr7-1.4.2/CHANGELOG.md 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/CHANGELOG.md 2021-01-08 03:18:18.000000000 +0000 @@ -1,32 +1,126 @@ -# CHANGELOG +# Change Log -## 1.4.2 - 2017-03-20 -* Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## [Unreleased] + +## [1.7.0] - 2020-09-30 + +### Added + +- Replaced functions by static methods + +### Fixed + +- Converting a non-seekable stream to a string +- Handle multiple Set-Cookie correctly +- Ignore array keys in header values when merging +- Allow multibyte characters to be parsed in `Message:bodySummary()` + +### Changed + +- Restored partial HHVM 3 support + + +## [1.6.1] - 2019-07-02 + +### Fixed + +- Accept null and bool header values again + + +## [1.6.0] - 2019-06-30 + +### Added + +- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244) +- Added MIME type for WEBP image format (#246) +- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272) + +### Changed + +- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262) +- Accept port number 0 to be valid (#270) + +### Fixed + +- Fixed subsequent reads from `php://input` in ServerRequest (#247) +- Fixed readable/writable detection for certain stream modes (#248) +- Fixed encoding of special characters in the `userInfo` component of an URI (#253) + + +## [1.5.2] - 2018-12-04 + +### Fixed + +- Check body size when getting the message summary + + +## [1.5.1] - 2018-12-04 + +### Fixed + +- Get the summary of a body only if it is readable + + +## [1.5.0] - 2018-12-03 + +### Added + +- Response first-line to response string exception (fixes #145) +- A test for #129 behavior +- `get_message_body_summary` function in order to get the message summary +- `3gp` and `mkv` mime types + +### Changed + +- Clarify exception message when stream is detached + +### Deprecated + +- Deprecated parsing folded header lines as per RFC 7230 + +### Fixed + +- Fix `AppendStream::detach` to not close streams +- `InflateStream` preserves `isSeekable` attribute of the underlying stream +- `ServerRequest::getUriFromGlobals` to support URLs in query parameters + + +Several other fixes and improvements. + + +## [1.4.2] - 2017-03-20 + +### Fixed + +- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing calls to `trigger_error` when deprecated methods are invoked. -## 1.4.1 - 2017-02-27 -* Reverted BC break by reintroducing behavior to automagically fix a URI with a +## [1.4.1] - 2017-02-27 + +### Added + +- Rriggering of silenced deprecation warnings. + +### Fixed + +- Reverted BC break by reintroducing behavior to automagically fix a URI with a relative path and an authority by adding a leading slash to the path. It's only deprecated now. -* Added triggering of silenced deprecation warnings. -## 1.4.0 - 2017-02-21 -* Fix `Stream::read` when length parameter <= 0. -* `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. -* Fix `ServerRequest::getUriFromGlobals` when `Host` header contains port. -* Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. -* Allow `parse_response` to parse a response without delimiting space and reason. -* Ensure each URI modification results in a valid URI according to PSR-7 discussions. - Invalid modifications will throw an exception instead of returning a wrong URI or - doing some magic. - - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception - because the path of a URI with an authority must start with a slash "/" or be empty - - `(new Uri())->withScheme('http')` will return `'http://localhost'` -* Fix compatibility of URIs with `file` scheme and empty host. -* Added common URI utility methods based on RFC 3986 (see documentation in the readme): +## [1.4.0] - 2017-02-21 + +### Added + +- Added common URI utility methods based on RFC 3986 (see documentation in the readme): - `Uri::isDefaultPort` - `Uri::isAbsolute` - `Uri::isNetworkPathReference` @@ -37,69 +131,117 @@ - `UriNormalizer::normalize` - `UriNormalizer::isEquivalent` - `UriResolver::relativize` -* Deprecated `Uri::resolve` in favor of `UriResolver::resolve` -* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` -## 1.3.1 - 2016-06-25 +### Changed + +- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. +- Allow `parse_response` to parse a response without delimiting space and reason. +- Ensure each URI modification results in a valid URI according to PSR-7 discussions. + Invalid modifications will throw an exception instead of returning a wrong URI or + doing some magic. + - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. -* Fix `Uri::__toString` for network path references, e.g. `//example.org`. -* Fix missing lowercase normalization for host. -* Fix handling of URI components in case they are `'0'` in a lot of places, + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, e.g. as a user info password. -* Fix `Uri::withAddedHeader` to correctly merge headers with different case. -* Fix trimming of header values in `Uri::withAddedHeader`. Header values may +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may be surrounded by whitespace which should be ignored according to RFC 7230 Section 3.2.4. This does not apply to header names. -* Fix `Uri::withAddedHeader` with an array of header values. -* Fix `Uri::resolve` when base path has no slash and handling of fragment. -* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the key/value both in encoded as well as decoded form to those methods. This is consistent with withPath, withQuery etc. -* Fix `ServerRequest::withoutAttribute` when attribute value is null. +- `ServerRequest::withoutAttribute` when attribute value is null. -## 1.3.0 - 2016-04-13 -* Added remaining interfaces needed for full PSR7 compatibility +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility (ServerRequestInterface, UploadedFileInterface, etc.). -* Added support for stream_for from scalars. -* Can now extend Uri. -* Fixed a bug in validating request methods by making it more permissive. +- Support for stream_for from scalars. + +### Changed -## 1.2.3 - 2016-02-18 +- Can now extend Uri. -* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote streams, which can sometimes return fewer bytes than requested with `fread`. -* Fixed handling of gzipped responses with FNAME headers. +- Handling of gzipped responses with FNAME headers. + -## 1.2.2 - 2016-01-22 +## [1.2.2] - 2016-01-22 -* Added support for URIs without any authority. -* Added support for HTTP 451 'Unavailable For Legal Reasons.' -* Added support for using '0' as a filename. -* Added support for including non-standard ports in Host headers. +### Added -## 1.2.1 - 2015-11-02 +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. -* Now supporting negative offsets when seeking to SEEK_END. -## 1.2.0 - 2015-08-15 +## [1.2.1] - 2015-11-02 -* Body as `"0"` is now properly added to a response. -* Now allowing forward seeking in CachingStream. -* Now properly parsing HTTP requests that contain proxy targets in +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in `parse_request`. -* functions.php is now conditionally required. -* user-info is no longer dropped when resolving URIs. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + -## 1.1.0 - 2015-06-24 +## [1.1.0] - 2015-06-24 -* URIs can now be relative. -* `multipart/form-data` headers are now overridden case-insensitively. -* URI paths no longer encode the following characters because they are allowed +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed in URIs: "(", ")", "*", "!", "'" -* A port is no longer added to a URI when the scheme is missing and no port is +- A port is no longer added to a URI when the scheme is missing and no port is present. + ## 1.0.0 - 2015-05-19 Initial release. @@ -108,3 +250,21 @@ - `Psr\Http\Message\ServerRequestInterface` - `Psr\Http\Message\UploadedFileInterface` + + + +[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff -Nru php-guzzlehttp-psr7-1.4.2/composer.json php-guzzlehttp-psr7-1.7.0/composer.json --- php-guzzlehttp-psr7-1.4.2/composer.json 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/composer.json 2021-01-08 03:18:18.000000000 +0000 @@ -2,7 +2,7 @@ "name": "guzzlehttp/psr7", "type": "library", "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": ["request", "response", "message", "stream", "http", "uri", "url"], + "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"], "license": "MIT", "authors": [ { @@ -17,23 +17,33 @@ ], "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10", + "ext-zlib": "*" }, "provide": { "psr/http-message-implementation": "1.0" }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" }, "files": ["src/functions_include.php"] }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.7-dev" } } } diff -Nru php-guzzlehttp-psr7-1.4.2/debian/autoloaders/php-guzzlehttp-psr7 php-guzzlehttp-psr7-1.7.0/debian/autoloaders/php-guzzlehttp-psr7 --- php-guzzlehttp-psr7-1.4.2/debian/autoloaders/php-guzzlehttp-psr7 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/autoloaders/php-guzzlehttp-psr7 2021-01-08 18:14:53.000000000 +0000 @@ -0,0 +1 @@ +guzzlehttp psr7 GuzzleHttp/Psr7/autoload.php diff -Nru php-guzzlehttp-psr7-1.4.2/debian/autoload.php.tpl php-guzzlehttp-psr7-1.7.0/debian/autoload.php.tpl --- php-guzzlehttp-psr7-1.4.2/debian/autoload.php.tpl 2016-07-18 16:32:26.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/autoload.php.tpl 2021-01-26 02:11:59.000000000 +0000 @@ -1,6 +1,7 @@ Tue, 05 Jan 2021 12:37:08 +0100 + -- David Prévot Mon, 25 Jan 2021 22:21:04 -0400 + +php-guzzlehttp-psr7 (1.7.0-1~deb11u2) unstable; urgency=medium + + * Fix test for recent versions of PHP (Closes: #980664) + + -- David Prévot Wed, 20 Jan 2021 18:41:37 -0400 + +php-guzzlehttp-psr7 (1.7.0-1~deb11u1) unstable; urgency=medium + + * Upload to unstable without php-getallheaders dependency + * Bundle php-getallheaders being processed in NEW + + -- David Prévot Thu, 14 Jan 2021 09:39:54 -0400 + +php-guzzlehttp-psr7 (1.7.0-1~exp1) experimental; urgency=medium + + * Upload to experimental since php-getallheaders is not yet available + + [ Sam Reed ] + * Allow ralouphie/getallheaders ^3.0.0 + + [ Tobias Nyholm ] + * Prepare for 1.7.0 release (#353) + + [ David Prévot ] + * Document gbp import-ref usage + * Restore autoload silently removed in previous NMU (Closes: #949693) + * Use secure copyright file specification URI. + * Bump debhelper from old 9 to 13. + * Set debhelper-compat version in Build-Depends. + * Set upstream metadata fields: + Bug-Database, Repository, Repository-Browse, Bug-Submit. + * Update Vcs-* headers to use salsa repository. + * Remove unnecessary get-orig-source-target. + * Drop versioned dependency satisfied in (old)stable + * Install /u/s/p/a/php-guzzlehttp-psr7 + * Update watch file format version to 4. + * Set Rules-Requires-Root: no. + * Update Standards-Version to 4.5.1 + * Update copyright + * Restore test suite at build time + * Add php-getallheaders (build-)dependency + + -- David Prévot Fri, 08 Jan 2021 14:35:01 -0400 php-guzzlehttp-psr7 (1.4.2-0.1) unstable; urgency=medium diff -Nru php-guzzlehttp-psr7-1.4.2/debian/clean php-guzzlehttp-psr7-1.7.0/debian/clean --- php-guzzlehttp-psr7-1.4.2/debian/clean 2016-07-18 16:31:57.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/clean 2021-01-08 18:14:54.000000000 +0000 @@ -1 +1,4 @@ +.phpunit.result.cache +GuzzleHttp/ src/autoload.php +vendor/ diff -Nru php-guzzlehttp-psr7-1.4.2/debian/compat php-guzzlehttp-psr7-1.7.0/debian/compat --- php-guzzlehttp-psr7-1.4.2/debian/compat 2016-07-18 16:31:57.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -9 diff -Nru php-guzzlehttp-psr7-1.4.2/debian/control php-guzzlehttp-psr7-1.7.0/debian/control --- php-guzzlehttp-psr7-1.4.2/debian/control 2018-12-02 17:42:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/control 2021-01-26 02:19:48.000000000 +0000 @@ -3,13 +3,17 @@ Priority: optional Maintainer: Debian PHP PEAR Maintainers Uploaders: David Prévot -Build-Depends: debhelper (>= 9), +Build-Depends: debhelper-compat (= 13), + php-getallheaders, php-psr-http-message, - pkg-php-tools (>= 1.7~) -Standards-Version: 3.9.8 + phpab, + phpunit, + pkg-php-tools +Standards-Version: 4.5.1 Homepage: https://github.com/guzzle/psr7 -Vcs-Git: git://anonscm.debian.org/pkg-php/php-guzzlehttp-psr7.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-php/php-guzzlehttp-psr7.git +Vcs-Git: https://salsa.debian.org/php-team/pear/php-guzzlehttp-psr7.git +Vcs-Browser: https://salsa.debian.org/php-team/pear/php-guzzlehttp-psr7 +Rules-Requires-Root: no Package: php-guzzlehttp-psr7 Architecture: all diff -Nru php-guzzlehttp-psr7-1.4.2/debian/copyright php-guzzlehttp-psr7-1.7.0/debian/copyright --- php-guzzlehttp-psr7-1.4.2/debian/copyright 2016-07-18 16:32:26.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/copyright 2021-01-26 02:19:48.000000000 +0000 @@ -1,10 +1,11 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: PSR-7 Message Implementation Upstream-Contact: Michael Dowling Source: https://github.com/guzzle/psr7 Files: * Copyright: 2015, Michael Dowling + 2018, amphp License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -Nru php-guzzlehttp-psr7-1.4.2/debian/gbp.conf php-guzzlehttp-psr7-1.7.0/debian/gbp.conf --- php-guzzlehttp-psr7-1.4.2/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/gbp.conf 2021-01-08 03:18:22.000000000 +0000 @@ -0,0 +1,9 @@ +[DEFAULT] +pristine-tar = True +pristine-tar-commit = True +debian-branch = debian/latest + +## Once --filter support gets added to gbp import-ref, we should be able +## to simplify the workflow and ignore the upstream branch. +# filter = [ '.gitattributes' ] +# upstream-tag = %(version%~%-)s diff -Nru php-guzzlehttp-psr7-1.4.2/debian/install php-guzzlehttp-psr7-1.7.0/debian/install --- php-guzzlehttp-psr7-1.4.2/debian/install 2016-07-18 16:31:57.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/install 2021-01-08 18:14:53.000000000 +0000 @@ -1 +1,2 @@ -src/* usr/share/php/GuzzleHttp/Psr7 +debian/autoloaders usr/share/pkg-php-tools +src/* usr/share/php/GuzzleHttp/Psr7 diff -Nru php-guzzlehttp-psr7-1.4.2/debian/patches/0002-Fixed-bad-test-355.patch php-guzzlehttp-psr7-1.7.0/debian/patches/0002-Fixed-bad-test-355.patch --- php-guzzlehttp-psr7-1.4.2/debian/patches/0002-Fixed-bad-test-355.patch 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/patches/0002-Fixed-bad-test-355.patch 2021-01-20 22:40:46.000000000 +0000 @@ -0,0 +1,30 @@ +From: Graham Campbell +Date: Thu, 22 Oct 2020 08:42:05 +0100 +Subject: Fixed bad test (#355) + +PHP 7.3.24, 7.4.12 and 8.0.0RC1 or higher now allow zero as a port: php/php-src@81b2f3e. + +Origin: upstream, https://github.com/guzzle/psr7/commit/25f7f893f0b52b7b14e244a16679d72b1f0088de +Bug-Debian: https://bugs.debian.org/980664 +--- + tests/UriTest.php | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tests/UriTest.php b/tests/UriTest.php +index 3953e82..379a8e7 100644 +--- a/tests/UriTest.php ++++ b/tests/UriTest.php +@@ -130,11 +130,11 @@ class UriTest extends BaseTest + (new Uri())->withPort(-1); + } + +- public function testParseUriPortCannotBeZero() ++ public function testParseUriPortCannotBeNegative() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Unable to parse URI'); + +- new Uri('//example.com:0'); ++ new Uri('//example.com:-1'); + } + + public function testSchemeMustHaveCorrectType() diff -Nru php-guzzlehttp-psr7-1.4.2/debian/patches/series php-guzzlehttp-psr7-1.7.0/debian/patches/series --- php-guzzlehttp-psr7-1.4.2/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/patches/series 2021-01-26 02:12:14.000000000 +0000 @@ -0,0 +1 @@ +0002-Fixed-bad-test-355.patch diff -Nru php-guzzlehttp-psr7-1.4.2/debian/README.source php-guzzlehttp-psr7-1.7.0/debian/README.source --- php-guzzlehttp-psr7-1.4.2/debian/README.source 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/README.source 2021-01-08 03:18:22.000000000 +0000 @@ -0,0 +1,8 @@ +Starting with 1.7.0, we are merging the signed upstream tags as +upstream, instead of using the tarball that doesn’t ship tests. + +# Example to import upstream 1.8.0 version +version=1.8.0 +# We still use the upstream branch to drop the .gitattributes file from upstream +gbp import-ref --upstream-tag=%\(version%~%-\)s -u$version --debian-branch=upstream +gbp import-ref --upstream-tree=BRANCH -u$version diff -Nru php-guzzlehttp-psr7-1.4.2/debian/rules php-guzzlehttp-psr7-1.7.0/debian/rules --- php-guzzlehttp-psr7-1.4.2/debian/rules 2018-12-02 17:42:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/rules 2021-01-08 18:14:54.000000000 +0000 @@ -2,5 +2,17 @@ %: dh $@ --with phpcomposer -get-orig-source: - uscan --verbose --rename --force +override_dh_auto_build: + phpab \ + --output src/autoload.php \ + --template debian/autoload.php.tpl \ + src + mkdir --parents vendor GuzzleHttp + phpab \ + --output vendor/autoload.php \ + --template debian/autoload.tests.php.tpl \ + tests + ln -s ../src GuzzleHttp/Psr7 + +override_dh_auto_test: + phpunit diff -Nru php-guzzlehttp-psr7-1.4.2/debian/tests/control php-guzzlehttp-psr7-1.7.0/debian/tests/control --- php-guzzlehttp-psr7-1.4.2/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/tests/control 2021-01-08 18:43:29.000000000 +0000 @@ -0,0 +1,3 @@ +Test-Command: mkdir -p vendor && phpab -o vendor/autoload.php -t debian/autoload.tests.php.tpl tests && phpunit +Restrictions: rw-build-tree +Depends: php-symfony-console, phpab, phpunit, @ diff -Nru php-guzzlehttp-psr7-1.4.2/debian/upstream/metadata php-guzzlehttp-psr7-1.7.0/debian/upstream/metadata --- php-guzzlehttp-psr7-1.4.2/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/upstream/metadata 2021-01-08 03:18:22.000000000 +0000 @@ -0,0 +1,4 @@ +Bug-Database: https://github.com/guzzle/psr7/issues +Bug-Submit: https://github.com/guzzle/psr7/issues/new +Repository: https://github.com/guzzle/psr7.git +Repository-Browse: https://github.com/guzzle/psr7 diff -Nru php-guzzlehttp-psr7-1.4.2/debian/watch php-guzzlehttp-psr7-1.7.0/debian/watch --- php-guzzlehttp-psr7-1.4.2/debian/watch 2016-07-18 16:31:57.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/debian/watch 2021-01-08 18:14:53.000000000 +0000 @@ -1,4 +1,4 @@ -version=3 +version=4 options=uversionmangle=s/-?([^\d.])\.?/~$1/i;tr/A-Z/a-z/ \ https://github.com/guzzle/psr7/releases \ .*/archive/v?(\d.+).tar.gz diff -Nru php-guzzlehttp-psr7-1.4.2/.editorconfig php-guzzlehttp-psr7-1.7.0/.editorconfig --- php-guzzlehttp-psr7-1.4.2/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/.editorconfig 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff -Nru php-guzzlehttp-psr7-1.4.2/.gitignore php-guzzlehttp-psr7-1.7.0/.gitignore --- php-guzzlehttp-psr7-1.4.2/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/.gitignore 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,7 @@ +artifacts/ +vendor/ +.php_cs +.php_cs.cache +.phpunit.result.cache +composer.lock +phpunit.xml diff -Nru php-guzzlehttp-psr7-1.4.2/Makefile php-guzzlehttp-psr7-1.7.0/Makefile --- php-guzzlehttp-psr7-1.4.2/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/Makefile 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,29 @@ +all: clean test + +test: + vendor/bin/phpunit $(TEST) + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST) + +view-coverage: + open artifacts/coverage/index.html + +check-tag: + $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) + +tag: check-tag + @echo Tagging $(TAG) + chag update $(TAG) + git commit -a -m '$(TAG) release' + chag tag + @echo "Release has been created. Push using 'make release'" + @echo "Changes made in the release commit" + git diff HEAD~1 HEAD + +release: check-tag + git push origin master + git push origin $(TAG) + +clean: + rm -rf artifacts/* diff -Nru php-guzzlehttp-psr7-1.4.2/phpunit.xml.dist php-guzzlehttp-psr7-1.7.0/phpunit.xml.dist --- php-guzzlehttp-psr7-1.4.2/phpunit.xml.dist 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/phpunit.xml.dist 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,19 @@ + + + + + tests/ + + + + + src/ + + + diff -Nru php-guzzlehttp-psr7-1.4.2/README.md php-guzzlehttp-psr7-1.7.0/README.md --- php-guzzlehttp-psr7-1.4.2/README.md 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/README.md 2021-01-08 03:18:18.000000000 +0000 @@ -23,11 +23,11 @@ ```php use GuzzleHttp\Psr7; -$a = Psr7\stream_for('abc, '); -$b = Psr7\stream_for('123.'); +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); $composed = new Psr7\AppendStream([$a, $b]); -$composed->addStream(Psr7\stream_for(' Above all listen to me')); +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); echo $composed; // abc, 123. Above all listen to me. ``` @@ -65,7 +65,7 @@ ```php use GuzzleHttp\Psr7; -$original = Psr7\stream_for(fopen('http://www.google.com', 'r')); +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); $stream = new Psr7\CachingStream($original); $stream->read(1024); @@ -89,7 +89,7 @@ use GuzzleHttp\Psr7; // Create an empty stream -$stream = Psr7\stream_for(); +$stream = Psr7\Utils::streamFor(); // Start dropping data when the stream has more than 10 bytes $dropping = new Psr7\DroppingStream($stream, 10); @@ -112,7 +112,7 @@ use GuzzleHttp\Psr7; -$stream = Psr7\stream_for('hi'); +$stream = Psr7\Utils::streamFor('hi'); $fnStream = Psr7\FnStream::decorate($stream, [ 'rewind' => function () use ($stream) { echo 'About to rewind - '; @@ -167,7 +167,7 @@ ```php use GuzzleHttp\Psr7; -$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+')); +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); echo $original->getSize(); // >>> 1048576 @@ -197,7 +197,7 @@ ```php use GuzzleHttp\Psr7; -$original = Psr7\stream_for('foo'); +$original = Psr7\Utils::streamFor('foo'); $noSeek = new Psr7\NoSeekStream($original); echo $noSeek->read(3); @@ -271,7 +271,7 @@ ```php use GuzzleHttp\Psr7; -$original = Psr7\stream_for('foo'); +$original = Psr7\Utils::streamFor('foo'); $eofStream = new EofCallbackStream($original, function () { echo 'EOF!'; @@ -297,228 +297,292 @@ ```php use GuzzleHttp\Psr7\StreamWrapper; -$stream = GuzzleHttp\Psr7\stream_for('hello!'); +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); $resource = StreamWrapper::getResource($stream); echo fread($resource, 6); // outputs hello! ``` -# Function API +# Static API -There are various functions available under the `GuzzleHttp\Psr7` namespace. +There are various static methods available under the `GuzzleHttp\Psr7` namespace. -## `function str` +## `GuzzleHttp\Psr7\Message::toString` -`function str(MessageInterface $message)` +`public static function toString(MessageInterface $message): string` Returns the string representation of an HTTP message. ```php $request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); -echo GuzzleHttp\Psr7\str($request); +echo GuzzleHttp\Psr7\Message::toString($request); ``` -## `function uri_for` +## `GuzzleHttp\Psr7\Message::bodySummary` -`function uri_for($uri)` +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` -This function accepts a string or `Psr\Http\Message\UriInterface` and returns a -UriInterface for the given value. If the value is already a `UriInterface`, it -is returned as-is. +Get a short summary of the message body. -```php -$uri = GuzzleHttp\Psr7\uri_for('http://example.com'); -assert($uri === GuzzleHttp\Psr7\uri_for($uri)); -``` +Will return `null` if the response is not printable. -## `function stream_for` +## `GuzzleHttp\Psr7\Message::rewindBody` -`function stream_for($resource = '', array $options = [])` +`public static function rewindBody(MessageInterface $message): void` -Create a new stream based on the input type. +Attempts to rewind a message body and throws an exception on failure. -Options is an associative array that can contain the following keys: +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. -* - metadata: Array of custom metadata. -* - size: Size of the stream. -This method accepts the following `$resource` types: +## `GuzzleHttp\Psr7\Message::parseMessage` -- `Psr\Http\Message\StreamInterface`: Returns the value as-is. -- `string`: Creates a stream object that uses the given string as the contents. -- `resource`: Creates a stream object that wraps the given PHP stream resource. -- `Iterator`: If the provided value implements `Iterator`, then a read-only - stream object will be created that wraps the given iterable. Each time the - stream is read from, data from the iterator will fill a buffer and will be - continuously called until the buffer is equal to the requested read size. - Subsequent read calls will first read from the buffer and then call `next` - on the underlying iterator until it is exhausted. -- `object` with `__toString()`: If the object has the `__toString()` method, - the object will be cast to a string and then a stream will be returned that - uses the string value. -- `NULL`: When `null` is passed, an empty stream object is returned. -- `callable` When a callable is passed, a read-only stream object will be - created that invokes the given callable. The callable is invoked with the - number of suggested bytes to read. The callable can return any number of - bytes, but MUST return `false` when there is no more data to return. The - stream object that wraps the callable will invoke the callable until the - number of requested bytes are available. Any additional bytes will be - buffered and used in subsequent reads. +`public static function parseMessage(string $message): array` -```php -$stream = GuzzleHttp\Psr7\stream_for('foo'); -$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r')); +Parses an HTTP message into an associative array. -$generator function ($bytes) { - for ($i = 0; $i < $bytes; $i++) { - yield ' '; - } -} +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. -$stream = GuzzleHttp\Psr7\stream_for($generator(100)); -``` +## `GuzzleHttp\Psr7\Message::parseRequestUri` -## `function parse_header` +`public static function parseRequestUri(string $path, array $headers): string` -`function parse_header($header)` +Constructs a URI for an HTTP request message. -Parse an array of header values containing ";" separated data into an array of -associative arrays representing the header key value pair data of the header. -When a parameter does not contain a value, but just contains a key, this -function will inject a key with a '' string value. +## `GuzzleHttp\Psr7\Message::parseRequest` -## `function normalize_header` +`public static function parseRequest(string $message): Request` -`function normalize_header($header)` +Parses a request message string into a request object. -Converts an array of header values that may contain comma separated headers -into an array of headers with no comma separated values. +## `GuzzleHttp\Psr7\Message::parseResponse` -## `function modify_request` +`public static function parseResponse(string $message): Response` -`function modify_request(RequestInterface $request, array $changes)` +Parses a response message string into a response object. -Clone and modify a request with the given changes. This method is useful for -reducing the number of clones needed to mutate a message. -The changes can be one of: +## `GuzzleHttp\Psr7\Header::parse` -- method: (string) Changes the HTTP method. -- set_headers: (array) Sets the given headers. -- remove_headers: (array) Remove the given headers. -- body: (mixed) Sets the given body. -- uri: (UriInterface) Set the URI. -- query: (string) Set the query string value of the URI. -- version: (string) Set the protocol version. +`public static function parse(string|array $header): array` +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. -## `function rewind_body` -`function rewind_body(MessageInterface $message)` +## `GuzzleHttp\Psr7\Header::normalize` -Attempts to rewind a message body and throws an exception on failure. The body -of the message will only be rewound if a call to `tell()` returns a value other -than `0`. +`public static function normalize(string|array $header): array` +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. -## `function try_fopen` -`function try_fopen($filename, $mode)` +## `GuzzleHttp\Psr7\Query::parse` -Safely opens a PHP stream resource using a filename. +`public static function parse(string $str, int|bool $urlEncoding = true): array` -When fopen fails, PHP normally raises a warning. This function adds an error -handler that checks for errors and throws an exception instead. +Parse a query string into an associative array. +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. -## `function copy_to_string` -`function copy_to_string(StreamInterface $stream, $maxLen = -1)` +## `GuzzleHttp\Psr7\Query::build` -Copy the contents of a stream into a string until the given number of bytes -have been read. +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. -## `function copy_to_stream` +## `GuzzleHttp\Psr7\Utils::copyToStream` -`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)` +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` -Copy the contents of a stream into another stream until the given number of +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of bytes have been read. -## `function hash` +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. -`function hash(StreamInterface $stream, $algo, $rawOutput = false)` +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. -Calculate a hash of a Stream. This method reads the entire stream to calculate -a rolling hash (based on PHP's hash_init functions). +## `GuzzleHttp\Psr7\Utils::modifyRequest` -## `function readline` +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` -`function readline(StreamInterface $stream, $maxLength = null)` +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` Read a line from the stream up to the maximum allowed buffer length. -## `function parse_request` +## `GuzzleHttp\Psr7\Utils::streamFor` -`function parse_request($message)` +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` -Parses a request message string into a request object. +Create a new stream based on the input type. +Options is an associative array that can contain the following keys: -## `function parse_response` +- metadata: Array of custom metadata. +- size: Size of the stream. -`function parse_response($message)` +This method accepts the following `$resource` types: -Parses a response message string into a response object. +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); -## `function parse_query` +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} -`function parse_query($str, $urlEncoding = true)` +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` -Parse a query string into an associative array. -If multiple values are found for the same key, the value of that key value pair -will become an array. This function does not parse nested PHP style arrays into -an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into -`['foo[a]' => '1', 'foo[b]' => '2']`). +## `GuzzleHttp\Psr7\Utils::tryFopen` +`public static function tryFopen(string $filename, string $mode): resource` -## `function build_query` +Safely opens a PHP stream resource using a filename. -`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)` +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. -Build a query string from an array of key value pairs. -This function can use the return value of parse_query() to build a query string. -This function does not modify the provided keys when an array is encountered -(like http_build_query would). +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. -## `function mimetype_from_filename` +## `GuzzleHttp\Psr7\MimeType::fromFilename` -`function mimetype_from_filename($filename)` +`public static function fromFilename(string $filename): string|null` Determines the mimetype of a file by looking at its extension. -## `function mimetype_from_extension` +## `GuzzleHttp\Psr7\MimeType::fromExtension` -`function mimetype_from_extension($extension)` +`public static function fromExtension(string $extension): string|null` Maps a file extensions to a mimetype. +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + # Additional URI Methods Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, @@ -606,6 +670,12 @@ provided key are removed and replaced with the given key value pair. A value of null will set the query string key without a value, e.g. "key" instead of "key=value". +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. ### `GuzzleHttp\Psr7\Uri::withoutQueryValue` diff -Nru php-guzzlehttp-psr7-1.4.2/src/AppendStream.php php-guzzlehttp-psr7-1.7.0/src/AppendStream.php --- php-guzzlehttp-psr7-1.4.2/src/AppendStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/AppendStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ pos = $this->current = 0; + $this->seekable = true; foreach ($this->streams as $stream) { $stream->close(); @@ -82,14 +83,24 @@ } /** - * Detaches each attached stream + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. * * {@inheritdoc} */ public function detach() { - $this->close(); - $this->detached = true; + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; } public function tell() diff -Nru php-guzzlehttp-psr7-1.4.2/src/BufferStream.php php-guzzlehttp-psr7-1.7.0/src/BufferStream.php --- php-guzzlehttp-psr7-1.4.2/src/BufferStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/BufferStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ close(); + + return null; } public function getSize() diff -Nru php-guzzlehttp-psr7-1.4.2/src/CachingStream.php php-guzzlehttp-psr7-1.7.0/src/CachingStream.php --- php-guzzlehttp-psr7-1.4.2/src/CachingStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/CachingStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ 'strlen']); - copy_to_stream($this, $target); + Utils::copyToStream($this, $target); return $this->tell(); } diff -Nru php-guzzlehttp-psr7-1.4.2/src/DroppingStream.php php-guzzlehttp-psr7-1.7.0/src/DroppingStream.php --- php-guzzlehttp-psr7-1.4.2/src/DroppingStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/DroppingStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ getMethod() . ' ' - . $message->getRequestTarget()) - . ' HTTP/' . $message->getProtocolVersion(); - if (!$message->hasHeader('host')) { - $msg .= "\r\nHost: " . $message->getUri()->getHost(); - } - } elseif ($message instanceof ResponseInterface) { - $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' - . $message->getStatusCode() . ' ' - . $message->getReasonPhrase(); - } else { - throw new \InvalidArgumentException('Unknown message type'); - } - - foreach ($message->getHeaders() as $name => $values) { - $msg .= "\r\n{$name}: " . implode(', ', $values); - } - - return "{$msg}\r\n\r\n" . $message->getBody(); + return Message::toString($message); } /** * Returns a UriInterface for the given value. * - * This function accepts a string or {@see Psr\Http\Message\UriInterface} and - * returns a UriInterface for the given value. If the value is already a - * `UriInterface`, it is returned as-is. + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. * * @param string|UriInterface $uri * * @return UriInterface + * * @throws \InvalidArgumentException + * + * @deprecated uri_for will be removed in guzzlehttp/psr7:2.0. Use Utils::uriFor instead. */ function uri_for($uri) { - if ($uri instanceof UriInterface) { - return $uri; - } elseif (is_string($uri)) { - return new Uri($uri); - } - - throw new \InvalidArgumentException('URI must be a string or UriInterface'); + return Utils::uriFor($uri); } /** @@ -69,86 +48,57 @@ * - metadata: Array of custom metadata. * - size: Size of the stream. * - * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data - * @param array $options Additional options + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data + * @param array $options Additional options + * + * @return StreamInterface * - * @return Stream * @throws \InvalidArgumentException if the $resource arg is not valid. + * + * @deprecated stream_for will be removed in guzzlehttp/psr7:2.0. Use Utils::streamFor instead. */ function stream_for($resource = '', array $options = []) { - if (is_scalar($resource)) { - $stream = fopen('php://temp', 'r+'); - if ($resource !== '') { - fwrite($stream, $resource); - fseek($stream, 0); - } - return new Stream($stream, $options); - } - - switch (gettype($resource)) { - case 'resource': - return new Stream($resource, $options); - case 'object': - if ($resource instanceof StreamInterface) { - return $resource; - } elseif ($resource instanceof \Iterator) { - return new PumpStream(function () use ($resource) { - if (!$resource->valid()) { - return false; - } - $result = $resource->current(); - $resource->next(); - return $result; - }, $options); - } elseif (method_exists($resource, '__toString')) { - return stream_for((string) $resource, $options); - } - break; - case 'NULL': - return new Stream(fopen('php://temp', 'r+'), $options); - } - - if (is_callable($resource)) { - return new PumpStream($resource, $options); - } - - throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + return Utils::streamFor($resource, $options); } /** * Parse an array of header values containing ";" separated data into an - * array of associative arrays representing the header key value pair - * data of the header. When a parameter does not contain a value, but just + * array of associative arrays representing the header key value pair data + * of the header. When a parameter does not contain a value, but just * contains a key, this function will inject a key with a '' string value. * * @param string|array $header Header to parse into components. * * @return array Returns the parsed header values. + * + * @deprecated parse_header will be removed in guzzlehttp/psr7:2.0. Use Header::parse instead. */ function parse_header($header) { - static $trimmed = "\"' \n\t\r"; - $params = $matches = []; - - foreach (normalize_header($header) as $val) { - $part = []; - foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { - if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { - $m = $matches[0]; - if (isset($m[1])) { - $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); - } else { - $part[] = trim($m[0], $trimmed); - } - } - } - if ($part) { - $params[] = $part; - } - } - - return $params; + return Header::parse($header); } /** @@ -158,32 +108,20 @@ * @param string|array $header Header to normalize. * * @return array Returns the normalized header field values. + * + * @deprecated normalize_header will be removed in guzzlehttp/psr7:2.0. Use Header::normalize instead. */ function normalize_header($header) { - if (!is_array($header)) { - return array_map('trim', explode(',', $header)); - } - - $result = []; - foreach ($header as $value) { - foreach ((array) $value as $v) { - if (strpos($v, ',') === false) { - $result[] = $v; - continue; - } - foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { - $result[] = trim($vv); - } - } - } - - return $result; + return Header::normalize($header); } /** * Clone and modify a request with the given changes. * + * This method is useful for reducing the number of clones needed to mutate a + * message. + * * The changes can be one of: * - method: (string) Changes the HTTP method. * - set_headers: (array) Sets the given headers. @@ -197,68 +135,12 @@ * @param array $changes Changes to apply. * * @return RequestInterface + * + * @deprecated modify_request will be removed in guzzlehttp/psr7:2.0. Use Utils::modifyRequest instead. */ function modify_request(RequestInterface $request, array $changes) { - if (!$changes) { - return $request; - } - - $headers = $request->getHeaders(); - - if (!isset($changes['uri'])) { - $uri = $request->getUri(); - } else { - // Remove the host header if one is on the URI - if ($host = $changes['uri']->getHost()) { - $changes['set_headers']['Host'] = $host; - - if ($port = $changes['uri']->getPort()) { - $standardPorts = ['http' => 80, 'https' => 443]; - $scheme = $changes['uri']->getScheme(); - if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { - $changes['set_headers']['Host'] .= ':'.$port; - } - } - } - $uri = $changes['uri']; - } - - if (!empty($changes['remove_headers'])) { - $headers = _caseless_remove($changes['remove_headers'], $headers); - } - - if (!empty($changes['set_headers'])) { - $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); - $headers = $changes['set_headers'] + $headers; - } - - if (isset($changes['query'])) { - $uri = $uri->withQuery($changes['query']); - } - - if ($request instanceof ServerRequestInterface) { - return new ServerRequest( - isset($changes['method']) ? $changes['method'] : $request->getMethod(), - $uri, - $headers, - isset($changes['body']) ? $changes['body'] : $request->getBody(), - isset($changes['version']) - ? $changes['version'] - : $request->getProtocolVersion(), - $request->getServerParams() - ); - } - - return new Request( - isset($changes['method']) ? $changes['method'] : $request->getMethod(), - $uri, - $headers, - isset($changes['body']) ? $changes['body'] : $request->getBody(), - isset($changes['version']) - ? $changes['version'] - : $request->getProtocolVersion() - ); + return Utils::modifyRequest($request, $changes); } /** @@ -270,14 +152,12 @@ * @param MessageInterface $message Message to rewind * * @throws \RuntimeException + * + * @deprecated rewind_body will be removed in guzzlehttp/psr7:2.0. Use Message::rewindBody instead. */ function rewind_body(MessageInterface $message) { - $body = $message->getBody(); - - if ($body->tell()) { - $body->rewind(); - } + Message::rewindBody($message); } /** @@ -290,29 +170,14 @@ * @param string $mode Mode used to open the file * * @return resource + * * @throws \RuntimeException if the file cannot be opened + * + * @deprecated try_fopen will be removed in guzzlehttp/psr7:2.0. Use Utils::tryFopen instead. */ function try_fopen($filename, $mode) { - $ex = null; - set_error_handler(function () use ($filename, $mode, &$ex) { - $ex = new \RuntimeException(sprintf( - 'Unable to open %s using mode %s: %s', - $filename, - $mode, - func_get_args()[1] - )); - }); - - $handle = fopen($filename, $mode); - restore_error_handler(); - - if ($ex) { - /** @var $ex \RuntimeException */ - throw $ex; - } - - return $handle; + return Utils::tryFopen($filename, $mode); } /** @@ -323,36 +188,14 @@ * @param int $maxLen Maximum number of bytes to read. Pass -1 * to read the entire stream. * @return string + * * @throws \RuntimeException on error. + * + * @deprecated copy_to_string will be removed in guzzlehttp/psr7:2.0. Use Utils::copyToString instead. */ function copy_to_string(StreamInterface $stream, $maxLen = -1) { - $buffer = ''; - - if ($maxLen === -1) { - while (!$stream->eof()) { - $buf = $stream->read(1048576); - // Using a loose equality here to match on '' and false. - if ($buf == null) { - break; - } - $buffer .= $buf; - } - return $buffer; - } - - $len = 0; - while (!$stream->eof() && $len < $maxLen) { - $buf = $stream->read($maxLen - $len); - // Using a loose equality here to match on '' and false. - if ($buf == null) { - break; - } - $buffer .= $buf; - $len = strlen($buffer); - } - - return $buffer; + return Utils::copyToString($stream, $maxLen); } /** @@ -365,92 +208,48 @@ * to read the entire stream. * * @throws \RuntimeException on error. + * + * @deprecated copy_to_stream will be removed in guzzlehttp/psr7:2.0. Use Utils::copyToStream instead. */ -function copy_to_stream( - StreamInterface $source, - StreamInterface $dest, - $maxLen = -1 -) { - $bufferSize = 8192; - - if ($maxLen === -1) { - while (!$source->eof()) { - if (!$dest->write($source->read($bufferSize))) { - break; - } - } - } else { - $remaining = $maxLen; - while ($remaining > 0 && !$source->eof()) { - $buf = $source->read(min($bufferSize, $remaining)); - $len = strlen($buf); - if (!$len) { - break; - } - $remaining -= $len; - $dest->write($buf); - } - } +function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1) +{ + return Utils::copyToStream($source, $dest, $maxLen); } /** - * Calculate a hash of a Stream + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based on + * PHP's `hash_init` functions. * * @param StreamInterface $stream Stream to calculate the hash for * @param string $algo Hash algorithm (e.g. md5, crc32, etc) * @param bool $rawOutput Whether or not to use raw output * * @return string Returns the hash of the stream + * * @throws \RuntimeException on error. + * + * @deprecated hash will be removed in guzzlehttp/psr7:2.0. Use Utils::hash instead. */ -function hash( - StreamInterface $stream, - $algo, - $rawOutput = false -) { - $pos = $stream->tell(); - - if ($pos > 0) { - $stream->rewind(); - } - - $ctx = hash_init($algo); - while (!$stream->eof()) { - hash_update($ctx, $stream->read(1048576)); - } - - $out = hash_final($ctx, (bool) $rawOutput); - $stream->seek($pos); - - return $out; +function hash(StreamInterface $stream, $algo, $rawOutput = false) +{ + return Utils::hash($stream, $algo, $rawOutput); } /** - * Read a line from the stream up to the maximum allowed buffer length + * Read a line from the stream up to the maximum allowed buffer length. * * @param StreamInterface $stream Stream to read from - * @param int $maxLength Maximum buffer length + * @param int|null $maxLength Maximum buffer length + * + * @return string * - * @return string|bool + * @deprecated readline will be removed in guzzlehttp/psr7:2.0. Use Utils::readLine instead. */ function readline(StreamInterface $stream, $maxLength = null) { - $buffer = ''; - $size = 0; - - while (!$stream->eof()) { - // Using a loose equality here to match on '' and false. - if (null == ($byte = $stream->read(1))) { - return $buffer; - } - $buffer .= $byte; - // Break when a new line is found or the max length - 1 is reached - if ($byte === "\n" || ++$size === $maxLength - 1) { - break; - } - } - - return $buffer; + return Utils::readLine($stream, $maxLength); } /** @@ -459,26 +258,12 @@ * @param string $message Request message string. * * @return Request + * + * @deprecated parse_request will be removed in guzzlehttp/psr7:2.0. Use Message::parseRequest instead. */ function parse_request($message) { - $data = _parse_message($message); - $matches = []; - if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { - throw new \InvalidArgumentException('Invalid request string'); - } - $parts = explode(' ', $data['start-line'], 3); - $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; - - $request = new Request( - $parts[0], - $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], - $data['headers'], - $data['body'], - $version - ); - - return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + return Message::parseRequest($message); } /** @@ -487,139 +272,66 @@ * @param string $message Response message string. * * @return Response + * + * @deprecated parse_response will be removed in guzzlehttp/psr7:2.0. Use Message::parseResponse instead. */ function parse_response($message) { - $data = _parse_message($message); - // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space - // between status-code and reason-phrase is required. But browsers accept - // responses without space and reason as well. - if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { - throw new \InvalidArgumentException('Invalid response string'); - } - $parts = explode(' ', $data['start-line'], 3); - - return new Response( - $parts[1], - $data['headers'], - $data['body'], - explode('/', $parts[0])[1], - isset($parts[2]) ? $parts[2] : null - ); + return Message::parseResponse($message); } /** * Parse a query string into an associative array. * - * If multiple values are found for the same key, the value of that key - * value pair will become an array. This function does not parse nested - * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will - * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). + * If multiple values are found for the same key, the value of that key value + * pair will become an array. This function does not parse nested PHP style + * arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed + * into `['foo[a]' => '1', 'foo[b]' => '2'])`. * - * @param string $str Query string to parse - * @param bool|string $urlEncoding How the query string is encoded + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded * * @return array + * + * @deprecated parse_query will be removed in guzzlehttp/psr7:2.0. Use Query::parse instead. */ function parse_query($str, $urlEncoding = true) { - $result = []; - - if ($str === '') { - return $result; - } - - if ($urlEncoding === true) { - $decoder = function ($value) { - return rawurldecode(str_replace('+', ' ', $value)); - }; - } elseif ($urlEncoding == PHP_QUERY_RFC3986) { - $decoder = 'rawurldecode'; - } elseif ($urlEncoding == PHP_QUERY_RFC1738) { - $decoder = 'urldecode'; - } else { - $decoder = function ($str) { return $str; }; - } - - foreach (explode('&', $str) as $kvp) { - $parts = explode('=', $kvp, 2); - $key = $decoder($parts[0]); - $value = isset($parts[1]) ? $decoder($parts[1]) : null; - if (!isset($result[$key])) { - $result[$key] = $value; - } else { - if (!is_array($result[$key])) { - $result[$key] = [$result[$key]]; - } - $result[$key][] = $value; - } - } - - return $result; + return Query::parse($str, $urlEncoding); } /** * Build a query string from an array of key value pairs. * - * This function can use the return value of parse_query() to build a query + * This function can use the return value of `parse_query()` to build a query * string. This function does not modify the provided keys when an array is - * encountered (like http_build_query would). + * encountered (like `http_build_query()` would). * * @param array $params Query string parameters. * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 * to encode using RFC3986, or PHP_QUERY_RFC1738 * to encode using RFC1738. * @return string + * + * @deprecated build_query will be removed in guzzlehttp/psr7:2.0. Use Query::build instead. */ function build_query(array $params, $encoding = PHP_QUERY_RFC3986) { - if (!$params) { - return ''; - } - - if ($encoding === false) { - $encoder = function ($str) { return $str; }; - } elseif ($encoding === PHP_QUERY_RFC3986) { - $encoder = 'rawurlencode'; - } elseif ($encoding === PHP_QUERY_RFC1738) { - $encoder = 'urlencode'; - } else { - throw new \InvalidArgumentException('Invalid type'); - } - - $qs = ''; - foreach ($params as $k => $v) { - $k = $encoder($k); - if (!is_array($v)) { - $qs .= $k; - if ($v !== null) { - $qs .= '=' . $encoder($v); - } - $qs .= '&'; - } else { - foreach ($v as $vv) { - $qs .= $k; - if ($vv !== null) { - $qs .= '=' . $encoder($vv); - } - $qs .= '&'; - } - } - } - - return $qs ? (string) substr($qs, 0, -1) : ''; + return Query::build($params, $encoding); } /** * Determines the mimetype of a file by looking at its extension. * - * @param $filename + * @param string $filename + * + * @return string|null * - * @return null|string + * @deprecated mimetype_from_filename will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromFilename instead. */ function mimetype_from_filename($filename) { - return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); + return MimeType::fromFilename($filename); } /** @@ -628,116 +340,13 @@ * @param $extension string The file extension. * * @return string|null + * * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + * @deprecated mimetype_from_extension will be removed in guzzlehttp/psr7:2.0. Use MimeType::fromExtension instead. */ function mimetype_from_extension($extension) { - static $mimetypes = [ - '7z' => 'application/x-7z-compressed', - 'aac' => 'audio/x-aac', - 'ai' => 'application/postscript', - 'aif' => 'audio/x-aiff', - 'asc' => 'text/plain', - 'asf' => 'video/x-ms-asf', - 'atom' => 'application/atom+xml', - 'avi' => 'video/x-msvideo', - 'bmp' => 'image/bmp', - 'bz2' => 'application/x-bzip2', - 'cer' => 'application/pkix-cert', - 'crl' => 'application/pkix-crl', - 'crt' => 'application/x-x509-ca-cert', - 'css' => 'text/css', - 'csv' => 'text/csv', - 'cu' => 'application/cu-seeme', - 'deb' => 'application/x-debian-package', - 'doc' => 'application/msword', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'dvi' => 'application/x-dvi', - 'eot' => 'application/vnd.ms-fontobject', - 'eps' => 'application/postscript', - 'epub' => 'application/epub+zip', - 'etx' => 'text/x-setext', - 'flac' => 'audio/flac', - 'flv' => 'video/x-flv', - 'gif' => 'image/gif', - 'gz' => 'application/gzip', - 'htm' => 'text/html', - 'html' => 'text/html', - 'ico' => 'image/x-icon', - 'ics' => 'text/calendar', - 'ini' => 'text/plain', - 'iso' => 'application/x-iso9660-image', - 'jar' => 'application/java-archive', - 'jpe' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'js' => 'text/javascript', - 'json' => 'application/json', - 'latex' => 'application/x-latex', - 'log' => 'text/plain', - 'm4a' => 'audio/mp4', - 'm4v' => 'video/mp4', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mov' => 'video/quicktime', - 'mp3' => 'audio/mpeg', - 'mp4' => 'video/mp4', - 'mp4a' => 'audio/mp4', - 'mp4v' => 'video/mp4', - 'mpe' => 'video/mpeg', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpg4' => 'video/mp4', - 'oga' => 'audio/ogg', - 'ogg' => 'audio/ogg', - 'ogv' => 'video/ogg', - 'ogx' => 'application/ogg', - 'pbm' => 'image/x-portable-bitmap', - 'pdf' => 'application/pdf', - 'pgm' => 'image/x-portable-graymap', - 'png' => 'image/png', - 'pnm' => 'image/x-portable-anymap', - 'ppm' => 'image/x-portable-pixmap', - 'ppt' => 'application/vnd.ms-powerpoint', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'ps' => 'application/postscript', - 'qt' => 'video/quicktime', - 'rar' => 'application/x-rar-compressed', - 'ras' => 'image/x-cmu-raster', - 'rss' => 'application/rss+xml', - 'rtf' => 'application/rtf', - 'sgm' => 'text/sgml', - 'sgml' => 'text/sgml', - 'svg' => 'image/svg+xml', - 'swf' => 'application/x-shockwave-flash', - 'tar' => 'application/x-tar', - 'tif' => 'image/tiff', - 'tiff' => 'image/tiff', - 'torrent' => 'application/x-bittorrent', - 'ttf' => 'application/x-font-ttf', - 'txt' => 'text/plain', - 'wav' => 'audio/x-wav', - 'webm' => 'video/webm', - 'wma' => 'audio/x-ms-wma', - 'wmv' => 'video/x-ms-wmv', - 'woff' => 'application/x-font-woff', - 'wsdl' => 'application/wsdl+xml', - 'xbm' => 'image/x-xbitmap', - 'xls' => 'application/vnd.ms-excel', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xml' => 'application/xml', - 'xpm' => 'image/x-xpixmap', - 'xwd' => 'image/x-xwindowdump', - 'yaml' => 'text/yaml', - 'yml' => 'text/yaml', - 'zip' => 'application/zip', - ]; - - $extension = strtolower($extension); - - return isset($mimetypes[$extension]) - ? $mimetypes[$extension] - : null; + return MimeType::fromExtension($extension); } /** @@ -750,37 +359,13 @@ * @param string $message HTTP request or response to parse. * * @return array + * * @internal + * @deprecated _parse_message will be removed in guzzlehttp/psr7:2.0. Use Message::parseMessage instead. */ function _parse_message($message) { - if (!$message) { - throw new \InvalidArgumentException('Invalid message'); - } - - // Iterate over each line in the message, accounting for line endings - $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); - $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; - array_shift($lines); - - for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { - $line = $lines[$i]; - // If two line breaks were encountered, then this is the end of body - if (empty($line)) { - if ($i < $totalLines - 1) { - $result['body'] = implode('', array_slice($lines, $i + 2)); - } - break; - } - if (strpos($line, ':')) { - $parts = explode(':', $line, 2); - $key = trim($parts[0]); - $value = isset($parts[1]) ? trim($parts[1]) : ''; - $result['headers'][$key][] = $value; - } - } - - return $result; + return Message::parseMessage($message); } /** @@ -790,39 +375,43 @@ * @param array $headers Array of headers (each value an array). * * @return string + * * @internal + * @deprecated _parse_request_uri will be removed in guzzlehttp/psr7:2.0. Use Message::parseRequestUri instead. */ function _parse_request_uri($path, array $headers) { - $hostKey = array_filter(array_keys($headers), function ($k) { - return strtolower($k) === 'host'; - }); - - // If no host is found, then a full URI cannot be constructed. - if (!$hostKey) { - return $path; - } - - $host = $headers[reset($hostKey)][0]; - $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + return Message::parseRequestUri($path, $headers); +} - return $scheme . '://' . $host . '/' . ltrim($path, '/'); +/** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + * + * @return string|null + * + * @deprecated get_message_body_summary will be removed in guzzlehttp/psr7:2.0. Use Message::bodySummary instead. + */ +function get_message_body_summary(MessageInterface $message, $truncateAt = 120) +{ + return Message::bodySummary($message, $truncateAt); } -/** @internal */ +/** + * Remove the items given by the keys, case insensitively from the data. + * + * @param iterable $keys + * + * @return array + * + * @internal + * @deprecated _caseless_remove will be removed in guzzlehttp/psr7:2.0. Use Utils::caselessRemove instead. + */ function _caseless_remove($keys, array $data) { - $result = []; - - foreach ($keys as &$key) { - $key = strtolower($key); - } - - foreach ($data as $k => $v) { - if (!in_array(strtolower($k), $keys)) { - $result[$k] = $v; - } - } - - return $result; + return Utils::caselessRemove($keys, $data); } diff -Nru php-guzzlehttp-psr7-1.4.2/src/Header.php php-guzzlehttp-psr7-1.7.0/src/Header.php --- php-guzzlehttp-psr7-1.4.2/src/Header.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/Header.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,71 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ + public static function normalize($header) + { + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/src/InflateStream.php php-guzzlehttp-psr7-1.7.0/src/InflateStream.php --- php-guzzlehttp-psr7-1.4.2/src/InflateStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/InflateStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ stream = new Stream($resource); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); } /** diff -Nru php-guzzlehttp-psr7-1.4.2/src/LazyOpenStream.php php-guzzlehttp-psr7-1.7.0/src/LazyOpenStream.php --- php-guzzlehttp-psr7-1.4.2/src/LazyOpenStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/LazyOpenStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ filename, $this->mode)); + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); } } diff -Nru php-guzzlehttp-psr7-1.4.2/src/LimitStream.php php-guzzlehttp-psr7-1.7.0/src/LimitStream.php --- php-guzzlehttp-psr7-1.4.2/src/LimitStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/LimitStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + * + * @return string|null + */ + public static function bodySummary(MessageInterface $message, $truncateAt = 120) + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message) + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + */ + public static function parseMessage($message) + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + list($rawHeaders, $body) = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + list($startLine, $rawHeaders) = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + */ + public static function parseRequestUri($path, array $headers) + { + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ + public static function parseRequest($message) + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ + public static function parseResponse($message) + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/src/MessageTrait.php php-guzzlehttp-psr7-1.7.0/src/MessageTrait.php --- php-guzzlehttp-psr7-1.4.2/src/MessageTrait.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/MessageTrait.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -85,11 +83,8 @@ public function withAddedHeader($header, $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -123,7 +118,7 @@ public function getBody() { if (!$this->stream) { - $this->stream = stream_for(''); + $this->stream = Utils::streamFor(''); } return $this->stream; @@ -144,11 +139,13 @@ { $this->headerNames = $this->headers = []; foreach ($headers as $header => $value) { - if (!is_array($value)) { - $value = [$value]; + if (is_int($header)) { + // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec + // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass. + $header = (string) $header; } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; @@ -160,6 +157,19 @@ } } + private function normalizeHeaderValue($value) + { + if (!is_array($value)) { + return $this->trimHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimHeaderValues($value); + } + /** * Trims whitespace from the header values. * @@ -177,7 +187,28 @@ private function trimHeaderValues(array $values) { return array_map(function ($value) { - return trim($value, " \t"); - }, $values); + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + return trim((string) $value, " \t"); + }, array_values($values)); + } + + private function assertHeader($header) + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if ($header === '') { + throw new \InvalidArgumentException('Header name can not be empty.'); + } } } diff -Nru php-guzzlehttp-psr7-1.4.2/src/MimeType.php php-guzzlehttp-psr7-1.7.0/src/MimeType.php --- php-guzzlehttp-psr7-1.4.2/src/MimeType.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/MimeType.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,140 @@ + 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mkv' => 'video/x-matroska', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/src/MultipartStream.php php-guzzlehttp-psr7-1.7.0/src/MultipartStream.php --- php-guzzlehttp-psr7-1.4.2/src/MultipartStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/MultipartStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ addStream(stream_for("--{$this->boundary}--\r\n")); + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); return $stream; } @@ -84,7 +85,7 @@ } } - $element['contents'] = stream_for($element['contents']); + $element['contents'] = Utils::streamFor($element['contents']); if (empty($element['filename'])) { $uri = $element['contents']->getMetadata('uri'); @@ -100,9 +101,9 @@ isset($element['headers']) ? $element['headers'] : [] ); - $stream->addStream(stream_for($this->getHeaders($headers))); + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); $stream->addStream($body); - $stream->addStream(stream_for("\r\n")); + $stream->addStream(Utils::streamFor("\r\n")); } /** @@ -131,7 +132,7 @@ // Set a default Content-Type if one was not supplied $type = $this->getHeader($headers, 'content-type'); if (!$type && ($filename === '0' || $filename)) { - if ($type = mimetype_from_filename($filename)) { + if ($type = MimeType::fromFilename($filename)) { $headers['Content-Type'] = $type; } } diff -Nru php-guzzlehttp-psr7-1.4.2/src/NoSeekStream.php php-guzzlehttp-psr7-1.7.0/src/NoSeekStream.php --- php-guzzlehttp-psr7-1.4.2/src/NoSeekStream.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/NoSeekStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ tellPos = false; $this->source = null; + + return null; } public function getSize() diff -Nru php-guzzlehttp-psr7-1.4.2/src/Query.php php-guzzlehttp-psr7-1.7.0/src/Query.php --- php-guzzlehttp-psr7-1.4.2/src/Query.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/Query.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,108 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + * + * @return array + */ + public static function parse($str, $urlEncoding = true) + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { return $str; }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * @return string + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986) + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { return $str; }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/src/Request.php php-guzzlehttp-psr7-1.7.0/src/Request.php --- php-guzzlehttp-psr7-1.4.2/src/Request.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/Request.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ assertMethod($method); if (!($uri instanceof UriInterface)) { $uri = new Uri($uri); } @@ -45,12 +47,12 @@ $this->setHeaders($headers); $this->protocol = $version; - if (!$this->hasHeader('Host')) { + if (!isset($this->headerNames['host'])) { $this->updateHostFromUri(); } if ($body !== '' && $body !== null) { - $this->stream = stream_for($body); + $this->stream = Utils::streamFor($body); } } @@ -91,6 +93,7 @@ public function withMethod($method) { + $this->assertMethod($method); $new = clone $this; $new->method = strtoupper($method); return $new; @@ -110,7 +113,7 @@ $new = clone $this; $new->uri = $uri; - if (!$preserveHost) { + if (!$preserveHost || !isset($this->headerNames['host'])) { $new->updateHostFromUri(); } @@ -139,4 +142,11 @@ // See: http://tools.ietf.org/html/rfc7230#section-5.4 $this->headers = [$header => [$host]] + $this->headers; } + + private function assertMethod($method) + { + if (!is_string($method) || $method === '') { + throw new \InvalidArgumentException('Method must be a non-empty string.'); + } + } } diff -Nru php-guzzlehttp-psr7-1.4.2/src/Response.php php-guzzlehttp-psr7-1.7.0/src/Response.php --- php-guzzlehttp-psr7-1.4.2/src/Response.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/Response.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ statusCode = (int) $status; + $this->assertStatusCodeIsInteger($status); + $status = (int) $status; + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; if ($body !== '' && $body !== null) { - $this->stream = stream_for($body); + $this->stream = Utils::streamFor($body); } $this->setHeaders($headers); @@ -121,12 +126,30 @@ public function withStatus($code, $reasonPhrase = '') { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + $new = clone $this; - $new->statusCode = (int) $code; + $new->statusCode = $code; if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { $reasonPhrase = self::$phrases[$new->statusCode]; } - $new->reasonPhrase = $reasonPhrase; + $new->reasonPhrase = (string) $reasonPhrase; return $new; } + + private function assertStatusCodeIsInteger($statusCode) + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange($statusCode) + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } } diff -Nru php-guzzlehttp-psr7-1.4.2/src/Rfc7230.php php-guzzlehttp-psr7-1.7.0/src/Rfc7230.php --- php-guzzlehttp-psr7-1.4.2/src/Rfc7230.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/Rfc7230.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,18 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff -Nru php-guzzlehttp-psr7-1.4.2/src/ServerRequest.php php-guzzlehttp-psr7-1.7.0/src/ServerRequest.php --- php-guzzlehttp-psr7-1.4.2/src/ServerRequest.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/ServerRequest.php 2021-01-08 03:18:18.000000000 +0000 @@ -79,8 +79,10 @@ * Return an UploadedFile instance array. * * @param array $files A array which respect $_FILES structure - * @throws InvalidArgumentException for unrecognized values + * * @return array + * + * @throws InvalidArgumentException for unrecognized values */ public static function normalizeFiles(array $files) { @@ -166,9 +168,9 @@ public static function fromGlobals() { $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; - $headers = function_exists('getallheaders') ? getallheaders() : []; + $headers = getallheaders(); $uri = self::getUriFromGlobals(); - $body = new LazyOpenStream('php://input', 'r+'); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); @@ -180,23 +182,41 @@ ->withUploadedFiles(self::normalizeFiles($_FILES)); } + private static function extractHostAndPortFromAuthority($authority) + { + $uri = 'http://'.$authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = isset($parts['host']) ? $parts['host'] : null; + $port = isset($parts['port']) ? $parts['port'] : null; + + return [$host, $port]; + } + /** * Get a Uri populated with values from $_SERVER. * * @return UriInterface */ - public static function getUriFromGlobals() { + public static function getUriFromGlobals() + { $uri = new Uri(''); $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); $hasPort = false; if (isset($_SERVER['HTTP_HOST'])) { - $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']); - $uri = $uri->withHost($hostHeaderParts[0]); - if (isset($hostHeaderParts[1])) { + list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { $hasPort = true; - $uri = $uri->withPort($hostHeaderParts[1]); + $uri = $uri->withPort($port); } } elseif (isset($_SERVER['SERVER_NAME'])) { $uri = $uri->withHost($_SERVER['SERVER_NAME']); @@ -210,7 +230,7 @@ $hasQuery = false; if (isset($_SERVER['REQUEST_URI'])) { - $requestUriParts = explode('?', $_SERVER['REQUEST_URI']); + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); $uri = $uri->withPath($requestUriParts[0]); if (isset($requestUriParts[1])) { $hasQuery = true; diff -Nru php-guzzlehttp-psr7-1.4.2/src/StreamDecoratorTrait.php php-guzzlehttp-psr7-1.7.0/src/StreamDecoratorTrait.php --- php-guzzlehttp-psr7-1.4.2/src/StreamDecoratorTrait.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/StreamDecoratorTrait.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ [ - 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, - 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, - 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a+' => true - ], - 'write' => [ - 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, - 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, - 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true - ] - ]; - /** * This constructor accepts an associative array of options. * @@ -65,20 +61,11 @@ $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; - $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); - $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); $this->uri = $this->getMetadata('uri'); } - public function __get($name) - { - if ($name == 'stream') { - throw new \RuntimeException('The stream is detached'); - } - - throw new \BadMethodCallException('No value for ' . $name); - } - /** * Closes the stream when the destructed */ @@ -90,8 +77,10 @@ public function __toString() { try { - $this->seek(0); - return (string) stream_get_contents($this->stream); + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); } catch (\Exception $e) { return ''; } @@ -99,6 +88,10 @@ public function getContents() { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + $contents = stream_get_contents($this->stream); if ($contents === false) { @@ -173,11 +166,19 @@ public function eof() { - return !$this->stream || feof($this->stream); + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); } public function tell() { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + $result = ftell($this->stream); if ($result === false) { @@ -194,9 +195,15 @@ public function seek($offset, $whence = SEEK_SET) { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } if (!$this->seekable) { throw new \RuntimeException('Stream is not seekable'); - } elseif (fseek($this->stream, $offset, $whence) === -1) { + } + if (fseek($this->stream, $offset, $whence) === -1) { throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . var_export($whence, true)); } @@ -204,6 +211,9 @@ public function read($length) { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } if (!$this->readable) { throw new \RuntimeException('Cannot read from non-readable stream'); } @@ -225,6 +235,9 @@ public function write($string) { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } if (!$this->writable) { throw new \RuntimeException('Cannot write to a non-writable stream'); } diff -Nru php-guzzlehttp-psr7-1.4.2/src/StreamWrapper.php php-guzzlehttp-psr7-1.7.0/src/StreamWrapper.php --- php-guzzlehttp-psr7-1.4.2/src/StreamWrapper.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/StreamWrapper.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ ['stream' => $stream] - ])); + ]); } /** @@ -94,12 +108,21 @@ return true; } + public function stream_cast($cast_as) + { + $stream = clone($this->stream); + + return $stream->detach(); + } + public function stream_stat() { static $modeMap = [ 'r' => 33060, + 'rb' => 33060, 'r+' => 33206, - 'w' => 33188 + 'w' => 33188, + 'wb' => 33188 ]; return [ @@ -114,6 +137,25 @@ 'atime' => 0, 'mtime' => 0, 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + public function url_stat($path, $flags) + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ]; diff -Nru php-guzzlehttp-psr7-1.4.2/src/UploadedFile.php php-guzzlehttp-psr7-1.7.0/src/UploadedFile.php --- php-guzzlehttp-psr7-1.4.2/src/UploadedFile.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/UploadedFile.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ file, $targetPath) : move_uploaded_file($this->file, $targetPath); } else { - copy_to_stream( + Utils::copyToStream( $this->getStream(), new LazyOpenStream($targetPath, 'w') ); diff -Nru php-guzzlehttp-psr7-1.4.2/src/UriNormalizer.php php-guzzlehttp-psr7-1.7.0/src/UriNormalizer.php --- php-guzzlehttp-psr7-1.4.2/src/UriNormalizer.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/UriNormalizer.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ getQuery(); - if ($current === '') { - return $uri; - } - - $decodedKey = rawurldecode($key); - $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { - return rawurldecode(explode('=', $part)[0]) !== $decodedKey; - }); + $result = self::getFilteredQueryString($uri, [$key]); return $uri->withQuery(implode('&', $result)); } @@ -331,26 +324,29 @@ */ public static function withQueryValue(UriInterface $uri, $key, $value) { - $current = $uri->getQuery(); + $result = self::getFilteredQueryString($uri, [$key]); - if ($current === '') { - $result = []; - } else { - $decodedKey = rawurldecode($key); - $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { - return rawurldecode(explode('=', $part)[0]) !== $decodedKey; - }); - } + $result[] = self::generateQueryString($key, $value); - // Query string separators ("=", "&") within the key or value need to be encoded - // (while preventing double-encoding) before setting the query string. All other - // chars that need percent-encoding will be encoded by withQuery(). - $key = strtr($key, self::$replaceQuery); + return $uri->withQuery(implode('&', $result)); + } - if ($value !== null) { - $result[] = $key . '=' . strtr($value, self::$replaceQuery); - } else { - $result[] = $key; + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param array $keyValueArray Associative array of key and values + * + * @return UriInterface + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray) + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString($key, $value); } return $uri->withQuery(implode('&', $result)); @@ -442,9 +438,9 @@ public function withUserInfo($user, $password = null) { - $info = $user; - if ($password != '') { - $info .= ':' . $password; + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); } if ($this->userInfo === $info) { @@ -542,7 +538,9 @@ $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; - $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : ''; @@ -559,7 +557,7 @@ ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { - $this->userInfo .= ':' . $parts['pass']; + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); } $this->removeDefaultPort(); @@ -582,6 +580,26 @@ } /** + * @param string $component + * + * @return string + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component) + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** * @param string $host * * @return string @@ -611,15 +629,56 @@ } $port = (int) $port; - if (1 > $port || 0xffff < $port) { + if (0 > $port || 0xffff < $port) { throw new \InvalidArgumentException( - sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) ); } return $port; } + /** + * @param UriInterface $uri + * @param array $keys + * + * @return array + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys) + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + /** + * @param string $key + * @param string|null $value + * + * @return string + */ + private static function generateQueryString($key, $value) + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::$replaceQuery); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::$replaceQuery); + } + + return $queryString; + } + private function removeDefaultPort() { if ($this->port !== null && self::isDefaultPort($this)) { diff -Nru php-guzzlehttp-psr7-1.4.2/src/UriResolver.php php-guzzlehttp-psr7-1.7.0/src/UriResolver.php --- php-guzzlehttp-psr7-1.4.2/src/UriResolver.php 2017-03-20 17:10:46.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/src/UriResolver.php 2021-01-08 03:18:18.000000000 +0000 @@ -1,4 +1,5 @@ $keys + * + * @return array + */ + public static function caselessRemove($keys, array $data) + { + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1) + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * @return string + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, $maxLen = -1) + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, $algo, $rawOutput = false) + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ + public static function modifyRequest(RequestInterface $request, array $changes) + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + return (new ServerRequest( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + * + * @return string + */ + public static function readLine(StreamInterface $stream, $maxLength = null) + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data + * @param array $options Additional options + * + * @return StreamInterface + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []) + { + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return Utils::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen($filename, $mode) + { + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + func_get_args()[1] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/AppendStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/AppendStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/AppendStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/AppendStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,213 @@ +getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + + $this->expectExceptionGuzzle('InvalidArgumentException', 'Each stream must be readable'); + + $a->addStream($s); + } + + public function testValidatesSeekType() + { + $a = new AppendStream(); + + $this->expectExceptionGuzzle('RuntimeException', 'The AppendStream can only seek with SEEK_SET'); + + $a->seek(100, SEEK_CUR); + } + + public function testTriesToRewindOnSeek() + { + $a = new AppendStream(); + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'rewind', 'isSeekable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(true)); + $s->expects($this->once()) + ->method('rewind') + ->will($this->throwException(new \RuntimeException())); + $a->addStream($s); + + $this->expectExceptionGuzzle('RuntimeException', 'Unable to seek stream 0 of the AppendStream'); + + $a->seek(10); + } + + public function testSeeksToPositionByReading() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar'), + Psr7\Utils::streamFor('baz'), + ]); + + $a->seek(3); + $this->assertSame(3, $a->tell()); + $this->assertSame('bar', $a->read(3)); + + $a->seek(6); + $this->assertSame(6, $a->tell()); + $this->assertSame('baz', $a->read(3)); + } + + public function testDetachWithoutStreams() + { + $s = new AppendStream(); + $s->detach(); + + $this->assertSame(0, $s->getSize()); + $this->assertTrue($s->eof()); + $this->assertTrue($s->isReadable()); + $this->assertSame('', (string) $s); + $this->assertTrue($s->isSeekable()); + $this->assertFalse($s->isWritable()); + } + + public function testDetachesEachStream() + { + $handle = fopen('php://temp', 'r'); + + $s1 = Psr7\Utils::streamFor($handle); + $s2 = Psr7\Utils::streamFor('bar'); + $a = new AppendStream([$s1, $s2]); + + $a->detach(); + + $this->assertSame(0, $a->getSize()); + $this->assertTrue($a->eof()); + $this->assertTrue($a->isReadable()); + $this->assertSame('', (string) $a); + $this->assertTrue($a->isSeekable()); + $this->assertFalse($a->isWritable()); + + $this->assertNull($s1->detach()); + $this->assertInternalTypeGuzzle('resource', $handle, 'resource is not closed when detaching'); + fclose($handle); + } + + public function testClosesEachStream() + { + $handle = fopen('php://temp', 'r'); + + $s1 = Psr7\Utils::streamFor($handle); + $s2 = Psr7\Utils::streamFor('bar'); + $a = new AppendStream([$s1, $s2]); + + $a->close(); + + $this->assertSame(0, $a->getSize()); + $this->assertTrue($a->eof()); + $this->assertTrue($a->isReadable()); + $this->assertSame('', (string) $a); + $this->assertTrue($a->isSeekable()); + $this->assertFalse($a->isWritable()); + + $this->assertFalse(is_resource($handle)); + } + + public function testIsNotWritable() + { + $a = new AppendStream([Psr7\Utils::streamFor('foo')]); + $this->assertFalse($a->isWritable()); + $this->assertTrue($a->isSeekable()); + $this->assertTrue($a->isReadable()); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot write to an AppendStream'); + + $a->write('foo'); + } + + public function testDoesNotNeedStreams() + { + $a = new AppendStream(); + $this->assertSame('', (string) $a); + } + + public function testCanReadFromMultipleStreams() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar'), + Psr7\Utils::streamFor('baz'), + ]); + $this->assertFalse($a->eof()); + $this->assertSame(0, $a->tell()); + $this->assertSame('foo', $a->read(3)); + $this->assertSame('bar', $a->read(3)); + $this->assertSame('baz', $a->read(3)); + $this->assertSame('', $a->read(1)); + $this->assertTrue($a->eof()); + $this->assertSame(9, $a->tell()); + $this->assertSame('foobarbaz', (string) $a); + } + + public function testCanDetermineSizeFromMultipleStreams() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar') + ]); + $this->assertSame(6, $a->getSize()); + + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(null)); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $a->addStream($s); + $this->assertNull($a->getSize()); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'read', 'isReadable', 'eof']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(true)); + $s->expects($this->once()) + ->method('read') + ->will($this->throwException(new \RuntimeException('foo'))); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $s->expects($this->any()) + ->method('eof') + ->will($this->returnValue(false)); + $a = new AppendStream([$s]); + $this->assertFalse($a->eof()); + $this->assertSame('', (string) $a); + } + + public function testReturnsEmptyMetadata() + { + $s = new AppendStream(); + $this->assertSame([], $s->getMetadata()); + $this->assertNull($s->getMetadata('foo')); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/BaseTest.php php-guzzlehttp-psr7-1.7.0/tests/BaseTest.php --- php-guzzlehttp-psr7-1.4.2/tests/BaseTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/BaseTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,139 @@ +setExpectedException($exception, $message); + } else { + $this->expectException($exception); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + } + } + + public function expectWarningGuzzle() + { + if (method_exists($this, 'expectWarning')) { + $this->expectWarning(); + } elseif (class_exists('PHPUnit\Framework\Error\Warning')) { + $this->expectExceptionGuzzle('PHPUnit\Framework\Error\Warning'); + } else { + $this->expectExceptionGuzzle('PHPUnit_Framework_Error_Warning'); + } + } + + /** + * @param string $type + * @param mixed $input + */ + public function assertInternalTypeGuzzle($type, $input) + { + switch ($type) { + case 'array': + if (method_exists($this, 'assertIsArray')) { + $this->assertIsArray($input); + } else { + $this->assertInternalType('array', $input); + } + break; + case 'bool': + case 'boolean': + if (method_exists($this, 'assertIsBool')) { + $this->assertIsBool($input); + } else { + $this->assertInternalType('bool', $input); + } + break; + case 'double': + case 'float': + case 'real': + if (method_exists($this, 'assertIsFloat')) { + $this->assertIsFloat($input); + } else { + $this->assertInternalType('float', $input); + } + break; + case 'int': + case 'integer': + if (method_exists($this, 'assertIsInt')) { + $this->assertIsInt($input); + } else { + $this->assertInternalType('int', $input); + } + break; + case 'numeric': + if (method_exists($this, 'assertIsNumeric')) { + $this->assertIsNumeric($input); + } else { + $this->assertInternalType('numeric', $input); + } + break; + case 'object': + if (method_exists($this, 'assertIsObject')) { + $this->assertIsObject($input); + } else { + $this->assertInternalType('object', $input); + } + break; + case 'resource': + if (method_exists($this, 'assertIsResource')) { + $this->assertIsResource($input); + } else { + $this->assertInternalType('resource', $input); + } + break; + case 'string': + if (method_exists($this, 'assertIsString')) { + $this->assertIsString($input); + } else { + $this->assertInternalType('string', $input); + } + break; + case 'scalar': + if (method_exists($this, 'assertIsScalar')) { + $this->assertIsScalar($input); + } else { + $this->assertInternalType('scalar', $input); + } + break; + case 'callable': + if (method_exists($this, 'assertIsCallable')) { + $this->assertIsCallable($input); + } else { + $this->assertInternalType('callable', $input); + } + break; + case 'iterable': + if (method_exists($this, 'assertIsIterable')) { + $this->assertIsIterable($input); + } else { + $this->assertInternalType('iterable', $input); + } + break; + } + } + + /** + * @param string $needle + * @param string $haystack + */ + public function assertStringContainsStringGuzzle($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsString')) { + $this->assertStringContainsString($needle, $haystack); + } else { + $this->assertContains($needle, $haystack); + } + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/BufferStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/BufferStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/BufferStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/BufferStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,63 @@ +assertTrue($b->isReadable()); + $this->assertTrue($b->isWritable()); + $this->assertFalse($b->isSeekable()); + $this->assertSame(null, $b->getMetadata('foo')); + $this->assertSame(10, $b->getMetadata('hwm')); + $this->assertSame([], $b->getMetadata()); + } + + public function testRemovesReadDataFromBuffer() + { + $b = new BufferStream(); + $this->assertSame(3, $b->write('foo')); + $this->assertSame(3, $b->getSize()); + $this->assertFalse($b->eof()); + $this->assertSame('foo', $b->read(10)); + $this->assertTrue($b->eof()); + $this->assertSame('', $b->read(10)); + } + + public function testCanCastToStringOrGetContents() + { + $b = new BufferStream(); + $b->write('foo'); + $b->write('baz'); + $this->assertSame('foo', $b->read(3)); + $b->write('bar'); + $this->assertSame('bazbar', (string) $b); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot determine the position of a BufferStream'); + + $b->tell(); + } + + public function testDetachClearsBuffer() + { + $b = new BufferStream(); + $b->write('foo'); + $b->detach(); + $this->assertTrue($b->eof()); + $this->assertSame(3, $b->write('abc')); + $this->assertSame('abc', $b->read(10)); + } + + public function testExceedingHighwaterMarkReturnsFalseButStillBuffers() + { + $b = new BufferStream(5); + $this->assertSame(3, $b->write('hi ')); + $this->assertFalse($b->write('hello')); + $this->assertSame('hi hello', (string) $b); + $this->assertSame(4, $b->write('test')); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/CachingStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/CachingStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/CachingStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/CachingStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,201 @@ +decorated = Psr7\Utils::streamFor('testing'); + $this->body = new CachingStream($this->decorated); + } + + /** + * @after + */ + public function tearDownTest() + { + $this->decorated->close(); + $this->body->close(); + } + + public function testUsesRemoteSizeIfPossible() + { + $body = Psr7\Utils::streamFor('test'); + $caching = new CachingStream($body); + $this->assertSame(4, $caching->getSize()); + } + + public function testReadsUntilCachedToByte() + { + $this->body->seek(5); + $this->assertSame('n', $this->body->read(1)); + $this->body->seek(0); + $this->assertSame('t', $this->body->read(1)); + } + + public function testCanSeekNearEndWithSeekEnd() + { + $baseStream = Psr7\Utils::streamFor(implode('', range('a', 'z'))); + $cached = new CachingStream($baseStream); + $cached->seek(-1, SEEK_END); + $this->assertSame(25, $baseStream->tell()); + $this->assertSame('z', $cached->read(1)); + $this->assertSame(26, $cached->getSize()); + } + + public function testCanSeekToEndWithSeekEnd() + { + $baseStream = Psr7\Utils::streamFor(implode('', range('a', 'z'))); + $cached = new CachingStream($baseStream); + $cached->seek(0, SEEK_END); + $this->assertSame(26, $baseStream->tell()); + $this->assertSame('', $cached->read(1)); + $this->assertSame(26, $cached->getSize()); + } + + public function testCanUseSeekEndWithUnknownSize() + { + $baseStream = Psr7\Utils::streamFor('testing'); + $decorated = Psr7\FnStream::decorate($baseStream, [ + 'getSize' => function () { return null; } + ]); + $cached = new CachingStream($decorated); + $cached->seek(-1, SEEK_END); + $this->assertSame('g', $cached->read(1)); + } + + public function testRewindUsesSeek() + { + $a = Psr7\Utils::streamFor('foo'); + $d = $this->getMockBuilder('GuzzleHttp\Psr7\CachingStream') + ->setMethods(array('seek')) + ->setConstructorArgs(array($a)) + ->getMock(); + $d->expects($this->once()) + ->method('seek') + ->with(0) + ->will($this->returnValue(true)); + $d->seek(0); + } + + public function testCanSeekToReadBytes() + { + $this->assertSame('te', $this->body->read(2)); + $this->body->seek(0); + $this->assertSame('test', $this->body->read(4)); + $this->assertSame(4, $this->body->tell()); + $this->body->seek(2); + $this->assertSame(2, $this->body->tell()); + $this->body->seek(2, SEEK_CUR); + $this->assertSame(4, $this->body->tell()); + $this->assertSame('ing', $this->body->read(3)); + } + + public function testCanSeekToReadBytesWithPartialBodyReturned() + { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, 'testing'); + fseek($stream, 0); + + $this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream') + ->setConstructorArgs([$stream]) + ->setMethods(['read']) + ->getMock(); + + $this->decorated->expects($this->exactly(2)) + ->method('read') + ->willReturnCallback(function($length) use ($stream){ + return fread($stream, 2); + }); + + $this->body = new CachingStream($this->decorated); + + $this->assertSame(0, $this->body->tell()); + $this->body->seek(4, SEEK_SET); + $this->assertSame(4, $this->body->tell()); + + $this->body->seek(0); + $this->assertSame('test', $this->body->read(4)); + } + + public function testWritesToBufferStream() + { + $this->body->read(2); + $this->body->write('hi'); + $this->body->seek(0); + $this->assertSame('tehiing', (string) $this->body); + } + + public function testSkipsOverwrittenBytes() + { + $decorated = Psr7\Utils::streamFor( + implode("\n", array_map(function ($n) { + return str_pad($n, 4, '0', STR_PAD_LEFT); + }, range(0, 25))) + ); + + $body = new CachingStream($decorated); + + $this->assertSame("0000\n", Psr7\Utils::readLine($body)); + $this->assertSame("0001\n", Psr7\Utils::readLine($body)); + // Write over part of the body yet to be read, so skip some bytes + $this->assertSame(5, $body->write("TEST\n")); + $this->assertSame(5, Helpers::readObjectAttribute($body, 'skipReadBytes')); + // Read, which skips bytes, then reads + $this->assertSame("0003\n", Psr7\Utils::readLine($body)); + $this->assertSame(0, Helpers::readObjectAttribute($body, 'skipReadBytes')); + $this->assertSame("0004\n", Psr7\Utils::readLine($body)); + $this->assertSame("0005\n", Psr7\Utils::readLine($body)); + + // Overwrite part of the cached body (so don't skip any bytes) + $body->seek(5); + $this->assertSame(5, $body->write("ABCD\n")); + $this->assertSame(0, Helpers::readObjectAttribute($body, 'skipReadBytes')); + $this->assertSame("TEST\n", Psr7\Utils::readLine($body)); + $this->assertSame("0003\n", Psr7\Utils::readLine($body)); + $this->assertSame("0004\n", Psr7\Utils::readLine($body)); + $this->assertSame("0005\n", Psr7\Utils::readLine($body)); + $this->assertSame("0006\n", Psr7\Utils::readLine($body)); + $this->assertSame(5, $body->write("1234\n")); + $this->assertSame(5, Helpers::readObjectAttribute($body, 'skipReadBytes')); + + // Seek to 0 and ensure the overwritten bit is replaced + $body->seek(0); + $this->assertSame("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); + + // Ensure that casting it to a string does not include the bit that was overwritten + $this->assertStringContainsStringGuzzle("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); + } + + public function testClosesBothStreams() + { + $s = fopen('php://temp', 'r'); + $a = Psr7\Utils::streamFor($s); + $d = new CachingStream($a); + $d->close(); + $this->assertFalse(is_resource($s)); + } + + public function testEnsuresValidWhence() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + $this->body->seek(10, -123456); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/DroppingStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/DroppingStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/DroppingStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/DroppingStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,27 @@ +assertSame(3, $drop->write('hel')); + $this->assertSame(2, $drop->write('lo')); + $this->assertSame(5, $drop->getSize()); + $this->assertSame('hello', $drop->read(5)); + $this->assertSame(0, $drop->getSize()); + $drop->write('12345678910'); + $this->assertSame(5, $stream->getSize()); + $this->assertSame(5, $drop->getSize()); + $this->assertSame('12345', (string) $drop); + $this->assertSame(0, $drop->getSize()); + $drop->write('hello'); + $this->assertSame(0, $drop->write('test')); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/FnStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/FnStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/FnStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/FnStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,98 @@ +expectExceptionGuzzle('BadMethodCallException', 'seek() is not implemented in the FnStream'); + + (new FnStream([]))->seek(1); + } + + public function testProxiesToFunction() + { + $s = new FnStream([ + 'read' => function ($len) { + $this->assertSame(3, $len); + return 'foo'; + } + ]); + + $this->assertSame('foo', $s->read(3)); + } + + public function testCanCloseOnDestruct() + { + $called = false; + $s = new FnStream([ + 'close' => function () use (&$called) { + $called = true; + } + ]); + unset($s); + $this->assertTrue($called); + } + + public function testDoesNotRequireClose() + { + $s = new FnStream([]); + unset($s); + $this->assertTrue(true); // strict mode requires an assertion + } + + public function testDecoratesStream() + { + $a = Psr7\Utils::streamFor('foo'); + $b = FnStream::decorate($a, []); + $this->assertSame(3, $b->getSize()); + $this->assertSame($b->isWritable(), true); + $this->assertSame($b->isReadable(), true); + $this->assertSame($b->isSeekable(), true); + $this->assertSame($b->read(3), 'foo'); + $this->assertSame($b->tell(), 3); + $this->assertSame($a->tell(), 3); + $this->assertSame('', $a->read(1)); + $this->assertSame($b->eof(), true); + $this->assertSame($a->eof(), true); + $b->seek(0); + $this->assertSame('foo', (string) $b); + $b->seek(0); + $this->assertSame('foo', $b->getContents()); + $this->assertSame($a->getMetadata(), $b->getMetadata()); + $b->seek(0, SEEK_END); + $b->write('bar'); + $this->assertSame('foobar', (string) $b); + $this->assertInternalTypeGuzzle('resource', $b->detach()); + $b->close(); + } + + public function testDecoratesWithCustomizations() + { + $called = false; + $a = Psr7\Utils::streamFor('foo'); + $b = FnStream::decorate($a, [ + 'read' => function ($len) use (&$called, $a) { + $called = true; + return $a->read($len); + } + ]); + $this->assertSame('foo', $b->read(3)); + $this->assertTrue($called); + } + + public function testDoNotAllowUnserialization() + { + $a = new FnStream([]); + $b = serialize($a); + $this->expectExceptionGuzzle('\LogicException', 'FnStream should never be unserialized'); + unserialize($b); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/HasToString.php php-guzzlehttp-psr7-1.7.0/tests/HasToString.php --- php-guzzlehttp-psr7-1.4.2/tests/HasToString.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/HasToString.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,11 @@ +', + 'rel' => 'front', + 'type' => 'image/jpeg', + ], + [ + '', + 'rel' => 'back', + 'type' => 'image/jpeg', + ], + ]; + return [ + [ + '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', + $res1, + ], + [ + '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', + $res1, + ], + [ + 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', + [ + ['foo' => 'baz', 'bar' => '123'], + ['boo'], + ['test' => '123'], + ['foobar' => 'foo;bar'], + ], + ], + [ + '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', + [ + ['', 'rel' => 'side', 'type' => 'image/jpeg'], + ['', 'rel' => 'side', 'type' => 'image/jpeg'], + ], + ], + [ + '', + [], + ], + ]; + } + + /** + * @dataProvider parseParamsProvider + */ + public function testParseParams($header, $result) + { + $this->assertSame($result, Psr7\Header::parse($header)); + } + + public function testParsesArrayHeaders() + { + $header = ['a, b', 'c', 'd, e']; + $this->assertSame(['a', 'b', 'c', 'd', 'e'], Psr7\Header::normalize($header)); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/Helpers.php php-guzzlehttp-psr7-1.7.0/tests/Helpers.php --- php-guzzlehttp-psr7-1.4.2/tests/Helpers.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/Helpers.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,35 @@ +getProperty($attributeName); + + if (!$attribute || $attribute->isPublic()) { + return $object->$attributeName; + } + + $attribute->setAccessible(true); + + return $attribute->getValue($object); + } catch (\ReflectionException $e) { + // do nothing + } + } while ($reflector = $reflector->getParentClass()); + + throw new \Exception( + sprintf('Attribute "%s" not found in object.', $attributeName) + ); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/InflateStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/InflateStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/InflateStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/InflateStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,51 @@ +assertSame('test', (string) $b); + } + + public function testInflatesStreamsWithFilename() + { + $content = $this->getGzipStringWithFilename('test'); + $a = Psr7\Utils::streamFor($content); + $b = new InflateStream($a); + $this->assertSame('test', (string) $b); + } + + public function testInflatesStreamsPreserveSeekable() + { + $content = $this->getGzipStringWithFilename('test'); + $seekable = Psr7\Utils::streamFor($content); + $nonSeekable = new NoSeekStream(Psr7\Utils::streamFor($content)); + + $this->assertTrue((new InflateStream($seekable))->isSeekable()); + $this->assertFalse((new InflateStream($nonSeekable))->isSeekable()); + } + + private function getGzipStringWithFilename($original_string) + { + $gzipped = bin2hex(gzencode($original_string)); + + $header = substr($gzipped, 0, 20); + // set FNAME flag + $header[6]=0; + $header[7]=8; + // make a dummy filename + $filename = '64756d6d7900'; + $rest = substr($gzipped, 20); + + return hex2bin($header . $filename . $rest); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/LazyOpenStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/LazyOpenStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/LazyOpenStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/LazyOpenStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,71 @@ +fname = tempnam(sys_get_temp_dir(), 'tfile'); + + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + /** + * @after + */ + public function tearDownTest() + { + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + public function testOpensLazily() + { + $l = new LazyOpenStream($this->fname, 'w+'); + $l->write('foo'); + $this->assertInternalTypeGuzzle('array', $l->getMetadata()); + $this->assertFileExists($this->fname); + $this->assertSame('foo', file_get_contents($this->fname)); + $this->assertSame('foo', (string) $l); + } + + public function testProxiesToFile() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + $this->assertSame('foo', $l->read(4)); + $this->assertTrue($l->eof()); + $this->assertSame(3, $l->tell()); + $this->assertTrue($l->isReadable()); + $this->assertTrue($l->isSeekable()); + $this->assertFalse($l->isWritable()); + $l->seek(1); + $this->assertSame('oo', $l->getContents()); + $this->assertSame('foo', (string) $l); + $this->assertSame(3, $l->getSize()); + $this->assertInternalTypeGuzzle('array', $l->getMetadata()); + $l->close(); + } + + public function testDetachesUnderlyingStream() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + $r = $l->detach(); + $this->assertInternalTypeGuzzle('resource', $r); + fseek($r, 0); + $this->assertSame('foo', stream_get_contents($r)); + fclose($r); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/LimitStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/LimitStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/LimitStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/LimitStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,160 @@ +decorated = Psr7\Utils::streamFor(fopen(__FILE__, 'r')); + $this->body = new LimitStream($this->decorated, 10, 3); + } + + public function testReturnsSubset() + { + $body = new LimitStream(Psr7\Utils::streamFor('foo'), -1, 1); + $this->assertSame('oo', (string) $body); + $this->assertTrue($body->eof()); + $body->seek(0); + $this->assertFalse($body->eof()); + $this->assertSame('oo', $body->read(100)); + $this->assertSame('', $body->read(1)); + $this->assertTrue($body->eof()); + } + + public function testReturnsSubsetWhenCastToString() + { + $body = Psr7\Utils::streamFor('foo_baz_bar'); + $limited = new LimitStream($body, 3, 4); + $this->assertSame('baz', (string) $limited); + } + + public function testReturnsSubsetOfEmptyBodyWhenCastToString() + { + $body = Psr7\Utils::streamFor('01234567891234'); + $limited = new LimitStream($body, 0, 10); + $this->assertSame('', (string) $limited); + } + + public function testReturnsSpecificSubsetOBodyWhenCastToString() + { + $body = Psr7\Utils::streamFor('0123456789abcdef'); + $limited = new LimitStream($body, 3, 10); + $this->assertSame('abc', (string) $limited); + } + + public function testSeeksWhenConstructed() + { + $this->assertSame(0, $this->body->tell()); + $this->assertSame(3, $this->decorated->tell()); + } + + public function testAllowsBoundedSeek() + { + $this->body->seek(100); + $this->assertSame(10, $this->body->tell()); + $this->assertSame(13, $this->decorated->tell()); + $this->body->seek(0); + $this->assertSame(0, $this->body->tell()); + $this->assertSame(3, $this->decorated->tell()); + try { + $this->body->seek(-10); + $this->fail(); + } catch (\RuntimeException $e) {} + $this->assertSame(0, $this->body->tell()); + $this->assertSame(3, $this->decorated->tell()); + $this->body->seek(5); + $this->assertSame(5, $this->body->tell()); + $this->assertSame(8, $this->decorated->tell()); + // Fail + try { + $this->body->seek(1000, SEEK_END); + $this->fail(); + } catch (\RuntimeException $e) {} + } + + public function testReadsOnlySubsetOfData() + { + $data = $this->body->read(100); + $this->assertSame(10, strlen($data)); + $this->assertSame('', $this->body->read(1000)); + + $this->body->setOffset(10); + $newData = $this->body->read(100); + $this->assertSame(10, strlen($newData)); + $this->assertNotSame($data, $newData); + } + + public function testThrowsWhenCurrentGreaterThanOffsetSeek() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new NoSeekStream($a); + $c = new LimitStream($b); + $a->getContents(); + + $this->expectExceptionGuzzle('RuntimeException', 'Could not seek to stream offset 2'); + + $c->setOffset(2); + } + + public function testCanGetContentsWithoutSeeking() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new NoSeekStream($a); + $c = new LimitStream($b); + $this->assertSame('foo_bar', $c->getContents()); + } + + public function testClaimsConsumedWhenReadLimitIsReached() + { + $this->assertFalse($this->body->eof()); + $this->body->read(1000); + $this->assertTrue($this->body->eof()); + } + + public function testContentLengthIsBounded() + { + $this->assertSame(10, $this->body->getSize()); + } + + public function testGetContentsIsBasedOnSubset() + { + $body = new LimitStream(Psr7\Utils::streamFor('foobazbar'), 3, 3); + $this->assertSame('baz', $body->getContents()); + } + + public function testReturnsNullIfSizeCannotBeDetermined() + { + $a = new FnStream([ + 'getSize' => function () { return null; }, + 'tell' => function () { return 0; }, + ]); + $b = new LimitStream($a); + $this->assertNull($b->getSize()); + } + + public function testLengthLessOffsetWhenNoLimitSize() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new LimitStream($a, -1, 4); + $this->assertSame(3, $b->getSize()); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/MessageTest.php php-guzzlehttp-psr7-1.7.0/tests/MessageTest.php --- php-guzzlehttp-psr7-1.4.2/tests/MessageTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/MessageTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,267 @@ + 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0'); + $this->assertSame( + "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($request) + ); + } + + public function testConvertsResponsesToStrings() + { + $response = new Psr7\Response(200, [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0', 'FOO'); + $this->assertSame( + "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCorrectlyRendersSetCookieHeadersToString() + { + $response = new Psr7\Response(200, [ + 'Set-Cookie' => ['bar','baz','qux'] + ], 'hello', '1.0', 'FOO'); + $this->assertSame( + "HTTP/1.0 200 FOO\r\nSet-Cookie: bar\r\nSet-Cookie: baz\r\nSet-Cookie: qux\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testRewindsBody() + { + $body = Psr7\Utils::streamFor('abc'); + $res = new Psr7\Response(200, [], $body); + Psr7\Message::rewindBody($res); + $this->assertSame(0, $body->tell()); + $body->rewind(); + Psr7\Message::rewindBody($res); + $this->assertSame(0, $body->tell()); + } + + public function testThrowsWhenBodyCannotBeRewound() + { + $body = Psr7\Utils::streamFor('abc'); + $body->read(1); + $body = FnStream::decorate($body, [ + 'rewind' => function () { + throw new \RuntimeException('a'); + }, + ]); + $res = new Psr7\Response(200, [], $body); + + $this->expectExceptionGuzzle('RuntimeException'); + + Psr7\Message::rewindBody($res); + } + + public function testParsesRequestMessages() + { + $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('GET', $request->getMethod()); + $this->assertSame('/abc', $request->getRequestTarget()); + $this->assertSame('1.0', $request->getProtocolVersion()); + $this->assertSame('foo.com', $request->getHeaderLine('Host')); + $this->assertSame('Bar', $request->getHeaderLine('Foo')); + $this->assertSame('Bam, Qux', $request->getHeaderLine('Baz')); + $this->assertSame('Test', (string)$request->getBody()); + $this->assertSame('http://foo.com/abc', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithHttpsScheme() + { + $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('PUT', $request->getMethod()); + $this->assertSame('/abc?baz=bar', $request->getRequestTarget()); + $this->assertSame('1.1', $request->getProtocolVersion()); + $this->assertSame('foo.com:443', $request->getHeaderLine('Host')); + $this->assertSame('', (string)$request->getBody()); + $this->assertSame('https://foo.com/abc?baz=bar', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithUriWhenHostIsNotFirst() + { + $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('PUT', $request->getMethod()); + $this->assertSame('/', $request->getRequestTarget()); + $this->assertSame('http://foo.com/', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithFullUri() + { + $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('GET', $request->getMethod()); + $this->assertSame('https://www.google.com:443/search?q=foobar', $request->getRequestTarget()); + $this->assertSame('1.1', $request->getProtocolVersion()); + $this->assertSame('www.google.com', $request->getHeaderLine('Host')); + $this->assertSame('', (string)$request->getBody()); + $this->assertSame('https://www.google.com/search?q=foobar', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithCustomMethod() + { + $req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('GET_DATA', $request->getMethod()); + } + + public function testParsesRequestMessagesWithFoldedHeadersOnHttp10() + { + $req = "PUT / HTTP/1.0\r\nFoo: Bar\r\n Bam\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('PUT', $request->getMethod()); + $this->assertSame('/', $request->getRequestTarget()); + $this->assertSame('Bar Bam', $request->getHeaderLine('Foo')); + } + + public function testRequestParsingFailsWithFoldedHeadersOnHttp11() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid header syntax: Obsolete line folding'); + + Psr7\Message::parseResponse("GET_DATA / HTTP/1.1\r\nFoo: Bar\r\n Biz: Bam\r\n\r\n"); + } + + public function testParsesRequestMessagesWhenHeaderDelimiterIsOnlyALineFeed() + { + $req = "PUT / HTTP/1.0\nFoo: Bar\nBaz: Bam\n\n"; + $request = Psr7\Message::parseRequest($req); + $this->assertSame('PUT', $request->getMethod()); + $this->assertSame('/', $request->getRequestTarget()); + $this->assertSame('Bar', $request->getHeaderLine('Foo')); + $this->assertSame('Bam', $request->getHeaderLine('Baz')); + } + + public function testValidatesRequestMessages() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Message::parseRequest("HTTP/1.1 200 OK\r\n\r\n"); + } + + public function testParsesResponseMessages() + { + $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame('1.0', $response->getProtocolVersion()); + $this->assertSame('Bar', $response->getHeaderLine('Foo')); + $this->assertSame('Bam, Qux', $response->getHeaderLine('Baz')); + $this->assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithoutReason() + { + $res = "HTTP/1.0 200\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame('1.0', $response->getProtocolVersion()); + $this->assertSame('Bar', $response->getHeaderLine('Foo')); + $this->assertSame('Bam, Qux', $response->getHeaderLine('Baz')); + $this->assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithLeadingDelimiter() + { + $res = "\r\nHTTP/1.0 200\r\nFoo: Bar\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame('1.0', $response->getProtocolVersion()); + $this->assertSame('Bar', $response->getHeaderLine('Foo')); + $this->assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithFoldedHeadersOnHttp10() + { + $res = "HTTP/1.0 200\r\nFoo: Bar\r\n Bam\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame('1.0', $response->getProtocolVersion()); + $this->assertSame('Bar Bam', $response->getHeaderLine('Foo')); + $this->assertSame('Test', (string)$response->getBody()); + } + + public function testResponseParsingFailsWithFoldedHeadersOnHttp11() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid header syntax: Obsolete line folding'); + + Psr7\Message::parseResponse("HTTP/1.1 200\r\nFoo: Bar\r\n Biz: Bam\r\nBaz: Qux\r\n\r\nTest"); + } + + public function testParsesResponseWhenHeaderDelimiterIsOnlyALineFeed() + { + $res = "HTTP/1.0 200\nFoo: Bar\nBaz: Bam\n\nTest\n\nOtherTest"; + $response = Psr7\Message::parseResponse($res); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame('1.0', $response->getProtocolVersion()); + $this->assertSame('Bar', $response->getHeaderLine('Foo')); + $this->assertSame('Bam', $response->getHeaderLine('Baz')); + $this->assertSame("Test\n\nOtherTest", (string)$response->getBody()); + } + + public function testResponseParsingFailsWithoutHeaderDelimiter() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid message: Missing header delimiter'); + + Psr7\Message::parseResponse("HTTP/1.0 200\r\nFoo: Bar\r\n Baz: Bam\r\nBaz: Qux\r\n"); + } + + public function testValidatesResponseMessages() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Message::parseResponse("GET / HTTP/1.1\r\n\r\n"); + } + + + public function testMessageBodySummaryWithSmallBody() + { + $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + $this->assertSame('Lorem ipsum dolor sit amet, consectetur adipiscing elit.', Psr7\Message::bodySummary($message)); + } + + public function testMessageBodySummaryWithLargeBody() + { + $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + $this->assertSame('Lorem ipsu (truncated...)', Psr7\Message::bodySummary($message, 10)); + } + + public function testMessageBodySummaryWithSpecialUTF8Characters() + { + $message = new Psr7\Response(200, [], '’é€௵ဪ‱'); + self::assertSame('’é€௵ဪ‱', Psr7\Message::bodySummary($message)); + } + + public function testMessageBodySummaryWithEmptyBody() + { + $message = new Psr7\Response(200, [], ''); + $this->assertNull(Psr7\Message::bodySummary($message)); + } + + public function testGetResponseBodySummaryOfNonReadableStream() + { + $this->assertNull(Psr7\Message::bodySummary(new Psr7\Response(500, [], new ReadSeekOnlyStream()))); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/MimeTypeTest.php php-guzzlehttp-psr7-1.7.0/tests/MimeTypeTest.php --- php-guzzlehttp-psr7-1.4.2/tests/MimeTypeTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/MimeTypeTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,22 @@ +assertNull(Psr7\MimeType::fromExtension('not-a-real-extension')); + $this->assertSame('application/json', Psr7\MimeType::fromExtension('json')); + } + + public function testDetermineFromFilename() + { + $this->assertSame( + 'image/jpeg', + Psr7\MimeType::fromFilename('/tmp/images/IMG034821.JPEG') + ); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/MultipartStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/MultipartStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/MultipartStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/MultipartStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,241 @@ +assertNotEmpty($b->getBoundary()); + } + + public function testCanProvideBoundary() + { + $b = new MultipartStream([], 'foo'); + $this->assertSame('foo', $b->getBoundary()); + } + + public function testIsNotWritable() + { + $b = new MultipartStream(); + $this->assertFalse($b->isWritable()); + } + + public function testCanCreateEmptyStream() + { + $b = new MultipartStream(); + $boundary = $b->getBoundary(); + $this->assertSame("--{$boundary}--\r\n", $b->getContents()); + $this->assertSame(strlen($boundary) + 6, $b->getSize()); + } + + public function testValidatesFilesArrayElement() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new MultipartStream([['foo' => 'bar']]); + } + + public function testEnsuresFileHasName() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new MultipartStream([['contents' => 'bar']]); + } + + public function testSerializesFields() + { + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => 'bar' + ], + [ + 'name' => 'baz', + 'contents' => 'bam' + ] + ], 'boundary'); + $this->assertSame( + "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n" + . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3" + . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b); + } + + public function testSerializesNonStringFields() + { + $b = new MultipartStream([ + [ + 'name' => 'int', + 'contents' => (int) 1 + ], + [ + 'name' => 'bool', + 'contents' => (boolean) false + ], + [ + 'name' => 'bool2', + 'contents' => (boolean) true + ], + [ + 'name' => 'float', + 'contents' => (float) 1.1 + ] + ], 'boundary'); + $this->assertSame( + "--boundary\r\nContent-Disposition: form-data; name=\"int\"\r\nContent-Length: 1\r\n\r\n" + . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"bool\"\r\n\r\n\r\n--boundary" + . "\r\nContent-Disposition: form-data; name=\"bool2\"\r\nContent-Length: 1\r\n\r\n" + . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"float\"\r\nContent-Length: 3" + . "\r\n\r\n1.1\r\n--boundary--\r\n", (string) $b); + } + + public function testSerializesFiles() + { + $f1 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [ + 'getMetadata' => function () { + return '/foo/bar.txt'; + } + ]); + + $f2 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('baz'), [ + 'getMetadata' => function () { + return '/foo/baz.jpg'; + } + ]); + + $f3 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('bar'), [ + 'getMetadata' => function () { + return '/foo/bar.gif'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1 + ], + [ + 'name' => 'qux', + 'contents' => $f2 + ], + [ + 'name' => 'qux', + 'contents' => $f3 + ], + ], 'boundary'); + + $expected = <<assertSame($expected, str_replace("\r", '', $b)); + } + + public function testSerializesFilesWithCustomHeaders() + { + $f1 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [ + 'getMetadata' => function () { + return '/foo/bar.txt'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1, + 'headers' => [ + 'x-foo' => 'bar', + 'content-disposition' => 'custom' + ] + ] + ], 'boundary'); + + $expected = <<assertSame($expected, str_replace("\r", '', $b)); + } + + public function testSerializesFilesWithCustomHeadersAndMultipleValues() + { + $f1 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [ + 'getMetadata' => function () { + return '/foo/bar.txt'; + } + ]); + + $f2 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('baz'), [ + 'getMetadata' => function () { + return '/foo/baz.jpg'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1, + 'headers' => [ + 'x-foo' => 'bar', + 'content-disposition' => 'custom' + ] + ], + [ + 'name' => 'foo', + 'contents' => $f2, + 'headers' => ['cOntenT-Type' => 'custom'], + ] + ], 'boundary'); + + $expected = <<assertSame($expected, str_replace("\r", '', $b)); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/NoSeekStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/NoSeekStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/NoSeekStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/NoSeekStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,38 @@ +getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'seek']) + ->getMockForAbstractClass(); + $s->expects($this->never())->method('seek'); + $s->expects($this->never())->method('isSeekable'); + $wrapped = new NoSeekStream($s); + $this->assertFalse($wrapped->isSeekable()); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot seek a NoSeekStream'); + + $wrapped->seek(2); + } + + public function testToStringDoesNotSeek() + { + $s = \GuzzleHttp\Psr7\Utils::streamFor('foo'); + $s->seek(1); + $wrapped = new NoSeekStream($s); + $this->assertSame('oo', (string) $wrapped); + + $wrapped->close(); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/PumpStreamTest.php php-guzzlehttp-psr7-1.7.0/tests/PumpStreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/PumpStreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/PumpStreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,73 @@ + ['foo' => 'bar'], + 'size' => 100 + ]); + + $this->assertSame('bar', $p->getMetadata('foo')); + $this->assertSame(['foo' => 'bar'], $p->getMetadata()); + $this->assertSame(100, $p->getSize()); + } + + public function testCanReadFromCallable() + { + $p = Psr7\Utils::streamFor(function ($size) { + return 'a'; + }); + $this->assertSame('a', $p->read(1)); + $this->assertSame(1, $p->tell()); + $this->assertSame('aaaaa', $p->read(5)); + $this->assertSame(6, $p->tell()); + } + + public function testStoresExcessDataInBuffer() + { + $called = []; + $p = Psr7\Utils::streamFor(function ($size) use (&$called) { + $called[] = $size; + return 'abcdef'; + }); + $this->assertSame('a', $p->read(1)); + $this->assertSame('b', $p->read(1)); + $this->assertSame('cdef', $p->read(4)); + $this->assertSame('abcdefabc', $p->read(9)); + $this->assertSame([1, 9, 3], $called); + } + + public function testInifiniteStreamWrappedInLimitStream() + { + $p = Psr7\Utils::streamFor(function () { return 'a'; }); + $s = new LimitStream($p, 5); + $this->assertSame('aaaaa', (string) $s); + } + + public function testDescribesCapabilities() + { + $p = Psr7\Utils::streamFor(function () {}); + $this->assertTrue($p->isReadable()); + $this->assertFalse($p->isSeekable()); + $this->assertFalse($p->isWritable()); + $this->assertNull($p->getSize()); + $this->assertSame('', $p->getContents()); + $this->assertSame('', (string) $p); + $p->close(); + $this->assertSame('', $p->read(10)); + $this->assertTrue($p->eof()); + + try { + $this->assertFalse($p->write('aa')); + $this->fail(); + } catch (\RuntimeException $e) {} + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/QueryTest.php php-guzzlehttp-psr7-1.7.0/tests/QueryTest.php --- php-guzzlehttp-psr7-1.4.2/tests/QueryTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/QueryTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,98 @@ + ['a', 'b']]], + // Can parse multi-valued items that use numeric indices + ['q[0]=a&q[1]=b', ['q[0]' => 'a', 'q[1]' => 'b']], + // Can parse duplicates and does not include numeric indices + ['q[]=a&q[]=b', ['q[]' => ['a', 'b']]], + // Ensures that the value of "q" is an array even though one value + ['q[]=a', ['q[]' => 'a']], + // Does not modify "." to "_" like PHP's parse_str() + ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']], + // Can decode %20 to " " + ['q%20a=a%20b', ['q a' => 'a b']], + // Can parse funky strings with no values by assigning each to null + ['q&a', ['q' => null, 'a' => null]], + // Does not strip trailing equal signs + ['data=abc=', ['data' => 'abc=']], + // Can store duplicates without affecting other values + ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']], + // Sets value to null when no "=" is present + ['foo', ['foo' => null]], + // Preserves "0" keys. + ['0', ['0' => null]], + // Sets the value to an empty string when "=" is present + ['0=', ['0' => '']], + // Preserves falsey keys + ['var=0', ['var' => '0']], + ['a[b][c]=1&a[b][c]=2', ['a[b][c]' => ['1', '2']]], + ['a[b]=c&a[d]=e', ['a[b]' => 'c', 'a[d]' => 'e']], + // Ensure it doesn't leave things behind with repeated values + // Can parse mult-values items + ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]], + ]; + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueries($input, $output) + { + $result = Psr7\Query::parse($input); + $this->assertSame($output, $result); + } + + public function testDoesNotDecode() + { + $str = 'foo%20=bar'; + $data = Psr7\Query::parse($str, false); + $this->assertSame(['foo%20' => 'bar'], $data); + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesAndBuildsQueries($input) + { + $result = Psr7\Query::parse($input, false); + $this->assertSame($input, Psr7\Query::build($result, false)); + } + + public function testEncodesWithRfc1738() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], PHP_QUERY_RFC1738); + $this->assertSame('foo+bar=baz%2B', $str); + } + + public function testEncodesWithRfc3986() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], PHP_QUERY_RFC3986); + $this->assertSame('foo%20bar=baz%2B', $str); + } + + public function testDoesNotEncode() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], false); + $this->assertSame('foo bar=baz+', $str); + } + + public function testCanControlDecodingType() + { + $result = Psr7\Query::parse('var=foo+bar', PHP_QUERY_RFC3986); + $this->assertSame('foo+bar', $result['var']); + $result = Psr7\Query::parse('var=foo+bar', PHP_QUERY_RFC1738); + $this->assertSame('foo bar', $result['var']); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/ReadSeekOnlyStream.php php-guzzlehttp-psr7-1.7.0/tests/ReadSeekOnlyStream.php --- php-guzzlehttp-psr7-1.4.2/tests/ReadSeekOnlyStream.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/ReadSeekOnlyStream.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,23 @@ +assertSame('/', (string) $r->getUri()); + } + + public function testRequestUriMayBeUri() + { + $uri = new Uri('/'); + $r = new Request('GET', $uri); + $this->assertSame($uri, $r->getUri()); + } + + public function testValidateRequestUri() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new Request('GET', '///'); + } + + public function testCanConstructWithBody() + { + $r = new Request('GET', '/', [], 'baz'); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('baz', (string) $r->getBody()); + } + + public function testNullBody() + { + $r = new Request('GET', '/', [], null); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('', (string) $r->getBody()); + } + + public function testFalseyBody() + { + $r = new Request('GET', '/', [], '0'); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('0', (string) $r->getBody()); + } + + public function testConstructorDoesNotReadStreamBody() + { + $streamIsRead = false; + $body = Psr7\FnStream::decorate(Psr7\Utils::streamFor(''), [ + '__toString' => function () use (&$streamIsRead) { + $streamIsRead = true; + return ''; + } + ]); + + $r = new Request('GET', '/', [], $body); + $this->assertFalse($streamIsRead); + $this->assertSame($body, $r->getBody()); + } + + public function testCapitalizesMethod() + { + $r = new Request('get', '/'); + $this->assertSame('GET', $r->getMethod()); + } + + public function testCapitalizesWithMethod() + { + $r = new Request('GET', '/'); + $this->assertSame('PUT', $r->withMethod('put')->getMethod()); + } + + public function testWithUri() + { + $r1 = new Request('GET', '/'); + $u1 = $r1->getUri(); + $u2 = new Uri('http://www.example.com'); + $r2 = $r1->withUri($u2); + $this->assertNotSame($r1, $r2); + $this->assertSame($u2, $r2->getUri()); + $this->assertSame($u1, $r1->getUri()); + } + + /** + * @dataProvider invalidMethodsProvider + */ + public function testConstructWithInvalidMethods($method) + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + new Request($method, '/'); + } + + /** + * @dataProvider invalidMethodsProvider + */ + public function testWithInvalidMethods($method) + { + $r = new Request('get', '/'); + $this->expectExceptionGuzzle('InvalidArgumentException'); + $r->withMethod($method); + } + + public function invalidMethodsProvider() + { + return [ + [null], + [false], + [['foo']], + [new \stdClass()], + ]; + } + + public function testSameInstanceWhenSameUri() + { + $r1 = new Request('GET', 'http://foo.com'); + $r2 = $r1->withUri($r1->getUri()); + $this->assertSame($r1, $r2); + } + + public function testWithRequestTarget() + { + $r1 = new Request('GET', '/'); + $r2 = $r1->withRequestTarget('*'); + $this->assertSame('*', $r2->getRequestTarget()); + $this->assertSame('/', $r1->getRequestTarget()); + } + + public function testRequestTargetDoesNotAllowSpaces() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + $r1 = new Request('GET', '/'); + $r1->withRequestTarget('/foo bar'); + } + + public function testRequestTargetDefaultsToSlash() + { + $r1 = new Request('GET', ''); + $this->assertSame('/', $r1->getRequestTarget()); + $r2 = new Request('GET', '*'); + $this->assertSame('*', $r2->getRequestTarget()); + $r3 = new Request('GET', 'http://foo.com/bar baz/'); + $this->assertSame('/bar%20baz/', $r3->getRequestTarget()); + } + + public function testBuildsRequestTarget() + { + $r1 = new Request('GET', 'http://foo.com/baz?bar=bam'); + $this->assertSame('/baz?bar=bam', $r1->getRequestTarget()); + } + + public function testBuildsRequestTargetWithFalseyQuery() + { + $r1 = new Request('GET', 'http://foo.com/baz?0'); + $this->assertSame('/baz?0', $r1->getRequestTarget()); + } + + public function testHostIsAddedFirst() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']); + $this->assertSame([ + 'Host' => ['foo.com'], + 'Foo' => ['Bar'] + ], $r->getHeaders()); + } + + public function testCanGetHeaderAsCsv() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', [ + 'Foo' => ['a', 'b', 'c'] + ]); + $this->assertSame('a, b, c', $r->getHeaderLine('Foo')); + $this->assertSame('', $r->getHeaderLine('Bar')); + } + + public function testHostIsNotOverwrittenWhenPreservingHost() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']); + $this->assertSame(['Host' => ['a.com']], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true); + $this->assertSame('a.com', $r2->getHeaderLine('Host')); + } + + public function testWithUriSetsHostIfNotSet() + { + $r = (new Request('GET', 'http://foo.com/baz?bar=bam'))->withoutHeader('Host'); + $this->assertSame([], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.baz.com/bar'), true); + $this->assertSame('www.baz.com', $r2->getHeaderLine('Host')); + } + + public function testOverridesHostWithUri() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam'); + $this->assertSame(['Host' => ['foo.com']], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.baz.com/bar')); + $this->assertSame('www.baz.com', $r2->getHeaderLine('Host')); + } + + public function testAggregatesHeaders() + { + $r = new Request('GET', '', [ + 'ZOO' => 'zoobar', + 'zoo' => ['foobar', 'zoobar'] + ]); + $this->assertSame(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders()); + $this->assertSame('zoobar, foobar, zoobar', $r->getHeaderLine('zoo')); + } + + public function testAddsPortToHeader() + { + $r = new Request('GET', 'http://foo.com:8124/bar'); + $this->assertSame('foo.com:8124', $r->getHeaderLine('host')); + } + + public function testAddsPortToHeaderAndReplacePreviousPort() + { + $r = new Request('GET', 'http://foo.com:8124/bar'); + $r = $r->withUri(new Uri('http://foo.com:8125/bar')); + $this->assertSame('foo.com:8125', $r->getHeaderLine('host')); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/ResponseTest.php php-guzzlehttp-psr7-1.7.0/tests/ResponseTest.php --- php-guzzlehttp-psr7-1.4.2/tests/ResponseTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/ResponseTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,371 @@ +assertSame(200, $r->getStatusCode()); + $this->assertSame('1.1', $r->getProtocolVersion()); + $this->assertSame('OK', $r->getReasonPhrase()); + $this->assertSame([], $r->getHeaders()); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('', (string) $r->getBody()); + } + + public function testCanConstructWithStatusCode() + { + $r = new Response(404); + $this->assertSame(404, $r->getStatusCode()); + $this->assertSame('Not Found', $r->getReasonPhrase()); + } + + public function testConstructorDoesNotReadStreamBody() + { + $streamIsRead = false; + $body = Psr7\FnStream::decorate(Psr7\Utils::streamFor(''), [ + '__toString' => function () use (&$streamIsRead) { + $streamIsRead = true; + return ''; + } + ]); + + $r = new Response(200, [], $body); + $this->assertFalse($streamIsRead); + $this->assertSame($body, $r->getBody()); + } + + public function testStatusCanBeNumericString() + { + $r = new Response('404'); + $r2 = $r->withStatus('201'); + $this->assertSame(404, $r->getStatusCode()); + $this->assertSame('Not Found', $r->getReasonPhrase()); + $this->assertSame(201, $r2->getStatusCode()); + $this->assertSame('Created', $r2->getReasonPhrase()); + } + + public function testCanConstructWithHeaders() + { + $r = new Response(200, ['Foo' => 'Bar']); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame('Bar', $r->getHeaderLine('Foo')); + $this->assertSame(['Bar'], $r->getHeader('Foo')); + } + + public function testCanConstructWithHeadersAsArray() + { + $r = new Response(200, [ + 'Foo' => ['baz', 'bar'] + ]); + $this->assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders()); + $this->assertSame('baz, bar', $r->getHeaderLine('Foo')); + $this->assertSame(['baz', 'bar'], $r->getHeader('Foo')); + } + + public function testCanConstructWithBody() + { + $r = new Response(200, [], 'baz'); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('baz', (string) $r->getBody()); + } + + public function testNullBody() + { + $r = new Response(200, [], null); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('', (string) $r->getBody()); + } + + public function testFalseyBody() + { + $r = new Response(200, [], '0'); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('0', (string) $r->getBody()); + } + + public function testCanConstructWithReason() + { + $r = new Response(200, [], null, '1.1', 'bar'); + $this->assertSame('bar', $r->getReasonPhrase()); + + $r = new Response(200, [], null, '1.1', '0'); + $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works'); + } + + public function testCanConstructWithProtocolVersion() + { + $r = new Response(200, [], null, '1000'); + $this->assertSame('1000', $r->getProtocolVersion()); + } + + public function testWithStatusCodeAndNoReason() + { + $r = (new Response())->withStatus(201); + $this->assertSame(201, $r->getStatusCode()); + $this->assertSame('Created', $r->getReasonPhrase()); + } + + public function testWithStatusCodeAndReason() + { + $r = (new Response())->withStatus(201, 'Foo'); + $this->assertSame(201, $r->getStatusCode()); + $this->assertSame('Foo', $r->getReasonPhrase()); + + $r = (new Response())->withStatus(201, '0'); + $this->assertSame(201, $r->getStatusCode()); + $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works'); + } + + public function testWithProtocolVersion() + { + $r = (new Response())->withProtocolVersion('1000'); + $this->assertSame('1000', $r->getProtocolVersion()); + } + + public function testSameInstanceWhenSameProtocol() + { + $r = new Response(); + $this->assertSame($r, $r->withProtocolVersion('1.1')); + } + + public function testWithBody() + { + $b = Psr7\Utils::streamFor('0'); + $r = (new Response())->withBody($b); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + $this->assertSame('0', (string) $r->getBody()); + } + + public function testSameInstanceWhenSameBody() + { + $r = new Response(); + $b = $r->getBody(); + $this->assertSame($r, $r->withBody($b)); + } + + public function testWithHeader() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('baZ', 'Bam'); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders()); + $this->assertSame('Bam', $r2->getHeaderLine('baz')); + $this->assertSame(['Bam'], $r2->getHeader('baz')); + } + + public function testNumericHeaderValue() + { + $r = (new Response())->withHeader('Api-Version', 1); + $this->assertSame(['Api-Version' => ['1']], $r->getHeaders()); + } + + public function testWithHeaderAsArray() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('baZ', ['Bam', 'Bar']); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders()); + $this->assertSame('Bam, Bar', $r2->getHeaderLine('baz')); + $this->assertSame(['Bam', 'Bar'], $r2->getHeader('baz')); + } + + public function testWithHeaderReplacesDifferentCase() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('foO', 'Bam'); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['foO' => ['Bam']], $r2->getHeaders()); + $this->assertSame('Bam', $r2->getHeaderLine('foo')); + $this->assertSame(['Bam'], $r2->getHeader('foo')); + } + + public function testWithAddedHeader() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('foO', 'Baz'); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders()); + $this->assertSame('Bar, Baz', $r2->getHeaderLine('foo')); + $this->assertSame(['Bar', 'Baz'], $r2->getHeader('foo')); + } + + public function testWithAddedHeaderAsArray() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders()); + $this->assertSame('Bar, Baz, Bam', $r2->getHeaderLine('foo')); + $this->assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo')); + } + + public function testWithAddedHeaderThatDoesNotExist() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('nEw', 'Baz'); + $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders()); + $this->assertSame('Baz', $r2->getHeaderLine('new')); + $this->assertSame(['Baz'], $r2->getHeader('new')); + } + + public function testWithoutHeaderThatExists() + { + $r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']); + $r2 = $r->withoutHeader('foO'); + $this->assertTrue($r->hasHeader('foo')); + $this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders()); + $this->assertFalse($r2->hasHeader('foo')); + $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + } + + public function testWithoutHeaderThatDoesNotExist() + { + $r = new Response(200, ['Baz' => 'Bam']); + $r2 = $r->withoutHeader('foO'); + $this->assertSame($r, $r2); + $this->assertFalse($r2->hasHeader('foo')); + $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + } + + public function testSameInstanceWhenRemovingMissingHeader() + { + $r = new Response(); + $this->assertSame($r, $r->withoutHeader('foo')); + } + + public function testPassNumericHeaderNameInConstructor() + { + $r = new Response(200, ['Location' => 'foo', '123' => 'bar']); + $this->assertSame('bar', $r->getHeaderLine('123')); + } + + /** + * @dataProvider invalidHeaderProvider + */ + public function testConstructResponseInvalidHeader($header, $headerValue, $expectedMessage) + { + $this->expectExceptionGuzzle('InvalidArgumentException', $expectedMessage); + new Response(200, [$header => $headerValue]); + } + + public function invalidHeaderProvider() + { + return [ + ['foo', [], 'Header value can not be an empty array.'], + ['', '', 'Header name can not be empty.'], + ['foo', new \stdClass(), 'Header value must be scalar or null but stdClass provided.'], + ]; + } + + /** + * @dataProvider invalidWithHeaderProvider + */ + public function testWithInvalidHeader($header, $headerValue, $expectedMessage) + { + $r = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', $expectedMessage); + $r->withHeader($header, $headerValue); + } + + public function invalidWithHeaderProvider() + { + return array_merge($this->invalidHeaderProvider(), [ + [[], 'foo', 'Header name must be a string but array provided.'], + [false, 'foo', 'Header name must be a string but boolean provided.'], + [new \stdClass(), 'foo', 'Header name must be a string but stdClass provided.'], + ]); + } + + public function testHeaderValuesAreTrimmed() + { + $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]); + $r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t "); + $r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t ");; + + foreach ([$r1, $r2, $r3] as $r) { + $this->assertSame(['OWS' => ['Foo']], $r->getHeaders()); + $this->assertSame('Foo', $r->getHeaderLine('OWS')); + $this->assertSame(['Foo'], $r->getHeader('OWS')); + } + } + + public function testWithAddedHeaderArrayValueAndKeys() + { + $message = (new Response())->withAddedHeader('list', ['foo' => 'one']); + $message = $message->withAddedHeader('list', ['foo' => 'two', 'bar' => 'three']); + + $headerLine = $message->getHeaderLine('list'); + $this->assertSame('one, two, three', $headerLine); + } + + /** + * @dataProvider nonIntegerStatusCodeProvider + * @param mixed $invalidValues + */ + public function testConstructResponseWithNonIntegerStatusCode($invalidValues) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value.'); + new Response($invalidValues); + } + + /** + * @dataProvider nonIntegerStatusCodeProvider + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithNonInteger($invalidValues) + { + $response = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value.'); + $response->withStatus($invalidValues); + } + + public function nonIntegerStatusCodeProvider() + { + return [ + ['whatever'], + ['1.01'], + [1.01], + [new \stdClass()], + ]; + } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * @param mixed $invalidValues + */ + public function testConstructResponseWithInvalidRangeStatusCode($invalidValues) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + new Response($invalidValues); + } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithWithInvalidRange($invalidValues) + { + $response = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + $response->withStatus($invalidValues); + } + + public function invalidStatusCodeRangeProvider() + { + return [ + [600], + [99], + ]; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/ServerRequestTest.php php-guzzlehttp-psr7-1.7.0/tests/ServerRequestTest.php --- php-guzzlehttp-psr7-1.4.2/tests/ServerRequestTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/ServerRequestTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,544 @@ + [ + [ + 'file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => '0', + 'size' => '123' + ] + ], + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ] + ], + 'Empty file' => [ + [ + 'image_file' => [ + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => '4', + 'size' => '0' + ] + ], + [ + 'image_file' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ] + ], + 'Already Converted' => [ + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ], + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ] + ], + 'Already Converted array' => [ + [ + 'file' => [ + new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ], + ], + [ + 'file' => [ + new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ], + ] + ], + 'Multiple files' => [ + [ + 'text_file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => '0', + 'size' => '123' + ], + 'image_file' => [ + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => '4', + 'size' => '0' + ] + ], + [ + 'text_file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + 'image_file' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ] + ], + 'Nested files' => [ + [ + 'file' => [ + 'name' => [ + 0 => 'MyFile.txt', + 1 => 'Image.png', + ], + 'type' => [ + 0 => 'text/plain', + 1 => 'image/png', + ], + 'tmp_name' => [ + 0 => '/tmp/php/hp9hskjhf', + 1 => '/tmp/php/php1h4j1o', + ], + 'error' => [ + 0 => '0', + 1 => '0', + ], + 'size' => [ + 0 => '123', + 1 => '7349', + ], + ], + 'nested' => [ + 'name' => [ + 'other' => 'Flag.txt', + 'test' => [ + 0 => 'Stuff.txt', + 1 => '', + ], + ], + 'type' => [ + 'other' => 'text/plain', + 'test' => [ + 0 => 'text/plain', + 1 => '', + ], + ], + 'tmp_name' => [ + 'other' => '/tmp/php/hp9hskjhf', + 'test' => [ + 0 => '/tmp/php/asifu2gp3', + 1 => '', + ], + ], + 'error' => [ + 'other' => '0', + 'test' => [ + 0 => '0', + 1 => '4', + ], + ], + 'size' => [ + 'other' => '421', + 'test' => [ + 0 => '32', + 1 => '0', + ] + ] + ], + ], + [ + 'file' => [ + 0 => new UploadedFile( + '/tmp/php/hp9hskjhf', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + 1 => new UploadedFile( + '/tmp/php/php1h4j1o', + 7349, + UPLOAD_ERR_OK, + 'Image.png', + 'image/png' + ), + ], + 'nested' => [ + 'other' => new UploadedFile( + '/tmp/php/hp9hskjhf', + 421, + UPLOAD_ERR_OK, + 'Flag.txt', + 'text/plain' + ), + 'test' => [ + 0 => new UploadedFile( + '/tmp/php/asifu2gp3', + 32, + UPLOAD_ERR_OK, + 'Stuff.txt', + 'text/plain' + ), + 1 => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ), + ] + ] + ] + ] + ]; + } + + /** + * @dataProvider dataNormalizeFiles + */ + public function testNormalizeFiles($files, $expected) + { + $result = ServerRequest::normalizeFiles($files); + + $this->assertEquals($expected, $result); + } + + public function testNormalizeFilesRaisesException() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid value in files specification'); + ServerRequest::normalizeFiles(['test' => 'something']); + } + + public function dataGetUriFromGlobals() + { + $server = [ + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + 'SERVER_PORT' => '443', + 'SERVER_ADDR' => '217.112.82.20', + 'SERVER_NAME' => 'www.example.org', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'HTTP_HOST' => 'www.example.org', + 'HTTPS' => 'on', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_PORT' => '5390', + 'SCRIPT_NAME' => '/blog/article.php', + 'SCRIPT_FILENAME' => '/path/to/your/server/root/blog/article.php', + 'PHP_SELF' => '/blog/article.php', + ]; + + return [ + 'HTTPS request' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + $server, + ], + 'HTTPS request with different on value' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTPS' => '1']), + ], + 'HTTP request' => [ + 'http://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTPS' => 'off', 'SERVER_PORT' => '80']), + ], + 'HTTP_HOST missing -> fallback to SERVER_NAME' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => null]), + ], + 'HTTP_HOST and SERVER_NAME missing -> fallback to SERVER_ADDR' => [ + 'https://217.112.82.20/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => null, 'SERVER_NAME' => null]), + ], + 'Query string with ?' => [ + 'https://www.example.org/path?continue=https://example.com/path?param=1', + array_merge($server, ['REQUEST_URI' => '/path?continue=https://example.com/path?param=1', 'QUERY_STRING' => '']), + ], + 'No query String' => [ + 'https://www.example.org/blog/article.php', + array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']), + ], + 'Host header with port' => [ + 'https://www.example.org:8324/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'www.example.org:8324']), + ], + 'IPv6 local loopback address' => [ + 'https://[::1]:8000/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => '[::1]:8000']), + ], + 'Invalid host' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'a:b']), + ], + 'Different port with SERVER_PORT' => [ + 'https://www.example.org:8324/blog/article.php?id=10&user=foo', + array_merge($server, ['SERVER_PORT' => '8324']), + ], + 'REQUEST_URI missing query string' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['REQUEST_URI' => '/blog/article.php']), + ], + 'Empty server variable' => [ + 'http://localhost', + [], + ], + ]; + } + + /** + * @dataProvider dataGetUriFromGlobals + */ + public function testGetUriFromGlobals($expected, $serverParams) + { + $_SERVER = $serverParams; + + $this->assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals()); + } + + public function testFromGlobals() + { + $_SERVER = [ + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + 'SERVER_PORT' => '443', + 'SERVER_ADDR' => '217.112.82.20', + 'SERVER_NAME' => 'www.example.org', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'CONTENT_TYPE' => 'text/plain', + 'HTTP_HOST' => 'www.example.org', + 'HTTP_ACCEPT' => 'text/html', + 'HTTP_REFERRER' => 'https://example.com', + 'HTTP_USER_AGENT' => 'My User Agent', + 'HTTPS' => 'on', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_PORT' => '5390', + 'SCRIPT_NAME' => '/blog/article.php', + 'SCRIPT_FILENAME' => '/path/to/your/server/root/blog/article.php', + 'PHP_SELF' => '/blog/article.php', + ]; + + $_COOKIE = [ + 'logged-in' => 'yes!' + ]; + + $_POST = [ + 'name' => 'Pesho', + 'email' => 'pesho@example.com', + ]; + + $_GET = [ + 'id' => 10, + 'user' => 'foo', + ]; + + $_FILES = [ + 'file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => UPLOAD_ERR_OK, + 'size' => 123, + ] + ]; + + $server = ServerRequest::fromGlobals(); + + $this->assertSame('POST', $server->getMethod()); + $this->assertEquals([ + 'Host' => ['www.example.org'], + 'Content-Type' => ['text/plain'], + 'Accept' => ['text/html'], + 'Referrer' => ['https://example.com'], + 'User-Agent' => ['My User Agent'], + ], $server->getHeaders()); + $this->assertSame('', (string) $server->getBody()); + $this->assertSame('1.1', $server->getProtocolVersion()); + $this->assertSame($_COOKIE, $server->getCookieParams()); + $this->assertSame($_POST, $server->getParsedBody()); + $this->assertSame($_GET, $server->getQueryParams()); + + $this->assertEquals( + new Uri('https://www.example.org/blog/article.php?id=10&user=foo'), + $server->getUri() + ); + + $expectedFiles = [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + ]; + + $this->assertEquals($expectedFiles, $server->getUploadedFiles()); + } + + public function testUploadedFiles() + { + $request1 = new ServerRequest('GET', '/'); + + $files = [ + 'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK) + ]; + + $request2 = $request1->withUploadedFiles($files); + + $this->assertNotSame($request2, $request1); + $this->assertSame([], $request1->getUploadedFiles()); + $this->assertSame($files, $request2->getUploadedFiles()); + } + + public function testServerParams() + { + $params = ['name' => 'value']; + + $request = new ServerRequest('GET', '/', [], null, '1.1', $params); + $this->assertSame($params, $request->getServerParams()); + } + + public function testCookieParams() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withCookieParams($params); + + $this->assertNotSame($request2, $request1); + $this->assertEmpty($request1->getCookieParams()); + $this->assertSame($params, $request2->getCookieParams()); + } + + public function testQueryParams() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withQueryParams($params); + + $this->assertNotSame($request2, $request1); + $this->assertEmpty($request1->getQueryParams()); + $this->assertSame($params, $request2->getQueryParams()); + } + + public function testParsedBody() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withParsedBody($params); + + $this->assertNotSame($request2, $request1); + $this->assertEmpty($request1->getParsedBody()); + $this->assertSame($params, $request2->getParsedBody()); + } + + public function testAttributes() + { + $request1 = new ServerRequest('GET', '/'); + + $request2 = $request1->withAttribute('name', 'value'); + $request3 = $request2->withAttribute('other', 'otherValue'); + $request4 = $request3->withoutAttribute('other'); + $request5 = $request3->withoutAttribute('unknown'); + + $this->assertNotSame($request2, $request1); + $this->assertNotSame($request3, $request2); + $this->assertNotSame($request4, $request3); + $this->assertSame($request5, $request3); + + $this->assertSame([], $request1->getAttributes()); + $this->assertNull($request1->getAttribute('name')); + $this->assertSame( + 'something', + $request1->getAttribute('name', 'something'), + 'Should return the default value' + ); + + $this->assertSame('value', $request2->getAttribute('name')); + $this->assertSame(['name' => 'value'], $request2->getAttributes()); + $this->assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes()); + $this->assertSame(['name' => 'value'], $request4->getAttributes()); + } + + public function testNullAttribute() + { + $request = (new ServerRequest('GET', '/'))->withAttribute('name', null); + + $this->assertSame(['name' => null], $request->getAttributes()); + $this->assertNull($request->getAttribute('name', 'different-default')); + + $requestWithoutAttribute = $request->withoutAttribute('name'); + + $this->assertSame([], $requestWithoutAttribute->getAttributes()); + $this->assertSame('different-default', $requestWithoutAttribute->getAttribute('name', 'different-default')); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/StreamDecoratorTraitTest.php php-guzzlehttp-psr7-1.7.0/tests/StreamDecoratorTraitTest.php --- php-guzzlehttp-psr7-1.4.2/tests/StreamDecoratorTraitTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/StreamDecoratorTraitTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,143 @@ +c = fopen('php://temp', 'r+'); + fwrite($this->c, 'foo'); + fseek($this->c, 0); + $this->a = Psr7\Utils::streamFor($this->c); + $this->b = new Str($this->a); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['read']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('read') + ->will($this->throwException(new \Exception('foo'))); + $msg = ''; + set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; }); + echo new Str($s); + restore_error_handler(); + $this->assertStringContainsStringGuzzle('foo', $msg); + } + + public function testToString() + { + $this->assertSame('foo', (string) $this->b); + } + + public function testHasSize() + { + $this->assertSame(3, $this->b->getSize()); + } + + public function testReads() + { + $this->assertSame('foo', $this->b->read(10)); + } + + public function testCheckMethods() + { + $this->assertSame($this->a->isReadable(), $this->b->isReadable()); + $this->assertSame($this->a->isWritable(), $this->b->isWritable()); + $this->assertSame($this->a->isSeekable(), $this->b->isSeekable()); + } + + public function testSeeksAndTells() + { + $this->b->seek(1); + $this->assertSame(1, $this->a->tell()); + $this->assertSame(1, $this->b->tell()); + $this->b->seek(0); + $this->assertSame(0, $this->a->tell()); + $this->assertSame(0, $this->b->tell()); + $this->b->seek(0, SEEK_END); + $this->assertSame(3, $this->a->tell()); + $this->assertSame(3, $this->b->tell()); + } + + public function testGetsContents() + { + $this->assertSame('foo', $this->b->getContents()); + $this->assertSame('', $this->b->getContents()); + $this->b->seek(1); + $this->assertSame('oo', $this->b->getContents()); + } + + public function testCloses() + { + $this->b->close(); + $this->assertFalse(is_resource($this->c)); + } + + public function testDetaches() + { + $this->b->detach(); + $this->assertFalse($this->b->isReadable()); + } + + public function testWrapsMetadata() + { + $this->assertSame($this->b->getMetadata(), $this->a->getMetadata()); + $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri')); + } + + public function testWrapsWrites() + { + $this->b->seek(0, SEEK_END); + $this->b->write('foo'); + $this->assertSame('foofoo', (string) $this->a); + } + + public function testThrowsWithInvalidGetter() + { + $this->expectExceptionGuzzle('UnexpectedValueException'); + + $this->b->foo; + } + + public function testThrowsWhenGetterNotImplemented() + { + $s = new BadStream(); + + $this->expectExceptionGuzzle('BadMethodCallException'); + + $s->stream; + } +} + +class BadStream +{ + use StreamDecoratorTrait; + + public function __construct() {} +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/StreamTest.php php-guzzlehttp-psr7-1.7.0/tests/StreamTest.php --- php-guzzlehttp-psr7-1.4.2/tests/StreamTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/StreamTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,392 @@ +expectExceptionGuzzle('InvalidArgumentException'); + + new Stream(true); + } + + public function testConstructorInitializesProperties() + { + $handle = fopen('php://temp', 'r+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isSeekable()); + $this->assertSame('php://temp', $stream->getMetadata('uri')); + $this->assertInternalTypeGuzzle('array', $stream->getMetadata()); + $this->assertSame(4, $stream->getSize()); + $this->assertFalse($stream->eof()); + $stream->close(); + } + + public function testConstructorInitializesPropertiesWithRbPlus() + { + $handle = fopen('php://temp', 'rb+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isSeekable()); + $this->assertSame('php://temp', $stream->getMetadata('uri')); + $this->assertInternalTypeGuzzle('array', $stream->getMetadata()); + $this->assertSame(4, $stream->getSize()); + $this->assertFalse($stream->eof()); + $stream->close(); + } + + public function testStreamClosesHandleOnDestruct() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + unset($stream); + $this->assertFalse(is_resource($handle)); + } + + public function testConvertsToString() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertSame('data', (string) $stream); + $this->assertSame('data', (string) $stream); + $stream->close(); + } + + public function testConvertsToStringNonSeekableStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('This does not work on HHVM.'); + } + + $handle = popen('echo foo', 'r'); + $stream = new Stream($handle); + $this->assertFalse($stream->isSeekable()); + $this->assertSame('foo', trim((string) $stream)); + } + + public function testConvertsToStringNonSeekablePartiallyReadStream() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('This does not work on HHVM.'); + } + + $handle = popen('echo bar', 'r'); + $stream = new Stream($handle); + $firstLetter = $stream->read(1); + $this->assertFalse($stream->isSeekable()); + $this->assertSame('b', $firstLetter); + $this->assertSame('ar', trim((string) $stream)); + } + + public function testGetsContents() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertSame('', $stream->getContents()); + $stream->seek(0); + $this->assertSame('data', $stream->getContents()); + $this->assertSame('', $stream->getContents()); + $stream->close(); + } + + public function testChecksEof() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertSame(4, $stream->tell(), 'Stream cursor already at the end'); + $this->assertFalse($stream->eof(), 'Stream still not eof'); + $this->assertSame('', $stream->read(1), 'Need to read one more byte to reach eof'); + $this->assertTrue($stream->eof()); + $stream->close(); + } + + public function testGetSize() + { + $size = filesize(__FILE__); + $handle = fopen(__FILE__, 'r'); + $stream = new Stream($handle); + $this->assertSame($size, $stream->getSize()); + // Load from cache + $this->assertSame($size, $stream->getSize()); + $stream->close(); + } + + public function testEnsuresSizeIsConsistent() + { + $h = fopen('php://temp', 'w+'); + $this->assertSame(3, fwrite($h, 'foo')); + $stream = new Stream($h); + $this->assertSame(3, $stream->getSize()); + $this->assertSame(4, $stream->write('test')); + $this->assertSame(7, $stream->getSize()); + $this->assertSame(7, $stream->getSize()); + $stream->close(); + } + + public function testProvidesStreamPosition() + { + $handle = fopen('php://temp', 'w+'); + $stream = new Stream($handle); + $this->assertSame(0, $stream->tell()); + $stream->write('foo'); + $this->assertSame(3, $stream->tell()); + $stream->seek(1); + $this->assertSame(1, $stream->tell()); + $this->assertSame(ftell($handle), $stream->tell()); + $stream->close(); + } + + public function testDetachStreamAndClearProperties() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + $this->assertSame($handle, $stream->detach()); + $this->assertInternalTypeGuzzle('resource', $handle, 'Stream is not closed'); + $this->assertNull($stream->detach()); + + $this->assertStreamStateAfterClosedOrDetached($stream); + + $stream->close(); + } + + public function testCloseResourceAndClearProperties() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + $stream->close(); + + $this->assertFalse(is_resource($handle)); + + $this->assertStreamStateAfterClosedOrDetached($stream); + } + + private function assertStreamStateAfterClosedOrDetached(Stream $stream) + { + $this->assertFalse($stream->isReadable()); + $this->assertFalse($stream->isWritable()); + $this->assertFalse($stream->isSeekable()); + $this->assertNull($stream->getSize()); + $this->assertSame([], $stream->getMetadata()); + $this->assertNull($stream->getMetadata('foo')); + + $throws = function (callable $fn) { + try { + $fn(); + } catch (\Exception $e) { + $this->assertStringContainsStringGuzzle('Stream is detached', $e->getMessage()); + + return; + } + + $this->fail('Exception should be thrown after the stream is detached.'); + }; + + $throws(function () use ($stream) { $stream->read(10); }); + $throws(function () use ($stream) { $stream->write('bar'); }); + $throws(function () use ($stream) { $stream->seek(10); }); + $throws(function () use ($stream) { $stream->tell(); }); + $throws(function () use ($stream) { $stream->eof(); }); + $throws(function () use ($stream) { $stream->getContents(); }); + $this->assertSame('', (string) $stream); + } + + public function testStreamReadingWithZeroLength() + { + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + $this->assertSame('', $stream->read(0)); + + $stream->close(); + } + + public function testStreamReadingWithNegativeLength() + { + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + $this->expectExceptionGuzzle('RuntimeException', 'Length parameter cannot be negative'); + + try { + $stream->read(-1); + } catch (\Exception $e) { + $stream->close(); + throw $e; + } + + $stream->close(); + } + + public function testStreamReadingFreadError() + { + self::$isFReadError = true; + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + $this->expectExceptionGuzzle('RuntimeException', 'Unable to read from stream'); + + try { + $stream->read(1); + } catch (\Exception $e) { + self::$isFReadError = false; + $stream->close(); + throw $e; + } + + self::$isFReadError = false; + $stream->close(); + } + + /** + * @dataProvider gzipModeProvider + * + * @param string $mode + * @param bool $readable + * @param bool $writable + */ + public function testGzipStreamModes($mode, $readable, $writable) + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('This does not work on HHVM.'); + } + + $r = gzopen('php://temp', $mode); + $stream = new Stream($r); + + $this->assertSame($readable, $stream->isReadable()); + $this->assertSame($writable, $stream->isWritable()); + + $stream->close(); + } + + public function gzipModeProvider() + { + return [ + ['mode' => 'rb9', 'readable' => true, 'writable' => false], + ['mode' => 'wb2', 'readable' => false, 'writable' => true], + ]; + } + + /** + * @dataProvider readableModeProvider + * + * @param string $mode + */ + public function testReadableStream($mode) + { + $r = fopen('php://temp', $mode); + $stream = new Stream($r); + + $this->assertTrue($stream->isReadable()); + + $stream->close(); + } + + public function readableModeProvider() + { + return [ + ['r'], + ['w+'], + ['r+'], + ['x+'], + ['c+'], + ['rb'], + ['w+b'], + ['r+b'], + ['x+b'], + ['c+b'], + ['rt'], + ['w+t'], + ['r+t'], + ['x+t'], + ['c+t'], + ['a+'], + ['rb+'], + ]; + } + + public function testWriteOnlyStreamIsNotReadable() + { + $r = fopen('php://output', 'w'); + $stream = new Stream($r); + + $this->assertFalse($stream->isReadable()); + + $stream->close(); + } + + /** + * @dataProvider writableModeProvider + * + * @param string $mode + */ + public function testWritableStream($mode) + { + $r = fopen('php://temp', $mode); + $stream = new Stream($r); + + $this->assertTrue($stream->isWritable()); + + $stream->close(); + } + + public function writableModeProvider() + { + return [ + ['w'], + ['w+'], + ['rw'], + ['r+'], + ['x+'], + ['c+'], + ['wb'], + ['w+b'], + ['r+b'], + ['rb+'], + ['x+b'], + ['c+b'], + ['w+t'], + ['r+t'], + ['x+t'], + ['c+t'], + ['a'], + ['a+'], + ]; + } + + public function testReadOnlyStreamIsNotWritable() + { + $r = fopen('php://input', 'r'); + $stream = new Stream($r); + + $this->assertFalse($stream->isWritable()); + + $stream->close(); + } +} + +namespace GuzzleHttp\Psr7; + +use GuzzleHttp\Tests\Psr7\StreamTest; + +function fread($handle, $length) +{ + return StreamTest::$isFReadError ? false : \fread($handle, $length); +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/StreamWrapperTest.php php-guzzlehttp-psr7-1.7.0/tests/StreamWrapperTest.php --- php-guzzlehttp-psr7-1.4.2/tests/StreamWrapperTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/StreamWrapperTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,200 @@ +assertSame('foo', fread($handle, 3)); + $this->assertSame(3, ftell($handle)); + $this->assertSame(3, fwrite($handle, 'bar')); + $this->assertSame(0, fseek($handle, 0)); + $this->assertSame('foobar', fread($handle, 6)); + $this->assertSame('', fread($handle, 1)); + $this->assertTrue(feof($handle)); + + $stBlksize = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0; + + // This fails on HHVM for some reason + if (!defined('HHVM_VERSION')) { + $this->assertSame([ + 0 => 0, + 1 => 0, + 2 => 33206, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 6, + 8 => 0, + 9 => 0, + 10 => 0, + 11 => $stBlksize, + 12 => $stBlksize, + 'dev' => 0, + 'ino' => 0, + 'mode' => 33206, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 6, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => $stBlksize, + 'blocks' => $stBlksize, + ], fstat($handle)); + } + + $this->assertTrue(fclose($handle)); + $this->assertSame('foobar', (string) $stream); + } + + public function testStreamContext() + { + $stream = Psr7\Utils::streamFor('foo'); + + $this->assertSame('foo', file_get_contents('guzzle://stream', false, StreamWrapper::createStreamContext($stream))); + } + + public function testStreamCast() + { + $streams = [ + StreamWrapper::getResource(Psr7\Utils::streamFor('foo')), + StreamWrapper::getResource(Psr7\Utils::streamFor('bar')) + ]; + $write = null; + $except = null; + $this->assertInternalTypeGuzzle('integer', stream_select($streams, $write, $except, 0)); + } + + public function testValidatesStream() + { + $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + $stream->expects($this->once()) + ->method('isWritable') + ->will($this->returnValue(false)); + + $this->expectExceptionGuzzle('InvalidArgumentException'); + + StreamWrapper::getResource($stream); + } + + public function testReturnsFalseWhenStreamDoesNotExist() + { + $this->expectWarningGuzzle(); + + fopen('guzzle://foo', 'r'); + } + + public function testCanOpenReadonlyStream() + { + $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + $stream->expects($this->once()) + ->method('isWritable') + ->will($this->returnValue(true)); + $r = StreamWrapper::getResource($stream); + $this->assertInternalTypeGuzzle('resource', $r); + fclose($r); + } + + public function testUrlStat() + { + StreamWrapper::register(); + + $stBlksize = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0; + + $this->assertSame([ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + 9 => 0, + 10 => 0, + 11 => $stBlksize, + 12 => $stBlksize, + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => $stBlksize, + 'blocks' => $stBlksize, + ], stat('guzzle://stream')); + } + + public function testXmlReaderWithStream() + { + if (!class_exists('XMLReader')) { + $this->markTestSkipped('XML Reader is not available.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('This does not work on HHVM.'); + } + + $stream = Psr7\Utils::streamFor(''); + + StreamWrapper::register(); + libxml_set_streams_context(StreamWrapper::createStreamContext($stream)); + $reader = new \XMLReader(); + + $this->assertTrue($reader->open('guzzle://stream')); + $this->assertTrue($reader->read()); + $this->assertSame('foo', $reader->name); + } + + public function testXmlWriterWithStream() + { + if (!class_exists('XMLWriter')) { + $this->markTestSkipped('XML Writer is not available.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('This does not work on HHVM.'); + } + + $stream = Psr7\Utils::streamFor(fopen('php://memory', 'wb')); + + StreamWrapper::register(); + libxml_set_streams_context(StreamWrapper::createStreamContext($stream)); + $writer = new \XMLWriter(); + + $this->assertTrue($writer->openURI('guzzle://stream')); + $this->assertTrue($writer->startDocument()); + $this->assertTrue($writer->writeElement('foo')); + $this->assertTrue($writer->endDocument()); + + $stream->rewind(); + $this->assertXmlStringEqualsXmlString('', (string) $stream); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/UploadedFileTest.php php-guzzlehttp-psr7-1.7.0/tests/UploadedFileTest.php --- php-guzzlehttp-psr7-1.4.2/tests/UploadedFileTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/UploadedFileTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,287 @@ +cleanup = []; + } + + /** + * @after + */ + public function tearDownTest() + { + foreach ($this->cleanup as $file) { + if (is_scalar($file) && file_exists($file)) { + unlink($file); + } + } + } + + public function invalidStreams() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'array' => [['filename']], + 'object' => [(object) ['filename']], + ]; + } + + /** + * @dataProvider invalidStreams + */ + public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile) + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK); + } + + public function invalidSizes() + { + return [ + 'null' => [null], + 'float' => [1.1], + 'array' => [[1]], + 'object' => [(object) [1]], + ]; + } + + /** + * @dataProvider invalidSizes + */ + public function testRaisesExceptionOnInvalidSize($size) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'size'); + + new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK); + } + + public function invalidErrorStatuses() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'float' => [1.1], + 'string' => ['1'], + 'array' => [[1]], + 'object' => [(object) [1]], + 'negative' => [-1], + 'too-big' => [9], + ]; + } + + /** + * @dataProvider invalidErrorStatuses + */ + public function testRaisesExceptionOnInvalidErrorStatus($status) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'status'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, $status); + } + + public function invalidFilenamesAndMediaTypes() + { + return [ + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'array' => [['string']], + 'object' => [(object) ['string']], + ]; + } + + /** + * @dataProvider invalidFilenamesAndMediaTypes + */ + public function testRaisesExceptionOnInvalidClientFilename($filename) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'filename'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename); + } + + /** + * @dataProvider invalidFilenamesAndMediaTypes + */ + public function testRaisesExceptionOnInvalidClientMediaType($mediaType) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'media type'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType); + } + + public function testGetStreamReturnsOriginalStreamObject() + { + $stream = new Stream(fopen('php://temp', 'r')); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->assertSame($stream, $upload->getStream()); + } + + public function testGetStreamReturnsWrappedPhpStream() + { + $stream = fopen('php://temp', 'wb+'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + $uploadStream = $upload->getStream()->detach(); + + $this->assertSame($stream, $uploadStream); + } + + public function testGetStreamReturnsStreamForFile() + { + $this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + $uploadStream = $upload->getStream(); + $r = new ReflectionProperty($uploadStream, 'filename'); + $r->setAccessible(true); + + $this->assertSame($stream, $r->getValue($uploadStream)); + } + + public function testSuccessful() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain'); + + $this->assertSame($stream->getSize(), $upload->getSize()); + $this->assertSame('filename.txt', $upload->getClientFilename()); + $this->assertSame('text/plain', $upload->getClientMediaType()); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful'); + $upload->moveTo($to); + $this->assertFileExists($to); + $this->assertSame($stream->__toString(), file_get_contents($to)); + } + + public function invalidMovePaths() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'empty' => [''], + 'array' => [['filename']], + 'object' => [(object) ['filename']], + ]; + } + + /** + * @dataProvider invalidMovePaths + */ + public function testMoveRaisesExceptionForInvalidPath($path) + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $path; + + $this->expectExceptionGuzzle('InvalidArgumentException', 'path'); + $upload->moveTo($path); + } + + public function testMoveCannotBeCalledMoreThanOnce() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); + $upload->moveTo($to); + $this->assertFileExists($to); + + $this->expectExceptionGuzzle('RuntimeException', 'moved'); + $upload->moveTo($to); + } + + public function testCannotRetrieveStreamAfterMove() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); + $upload->moveTo($to); + $this->assertFileExists($to); + + $this->expectExceptionGuzzle('RuntimeException', 'moved'); + $upload->getStream(); + } + + public function nonOkErrorStatus() + { + return [ + 'UPLOAD_ERR_INI_SIZE' => [ UPLOAD_ERR_INI_SIZE ], + 'UPLOAD_ERR_FORM_SIZE' => [ UPLOAD_ERR_FORM_SIZE ], + 'UPLOAD_ERR_PARTIAL' => [ UPLOAD_ERR_PARTIAL ], + 'UPLOAD_ERR_NO_FILE' => [ UPLOAD_ERR_NO_FILE ], + 'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ], + 'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ], + 'UPLOAD_ERR_EXTENSION' => [ UPLOAD_ERR_EXTENSION ], + ]; + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + $this->assertSame($status, $uploadedFile->getError()); + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testMoveToRaisesExceptionWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + $this->expectExceptionGuzzle('RuntimeException', 'upload error'); + $uploadedFile->moveTo(__DIR__ . '/' . sha1(uniqid('', true))); + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + $this->expectExceptionGuzzle('RuntimeException', 'upload error'); + $uploadedFile->getStream(); + } + + public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided() + { + $this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from'); + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to'); + + copy(__FILE__, $from); + + $uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain'); + $uploadedFile->moveTo($to); + + $this->assertFileEquals(__FILE__, $to); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/UriNormalizerTest.php php-guzzlehttp-psr7-1.7.0/tests/UriNormalizerTest.php --- php-guzzlehttp-psr7-1.4.2/tests/UriNormalizerTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/UriNormalizerTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,176 @@ +withPath("/$actualEncoding")->withQuery($actualEncoding); + + $this->assertSame("/$actualEncoding?$actualEncoding", (string) $uri, 'Not normalized automatically beforehand'); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::CAPITALIZE_PERCENT_ENCODING); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame("/$expectEncoding?$expectEncoding", (string) $normalizedUri); + } + + /** + * @dataProvider getUnreservedCharacters + */ + public function testDecodeUnreservedCharacters($char) + { + $percentEncoded = '%'.bin2hex($char); + // Add encoded reserved characters to test that those are not decoded and include the percent-encoded + // unreserved character both in lower and upper case to test the decoding is case-insensitive. + $encodedChars = $percentEncoded.'%2F%5B'.strtoupper($percentEncoded); + $uri = (new Uri())->withPath("/$encodedChars")->withQuery($encodedChars); + + $this->assertSame("/$encodedChars?$encodedChars", (string) $uri, 'Not normalized automatically beforehand'); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::DECODE_UNRESERVED_CHARACTERS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame("/$char%2F%5B$char?$char%2F%5B$char", (string) $normalizedUri); + } + + public function getUnreservedCharacters() + { + $unreservedChars = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9'), ['-', '.', '_', '~']); + + return array_map(function ($char) { + return [$char]; + }, $unreservedChars); + } + + /** + * @dataProvider getEmptyPathTestCases + */ + public function testConvertEmptyPath($uri, $expected) + { + $normalizedUri = UriNormalizer::normalize(new Uri($uri), UriNormalizer::CONVERT_EMPTY_PATH); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame($expected, (string) $normalizedUri); + } + + public function getEmptyPathTestCases() + { + return [ + ['http://example.org', 'http://example.org/'], + ['https://example.org', 'https://example.org/'], + ['urn://example.org', 'urn://example.org'], + ]; + } + + public function testRemoveDefaultHost() + { + $uri = new Uri('file://localhost/myfile'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DEFAULT_HOST); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('file:///myfile', (string) $normalizedUri); + } + + public function testRemoveDefaultPort() + { + $uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock(); + $uri->expects($this->any())->method('getScheme')->will($this->returnValue('http')); + $uri->expects($this->any())->method('getPort')->will($this->returnValue(80)); + $uri->expects($this->once())->method('withPort')->with(null)->will($this->returnValue(new Uri('http://example.org'))); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DEFAULT_PORT); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertNull($normalizedUri->getPort()); + } + + public function testRemoveDotSegments() + { + $uri = new Uri('http://example.org/../a/b/../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('http://example.org/a/c/d.html', (string) $normalizedUri); + } + + public function testRemoveDotSegmentsOfAbsolutePathReference() + { + $uri = new Uri('/../a/b/../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('/a/c/d.html', (string) $normalizedUri); + } + + public function testRemoveDotSegmentsOfRelativePathReference() + { + $uri = new Uri('../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('../c/./d.html', (string) $normalizedUri); + } + + public function testRemoveDuplicateSlashes() + { + $uri = new Uri('http://example.org//foo///bar/bam.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DUPLICATE_SLASHES); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('http://example.org/foo/bar/bam.html', (string) $normalizedUri); + } + + public function testSortQueryParameters() + { + $uri = new Uri('?lang=en&article=fred'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::SORT_QUERY_PARAMETERS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('?article=fred&lang=en', (string) $normalizedUri); + } + + public function testSortQueryParametersWithSameKeys() + { + $uri = new Uri('?a=b&b=c&a=a&a&b=a&b=b&a=d&a=c'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::SORT_QUERY_PARAMETERS); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + $this->assertSame('?a&a=a&a=b&a=c&a=d&b=a&b=b&b=c', (string) $normalizedUri); + } + + /** + * @dataProvider getEquivalentTestCases + */ + public function testIsEquivalent($uri1, $uri2, $expected) + { + $equivalent = UriNormalizer::isEquivalent(new Uri($uri1), new Uri($uri2)); + + $this->assertSame($expected, $equivalent); + } + + public function getEquivalentTestCases() + { + return [ + ['http://example.org', 'http://example.org', true], + ['hTTp://eXaMpLe.org', 'http://example.org', true], + ['http://example.org/path?#', 'http://example.org/path', true], + ['http://example.org:80', 'http://example.org/', true], + ['http://example.org/../a/.././p%61th?%7a=%5e', 'http://example.org/path?z=%5E', true], + ['https://example.org/', 'http://example.org/', false], + ['https://example.org/', '//example.org/', false], + ['//example.org/', '//example.org/', true], + ['file:/myfile', 'file:///myfile', true], + ['file:///myfile', 'file://localhost/myfile', true], + ]; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/UriResoverTest.php php-guzzlehttp-psr7-1.7.0/tests/UriResoverTest.php --- php-guzzlehttp-psr7-1.4.2/tests/UriResoverTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/UriResoverTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,204 @@ +assertInstanceOf('Psr\Http\Message\UriInterface', $targetUri); + $this->assertSame($expectedTarget, (string) $targetUri); + // This ensures there are no test cases that only work in the resolve() direction but not the + // opposite via relativize(). This can happen when both base and rel URI are relative-path + // references resulting in another relative-path URI. + $this->assertSame($expectedTarget, (string) UriResolver::resolve($baseUri, $targetUri)); + } + + /** + * @dataProvider getResolveTestCases + */ + public function testRelativizeUri($base, $expectedRelativeReference, $target) + { + $baseUri = new Uri($base); + $relativeUri = UriResolver::relativize($baseUri, new Uri($target)); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $relativeUri); + // There are test-cases with too many dot-segments and relative references that are equal like "." == "./". + // So apart from the same-as condition, this alternative success condition is necessary. + $this->assertTrue( + $expectedRelativeReference === (string) $relativeUri + || $target === (string) UriResolver::resolve($baseUri, $relativeUri), + sprintf( + '"%s" is not the correct relative reference as it does not resolve to the target URI from the base URI', + (string) $relativeUri + ) + ); + } + + /** + * @dataProvider getRelativizeTestCases + */ + public function testRelativizeUriWithUniqueTests($base, $target, $expectedRelativeReference) + { + $baseUri = new Uri($base); + $targetUri = new Uri($target); + $relativeUri = UriResolver::relativize($baseUri, $targetUri); + + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $relativeUri); + $this->assertSame($expectedRelativeReference, (string) $relativeUri); + + $this->assertSame((string) UriResolver::resolve($baseUri, $targetUri), (string) UriResolver::resolve($baseUri, $relativeUri)); + } + + public function getResolveTestCases() + { + return [ + [self::RFC3986_BASE, 'g:h', 'g:h'], + [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], + [self::RFC3986_BASE, './g', 'http://a/b/c/g'], + [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], + [self::RFC3986_BASE, '/g', 'http://a/g'], + [self::RFC3986_BASE, '//g', 'http://g'], + [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'], + [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'], + [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'], + [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'], + [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'], + [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'], + [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'], + [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'], + [self::RFC3986_BASE, '', self::RFC3986_BASE], + [self::RFC3986_BASE, '.', 'http://a/b/c/'], + [self::RFC3986_BASE, './', 'http://a/b/c/'], + [self::RFC3986_BASE, '..', 'http://a/b/'], + [self::RFC3986_BASE, '../', 'http://a/b/'], + [self::RFC3986_BASE, '../g', 'http://a/b/g'], + [self::RFC3986_BASE, '../..', 'http://a/'], + [self::RFC3986_BASE, '../../', 'http://a/'], + [self::RFC3986_BASE, '../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../../g', 'http://a/g'], + [self::RFC3986_BASE, '/./g', 'http://a/g'], + [self::RFC3986_BASE, '/../g', 'http://a/g'], + [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'], + [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'], + [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'], + [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'], + [self::RFC3986_BASE, './../g', 'http://a/b/g'], + [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'], + [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'], + [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'], + [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'], + [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'], + [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'], + // dot-segments in the query or fragment + [self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'], + [self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'], + [self::RFC3986_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x'], + [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], + [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], + [self::RFC3986_BASE, '?y#s', 'http://a/b/c/d;p?y#s'], + // base with fragment + ['http://a/b/c?q#s', '?y', 'http://a/b/c?y'], + // base with user info + ['http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'], + ['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'], + // path ending with slash or no slash at all + ['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'], + ['urn:no-slash', 'e', 'urn:e'], + // falsey relative parts + [self::RFC3986_BASE, '//0', 'http://0'], + [self::RFC3986_BASE, '0', 'http://a/b/c/0'], + [self::RFC3986_BASE, '?0', 'http://a/b/c/d;p?0'], + [self::RFC3986_BASE, '#0', 'http://a/b/c/d;p?q#0'], + // absolute path base URI + ['/a/b/', '', '/a/b/'], + ['/a/b', '', '/a/b'], + ['/', 'a', '/a'], + ['/', 'a/b', '/a/b'], + ['/a/b', 'g', '/a/g'], + ['/a/b/c', './', '/a/b/'], + ['/a/b/', '../', '/a/'], + ['/a/b/c', '../', '/a/'], + ['/a/b/', '../../x/y/z/', '/x/y/z/'], + ['/a/b/c/d/e', '../../../c/d', '/a/c/d'], + ['/a/b/c//', '../', '/a/b/c/'], + ['/a/b/c/', './/', '/a/b/c//'], + ['/a/b/c', '../../../../a', '/a'], + ['/a/b/c', '../../../..', '/'], + // not actually a dot-segment + ['/a/b/c', '..a/b..', '/a/b/..a/b..'], + // '' cannot be used as relative reference as it would inherit the base query component + ['/a/b?q', 'b', '/a/b'], + ['/a/b/?q', './', '/a/b/'], + // path with colon: "with:colon" would be the wrong relative reference + ['/a/', './with:colon', '/a/with:colon'], + ['/a/', 'b/with:colon', '/a/b/with:colon'], + ['/a/', './:b/', '/a/:b/'], + // relative path references + ['a', 'a/b', 'a/b'], + ['', '', ''], + ['', '..', ''], + ['/', '..', '/'], + ['urn:a/b', '..//a/b', 'urn:/a/b'], + // network path references + // empty base path and relative-path reference + ['//example.com', 'a', '//example.com/a'], + // path starting with two slashes + ['//example.com//two-slashes', './', '//example.com//'], + ['//example.com', './/', '//example.com//'], + ['//example.com/', './/', '//example.com//'], + // base URI has less components than relative URI + ['/', '//a/b?q#h', '//a/b?q#h'], + ['/', 'urn:/', 'urn:/'], + ]; + } + + /** + * Some additional tests to getResolveTestCases() that only make sense for relativize. + */ + public function getRelativizeTestCases() + { + return [ + // targets that are relative-path references are returned as-is + ['a/b', 'b/c', 'b/c'], + ['a/b/c', '../b/c', '../b/c'], + ['a', '', ''], + ['a', './', './'], + ['a', 'a/..', 'a/..'], + ['/a/b/?q', '?q#h', '?q#h'], + ['/a/b/?q', '#h', '#h'], + ['/a/b/?q', 'c#h', 'c#h'], + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + ['/a/b/?q', '/a/b/#h', './#h'], + ['/', '/#h', '#h'], + ['/', '/', ''], + ['http://a', 'http://a/', './'], + ['urn:a/b?q', 'urn:x/y?q', '../x/y?q'], + ['urn:', 'urn:/', './/'], + ['urn:a/b?q', 'urn:', '../'], + // target URI has less components than base URI + ['http://a/b/', '//a/b/c', 'c'], + ['http://a/b/', '/b/c', 'c'], + ['http://a/b/', '/x/y', '../x/y'], + ['http://a/b/', '/', '../'], + // absolute target URI without authority but base URI has one + ['urn://a/b/', 'urn:/b/', 'urn:/b/'], + ]; + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/UriTest.php php-guzzlehttp-psr7-1.7.0/tests/UriTest.php --- php-guzzlehttp-psr7-1.4.2/tests/UriTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/UriTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,690 @@ +assertSame('https', $uri->getScheme()); + $this->assertSame('user:pass@example.com:8080', $uri->getAuthority()); + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame(8080, $uri->getPort()); + $this->assertSame('/path/123', $uri->getPath()); + $this->assertSame('q=abc', $uri->getQuery()); + $this->assertSame('test', $uri->getFragment()); + $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } + + public function testCanTransformAndRetrievePartsIndividually() + { + $uri = (new Uri()) + ->withScheme('https') + ->withUserInfo('user', 'pass') + ->withHost('example.com') + ->withPort(8080) + ->withPath('/path/123') + ->withQuery('q=abc') + ->withFragment('test'); + + $this->assertSame('https', $uri->getScheme()); + $this->assertSame('user:pass@example.com:8080', $uri->getAuthority()); + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame(8080, $uri->getPort()); + $this->assertSame('/path/123', $uri->getPath()); + $this->assertSame('q=abc', $uri->getQuery()); + $this->assertSame('test', $uri->getFragment()); + $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testValidUrisStayValid($input) + { + $uri = new Uri($input); + + $this->assertSame($input, (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testFromParts($input) + { + $uri = Uri::fromParts(parse_url($input)); + + $this->assertSame($input, (string) $uri); + } + + public function getValidUris() + { + return [ + ['urn:path-rootless'], + ['urn:path:with:colon'], + ['urn:/path-absolute'], + ['urn:/'], + // only scheme with empty path + ['urn:'], + // only path + ['/'], + ['relative/'], + ['0'], + // same document reference + [''], + // network path without scheme + ['//example.org'], + ['//example.org/'], + ['//example.org?q#h'], + // only query + ['?q'], + ['?q=abc&foo=bar'], + // only fragment + ['#fragment'], + // dot segments are not removed automatically + ['./foo/../bar'], + ]; + } + + /** + * @dataProvider getInvalidUris + */ + public function testInvalidUrisThrowException($invalidUri) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Unable to parse URI'); + + new Uri($invalidUri); + } + + public function getInvalidUris() + { + return [ + // parse_url() requires the host component which makes sense for http(s) + // but not when the scheme is not known or different. So '//' or '///' is + // currently invalid as well but should not according to RFC 3986. + ['http://'], + ['urn://host:with:colon'], // host cannot contain ":" + ]; + } + + public function testPortMustBeValid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Must be between 0 and 65535'); + + (new Uri())->withPort(100000); + } + + public function testWithPortCannotBeNegative() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid port: -1. Must be between 0 and 65535'); + + (new Uri())->withPort(-1); + } + + public function testParseUriPortCannotBeZero() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Unable to parse URI'); + + new Uri('//example.com:0'); + } + + public function testSchemeMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withScheme([]); + } + + public function testHostMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withHost([]); + } + + public function testPathMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withPath([]); + } + + public function testQueryMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withQuery([]); + } + + public function testFragmentMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withFragment([]); + } + + public function testCanParseFalseyUriParts() + { + $uri = new Uri('0://0:0@0/0?0#0'); + + $this->assertSame('0', $uri->getScheme()); + $this->assertSame('0:0@0', $uri->getAuthority()); + $this->assertSame('0:0', $uri->getUserInfo()); + $this->assertSame('0', $uri->getHost()); + $this->assertSame('/0', $uri->getPath()); + $this->assertSame('0', $uri->getQuery()); + $this->assertSame('0', $uri->getFragment()); + $this->assertSame('0://0:0@0/0?0#0', (string) $uri); + } + + public function testCanConstructFalseyUriParts() + { + $uri = (new Uri()) + ->withScheme('0') + ->withUserInfo('0', '0') + ->withHost('0') + ->withPath('/0') + ->withQuery('0') + ->withFragment('0'); + + $this->assertSame('0', $uri->getScheme()); + $this->assertSame('0:0@0', $uri->getAuthority()); + $this->assertSame('0:0', $uri->getUserInfo()); + $this->assertSame('0', $uri->getHost()); + $this->assertSame('/0', $uri->getPath()); + $this->assertSame('0', $uri->getQuery()); + $this->assertSame('0', $uri->getFragment()); + $this->assertSame('0://0:0@0/0?0#0', (string) $uri); + } + + /** + * @dataProvider getPortTestCases + */ + public function testIsDefaultPort($scheme, $port, $isDefaultPort) + { + $uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock(); + $uri->expects($this->any())->method('getScheme')->will($this->returnValue($scheme)); + $uri->expects($this->any())->method('getPort')->will($this->returnValue($port)); + + $this->assertSame($isDefaultPort, Uri::isDefaultPort($uri)); + } + + public function getPortTestCases() + { + return [ + ['http', null, true], + ['http', 80, true], + ['http', 8080, false], + ['https', null, true], + ['https', 443, true], + ['https', 444, false], + ['ftp', 21, true], + ['gopher', 70, true], + ['nntp', 119, true], + ['news', 119, true], + ['telnet', 23, true], + ['tn3270', 23, true], + ['imap', 143, true], + ['pop', 110, true], + ['ldap', 389, true], + ]; + } + + public function testIsAbsolute() + { + $this->assertTrue(Uri::isAbsolute(new Uri('http://example.org'))); + $this->assertFalse(Uri::isAbsolute(new Uri('//example.org'))); + $this->assertFalse(Uri::isAbsolute(new Uri('/abs-path'))); + $this->assertFalse(Uri::isAbsolute(new Uri('rel-path'))); + } + + public function testIsNetworkPathReference() + { + $this->assertFalse(Uri::isNetworkPathReference(new Uri('http://example.org'))); + $this->assertTrue(Uri::isNetworkPathReference(new Uri('//example.org'))); + $this->assertFalse(Uri::isNetworkPathReference(new Uri('/abs-path'))); + $this->assertFalse(Uri::isNetworkPathReference(new Uri('rel-path'))); + } + + public function testIsAbsolutePathReference() + { + $this->assertFalse(Uri::isAbsolutePathReference(new Uri('http://example.org'))); + $this->assertFalse(Uri::isAbsolutePathReference(new Uri('//example.org'))); + $this->assertTrue(Uri::isAbsolutePathReference(new Uri('/abs-path'))); + $this->assertTrue(Uri::isAbsolutePathReference(new Uri('/'))); + $this->assertFalse(Uri::isAbsolutePathReference(new Uri('rel-path'))); + } + + public function testIsRelativePathReference() + { + $this->assertFalse(Uri::isRelativePathReference(new Uri('http://example.org'))); + $this->assertFalse(Uri::isRelativePathReference(new Uri('//example.org'))); + $this->assertFalse(Uri::isRelativePathReference(new Uri('/abs-path'))); + $this->assertTrue(Uri::isRelativePathReference(new Uri('rel-path'))); + $this->assertTrue(Uri::isRelativePathReference(new Uri(''))); + } + + public function testIsSameDocumentReference() + { + $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org'))); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('//example.org'))); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('/abs-path'))); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('rel-path'))); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('?query'))); + $this->assertTrue(Uri::isSameDocumentReference(new Uri(''))); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('#fragment'))); + + $baseUri = new Uri('http://example.org/path?foo=bar'); + + $this->assertTrue(Uri::isSameDocumentReference(new Uri('#fragment'), $baseUri)); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('?foo=bar#fragment'), $baseUri)); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('/path?foo=bar#fragment'), $baseUri)); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('path?foo=bar#fragment'), $baseUri)); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('//example.org/path?foo=bar#fragment'), $baseUri)); + $this->assertTrue(Uri::isSameDocumentReference(new Uri('http://example.org/path?foo=bar#fragment'), $baseUri)); + + $this->assertFalse(Uri::isSameDocumentReference(new Uri('https://example.org/path?foo=bar'), $baseUri)); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.com/path?foo=bar'), $baseUri)); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org/'), $baseUri)); + $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org'), $baseUri)); + + $this->assertFalse(Uri::isSameDocumentReference(new Uri('urn:/path'), new Uri('urn://example.com/path'))); + } + + public function testAddAndRemoveQueryValues() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withQueryValue($uri, 'c', 'd'); + $uri = Uri::withQueryValue($uri, 'e', null); + $this->assertSame('a=b&c=d&e', $uri->getQuery()); + + $uri = Uri::withoutQueryValue($uri, 'c'); + $this->assertSame('a=b&e', $uri->getQuery()); + $uri = Uri::withoutQueryValue($uri, 'e'); + $this->assertSame('a=b', $uri->getQuery()); + $uri = Uri::withoutQueryValue($uri, 'a'); + $this->assertSame('', $uri->getQuery()); + } + + public function testNumericQueryValue() + { + $uri = Uri::withQueryValue(new Uri(), 'version', 1); + $this->assertSame('version=1', $uri->getQuery()); + } + + public function testWithQueryValues() + { + $uri = new Uri(); + $uri = Uri::withQueryValues($uri, [ + 'key1' => 'value1', + 'key2' => 'value2' + ]); + + $this->assertSame('key1=value1&key2=value2', $uri->getQuery()); + } + + public function testWithQueryValuesReplacesSameKeys() + { + $uri = new Uri(); + + $uri = Uri::withQueryValues($uri, [ + 'key1' => 'value1', + 'key2' => 'value2' + ]); + + $uri = Uri::withQueryValues($uri, [ + 'key2' => 'newvalue' + ]); + + $this->assertSame('key1=value1&key2=newvalue', $uri->getQuery()); + } + + public function testWithQueryValueReplacesSameKeys() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withQueryValue($uri, 'c', 'd'); + $uri = Uri::withQueryValue($uri, 'a', 'e'); + $this->assertSame('c=d&a=e', $uri->getQuery()); + } + + public function testWithoutQueryValueRemovesAllSameKeys() + { + $uri = (new Uri())->withQuery('a=b&c=d&a=e'); + $uri = Uri::withoutQueryValue($uri, 'a'); + $this->assertSame('c=d', $uri->getQuery()); + } + + public function testRemoveNonExistingQueryValue() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withoutQueryValue($uri, 'c'); + $this->assertSame('a=b', $uri->getQuery()); + } + + public function testWithQueryValueHandlesEncoding() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein'); + $this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded'); + + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein'); + $this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded'); + } + + public function testWithoutQueryValueHandlesEncoding() + { + // It also tests that the case of the percent-encoding does not matter, + // i.e. both lowercase "%3d" and uppercase "%5E" can be removed. + $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar'); + $uri = Uri::withoutQueryValue($uri, 'E=mc^2'); + $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form'); + + $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar'); + $uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2'); + $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form'); + } + + public function testSchemeIsNormalizedToLowercase() + { + $uri = new Uri('HTTP://example.com'); + + $this->assertSame('http', $uri->getScheme()); + $this->assertSame('http://example.com', (string) $uri); + + $uri = (new Uri('//example.com'))->withScheme('HTTP'); + + $this->assertSame('http', $uri->getScheme()); + $this->assertSame('http://example.com', (string) $uri); + } + + public function testHostIsNormalizedToLowercase() + { + $uri = new Uri('//eXaMpLe.CoM'); + + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame('//example.com', (string) $uri); + + $uri = (new Uri())->withHost('eXaMpLe.CoM'); + + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame('//example.com', (string) $uri); + } + + public function testPortIsNullIfStandardPortForScheme() + { + // HTTPS standard port + $uri = new Uri('https://example.com:443'); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('https://example.com'))->withPort(443); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + // HTTP standard port + $uri = new Uri('http://example.com:80'); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('http://example.com'))->withPort(80); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + } + + public function testPortIsReturnedIfSchemeUnknown() + { + $uri = (new Uri('//example.com'))->withPort(80); + + $this->assertSame(80, $uri->getPort()); + $this->assertSame('example.com:80', $uri->getAuthority()); + } + + public function testStandardPortIsNullIfSchemeChanges() + { + $uri = new Uri('http://example.com:443'); + $this->assertSame('http', $uri->getScheme()); + $this->assertSame(443, $uri->getPort()); + + $uri = $uri->withScheme('https'); + $this->assertNull($uri->getPort()); + } + + public function testPortPassedAsStringIsCastedToInt() + { + $uri = (new Uri('//example.com'))->withPort('8080'); + + $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer'); + $this->assertSame('example.com:8080', $uri->getAuthority()); + } + + public function testPortCanBeRemoved() + { + $uri = (new Uri('http://example.com:8080'))->withPort(null); + + $this->assertNull($uri->getPort()); + $this->assertSame('http://example.com', (string) $uri); + } + + /** + * In RFC 8986 the host is optional and the authority can only + * consist of the user info and port. + */ + public function testAuthorityWithUserInfoOrPortButWithoutHost() + { + $uri = (new Uri())->withUserInfo('user', 'pass'); + + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('user:pass@', $uri->getAuthority()); + + $uri = $uri->withPort(8080); + $this->assertSame(8080, $uri->getPort()); + $this->assertSame('user:pass@:8080', $uri->getAuthority()); + $this->assertSame('//user:pass@:8080', (string) $uri); + + $uri = $uri->withUserInfo(''); + $this->assertSame(':8080', $uri->getAuthority()); + } + + public function testHostInHttpUriDefaultsToLocalhost() + { + $uri = (new Uri())->withScheme('http'); + + $this->assertSame('localhost', $uri->getHost()); + $this->assertSame('localhost', $uri->getAuthority()); + $this->assertSame('http://localhost', (string) $uri); + } + + public function testHostInHttpsUriDefaultsToLocalhost() + { + $uri = (new Uri())->withScheme('https'); + + $this->assertSame('localhost', $uri->getHost()); + $this->assertSame('localhost', $uri->getAuthority()); + $this->assertSame('https://localhost', (string) $uri); + } + + public function testFileSchemeWithEmptyHostReconstruction() + { + $uri = new Uri('file:///tmp/filename.ext'); + + $this->assertSame('', $uri->getHost()); + $this->assertSame('', $uri->getAuthority()); + $this->assertSame('file:///tmp/filename.ext', (string) $uri); + } + + public function uriComponentsEncodingProvider() + { + $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@'; + + return [ + // Percent encode spaces + ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], + // Percent encode multibyte + ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'], + // Don't encode something that's already encoded + ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], + // Percent encode invalid percent encodings + ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'], + // Don't encode path segments + ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'], + // Don't encode unreserved chars or sub-delimiters + ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"], + // Encoded unreserved chars are not decoded + ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'], + ]; + } + + /** + * @dataProvider uriComponentsEncodingProvider + */ + public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output) + { + $uri = new Uri($input); + $this->assertSame($path, $uri->getPath()); + $this->assertSame($query, $uri->getQuery()); + $this->assertSame($fragment, $uri->getFragment()); + $this->assertSame($output, (string) $uri); + } + + public function testWithPathEncodesProperly() + { + $uri = (new Uri())->withPath('/baz?#€/b%61r'); + // Query and fragment delimiters and multibyte chars are encoded. + $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath()); + $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri); + } + + public function testWithQueryEncodesProperly() + { + $uri = (new Uri())->withQuery('?=#&€=/&b%61r'); + // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to + // construct such an URI. Also the "?" and "/" does not need to be encoded in the query. + $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery()); + $this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri); + } + + public function testWithFragmentEncodesProperly() + { + $uri = (new Uri())->withFragment('#€?/b%61r'); + // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to + // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment. + $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment()); + $this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri); + } + + public function testAllowsForRelativeUri() + { + $uri = (new Uri)->withPath('foo'); + $this->assertSame('foo', $uri->getPath()); + $this->assertSame('foo', (string) $uri); + } + + public function testRelativePathAndAuhorityIsAutomagicallyFixed() + { + // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong + $uri = (new Uri)->withPath('foo')->withHost('example.com'); + $this->assertSame('/foo', $uri->getPath()); + $this->assertSame('//example.com/foo', (string) $uri); + } + + public function testPathStartingWithTwoSlashesAndNoAuthorityIsInvalid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'The path of a URI without an authority must not start with two slashes "//"'); + + // URI "//foo" would be interpreted as network reference and thus change the original path to the host + (new Uri)->withPath('//foo'); + } + + public function testPathStartingWithTwoSlashes() + { + $uri = new Uri('http://example.org//path-not-host.com'); + $this->assertSame('//path-not-host.com', $uri->getPath()); + + $uri = $uri->withScheme(''); + $this->assertSame('//example.org//path-not-host.com', (string) $uri); // This is still valid + $this->expectExceptionGuzzle('\InvalidArgumentException'); + $uri->withHost(''); // Now it becomes invalid + } + + public function testRelativeUriWithPathBeginngWithColonSegmentIsInvalid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'A relative URI must not have a path beginning with a segment containing a colon'); + + (new Uri)->withPath('mailto:foo'); + } + + public function testRelativeUriWithPathHavingColonSegment() + { + $uri = (new Uri('urn:/mailto:foo'))->withScheme(''); + $this->assertSame('/mailto:foo', $uri->getPath()); + + $this->expectExceptionGuzzle('\InvalidArgumentException'); + (new Uri('urn:mailto:foo'))->withScheme(''); + } + + public function testDefaultReturnValuesOfGetters() + { + $uri = new Uri(); + + $this->assertSame('', $uri->getScheme()); + $this->assertSame('', $uri->getAuthority()); + $this->assertSame('', $uri->getUserInfo()); + $this->assertSame('', $uri->getHost()); + $this->assertNull($uri->getPort()); + $this->assertSame('', $uri->getPath()); + $this->assertSame('', $uri->getQuery()); + $this->assertSame('', $uri->getFragment()); + } + + public function testImmutability() + { + $uri = new Uri(); + + $this->assertNotSame($uri, $uri->withScheme('https')); + $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass')); + $this->assertNotSame($uri, $uri->withHost('example.com')); + $this->assertNotSame($uri, $uri->withPort(8080)); + $this->assertNotSame($uri, $uri->withPath('/path/123')); + $this->assertNotSame($uri, $uri->withQuery('q=abc')); + $this->assertNotSame($uri, $uri->withFragment('test')); + } + + public function testExtendingClassesInstantiates() + { + // The non-standard port triggers a cascade of private methods which + // should not use late static binding to access private static members. + // If they do, this will fatal. + $this->assertInstanceOf( + 'GuzzleHttp\Tests\Psr7\ExtendedUriTest', + new ExtendedUriTest('http://h:9/') + ); + } + + public function testSpecialCharsOfUserInfo() + { + // The `userInfo` must always be URL-encoded. + $uri = (new Uri)->withUserInfo('foo@bar.com', 'pass#word'); + $this->assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo()); + + // The `userInfo` can already be URL-encoded: it should not be encoded twice. + $uri = (new Uri)->withUserInfo('foo%40bar.com', 'pass%23word'); + $this->assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo()); + } +} + +class ExtendedUriTest extends Uri +{ +} diff -Nru php-guzzlehttp-psr7-1.4.2/tests/UtilsTest.php php-guzzlehttp-psr7-1.7.0/tests/UtilsTest.php --- php-guzzlehttp-psr7-1.4.2/tests/UtilsTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/tests/UtilsTest.php 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,451 @@ +assertSame('foobaz', Psr7\Utils::copyToString($s)); + $s->seek(0); + $this->assertSame('foo', Psr7\Utils::copyToString($s, 3)); + $this->assertSame('baz', Psr7\Utils::copyToString($s, 3)); + $this->assertSame('', Psr7\Utils::copyToString($s)); + } + + public function testCopiesToStringStopsWhenReadFails() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s1 = FnStream::decorate($s1, [ + 'read' => function () { + return ''; + }, + ]); + $result = Psr7\Utils::copyToString($s1); + $this->assertSame('', $result); + } + + public function testCopiesToStream() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2); + $this->assertSame('foobaz', (string)$s2); + $s2 = Psr7\Utils::streamFor(''); + $s1->seek(0); + Psr7\Utils::copyToStream($s1, $s2, 3); + $this->assertSame('foo', (string)$s2); + Psr7\Utils::copyToStream($s1, $s2, 3); + $this->assertSame('foobaz', (string)$s2); + } + + public function testStopsCopyToStreamWhenWriteFails() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + $s2 = FnStream::decorate($s2, [ + 'write' => function () { + return 0; + }, + ]); + Psr7\Utils::copyToStream($s1, $s2); + $this->assertSame('', (string)$s2); + } + + public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + $s2 = FnStream::decorate($s2, [ + 'write' => function () { + return 0; + }, + ]); + Psr7\Utils::copyToStream($s1, $s2, 10); + $this->assertSame('', (string)$s2); + } + + public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() + { + $sizes = []; + $s1 = new Psr7\FnStream([ + 'eof' => function () { + return false; + }, + 'read' => function ($size) use (&$sizes) { + $sizes[] = $size; + return str_repeat('.', $size); + }, + ]); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2, 16394); + $s2->seek(0); + $this->assertSame(16394, strlen($s2->getContents())); + $this->assertSame(8192, $sizes[0]); + $this->assertSame(8192, $sizes[1]); + $this->assertSame(10, $sizes[2]); + } + + public function testStopsCopyToSteamWhenReadFailsWithMaxLen() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s1 = FnStream::decorate($s1, [ + 'read' => function () { + return ''; + }, + ]); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2, 10); + $this->assertSame('', (string)$s2); + } + + public function testReadsLines() + { + $s = Psr7\Utils::streamFor("foo\nbaz\nbar"); + $this->assertSame("foo\n", Psr7\Utils::readLine($s)); + $this->assertSame("baz\n", Psr7\Utils::readLine($s)); + $this->assertSame('bar', Psr7\Utils::readLine($s)); + } + + public function testReadsLinesUpToMaxLength() + { + $s = Psr7\Utils::streamFor("12345\n"); + $this->assertSame('123', Psr7\Utils::readLine($s, 4)); + $this->assertSame("45\n", Psr7\Utils::readLine($s)); + } + + public function testReadLinesEof() + { + // Should return empty string on EOF + $s = Psr7\Utils::streamFor("foo\nbar"); + while (!$s->eof()) { + Psr7\Utils::readLine($s); + } + $this->assertSame('', Psr7\Utils::readLine($s)); + } + + public function testReadsLineUntilFalseReturnedFromRead() + { + $s = $this->getMockBuilder('GuzzleHttp\Psr7\Stream') + ->setMethods(['read', 'eof']) + ->disableOriginalConstructor() + ->getMock(); + $s->expects($this->exactly(2)) + ->method('read') + ->will($this->returnCallback(function () { + static $c = false; + if ($c) { + return false; + } + $c = true; + return 'h'; + })); + $s->expects($this->exactly(2)) + ->method('eof') + ->will($this->returnValue(false)); + $this->assertSame('h', Psr7\Utils::readLine($s)); + } + + public function testCalculatesHash() + { + $s = Psr7\Utils::streamFor('foobazbar'); + $this->assertSame(md5('foobazbar'), Psr7\Utils::hash($s, 'md5')); + } + + public function testCalculatesHashThrowsWhenSeekFails() + { + $s = new NoSeekStream(Psr7\Utils::streamFor('foobazbar')); + $s->read(2); + + $this->expectExceptionGuzzle('RuntimeException'); + + Psr7\Utils::hash($s, 'md5'); + } + + public function testCalculatesHashSeeksToOriginalPosition() + { + $s = Psr7\Utils::streamFor('foobazbar'); + $s->seek(4); + $this->assertSame(md5('foobazbar'), Psr7\Utils::hash($s, 'md5')); + $this->assertSame(4, $s->tell()); + } + + public function testOpensFilesSuccessfully() + { + $r = Psr7\Utils::tryFopen(__FILE__, 'r'); + $this->assertInternalTypeGuzzle('resource', $r); + fclose($r); + } + + public function testThrowsExceptionNotWarning() + { + $this->expectExceptionGuzzle('RuntimeException', 'Unable to open /path/to/does/not/exist using mode r'); + + Psr7\Utils::tryFopen('/path/to/does/not/exist', 'r'); + } + + public function testCreatesUriForValue() + { + $this->assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\Utils::uriFor('/foo')); + $this->assertInstanceOf( + 'GuzzleHttp\Psr7\Uri', + Psr7\Utils::uriFor(new Psr7\Uri('/foo')) + ); + } + + public function testValidatesUri() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Utils::uriFor([]); + } + + public function testKeepsPositionOfResource() + { + $h = fopen(__FILE__, 'r'); + fseek($h, 10); + $stream = Psr7\Utils::streamFor($h); + $this->assertSame(10, $stream->tell()); + $stream->close(); + } + + public function testCreatesWithFactory() + { + $stream = Psr7\Utils::streamFor('foo'); + $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream); + $this->assertSame('foo', $stream->getContents()); + $stream->close(); + } + + public function testFactoryCreatesFromEmptyString() + { + $s = Psr7\Utils::streamFor(); + $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + } + + public function testFactoryCreatesFromNull() + { + $s = Psr7\Utils::streamFor(null); + $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + } + + public function testFactoryCreatesFromResource() + { + $r = fopen(__FILE__, 'r'); + $s = Psr7\Utils::streamFor($r); + $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + $this->assertSame(file_get_contents(__FILE__), (string)$s); + } + + public function testFactoryCreatesFromObjectWithToString() + { + $r = new HasToString(); + $s = Psr7\Utils::streamFor($r); + $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + $this->assertSame('foo', (string)$s); + } + + public function testCreatePassesThrough() + { + $s = Psr7\Utils::streamFor('foo'); + $this->assertSame($s, Psr7\Utils::streamFor($s)); + } + + public function testThrowsExceptionForUnknown() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Utils::streamFor(new \stdClass()); + } + + public function testReturnsCustomMetadata() + { + $s = Psr7\Utils::streamFor('foo', ['metadata' => ['hwm' => 3]]); + $this->assertSame(3, $s->getMetadata('hwm')); + $this->assertArrayHasKey('hwm', $s->getMetadata()); + } + + public function testCanSetSize() + { + $s = Psr7\Utils::streamFor('', ['size' => 10]); + $this->assertSame(10, $s->getSize()); + } + + public function testCanCreateIteratorBasedStream() + { + $a = new \ArrayIterator(['foo', 'bar', '123']); + $p = Psr7\Utils::streamFor($a); + $this->assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p); + $this->assertSame('foo', $p->read(3)); + $this->assertFalse($p->eof()); + $this->assertSame('b', $p->read(1)); + $this->assertSame('a', $p->read(1)); + $this->assertSame('r12', $p->read(3)); + $this->assertFalse($p->eof()); + $this->assertSame('3', $p->getContents()); + $this->assertTrue($p->eof()); + $this->assertSame(9, $p->tell()); + } + + public function testConvertsRequestsToStrings() + { + $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0'); + $this->assertSame( + "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($request) + ); + } + + public function testConvertsResponsesToStrings() + { + $response = new Psr7\Response(200, [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0', 'FOO'); + $this->assertSame( + "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCorrectlyRendersSetCookieHeadersToString() + { + $response = new Psr7\Response(200, [ + 'Set-Cookie' => ['bar','baz','qux'] + ], 'hello', '1.0', 'FOO'); + $this->assertSame( + "HTTP/1.0 200 FOO\r\nSet-Cookie: bar\r\nSet-Cookie: baz\r\nSet-Cookie: qux\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCanModifyRequestWithUri() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, [ + 'uri' => new Psr7\Uri('http://www.foo.com'), + ]); + $this->assertSame('http://www.foo.com', (string)$r2->getUri()); + $this->assertSame('www.foo.com', (string)$r2->getHeaderLine('host')); + } + + public function testCanModifyRequestWithUriAndPort() + { + $r1 = new Psr7\Request('GET', 'http://foo.com:8000'); + $r2 = Psr7\Utils::modifyRequest($r1, [ + 'uri' => new Psr7\Uri('http://www.foo.com:8000'), + ]); + $this->assertSame('http://www.foo.com:8000', (string)$r2->getUri()); + $this->assertSame('www.foo.com:8000', (string)$r2->getHeaderLine('host')); + } + + public function testCanModifyRequestWithCaseInsensitiveHeader() + { + $r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']); + $r2 = Psr7\Utils::modifyRequest($r1, ['set_headers' => ['User-agent' => 'bar']]); + $this->assertSame('bar', $r2->getHeaderLine('User-Agent')); + $this->assertSame('bar', $r2->getHeaderLine('User-agent')); + } + + public function testReturnsAsIsWhenNoChanges() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, []); + $this->assertInstanceOf('GuzzleHttp\Psr7\Request', $r2); + + $r1 = new Psr7\ServerRequest('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, []); + $this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $r2); + } + + public function testReturnsUriAsIsWhenNoChanges() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['set_headers' => ['foo' => 'bar']]); + $this->assertNotSame($r1, $r2); + $this->assertSame('bar', $r2->getHeaderLine('foo')); + } + + public function testRemovesHeadersFromMessage() + { + $r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['foo']]); + $this->assertNotSame($r1, $r2); + $this->assertFalse($r2->hasHeader('foo')); + } + + public function testAddsQueryToUri() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['query' => 'foo=bar']); + $this->assertNotSame($r1, $r2); + $this->assertSame('foo=bar', $r2->getUri()->getQuery()); + } + + public function testModifyRequestKeepInstanceOfRequest() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['non-existent']]); + $this->assertInstanceOf('GuzzleHttp\Psr7\Request', $r2); + + $r1 = new Psr7\ServerRequest('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['non-existent']]); + $this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $r2); + } + + public function testModifyServerRequestWithUploadedFiles() + { + $request = new Psr7\ServerRequest('GET', 'http://example.com/bla'); + $file = new Psr7\UploadedFile('Test', 100, \UPLOAD_ERR_OK); + $request = $request->withUploadedFiles([$file]); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + $this->assertCount(1, $modifiedRequest->getUploadedFiles()); + + $files = $modifiedRequest->getUploadedFiles(); + $this->assertInstanceOf('GuzzleHttp\Psr7\UploadedFile', $files[0]); + } + + public function testModifyServerRequestWithCookies() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withCookieParams(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + $this->assertSame(['name' => 'value'], $modifiedRequest->getCookieParams()); + } + + public function testModifyServerRequestParsedBody() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withParsedBody(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + $this->assertSame(['name' => 'value'], $modifiedRequest->getParsedBody()); + } + + public function testModifyServerRequestQueryParams() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withQueryParams(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + $this->assertSame(['name' => 'value'], $modifiedRequest->getQueryParams()); + } +} diff -Nru php-guzzlehttp-psr7-1.4.2/.travis.yml php-guzzlehttp-psr7-1.7.0/.travis.yml --- php-guzzlehttp-psr7-1.4.2/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ php-guzzlehttp-psr7-1.7.0/.travis.yml 2021-01-08 03:18:18.000000000 +0000 @@ -0,0 +1,42 @@ +language: php + +matrix: + include: + - php: hhvm-3.24 + dist: trusty + - php: 5.4 + dist: trusty + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" + - php: 5.4 + dist: trusty + - php: 5.5.9 + dist: trusty + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" + - php: 5.5 + dist: trusty + - php: 5.6 + dist: xenial + - php: 7.0 + dist: xenial + - php: 7.1 + dist: bionic + - php: 7.2 + dist: bionic + - php: 7.3 + dist: bionic + - php: 7.4 + dist: bionic + - php: nightly + dist: bionic + fast_finish: true + +before_install: + - if [[ "$TRAVIS_PHP_VERSION" != "hhvm-3.24" ]]; then echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi + - if [[ "$TRAVIS_PHP_VERSION" == "hhvm-3.24" ]]; then travis_retry composer require "phpunit/phpunit:^5.7.27" --dev --no-update -n; fi + +install: + - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]]; then travis_retry composer update --prefer-dist; fi + - if [[ "$TRAVIS_PHP_VERSION" == "nightly" ]]; then travis_retry composer update --prefer-dist --ignore-platform-reqs; fi + +script: + - make test