diff --git a/.gitattributes b/.gitattributes index 160a6002c..ba1c92ba3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,7 @@ -.editorconfig export-ignore -.gitattributes export-ignore +.editorconfig export-ignore +.gitattributes export-ignore /.github/ export-ignore -.gitignore export-ignore -/.travis.yml export-ignore +.gitignore export-ignore /build/ export-ignore /docs/ export-ignore /Makefile export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..88649df2e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +jobs: + build-lowest: + name: Build lowest + runs-on: ubuntu-latest + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '5.5' + coverage: none + extensions: mbstring, intl + + - name: Set up Node + uses: actions/setup-node@v1 + with: + node-version: '14.x' + + - name: Setup Problem Matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download dependencies + run: composer update --no-interaction --no-progress --prefer-stable --prefer-lowest + + - name: Run tests + run: make test + + build: + name: Build + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + extensions: mbstring, intl + + - name: Set up Node + uses: actions/setup-node@v1 + with: + node-version: '14.x' + + - name: Setup Problem Matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download dependencies + run: composer update --no-interaction --no-progress + + - name: Run tests + run: make test diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml deleted file mode 100644 index 8948130b6..000000000 --- a/.github/workflows/static.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Static analysis - -on: - push: - branches: - - master - pull_request: - -jobs: - phpstan: - name: PHPStan - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: PHPStan - uses: OskarStark/phpstan-ga@0.12.28 - env: - REQUIRE_DEV: true - with: - args: analyze --no-progress - - php-cs-fixer: - name: PHP-CS-Fixer - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: PHP-CS-Fixer - uses: OskarStark/php-cs-fixer-ga@2.16.3.1 - with: - args: --dry-run --diff-format udiff diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 96955d9d0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,56 +0,0 @@ -language: php - -matrix: - include: - - php: 5.5 - dist: trusty - - php: 5.6 - dist: xenial - - php: 7.0 - dist: xenial - - php: 7.1 - dist: bionic - - php: 7.2 - dist: bionic - - php: 7.3 - dist: bionic - - php: 7.4 - dist: bionic - - php: nightly - dist: bionic - env: COMPOSER_ARGS="--ignore-platform-reqs" - - php: hhvm-3.24 - dist: trusty - allow_failures: - - php: hhvm-3.24 - - php: nightly - fast_finish: true - -install: - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm-3.24" || "$TRAVIS_PHP_VERSION" != "nightly" ]]; then echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini || true; fi - - composer install --prefer-dist $COMPOSER_ARGS - - ~/.nvm/nvm.sh install v10.18.0 - - ~/.nvm/nvm.sh run v10.18.0 - -before_script: - - curl --version - -script: - - make test - -before_deploy: - - make package - -deploy: - provider: releases - skip_cleanup: true - api_key: - secure: mz9H1B4cPH7dW9hTzgHnbh75+HJ6fJZ9S/1nMWFaqgj5C0wDzTqkJ+BbwiCEiqXGh6VGZbM4EmO1/wnZ7B+Hk8zsB1PP+GKVkq8+7a/261o60W3OS4gQpZQ9R68dyEO1EyZBJvL1Lzc03rkt/0WnKiAjg7nsc1j4aLKhWMDQ6x8= - file: - - build/artifacts/guzzle.phar - - build/artifacts/guzzle.zip - on: - repo: guzzle/guzzle - tags: true - all_branches: true - php: 5.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 464cf1c50..95d26df21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 6.5.6 - 2022-05-25 + +* Fix cross-domain cookie leakage + ## 6.5.5 - 2020-06-16 * Unpin version constraint for `symfony/polyfill-intl-idn` [#2678](https://github.com/guzzle/guzzle/pull/2678) diff --git a/LICENSE b/LICENSE index 50a177b03..fd2375d88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,12 @@ -Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5fdb6c5f4..00d2066e9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle) +[![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) [![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 @@ -74,17 +74,20 @@ composer update ## Version Guidance -| 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 | EOL | `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 | +| 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,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 | [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-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://guzzle.readthedocs.org/en/5.3/ -[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/ +[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/ diff --git a/composer.json b/composer.json index c01864f01..b9cb386a6 100644 --- a/composer.json +++ b/composer.json @@ -14,10 +14,40 @@ "homepage": "http://guzzlephp.org/", "license": "MIT", "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "require": { diff --git a/src/Cookie/CookieJar.php b/src/Cookie/CookieJar.php index 38f98ad7c..394df3a7c 100644 --- a/src/Cookie/CookieJar.php +++ b/src/Cookie/CookieJar.php @@ -240,6 +240,11 @@ public function extractCookies( if (0 !== strpos($sc->getPath(), '/')) { $sc->setPath($this->getCookiePathFromRequest($request)); } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. $this->setCookie($sc); } } diff --git a/src/Cookie/SetCookie.php b/src/Cookie/SetCookie.php index 3d776a70b..55f6901a7 100644 --- a/src/Cookie/SetCookie.php +++ b/src/Cookie/SetCookie.php @@ -333,12 +333,19 @@ public function matchesPath($requestPath) */ public function matchesDomain($domain) { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + // Remove the leading '.' as per spec in RFC 6265. // http://tools.ietf.org/html/rfc6265#section-5.2.3 - $cookieDomain = ltrim($this->getDomain(), '.'); + $cookieDomain = ltrim(strtolower($cookieDomain), '.'); + + $domain = strtolower($domain); // Domain not set or exact match. - if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + if ('' === $cookieDomain || $domain === $cookieDomain) { return true; } diff --git a/tests/Cookie/CookieJarTest.php b/tests/Cookie/CookieJarTest.php index 873999cf9..1e9c84c92 100644 --- a/tests/Cookie/CookieJarTest.php +++ b/tests/Cookie/CookieJarTest.php @@ -385,9 +385,6 @@ public function getCookiePathsDataProvider() ['/foo', '/'], ['/foo/bar', '/foo'], ['/foo/bar/', '/foo/bar'], - ['foo', '/'], - ['foo/bar', '/'], - ['foo/bar/', '/'], ]; } @@ -406,13 +403,45 @@ public function testCookiePathWithEmptySetCookiePath($uriPath, $cookiePath) "bar=foo; expires={$this->futureExpirationDate()}; domain=www.example.com; path=foobar;" ) ; - $request = (new Request('GET', $uriPath))->withHeader('Host', 'www.example.com'); + $request = (new Request('GET', "https://www.example.com{$uriPath}")); $this->jar->extractCookies($request, $response); self::assertSame($cookiePath, $this->jar->toArray()[0]['Path']); self::assertSame($cookiePath, $this->jar->toArray()[1]['Path']); } + public function getDomainMatchesProvider() + { + return [ + ['www.example.com', 'www.example.com', true], + ['www.example.com', 'www.EXAMPLE.com', true], + ['www.example.com', 'www.example.net', false], + ['www.example.com', 'ftp.example.com', false], + ['www.example.com', 'example.com', true], + ['www.example.com', 'EXAMPLE.com', true], + ['fra.de.example.com', 'EXAMPLE.com', true], + ['www.EXAMPLE.com', 'www.example.com', true], + ['www.EXAMPLE.com', 'www.example.COM', true], + ]; + } + + /** + * @dataProvider getDomainMatchesProvider + */ + public function testIgnoresCookiesForMismatchingDomains($requestHost, $domainAttribute, $matches) + { + $response = (new Response(200)) + ->withAddedHeader( + 'Set-Cookie', + "foo=bar; expires={$this->futureExpirationDate()}; domain={$domainAttribute}; path=/;" + ) + ; + $request = (new Request('GET', "https://{$requestHost}/")); + $this->jar->extractCookies($request, $response); + + self::assertCount($matches ? 1 : 0, $this->jar->toArray()); + } + private function futureExpirationDate() { return (new DateTimeImmutable)->add(new DateInterval('P1D'))->format(DateTime::COOKIE);