Merge "maintenance: Script to rename titles for Unicode uppercasing changes"
[lhc/web/wiklou.git] / includes / Rest / HeaderContainer.php
1 <?php
2
3 namespace MediaWiki\Rest;
4
5 /**
6 * This is a container for storing headers. The header names are case-insensitive,
7 * but the case is preserved for methods that return headers in bulk. The
8 * header values are a comma-separated list, or equivalently, an array of strings.
9 *
10 * Unlike PSR-7, the container is mutable.
11 */
12 class HeaderContainer {
13 private $headerLists = [];
14 private $headerLines = [];
15 private $headerNames = [];
16
17 /**
18 * Erase any existing headers and replace them with the specified
19 * header arrays or values.
20 *
21 * @param array $headers
22 */
23 public function resetHeaders( $headers = [] ) {
24 $this->headerLines = [];
25 $this->headerLists = [];
26 $this->headerNames = [];
27 foreach ( $headers as $name => $value ) {
28 $this->headerNames[ strtolower( $name ) ] = $name;
29 list( $valueParts, $valueLine ) = $this->convertToListAndString( $value );
30 $this->headerLines[$name] = $valueLine;
31 $this->headerLists[$name] = $valueParts;
32 }
33 }
34
35 /**
36 * Take an input header value, which may either be a string or an array,
37 * and convert it to an array of header values and a header line.
38 *
39 * The return value is an array where element 0 has the array of header
40 * values, and element 1 has the header line.
41 *
42 * Theoretically, if the input is a string, this could parse the string
43 * and split it on commas. Doing this is complicated, because some headers
44 * can contain double-quoted strings containing commas. The User-Agent
45 * header allows commas in comments delimited by parentheses. So it is not
46 * just explode(",", $value), we would need to parse a grammar defined by
47 * RFC 7231 appendix D which depends on header name.
48 *
49 * It's unclear how much it would help handlers to have fully spec-aware
50 * HTTP header handling just to split on commas. They would probably be
51 * better served by an HTTP header parsing library which provides the full
52 * parse tree.
53 *
54 * @param string $name The header name
55 * @param string|string[] $value The input header value
56 * @return array
57 */
58 private function convertToListAndString( $value ) {
59 if ( is_array( $value ) ) {
60 return [ array_values( $value ), implode( ', ', $value ) ];
61 } else {
62 return [ [ $value ], $value ];
63 }
64 }
65
66 /**
67 * Set or replace a header
68 *
69 * @param string $name
70 * @param string|string[] $value
71 */
72 public function setHeader( $name, $value ) {
73 list( $valueParts, $valueLine ) = $this->convertToListAndString( $value );
74 $lowerName = strtolower( $name );
75 $origName = $this->headerNames[$lowerName] ?? null;
76 if ( $origName !== null ) {
77 unset( $this->headerLines[$origName] );
78 unset( $this->headerLists[$origName] );
79 }
80 $this->headerNames[$lowerName] = $name;
81 $this->headerLines[$name] = $valueLine;
82 $this->headerLists[$name] = $valueParts;
83 }
84
85 /**
86 * Set a header or append to an existing header
87 *
88 * @param string $name
89 * @param string|string[] $value
90 */
91 public function addHeader( $name, $value ) {
92 list( $valueParts, $valueLine ) = $this->convertToListAndString( $value );
93 $lowerName = strtolower( $name );
94 $origName = $this->headerNames[$lowerName] ?? null;
95 if ( $origName === null ) {
96 $origName = $name;
97 $this->headerNames[$lowerName] = $origName;
98 $this->headerLines[$origName] = $valueLine;
99 $this->headerLists[$origName] = $valueParts;
100 } else {
101 $this->headerLines[$origName] .= ', ' . $valueLine;
102 $this->headerLists[$origName] = array_merge( $this->headerLists[$origName],
103 $valueParts );
104 }
105 }
106
107 /**
108 * Remove a header
109 *
110 * @param string $name
111 */
112 public function removeHeader( $name ) {
113 $lowerName = strtolower( $name );
114 $origName = $this->headerNames[$lowerName] ?? null;
115 if ( $origName !== null ) {
116 unset( $this->headerNames[$lowerName] );
117 unset( $this->headerLines[$origName] );
118 unset( $this->headerLists[$origName] );
119 }
120 }
121
122 /**
123 * Get header arrays indexed by original name
124 *
125 * @return string[][]
126 */
127 public function getHeaders() {
128 return $this->headerLists;
129 }
130
131 /**
132 * Get the header with a particular name, or an empty array if there is no
133 * such header.
134 *
135 * @param string $name
136 * @return string[]
137 */
138 public function getHeader( $name ) {
139 $headerName = $this->headerNames[ strtolower( $name ) ] ?? null;
140 if ( $headerName === null ) {
141 return [];
142 }
143 return $this->headerLists[$headerName];
144 }
145
146 /**
147 * Return true if the header exists, false otherwise
148 * @param string $name
149 * @return bool
150 */
151 public function hasHeader( $name ) {
152 return isset( $this->headerNames[ strtolower( $name ) ] );
153 }
154
155 /**
156 * Get the specified header concatenated into a comma-separated string.
157 * If the header does not exist, an empty string is returned.
158 *
159 * @param string $name
160 * @return string
161 */
162 public function getHeaderLine( $name ) {
163 $headerName = $this->headerNames[ strtolower( $name ) ] ?? null;
164 if ( $headerName === null ) {
165 return '';
166 }
167 return $this->headerLines[$headerName];
168 }
169
170 /**
171 * Get all header lines
172 *
173 * @return string[]
174 */
175 public function getHeaderLines() {
176 return $this->headerLines;
177 }
178
179 /**
180 * Get an array of strings of the form "Name: Value", suitable for passing
181 * directly to header() to set response headers. The PHP manual describes
182 * these strings as "raw HTTP headers", so we adopt that terminology.
183 *
184 * @return string[] Header list (integer indexed)
185 */
186 public function getRawHeaderLines() {
187 $lines = [];
188 foreach ( $this->headerNames as $lowerName => $name ) {
189 if ( $lowerName === 'set-cookie' ) {
190 // As noted by RFC 7230 section 3.2.2, Set-Cookie is the only
191 // header for which multiple values cannot be concatenated into
192 // a single comma-separated line.
193 foreach ( $this->headerLists[$name] as $value ) {
194 $lines[] = "$name: $value";
195 }
196 } else {
197 $lines[] = "$name: " . $this->headerLines[$name];
198 }
199 }
200 return $lines;
201 }
202 }