@@ -157,17 +157,22 @@ private function setHeaders(array $headers)
157157 }
158158 }
159159
160+ /**
161+ * @param mixed $value
162+ *
163+ * @return string[]
164+ */
160165 private function normalizeHeaderValue ($ value )
161166 {
162167 if (!is_array ($ value )) {
163- return $ this ->trimHeaderValues ([$ value ]);
168+ return $ this ->trimAndValidateHeaderValues ([$ value ]);
164169 }
165170
166171 if (count ($ value ) === 0 ) {
167172 throw new \InvalidArgumentException ('Header value can not be an empty array. ' );
168173 }
169174
170- return $ this ->trimHeaderValues ($ value );
175+ return $ this ->trimAndValidateHeaderValues ($ value );
171176 }
172177
173178 /**
@@ -178,13 +183,13 @@ private function normalizeHeaderValue($value)
178183 * header-field = field-name ":" OWS field-value OWS
179184 * OWS = *( SP / HTAB )
180185 *
181- * @param string [] $values Header values
186+ * @param mixed [] $values Header values
182187 *
183188 * @return string[] Trimmed header values
184189 *
185190 * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
186191 */
187- private function trimHeaderValues (array $ values )
192+ private function trimAndValidateHeaderValues (array $ values )
188193 {
189194 return array_map (function ($ value ) {
190195 if (!is_scalar ($ value ) && null !== $ value ) {
@@ -194,10 +199,20 @@ private function trimHeaderValues(array $values)
194199 ));
195200 }
196201
197- return trim ((string ) $ value , " \t" );
202+ $ trimmed = trim ((string ) $ value , " \t" );
203+ $ this ->assertValue ($ trimmed );
204+
205+ return $ trimmed ;
198206 }, array_values ($ values ));
199207 }
200208
209+ /**
210+ * @see https://tools.ietf.org/html/rfc7230#section-3.2
211+ *
212+ * @param mixed $header
213+ *
214+ * @return void
215+ */
201216 private function assertHeader ($ header )
202217 {
203218 if (!is_string ($ header )) {
@@ -210,5 +225,46 @@ private function assertHeader($header)
210225 if ($ header === '' ) {
211226 throw new \InvalidArgumentException ('Header name can not be empty. ' );
212227 }
228+
229+ if (! preg_match ('/^[a-zA-Z0-9 \'`#$%&*+.^_|~!-]+$/ ' , $ header )) {
230+ throw new \InvalidArgumentException (
231+ sprintf (
232+ '"%s" is not valid header name ' ,
233+ $ header
234+ )
235+ );
236+ }
237+ }
238+
239+ /**
240+ * @param string $value
241+ *
242+ * @return void
243+ *
244+ * @see https://tools.ietf.org/html/rfc7230#section-3.2
245+ *
246+ * field-value = *( field-content / obs-fold )
247+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
248+ * field-vchar = VCHAR / obs-text
249+ * VCHAR = %x21-7E
250+ * obs-text = %x80-FF
251+ * obs-fold = CRLF 1*( SP / HTAB )
252+ */
253+ private function assertValue ($ value )
254+ {
255+ // The regular expression intentionally does not support the obs-fold production, because as
256+ // per RFC 7230#3.2.4:
257+ //
258+ // A sender MUST NOT generate a message that includes
259+ // line folding (i.e., that has any field-value that contains a match to
260+ // the obs-fold rule) unless the message is intended for packaging
261+ // within the message/http media type.
262+ //
263+ // Clients must not send a request with line folding and a server sending folded headers is
264+ // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
265+ // folding is not likely to break any legitimate use case.
266+ if (! preg_match ('/^(?:[\x21-\x7E\x80-\xFF](?:[\x20\x09]+[\x21-\x7E\x80-\xFF])?)*$/ ' , $ value )) {
267+ throw new \InvalidArgumentException (sprintf ('"%s" is not valid header value ' , $ value ));
268+ }
213269 }
214270}
0 commit comments