wiki.techinc.nl/includes/libs/http/HttpAcceptNegotiator.php
Tim Starling 917f0a5996 Replace all instances of "per default" with "by default"
According to the dictionary, "per" (or more conventionally "as per")
means "according to". Refer OED "per" sense II.3.a. For example:

"No value was passed, so return null, as per default".

In this sentence, we are not specifying the default, we are referring
to the default. This correct usage of "per default" was used nowhere
in MediaWiki core as far as I can see.

Instead we have "per default" being used to mean "by default", that is,
giving the value to use when no explicit value was specified.

In OED, the phrase "by default" is blessed with its own section just
for computing usage:

"P.1.e. Computing. As an option or setting adopted automatically by a
computer program whenever an alternative is not specified by the user
or programmer. Cf. sense I.7a."

There are highly similar pre-computing usages of the same phrase,
whereas the phrase "per default" is not mentioned.

As a matter of style, I think "per default" should not be used even
when it is strictly correct, since the common incorrect usage makes it
ambiguous and misleading.

Change-Id: Ibcccc65ead864d082677b472b34ff32ff41c60ae
2024-04-29 10:47:54 +10:00

138 lines
4.4 KiB
PHP

<?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;
}
}