Header.php 6.5 KB

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