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