Merge pull request #321 from nextcloud/update-test-deps

Update test deps and integration test fixes
This commit is contained in:
Roeland Jago Douma 2019-04-12 14:02:37 +02:00 committed by GitHub
commit 054a20a0cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 3131 additions and 5507 deletions

View file

@ -70,17 +70,18 @@ steps:
- cd tests/unit/
- phpunit --configuration phpunit.xml
- name: integration-tests-master
image: nextcloudci/user_saml_shibboleth-php7.2:user_saml_shibboleth_php7.2-1
image: nextcloudci/user_saml_shibboleth-php7.2:user_saml_shibboleth_php7.2-2
environment:
CORE_BRANCH: master
commands:
- sed -i 's_/etc/init.d/jetty run \&_sleep 4 \&\& /etc/init.d/jetty run \&_' /start.sh
- /start.sh &
- sleep 3
- sleep 7
- rm -rf /var/www/html
- cd /var/www/
- git clone --depth 1 -b $CORE_BRANCH https://github.com/nextcloud/server html
- cd /var/www/html && git submodule update --init
- cd /var/www/html/apps/ && git clone -b $DRONE_BRANCH https://github.com/nextcloud/user_saml.git
- cd /var/www/html/apps/ && git clone -b $DRONE_SOURCE_BRANCH https://github.com/nextcloud/user_saml.git
- scl enable rh-php72 "bash -c 'php /var/www/html/occ maintenance:install --database sqlite --admin-pass password; php /var/www/html/occ app:enable user_saml'"
- chown -R apache:apache /var/www/html/
- scl enable rh-php72 "bash -c 'cd /var/www/html/apps/user_saml/tests/integration && vendor/bin/behat'"
@ -123,7 +124,7 @@ steps:
- cd /var/www/
- git clone --depth 1 -b $CORE_BRANCH https://github.com/nextcloud/server html
- cd /var/www/html && git submodule update --init
- cd /var/www/html/apps/ && git clone -b $DRONE_BRANCH https://github.com/nextcloud/user_saml.git
- cd /var/www/html/apps/ && git clone -b $DRONE_SOURCE_BRANCH https://github.com/nextcloud/user_saml.git
- scl enable rh-php70 "bash -c 'php /var/www/html/occ maintenance:install --database sqlite --admin-pass password; php /var/www/html/occ app:enable user_saml'"
- chown -R apache:apache /var/www/html/
- scl enable rh-php70 "bash -c 'cd /var/www/html/apps/user_saml/tests/integration && vendor/bin/behat'"
@ -166,7 +167,7 @@ steps:
- cd /var/www/
- git clone --depth 1 -b $CORE_BRANCH https://github.com/nextcloud/server html
- cd /var/www/html && git submodule update --init
- cd /var/www/html/apps/ && git clone -b $DRONE_BRANCH https://github.com/nextcloud/user_saml.git
- cd /var/www/html/apps/ && git clone -b $DRONE_SOURCE_BRANCH https://github.com/nextcloud/user_saml.git
- scl enable rh-php70 "bash -c 'php /var/www/html/occ maintenance:install --database sqlite --admin-pass password; php /var/www/html/occ app:enable user_saml'"
- chown -R apache:apache /var/www/html/
- scl enable rh-php70 "bash -c 'cd /var/www/html/apps/user_saml/tests/integration && vendor/bin/behat'"

View file

@ -1,6 +1,6 @@
{
"require-dev": {
"behat/behat": "^3.3",
"guzzlehttp/guzzle": "^6.2"
"guzzlehttp/guzzle": "^6.3"
}
}

