--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:php-guzzlehttp-psr7
User: [email protected]
Usertags: pu
Hi,
As agreed with the security team, I’dd like to address CVE-2026-48998
and CVE-2026-49214 in the next point release since they don’t deserve a
DSA. Please note that I had to include three more upstream patches in
order to properly include the fix for CVE-2026-48998.
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
Thanks in advance for considering.
Regards,
taffit
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/changelog php-guzzlehttp-psr7-2.7.1/debian/changelog
--- php-guzzlehttp-psr7-2.7.1/debian/changelog 2025-03-31 12:19:42.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/changelog 2026-05-30 13:39:01.000000000 +0200
@@ -1,3 +1,16 @@
+php-guzzlehttp-psr7 (2.7.1-1+deb13u1) trixie; urgency=medium
+
+ * Backport fixes from upstream
+ - Encode plus sign in withQueryValue() and withQueryValues() (#636)
+ - Harden ServerRequest globals handling (#660)
+ - Normalize global header values (#718)
+ - Reject control characters in URI hosts (#715) [CVE-2026-49214]
+ - Reject malformed Host authorities (#717) [CVE-2026-48998]
+ (Closes: #1138265)
+ * Track debian/trixie branch
+
+ -- David Prévot <[email protected]> Sat, 30 May 2026 13:39:01 +0200
+
php-guzzlehttp-psr7 (2.7.1-1) unstable; urgency=medium
[ Graham Campbell ]
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/control php-guzzlehttp-psr7-2.7.1/debian/control
--- php-guzzlehttp-psr7-2.7.1/debian/control 2025-03-31 12:19:42.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/control 2026-05-30 13:39:01.000000000 +0200
@@ -15,7 +15,7 @@
psmisc
Standards-Version: 4.7.2
Homepage: https://github.com/guzzle/psr7
-Vcs-Git: https://salsa.debian.org/php-team/pear/php-guzzlehttp-psr7.git
+Vcs-Git: https://salsa.debian.org/php-team/pear/php-guzzlehttp-psr7.git -b debian/trixie
Vcs-Browser: https://salsa.debian.org/php-team/pear/php-guzzlehttp-psr7
Rules-Requires-Root: no
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/gbp.conf php-guzzlehttp-psr7-2.7.1/debian/gbp.conf
--- php-guzzlehttp-psr7-2.7.1/debian/gbp.conf 2024-07-21 02:49:36.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/gbp.conf 2026-05-30 13:39:01.000000000 +0200
@@ -1,5 +1,5 @@
[DEFAULT]
-debian-branch = debian/latest
+debian-branch = debian/trixie
filter = [ '.gitattributes' ]
pristine-tar = True
upstream-vcs-tag = %(version%~%-)s
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0004-2.8-Encode-plus-sign-in-withQueryValue-and-withQuery.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0004-2.8-Encode-plus-sign-in-withQueryValue-and-withQuery.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0004-2.8-Encode-plus-sign-in-withQueryValue-and-withQuery.patch 1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0004-2.8-Encode-plus-sign-in-withQueryValue-and-withQuery.patch 2026-05-30 13:39:01.000000000 +0200
@@ -0,0 +1,61 @@
+From: =?utf-8?q?Ey=C3=BCp_Can_Akman?= <[email protected]>
+Date: Tue, 10 Mar 2026 12:53:17 +0300
+Subject: [2.8] Encode plus sign in withQueryValue() and withQueryValues()
+ (#636)
+
+* Encode plus sign in withQueryValue() and withQueryValues()
+
+* Add plus sign note to Uri query comment
+
+Origin: upstrem, https://github.com/guzzle/psr7/commit/bf5b5784ad30e4d01d7723366977ae54dc1d06ff
+Bug: https://github.com/guzzle/psr7/pull/636
+---
+ src/Uri.php | 5 +++--
+ tests/UriTest.php | 11 +++++++++++
+ 2 files changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/src/Uri.php b/src/Uri.php
+index a7cdfb0..bef82e2 100644
+--- a/src/Uri.php
++++ b/src/Uri.php
+@@ -51,7 +51,7 @@ class Uri implements UriInterface, \JsonSerializable
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+ */
+ private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
+- private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
++ private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+@@ -661,7 +661,8 @@ class Uri implements UriInterface, \JsonSerializable
+
+ private static function generateQueryString(string $key, ?string $value): string
+ {
+- // Query string separators ("=", "&") within the key or value need to be encoded
++ // Query string separators ("=", "&") and literal plus signs ("+") 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::QUERY_SEPARATORS_REPLACEMENT);
+diff --git a/tests/UriTest.php b/tests/UriTest.php
+index 185b7f1..3fb0716 100644
+--- a/tests/UriTest.php
++++ b/tests/UriTest.php
+@@ -390,6 +390,17 @@ class UriTest extends TestCase
+ self::assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
+ }
+
++ public function testWithQueryValueEncodesPlusSign(): void
++ {
++ $uri = new Uri();
++ $uri = Uri::withQueryValue($uri, 'a+b', 'c+d');
++ self::assertSame('a%2Bb=c%2Bd', $uri->getQuery(), 'Plus signs in key and value get encoded to %2B');
++
++ $uri = new Uri();
++ $uri = Uri::withQueryValue($uri, 'query', 'a+b c');
++ self::assertSame('query=a%2Bb%20c', $uri->getQuery(), 'Plus sign is encoded distinctly from space');
++ }
++
+ public function testWithoutQueryValueHandlesEncoding(): void
+ {
+ // It also tests that the case of the percent-encoding does not matter,
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch 2025-03-31 12:18:24.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0004-Modernize-PHPUnit-syntax.patch 2026-05-30 13:39:01.000000000 +0200
@@ -6,20 +6,21 @@
tests/AppendStreamTest.php | 5 ++---
tests/FnStreamTest.php | 5 ++---
tests/HeaderTest.php | 13 ++++---------
+ tests/MessageTest.php | 9 +++------
tests/PumpStreamTest.php | 5 ++---
tests/QueryTest.php | 9 +++------
tests/RequestTest.php | 21 ++++++---------------
tests/ResponseTest.php | 21 +++++++--------------
- tests/ServerRequestTest.php | 9 +++------
+ tests/ServerRequestTest.php | 13 ++++---------
tests/StreamDecoratorTraitTest.php | 5 ++---
tests/StreamTest.php | 12 ++++--------
tests/UploadedFileTest.php | 21 ++++++---------------
tests/UriComparatorTest.php | 5 ++---
tests/UriNormalizerTest.php | 13 ++++---------
tests/UriResolverTest.php | 13 ++++---------
- tests/UriTest.php | 21 ++++++---------------
+ tests/UriTest.php | 25 +++++++------------------
tests/UtilsTest.php | 9 ++++-----
- 16 files changed, 61 insertions(+), 126 deletions(-)
+ 17 files changed, 66 insertions(+), 138 deletions(-)
diff --git a/tests/AppendStreamTest.php b/tests/AppendStreamTest.php
index 302470a..b0c6a2e 100644
@@ -111,6 +112,40 @@
public function testSplitList($header, $result): void
{
self::assertSame($result, Psr7\Header::splitList($header));
+diff --git a/tests/MessageTest.php b/tests/MessageTest.php
+index 42909be..4df397a 100644
+--- a/tests/MessageTest.php
++++ b/tests/MessageTest.php
+@@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7;
+
+ use GuzzleHttp\Psr7;
+ use GuzzleHttp\Psr7\FnStream;
++use PHPUnit\Framework\Attributes\DataProvider;
+ use PHPUnit\Framework\TestCase;
+
+ class MessageTest extends TestCase
+@@ -107,9 +108,7 @@ class MessageTest extends TestCase
+ self::assertSame('http://foo.com/', (string) $request->getUri());
+ }
+
+- /**
+- * @dataProvider invalidHostHeaderProvider
+- */
++ #[DataProvider('invalidHostHeaderProvider')]
+ public function testParseRequestRejectsInvalidHostHeader(string $host): void
+ {
+ $this->expectException(\InvalidArgumentException::class);
+@@ -136,9 +135,7 @@ class MessageTest extends TestCase
+ yield 'unexpected closing bracket' => ['foo]bar'];
+ }
+
+- /**
+- * @dataProvider validHostHeaderProvider
+- */
++ #[DataProvider('validHostHeaderProvider')]
+ public function testParseRequestAcceptsValidHostHeader(string $host, string $expectedUri): void
+ {
+ $request = Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n");
diff --git a/tests/PumpStreamTest.php b/tests/PumpStreamTest.php
index a4f3f2c..52ded59 100644
--- a/tests/PumpStreamTest.php
@@ -169,7 +204,7 @@
{
$result = Psr7\Query::parse($input, false);
diff --git a/tests/RequestTest.php b/tests/RequestTest.php
-index 19d71c3..92c9733 100644
+index 3a62c57..0ea5069 100644
--- a/tests/RequestTest.php
+++ b/tests/RequestTest.php
@@ -7,6 +7,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -179,8 +214,8 @@
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamInterface;
-
-@@ -95,18 +96,14 @@ class RequestTest extends TestCase
+ use Psr\Http\Message\UriInterface;
+@@ -96,18 +97,14 @@ class RequestTest extends TestCase
self::assertSame($u1, $r1->getUri());
}
@@ -201,7 +236,7 @@
public function testWithInvalidMethods($method): void
{
$r = new Request('get', '/');
-@@ -197,9 +194,7 @@ class RequestTest extends TestCase
+@@ -198,9 +195,7 @@ class RequestTest extends TestCase
self::assertSame('', $r->getHeaderLine('Bar'));
}
@@ -212,7 +247,7 @@
public function testContainsNotAllowedCharsOnHeaderField($header): void
{
$this->expectExceptionMessage(
-@@ -222,9 +217,7 @@ class RequestTest extends TestCase
+@@ -223,9 +218,7 @@ class RequestTest extends TestCase
return [[' key '], ['key '], [' key'], ['key/'], ['key('], ['key\\'], [' ']];
}
@@ -223,8 +258,8 @@
public function testContainsAllowedCharsOnHeaderField($header): void
{
$r = new Request(
-@@ -306,9 +299,7 @@ class RequestTest extends TestCase
- self::assertSame('foo.com:8125', $r->getHeaderLine('host'));
+@@ -329,9 +322,7 @@ class RequestTest extends TestCase
+ self::assertSame('example.com:8080', $request->getHeaderLine('Host'));
}
- /**
@@ -317,7 +352,7 @@
{
$response = new Response();
diff --git a/tests/ServerRequestTest.php b/tests/ServerRequestTest.php
-index f770078..50d173b 100644
+index 914f949..b81b663 100644
--- a/tests/ServerRequestTest.php
+++ b/tests/ServerRequestTest.php
@@ -7,6 +7,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -339,7 +374,7 @@
public function testNormalizeFiles($files, $expected): void
{
$result = ServerRequest::normalizeFiles($files);
-@@ -369,9 +368,7 @@ class ServerRequestTest extends TestCase
+@@ -433,9 +432,7 @@ class ServerRequestTest extends TestCase
];
}
@@ -350,6 +385,17 @@
public function testGetUriFromGlobals($expected, $serverParams): void
{
$_SERVER = $serverParams;
+@@ -576,9 +573,7 @@ class ServerRequestTest extends TestCase
+ self::assertSame('1.1', $server->getProtocolVersion());
+ }
+
+- /**
+- * @dataProvider invalidHostHeaderFromGlobalsProvider
+- */
++ #[DataProvider('invalidHostHeaderFromGlobalsProvider')]
+ public function testFromGlobalsDropsInvalidHostHeaderWhenUriFallsBack(string $host): void
+ {
+ if (!\function_exists('getallheaders')) {
diff --git a/tests/StreamDecoratorTraitTest.php b/tests/StreamDecoratorTraitTest.php
index aaa9f28..11e699c 100644
--- a/tests/StreamDecoratorTraitTest.php
@@ -598,7 +644,7 @@
{
$baseUri = new Uri($base);
diff --git a/tests/UriTest.php b/tests/UriTest.php
-index 185b7f1..b0b39a4 100644
+index da229a7..01d7df5 100644
--- a/tests/UriTest.php
+++ b/tests/UriTest.php
@@ -6,6 +6,7 @@ namespace GuzzleHttp\Tests\Psr7;
@@ -642,7 +688,18 @@
public function testInvalidUrisThrowException(string $invalidUri): void
{
$this->expectException(MalformedUriException::class);
-@@ -205,9 +200,7 @@ class UriTest extends TestCase
+@@ -153,9 +148,7 @@ class UriTest extends TestCase
+ (new Uri())->withHost([]);
+ }
+
+- /**
+- * @dataProvider getInvalidHostsWithControlCharacters
+- */
++ #[DataProvider('getInvalidHostsWithControlCharacters')]
+ public function testHostMustRejectControlCharacters(string $host): void
+ {
+ $this->expectException(\InvalidArgumentException::class);
+@@ -231,9 +224,7 @@ class UriTest extends TestCase
self::assertSame('0://0:0@0/0?0#0', (string) $uri);
}
@@ -653,7 +710,7 @@
public function testIsDefaultPort(string $scheme, ?int $port, bool $isDefaultPort): void
{
$uri = $this->createMock(UriInterface::class);
-@@ -553,9 +546,7 @@ class UriTest extends TestCase
+@@ -590,9 +581,7 @@ class UriTest extends TestCase
];
}
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0005-Harden-ServerRequest-globals-handling-660.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0005-Harden-ServerRequest-globals-handling-660.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0005-Harden-ServerRequest-globals-handling-660.patch 1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0005-Harden-ServerRequest-globals-handling-660.patch 2026-05-30 13:39:01.000000000 +0200
@@ -0,0 +1,181 @@
+From: Graham Campbell <[email protected]>
+Date: Tue, 19 May 2026 18:20:12 +0100
+Subject: Harden ServerRequest globals handling (#660)
+
+* Harden server request globals
+
+* Document ServerRequest globals hardening
+
+* Move ServerRequest changelog entry
+
+* Reorder ServerRequest changelog entry
+
+Origin: backport, https://github.com/guzzle/psr7/commit/ed659d5c2254b9d2af3a1b77257b91a4899e313c
+Bug: https://github.com/guzzle/psr7/pull/660
+---
+ src/ServerRequest.php | 46 +++++++++++++++++++++++++++++----------------
+ tests/ServerRequestTest.php | 41 ++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 71 insertions(+), 16 deletions(-)
+
+diff --git a/src/ServerRequest.php b/src/ServerRequest.php
+index 3cc9534..bc795fc 100644
+--- a/src/ServerRequest.php
++++ b/src/ServerRequest.php
+@@ -165,11 +165,12 @@ class ServerRequest extends Request implements ServerRequestInterface
+ */
+ public static function fromGlobals(): ServerRequestInterface
+ {
+- $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
++ $method = self::getServerParam('REQUEST_METHOD') ?? 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+- $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
++ $serverProtocol = self::getServerParam('SERVER_PROTOCOL');
++ $protocol = $serverProtocol !== null ? str_replace('HTTP/', '', $serverProtocol) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+@@ -180,11 +181,19 @@ class ServerRequest extends Request implements ServerRequestInterface
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
++ private static function getServerParam(string $key): ?string
++ {
++ return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null;
++ }
++
++ /**
++ * @return array{0: string|null, 1: int|null}
++ */
+ private static function extractHostAndPortFromAuthority(string $authority): array
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+- if (false === $parts) {
++ if (!is_array($parts)) {
+ return [null, null];
+ }
+
+@@ -201,11 +210,13 @@ class ServerRequest extends Request implements ServerRequestInterface
+ {
+ $uri = new Uri('');
+
+- $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
++ $https = self::getServerParam('HTTPS');
++ $uri = $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+- if (isset($_SERVER['HTTP_HOST'])) {
+- [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
++ $authority = self::getServerParam('HTTP_HOST');
++ if ($authority !== null) {
++ [$host, $port] = self::extractHostAndPortFromAuthority($authority);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+@@ -214,19 +225,21 @@ class ServerRequest extends Request implements ServerRequestInterface
+ $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']);
++ } elseif (($serverName = self::getServerParam('SERVER_NAME')) !== null) {
++ $uri = $uri->withHost($serverName);
++ } elseif (($serverAddr = self::getServerParam('SERVER_ADDR')) !== null) {
++ $uri = $uri->withHost($serverAddr);
+ }
+
+- if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+- $uri = $uri->withPort($_SERVER['SERVER_PORT']);
++ $serverPort = self::getServerParam('SERVER_PORT');
++ if (!$hasPort && $serverPort !== null && preg_match('/^[+-]?\d+$/', $serverPort) === 1) {
++ $uri = $uri->withPort((int) $serverPort);
+ }
+
+ $hasQuery = false;
+- if (isset($_SERVER['REQUEST_URI'])) {
+- $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
++ $requestUri = self::getServerParam('REQUEST_URI');
++ if ($requestUri !== null) {
++ $requestUriParts = explode('?', $requestUri, 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+@@ -234,8 +247,9 @@ class ServerRequest extends Request implements ServerRequestInterface
+ }
+ }
+
+- if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+- $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
++ $queryString = self::getServerParam('QUERY_STRING');
++ if (!$hasQuery && $queryString !== null) {
++ $uri = $uri->withQuery($queryString);
+ }
+
+ return $uri;
+diff --git a/tests/ServerRequestTest.php b/tests/ServerRequestTest.php
+index f770078..dbd2288 100644
+--- a/tests/ServerRequestTest.php
++++ b/tests/ServerRequestTest.php
+@@ -358,10 +358,34 @@ class ServerRequestTest extends TestCase
+ 'https://www.example.org:8324/blog/article.php?id=10&user=foo',
+ array_merge($server, ['SERVER_PORT' => '8324']),
+ ],
++ 'Invalid SERVER_PORT is ignored instead of coerced to zero' => [
++ 'https://www.example.org/blog/article.php?id=10&user=foo',
++ array_merge($server, ['SERVER_PORT' => 'not-a-port']),
++ ],
++ 'Non-string SERVER_PORT is ignored' => [
++ 'https://www.example.org/blog/article.php?id=10&user=foo',
++ array_merge($server, ['SERVER_PORT' => ['443']]),
++ ],
++ 'Non-string HTTP_HOST falls back to SERVER_NAME' => [
++ 'https://www.example.org/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => ['www.example.org']]),
++ ],
++ 'Non-string SERVER_NAME falls back to SERVER_ADDR' => [
++ 'https://217.112.82.20/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => null, 'SERVER_NAME' => ['www.example.org']]),
++ ],
+ 'REQUEST_URI missing query string' => [
+ 'https://www.example.org/blog/article.php?id=10&user=foo',
+ array_merge($server, ['REQUEST_URI' => '/blog/article.php']),
+ ],
++ 'Non-string REQUEST_URI is treated as missing' => [
++ 'https://www.example.org?id=10&user=foo',
++ array_merge($server, ['REQUEST_URI' => ['bad']]),
++ ],
++ 'Non-string QUERY_STRING is treated as missing' => [
++ 'https://www.example.org/blog/article.php',
++ array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => ['bad']]),
++ ],
+ 'Empty server variable' => [
+ 'http://localhost',
+ [],
+@@ -461,6 +485,23 @@ class ServerRequestTest extends TestCase
+ self::assertEquals($expectedFiles, $server->getUploadedFiles());
+ }
+
++ public function testFromGlobalsDefaultsNonStringMethodAndProtocol(): void
++ {
++ $_SERVER = [
++ 'REQUEST_METHOD' => ['POST'],
++ 'SERVER_PROTOCOL' => ['HTTP/1.1'],
++ 'REQUEST_URI' => '/',
++ 'SERVER_PORT' => '80',
++ ];
++
++ $_COOKIE = $_POST = $_GET = $_FILES = [];
++
++ $server = ServerRequest::fromGlobals();
++
++ self::assertSame('GET', $server->getMethod());
++ self::assertSame('1.1', $server->getProtocolVersion());
++ }
++
+ public function testUploadedFiles(): void
+ {
+ $request1 = new ServerRequest('GET', '/');
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0006-Normalize-global-header-values-718.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0006-Normalize-global-header-values-718.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0006-Normalize-global-header-values-718.patch 1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0006-Normalize-global-header-values-718.patch 2026-05-30 13:39:01.000000000 +0200
@@ -0,0 +1,102 @@
+From: Graham Campbell <[email protected]>
+Date: Mon, 25 May 2026 21:50:08 +0100
+Subject: Normalize global header values (#718)
+
+Origin: backport, https://github.com/guzzle/psr7/commit/a0fda818b0f74482925e66e814fe9afb48fd2fa5
+Bug: https://github.com/guzzle/psr7/pull/718
+---
+ src/ServerRequest.php | 28 +++++++++++++++++++++++++++-
+ tests/ServerRequestTest.php | 34 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 61 insertions(+), 1 deletion(-)
+
+diff --git a/src/ServerRequest.php b/src/ServerRequest.php
+index bc795fc..341af9c 100644
+--- a/src/ServerRequest.php
++++ b/src/ServerRequest.php
+@@ -166,7 +166,7 @@ class ServerRequest extends Request implements ServerRequestInterface
+ public static function fromGlobals(): ServerRequestInterface
+ {
+ $method = self::getServerParam('REQUEST_METHOD') ?? 'GET';
+- $headers = getallheaders();
++ $headers = self::getAllHeaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $serverProtocol = self::getServerParam('SERVER_PROTOCOL');
+@@ -181,6 +181,32 @@ class ServerRequest extends Request implements ServerRequestInterface
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
++ /**
++ * @return array<array-key, string>
++ */
++ private static function getAllHeaders(): array
++ {
++ return self::normalizeHeaderValues(getallheaders());
++ }
++
++ /**
++ * @param array<array-key, mixed> $headers
++ *
++ * @return array<array-key, string>
++ */
++ private static function normalizeHeaderValues(array $headers): array
++ {
++ $normalized = [];
++
++ foreach ($headers as $name => $value) {
++ if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
++ $normalized[$name] = (string) $value;
++ }
++ }
++
++ return $normalized;
++ }
++
+ private static function getServerParam(string $key): ?string
+ {
+ return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null;
+diff --git a/tests/ServerRequestTest.php b/tests/ServerRequestTest.php
+index dbd2288..939ab19 100644
+--- a/tests/ServerRequestTest.php
++++ b/tests/ServerRequestTest.php
+@@ -485,6 +485,40 @@ class ServerRequestTest extends TestCase
+ self::assertEquals($expectedFiles, $server->getUploadedFiles());
+ }
+
++ public function testFromGlobalsNormalizesUnexpectedHeaderValueTypes(): void
++ {
++ $_SERVER = [
++ 'REQUEST_URI' => '/',
++ 'HTTP_HOST' => 'www.example.org',
++ 'HTTP_X_INT' => 123,
++ 'HTTP_X_FLOAT' => 1.5,
++ 'HTTP_X_FALSE' => false,
++ 'HTTP_X_TRUE' => true,
++ 'HTTP_X_STRINGABLE' => new class {
++ public function __toString(): string
++ {
++ return 'stringable';
++ }
++ },
++ 'HTTP_X_ARRAY' => ['bad'],
++ 'HTTP_X_OBJECT' => new \stdClass(),
++ 'HTTP_123' => 'numeric header',
++ ];
++
++ $_COOKIE = $_POST = $_GET = $_FILES = [];
++
++ $server = ServerRequest::fromGlobals();
++
++ self::assertSame('123', $server->getHeaderLine('X-Int'));
++ self::assertSame('1.5', $server->getHeaderLine('X-Float'));
++ self::assertSame([''], $server->getHeader('X-False'));
++ self::assertSame('1', $server->getHeaderLine('X-True'));
++ self::assertSame('stringable', $server->getHeaderLine('X-Stringable'));
++ self::assertSame('numeric header', $server->getHeaderLine('123'));
++ self::assertFalse($server->hasHeader('X-Array'));
++ self::assertFalse($server->hasHeader('X-Object'));
++ }
++
+ public function testFromGlobalsDefaultsNonStringMethodAndProtocol(): void
+ {
+ $_SERVER = [
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0007-Reject-malformed-Host-authorities-717.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0007-Reject-malformed-Host-authorities-717.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0007-Reject-malformed-Host-authorities-717.patch 1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0007-Reject-malformed-Host-authorities-717.patch 2026-05-30 13:39:01.000000000 +0200
@@ -0,0 +1,476 @@
+From: Graham Campbell <[email protected]>
+Date: Mon, 25 May 2026 22:42:00 +0100
+Subject: Reject malformed Host authorities (#717)
+
+* Reject malformed Host authorities
+
+* Tighten Host authority validation
+
+* Mark Host authority fix as security
+
+* Remove redundant Host header type check
+
+* Share Host header authority parser
+
+* Move RFC 3986 character classes
+
+* Order RFC 3986 constants by section
+
+* Remove unused Host port flag
+
+* Avoid port zero parseRequest case
+
+Origin: backport, https://github.com/guzzle/psr7/commit/c68fe44ea6b56eb0a7ebdeb9012fb7efbc37c2d3
+Bug: https://github.com/guzzle/psr7/security/advisories/GHSA-34xg-wgjx-8xph
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-48998
+---
+ src/Message.php | 26 +++++++++++---
+ src/Rfc3986.php | 25 ++++++++++++++
+ src/Rfc7230.php | 83 +++++++++++++++++++++++++++++++++++++++++++++
+ src/ServerRequest.php | 31 +++++++++++------
+ src/Uri.php | 19 ++---------
+ tests/MessageTest.php | 56 ++++++++++++++++++++++++++++++
+ tests/ServerRequestTest.php | 78 ++++++++++++++++++++++++++++++++++++++++++
+ 7 files changed, 288 insertions(+), 30 deletions(-)
+ create mode 100644 src/Rfc3986.php
+
+diff --git a/src/Message.php b/src/Message.php
+index 5561a51..d3aafca 100644
+--- a/src/Message.php
++++ b/src/Message.php
+@@ -174,6 +174,23 @@ final class Message
+ * @param array $headers Array of headers (each value an array).
+ */
+ public static function parseRequestUri(string $path, array $headers): string
++ {
++ $host = self::getHostFromHeaders($headers);
++
++ // If no host is found, then a full URI cannot be constructed.
++ if ($host === null) {
++ return $path;
++ }
++
++ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
++
++ return $scheme.'://'.$host.'/'.ltrim($path, '/');
++ }
++
++ /**
++ * @param array $headers Array of headers (each value an array).
++ */
++ private static function getHostFromHeaders(array $headers): ?string
+ {
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ // Numeric array keys are converted to int by PHP.
+@@ -182,15 +199,16 @@ final class Message
+ return strtolower($k) === 'host';
+ });
+
+- // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+- return $path;
++ return null;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+- $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
++ if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) {
++ throw new \InvalidArgumentException('Invalid request string');
++ }
+
+- return $scheme.'://'.$host.'/'.ltrim($path, '/');
++ return $host;
+ }
+
+ /**
+diff --git a/src/Rfc3986.php b/src/Rfc3986.php
+new file mode 100644
+index 0000000..6cc2ec0
+--- /dev/null
++++ b/src/Rfc3986.php
+@@ -0,0 +1,25 @@
++<?php
++
++declare(strict_types=1);
++
++namespace GuzzleHttp\Psr7;
++
++/**
++ * @internal
++ */
++final class Rfc3986
++{
++ /**
++ * Sub-delims for use in a regex.
++ *
++ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
++ */
++ public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
++
++ /**
++ * Unreserved characters for use in a regex.
++ *
++ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
++ */
++ public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
++}
+diff --git a/src/Rfc7230.php b/src/Rfc7230.php
+index 8219dba..9cd4863 100644
+--- a/src/Rfc7230.php
++++ b/src/Rfc7230.php
+@@ -20,4 +20,87 @@ final class Rfc7230
+ */
+ public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
++
++ /**
++ * @return array{0: string, 1: int|null}|null
++ */
++ public static function parseHostHeader(string $authority): ?array
++ {
++ if ($authority === '') {
++ return null;
++ }
++
++ $host = $authority;
++ $port = null;
++
++ if ($authority[0] === '[') {
++ $closingBracket = strpos($authority, ']');
++ if ($closingBracket === false) {
++ return null;
++ }
++
++ $host = substr($authority, 0, $closingBracket + 1);
++ $remainder = substr($authority, $closingBracket + 1);
++ if ($remainder !== '') {
++ if ($remainder[0] !== ':') {
++ return null;
++ }
++
++ $port = self::parseAuthorityPort(substr($remainder, 1));
++ if ($port === null) {
++ return null;
++ }
++ }
++ } elseif (false !== ($colon = strpos($authority, ':'))) {
++ $host = substr($authority, 0, $colon);
++ $port = self::parseAuthorityPort(substr($authority, $colon + 1));
++ if ($port === null) {
++ return null;
++ }
++ }
++
++ if ($host === '' || !self::isValidHostHeaderHost($host)) {
++ return null;
++ }
++
++ return [$host, $port];
++ }
++
++ private static function isValidHostHeaderHost(string $host): bool
++ {
++ if (preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host)) {
++ return false;
++ }
++
++ if (strpos($host, '[') !== false || strpos($host, ']') !== false) {
++ if ($host[0] !== '[' || substr($host, -1) !== ']') {
++ return false;
++ }
++
++ $address = substr($host, 1, -1);
++
++ return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false
++ || preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1;
++ }
++
++ return strpos($host, ':') === false;
++ }
++
++ private static function parseAuthorityPort(string $port): ?int
++ {
++ if ($port === '' || !ctype_digit($port)) {
++ return null;
++ }
++
++ $normalized = ltrim($port, '0');
++ if ($normalized === '') {
++ return 0;
++ }
++
++ if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) {
++ return null;
++ }
++
++ return (int) $normalized;
++ }
+ }
+diff --git a/src/ServerRequest.php b/src/ServerRequest.php
+index 341af9c..3f87f96 100644
+--- a/src/ServerRequest.php
++++ b/src/ServerRequest.php
+@@ -166,7 +166,7 @@ class ServerRequest extends Request implements ServerRequestInterface
+ public static function fromGlobals(): ServerRequestInterface
+ {
+ $method = self::getServerParam('REQUEST_METHOD') ?? 'GET';
+- $headers = self::getAllHeaders();
++ $headers = self::removeInvalidHostHeader(self::getAllHeaders());
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $serverProtocol = self::getServerParam('SERVER_PROTOCOL');
+@@ -213,20 +213,31 @@ class ServerRequest extends Request implements ServerRequestInterface
+ }
+
+ /**
+- * @return array{0: string|null, 1: int|null}
++ * @param array<array-key, string> $headers
++ *
++ * @return array<array-key, string>
+ */
+- private static function extractHostAndPortFromAuthority(string $authority): array
++ private static function removeInvalidHostHeader(array $headers): array
+ {
+- $uri = 'http://'.$authority;
+- $parts = parse_url($uri);
+- if (!is_array($parts)) {
+- return [null, null];
++ foreach ($headers as $name => $value) {
++ if (strtolower((string) $name) !== 'host') {
++ continue;
++ }
++
++ if (Rfc7230::parseHostHeader($value) === null) {
++ unset($headers[$name]);
++ }
+ }
+
+- $host = $parts['host'] ?? null;
+- $port = $parts['port'] ?? null;
++ return $headers;
++ }
+
+- return [$host, $port];
++ /**
++ * @return array{0: string|null, 1: int|null}
++ */
++ private static function extractHostAndPortFromAuthority(string $authority): array
++ {
++ return Rfc7230::parseHostHeader($authority) ?? [null, null];
+ }
+
+ /**
+diff --git a/src/Uri.php b/src/Uri.php
+index bef82e2..54bbb0a 100644
+--- a/src/Uri.php
++++ b/src/Uri.php
+@@ -38,19 +38,6 @@ class Uri implements UriInterface, \JsonSerializable
+ 'ldap' => 389,
+ ];
+
+- /**
+- * Unreserved characters for use in a regex.
+- *
+- * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
+- */
+- private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
+-
+- /**
+- * Sub-delims for use in a regex.
+- *
+- * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+- */
+- private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
+ private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
+
+ /** @var string Uri scheme. */
+@@ -596,7 +583,7 @@ class Uri implements UriInterface, \JsonSerializable
+ }
+
+ return preg_replace_callback(
+- '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
++ '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+@@ -695,7 +682,7 @@ class Uri implements UriInterface, \JsonSerializable
+ }
+
+ return preg_replace_callback(
+- '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
++ '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+@@ -715,7 +702,7 @@ class Uri implements UriInterface, \JsonSerializable
+ }
+
+ return preg_replace_callback(
+- '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
++ '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+diff --git a/tests/MessageTest.php b/tests/MessageTest.php
+index 1a43152..42909be 100644
+--- a/tests/MessageTest.php
++++ b/tests/MessageTest.php
+@@ -107,6 +107,62 @@ class MessageTest extends TestCase
+ self::assertSame('http://foo.com/', (string) $request->getUri());
+ }
+
++ /**
++ * @dataProvider invalidHostHeaderProvider
++ */
++ public function testParseRequestRejectsInvalidHostHeader(string $host): void
++ {
++ $this->expectException(\InvalidArgumentException::class);
++
++ Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n");
++ }
++
++ public static function invalidHostHeaderProvider(): iterable
++ {
++ yield 'userinfo delimiter' => ['[email protected]'];
++ yield 'path delimiter' => ['example.com/path'];
++ yield 'query delimiter' => ['example.com?query'];
++ yield 'fragment delimiter' => ['example.com#fragment'];
++ yield 'backslash delimiter' => ['example.com\\evil'];
++ yield 'space' => ['bad host'];
++ yield 'tab' => ["bad\thost"];
++ yield 'control character' => ['example'.chr(1).'com'];
++ yield 'delete' => ['example'.chr(0x7F).'com'];
++ yield 'multiple ports' => ['example.com:443:8443'];
++ yield 'missing closing bracket' => ['[::1'];
++ yield 'unexpected bracket suffix' => ['[::1]x'];
++ yield 'invalid ip literal' => ['[bad]'];
++ yield 'unexpected opening bracket' => ['foo[bar'];
++ yield 'unexpected closing bracket' => ['foo]bar'];
++ }
++
++ /**
++ * @dataProvider validHostHeaderProvider
++ */
++ public function testParseRequestAcceptsValidHostHeader(string $host, string $expectedUri): void
++ {
++ $request = Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n");
++
++ self::assertSame($host, $request->getHeaderLine('Host'));
++ self::assertSame($expectedUri, (string) $request->getUri());
++ }
++
++ public static function validHostHeaderProvider(): iterable
++ {
++ yield 'host' => ['foo.com', 'http://foo.com/'];
++ yield 'https default port' => ['foo.com:443', 'https://foo.com/'];
++ yield 'non-default port' => ['foo.com:8080', 'http://foo.com:8080/'];
++ yield 'ipv6' => ['[::1]', 'http://[::1]/'];
++ yield 'ipv6 port' => ['[::1]:443', 'https://[::1]/'];
++ }
++
++ public function testParseRequestAcceptsMissingHostHeader(): void
++ {
++ $request = Psr7\Message::parseRequest("GET /abc HTTP/1.1\r\nFoo: bar\r\n\r\n");
++
++ self::assertSame('/abc', (string) $request->getUri());
++ }
++
+ public function testParsesRequestMessagesWithFullUri(): void
+ {
+ $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
+diff --git a/tests/ServerRequestTest.php b/tests/ServerRequestTest.php
+index 939ab19..914f949 100644
+--- a/tests/ServerRequestTest.php
++++ b/tests/ServerRequestTest.php
+@@ -354,6 +354,46 @@ class ServerRequestTest extends TestCase
+ 'https://localhost/blog/article.php?id=10&user=foo',
+ array_merge($server, ['HTTP_HOST' => 'a:b']),
+ ],
++ 'Host header with userinfo delimiter' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => '[email protected]']),
++ ],
++ 'Host header with path delimiter' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'example.com/path']),
++ ],
++ 'Host header with query delimiter' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'example.com?x=1']),
++ ],
++ 'Host header with fragment delimiter' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'example.com#frag']),
++ ],
++ 'Host header with backslash delimiter' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'example.com\\evil']),
++ ],
++ 'Host header with space' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'bad host']),
++ ],
++ 'Host header with multiple ports' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'example.com:80:90']),
++ ],
++ 'Host header with invalid ip literal' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => '[bad]']),
++ ],
++ 'Host header with unexpected opening bracket' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'foo[bar']),
++ ],
++ 'Host header with unexpected closing bracket' => [
++ 'https://localhost/blog/article.php?id=10&user=foo',
++ array_merge($server, ['HTTP_HOST' => 'foo]bar']),
++ ],
+ 'Different port with SERVER_PORT' => [
+ 'https://www.example.org:8324/blog/article.php?id=10&user=foo',
+ array_merge($server, ['SERVER_PORT' => '8324']),
+@@ -536,6 +576,44 @@ class ServerRequestTest extends TestCase
+ self::assertSame('1.1', $server->getProtocolVersion());
+ }
+
++ /**
++ * @dataProvider invalidHostHeaderFromGlobalsProvider
++ */
++ public function testFromGlobalsDropsInvalidHostHeaderWhenUriFallsBack(string $host): void
++ {
++ if (!\function_exists('getallheaders')) {
++ self::markTestSkipped('getallheaders() is not available.');
++ }
++
++ $_SERVER = [
++ 'REQUEST_URI' => '/',
++ 'HTTP_HOST' => $host,
++ 'SERVER_PORT' => '443',
++ 'HTTPS' => 'on',
++ ];
++
++ $_COOKIE = $_POST = $_GET = $_FILES = [];
++
++ $request = ServerRequest::fromGlobals();
++
++ self::assertSame('localhost', $request->getUri()->getHost());
++ self::assertSame('localhost', $request->getHeaderLine('Host'));
++ }
++
++ public static function invalidHostHeaderFromGlobalsProvider(): iterable
++ {
++ yield 'userinfo delimiter' => ['[email protected]'];
++ yield 'path delimiter' => ['example.com/path'];
++ yield 'query delimiter' => ['example.com?x=1'];
++ yield 'fragment delimiter' => ['example.com#frag'];
++ yield 'backslash delimiter' => ['example.com\\evil'];
++ yield 'space' => ['bad host'];
++ yield 'multiple ports' => ['example.com:80:90'];
++ yield 'invalid ip literal' => ['[bad]'];
++ yield 'unexpected opening bracket' => ['foo[bar'];
++ yield 'unexpected closing bracket' => ['foo]bar'];
++ }
++
+ public function testUploadedFiles(): void
+ {
+ $request1 = new ServerRequest('GET', '/');
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/0008-Reject-control-characters-in-URI-hosts-715.patch php-guzzlehttp-psr7-2.7.1/debian/patches/0008-Reject-control-characters-in-URI-hosts-715.patch
--- php-guzzlehttp-psr7-2.7.1/debian/patches/0008-Reject-control-characters-in-URI-hosts-715.patch 1970-01-01 01:00:00.000000000 +0100
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/0008-Reject-control-characters-in-URI-hosts-715.patch 2026-05-30 13:39:01.000000000 +0200
@@ -0,0 +1,185 @@
+From: Graham Campbell <[email protected]>
+Date: Mon, 25 May 2026 21:16:23 +0100
+Subject: Reject control characters in URI hosts (#715)
+
+* Reject control characters in URI hosts
+
+* Validate generated Host header values
+
+* Mark URI host fix as security
+
+Origin: backport, https://github.com/guzzle/psr7/commit/12caca7f2302477216a460fabf93a92659835a06
+Bug: https://github.com/guzzle/psr7/security/advisories/GHSA-hq7v-mx3g-29hw
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-49214
+---
+ src/Request.php | 4 ++++
+ src/Uri.php | 39 +++++++++++++++++++++++++++++++++++----
+ tests/RequestTest.php | 23 +++++++++++++++++++++++
+ tests/UriTest.php | 26 ++++++++++++++++++++++++++
+ 4 files changed, 88 insertions(+), 4 deletions(-)
+
+diff --git a/src/Request.php b/src/Request.php
+index faafe1a..743c5d2 100644
+--- a/src/Request.php
++++ b/src/Request.php
+@@ -132,10 +132,14 @@ class Request implements RequestInterface
+ return;
+ }
+
++ Uri::assertValidHost($host);
++
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':'.$port;
+ }
+
++ $this->assertValue($host);
++
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+diff --git a/src/Uri.php b/src/Uri.php
+index 54bbb0a..b26e12d 100644
+--- a/src/Uri.php
++++ b/src/Uri.php
+@@ -71,7 +71,13 @@ class Uri implements UriInterface, \JsonSerializable
+ if ($parts === false) {
+ throw new MalformedUriException("Unable to parse URI: $uri");
+ }
+- $this->applyParts($parts);
++ try {
++ $this->applyParts($parts);
++ } catch (MalformedUriException $e) {
++ throw $e;
++ } catch (\InvalidArgumentException $e) {
++ throw new MalformedUriException($e->getMessage(), 0, $e);
++ }
+ }
+ }
+
+@@ -347,12 +353,34 @@ class Uri implements UriInterface, \JsonSerializable
+ public static function fromParts(array $parts): UriInterface
+ {
+ $uri = new self();
+- $uri->applyParts($parts);
+- $uri->validateState();
++ try {
++ $uri->applyParts($parts);
++ $uri->validateState();
++ } catch (MalformedUriException $e) {
++ throw $e;
++ } catch (\InvalidArgumentException $e) {
++ throw new MalformedUriException($e->getMessage(), 0, $e);
++ }
+
+ return $uri;
+ }
+
++ /**
++ * @throws \InvalidArgumentException If the host is invalid.
++ *
++ * @internal
++ */
++ public static function assertValidHost(string $host): void
++ {
++ if ($host === '') {
++ return;
++ }
++
++ if (preg_match('/[\x00-\x20\x7F]/', $host)) {
++ throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host));
++ }
++ }
++
+ public function getScheme(): string
+ {
+ return $this->scheme;
+@@ -600,7 +628,10 @@ class Uri implements UriInterface, \JsonSerializable
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+- return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
++ $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
++ self::assertValidHost($host);
++
++ return $host;
+ }
+
+ /**
+diff --git a/tests/RequestTest.php b/tests/RequestTest.php
+index 19d71c3..3a62c57 100644
+--- a/tests/RequestTest.php
++++ b/tests/RequestTest.php
+@@ -9,6 +9,7 @@ use GuzzleHttp\Psr7\Request;
+ use GuzzleHttp\Psr7\Uri;
+ use PHPUnit\Framework\TestCase;
+ use Psr\Http\Message\StreamInterface;
++use Psr\Http\Message\UriInterface;
+
+ /**
+ * @covers \GuzzleHttp\Psr7\MessageTrait
+@@ -306,6 +307,28 @@ class RequestTest extends TestCase
+ self::assertSame('foo.com:8125', $r->getHeaderLine('host'));
+ }
+
++ public function testGeneratedHostHeaderRejectsInvalidUriHostFromCustomUri(): void
++ {
++ $uri = $this->createMock(UriInterface::class);
++ $uri->method('getHost')->willReturn("foo\nbar");
++ $uri->method('getPort')->willReturn(null);
++
++ $this->expectException(\InvalidArgumentException::class);
++
++ new Request('GET', $uri);
++ }
++
++ public function testGeneratedHostHeaderValidatesAssembledHostWithPort(): void
++ {
++ $uri = $this->createMock(UriInterface::class);
++ $uri->method('getHost')->willReturn('example.com');
++ $uri->method('getPort')->willReturn(8080);
++
++ $request = new Request('GET', $uri);
++
++ self::assertSame('example.com:8080', $request->getHeaderLine('Host'));
++ }
++
+ /**
+ * @dataProvider provideHeaderValuesContainingNotAllowedChars
+ */
+diff --git a/tests/UriTest.php b/tests/UriTest.php
+index 3fb0716..da229a7 100644
+--- a/tests/UriTest.php
++++ b/tests/UriTest.php
+@@ -153,6 +153,32 @@ class UriTest extends TestCase
+ (new Uri())->withHost([]);
+ }
+
++ /**
++ * @dataProvider getInvalidHostsWithControlCharacters
++ */
++ public function testHostMustRejectControlCharacters(string $host): void
++ {
++ $this->expectException(\InvalidArgumentException::class);
++
++ (new Uri())->withHost($host);
++ }
++
++ public static function getInvalidHostsWithControlCharacters(): iterable
++ {
++ for ($i = 0; $i <= 0x20; ++$i) {
++ yield 'ascii 0x'.strtoupper(dechex($i)) => ['example'.chr($i).'com'];
++ }
++
++ yield 'ascii 0x7F' => ['example'.chr(0x7F).'com'];
++ }
++
++ public function testParseUriRejectsHostWithControlCharacter(): void
++ {
++ $this->expectException(MalformedUriException::class);
++
++ new Uri("http://example.com\r\nX-Injected:%20yes/");
++ }
++
+ public function testPathMustHaveCorrectType(): void
+ {
+ $this->expectException(\InvalidArgumentException::class);
diff -Nru php-guzzlehttp-psr7-2.7.1/debian/patches/series php-guzzlehttp-psr7-2.7.1/debian/patches/series
--- php-guzzlehttp-psr7-2.7.1/debian/patches/series 2025-03-31 12:18:24.000000000 +0200
+++ php-guzzlehttp-psr7-2.7.1/debian/patches/series 2026-05-30 13:39:01.000000000 +0200
@@ -1,4 +1,9 @@
0001-Adapt-path-to-Debian-expectations.patch
0002-Compatibility-with-recent-PHPUnit-10.patch
0003-Make-provider-functions-static-PHPUnit-11-Fix.patch
+0004-2.8-Encode-plus-sign-in-withQueryValue-and-withQuery.patch
+0005-Harden-ServerRequest-globals-handling-660.patch
+0006-Normalize-global-header-values-718.patch
+0007-Reject-malformed-Host-authorities-717.patch
+0008-Reject-control-characters-in-URI-hosts-715.patch
0004-Modernize-PHPUnit-syntax.patch
signature.asc
Description: PGP signature
--- End Message ---