Skip to content

Commit e036cb7

Browse files
authored
Merge pull request #372 from qiniu/fix/header-key-naughty
make header key initial capital
2 parents 066b45c + 0a2d84b commit e036cb7

File tree

4 files changed

+464
-31
lines changed

4 files changed

+464
-31
lines changed

src/Qiniu/Http/Client.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -125,36 +125,16 @@ public static function sendRequest($request)
125125
}
126126
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
127127
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
128-
$headers = self::parseHeaders(substr($result, 0, $header_size));
128+
$headers = Header::parseRawText(substr($result, 0, $header_size));
129129
$body = substr($result, $header_size);
130130
curl_close($ch);
131131
return new Response($code, $duration, $headers, $body, null);
132132
}
133133

134-
private static function parseHeaders($raw)
135-
{
136-
$headers = array();
137-
$headerLines = explode("\r\n", $raw);
138-
foreach ($headerLines as $line) {
139-
$headerLine = trim($line);
140-
$kv = explode(':', $headerLine);
141-
if (count($kv) > 1) {
142-
$kv[0] =self::ucwordsHyphen($kv[0]);
143-
$headers[$kv[0]] = trim($kv[1]);
144-
}
145-
}
146-
return $headers;
147-
}
148-
149134
private static function escapeQuotes($str)
150135
{
151136
$find = array("\\", "\"");
152137
$replace = array("\\\\", "\\\"");
153138
return str_replace($find, $replace, $str);
154139
}
155-
156-
private static function ucwordsHyphen($str)
157-
{
158-
return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str)));
159-
}
160140
}

