Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.08% |
49 / 51 |
|
66.67% |
4 / 6 |
CRAP | |
0.00% |
0 / 1 |
IfNoneMatch | |
96.08% |
49 / 51 |
|
66.67% |
4 / 6 |
15 | |
0.00% |
0 / 1 |
parseHeaderList | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
5 | |||
parseETag | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
getLastError | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
parseHeader | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
consumeTagList | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
consumeTag | |
94.44% |
17 / 18 |
|
0.00% |
0 / 1 |
3.00 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\HeaderParser; |
4 | |
5 | /** |
6 | * A class to assist with the parsing of If-None-Match, If-Match and ETag headers |
7 | */ |
8 | class IfNoneMatch extends HeaderParserBase { |
9 | private $results = []; |
10 | |
11 | /** @var string|null */ |
12 | private $lastError; |
13 | |
14 | /** |
15 | * Parse an If-None-Match or If-Match header list as returned by |
16 | * RequestInterface::getHeader(). |
17 | * |
18 | * The return value is an array of tag info arrays. Each tag info array is |
19 | * an associative array with the following keys: |
20 | * |
21 | * - weak: True if the tag is weak, false otherwise |
22 | * - contents: The contents of the double-quoted opaque-tag, not |
23 | * including the quotes. |
24 | * - whole: The whole ETag, including weak indicator and quoted opaque-tag |
25 | * |
26 | * In the case of a wildcard header like "If-Match: *", there cannot be any |
27 | * other tags. The return value is an array with a single tag info array with |
28 | * 'whole' => '*'. |
29 | * |
30 | * If the header was invalid, an empty array will be returned. Further |
31 | * information about why the parsing failed can be found by calling |
32 | * getLastError(). |
33 | * |
34 | * @param string[] $headerList |
35 | * @return array[] |
36 | */ |
37 | public function parseHeaderList( $headerList ) { |
38 | $this->lastError = null; |
39 | if ( count( $headerList ) === 1 && $headerList[0] === '*' ) { |
40 | return [ [ |
41 | 'weak' => true, |
42 | 'contents' => null, |
43 | 'whole' => '*' |
44 | ] ]; |
45 | } |
46 | $this->results = []; |
47 | try { |
48 | foreach ( $headerList as $header ) { |
49 | $this->parseHeader( $header ); |
50 | } |
51 | return $this->results; |
52 | } catch ( HeaderParserError $e ) { |
53 | $this->lastError = $e->getMessage(); |
54 | return []; |
55 | } |
56 | } |
57 | |
58 | /** |
59 | * Parse an entity-tag such as might be found in an ETag response header. |
60 | * The result is an associative array in the same format returned by |
61 | * parseHeaderList(). |
62 | * |
63 | * If parsing failed, null is returned. |
64 | * |
65 | * @param string $eTag |
66 | * @return array|null |
67 | */ |
68 | public function parseETag( $eTag ) { |
69 | $this->setInput( $eTag ); |
70 | $this->results = []; |
71 | |
72 | try { |
73 | $this->consumeTag(); |
74 | $this->assertEnd(); |
75 | } catch ( HeaderParserError $e ) { |
76 | $this->lastError = $e->getMessage(); |
77 | return null; |
78 | } |
79 | /* @phan-suppress-next-line PhanTypeInvalidDimOffset */ |
80 | return $this->results[0]; |
81 | } |
82 | |
83 | /** |
84 | * Get the last parse error message, or null if there was none |
85 | * |
86 | * @return string|null |
87 | */ |
88 | public function getLastError() { |
89 | return $this->lastError; |
90 | } |
91 | |
92 | /** |
93 | * Parse a single header |
94 | * |
95 | * @param string $header |
96 | * @throws HeaderParserError |
97 | */ |
98 | private function parseHeader( $header ) { |
99 | $this->setInput( $header ); |
100 | $this->consumeTagList(); |
101 | $this->assertEnd(); |
102 | } |
103 | |
104 | /** |
105 | * Consume a comma-separated list of entity-tags |
106 | * |
107 | * @throws HeaderParserError |
108 | */ |
109 | private function consumeTagList() { |
110 | while ( true ) { |
111 | $this->skipWhitespace(); |
112 | $this->consumeTag(); |
113 | $this->skipWhitespace(); |
114 | if ( $this->pos === $this->inputLength ) { |
115 | break; |
116 | } else { |
117 | $this->consumeString( ',' ); |
118 | } |
119 | } |
120 | } |
121 | |
122 | /** |
123 | * Consume a single entity-tag and append to the result array |
124 | * |
125 | * @throws HeaderParserError |
126 | */ |
127 | private function consumeTag() { |
128 | $weak = false; |
129 | $whole = ''; |
130 | if ( substr( $this->input, $this->pos, 2 ) === 'W/' ) { |
131 | $weak = true; |
132 | $whole .= 'W/'; |
133 | $this->pos += 2; |
134 | } |
135 | $this->consumeString( '"' ); |
136 | if ( !preg_match( '/[!#-~\x80-\xff]*/A', $this->input, $m, 0, $this->pos ) ) { |
137 | $this->error( 'unexpected regex failure' ); |
138 | } |
139 | $contents = $m[0]; |
140 | $this->pos += strlen( $contents ); |
141 | $this->consumeString( '"' ); |
142 | $whole .= '"' . $contents . '"'; |
143 | $this->results[] = [ |
144 | 'weak' => $weak, |
145 | 'contents' => $contents, |
146 | 'whole' => $whole |
147 | ]; |
148 | } |
149 | } |