Merge "Special:Newpages feed now shows first revision instead of latest revision"
[lhc/web/wiklou.git] / includes / libs / http / HttpAcceptNegotiator.php
1 <?php
2
3 /**
4 * Utility for negotiating a value from a set of supported values using a preference list.
5 * This is intended for use with HTTP headers like Accept, Accept-Language, Accept-Encoding, etc.
6 * See RFC 2616 section 14 for details.
7 *
8 * To use this with a request header, first parse the header value into an array of weights
9 * using HttpAcceptParser, then call getBestSupportedKey.
10 *
11 * @license GPL-2.0+
12 * @author Daniel Kinzler
13 * @author Thiemo Mättig
14 */
15
16 namespace Wikimedia\Http;
17
18 class HttpAcceptNegotiator {
19
20 /**
21 * @var string[]
22 */
23 private $supportedValues;
24
25 /**
26 * @var string
27 */
28 private $defaultValue;
29
30 /**
31 * @param string[] $supported A list of supported values.
32 */
33 public function __construct( array $supported ) {
34 $this->supportedValues = $supported;
35 $this->defaultValue = reset( $supported );
36 }
37
38 /**
39 * Returns the best supported key from the given weight map. Of the keys from the
40 * $weights parameter that are also in the list of supported values supplied to
41 * the constructor, this returns the key that has the highest weight associated
42 * with it. If two keys have the same weight, the more specific key is preferred,
43 * as required by RFC2616 section 14. Keys that map to 0 or false are ignored.
44 * If no matching key is found, $default is returned.
45 *
46 * @param float[] $weights An associative array mapping accepted values to their
47 * respective weights.
48 *
49 * @param null|string $default The value to return if non of the keys in $weights
50 * is supported (null per default).
51 *
52 * @return null|string The best supported key from the $weights parameter.
53 */
54 public function getBestSupportedKey( array $weights, $default = null ) {
55 // Make sure we correctly bias against wildcards and ranges, see RFC2616, section 14.
56 foreach ( $weights as $name => &$weight ) {
57 if ( $name === '*' || $name === '*/*' ) {
58 $weight -= 0.000002;
59 } elseif ( substr( $name, -2 ) === '/*' ) {
60 $weight -= 0.000001;
61 }
62 }
63
64 // Sort $weights by value and...
65 asort( $weights );
66
67 // remove any keys with values equal to 0 or false (HTTP/1.1 section 3.9)
68 $weights = array_filter( $weights );
69
70 // ...use the ordered list of keys
71 $preferences = array_reverse( array_keys( $weights ) );
72
73 $value = $this->getFirstSupportedValue( $preferences, $default );
74 return $value;
75 }
76
77 /**
78 * Returns the first supported value from the given preference list. Of the values from
79 * the $preferences parameter that are also in the list of supported values supplied
80 * to the constructor, this returns the value that has the lowest index in the list.
81 * If no such value is found, $default is returned.
82 *
83 * @param string[] $preferences A list of acceptable values, in order of preference.
84 *
85 * @param null|string $default The value to return if non of the keys in $weights
86 * is supported (null per default).
87 *
88 * @return null|string The best supported key from the $weights parameter.
89 */
90 public function getFirstSupportedValue( array $preferences, $default = null ) {
91 foreach ( $preferences as $value ) {
92 foreach ( $this->supportedValues as $supported ) {
93 if ( $this->valueMatches( $value, $supported ) ) {
94 return $supported;
95 }
96 }
97 }
98
99 return $default;
100 }
101
102 /**
103 * Returns true if the given acceptable value matches the given supported value,
104 * according to the HTTP specification. The following rules are used:
105 *
106 * - comparison is case-insensitive
107 * - if $accepted and $supported are equal, they match
108 * - if $accepted is `*` or `*` followed by `/*`, it matches any $supported value.
109 * - if both $accepted and $supported contain a `/`, and $accepted ends with `/*`,
110 * they match if the part before the first `/` is equal.
111 *
112 * @param string $accepted An accepted value (may contain wildcards)
113 * @param string $supported A supported value.
114 *
115 * @return bool Whether the given supported value matches the given accepted value.
116 */
117 private function valueMatches( $accepted, $supported ) {
118 // RDF 2045: MIME types are case insensitive.
119 // full match
120 if ( strcasecmp( $accepted, $supported ) === 0 ) {
121 return true;
122 }
123
124 // wildcard match (HTTP/1.1 section 14.1, 14.2, 14.3)
125 if ( $accepted === '*' || $accepted === '*/*' ) {
126 return true;
127 }
128
129 // wildcard match (HTTP/1.1 section 14.1)
130 if ( substr( $accepted, -2 ) === '/*'
131 && strncasecmp( $accepted, $supported, strlen( $accepted ) - 2 ) === 0
132 ) {
133 return true;
134 }
135
136 return false;
137 }
138
139 }