Repository: guzzle/psr7 (php)

Issue:
Relative path with authority part present fails
Why relative paths forbidden when authority part is present?

https://github.com/guzzle/psr7/blob/69568e4293f4fa993f3b0e51c9723e1e17c41379/tests/UriTest.php#L599-L605

This violates [withPath](https://github.com/php-fig/http-message/blob/efd67d1dc14a7ef4fc4e518e7dee91c271d524e4/src/UriInterface.php#L242-L264) contract. Also there's [clear description](https://github.com/php-fig/http-message/blob/efd67d1dc14a7ef4fc4e518e7dee91c271d524e4/src/UriInterface.php#L312-L313) how to avoid problem described in the aforementioned code comment.

Pull request description:
Fix erroneous behaviour when combining host and relative path
Fixes #531

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83d7d30..339b075 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -103,7 +103,7 @@ Identical to the RC release.
 ### Removed
 
 - PHP < 7.2 support
-- All functions in the Guzzle\Psr7 namespace
+- All functions in the `GuzzleHttp\Psr7` namespace
 
 ## 1.8.1 - 2021-03-21
 
diff --git a/README.md b/README.md
index eea803a..8b9929a 100644
--- a/README.md
+++ b/README.md
@@ -380,10 +380,28 @@ of the header. When a parameter does not contain a value, but just
 contains a key, this function will inject a key with a '' string value.
 
 
-## `GuzzleHttp\Psr7\Header::normalize`
+## `GuzzleHttp\Psr7\Header::splitList`
+
+`public static function splitList(string|string[] $header): string[]`
+
+Splits a HTTP header defined to contain a comma-separated list into
+each individual value:
+
+```
+$knownEtags = Header::splitList($request->getHeader('if-none-match'));
+```
+
+Example headers include `accept`, `cache-control` and `if-none-match`.
+
+
+## `GuzzleHttp\Psr7\Header::normalize` (deprecated)
 
 `public static function normalize(string|array $header): array`
 
+`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist)
+which performs the same operation with a cleaned up API and improved
+documentation.
+
 Converts an array of header values that may contain comma separated
 headers into an array of headers with no comma separated values.
 
diff --git a/src/Header.php b/src/Header.php
index 10744ed..4d7005b 100644
--- a/src/Header.php
+++ b/src/Header.php
@@ -62,7 +62,7 @@ final class Header
     }
 
     /**
-     * Splits a HTTP header defined to contain comma-separated list into
+     * Splits a HTTP header defined to contain a comma-separated list into
      * each individual value. Empty values will be removed.
      *
      * Example headers include 'accept', 'cache-control' and 'if-none-match'.
diff --git a/src/Uri.php b/src/Uri.php
index 5c6416a..09e878d 100644
--- a/src/Uri.php
+++ b/src/Uri.php
@@ -172,10 +172,14 @@ class Uri implements UriInterface, \JsonSerializable
             $uri .= $scheme . ':';
         }
 
-        if ($authority != ''|| $scheme === 'file') {
+        if ($authority != '' || $scheme === 'file') {
             $uri .= '//' . $authority;
         }
 
+        if ($authority != '' && $path != '' && $path[0] != '/') {
+            $path = '/' . $path;
+        }
+
         $uri .= $path;
 
         if ($query != '') {
@@ -731,8 +735,6 @@ class Uri implements UriInterface, \JsonSerializable
             if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
                 throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
             }
-        } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
-            throw new MalformedUriException('The path of a URI with an authority must start with a slash "/" or be empty');
         }
     }
 }


Test patch:
diff --git a/tests/UriTest.php b/tests/UriTest.php
index 29e0130..6484a24 100644
--- a/tests/UriTest.php
+++ b/tests/UriTest.php
@@ -596,12 +596,11 @@ class UriTest extends TestCase
         self::assertSame('foo', (string) $uri);
     }
 
-    public function testRelativePathAndAuthorityThrowsException(): void
+    public function testRelativePathAndAuthority(): void
     {
-        // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('The path of a URI with an authority must start with a slash "/" or be empty');
-        (new Uri)->withHost('example.com')->withPath('foo');
+        $uri = (new Uri())->withHost('example.com')->withPath('foo');
+        self::assertSame('foo', $uri->getPath());
+        self::assertSame('//example.com/foo', $uri->__toString());
     }
 
     public function testPathStartingWithTwoSlashesAndNoAuthorityIsInvalid(): void