HeaderUtils.php 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpFoundation;
  11. /**
  12. * HTTP header utility functions.
  13. *
  14. * @author Christian Schmidt <github@chsc.dk>
  15. */
  16. class HeaderUtils
  17. {
  18. /**
  19. * This class should not be instantiated.
  20. */
  21. private function __construct()
  22. {
  23. }
  24. /**
  25. * Splits an HTTP header by one or more separators.
  26. *
  27. * Example:
  28. *
  29. * HeaderUtils::split("da, en-gb;q=0.8", ",;")
  30. * // => array(array('da'), array('en-gb', 'q=0.8'))
  31. *
  32. * @param string $header HTTP header value
  33. * @param string $separators List of characters to split on, ordered by
  34. * precedence, e.g. ",", ";=", or ",;="
  35. *
  36. * @return array Nested array with as many levels as there are characters in
  37. * $separators
  38. */
  39. public static function split(string $header, string $separators): array
  40. {
  41. $quotedSeparators = preg_quote($separators, '/');
  42. preg_match_all('
  43. /
  44. (?!\s)
  45. (?:
  46. # quoted-string
  47. "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
  48. |
  49. # token
  50. [^"'.$quotedSeparators.']+
  51. )+
  52. (?<!\s)
  53. |
  54. # separator
  55. \s*
  56. (?<separator>['.$quotedSeparators.'])
  57. \s*
  58. /x', trim($header), $matches, PREG_SET_ORDER);
  59. return self::groupParts($matches, $separators);
  60. }
  61. /**
  62. * Combines an array of arrays into one associative array.
  63. *
  64. * Each of the nested arrays should have one or two elements. The first
  65. * value will be used as the keys in the associative array, and the second
  66. * will be used as the values, or true if the nested array only contains one
  67. * element. Array keys are lowercased.
  68. *
  69. * Example:
  70. *
  71. * HeaderUtils::combine(array(array("foo", "abc"), array("bar")))
  72. * // => array("foo" => "abc", "bar" => true)
  73. */
  74. public static function combine(array $parts): array
  75. {
  76. $assoc = array();
  77. foreach ($parts as $part) {
  78. $name = strtolower($part[0]);
  79. $value = $part[1] ?? true;
  80. $assoc[$name] = $value;
  81. }
  82. return $assoc;
  83. }
  84. /**
  85. * Joins an associative array into a string for use in an HTTP header.
  86. *
  87. * The key and value of each entry are joined with "=", and all entries
  88. * are joined with the specified separator and an additional space (for
  89. * readability). Values are quoted if necessary.
  90. *
  91. * Example:
  92. *
  93. * HeaderUtils::toString(array("foo" => "abc", "bar" => true, "baz" => "a b c"), ",")
  94. * // => 'foo=abc, bar, baz="a b c"'
  95. */
  96. public static function toString(array $assoc, string $separator): string
  97. {
  98. $parts = array();
  99. foreach ($assoc as $name => $value) {
  100. if (true === $value) {
  101. $parts[] = $name;
  102. } else {
  103. $parts[] = $name.'='.self::quote($value);
  104. }
  105. }
  106. return implode($separator.' ', $parts);
  107. }
  108. /**
  109. * Encodes a string as a quoted string, if necessary.
  110. *
  111. * If a string contains characters not allowed by the "token" construct in
  112. * the HTTP specification, it is backslash-escaped and enclosed in quotes
  113. * to match the "quoted-string" construct.
  114. */
  115. public static function quote(string $s): string
  116. {
  117. if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
  118. return $s;
  119. }
  120. return '"'.addcslashes($s, '"\\"').'"';
  121. }
  122. /**
  123. * Decodes a quoted string.
  124. *
  125. * If passed an unquoted string that matches the "token" construct (as
  126. * defined in the HTTP specification), it is passed through verbatimly.
  127. */
  128. public static function unquote(string $s): string
  129. {
  130. return preg_replace('/\\\\(.)|"/', '$1', $s);
  131. }
  132. private static function groupParts(array $matches, string $separators): array
  133. {
  134. $separator = $separators[0];
  135. $partSeparators = substr($separators, 1);
  136. $i = 0;
  137. $partMatches = array();
  138. foreach ($matches as $match) {
  139. if (isset($match['separator']) && $match['separator'] === $separator) {
  140. ++$i;
  141. } else {
  142. $partMatches[$i][] = $match;
  143. }
  144. }
  145. $parts = array();
  146. if ($partSeparators) {
  147. foreach ($partMatches as $matches) {
  148. $parts[] = self::groupParts($matches, $partSeparators);
  149. }
  150. } else {
  151. foreach ($partMatches as $matches) {
  152. $parts[] = self::unquote($matches[0][0]);
  153. }
  154. }
  155. return $parts;
  156. }
  157. }