View file

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "66da188fa9ccc00a1dd1beb21e3fab71",
"content-hash": "f0b4184dd0582f1a03bfc549bd3d1de0",
"packages": [],
"packages-dev": [
{
@ -217,32 +217,35 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.2.2",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60"
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
"reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.3.1",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
"dev-master": "6.3-dev"
}
},
"autoload": {
@ -275,7 +278,7 @@
"rest",
"web service"
],
"time": "2016-10-08T15:01:37+00:00"
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
@ -330,32 +333,33 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.3.1",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
"reference": "9f83dded91781a01c63574e387eaa769be769115"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
"reference": "9f83dded91781a01c63574e387eaa769be769115",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -375,16 +379,24 @@
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation",
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri"
"uri",
"url"
],
"time": "2016-06-24T23:00:38+00:00"
"time": "2018-12-04T20:46:45+00:00"
},
{
"name": "psr/http-message",
@ -483,6 +495,46 @@
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "~3.7.0",
"satooshi/php-coveralls": ">=1.0"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"time": "2016-02-11T07:05:27+00:00"
},
{
"name": "symfony/class-loader",
"version": "v3.2.1",

View file

@ -217,13 +217,12 @@ class FeatureContext implements Context {
$xml = simplexml_load_string($this->response->getBody());
/** @var array $responseArray */
$responseArray = json_decode(json_encode((array)$xml), true);
foreach($responseArray['data'] as $arrayKey => $arrayValue) {
if(count($responseArray['data'][$arrayKey]) === 0) {
$responseArray['data'][$arrayKey] = '';
}
}
if (count((array)$responseArray['data'][$key]) === 0) {
$responseArray['data'][$key] = '';
}
$actualValue = $responseArray['data'][$key];
if($actualValue !== $value) {
throw new UnexpectedValueException(
sprintf(
@ -242,7 +241,7 @@ class FeatureContext implements Context {
public function aLocalUserWithUidExists($uid) {
shell_exec(
sprintf(
'sudo -u apache OC_PASS=password%s %s user:add %s --display-name "Default displayname of '.$uid.'" --password-from-env',
'sudo -u apache OC_PASS=password %s %s user:add %s --display-name "Default displayname of '.$uid.'" --password-from-env',
PHP_BINARY,
__DIR__ . '/../../../../../../occ',
$uid

View file

@ -55,6 +55,7 @@ class ClassLoader
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
@ -271,6 +272,26 @@ class ClassLoader
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
@ -313,11 +334,6 @@ class ClassLoader
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
@ -325,6 +341,12 @@ class ClassLoader
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
@ -333,6 +355,10 @@ class ClassLoader
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
@ -348,10 +374,14 @@ class ClassLoader
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}

View file

@ -1,5 +1,5 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',

View file

@ -8,6 +8,7 @@ class ComposerStaticInit2b078a63e93bc9e9825cefae96ca1eb3
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
language: php
sudo: false
php:
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
before_script:
- curl --version
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
- ~/.nvm/nvm.sh run v0.6.14
- '[ "$TRAVIS_PHP_VERSION" != "7.0" ] || echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true
before_deploy:
- rvm 1.9.3 do gem install mime-types -v 2.6.2
- make package
deploy:
provider: releases
api_key:
secure: UpypqlYgsU68QT/x40YzhHXvzWjFwCNo9d+G8KAdm7U9+blFfcWhV1aMdzugvPMl6woXgvJj7qHq5tAL4v6oswCORhpSBfLgOQVFaica5LiHsvWlAedOhxGmnJqMTwuepjBCxXhs3+I8Kof1n4oUL9gKytXjOVCX/f7XU1HiinU=
file:
- build/artifacts/guzzle.phar
- build/artifacts/guzzle.zip
on:
repo: guzzle/guzzle
tags: true
all_branches: true
php: 5.5

View file

@ -1,4 +1,52 @@
# CHANGELOG
# Change Log
## 6.3.3 - 2018-04-22
* Fix: Default headers when decode_content is specified
## 6.3.2 - 2018-03-26
* Fix: Release process
## 6.3.1 - 2018-03-26
* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+ Minor code cleanups, documentation fixes and clarifications.
## 6.3.0 - 2017-06-22
* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+ Minor code cleanups, documentation fixes and clarifications.
## 6.2.3 - 2017-02-28
* Fix deprecations with guzzle/psr7 version 1.4
## 6.2.2 - 2016-10-08

View file

@ -1,4 +1,4 @@
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,7 +1,9 @@
Guzzle, PHP HTTP client
=======================
[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle)
[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
[![Build Status](https://img.shields.io/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle)
[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
trivial to integrate with web services.
@ -19,15 +21,13 @@ trivial to integrate with web services.
```php
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
echo $res->getStatusCode();
// 200
echo $res->getHeaderLine('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
// '{"id": 1420053, "name": "guzzle", ...}'
// Send an asynchronous request.
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
@ -40,7 +40,7 @@ $promise->wait();
## Help and docs
- [Documentation](http://guzzlephp.org/)
- [stackoverflow](http://stackoverflow.com/questions/tagged/guzzle)
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
- [Gitter](https://gitter.im/guzzle/guzzle)
@ -75,14 +75,15 @@ composer.phar update
## Version Guidance
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 |
|---------|-------------|---------------------|--------------|---------------------|---------------------|-------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | N/A | N/A | No |
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No |
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes |
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
[guzzle-6-repo]: https://github.com/guzzle/guzzle
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/

View file

@ -14,12 +14,12 @@
],
"require": {
"php": ">=5.5",
"guzzlehttp/psr7": "^1.3.1",
"guzzlehttp/psr7": "^1.4",
"guzzlehttp/promises": "^1.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"autoload": {
@ -33,9 +33,12 @@
"GuzzleHttp\\Tests\\": "tests/"
}
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
"dev-master": "6.3-dev"
}
}
}

View file

@ -63,6 +63,8 @@ class Client implements ClientInterface
{
if (!isset($config['handler'])) {
$config['handler'] = HandlerStack::create();
} elseif (!is_callable($config['handler'])) {
throw new \InvalidArgumentException('handler must be a callable');
}
// Convert the base_uri to a UriInterface
@ -142,7 +144,7 @@ class Client implements ClientInterface
$uri = Psr7\uri_for($uri === null ? '' : $uri);
if (isset($config['base_uri'])) {
$uri = Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
$uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
}
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
@ -288,7 +290,14 @@ class Client implements ClientInterface
*/
private function applyOptions(RequestInterface $request, array &$options)
{
$modify = [];
$modify = [
'set_headers' => [],
];
if (isset($options['headers'])) {
$modify['set_headers'] = $options['headers'];
unset($options['headers']);
}
if (isset($options['form_params'])) {
if (isset($options['multipart'])) {
@ -300,6 +309,8 @@ class Client implements ClientInterface
}
$options['body'] = http_build_query($options['form_params'], '', '&');
unset($options['form_params']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
}
@ -311,24 +322,19 @@ class Client implements ClientInterface
if (isset($options['json'])) {
$options['body'] = \GuzzleHttp\json_encode($options['json']);
unset($options['json']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/json';
}
if (!empty($options['decode_content'])
&& $options['decode_content'] !== true
) {
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
}
if (isset($options['headers'])) {
if (isset($modify['set_headers'])) {
$modify['set_headers'] = $options['headers'] + $modify['set_headers'];
} else {
$modify['set_headers'] = $options['headers'];
}
unset($options['headers']);
}
if (isset($options['body'])) {
if (is_array($options['body'])) {
$this->invalidBody();
@ -342,6 +348,8 @@ class Client implements ClientInterface
$type = isset($value[2]) ? strtolower($value[2]) : 'basic';
switch ($type) {
case 'basic':
// Ensure that we don't have the header in different case and set the new value.
$modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
$modify['set_headers']['Authorization'] = 'Basic '
. base64_encode("$value[0]:$value[1]");
break;
@ -350,6 +358,10 @@ class Client implements ClientInterface
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
case 'ntlm':
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
}
}
@ -376,6 +388,8 @@ class Client implements ClientInterface
$request = Psr7\modify_request($request, $modify);
if ($request->getBody() instanceof Psr7\MultipartStream) {
// Use a multipart/form-data POST if a Content-Type is not set.
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
. $request->getBody()->getBoundary();
}
@ -402,7 +416,7 @@ class Client implements ClientInterface
throw new \InvalidArgumentException('Passing in the "body" request '
. 'option as an array to send a POST request has been deprecated. '
. 'Please use the "form_params" request option to send a '
. 'application/x-www-form-urlencoded request, or a the "multipart" '
. 'application/x-www-form-urlencoded request, or the "multipart" '
. 'request option to send a multipart/form-data request.');
}
}

View file

@ -12,7 +12,7 @@ use Psr\Http\Message\UriInterface;
*/
interface ClientInterface
{
const VERSION = '6.2.1';
const VERSION = '6.3.3';
/**
* Send an HTTP request.

View file

@ -86,6 +86,25 @@ class CookieJar implements CookieJarInterface
return false;
}
/**
* Finds and returns the cookie based on the name
*
* @param string $name cookie name to search for
* @return SetCookie|null cookie that was found or null if not found
*/
public function getCookieByName($name)
{
// don't allow a null name
if ($name === null) {
return null;
}
foreach ($this->cookies as $cookie) {
if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
return $cookie;
}
}
}
public function toArray()
{
return array_map(function (SetCookie $cookie) {
@ -216,11 +235,41 @@ class CookieJar implements CookieJarInterface
if (!$sc->getDomain()) {
$sc->setDomain($request->getUri()->getHost());
}
if (0 !== strpos($sc->getPath(), '/')) {
$sc->setPath($this->getCookiePathFromRequest($request));
}
$this->setCookie($sc);
}
}
}
/**
* Computes cookie path following RFC 6265 section 5.1.4
*
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4
*
* @param RequestInterface $request
* @return string
*/
private function getCookiePathFromRequest(RequestInterface $request)
{
$uriPath = $request->getUri()->getPath();
if ('' === $uriPath) {
return '/';
}
if (0 !== strpos($uriPath, '/')) {
return '/';
}
if ('/' === $uriPath) {
return '/';
}
if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
return '/';
}
return substr($uriPath, 0, $lastSlashPos);
}
public function withCookieHeader(RequestInterface $request)
{
$values = [];

View file

@ -15,7 +15,7 @@ class SessionCookieJar extends CookieJar
/**
* Create a new SessionCookieJar object
*
* @param string $sessionKey Session key name to store the cookie
* @param string $sessionKey Session key name to store the cookie
* data in session
* @param bool $storeSessionCookies Set to true to store session cookies
* in the cookie jar.

View file

@ -35,14 +35,13 @@ class SetCookie
$data = self::$defaults;
// Explode the cookie string using a series of semicolons
$pieces = array_filter(array_map('trim', explode(';', $cookie)));
// The name of the cookie (first kvp) must include an equal sign.
if (empty($pieces) || !strpos($pieces[0], '=')) {
// The name of the cookie (first kvp) must exist and include an equal sign.
if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
return new self($data);
}
// Add the cookie pieces into the parsed data array
foreach ($pieces as $part) {
$cookieParts = explode('=', $part, 2);
$key = trim($cookieParts[0]);
$value = isset($cookieParts[1])
@ -349,7 +348,7 @@ class SetCookie
return false;
}
return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
}
/**
@ -359,7 +358,7 @@ class SetCookie
*/
public function isExpired()
{
return $this->getExpires() && time() > $this->getExpires();
return $this->getExpires() !== null && time() > $this->getExpires();
}
/**
@ -378,8 +377,8 @@ class SetCookie
// Check if any of the invalid characters are present in the cookie name
if (preg_match(
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
$name)
) {
$name
)) {
return 'Cookie name must not contain invalid characters: ASCII '
. 'Control characters (0-31;127), space, tab and the '
. 'following characters: ()<>@,;:\"/?={}';

View file

@ -1,7 +1,27 @@
<?php
namespace GuzzleHttp\Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Exception when an HTTP error occurs (4xx or 5xx error)
*/
class BadResponseException extends RequestException {}
class BadResponseException extends RequestException
{
public function __construct(
$message,
RequestInterface $request,
ResponseInterface $response = null,
\Exception $previous = null,
array $handlerContext = []
) {
if (null === $response) {
@trigger_error(
'Instantiating the ' . __CLASS__ . ' class without a Response is deprecated since version 6.3 and will be removed in 7.0.',
E_USER_DEPRECATED
);
}
parent::__construct($message, $request, $response, $previous, $handlerContext);
}
}

View file

@ -1,4 +1,13 @@
<?php
namespace GuzzleHttp\Exception;
/**
* @method string getMessage()
* @method \Throwable|null getPrevious()
* @method mixed getCode()
* @method string getFile()
* @method int getLine()
* @method array getTrace()
* @method string getTraceAsString()
*/
interface GuzzleException {}

View file

@ -81,10 +81,10 @@ class RequestException extends TransferException
$level = (int) floor($response->getStatusCode() / 100);
if ($level === 4) {
$label = 'Client error';
$className = __NAMESPACE__ . '\\ClientException';
$className = ClientException::class;
} elseif ($level === 5) {
$label = 'Server error';
$className = __NAMESPACE__ . '\\ServerException';
$className = ServerException::class;
} else {
$label = 'Unsuccessful request';
$className = __CLASS__;
@ -93,13 +93,15 @@ class RequestException extends TransferException
$uri = $request->getUri();
$uri = static::obfuscateUri($uri);
// Server Error: `GET /` resulted in a `404 Not Found` response:
// Client Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated)
$message = sprintf(
'%s: `%s` resulted in a `%s` response',
'%s: `%s %s` resulted in a `%s %s` response',
$label,
$request->getMethod() . ' ' . $uri,
$response->getStatusCode() . ' ' . $response->getReasonPhrase()
$request->getMethod(),
$uri,
$response->getStatusCode(),
$response->getReasonPhrase()
);
$summary = static::getResponseBodySummary($response);
@ -129,6 +131,11 @@ class RequestException extends TransferException
}
$size = $body->getSize();
if ($size === 0) {
return null;
}
$summary = $body->read(120);
$body->rewind();

View file

@ -4,7 +4,6 @@ namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
@ -16,7 +15,7 @@ use Psr\Http\Message\RequestInterface;
class CurlFactory implements CurlFactoryInterface
{
/** @var array */
private $handles;
private $handles = [];
/** @var int Total number of idle handles to keep in cache */
private $maxHandles;
@ -163,7 +162,7 @@ class CurlFactory implements CurlFactoryInterface
// If an exception was encountered during the onHeaders event, then
// return a rejected promise that wraps that exception.
if ($easy->onHeadersException) {
return new RejectedPromise(
return \GuzzleHttp\Promise\rejection_for(
new RequestException(
'An error was encountered during the on_headers event',
$easy->request,
@ -186,7 +185,7 @@ class CurlFactory implements CurlFactoryInterface
? new ConnectException($message, $easy->request, null, $ctx)
: new RequestException($message, $easy->request, $easy->response, null, $ctx);
return new RejectedPromise($error);
return \GuzzleHttp\Promise\rejection_for($error);
}
private function getDefaultConf(EasyHandle $easy)
@ -288,7 +287,14 @@ class CurlFactory implements CurlFactoryInterface
{
foreach ($conf['_headers'] as $name => $values) {
foreach ($values as $value) {
$conf[CURLOPT_HTTPHEADER][] = "$name: $value";
$value = (string) $value;
if ($value === '') {
// cURL requires a special format for empty headers.
// See https://github.com/guzzle/guzzle/issues/1882 for more details.
$conf[CURLOPT_HTTPHEADER][] = "$name;";
} else {
$conf[CURLOPT_HTTPHEADER][] = "$name: $value";
}
}
}
@ -326,12 +332,20 @@ class CurlFactory implements CurlFactoryInterface
$conf[CURLOPT_SSL_VERIFYHOST] = 2;
$conf[CURLOPT_SSL_VERIFYPEER] = true;
if (is_string($options['verify'])) {
$conf[CURLOPT_CAINFO] = $options['verify'];
// Throw an error if the file/folder/link path is not valid or doesn't exist.
if (!file_exists($options['verify'])) {
throw new \InvalidArgumentException(
"SSL CA bundle not found: {$options['verify']}"
);
}
// If it's a directory or a link to a directory use CURLOPT_CAPATH.
// If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
if (is_dir($options['verify']) ||
(is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
$conf[CURLOPT_CAPATH] = $options['verify'];
} else {
$conf[CURLOPT_CAINFO] = $options['verify'];
}
}
}
}
@ -370,15 +384,30 @@ class CurlFactory implements CurlFactoryInterface
$conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
$easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
}
$timeoutRequiresNoSignal = false;
if (isset($options['timeout'])) {
$timeoutRequiresNoSignal |= $options['timeout'] < 1;
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
}
// CURL default value is CURL_IPRESOLVE_WHATEVER
if (isset($options['force_ip_resolve'])) {
if ('v4' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
} elseif ('v6' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
}
}
if (isset($options['connect_timeout'])) {
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
}
if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$conf[CURLOPT_NOSIGNAL] = true;
}
if (isset($options['proxy'])) {
if (!is_array($options['proxy'])) {
$conf[CURLOPT_PROXY] = $options['proxy'];

View file

@ -65,7 +65,9 @@ class CurlMultiHandler
$promise = new Promise(
[$this, 'execute'],
function () use ($id) { return $this->cancel($id); }
function () use ($id) {
return $this->cancel($id);
}
);
$this->addRequest(['easy' => $easy, 'deferred' => $promise]);

View file

@ -1,6 +1,7 @@
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
@ -13,7 +14,7 @@ use Psr\Http\Message\ResponseInterface;
*/
class MockHandler implements \Countable
{
private $queue;
private $queue = [];
private $lastRequest;
private $lastOptions;
private $onFulfilled;
@ -73,12 +74,24 @@ class MockHandler implements \Countable
$this->lastOptions = $options;
$response = array_shift($this->queue);
if (isset($options['on_headers'])) {
if (!is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
}
try {
$options['on_headers']($response);
} catch (\Exception $e) {
$msg = 'An error was encountered during the on_headers event';
$response = new RequestException($msg, $request, $response, $e);
}
}
if (is_callable($response)) {
$response = call_user_func($response, $request, $options);
}
$response = $response instanceof \Exception
? new RejectedPromise($response)
? \GuzzleHttp\Promise\rejection_for($response)
: \GuzzleHttp\Promise\promise_for($response);
return $response->then(
@ -107,7 +120,7 @@ class MockHandler implements \Countable
if ($this->onRejected) {
call_user_func($this->onRejected, $reason);
}
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
);
}
@ -145,7 +158,7 @@ class MockHandler implements \Countable
/**
* Get the last received request options.
*
* @return RequestInterface
* @return array
*/
public function getLastOptions()
{

View file

@ -4,7 +4,6 @@ namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
@ -61,13 +60,14 @@ class StreamHandler
if (strpos($message, 'getaddrinfo') // DNS lookup failed
|| strpos($message, 'Connection refused')
|| strpos($message, "couldn't connect to host") // error on HHVM
|| strpos($message, "connection attempt failed")
) {
$e = new ConnectException($e->getMessage(), $request, $e);
}
$e = RequestException::wrapException($request, $e);
$this->invokeStats($options, $request, $startTime, null, $e);
return new RejectedPromise($e);
return \GuzzleHttp\Promise\rejection_for($e);
}
}
@ -103,7 +103,7 @@ class StreamHandler
$status = $parts[1];
$reason = isset($parts[2]) ? $parts[2] : null;
$headers = \GuzzleHttp\headers_from_lines($hdrs);
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream);
$sink = $stream;
@ -119,7 +119,7 @@ class StreamHandler
} catch (\Exception $e) {
$msg = 'An error was encountered during the on_headers event';
$ex = new RequestException($msg, $request, $response, $e);
return new RejectedPromise($ex);
return \GuzzleHttp\Promise\rejection_for($ex);
}
}
@ -276,7 +276,7 @@ class StreamHandler
}
$params = [];
$context = $this->getDefaultContext($request, $options);
$context = $this->getDefaultContext($request);
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
@ -301,6 +301,17 @@ class StreamHandler
);
}
// Microsoft NTLM authentication only supported with curl handler
if (isset($options['auth'])
&& is_array($options['auth'])
&& isset($options['auth'][2])
&& 'ntlm' == $options['auth'][2]
) {
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
}
$uri = $this->resolveHost($request, $options);
$context = $this->createResource(
function () use ($context, $params) {
return stream_context_create($context, $params);
@ -308,14 +319,45 @@ class StreamHandler
);
return $this->createResource(
function () use ($request, &$http_response_header, $context) {
$resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context);
function () use ($uri, &$http_response_header, $context, $options) {
$resource = fopen((string) $uri, 'r', null, $context);
$this->lastHeaders = $http_response_header;
if (isset($options['read_timeout'])) {
$readTimeout = $options['read_timeout'];
$sec = (int) $readTimeout;
$usec = ($readTimeout - $sec) * 100000;
stream_set_timeout($resource, $sec, $usec);
}
return $resource;
}
);
}
private function resolveHost(RequestInterface $request, array $options)
{
$uri = $request->getUri();
if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
if ('v4' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_A);
if (!isset($records[0]['ip'])) {
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
}
$uri = $uri->withHost($records[0]['ip']);
} elseif ('v6' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_AAAA);
if (!isset($records[0]['ipv6'])) {
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
}
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
}
}
return $uri;
}
private function getDefaultContext(RequestInterface $request)
{
$headers = '';

View file

@ -22,7 +22,7 @@ class HandlerStack
* Creates a default handler stack that can be used by clients.
*
* The returned handler will wrap the provided handler or use the most
* appropriate default handler for you system. The returned HandlerStack has
* appropriate default handler for your system. The returned HandlerStack has
* support for cookies, redirects, HTTP error exceptions, and preparing a body
* before sending.
*

View file

@ -19,7 +19,6 @@ use Psr\Http\Message\ResponseInterface;
* - {host}: Host of the request
* - {method}: Method of the request
* - {uri}: URI of the request
* - {host}: Host of the request
* - {version}: Protocol version
* - {target}: Request target of the request (path + query + fragment)
* - {hostname}: Hostname of the machine that sent the request
@ -74,7 +73,6 @@ class MessageFormatter
return preg_replace_callback(
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
function (array $matches) use ($request, $response, $error, &$cache) {
if (isset($cache[$matches[1]])) {
return $cache[$matches[1]];
}

View file

@ -34,10 +34,11 @@ final class Middleware
$cookieJar = $options['cookies'];
$request = $cookieJar->withCookieHeader($request);
return $handler($request, $options)
->then(function ($response) use ($cookieJar, $request) {
$cookieJar->extractCookies($request, $response);
return $response;
}
->then(
function ($response) use ($cookieJar, $request) {
$cookieJar->extractCookies($request, $response);
return $response;
}
);
};
};
@ -72,7 +73,7 @@ final class Middleware
/**
* Middleware that pushes history data to an ArrayAccess container.
*
* @param array $container Container to hold the history (by reference).
* @param array|\ArrayAccess $container Container to hold the history (by reference).
*
* @return callable Returns a function that accepts the next handler.
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
@ -102,7 +103,7 @@ final class Middleware
'error' => $reason,
'options' => $options
];
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
);
};

View file

@ -14,9 +14,6 @@ class PrepareBodyMiddleware
/** @var callable */
private $nextHandler;
/** @var array */
private static $skipMethods = ['GET' => true, 'HEAD' => true];
/**
* @param callable $nextHandler Next handler to invoke.
*/
@ -36,9 +33,7 @@ class PrepareBodyMiddleware
$fn = $this->nextHandler;
// Don't do anything if the request has no body.
if (isset(self::$skipMethods[$request->getMethod()])
|| $request->getBody()->getSize() === 0
) {
if ($request->getBody()->getSize() === 0) {
return $fn($request, $options);
}
@ -54,8 +49,7 @@ class PrepareBodyMiddleware
}
// Add a default content-length or transfer-encoding header.
if (!isset(self::$skipMethods[$request->getMethod()])
&& !$request->hasHeader('Content-Length')
if (!$request->hasHeader('Content-Length')
&& !$request->hasHeader('Transfer-Encoding')
) {
$size = $request->getBody()->getSize();

View file

@ -19,6 +19,8 @@ class RedirectMiddleware
{
const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
public static $defaultSettings = [
'max' => 5,
'protocols' => ['http', 'https'],
@ -108,23 +110,27 @@ class RedirectMiddleware
if (!empty($options['allow_redirects']['track_redirects'])) {
return $this->withTracking(
$promise,
(string) $nextRequest->getUri()
(string) $nextRequest->getUri(),
$response->getStatusCode()
);
}
return $promise;
}
private function withTracking(PromiseInterface $promise, $uri)
private function withTracking(PromiseInterface $promise, $uri, $statusCode)
{
return $promise->then(
function (ResponseInterface $response) use ($uri) {
function (ResponseInterface $response) use ($uri, $statusCode) {
// Note that we are pushing to the front of the list as this
// would be an earlier response than what is currently present
// in the history header.
$header = $response->getHeader(self::HISTORY_HEADER);
array_unshift($header, $uri);
return $response->withHeader(self::HISTORY_HEADER, $header);
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
array_unshift($historyHeader, $uri);
array_unshift($statusHeader, $statusCode);
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
}
);
}
@ -208,9 +214,9 @@ class RedirectMiddleware
ResponseInterface $response,
array $protocols
) {
$location = Psr7\Uri::resolve(
$location = Psr7\UriResolver::resolve(
$request->getUri(),
$response->getHeaderLine('Location')
new Psr7\Uri($response->getHeaderLine('Location'))
);
// Ensure that the redirect URI is allowed based on the protocols.

View file

@ -43,8 +43,8 @@ final class RequestOptions
const AUTH = 'auth';
/**
* body: (string|null|callable|iterator|object) Body to send in the
* request.
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
* Body to send in the request.
*/
const BODY = 'body';
@ -237,8 +237,19 @@ final class RequestOptions
*/
const TIMEOUT = 'timeout';
/**
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing
* the body read timeout, for stream requests.
*/
const READ_TIMEOUT = 'read_timeout';
/**
* version: (float) Specifies the HTTP protocol version to attempt to use.
*/
const VERSION = 'version';
/**
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
*/
const FORCE_IP_RESOLVE = 'force_ip_resolve';
}

View file

@ -97,7 +97,7 @@ class RetryMiddleware
null,
$reason
)) {
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
return $this->doRetry($req, $options);
};

View file

@ -107,7 +107,6 @@ class UriTemplate
$useQuery = self::$operatorHash[$parsed['operator']]['query'];
foreach ($parsed['values'] as $value) {
if (!isset($this->variables[$value['value']])) {
continue;
}
@ -117,11 +116,9 @@ class UriTemplate
$expanded = '';
if (is_array($variable)) {
$isAssoc = $this->isAssoc($variable);
$kvp = [];
foreach ($variable as $key => $var) {
if ($isAssoc) {
$key = rawurlencode($key);
$isNestedArray = is_array($var);
@ -179,7 +176,6 @@ class UriTemplate
}
$expanded = implode(',', $kvp);
}
} else {
if ($value['modifier'] === ':') {
$variable = substr($variable, 0, $value['position']);

View file

@ -167,6 +167,8 @@ function default_ca_bundle()
'/etc/ssl/certs/ca-certificates.crt',
// FreeBSD (provided by the ca_root_nss package)
'/usr/local/share/certs/ca-root-nss.crt',
// SLES 12 (provided by the ca-certificates package)
'/var/lib/ca-certificates/ca-bundle.pem',
// OS X provided by homebrew (using the default path)
'/usr/local/etc/openssl/cert.pem',
// Google app engine
@ -300,7 +302,8 @@ function json_decode($json, $assoc = false, $depth = 512, $options = 0)
$data = \json_decode($json, $assoc, $depth, $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_decode error: ' . json_last_error_msg());
'json_decode error: ' . json_last_error_msg()
);
}
return $data;
@ -322,7 +325,8 @@ function json_encode($value, $options = 0, $depth = 512)
$json = \json_encode($value, $options, $depth);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_encode error: ' . json_last_error_msg());
'json_encode error: ' . json_last_error_msg()
);
}
return $json;

View file

@ -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

View file

@ -1,11 +0,0 @@
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE

View file

@ -1,20 +0,0 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
install:
- travis_retry composer install --no-interaction --prefer-source
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true

View file

@ -1,65 +1,203 @@
# CHANGELOG
# Change Log
## 1.3.1 - 2016-06-25
* 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,
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.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
### 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.
## [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`
- `Uri::isAbsolutePathReference`
- `Uri::isRelativePathReference`
- `Uri::isSameDocumentReference`
- `Uri::composeComponents`
- `UriNormalizer::normalize`
- `UriNormalizer::isEquivalent`
- `UriResolver::relativize`
### 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.
## [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.
## 1.2.3 - 2016-02-18
### Changed
* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
- Can now extend Uri.
### 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
* 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.
## [1.2.2] - 2016-01-22
## 1.2.1 - 2015-11-02
### Added
* Now supporting negative offsets when seeking to SEEK_END.
- 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.
## 1.2.0 - 2015-08-15
* 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
## [1.2.1] - 2015-11-02
### 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
* 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
## [1.1.0] - 2015-06-24
### 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.
@ -68,3 +206,20 @@ Currently unsupported:
- `Psr\Http\Message\ServerRequestInterface`
- `Psr\Http\Message\UploadedFileInterface`
[Unreleased]: https://github.com/guzzle/psr7/compare/1.5.2...HEAD
[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

View file

@ -1,29 +0,0 @@
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/*

View file

@ -372,7 +372,7 @@ This method accepts the following `$resource` types:
$stream = GuzzleHttp\Psr7\stream_for('foo');
$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
$generator function ($bytes) {
$generator = function ($bytes) {
for ($i = 0; $i < $bytes; $i++) {
yield ' ';
}
@ -519,51 +519,227 @@ Determines the mimetype of a file by looking at its extension.
Maps a file extensions to a mimetype.
# Static URI methods
# Additional URI Methods
The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.
Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
this library also provides additional functionality when working with URIs as static methods.
## URI Types
## `GuzzleHttp\Psr7\Uri::removeDotSegments`
An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
the base URI. Relative references can be divided into several forms according to
[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
`public static function removeDotSegments(string $path): string`
- network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path`
- relative-path references, e.g. `subpath`
Removes dot segments from a path and returns the new path.
The following methods can be used to identify the type of the URI.
See http://tools.ietf.org/html/rfc3986#section-5.2.4
### `GuzzleHttp\Psr7\Uri::isAbsolute`
`public static function isAbsolute(UriInterface $uri): bool`
## `GuzzleHttp\Psr7\Uri::resolve`
Whether the URI is absolute, i.e. it has a scheme.
`public static function resolve(UriInterface $base, $rel): UriInterface`
### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
Resolve a base URI with a relative URI and return a new URI.
`public static function isNetworkPathReference(UriInterface $uri): bool`
See http://tools.ietf.org/html/rfc3986#section-5
Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
termed an network-path reference.
### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
## `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function isAbsolutePathReference(UriInterface $uri): bool`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
termed an absolute-path reference.
Create a new URI with a specific query string value.
### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.
`public static function isRelativePathReference(UriInterface $uri): bool`
Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
termed a relative-path reference.
## `GuzzleHttp\Psr7\Uri::withoutQueryValue`
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
Create a new URI with a specific query string value removed.
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
(apart from its fragment) is considered a same-document reference.
Any existing query string values that exactly match the provided key are
removed.
## URI Components
Additional methods to work with URI components.
## `GuzzleHttp\Psr7\Uri::fromParts`
### `GuzzleHttp\Psr7\Uri::isDefaultPort`
`public static function isDefaultPort(UriInterface $uri): bool`
Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
or the standard port. This method can be used independently of the implementation.
### `GuzzleHttp\Psr7\Uri::composeComponents`
`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
### `GuzzleHttp\Psr7\Uri::fromParts`
`public static function fromParts(array $parts): UriInterface`
Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
### `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Creates a new URI with a specific query string value. Any existing query string values that exactly match the
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`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
provided key are removed.
## Reference Resolution
`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
do when resolving a link in a website based on the current request URI.
### `GuzzleHttp\Psr7\UriResolver::resolve`
`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
Converts the relative URI into a new URI that is resolved against the base URI.
### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
### `GuzzleHttp\Psr7\UriResolver::relativize`
`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
```php
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
```
One use-case is to use the current request URI as base URI and then generate relative links in your documents
to reduce the document size or offer self-contained downloadable document archives.
```php
$base = new Uri('http://example.com/a/b/');
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
```
## Normalization and Comparison
`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
### `GuzzleHttp\Psr7\UriNormalizer::normalize`
`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
of normalizations to apply. The following normalizations are available:
- `UriNormalizer::PRESERVING_NORMALIZATIONS`
Default normalizations which only include the ones that preserve semantics.
- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
Example: `http://example.org/a%c2%b1b``http://example.org/a%C2%B1b`
- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
ALPHA (%41%5A and %61%7A), DIGIT (%30%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
characters by URI normalizers.
Example: `http://example.org/%7Eusern%61me/``http://example.org/~username/`
- `UriNormalizer::CONVERT_EMPTY_PATH`
Converts the empty path to "/" for http and https URIs.
Example: `http://example.org``http://example.org/`
- `UriNormalizer::REMOVE_DEFAULT_HOST`
Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
"localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
RFC 3986.
Example: `file://localhost/myfile``file:///myfile`
- `UriNormalizer::REMOVE_DEFAULT_PORT`
Removes the default port of the given URI scheme from the URI.
Example: `http://example.org:80/``http://example.org/`
- `UriNormalizer::REMOVE_DOT_SEGMENTS`
Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
change the semantics of the URI reference.
Example: `http://example.org/../a/b/../c/./d.html``http://example.org/a/c/d.html`
- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
may change the semantics. Encoded slashes (%2F) are not removed.
Example: `http://example.org//foo///bar.html``http://example.org/foo/bar.html`
- `UriNormalizer::SORT_QUERY_PARAMETERS`
Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
of the URI.
Example: `?lang=en&article=fred``?article=fred&lang=en`
### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
equivalence or difference of relative references does not mean anything.

View file

@ -1,22 +1,27 @@
{
"name": "guzzlehttp/psr7",
"type": "library",
"description": "PSR-7 message implementation",
"keywords": ["message", "stream", "http", "uri"],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"provide": {
"psr/http-message-implementation": "1.0"
@ -27,9 +32,14 @@
},
"files": ["src/functions_include.php"]
},
"autoload-dev": {
"psr-4": {
"GuzzleHttp\\Tests\\Psr7\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
}
}

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory suffix="Interface.php">src/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View file

@ -16,7 +16,6 @@ class AppendStream implements StreamInterface
private $seekable = true;
private $current = 0;
private $pos = 0;
private $detached = false;
/**
* @param StreamInterface[] $streams Streams to decorate. Each stream must
@ -73,6 +72,7 @@ class AppendStream implements StreamInterface
public function close()
{
$this->pos = $this->current = 0;
$this->seekable = true;
foreach ($this->streams as $stream) {
$stream->close();
@ -82,14 +82,22 @@ class AppendStream implements StreamInterface
}
/**
* 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 = [];
}
public function tell()

View file

@ -52,6 +52,15 @@ class FnStream implements StreamInterface
}
}
/**
* An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
* @throws \LogicException
*/
public function __wakeup()
{
throw new \LogicException('FnStream should never be unserialized');
}
/**
* Adds custom functionality to an underlying stream by intercepting
* specific method calls.

View file

@ -27,7 +27,7 @@ class InflateStream implements StreamInterface
$stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
$resource = StreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource);
$this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
}
/**

View file

@ -21,7 +21,7 @@ class LimitStream implements StreamInterface
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int|null $offset Position to seek to before reading (only
* @param int $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(

View file

@ -27,7 +27,7 @@ class MultipartStream implements StreamInterface
*/
public function __construct(array $elements = [], $boundary = null)
{
$this->boundary = $boundary ?: uniqid();
$this->boundary = $boundary ?: sha1(uniqid('', true));
$this->stream = $this->createStream($elements);
}
@ -108,7 +108,7 @@ class MultipartStream implements StreamInterface
/**
* @return array
*/
private function createElement($name, $stream, $filename, array $headers)
private function createElement($name, StreamInterface $stream, $filename, array $headers)
{
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');

View file

@ -19,7 +19,7 @@ class Request implements RequestInterface
/** @var null|string */
private $requestTarget;
/** @var null|UriInterface */
/** @var UriInterface */
private $uri;
/**
@ -45,7 +45,7 @@ class Request implements RequestInterface
$this->setHeaders($headers);
$this->protocol = $version;
if (!$this->hasHeader('Host')) {
if (!isset($this->headerNames['host'])) {
$this->updateHostFromUri();
}
@ -110,7 +110,7 @@ class Request implements RequestInterface
$new = clone $this;
$new->uri = $uri;
if (!$preserveHost) {
if (!$preserveHost || !isset($this->headerNames['host'])) {
$new->updateHostFromUri();
}

View file

@ -2,6 +2,7 @@
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* PSR-7 response implementation.
@ -92,6 +93,10 @@ class Response implements ResponseInterface
$version = '1.1',
$reason = null
) {
if (filter_var($status, FILTER_VALIDATE_INT) === false) {
throw new \InvalidArgumentException('Status code must be an integer value.');
}
$this->statusCode = (int) $status;
if ($body !== '' && $body !== null) {
@ -100,7 +105,7 @@ class Response implements ResponseInterface
$this->setHeaders($headers);
if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status];
$this->reasonPhrase = self::$phrases[$this->statusCode];
} else {
$this->reasonPhrase = (string) $reason;
}

View file

@ -0,0 +1,18 @@
<?php
namespace GuzzleHttp\Psr7;
final class Rfc7230
{
/**
* Header related regular expressions (copied from amphp/http package)
* (Note: once we require PHP 7.x we could just depend on the upstream package)
*
* Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
*
* @link https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
* @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
*/
const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
}

View file

@ -166,7 +166,7 @@ class ServerRequest extends Request implements ServerRequestInterface
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+');
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
@ -180,33 +180,63 @@ class ServerRequest extends Request implements ServerRequestInterface
->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('');
if (isset($_SERVER['HTTPS'])) {
$uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
}
$uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
$hasPort = false;
if (isset($_SERVER['HTTP_HOST'])) {
$uri = $uri->withHost($_SERVER['HTTP_HOST']);
list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
if ($host !== null) {
$uri = $uri->withHost($host);
}
if ($port !== null) {
$hasPort = true;
$uri = $uri->withPort($port);
}
} elseif (isset($_SERVER['SERVER_NAME'])) {
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
} elseif (isset($_SERVER['SERVER_ADDR'])) {
$uri = $uri->withHost($_SERVER['SERVER_ADDR']);
}
if (isset($_SERVER['SERVER_PORT'])) {
if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
$uri = $uri->withPort($_SERVER['SERVER_PORT']);
}
$hasQuery = false;
if (isset($_SERVER['REQUEST_URI'])) {
$uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
$requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
$uri = $uri->withPath($requestUriParts[0]);
if (isset($requestUriParts[1])) {
$hasQuery = true;
$uri = $uri->withQuery($requestUriParts[1]);
}
}
if (isset($_SERVER['QUERY_STRING'])) {
if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
}

View file

@ -24,11 +24,11 @@ class Stream implements StreamInterface
'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
'x+t' => true, 'c+t' => true, 'a+' => true, 'rb+' => true,
],
'write' => [
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'rb+' => true,
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
]
@ -70,15 +70,6 @@ class Stream implements StreamInterface
$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
*/
@ -99,6 +90,10 @@ class Stream implements StreamInterface
public function getContents()
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
$contents = stream_get_contents($this->stream);
if ($contents === false) {
@ -173,11 +168,19 @@ class Stream implements StreamInterface
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 +197,13 @@ class Stream implements StreamInterface
public function seek($offset, $whence = SEEK_SET)
{
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,15 +211,33 @@ class Stream implements StreamInterface
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');
}
if ($length < 0) {
throw new \RuntimeException('Length parameter cannot be negative');
}
return fread($this->stream, $length);
if (0 === $length) {
return '';
}
$string = fread($this->stream, $length);
if (false === $string) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}
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');
}

View file

@ -38,9 +38,21 @@ class StreamWrapper
. 'writable, or both.');
}
return fopen('guzzle://stream', $mode, null, stream_context_create([
return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
}
/**
* Creates a stream context that can be used to open a stream as a php stream resource.
*
* @param StreamInterface $stream
*
* @return resource
*/
public static function createStreamContext(StreamInterface $stream)
{
return stream_context_create([
'guzzle' => ['stream' => $stream]
]));
]);
}
/**
@ -94,12 +106,21 @@ class StreamWrapper
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 [
@ -118,4 +139,23 @@ class StreamWrapper
'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
];
}
}

View file

@ -12,9 +12,26 @@ use Psr\Http\Message\UriInterface;
*/
class Uri implements UriInterface
{
private static $schemes = [
/**
* Absolute http and https URIs require a host per RFC 7230 Section 2.7
* but in generic URIs the host can be empty. So for http(s) URIs
* we apply this default host when no host is given yet to form a
* valid URI.
*/
const HTTP_DEFAULT_HOST = 'localhost';
private static $defaultPorts = [
'http' => 80,
'https' => 443,
'ftp' => 21,
'gopher' => 70,
'nntp' => 119,
'news' => 119,
'telnet' => 23,
'tn3270' => 23,
'imap' => 143,
'pop' => 110,
'ldap' => 389,
];
private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
@ -47,6 +64,7 @@ class Uri implements UriInterface
*/
public function __construct($uri = '')
{
// weak type check to also accept null until we can add scalar type hints
if ($uri != '') {
$parts = parse_url($uri);
if ($parts === false) {
@ -58,7 +76,7 @@ class Uri implements UriInterface
public function __toString()
{
return self::createUriString(
return self::composeComponents(
$this->scheme,
$this->getAuthority(),
$this->path,
@ -67,57 +85,199 @@ class Uri implements UriInterface
);
}
/**
* Composes a URI reference string from its various components.
*
* Usually this method does not need to be called manually but instead is used indirectly via
* `Psr\Http\Message\UriInterface::__toString`.
*
* PSR-7 UriInterface treats an empty component the same as a missing component as
* getQuery(), getFragment() etc. always return a string. This explains the slight
* difference to RFC 3986 Section 5.3.
*
* Another adjustment is that the authority separator is added even when the authority is missing/empty
* for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
* `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
* that format).
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
*
* @return string
*
* @link https://tools.ietf.org/html/rfc3986#section-5.3
*/
public static function composeComponents($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
// weak type checks to also accept null until we can add scalar type hints
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != ''|| $scheme === 'file') {
$uri .= '//' . $authority;
}
$uri .= $path;
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Whether the URI has the default port of the current scheme.
*
* `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
* independently of the implementation.
*
* @param UriInterface $uri
*
* @return bool
*/
public static function isDefaultPort(UriInterface $uri)
{
return $uri->getPort() === null
|| (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
}
/**
* Whether the URI is absolute, i.e. it has a scheme.
*
* An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
* if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
* to another URI, the base URI. Relative references can be divided into several forms:
* - network-path references, e.g. '//example.com/path'
* - absolute-path references, e.g. '/path'
* - relative-path references, e.g. 'subpath'
*
* @param UriInterface $uri
*
* @return bool
* @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference
* @link https://tools.ietf.org/html/rfc3986#section-4
*/
public static function isAbsolute(UriInterface $uri)
{
return $uri->getScheme() !== '';
}
/**
* Whether the URI is a network-path reference.
*
* A relative reference that begins with two slash characters is termed an network-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isNetworkPathReference(UriInterface $uri)
{
return $uri->getScheme() === '' && $uri->getAuthority() !== '';
}
/**
* Whether the URI is a absolute-path reference.
*
* A relative reference that begins with a single slash character is termed an absolute-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isAbsolutePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& isset($uri->getPath()[0])
&& $uri->getPath()[0] === '/';
}
/**
* Whether the URI is a relative-path reference.
*
* A relative reference that does not begin with a slash character is termed a relative-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isRelativePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
}
/**
* Whether the URI is a same-document reference.
*
* A same-document reference refers to a URI that is, aside from its fragment
* component, identical to the base URI. When no base URI is given, only an empty
* URI reference (apart from its fragment) is considered a same-document reference.
*
* @param UriInterface $uri The URI to check
* @param UriInterface|null $base An optional base URI to compare against
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.4
*/
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
{
if ($base !== null) {
$uri = UriResolver::resolve($base, $uri);
return ($uri->getScheme() === $base->getScheme())
&& ($uri->getAuthority() === $base->getAuthority())
&& ($uri->getPath() === $base->getPath())
&& ($uri->getQuery() === $base->getQuery());
}
return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
}
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*
* @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
* @see UriResolver::removeDotSegments
*/
public static function removeDotSegments($path)
{
static $noopPaths = ['' => true, '/' => true, '*' => true];
static $ignoreSegments = ['.' => true, '..' => true];
if (isset($noopPaths[$path])) {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
// Add the leading slash if necessary
if (substr($path, 0, 1) === '/' &&
substr($newPath, 0, 1) !== '/'
) {
$newPath = '/' . $newPath;
}
// Add the trailing slash if necessary
if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/';
}
return $newPath;
return UriResolver::removeDotSegments($path);
}
/**
* Resolve a base URI with a relative URI and return a new URI.
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param string|UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*
* @deprecated since version 1.4. Use UriResolver::resolve instead.
* @see UriResolver::resolve
*/
public static function resolve(UriInterface $base, $rel)
{
@ -125,55 +285,11 @@ class Uri implements UriInterface
$rel = new self($rel);
}
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new self(self::createUriString(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
return UriResolver::resolve($base, $rel);
}
/**
* Create a new URI with a specific query string value removed.
* Creates a new URI with a specific query string value removed.
*
* Any existing query string values that exactly match the provided key are
* removed.
@ -185,21 +301,13 @@ class Uri implements UriInterface
*/
public static function withoutQueryValue(UriInterface $uri, $key)
{
$current = $uri->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));
}
/**
* Create a new URI with a specific query string value.
* Creates a new URI with a specific query string value.
*
* Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair.
@ -215,42 +323,50 @@ class Uri implements UriInterface
*/
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));
}
/**
* Create a URI from a hash of parse_url parts.
* Creates a URI from a hash of `parse_url` components.
*
* @param array $parts
*
* @return self
* @return UriInterface
* @link http://php.net/manual/en/function.parse-url.php
*
* @throws \InvalidArgumentException If the components do not form a valid URI.
*/
public static function fromParts(array $parts)
{
$uri = new self();
$uri->applyParts($parts);
$uri->validateState();
return $uri;
}
@ -261,12 +377,8 @@ class Uri implements UriInterface
public function getAuthority()
{
if ($this->host == '') {
return '';
}
$authority = $this->host;
if ($this->userInfo != '') {
if ($this->userInfo !== '') {
$authority = $this->userInfo . '@' . $authority;
}
@ -317,7 +429,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->port);
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@ -334,6 +448,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->userInfo = $info;
$new->validateState();
return $new;
}
@ -347,6 +463,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->host = $host;
$new->validateState();
return $new;
}
@ -360,6 +478,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->port = $port;
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@ -373,6 +494,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->path = $path;
$new->validateState();
return $new;
}
@ -386,6 +509,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->query = $query;
return $new;
}
@ -399,6 +523,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->fragment = $fragment;
return $new;
}
@ -431,69 +556,8 @@ class Uri implements UriInterface
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
/**
* Create a URI string from its various parts
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
* @return string
*/
private static function createUriString($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != '') {
$uri .= '//' . $authority;
}
if ($path != '') {
if ($path[0] !== '/') {
if ($authority != '') {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && $path[1] === '/') {
if ($authority == '') {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . ltrim($path, '/');
}
}
$uri .= $path;
}
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Is a given port non-standard for the current scheme?
*
* @param string $scheme
* @param int $port
*
* @return bool
*/
private static function isNonStandardPort($scheme, $port)
{
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
$this->removeDefaultPort();
}
/**
@ -548,7 +612,55 @@ class Uri implements UriInterface
);
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
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)) {
$this->port = null;
}
}
/**
@ -599,4 +711,28 @@ class Uri implements UriInterface
{
return rawurlencode($match[0]);
}
private function validateState()
{
if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
$this->host = self::HTTP_DEFAULT_HOST;
}
if ($this->getAuthority() === '') {
if (0 === strpos($this->path, '//')) {
throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
}
if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
}
} elseif (isset($this->path[0]) && $this->path[0] !== '/') {
@trigger_error(
'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
E_USER_DEPRECATED
);
$this->path = '/'. $this->path;
//throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
}
}
}

View file

@ -0,0 +1,216 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Provides methods to normalize and compare URIs.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-6
*/
final class UriNormalizer
{
/**
* Default normalizations which only include the ones that preserve semantics.
*
* self::CAPITALIZE_PERCENT_ENCODING | self::DECODE_UNRESERVED_CHARACTERS | self::CONVERT_EMPTY_PATH |
* self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | self::REMOVE_DOT_SEGMENTS
*/
const PRESERVING_NORMALIZATIONS = 63;
/**
* All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
*
* Example: http://example.org/a%c2%b1b http://example.org/a%C2%B1b
*/
const CAPITALIZE_PERCENT_ENCODING = 1;
/**
* Decodes percent-encoded octets of unreserved characters.
*
* For consistency, percent-encoded octets in the ranges of ALPHA (%41%5A and %61%7A), DIGIT (%30%39),
* hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
* when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
*
* Example: http://example.org/%7Eusern%61me/ http://example.org/~username/
*/
const DECODE_UNRESERVED_CHARACTERS = 2;
/**
* Converts the empty path to "/" for http and https URIs.
*
* Example: http://example.org http://example.org/
*/
const CONVERT_EMPTY_PATH = 4;
/**
* Removes the default host of the given URI scheme from the URI.
*
* Only the "file" scheme defines the default host "localhost".
* All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
* are equivalent according to RFC 3986. The first format is not accepted
* by PHPs stream functions and thus already normalized implicitly to the
* second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
*
* Example: file://localhost/myfile file:///myfile
*/
const REMOVE_DEFAULT_HOST = 8;
/**
* Removes the default port of the given URI scheme from the URI.
*
* Example: http://example.org:80/ http://example.org/
*/
const REMOVE_DEFAULT_PORT = 16;
/**
* Removes unnecessary dot-segments.
*
* Dot-segments in relative-path references are not removed as it would
* change the semantics of the URI reference.
*
* Example: http://example.org/../a/b/../c/./d.html http://example.org/a/c/d.html
*/
const REMOVE_DOT_SEGMENTS = 32;
/**
* Paths which include two or more adjacent slashes are converted to one.
*
* Webservers usually ignore duplicate slashes and treat those URIs equivalent.
* But in theory those URIs do not need to be equivalent. So this normalization
* may change the semantics. Encoded slashes (%2F) are not removed.
*
* Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
*/
const REMOVE_DUPLICATE_SLASHES = 64;
/**
* Sort query parameters with their values in alphabetical order.
*
* However, the order of parameters in a URI may be significant (this is not defined by the standard).
* So this normalization is not safe and may change the semantics of the URI.
*
* Example: ?lang=en&article=fred ?article=fred&lang=en
*
* Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
* purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
*/
const SORT_QUERY_PARAMETERS = 128;
/**
* Returns a normalized URI.
*
* The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
* This methods adds additional normalizations that can be configured with the $flags parameter.
*
* PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
* getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
* treated equivalent which is not necessarily true according to RFC 3986. But that difference
* is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
*
* @param UriInterface $uri The URI to normalize
* @param int $flags A bitmask of normalizations to apply, see constants
*
* @return UriInterface The normalized URI
* @link https://tools.ietf.org/html/rfc3986#section-6.2
*/
public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS)
{
if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
$uri = self::capitalizePercentEncoding($uri);
}
if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
$uri = self::decodeUnreservedCharacters($uri);
}
if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' &&
($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
) {
$uri = $uri->withPath('/');
}
if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
$uri = $uri->withHost('');
}
if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
$uri = $uri->withPort(null);
}
if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
$uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
}
if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
$uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
}
if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
$queryKeyValues = explode('&', $uri->getQuery());
sort($queryKeyValues);
$uri = $uri->withQuery(implode('&', $queryKeyValues));
}
return $uri;
}
/**
* Whether two URIs can be considered equivalent.
*
* Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
* accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
* resolved against the same base URI. If this is not the case, determination of equivalence or difference of
* relative references does not mean anything.
*
* @param UriInterface $uri1 An URI to compare
* @param UriInterface $uri2 An URI to compare
* @param int $normalizations A bitmask of normalizations to apply, see constants
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-6.1
*/
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
{
return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
}
private static function capitalizePercentEncoding(UriInterface $uri)
{
$regex = '/(?:%[A-Fa-f0-9]{2})++/';
$callback = function (array $match) {
return strtoupper($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private static function decodeUnreservedCharacters(UriInterface $uri)
{
$regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
$callback = function (array $match) {
return rawurldecode($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private function __construct()
{
// cannot be instantiated
}
}

View file

@ -0,0 +1,219 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Resolves a URI reference in the context of a base URI and the opposite way.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-5
*/
final class UriResolver
{
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*/
public static function removeDotSegments($path)
{
if ($path === '' || $path === '/') {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif ($segment !== '.') {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
// Re-add the leading slash if necessary for cases like "/.."
$newPath = '/' . $newPath;
} elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
// Add the trailing slash if necessary
// If newPath is not empty, then $segment must be set and is the last segment from the foreach
$newPath .= '/';
}
return $newPath;
}
/**
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, UriInterface $rel)
{
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new Uri(Uri::composeComponents(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
}
/**
* Returns the target URI as a relative reference from the base URI.
*
* This method is the counterpart to resolve():
*
* (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
*
* One use-case is to use the current request URI as base URI and then generate relative links in your documents
* to reduce the document size or offer self-contained downloadable document archives.
*
* $base = new Uri('http://example.com/a/b/');
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
* echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
*
* This method also accepts a target that is already relative and will try to relativize it further. Only a
* relative-path reference will be returned as-is.
*
* echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
*
* @param UriInterface $base Base URI
* @param UriInterface $target Target URI
*
* @return UriInterface The relative URI reference
*/
public static function relativize(UriInterface $base, UriInterface $target)
{
if ($target->getScheme() !== '' &&
($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
) {
return $target;
}
if (Uri::isRelativePathReference($target)) {
// As the target is already highly relative we return it as-is. It would be possible to resolve
// the target with `$target = self::resolve($base, $target);` and then try make it more relative
// by removing a duplicate query. But let's not do that automatically.
return $target;
}
if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
return $target->withScheme('');
}
// We must remove the path before removing the authority because if the path starts with two slashes, the URI
// would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
// invalid.
$emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
if ($base->getPath() !== $target->getPath()) {
return $emptyPathUri->withPath(self::getRelativePath($base, $target));
}
if ($base->getQuery() === $target->getQuery()) {
// Only the target fragment is left. And it must be returned even if base and target fragment are the same.
return $emptyPathUri->withQuery('');
}
// 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.
if ($target->getQuery() === '') {
$segments = explode('/', $target->getPath());
$lastSegment = end($segments);
return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
}
return $emptyPathUri;
}
private static function getRelativePath(UriInterface $base, UriInterface $target)
{
$sourceSegments = explode('/', $base->getPath());
$targetSegments = explode('/', $target->getPath());
array_pop($sourceSegments);
$targetLastSegment = array_pop($targetSegments);
foreach ($sourceSegments as $i => $segment) {
if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
unset($sourceSegments[$i], $targetSegments[$i]);
} else {
break;
}
}
$targetSegments[] = $targetLastSegment;
$relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
// A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
$relativePath = "./$relativePath";
} elseif ('/' === $relativePath[0]) {
if ($base->getAuthority() != '' && $base->getPath() === '') {
// In this case an extra slash is added by resolve() automatically. So we must not add one here.
$relativePath = ".$relativePath";
} else {
$relativePath = "./$relativePath";
}
}
return $relativePath;
}
private function __construct()
{
// cannot be instantiated
}
}

View file

@ -69,10 +69,10 @@ function uri_for($uri)
* - 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
* @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @return StreamInterface
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
function stream_for($resource = '', array $options = [])
@ -238,7 +238,7 @@ function modify_request(RequestInterface $request, array $changes)
}
if ($request instanceof ServerRequestInterface) {
return new ServerRequest(
return (new ServerRequest(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
$headers,
@ -247,7 +247,11 @@ function modify_request(RequestInterface $request, array $changes)
? $changes['version']
: $request->getProtocolVersion(),
$request->getServerParams()
);
))
->withParsedBody($request->getParsedBody())
->withQueryParams($request->getQueryParams())
->withCookieParams($request->getCookieParams())
->withUploadedFiles($request->getUploadedFiles());
}
return new Request(
@ -371,25 +375,24 @@ function copy_to_stream(
StreamInterface $dest,
$maxLen = -1
) {
$bufferSize = 8192;
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
if (!$dest->write($source->read($bufferSize))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
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);
}
}
}
@ -432,7 +435,7 @@ function hash(
* @param StreamInterface $stream Stream to read from
* @param int $maxLength Maximum buffer length
*
* @return string|bool
* @return string
*/
function readline(StreamInterface $stream, $maxLength = null)
{
@ -492,8 +495,11 @@ function parse_request($message)
function parse_response($message)
{
$data = _parse_message($message);
if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string');
// 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);
@ -514,8 +520,8 @@ function parse_response($message)
* 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
*/
@ -531,9 +537,9 @@ function parse_query($str, $urlEncoding = true)
$decoder = function ($value) {
return rawurldecode(str_replace('+', ' ', $value));
};
} elseif ($urlEncoding == PHP_QUERY_RFC3986) {
} elseif ($urlEncoding === PHP_QUERY_RFC3986) {
$decoder = 'rawurldecode';
} elseif ($urlEncoding == PHP_QUERY_RFC1738) {
} elseif ($urlEncoding === PHP_QUERY_RFC1738) {
$decoder = 'urldecode';
} else {
$decoder = function ($str) { return $str; };
@ -631,6 +637,7 @@ function mimetype_from_filename($filename)
function mimetype_from_extension($extension)
{
static $mimetypes = [
'3gp' => 'video/3gpp',
'7z' => 'application/x-7z-compressed',
'aac' => 'audio/x-aac',
'ai' => 'application/postscript',
@ -678,6 +685,7 @@ function mimetype_from_extension($extension)
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mov' => 'video/quicktime',
'mkv' => 'video/x-matroska',
'mp3' => 'audio/mpeg',
'mp4' => 'video/mp4',
'mp4a' => 'audio/mp4',
@ -756,29 +764,53 @@ function _parse_message($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);
$message = ltrim($message, "\r\n");
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;
}
$messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
if ($messageParts === false || count($messageParts) !== 2) {
throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
}
return $result;
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,
];
}
/**
@ -807,6 +839,46 @@ function _parse_request_uri($path, array $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 null|string
*/
function get_message_body_summary(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]/', $summary)) {
return null;
}
return $summary;
}
/** @internal */
function _caseless_remove($keys, array $data)
{

View file

@ -1,186 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\AppendStream;
use GuzzleHttp\Psr7;
class AppendStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Each stream must be readable
*/
public function testValidatesStreamsAreReadable()
{
$a = new AppendStream();
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isReadable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$a->addStream($s);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
*/
public function testValidatesSeekType()
{
$a = new AppendStream();
$a->seek(100, SEEK_CUR);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
*/
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);
$a->seek(10);
}
public function testSeeksToPositionByReading()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar'),
Psr7\stream_for('baz'),
]);
$a->seek(3);
$this->assertEquals(3, $a->tell());
$this->assertEquals('bar', $a->read(3));
$a->seek(6);
$this->assertEquals(6, $a->tell());
$this->assertEquals('baz', $a->read(3));
}
public function testDetachesEachStream()
{
$s1 = Psr7\stream_for('foo');
$s2 = Psr7\stream_for('bar');
$a = new AppendStream([$s1, $s2]);
$this->assertSame('foobar', (string) $a);
$a->detach();
$this->assertSame('', (string) $a);
$this->assertSame(0, $a->getSize());
}
public function testClosesEachStream()
{
$s1 = Psr7\stream_for('foo');
$a = new AppendStream([$s1]);
$a->close();
$this->assertSame('', (string) $a);
}
/**
* @expectedExceptionMessage Cannot write to an AppendStream
* @expectedException \RuntimeException
*/
public function testIsNotWritable()
{
$a = new AppendStream([Psr7\stream_for('foo')]);
$this->assertFalse($a->isWritable());
$this->assertTrue($a->isSeekable());
$this->assertTrue($a->isReadable());
$a->write('foo');
}
public function testDoesNotNeedStreams()
{
$a = new AppendStream();
$this->assertEquals('', (string) $a);
}
public function testCanReadFromMultipleStreams()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar'),
Psr7\stream_for('baz'),
]);
$this->assertFalse($a->eof());
$this->assertSame(0, $a->tell());
$this->assertEquals('foo', $a->read(3));
$this->assertEquals('bar', $a->read(3));
$this->assertEquals('baz', $a->read(3));
$this->assertSame('', $a->read(1));
$this->assertTrue($a->eof());
$this->assertSame(9, $a->tell());
$this->assertEquals('foobarbaz', (string) $a);
}
public function testCanDetermineSizeFromMultipleStreams()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar')
]);
$this->assertEquals(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 testCanDetach()
{
$s = new AppendStream();
$s->detach();
}
public function testReturnsEmptyMetadata()
{
$s = new AppendStream();
$this->assertEquals([], $s->getMetadata());
$this->assertNull($s->getMetadata('foo'));
}
}

View file

@ -1,63 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\BufferStream;
class BufferStreamTest extends \PHPUnit_Framework_TestCase
{
public function testHasMetadata()
{
$b = new BufferStream(10);
$this->assertTrue($b->isReadable());
$this->assertTrue($b->isWritable());
$this->assertFalse($b->isSeekable());
$this->assertEquals(null, $b->getMetadata('foo'));
$this->assertEquals(10, $b->getMetadata('hwm'));
$this->assertEquals([], $b->getMetadata());
}
public function testRemovesReadDataFromBuffer()
{
$b = new BufferStream();
$this->assertEquals(3, $b->write('foo'));
$this->assertEquals(3, $b->getSize());
$this->assertFalse($b->eof());
$this->assertEquals('foo', $b->read(10));
$this->assertTrue($b->eof());
$this->assertEquals('', $b->read(10));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot determine the position of a BufferStream
*/
public function testCanCastToStringOrGetContents()
{
$b = new BufferStream();
$b->write('foo');
$b->write('baz');
$this->assertEquals('foo', $b->read(3));
$b->write('bar');
$this->assertEquals('bazbar', (string) $b);
$b->tell();
}
public function testDetachClearsBuffer()
{
$b = new BufferStream();
$b->write('foo');
$b->detach();
$this->assertTrue($b->eof());
$this->assertEquals(3, $b->write('abc'));
$this->assertEquals('abc', $b->read(10));
}
public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
{
$b = new BufferStream(5);
$this->assertEquals(3, $b->write('hi '));
$this->assertFalse($b->write('hello'));
$this->assertEquals('hi hello', (string) $b);
$this->assertEquals(4, $b->write('test'));
}
}

View file

@ -1,193 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\CachingStream;
/**
* @covers GuzzleHttp\Psr7\CachingStream
*/
class CachingStreamTest extends \PHPUnit_Framework_TestCase
{
/** @var CachingStream */
protected $body;
protected $decorated;
public function setUp()
{
$this->decorated = Psr7\stream_for('testing');
$this->body = new CachingStream($this->decorated);
}
public function tearDown()
{
$this->decorated->close();
$this->body->close();
}
public function testUsesRemoteSizeIfPossible()
{
$body = Psr7\stream_for('test');
$caching = new CachingStream($body);
$this->assertEquals(4, $caching->getSize());
}
public function testReadsUntilCachedToByte()
{
$this->body->seek(5);
$this->assertEquals('n', $this->body->read(1));
$this->body->seek(0);
$this->assertEquals('t', $this->body->read(1));
}
public function testCanSeekNearEndWithSeekEnd()
{
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(-1, SEEK_END);
$this->assertEquals(25, $baseStream->tell());
$this->assertEquals('z', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanSeekToEndWithSeekEnd()
{
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(0, SEEK_END);
$this->assertEquals(26, $baseStream->tell());
$this->assertEquals('', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanUseSeekEndWithUnknownSize()
{
$baseStream = Psr7\stream_for('testing');
$decorated = Psr7\FnStream::decorate($baseStream, [
'getSize' => function () { return null; }
]);
$cached = new CachingStream($decorated);
$cached->seek(-1, SEEK_END);
$this->assertEquals('g', $cached->read(1));
}
public function testRewindUsesSeek()
{
$a = Psr7\stream_for('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->assertEquals('te', $this->body->read(2));
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
$this->assertEquals(4, $this->body->tell());
$this->body->seek(2);
$this->assertEquals(2, $this->body->tell());
$this->body->seek(2, SEEK_CUR);
$this->assertEquals(4, $this->body->tell());
$this->assertEquals('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->assertEquals(0, $this->body->tell());
$this->body->seek(4, SEEK_SET);
$this->assertEquals(4, $this->body->tell());
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
}
public function testWritesToBufferStream()
{
$this->body->read(2);
$this->body->write('hi');
$this->body->seek(0);
$this->assertEquals('tehiing', (string) $this->body);
}
public function testSkipsOverwrittenBytes()
{
$decorated = Psr7\stream_for(
implode("\n", array_map(function ($n) {
return str_pad($n, 4, '0', STR_PAD_LEFT);
}, range(0, 25)))
);
$body = new CachingStream($decorated);
$this->assertEquals("0000\n", Psr7\readline($body));
$this->assertEquals("0001\n", Psr7\readline($body));
// Write over part of the body yet to be read, so skip some bytes
$this->assertEquals(5, $body->write("TEST\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Read, which skips bytes, then reads
$this->assertEquals("0003\n", Psr7\readline($body));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("0004\n", Psr7\readline($body));
$this->assertEquals("0005\n", Psr7\readline($body));
// Overwrite part of the cached body (so don't skip any bytes)
$body->seek(5);
$this->assertEquals(5, $body->write("ABCD\n"));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("TEST\n", Psr7\readline($body));
$this->assertEquals("0003\n", Psr7\readline($body));
$this->assertEquals("0004\n", Psr7\readline($body));
$this->assertEquals("0005\n", Psr7\readline($body));
$this->assertEquals("0006\n", Psr7\readline($body));
$this->assertEquals(5, $body->write("1234\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Seek to 0 and ensure the overwritten bit is replaced
$body->seek(0);
$this->assertEquals("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->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
}
public function testClosesBothStreams()
{
$s = fopen('php://temp', 'r');
$a = Psr7\stream_for($s);
$d = new CachingStream($a);
$d->close();
$this->assertFalse(is_resource($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresValidWhence()
{
$this->body->seek(10, -123456);
}
}

View file

@ -1,26 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\DroppingStream;
class DroppingStreamTest extends \PHPUnit_Framework_TestCase
{
public function testBeginsDroppingWhenSizeExceeded()
{
$stream = new BufferStream();
$drop = new DroppingStream($stream, 5);
$this->assertEquals(3, $drop->write('hel'));
$this->assertEquals(2, $drop->write('lo'));
$this->assertEquals(5, $drop->getSize());
$this->assertEquals('hello', $drop->read(5));
$this->assertEquals(0, $drop->getSize());
$drop->write('12345678910');
$this->assertEquals(5, $stream->getSize());
$this->assertEquals(5, $drop->getSize());
$this->assertEquals('12345', (string) $drop);
$this->assertEquals(0, $drop->getSize());
$drop->write('hello');
$this->assertSame(0, $drop->write('test'));
}
}

View file

@ -1,90 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
/**
* @covers GuzzleHttp\Psr7\FnStream
*/
class FnStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \BadMethodCallException
* @expectedExceptionMessage seek() is not implemented in the FnStream
*/
public function testThrowsWhenNotImplemented()
{
(new FnStream([]))->seek(1);
}
public function testProxiesToFunction()
{
$s = new FnStream([
'read' => function ($len) {
$this->assertEquals(3, $len);
return 'foo';
}
]);
$this->assertEquals('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);
}
public function testDecoratesStream()
{
$a = Psr7\stream_for('foo');
$b = FnStream::decorate($a, []);
$this->assertEquals(3, $b->getSize());
$this->assertEquals($b->isWritable(), true);
$this->assertEquals($b->isReadable(), true);
$this->assertEquals($b->isSeekable(), true);
$this->assertEquals($b->read(3), 'foo');
$this->assertEquals($b->tell(), 3);
$this->assertEquals($a->tell(), 3);
$this->assertSame('', $a->read(1));
$this->assertEquals($b->eof(), true);
$this->assertEquals($a->eof(), true);
$b->seek(0);
$this->assertEquals('foo', (string) $b);
$b->seek(0);
$this->assertEquals('foo', $b->getContents());
$this->assertEquals($a->getMetadata(), $b->getMetadata());
$b->seek(0, SEEK_END);
$b->write('bar');
$this->assertEquals('foobar', (string) $b);
$this->assertInternalType('resource', $b->detach());
$b->close();
}
public function testDecoratesWithCustomizations()
{
$called = false;
$a = Psr7\stream_for('foo');
$b = FnStream::decorate($a, [
'read' => function ($len) use (&$called, $a) {
$called = true;
return $a->read($len);
}
]);
$this->assertEquals('foo', $b->read(3));
$this->assertTrue($called);
}
}

View file

@ -1,619 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\NoSeekStream;
class FunctionsTest extends \PHPUnit_Framework_TestCase
{
public function testCopiesToString()
{
$s = Psr7\stream_for('foobaz');
$this->assertEquals('foobaz', Psr7\copy_to_string($s));
$s->seek(0);
$this->assertEquals('foo', Psr7\copy_to_string($s, 3));
$this->assertEquals('baz', Psr7\copy_to_string($s, 3));
$this->assertEquals('', Psr7\copy_to_string($s));
}
public function testCopiesToStringStopsWhenReadFails()
{
$s1 = Psr7\stream_for('foobaz');
$s1 = FnStream::decorate($s1, [
'read' => function () { return ''; }
]);
$result = Psr7\copy_to_string($s1);
$this->assertEquals('', $result);
}
public function testCopiesToStream()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
Psr7\copy_to_stream($s1, $s2);
$this->assertEquals('foobaz', (string) $s2);
$s2 = Psr7\stream_for('');
$s1->seek(0);
Psr7\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foo', (string) $s2);
Psr7\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foobaz', (string) $s2);
}
public function testStopsCopyToStreamWhenWriteFails()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
$s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
Psr7\copy_to_stream($s1, $s2);
$this->assertEquals('', (string) $s2);
}
public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
$s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
Psr7\copy_to_stream($s1, $s2, 10);
$this->assertEquals('', (string) $s2);
}
public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
{
$s1 = Psr7\stream_for('foobaz');
$s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]);
$s2 = Psr7\stream_for('');
Psr7\copy_to_stream($s1, $s2, 10);
$this->assertEquals('', (string) $s2);
}
public function testReadsLines()
{
$s = Psr7\stream_for("foo\nbaz\nbar");
$this->assertEquals("foo\n", Psr7\readline($s));
$this->assertEquals("baz\n", Psr7\readline($s));
$this->assertEquals("bar", Psr7\readline($s));
}
public function testReadsLinesUpToMaxLength()
{
$s = Psr7\stream_for("12345\n");
$this->assertEquals("123", Psr7\readline($s, 4));
$this->assertEquals("45\n", Psr7\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->assertEquals("h", Psr7\readline($s));
}
public function testCalculatesHash()
{
$s = Psr7\stream_for('foobazbar');
$this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
}
/**
* @expectedException \RuntimeException
*/
public function testCalculatesHashThrowsWhenSeekFails()
{
$s = new NoSeekStream(Psr7\stream_for('foobazbar'));
$s->read(2);
Psr7\hash($s, 'md5');
}
public function testCalculatesHashSeeksToOriginalPosition()
{
$s = Psr7\stream_for('foobazbar');
$s->seek(4);
$this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
$this->assertEquals(4, $s->tell());
}
public function testOpensFilesSuccessfully()
{
$r = Psr7\try_fopen(__FILE__, 'r');
$this->assertInternalType('resource', $r);
fclose($r);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
*/
public function testThrowsExceptionNotWarning()
{
Psr7\try_fopen('/path/to/does/not/exist', 'r');
}
public function parseQueryProvider()
{
return [
// Does not need to parse when the string is empty
['', []],
// Can parse mult-values items
['q=a&q=b', ['q' => ['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\parse_query($input);
$this->assertSame($output, $result);
}
public function testDoesNotDecode()
{
$str = 'foo%20=bar';
$data = Psr7\parse_query($str, false);
$this->assertEquals(['foo%20' => 'bar'], $data);
}
/**
* @dataProvider parseQueryProvider
*/
public function testParsesAndBuildsQueries($input, $output)
{
$result = Psr7\parse_query($input, false);
$this->assertSame($input, Psr7\build_query($result, false));
}
public function testEncodesWithRfc1738()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC1738);
$this->assertEquals('foo+bar=baz%2B', $str);
}
public function testEncodesWithRfc3986()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC3986);
$this->assertEquals('foo%20bar=baz%2B', $str);
}
public function testDoesNotEncode()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], false);
$this->assertEquals('foo bar=baz+', $str);
}
public function testCanControlDecodingType()
{
$result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
$this->assertEquals('foo+bar', $result['var']);
$result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
$this->assertEquals('foo bar', $result['var']);
}
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\parse_request($req);
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('/abc', $request->getRequestTarget());
$this->assertEquals('1.0', $request->getProtocolVersion());
$this->assertEquals('foo.com', $request->getHeaderLine('Host'));
$this->assertEquals('Bar', $request->getHeaderLine('Foo'));
$this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
$this->assertEquals('Test', (string) $request->getBody());
$this->assertEquals('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\parse_request($req);
$this->assertEquals('PUT', $request->getMethod());
$this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
$this->assertEquals('', (string) $request->getBody());
$this->assertEquals('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\parse_request($req);
$this->assertEquals('PUT', $request->getMethod());
$this->assertEquals('/', $request->getRequestTarget());
$this->assertEquals('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\parse_request($req);
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
$this->assertEquals('', (string) $request->getBody());
$this->assertEquals('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\parse_request($req);
$this->assertEquals('GET_DATA', $request->getMethod());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesRequestMessages()
{
Psr7\parse_request("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\parse_response($res);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('OK', $response->getReasonPhrase());
$this->assertEquals('1.0', $response->getProtocolVersion());
$this->assertEquals('Bar', $response->getHeaderLine('Foo'));
$this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
$this->assertEquals('Test', (string) $response->getBody());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesResponseMessages()
{
Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
}
public function testDetermineMimetype()
{
$this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
$this->assertEquals(
'application/json',
Psr7\mimetype_from_extension('json')
);
$this->assertEquals(
'image/jpeg',
Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
);
}
public function testCreatesUriForValue()
{
$this->assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\uri_for('/foo'));
$this->assertInstanceOf(
'GuzzleHttp\Psr7\Uri',
Psr7\uri_for(new Psr7\Uri('/foo'))
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesUri()
{
Psr7\uri_for([]);
}
public function testKeepsPositionOfResource()
{
$h = fopen(__FILE__, 'r');
fseek($h, 10);
$stream = Psr7\stream_for($h);
$this->assertEquals(10, $stream->tell());
$stream->close();
}
public function testCreatesWithFactory()
{
$stream = Psr7\stream_for('foo');
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream);
$this->assertEquals('foo', $stream->getContents());
$stream->close();
}
public function testFactoryCreatesFromEmptyString()
{
$s = Psr7\stream_for();
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
}
public function testFactoryCreatesFromNull()
{
$s = Psr7\stream_for(null);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
}
public function testFactoryCreatesFromResource()
{
$r = fopen(__FILE__, 'r');
$s = Psr7\stream_for($r);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
$this->assertSame(file_get_contents(__FILE__), (string) $s);
}
public function testFactoryCreatesFromObjectWithToString()
{
$r = new HasToString();
$s = Psr7\stream_for($r);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
$this->assertEquals('foo', (string) $s);
}
public function testCreatePassesThrough()
{
$s = Psr7\stream_for('foo');
$this->assertSame($s, Psr7\stream_for($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionForUnknown()
{
Psr7\stream_for(new \stdClass());
}
public function testReturnsCustomMetadata()
{
$s = Psr7\stream_for('foo', ['metadata' => ['hwm' => 3]]);
$this->assertEquals(3, $s->getMetadata('hwm'));
$this->assertArrayHasKey('hwm', $s->getMetadata());
}
public function testCanSetSize()
{
$s = Psr7\stream_for('', ['size' => 10]);
$this->assertEquals(10, $s->getSize());
}
public function testCanCreateIteratorBasedStream()
{
$a = new \ArrayIterator(['foo', 'bar', '123']);
$p = Psr7\stream_for($a);
$this->assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p);
$this->assertEquals('foo', $p->read(3));
$this->assertFalse($p->eof());
$this->assertEquals('b', $p->read(1));
$this->assertEquals('a', $p->read(1));
$this->assertEquals('r12', $p->read(3));
$this->assertFalse($p->eof());
$this->assertEquals('3', $p->getContents());
$this->assertTrue($p->eof());
$this->assertEquals(9, $p->tell());
}
public function testConvertsRequestsToStrings()
{
$request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [
'Baz' => 'bar',
'Qux' => 'ipsum'
], 'hello', '1.0');
$this->assertEquals(
"PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
Psr7\str($request)
);
}
public function testConvertsResponsesToStrings()
{
$response = new Psr7\Response(200, [
'Baz' => 'bar',
'Qux' => 'ipsum'
], 'hello', '1.0', 'FOO');
$this->assertEquals(
"HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
Psr7\str($response)
);
}
public function parseParamsProvider()
{
$res1 = array(
array(
'<http:/.../front.jpeg>',
'rel' => 'front',
'type' => 'image/jpeg',
),
array(
'<http://.../back.jpeg>',
'rel' => 'back',
'type' => 'image/jpeg',
),
);
return array(
array(
'<http:/.../front.jpeg>; rel="front"; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"',
$res1
),
array(
'<http:/.../front.jpeg>; rel="front"; type="image/jpeg",<http://.../back.jpeg>; rel=back; type="image/jpeg"',
$res1
),
array(
'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
array(
array('foo' => 'baz', 'bar' => '123'),
array('boo'),
array('test' => '123'),
array('foobar' => 'foo;bar')
)
),
array(
'<http://.../side.jpeg?test=1>; rel="side"; type="image/jpeg",<http://.../side.jpeg?test=2>; rel=side; type="image/jpeg"',
array(
array('<http://.../side.jpeg?test=1>', 'rel' => 'side', 'type' => 'image/jpeg'),
array('<http://.../side.jpeg?test=2>', 'rel' => 'side', 'type' => 'image/jpeg')
)
),
array(
'',
array()
)
);
}
/**
* @dataProvider parseParamsProvider
*/
public function testParseParams($header, $result)
{
$this->assertEquals($result, Psr7\parse_header($header));
}
public function testParsesArrayHeaders()
{
$header = ['a, b', 'c', 'd, e'];
$this->assertEquals(['a', 'b', 'c', 'd', 'e'], Psr7\normalize_header($header));
}
public function testRewindsBody()
{
$body = Psr7\stream_for('abc');
$res = new Psr7\Response(200, [], $body);
Psr7\rewind_body($res);
$this->assertEquals(0, $body->tell());
$body->rewind(1);
Psr7\rewind_body($res);
$this->assertEquals(0, $body->tell());
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsWhenBodyCannotBeRewound()
{
$body = Psr7\stream_for('abc');
$body->read(1);
$body = FnStream::decorate($body, [
'rewind' => function () { throw new \RuntimeException('a'); }
]);
$res = new Psr7\Response(200, [], $body);
Psr7\rewind_body($res);
}
public function testCanModifyRequestWithUri()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, [
'uri' => new Psr7\Uri('http://www.foo.com')
]);
$this->assertEquals('http://www.foo.com', (string) $r2->getUri());
$this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithUriAndPort()
{
$r1 = new Psr7\Request('GET', 'http://foo.com:8000');
$r2 = Psr7\modify_request($r1, [
'uri' => new Psr7\Uri('http://www.foo.com:8000')
]);
$this->assertEquals('http://www.foo.com:8000', (string) $r2->getUri());
$this->assertEquals('www.foo.com:8000', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithCaseInsensitiveHeader()
{
$r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']);
$r2 = Psr7\modify_request($r1, ['set_headers' => ['User-agent' => 'bar']]);
$this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
$this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
}
public function testReturnsAsIsWhenNoChanges()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, []);
$this->assertTrue($r2 instanceof Psr7\Request);
$r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, []);
$this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
}
public function testReturnsUriAsIsWhenNoChanges()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['set_headers' => ['foo' => 'bar']]);
$this->assertNotSame($r1, $r2);
$this->assertEquals('bar', $r2->getHeaderLine('foo'));
}
public function testRemovesHeadersFromMessage()
{
$r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']);
$r2 = Psr7\modify_request($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\modify_request($r1, ['query' => 'foo=bar']);
$this->assertNotSame($r1, $r2);
$this->assertEquals('foo=bar', $r2->getUri()->getQuery());
}
public function testModifyRequestKeepInstanceOfRequest()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
$this->assertTrue($r2 instanceof Psr7\Request);
$r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
$this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\InflateStream;
class InflateStreamtest extends \PHPUnit_Framework_TestCase
{
public function testInflatesStreams()
{
$content = gzencode('test');
$a = Psr7\stream_for($content);
$b = new InflateStream($a);
$this->assertEquals('test', (string) $b);
}
public function testInflatesStreamsWithFilename()
{
$content = $this->getGzipStringWithFilename('test');
$a = Psr7\stream_for($content);
$b = new InflateStream($a);
$this->assertEquals('test', (string) $b);
}
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);
}
}

View file

@ -1,64 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
class LazyOpenStreamTest extends \PHPUnit_Framework_TestCase
{
private $fname;
public function setup()
{
$this->fname = tempnam('/tmp', 'tfile');
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function tearDown()
{
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function testOpensLazily()
{
$l = new LazyOpenStream($this->fname, 'w+');
$l->write('foo');
$this->assertInternalType('array', $l->getMetadata());
$this->assertFileExists($this->fname);
$this->assertEquals('foo', file_get_contents($this->fname));
$this->assertEquals('foo', (string) $l);
}
public function testProxiesToFile()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$this->assertEquals('foo', $l->read(4));
$this->assertTrue($l->eof());
$this->assertEquals(3, $l->tell());
$this->assertTrue($l->isReadable());
$this->assertTrue($l->isSeekable());
$this->assertFalse($l->isWritable());
$l->seek(1);
$this->assertEquals('oo', $l->getContents());
$this->assertEquals('foo', (string) $l);
$this->assertEquals(3, $l->getSize());
$this->assertInternalType('array', $l->getMetadata());
$l->close();
}
public function testDetachesUnderlyingStream()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$r = $l->detach();
$this->assertInternalType('resource', $r);
fseek($r, 0);
$this->assertEquals('foo', stream_get_contents($r));
fclose($r);
}
}

View file

@ -1,166 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\NoSeekStream;
/**
* @covers GuzzleHttp\Psr7\LimitStream
*/
class LimitStreamTest extends \PHPUnit_Framework_TestCase
{
/** @var LimitStream */
protected $body;
/** @var Stream */
protected $decorated;
public function setUp()
{
$this->decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
$this->body = new LimitStream($this->decorated, 10, 3);
}
public function testReturnsSubset()
{
$body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
$this->assertEquals('oo', (string) $body);
$this->assertTrue($body->eof());
$body->seek(0);
$this->assertFalse($body->eof());
$this->assertEquals('oo', $body->read(100));
$this->assertSame('', $body->read(1));
$this->assertTrue($body->eof());
}
public function testReturnsSubsetWhenCastToString()
{
$body = Psr7\stream_for('foo_baz_bar');
$limited = new LimitStream($body, 3, 4);
$this->assertEquals('baz', (string) $limited);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
*/
public function testEnsuresPositionCanBeekSeekedTo()
{
new LimitStream(Psr7\stream_for(''), 0, 10);
}
public function testReturnsSubsetOfEmptyBodyWhenCastToString()
{
$body = Psr7\stream_for('01234567891234');
$limited = new LimitStream($body, 0, 10);
$this->assertEquals('', (string) $limited);
}
public function testReturnsSpecificSubsetOBodyWhenCastToString()
{
$body = Psr7\stream_for('0123456789abcdef');
$limited = new LimitStream($body, 3, 10);
$this->assertEquals('abc', (string) $limited);
}
public function testSeeksWhenConstructed()
{
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
}
public function testAllowsBoundedSeek()
{
$this->body->seek(100);
$this->assertEquals(10, $this->body->tell());
$this->assertEquals(13, $this->decorated->tell());
$this->body->seek(0);
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
try {
$this->body->seek(-10);
$this->fail();
} catch (\RuntimeException $e) {}
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
$this->body->seek(5);
$this->assertEquals(5, $this->body->tell());
$this->assertEquals(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->assertEquals(10, strlen($data));
$this->assertSame('', $this->body->read(1000));
$this->body->setOffset(10);
$newData = $this->body->read(100);
$this->assertEquals(10, strlen($newData));
$this->assertNotSame($data, $newData);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Could not seek to stream offset 2
*/
public function testThrowsWhenCurrentGreaterThanOffsetSeek()
{
$a = Psr7\stream_for('foo_bar');
$b = new NoSeekStream($a);
$c = new LimitStream($b);
$a->getContents();
$c->setOffset(2);
}
public function testCanGetContentsWithoutSeeking()
{
$a = Psr7\stream_for('foo_bar');
$b = new NoSeekStream($a);
$c = new LimitStream($b);
$this->assertEquals('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->assertEquals(10, $this->body->getSize());
}
public function testGetContentsIsBasedOnSubset()
{
$body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
$this->assertEquals('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\stream_for('foo_bar');
$b = new LimitStream($a, -1, 4);
$this->assertEquals(3, $b->getSize());
}
}

View file

@ -1,242 +0,0 @@
<?php
namespace GuzzleHttp\Tests;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\MultipartStream;
class MultipartStreamTest extends \PHPUnit_Framework_TestCase
{
public function testCreatesDefaultBoundary()
{
$b = new MultipartStream();
$this->assertNotEmpty($b->getBoundary());
}
public function testCanProvideBoundary()
{
$b = new MultipartStream([], 'foo');
$this->assertEquals('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());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesFilesArrayElement()
{
new MultipartStream([['foo' => 'bar']]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresFileHasName()
{
new MultipartStream([['contents' => 'bar']]);
}
public function testSerializesFields()
{
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => 'bar'
],
[
'name' => 'baz',
'contents' => 'bam'
]
], 'boundary');
$this->assertEquals(
"--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->assertEquals(
"--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\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
'getMetadata' => function () {
return '/foo/baz.jpg';
}
]);
$f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), [
'getMetadata' => function () {
return '/foo/bar.gif';
}
]);
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => $f1
],
[
'name' => 'qux',
'contents' => $f2
],
[
'name' => 'qux',
'contents' => $f3
],
], 'boundary');
$expected = <<<EOT
--boundary
Content-Disposition: form-data; name="foo"; filename="bar.txt"
Content-Length: 3
Content-Type: text/plain
foo
--boundary
Content-Disposition: form-data; name="qux"; filename="baz.jpg"
Content-Length: 3
Content-Type: image/jpeg
baz
--boundary
Content-Disposition: form-data; name="qux"; filename="bar.gif"
Content-Length: 3
Content-Type: image/gif
bar
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
public function testSerializesFilesWithCustomHeaders()
{
$f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => $f1,
'headers' => [
'x-foo' => 'bar',
'content-disposition' => 'custom'
]
]
], 'boundary');
$expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain
foo
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
public function testSerializesFilesWithCustomHeadersAndMultipleValues()
{
$f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$f2 = Psr7\FnStream::decorate(Psr7\stream_for('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 = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain
foo
--boundary
cOntenT-Type: custom
Content-Disposition: form-data; name="foo"; filename="baz.jpg"
Content-Length: 3
baz
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;
/**
* @covers GuzzleHttp\Psr7\NoSeekStream
* @covers GuzzleHttp\Psr7\StreamDecoratorTrait
*/
class NoSeekStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot seek a NoSeekStream
*/
public function testCannotSeek()
{
$s = $this->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());
$wrapped->seek(2);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot write to a non-writable stream
*/
public function testHandlesClose()
{
$s = Psr7\stream_for('foo');
$wrapped = new NoSeekStream($s);
$wrapped->close();
$wrapped->write('foo');
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\PumpStream;
use GuzzleHttp\Psr7;
class PumpStreamTest extends \PHPUnit_Framework_TestCase
{
public function testHasMetadataAndSize()
{
$p = new PumpStream(function () {}, [
'metadata' => ['foo' => 'bar'],
'size' => 100
]);
$this->assertEquals('bar', $p->getMetadata('foo'));
$this->assertEquals(['foo' => 'bar'], $p->getMetadata());
$this->assertEquals(100, $p->getSize());
}
public function testCanReadFromCallable()
{
$p = Psr7\stream_for(function ($size) {
return 'a';
});
$this->assertEquals('a', $p->read(1));
$this->assertEquals(1, $p->tell());
$this->assertEquals('aaaaa', $p->read(5));
$this->assertEquals(6, $p->tell());
}
public function testStoresExcessDataInBuffer()
{
$called = [];
$p = Psr7\stream_for(function ($size) use (&$called) {
$called[] = $size;
return 'abcdef';
});
$this->assertEquals('a', $p->read(1));
$this->assertEquals('b', $p->read(1));
$this->assertEquals('cdef', $p->read(4));
$this->assertEquals('abcdefabc', $p->read(9));
$this->assertEquals([1, 9, 3], $called);
}
public function testInifiniteStreamWrappedInLimitStream()
{
$p = Psr7\stream_for(function () { return 'a'; });
$s = new LimitStream($p, 5);
$this->assertEquals('aaaaa', (string) $s);
}
public function testDescribesCapabilities()
{
$p = Psr7\stream_for(function () {});
$this->assertTrue($p->isReadable());
$this->assertFalse($p->isSeekable());
$this->assertFalse($p->isWritable());
$this->assertNull($p->getSize());
$this->assertEquals('', $p->getContents());
$this->assertEquals('', (string) $p);
$p->close();
$this->assertEquals('', $p->read(10));
$this->assertTrue($p->eof());
try {
$this->assertFalse($p->write('aa'));
$this->fail();
} catch (\RuntimeException $e) {}
}
}

View file

@ -1,195 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\Request
*/
class RequestTest extends \PHPUnit_Framework_TestCase
{
public function testRequestUriMayBeString()
{
$r = new Request('GET', '/');
$this->assertEquals('/', (string) $r->getUri());
}
public function testRequestUriMayBeUri()
{
$uri = new Uri('/');
$r = new Request('GET', $uri);
$this->assertSame($uri, $r->getUri());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidateRequestUri()
{
new Request('GET', '///');
}
public function testCanConstructWithBody()
{
$r = new Request('GET', '/', [], 'baz');
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertEquals('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\stream_for(''), [
'__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->assertEquals('GET', $r->getMethod());
}
public function testCapitalizesWithMethod()
{
$r = new Request('GET', '/');
$this->assertEquals('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());
}
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->assertEquals('*', $r2->getRequestTarget());
$this->assertEquals('/', $r1->getRequestTarget());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testRequestTargetDoesNotAllowSpaces()
{
$r1 = new Request('GET', '/');
$r1->withRequestTarget('/foo bar');
}
public function testRequestTargetDefaultsToSlash()
{
$r1 = new Request('GET', '');
$this->assertEquals('/', $r1->getRequestTarget());
$r2 = new Request('GET', '*');
$this->assertEquals('*', $r2->getRequestTarget());
$r3 = new Request('GET', 'http://foo.com/bar baz/');
$this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
}
public function testBuildsRequestTarget()
{
$r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
$this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
}
public function testBuildsRequestTargetWithFalseyQuery()
{
$r1 = new Request('GET', 'http://foo.com/baz?0');
$this->assertEquals('/baz?0', $r1->getRequestTarget());
}
public function testHostIsAddedFirst()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']);
$this->assertEquals([
'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->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
$this->assertEquals('', $r->getHeaderLine('Bar'));
}
public function testHostIsNotOverwrittenWhenPreservingHost()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']);
$this->assertEquals(['Host' => ['a.com']], $r->getHeaders());
$r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
$this->assertEquals('a.com', $r2->getHeaderLine('Host'));
}
public function testOverridesHostWithUri()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam');
$this->assertEquals(['Host' => ['foo.com']], $r->getHeaders());
$r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
$this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
}
public function testAggregatesHeaders()
{
$r = new Request('GET', '', [
'ZOO' => 'zoobar',
'zoo' => ['foobar', 'zoobar']
]);
$this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders());
$this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
}
public function testAddsPortToHeader()
{
$r = new Request('GET', 'http://foo.com:8124/bar');
$this->assertEquals('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->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
}
}

View file

@ -1,252 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Response;
/**
* @covers GuzzleHttp\Psr7\MessageTrait
* @covers GuzzleHttp\Psr7\Response
*/
class ResponseTest extends \PHPUnit_Framework_TestCase
{
public function testDefaultConstructor()
{
$r = new Response();
$this->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\stream_for(''), [
'__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\stream_for('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 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 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'));
}
}
}

View file

@ -1,532 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\ServerRequest
*/
class ServerRequestTest extends \PHPUnit_Framework_TestCase
{
public function dataNormalizeFiles()
{
return [
'Single file' => [
[
'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->setExpectedException('InvalidArgumentException', 'Invalid value in files specification');
ServerRequest::normalizeFiles(['test' => 'something']);
}
public function dataGetUriFromGlobals()
{
$server = [
'PHP_SELF' => '/blog/article.php',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_ADDR' => 'Server IP: 217.112.82.20',
'SERVER_NAME' => 'www.blakesimpson.co.uk',
'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
'SERVER_PROTOCOL' => 'HTTP/1.0',
'REQUEST_METHOD' => 'POST',
'REQUEST_TIME' => 'Request start time: 1280149029',
'QUERY_STRING' => 'id=10&user=foo',
'DOCUMENT_ROOT' => '/path/to/your/server/root/',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
'HTTP_CONNECTION' => 'keep-alive',
'HTTP_HOST' => 'www.blakesimpson.co.uk',
'HTTP_REFERER' => 'http://previous.url.com',
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
'HTTPS' => '1',
'REMOTE_ADDR' => '193.60.168.69',
'REMOTE_HOST' => 'Client server\'s host name',
'REMOTE_PORT' => '5390',
'SCRIPT_FILENAME' => '/path/to/this/script.php',
'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
'SERVER_PORT' => '80',
'SERVER_SIGNATURE' => 'Version signature: 5.123',
'SCRIPT_NAME' => '/blog/article.php',
'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
];
return [
'Normal request' => [
'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
$server,
],
'Secure request' => [
'https://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']),
],
'HTTP_HOST missing' => [
'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
array_merge($server, ['HTTP_HOST' => null]),
],
'No query String' => [
'http://www.blakesimpson.co.uk/blog/article.php',
array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']),
],
'Different port' => [
'http://www.blakesimpson.co.uk:8324/blog/article.php?id=10&user=foo',
array_merge($server, ['SERVER_PORT' => '8324']),
],
'Empty server variable' => [
'',
[],
],
];
}
/**
* @dataProvider dataGetUriFromGlobals
*/
public function testGetUriFromGlobals($expected, $serverParams)
{
$_SERVER = $serverParams;
$this->assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals());
}
public function testFromGlobals()
{
$_SERVER = [
'PHP_SELF' => '/blog/article.php',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_ADDR' => 'Server IP: 217.112.82.20',
'SERVER_NAME' => 'www.blakesimpson.co.uk',
'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
'SERVER_PROTOCOL' => 'HTTP/1.0',
'REQUEST_METHOD' => 'POST',
'REQUEST_TIME' => 'Request start time: 1280149029',
'QUERY_STRING' => 'id=10&user=foo',
'DOCUMENT_ROOT' => '/path/to/your/server/root/',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
'HTTP_CONNECTION' => 'keep-alive',
'HTTP_HOST' => 'www.blakesimpson.co.uk',
'HTTP_REFERER' => 'http://previous.url.com',
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
'HTTPS' => '1',
'REMOTE_ADDR' => '193.60.168.69',
'REMOTE_HOST' => 'Client server\'s host name',
'REMOTE_PORT' => '5390',
'SCRIPT_FILENAME' => '/path/to/this/script.php',
'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
'SERVER_PORT' => '80',
'SERVER_SIGNATURE' => 'Version signature: 5.123',
'SCRIPT_NAME' => '/blog/article.php',
'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
];
$_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->assertEquals('POST', $server->getMethod());
$this->assertEquals(['Host' => ['www.blakesimpson.co.uk']], $server->getHeaders());
$this->assertEquals('', (string) $server->getBody());
$this->assertEquals('1.0', $server->getProtocolVersion());
$this->assertEquals($_COOKIE, $server->getCookieParams());
$this->assertEquals($_POST, $server->getParsedBody());
$this->assertEquals($_GET, $server->getQueryParams());
$this->assertEquals(
new Uri('http://www.blakesimpson.co.uk/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->assertNotSame($request5, $request4);
$this->assertEmpty($request1->getAttributes());
$this->assertEmpty($request1->getAttribute('name'));
$this->assertEquals(
'something',
$request1->getAttribute('name', 'something'),
'Should return the default value'
);
$this->assertEquals('value', $request2->getAttribute('name'));
$this->assertEquals(['name' => 'value'], $request2->getAttributes());
$this->assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes());
$this->assertEquals(['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'));
}
}

View file

@ -1,137 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;
class Str implements StreamInterface
{
use StreamDecoratorTrait;
}
/**
* @covers GuzzleHttp\Psr7\StreamDecoratorTrait
*/
class StreamDecoratorTraitTest extends \PHPUnit_Framework_TestCase
{
private $a;
private $b;
private $c;
public function setUp()
{
$this->c = fopen('php://temp', 'r+');
fwrite($this->c, 'foo');
fseek($this->c, 0);
$this->a = Psr7\stream_for($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->assertContains('foo', $msg);
}
public function testToString()
{
$this->assertEquals('foo', (string) $this->b);
}
public function testHasSize()
{
$this->assertEquals(3, $this->b->getSize());
}
public function testReads()
{
$this->assertEquals('foo', $this->b->read(10));
}
public function testCheckMethods()
{
$this->assertEquals($this->a->isReadable(), $this->b->isReadable());
$this->assertEquals($this->a->isWritable(), $this->b->isWritable());
$this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
}
public function testSeeksAndTells()
{
$this->b->seek(1);
$this->assertEquals(1, $this->a->tell());
$this->assertEquals(1, $this->b->tell());
$this->b->seek(0);
$this->assertEquals(0, $this->a->tell());
$this->assertEquals(0, $this->b->tell());
$this->b->seek(0, SEEK_END);
$this->assertEquals(3, $this->a->tell());
$this->assertEquals(3, $this->b->tell());
}
public function testGetsContents()
{
$this->assertEquals('foo', $this->b->getContents());
$this->assertEquals('', $this->b->getContents());
$this->b->seek(1);
$this->assertEquals('oo', $this->b->getContents(1));
}
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->assertEquals('foofoo', (string) $this->a);
}
/**
* @expectedException \UnexpectedValueException
*/
public function testThrowsWithInvalidGetter()
{
$this->b->foo;
}
/**
* @expectedException \BadMethodCallException
*/
public function testThrowsWhenGetterNotImplemented()
{
$s = new BadStream();
$s->stream;
}
}
class BadStream
{
use StreamDecoratorTrait;
public function __construct() {}
}

View file

@ -1,161 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;
use GuzzleHttp\Psr7\Stream;
/**
* @covers GuzzleHttp\Psr7\Stream
*/
class StreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnInvalidArgument()
{
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->assertEquals('php://temp', $stream->getMetadata('uri'));
$this->assertInternalType('array', $stream->getMetadata());
$this->assertEquals(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->assertEquals('data', (string) $stream);
$this->assertEquals('data', (string) $stream);
$stream->close();
}
public function testGetsContents()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertEquals('', $stream->getContents());
$stream->seek(0);
$this->assertEquals('data', $stream->getContents());
$this->assertEquals('', $stream->getContents());
}
public function testChecksEof()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertFalse($stream->eof());
$stream->read(4);
$this->assertTrue($stream->eof());
$stream->close();
}
public function testGetSize()
{
$size = filesize(__FILE__);
$handle = fopen(__FILE__, 'r');
$stream = new Stream($handle);
$this->assertEquals($size, $stream->getSize());
// Load from cache
$this->assertEquals($size, $stream->getSize());
$stream->close();
}
public function testEnsuresSizeIsConsistent()
{
$h = fopen('php://temp', 'w+');
$this->assertEquals(3, fwrite($h, 'foo'));
$stream = new Stream($h);
$this->assertEquals(3, $stream->getSize());
$this->assertEquals(4, $stream->write('test'));
$this->assertEquals(7, $stream->getSize());
$this->assertEquals(7, $stream->getSize());
$stream->close();
}
public function testProvidesStreamPosition()
{
$handle = fopen('php://temp', 'w+');
$stream = new Stream($handle);
$this->assertEquals(0, $stream->tell());
$stream->write('foo');
$this->assertEquals(3, $stream->tell());
$stream->seek(1);
$this->assertEquals(1, $stream->tell());
$this->assertSame(ftell($handle), $stream->tell());
$stream->close();
}
public function testCanDetachStream()
{
$r = fopen('php://temp', 'w+');
$stream = new Stream($r);
$stream->write('foo');
$this->assertTrue($stream->isReadable());
$this->assertSame($r, $stream->detach());
$stream->detach();
$this->assertFalse($stream->isReadable());
$this->assertFalse($stream->isWritable());
$this->assertFalse($stream->isSeekable());
$throws = function (callable $fn) use ($stream) {
try {
$fn($stream);
$this->fail();
} catch (\Exception $e) {}
};
$throws(function ($stream) { $stream->read(10); });
$throws(function ($stream) { $stream->write('bar'); });
$throws(function ($stream) { $stream->seek(10); });
$throws(function ($stream) { $stream->tell(); });
$throws(function ($stream) { $stream->eof(); });
$throws(function ($stream) { $stream->getSize(); });
$throws(function ($stream) { $stream->getContents(); });
$this->assertSame('', (string) $stream);
$stream->close();
}
public function testCloseClearProperties()
{
$handle = fopen('php://temp', 'r+');
$stream = new Stream($handle);
$stream->close();
$this->assertFalse($stream->isSeekable());
$this->assertFalse($stream->isReadable());
$this->assertFalse($stream->isWritable());
$this->assertNull($stream->getSize());
$this->assertEmpty($stream->getMetadata());
}
public function testDoesNotThrowInToString()
{
$s = \GuzzleHttp\Psr7\stream_for('foo');
$s = new NoSeekStream($s);
$this->assertEquals('foo', (string) $s);
}
}

View file

@ -1,102 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\StreamWrapper;
use GuzzleHttp\Psr7;
/**
* @covers GuzzleHttp\Psr7\StreamWrapper
*/
class StreamWrapperTest extends \PHPUnit_Framework_TestCase
{
public function testResource()
{
$stream = Psr7\stream_for('foo');
$handle = StreamWrapper::getResource($stream);
$this->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->assertEquals([
'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,
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,
], fstat($handle));
}
$this->assertTrue(fclose($handle));
$this->assertSame('foobar', (string) $stream);
}
/**
* @expectedException \InvalidArgumentException
*/
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));
StreamWrapper::getResource($stream);
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testReturnsFalseWhenStreamDoesNotExist()
{
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->assertInternalType('resource', $r);
fclose($r);
}
}

View file

@ -1,280 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use ReflectionProperty;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\UploadedFile;
/**
* @covers GuzzleHttp\Psr7\UploadedFile
*/
class UploadedFileTest extends \PHPUnit_Framework_TestCase
{
protected $cleanup;
public function setUp()
{
$this->cleanup = [];
}
public function tearDown()
{
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->setExpectedException('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->setExpectedException('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->setExpectedException('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->setExpectedException('InvalidArgumentException', 'filename');
new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename);
}
/**
* @dataProvider invalidFilenamesAndMediaTypes
*/
public function testRaisesExceptionOnInvalidClientMediaType($mediaType)
{
$this->setExpectedException('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\stream_for('Foo bar!');
$upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain');
$this->assertEquals($stream->getSize(), $upload->getSize());
$this->assertEquals('filename.txt', $upload->getClientFilename());
$this->assertEquals('text/plain', $upload->getClientMediaType());
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful');
$upload->moveTo($to);
$this->assertFileExists($to);
$this->assertEquals($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\stream_for('Foo bar!');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->cleanup[] = $path;
$this->setExpectedException('InvalidArgumentException', 'path');
$upload->moveTo($path);
}
public function testMoveCannotBeCalledMoreThanOnce()
{
$stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
$upload->moveTo($to);
$this->assertTrue(file_exists($to));
$this->setExpectedException('RuntimeException', 'moved');
$upload->moveTo($to);
}
public function testCannotRetrieveStreamAfterMove()
{
$stream = \GuzzleHttp\Psr7\stream_for('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->setExpectedException('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->setExpectedException('RuntimeException', 'upload error');
$uploadedFile->moveTo(__DIR__ . '/' . uniqid());
}
/**
* @dataProvider nonOkErrorStatus
*/
public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status)
{
$uploadedFile = new UploadedFile('not ok', 0, $status);
$this->setExpectedException('RuntimeException', 'upload error');
$stream = $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);
}
}

View file

@ -1,573 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\Uri
*/
class UriTest extends \PHPUnit_Framework_TestCase
{
const RFC3986_BASE = 'http://a/b/c/d;p?q';
public function testParsesProvidedUri()
{
$uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#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);
}
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'],
];
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to parse URI
* @dataProvider getInvalidUris
*/
public function testInvalidUrisThrowException($invalidUri)
{
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 ":"
];
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535
*/
public function testPortMustBeValid()
{
(new Uri())->withPort(100000);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535
*/
public function testWithPortCannotBeZero()
{
(new Uri())->withPort(0);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to parse URI
*/
public function testParseUriPortCannotBeZero()
{
new Uri('//example.com:0');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testSchemeMustHaveCorrectType()
{
(new Uri())->withScheme([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testHostMustHaveCorrectType()
{
(new Uri())->withHost([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testPathMustHaveCorrectType()
{
(new Uri())->withPath([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testQueryMustHaveCorrectType()
{
(new Uri())->withQuery([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFragmentMustHaveCorrectType()
{
(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 getResolveTestCases
*/
public function testResolvesUris($base, $rel, $expected)
{
$uri = new Uri($base);
$actual = Uri::resolve($uri, $rel);
$this->assertSame($expected, (string) $actual);
}
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'],
['http://a/b/c/d;p?q#s', '?y', 'http://a/b/c/d;p?y'],
['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/'],
['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'],
];
}
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 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);
}
public function testAuthorityWithUserInfoButWithoutHost()
{
$uri = (new Uri())->withUserInfo('user', 'pass');
$this->assertSame('user:pass', $uri->getUserInfo());
$this->assertSame('', $uri->getAuthority());
}
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 testAddsSlashForRelativeUriStringWithHost()
{
// If the path is rootless and an authority is present, the path MUST
// be prefixed by "/".
$uri = (new Uri)->withPath('foo')->withHost('example.com');
$this->assertSame('foo', $uri->getPath());
// concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
$this->assertSame('//example.com/foo', (string) $uri);
}
public function testRemoveExtraSlashesWihoutHost()
{
// If the path is starting with more than one "/" and no authority is
// present, the starting slashes MUST be reduced to one.
$uri = (new Uri)->withPath('//foo');
$this->assertSame('//foo', $uri->getPath());
// URI "//foo" would be interpreted as network reference and thus change the original path to the host
$this->assertSame('/foo', (string) $uri);
}
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\ExtendingClassTest',
new ExtendingClassTest('http://h:9/')
);
}
}
class ExtendingClassTest extends \GuzzleHttp\Psr7\Uri
{
}

View file

@ -1,11 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
require __DIR__ . '/../vendor/autoload.php';
class HasToString
{
public function __toString() {
return 'foo';
}
}

View file

@ -0,0 +1,5 @@
.idea
.DS_store
/vendor/
composer.phar
composer.lock

View file

@ -0,0 +1,18 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
before_script:
- composer install
script:
- mkdir -p build/logs
- php vendor/bin/phpunit -c phpunit.xml
after_script:
- php vendor/bin/coveralls -v

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Ralph Khattar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,19 @@
getallheaders
=============
PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders)
[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders)
[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders)
[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders)
This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
## Install
```
composer require ralouphie/getallheaders
```

View file

@ -0,0 +1,21 @@
{
"name": "ralouphie/getallheaders",
"description": "A polyfill for getallheaders.",
"license": "MIT",
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "~3.7.0",
"satooshi/php-coveralls": ">=1.0"
},
"autoload": {
"files": ["src/getallheaders.php"]
}
}

View file

@ -0,0 +1,22 @@
<phpunit
bootstrap="vendor/autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
strict="true">
<testsuite>
<directory>./tests</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

View file

@ -0,0 +1,46 @@
<?php
if (!function_exists('getallheaders')) {
/**
* Get all HTTP header key/values as an associative array for the current request.
*
* @return string[string] The HTTP header key/value pairs.
*/
function getallheaders()
{
$headers = array();
$copy_server = array(
'CONTENT_TYPE' => 'Content-Type',
'CONTENT_LENGTH' => 'Content-Length',
'CONTENT_MD5' => 'Content-Md5',
);
foreach ($_SERVER as $key => $value) {
if (substr($key, 0, 5) === 'HTTP_') {
$key = substr($key, 5);
if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
$key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
$headers[$key] = $value;
}
} elseif (isset($copy_server[$key])) {
$headers[$copy_server[$key]] = $value;
}
}
if (!isset($headers['Authorization'])) {
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
$headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
} elseif (isset($_SERVER['PHP_AUTH_USER'])) {
$basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
$headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
} elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
$headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
}
}
return $headers;
}
}

View file

@ -0,0 +1,121 @@
<?php
class GetAllHeadersTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider testWorksData
*/
public function testWorks($test_type, $expected, $server)
{
foreach ($server as $key => $val) {
$_SERVER[$key] = $val;
}
$result = getallheaders();
$this->assertEquals($expected, $result, "Error testing $test_type works.");
}
public function testWorksData()
{
return array(
array(
'normal case',
array(
'Key-One' => 'foo',
'Key-Two' => 'bar',
'Another-Key-For-Testing' => 'baz'
),
array(
'HTTP_KEY_ONE' => 'foo',
'HTTP_KEY_TWO' => 'bar',
'HTTP_ANOTHER_KEY_FOR_TESTING' => 'baz'
)
),
array(
'Content-Type',
array(
'Content-Type' => 'two'
),
array(
'HTTP_CONTENT_TYPE' => 'one',
'CONTENT_TYPE' => 'two'
)
),
array(
'Content-Length',
array(
'Content-Length' => '222'
),
array(
'CONTENT_LENGTH' => '222',
'HTTP_CONTENT_LENGTH' => '111'
)
),
array(
'Content-Length (HTTP_CONTENT_LENGTH only)',
array(
'Content-Length' => '111'
),
array(
'HTTP_CONTENT_LENGTH' => '111'
)
),
array(
'Content-MD5',
array(
'Content-Md5' => 'aef123'
),
array(
'CONTENT_MD5' => 'aef123',
'HTTP_CONTENT_MD5' => 'fea321'
)
),
array(
'Content-MD5 (HTTP_CONTENT_MD5 only)',
array(
'Content-Md5' => 'f123'
),
array(
'HTTP_CONTENT_MD5' => 'f123'
)
),
array(
'Authorization (normal)',
array(
'Authorization' => 'testing'
),
array(
'HTTP_AUTHORIZATION' => 'testing',
)
),
array(
'Authorization (redirect)',
array(
'Authorization' => 'testing redirect'
),
array(
'REDIRECT_HTTP_AUTHORIZATION' => 'testing redirect',
)
),
array(
'Authorization (PHP_AUTH_USER + PHP_AUTH_PW)',
array(
'Authorization' => 'Basic ' . base64_encode('foo:bar')
),
array(
'PHP_AUTH_USER' => 'foo',
'PHP_AUTH_PW' => 'bar'
)
),
array(
'Authorization (PHP_AUTH_DIGEST)',
array(
'Authorization' => 'example-digest'
),
array(
'PHP_AUTH_DIGEST' => 'example-digest'
)
)
);
}
}