Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
41 / 41 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
HttpAcceptParser | |
100.00% |
41 / 41 |
|
100.00% |
2 / 2 |
12 | |
100.00% |
1 / 1 |
parseAccept | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
11 | |||
parseWeights | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Utility for parsing a HTTP Accept header value into a weight map. May also be used with |
5 | * other, similar headers like Accept-Language, Accept-Encoding, etc. |
6 | * |
7 | * @license GPL-2.0-or-later |
8 | * @author Daniel Kinzler |
9 | */ |
10 | |
11 | namespace Wikimedia\Http; |
12 | |
13 | class HttpAcceptParser { |
14 | |
15 | /** |
16 | * Parse media types from an Accept header and sort them by q-factor. |
17 | * |
18 | * Note that his was mostly ported from, |
19 | * https://github.com/arlolra/negotiator/blob/full-parse-access/lib/mediaType.js |
20 | * |
21 | * @param string $accept |
22 | * @return array[] |
23 | * - type: (string) |
24 | * - subtype: (string) |
25 | * - q: (float) q-factor weighting |
26 | * - i: (int) index |
27 | * - params: (array) |
28 | */ |
29 | public function parseAccept( $accept ): array { |
30 | $accepts = explode( ',', $accept ); // FIXME: Allow commas in quotes |
31 | $ret = []; |
32 | |
33 | foreach ( $accepts as $i => $a ) { |
34 | if ( !preg_match( '!^([^\s/;]+)/([^;\s]+)\s*(?:;(.*))?$!D', trim( $a ), $matches ) ) { |
35 | continue; |
36 | } |
37 | |
38 | $q = 1; |
39 | $params = []; |
40 | if ( isset( $matches[3] ) ) { |
41 | $kvps = explode( ';', $matches[3] ); // FIXME: Allow semi-colon in quotes |
42 | foreach ( $kvps as $kv ) { |
43 | [ $key, $val ] = explode( '=', trim( $kv ), 2 ); |
44 | $key = strtolower( trim( $key ) ); |
45 | $val = trim( $val ); |
46 | if ( $key === 'q' ) { |
47 | $q = (float)$val; // FIXME: Spec is stricter about this |
48 | } else { |
49 | if ( $val && $val[0] === '"' && $val[ strlen( $val ) - 1 ] === '"' ) { |
50 | $val = substr( $val, 1, strlen( $val ) - 2 ); |
51 | } |
52 | $params[$key] = $val; |
53 | } |
54 | } |
55 | } |
56 | $ret[] = [ |
57 | 'type' => $matches[1], |
58 | 'subtype' => $matches[2], |
59 | 'q' => $q, |
60 | 'i' => $i, |
61 | 'params' => $params, |
62 | ]; |
63 | } |
64 | |
65 | // Sort list. First by q values, then by order |
66 | usort( $ret, static function ( $a, $b ) { |
67 | if ( $b['q'] > $a['q'] ) { |
68 | return 1; |
69 | } elseif ( $b['q'] === $a['q'] ) { |
70 | return $a['i'] - $b['i']; |
71 | } else { |
72 | return -1; |
73 | } |
74 | } ); |
75 | |
76 | return $ret; |
77 | } |
78 | |
79 | /** |
80 | * Parses an HTTP header into a weight map, that is an associative array |
81 | * mapping values to their respective weights. Any header name preceding |
82 | * weight spec is ignored for convenience. |
83 | * |
84 | * Note that type parameters and accept extension like the "level" parameter |
85 | * are not supported, weights are derived from "q" values only. |
86 | * |
87 | * See RFC 7231 section 5.3.2 for details. |
88 | * |
89 | * @param string $rawHeader |
90 | * |
91 | * @return array |
92 | */ |
93 | public function parseWeights( $rawHeader ) { |
94 | // first, strip header name |
95 | $rawHeader = preg_replace( '/^[-\w]+:\s*/', '', $rawHeader ); |
96 | |
97 | // Return values in lower case |
98 | $rawHeader = strtolower( $rawHeader ); |
99 | |
100 | $accepts = $this->parseAccept( $rawHeader ); |
101 | |
102 | // Create a list like "en" => 0.8 |
103 | return array_reduce( $accepts, static function ( $prev, $next ) { |
104 | $type = "{$next['type']}/{$next['subtype']}"; |
105 | $prev[$type] = $next['q']; |
106 | return $prev; |
107 | }, [] ); |
108 | } |
109 | |
110 | } |