From: Graham Campbell <GrahamCampbell@users.noreply.github.com>
Date: Sun, 20 Mar 2022 13:44:44 +0000
Subject: Release 1.8.4 (#486)
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit

Co-authored-by: Tim Düsterhus <tim@bastelstu.be>

Origin: backport, https://github.com/guzzle/psr7/commit/902db15a551a4a415e732b622282e21ce1b508b4
---
 src/MessageTrait.php | 56 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 49 insertions(+), 7 deletions(-)

diff --git a/src/MessageTrait.php b/src/MessageTrait.php
index 1e4da64..f5f61db 100644
--- a/src/MessageTrait.php
+++ b/src/MessageTrait.php
@@ -70,7 +70,7 @@ trait MessageTrait
             $value = [$value];
         }
 
-        $value = $this->trimHeaderValues($value);
+        $value = $this->trimAndValidateHeaderValues($value);
         $normalized = strtolower($header);
 
         $new = clone $this;
@@ -89,7 +89,7 @@ trait MessageTrait
             $value = [$value];
         }
 
-        $value = $this->trimHeaderValues($value);
+        $value = $this->trimAndValidateHeaderValues($value);
         $normalized = strtolower($header);
 
         $new = clone $this;
@@ -148,7 +148,7 @@ trait MessageTrait
                 $value = [$value];
             }
 
-            $value = $this->trimHeaderValues($value);
+            $value = $this->trimAndValidateHeaderValues($value);
             $normalized = strtolower($header);
             if (isset($this->headerNames[$normalized])) {
                 $header = $this->headerNames[$normalized];
@@ -168,16 +168,58 @@ trait MessageTrait
      * header-field = field-name ":" OWS field-value OWS
      * OWS          = *( SP / HTAB )
      *
-     * @param string[] $values Header values
+     * @param mixed[] $values Header values
      *
      * @return string[] Trimmed header values
      *
      * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
      */
-    private function trimHeaderValues(array $values)
+    private function trimAndValidateHeaderValues(array $values)
     {
         return array_map(function ($value) {
-            return trim($value, " \t");
-        }, $values);
+            if (!is_scalar($value) && null !== $value) {
+                throw new \InvalidArgumentException(sprintf(
+                    'Header value must be scalar or null but %s provided.',
+                    is_object($value) ? get_class($value) : gettype($value)
+                ));
+            }
+
+            $trimmed = trim((string) $value, " \t");
+            $this->assertValue($trimmed);
+
+            return $trimmed;
+        }, array_values($values));
+    }
+
+    /**
+     * @param string $value
+     *
+     * @return void
+     *
+     * @see https://tools.ietf.org/html/rfc7230#section-3.2
+     *
+     * field-value    = *( field-content / obs-fold )
+     * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+     * field-vchar    = VCHAR / obs-text
+     * VCHAR          = %x21-7E
+     * obs-text       = %x80-FF
+     * obs-fold       = CRLF 1*( SP / HTAB )
+     */
+    private function assertValue($value)
+    {
+        // The regular expression intentionally does not support the obs-fold production, because as
+        // per RFC 7230#3.2.4:
+        //
+        // A sender MUST NOT generate a message that includes
+        // line folding (i.e., that has any field-value that contains a match to
+        // the obs-fold rule) unless the message is intended for packaging
+        // within the message/http media type.
+        //
+        // Clients must not send a request with line folding and a server sending folded headers is
+        // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
+        // folding is not likely to break any legitimate use case.
+        if (! preg_match('/^(?:[\x21-\x7E\x80-\xFF](?:[\x20\x09]+[\x21-\x7E\x80-\xFF])?)*$/', $value)) {
+            throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value));
+        }
     }
 }
