Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Release 6.5.8 (#3042)
* Release 6.5.8

* Update README.md

* Update RedirectMiddleware.php
  • Loading branch information
GrahamCampbell committed Jun 20, 2022
1 parent 724562f commit a52f044
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 51 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
# Change Log

## 6.5.8 - 2022-06-20

* Fix change in port should be considered a change in origin
* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin

## 6.5.7 - 2022-06-09

* Fix failure to strip Authorization header on HTTP downgrade
Expand Down
22 changes: 13 additions & 9 deletions README.md
@@ -1,5 +1,6 @@
Guzzle, PHP HTTP client
=======================
![Guzzle](.github/logo.png?raw=true)

# Guzzle, PHP HTTP client

[![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/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
Expand Down Expand Up @@ -38,15 +39,18 @@ $promise->wait();

## Help and docs

- [Documentation](http://guzzlephp.org/)
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
We use GitHub issues only to discuss bugs and new features. For support please refer to:

- [Documentation](https://docs.guzzlephp.org)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle)
- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/)
- [Gitter](https://gitter.im/guzzle/guzzle)


## Installing Guzzle

The recommended way to install Guzzle is through
[Composer](http://getcomposer.org).
[Composer](https://getcomposer.org/).

```bash
# Install Composer
Expand Down Expand Up @@ -87,7 +91,7 @@ composer update
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
[guzzle-7-repo]: https://github.com/guzzle/guzzle
[guzzle-3-docs]: http://guzzle3.readthedocs.org
[guzzle-5-docs]: http://docs.guzzlephp.org/en/5.3/
[guzzle-6-docs]: http://docs.guzzlephp.org/en/6.5/
[guzzle-7-docs]: http://docs.guzzlephp.org/en/latest/
[guzzle-3-docs]: https://guzzle3.readthedocs.io/
[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/
[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/
[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/
9 changes: 6 additions & 3 deletions composer.json
Expand Up @@ -53,9 +53,9 @@
"require": {
"php": ">=5.5",
"ext-json": "*",
"symfony/polyfill-intl-idn": "^1.17.0",
"symfony/polyfill-intl-idn": "^1.17",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1"
"guzzlehttp/psr7": "^1.9"
},
"require-dev": {
"ext-curl": "*",
Expand All @@ -66,7 +66,10 @@
"psr/log": "Required for using the Log middleware"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"branch-alias": {
Expand Down
37 changes: 10 additions & 27 deletions src/RedirectMiddleware.php
Expand Up @@ -94,6 +94,14 @@ public function checkRedirect(
$this->guardMax($request, $options);
$nextRequest = $this->modifyRequest($request, $options, $response);

// If authorization is handled by curl, unset it if URI is cross-origin.
if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) {
unset(
$options['curl'][\CURLOPT_HTTPAUTH],
$options['curl'][\CURLOPT_USERPWD]
);
}

if (isset($options['allow_redirects']['on_redirect'])) {
call_user_func(
$options['allow_redirects']['on_redirect'],
Expand Down Expand Up @@ -210,40 +218,15 @@ public function modifyRequest(
$modify['remove_headers'][] = 'Referer';
}

// Remove Authorization and Cookie headers if required.
if (self::shouldStripSensitiveHeaders($request->getUri(), $modify['uri'])) {
// Remove Authorization and Cookie headers if URI is cross-origin.
if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) {
$modify['remove_headers'][] = 'Authorization';
$modify['remove_headers'][] = 'Cookie';
}

return Psr7\modify_request($request, $modify);
}

/**
* Determine if we should strip sensitive headers from the request.
*
* We return true if either of the following conditions are true:
*
* 1. the host is different;
* 2. the scheme has changed, and now is non-https.
*
* @return bool
*/
private static function shouldStripSensitiveHeaders(
UriInterface $originalUri,
UriInterface $modifiedUri
) {
if (strcasecmp($originalUri->getHost(), $modifiedUri->getHost()) !== 0) {
return true;
}

if ($originalUri->getScheme() !== $modifiedUri->getScheme() && 'https' !== $modifiedUri->getScheme()) {
return true;
}

return false;
}

/**
* Set the appropriate URL on the request based on the location header.
*
Expand Down
177 changes: 165 additions & 12 deletions tests/RedirectMiddlewareTest.php
Expand Up @@ -251,30 +251,183 @@ public function testInvokesOnRedirectForRedirects()
self::assertTrue($call);
}

/**
* @testWith ["digest"]
* ["ntlm"]
*/
public function testRemoveCurlAuthorizationOptionsOnRedirectCrossHost($auth)
{
if (!defined('\CURLOPT_HTTPAUTH')) {
self::markTestSkipped('ext-curl is required for this test');
}

$mock = new MockHandler([
new Response(302, ['Location' => 'http://test.com']),
static function (RequestInterface $request, $options) {
self::assertFalse(
isset($options['curl'][\CURLOPT_HTTPAUTH]),
'curl options still contain CURLOPT_HTTPAUTH entry'
);
self::assertFalse(
isset($options['curl'][\CURLOPT_USERPWD]),
'curl options still contain CURLOPT_USERPWD entry'
);
return new Response(200);
}
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$client->get('http://example.com?a=b', ['auth' => ['testuser', 'testpass', $auth]]);
}

/**
* @testWith ["digest"]
* ["ntlm"]
*/
public function testRemoveCurlAuthorizationOptionsOnRedirectCrossPort($auth)
{
if (!defined('\CURLOPT_HTTPAUTH')) {
self::markTestSkipped('ext-curl is required for this test');
}

$mock = new MockHandler([
new Response(302, ['Location' => 'http://example.com:81/']),
static function (RequestInterface $request, $options) {
self::assertFalse(
isset($options['curl'][\CURLOPT_HTTPAUTH]),
'curl options still contain CURLOPT_HTTPAUTH entry'
);
self::assertFalse(
isset($options['curl'][\CURLOPT_USERPWD]),
'curl options still contain CURLOPT_USERPWD entry'
);
return new Response(200);
}
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$client->get('http://example.com?a=b', ['auth' => ['testuser', 'testpass', $auth]]);
}

/**
* @testWith ["digest"]
* ["ntlm"]
*/
public function testRemoveCurlAuthorizationOptionsOnRedirectCrossScheme($auth)
{
if (!defined('\CURLOPT_HTTPAUTH')) {
self::markTestSkipped('ext-curl is required for this test');
}

$mock = new MockHandler([
new Response(302, ['Location' => 'http://example.com?a=b']),
static function (RequestInterface $request, $options) {
self::assertFalse(
isset($options['curl'][\CURLOPT_HTTPAUTH]),
'curl options still contain CURLOPT_HTTPAUTH entry'
);
self::assertFalse(
isset($options['curl'][\CURLOPT_USERPWD]),
'curl options still contain CURLOPT_USERPWD entry'
);
return new Response(200);
}
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$client->get('https://example.com?a=b', ['auth' => ['testuser', 'testpass', $auth]]);
}

/**
* @testWith ["digest"]
* ["ntlm"]
*/
public function testRemoveCurlAuthorizationOptionsOnRedirectCrossSchemeSamePort($auth)
{
if (!defined('\CURLOPT_HTTPAUTH')) {
self::markTestSkipped('ext-curl is required for this test');
}

$mock = new MockHandler([
new Response(302, ['Location' => 'http://example.com:80?a=b']),
static function (RequestInterface $request, $options) {
self::assertFalse(
isset($options['curl'][\CURLOPT_HTTPAUTH]),
'curl options still contain CURLOPT_HTTPAUTH entry'
);
self::assertFalse(
isset($options['curl'][\CURLOPT_USERPWD]),
'curl options still contain CURLOPT_USERPWD entry'
);
return new Response(200);
}
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$client->get('https://example.com?a=b', ['auth' => ['testuser', 'testpass', $auth]]);
}

/**
* @testWith ["digest"]
* ["ntlm"]
*/
public function testNotRemoveCurlAuthorizationOptionsOnRedirect($auth)
{
if (!defined('\CURLOPT_HTTPAUTH') || !defined('\CURLOPT_USERPWD')) {
self::markTestSkipped('ext-curl is required for this test');
}

$mock = new MockHandler([
new Response(302, ['Location' => 'http://example.com/2']),
static function (RequestInterface $request, $options) {
self::assertTrue(
isset($options['curl'][\CURLOPT_HTTPAUTH]),
'curl options does not contain expected CURLOPT_HTTPAUTH entry'
);
self::assertTrue(
isset($options['curl'][\CURLOPT_USERPWD]),
'curl options does not contain expected CURLOPT_USERPWD entry'
);
return new Response(200);
}
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$client->get('http://example.com?a=b', ['auth' => ['testuser', 'testpass', $auth]]);
}

public function crossOriginRedirectProvider()
{
return [
['http://example.com?a=b', 'http://test.com/', false],
['https://example.com?a=b', 'https://test.com/', false],
['http://example.com?a=b', 'https://test.com/', false],
['https://example.com?a=b', 'http://test.com/', false],
['http://example.com?a=b', 'http://example.com/', true],
['https://example.com?a=b', 'https://example.com/', true],
['http://example.com?a=b', 'https://example.com/', true],
['https://example.com?a=b', 'http://example.com/', false],
['http://example.com/123', 'http://example.com/', false],
['http://example.com/123', 'http://example.com:80/', false],
['http://example.com:80/123', 'http://example.com/', false],
['http://example.com:80/123', 'http://example.com:80/', false],
['http://example.com/123', 'https://example.com/', true],
['http://example.com/123', 'http://www.example.com/', true],
['http://example.com/123', 'http://example.com:81/', true],
['http://example.com:80/123', 'http://example.com:81/', true],
['https://example.com/123', 'https://example.com/', false],
['https://example.com/123', 'https://example.com:443/', false],
['https://example.com:443/123', 'https://example.com/', false],
['https://example.com:443/123', 'https://example.com:443/', false],
['https://example.com/123', 'http://example.com/', true],
['https://example.com/123', 'https://www.example.com/', true],
['https://example.com/123', 'https://example.com:444/', true],
['https://example.com:443/123', 'https://example.com:444/', true],
];
}

/**
* @dataProvider crossOriginRedirectProvider
*/
public function testHeadersTreatmentOnRedirect($originalUri, $targetUri, $shouldBePresent)
public function testHeadersTreatmentOnRedirect($originalUri, $targetUri, $isCrossOrigin)
{
$mock = new MockHandler([
new Response(302, ['Location' => $targetUri]),
function (RequestInterface $request) use ($shouldBePresent) {
self::assertSame($shouldBePresent, $request->hasHeader('Authorization'));
self::assertSame($shouldBePresent, $request->hasHeader('Cookie'));
function (RequestInterface $request) use ($isCrossOrigin) {
self::assertSame(!$isCrossOrigin, $request->hasHeader('Authorization'));
self::assertSame(!$isCrossOrigin, $request->hasHeader('Cookie'));

return new Response(200);
}
Expand Down

0 comments on commit a52f044

Please sign in to comment.