@@ -30,58 +30,49 @@ public static function normalize(iterable $headers):array{
3030
3131 foreach ($ headers as $ key => $ val ){
3232
33- // the key is numeric, so $val is either a string or an array
33+ // the key is numeric, so $val is either a string or an array that contains both
3434 if (is_numeric ($ key )){
35- // "key: val"
36- if (is_string ($ val )){
37- $ header = explode (': ' , $ val , 2 );
35+ [$ key , $ val ] = self ::normalizeKV ($ val );
3836
39- if (count ($ header ) !== 2 ){
40- continue ;
41- }
42-
43- $ key = $ header [0 ];
44- $ val = $ header [1 ];
37+ if ($ key === null ){
38+ continue ;
4539 }
46- // [$key, $val], ["key" => $key, "val" => $val]
47- elseif (is_array ($ val ) && !empty ($ val )){
48- $ key = array_keys ($ val )[0 ];
49- $ val = array_values ($ val )[0 ];
50-
51- // skip if the key is numeric
52- if (is_numeric ($ key )){
53- continue ;
40+ }
41+
42+ $ key = self ::normalizeHeaderName ($ key );
43+
44+ // cookie headers may appear multiple times - we'll just collect the last value here
45+ // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2
46+ if ($ key === 'Set-Cookie ' ){
47+ $ name = fn (string $ v ):string => trim (strtolower (explode ('= ' , $ v , 2 )[0 ]));
48+
49+ // array received from Message::getHeaders()
50+ if (is_array ($ val )){
51+ foreach ($ val as $ line ){
52+ $ normalized [$ key ][$ name ($ line )] = trim ($ line );
5453 }
5554 }
5655 else {
57- continue ;
56+ $ normalized [ $ key ][ $ name ( $ val )] = trim ( $ val ) ;
5857 }
5958 }
60- // the key is named, so we assume $val holds the header values only, either as string or array
59+ // combine header fields with the same name
60+ // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
6161 else {
62+
63+ // the key is named, so we assume $val holds the header values only, either as string or array
6264 if (is_array ($ val )){
6365 $ val = implode (', ' , array_values ($ val ));
6466 }
65- }
6667
67- $ key = self ::normalizeHeaderName ($ key );
68- $ val = trim ((string )($ val ?? '' ));
68+ $ val = trim ((string )($ val ?? '' ));
6969
70- // skip if the header already exists but the current value is empty
71- if (isset ($ normalized [$ key ]) && empty ($ val )){
72- continue ;
73- }
70+ // skip if the header already exists but the current value is empty
71+ if (isset ($ normalized [$ key ]) && empty ($ val )){
72+ continue ;
73+ }
7474
75- // cookie headers may appear multiple times
76- // https://tools.ietf.org/html/rfc6265#section-4.1.2
77- if ($ key === 'Set-Cookie ' ){
78- // we'll just collect the last value here and leave parsing up to you :P
79- $ normalized [$ key ][strtolower (explode ('= ' , $ val , 2 )[0 ])] = $ val ;
80- }
81- // combine header fields with the same name
82- // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
83- else {
84- isset ($ normalized [$ key ]) && !empty ($ normalized [$ key ])
75+ !empty ($ normalized [$ key ])
8576 ? $ normalized [$ key ] .= ', ' .$ val
8677 : $ normalized [$ key ] = $ val ;
8778 }
@@ -90,6 +81,33 @@ public static function normalize(iterable $headers):array{
9081 return $ normalized ;
9182 }
9283
84+ /**
85+ * Extracts a key:value pair from the given value and returns it as 2-element array.
86+ * If the key cannot be parsed, both array values will be `null`.
87+ */
88+ protected static function normalizeKV (mixed $ value ):array {
89+
90+ // "key: val"
91+ if (is_string ($ value )){
92+ $ kv = explode (': ' , $ value , 2 );
93+
94+ if (count ($ kv ) === 2 ){
95+ return $ kv ;
96+ }
97+ }
98+ // [$key, $val], ["key" => $key, "val" => $val]
99+ elseif (is_array ($ value ) && !empty ($ value )){
100+ $ key = array_keys ($ value )[0 ];
101+ $ val = array_values ($ value )[0 ];
102+
103+ if (is_string ($ key )){
104+ return [$ key , $ val ];
105+ }
106+ }
107+
108+ return [null , null ];
109+ }
110+
93111 /**
94112 * Trims whitespace from the header values.
95113 *
0 commit comments