src/Qiniu/Http/Header.php

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
<?php
2+
3+
namespace Qiniu\Http;
4+
5+
/**
6+
* field name case-insensitive Header
7+
*/
8+
class Header implements \ArrayAccess, \IteratorAggregate, \Countable
9+
{
10+
/** @var array normalized key name map */
11+
private $data;
12+
13+
/**
14+
* @param array $obj non-normalized header object
15+
*/
16+
public function __construct($obj = array())
17+
{
18+
foreach ($obj as $key => $values) {
19+
$normalizedKey = self::normalizeKey($key);
20+
$normalizedValues = array();
21+
foreach ($values as $value) {
22+
array_push($normalizedValues, self::normalizeValue($value));
23+
}
24+
$this->data[$normalizedKey] = $normalizedValues;
25+
}
26+
return $this;
27+
}
28+
29+
/**
30+
* return origin headers, which is field name case-sensitive
31+
*
32+
* @param string $raw
33+
*
34+
* @return array
35+
*/
36+
public static function parseRawText($raw)
37+
{
38+
$headers = array();
39+
$headerLines = explode("\r\n", $raw);
40+
foreach ($headerLines as $line) {
41+
$headerLine = trim($line);
42+
$kv = explode(':', $headerLine);
43+
if (count($kv) <= 1) {
44+
continue;
45+
}
46+
// for http2 [Pseudo-Header Fields](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.1)
47+
if ($kv[0] == "") {
48+
$fieldName = ":" . $kv[1];
49+
} else {
50+
$fieldName = $kv[0];
51+
}
52+
$fieldValue = trim(substr($headerLine, strlen($fieldName . ":")));
53+
if (isset($headers[$fieldName])) {
54+
array_push($headers[$fieldName], $fieldValue);
55+
} else {
56+
$headers[$fieldName] = array($fieldValue);
57+
}
58+
}
59+
return $headers;
60+
}
61+
62+
/**
63+
* @param string $raw
64+
*
65+
* @return Header
66+
*/
67+
public static function fromRawText($raw)
68+
{
69+
return new Header(self::parseRawText($raw));
70+
}
71+
72+
/**
73+
* @param string $key
74+
*
75+
* @return string
76+
*/
77+
public static function normalizeKey($key)
78+
{
79+
$key = trim($key);
80+
81+
if (!self::isValidKeyName($key)) {
82+
return $key;
83+
}
84+
85+
return ucwords(strtolower($key), '-');
86+
}
87+
88+
/**
89+
* @param string|numeric $value
90+
*
91+
* @return string|numeric
92+
*/
93+
public static function normalizeValue($value)
94+
{
95+
if (is_numeric($value)) {
96+
return $value + 0;
97+
}
98+
return trim($value);
99+
}
100+
101+
/**
102+
* @return array
103+
*/
104+
public function getRawData()
105+
{
106+
return $this->data;
107+
}
108+
109+
/**
110+
* @param $offset string
111+
*
112+
* @return boolean
113+
*/
114+
public function offsetExists($offset)
115+
{
116+
$key = self::normalizeKey($offset);
117+
return isset($this->data[$key]);
118+
}
119+
120+
/**
121+
* @param $offset string
122+
*
123+
* @return string|null
124+
*/
125+
public function offsetGet($offset)
126+
{
127+
$key = self::normalizeKey($offset);
128+
if (isset($this->data[$key]) && count($this->data[$key])) {
129+
return $this->data[$key][0];
130+
} else {
131+
return null;
132+
}
133+
}
134+
135+
/**
136+
* @param $offset string
137+
* @param $value string
138+
*
139+
* @return void
140+
*/
141+
public function offsetSet($offset, $value)
142+
{
143+
$key = self::normalizeKey($offset);
144+
if (isset($this->data[$key]) && count($this->data[$key] > 0)) {
145+
$this->data[$key][0] = self::normalizeValue($value);
146+
} else {
147+
$this->data[$key] = array(self::normalizeValue($value));
148+
}
149+
}
150+
151+
/**
152+
* @return void
153+
*/
154+
public function offsetUnset($offset)
155+
{
156+
$key = self::normalizeKey($offset);
157+
unset($this->data[$key]);
158+
}
159+
160+
/**
161+
* @return \ArrayIterator
162+
*/
163+
public function getIterator()
164+
{
165+
$arr = array();
166+
foreach ($this->data as $k => $v) {
167+
$arr[$k] = $v[0];
168+
}
169+
return new \ArrayIterator($arr);
170+
}
171+
172+
/**
173+
* @return int
174+
*/
175+
public function count()
176+
{
177+
return count($this->data);
178+
}
179+
180+
private static $isTokenTable = array(
181+
'!' => true,
182+
'#' => true,
183+
'$' => true,
184+
'%' => true,
185+
'&' => true,
186+
'\'' => true,
187+
'*' => true,
188+
'+' => true,
189+
'-' => true,
190+
'.' => true,
191+
'0' => true,
192+
'1' => true,
193+
'2' => true,
194+
'3' => true,
195+
'4' => true,
196+
'5' => true,
197+
'6' => true,
198+
'7' => true,
199+
'8' => true,
200+
'9' => true,
201+
'A' => true,
202+
'B' => true,
203+
'C' => true,
204+
'D' => true,
205+
'E' => true,
206+
'F' => true,
207+
'G' => true,
208+
'H' => true,
209+
'I' => true,
210+
'J' => true,
211+
'K' => true,
212+
'L' => true,
213+
'M' => true,
214+
'N' => true,
215+
'O' => true,
216+
'P' => true,
217+
'Q' => true,
218+
'R' => true,
219+
'S' => true,
220+
'T' => true,
221+
'U' => true,
222+
'W' => true,
223+
'V' => true,
224+
'X' => true,
225+
'Y' => true,
226+
'Z' => true,
227+
'^' => true,
228+
'_' => true,
229+
'`' => true,
230+
'a' => true,
231+
'b' => true,
232+
'c' => true,
233+
'd' => true,
234+
'e' => true,
235+
'f' => true,
236+
'g' => true,
237+
'h' => true,
238+
'i' => true,
239+
'j' => true,
240+
'k' => true,
241+
'l' => true,
242+
'm' => true,
243+
'n' => true,
244+
'o' => true,
245+
'p' => true,
246+
'q' => true,
247+
'r' => true,
248+
's' => true,
249+
't' => true,
250+
'u' => true,
251+
'v' => true,
252+
'w' => true,
253+
'x' => true,
254+
'y' => true,
255+
'z' => true,
256+
'|' => true,
257+
'~' => true,
258+
);
259+
260+
/**
261+
* @param string $str
262+
*
263+
* @return boolean
264+
*/
265+
private static function isValidKeyName($str)
266+
{
267+
for ($i = 0; $i < count($str); $i += 1) {
268+
if (!isset(self::$isTokenTable[$str[$i]])) {
269+
return false;
270+
}
271+
}
272+
return true;
273+
}
274+
}

0 commit comments

Comments
 (0)