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