From 66e7f7252bfdd725f20bff9ab060728556b1bdad Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Wed, 20 May 2015 12:04:17 +0300 Subject: [PATCH] Moved all remaining 'util' functions to `functions.php`. Fixes #49. --- CHANGELOG.md | 9 ++ lib/URLUtil.php | 40 +++----- lib/Util.php | 167 ++------------------------------ lib/functions.php | 239 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 190 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b95e4d0..5fd8b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ ChangeLog ========= +4.0.0 (2015-05-20) +------------------ + +* Deprecated: All static functions from `Sabre\HTTP\URLUtil` and + `Sabre\HTTP\Util` moved to a separate `functions.php`, which is also + autoloaded. The old functions are still there, but will be removed in a + future version. (#49) + + 4.0.0-alpha3 (2015-05-19) ------------------------- diff --git a/lib/URLUtil.php b/lib/URLUtil.php index c4f3c54..82fe56f 100644 --- a/lib/URLUtil.php +++ b/lib/URLUtil.php @@ -7,15 +7,10 @@ /** * URL utility class * - * This class provides methods to deal with encoding and decoding url (percent encoded) strings. - * - * It was not possible to use PHP's built-in methods for this, because some clients don't like - * encoding of certain characters. - * - * Specifically, it was found that GVFS (gnome's webdav client) does not like encoding of ( and - * ). Since these are reserved, but don't have a reserved meaning in url, these characters are - * kept as-is. + * Note: this class is deprecated. All its functionality moved to functions.php + * or sabre\uri. * + * @deprectated * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License @@ -27,16 +22,13 @@ class URLUtil { * * slashes (/) are treated as path-separators. * + * @deprecated use \Sabre\HTTP\encodePath() * @param string $path * @return string */ static function encodePath($path) { - return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) { - - return '%' . sprintf('%02x', ord($match[0])); - - }, $path); + return encodePath($path); } @@ -45,49 +37,39 @@ static function encodePath($path) { * * Slashes are considered part of the name, and are encoded as %2f * + * @deprecated use \Sabre\HTTP\encodePathSegment() * @param string $pathSegment * @return string */ static function encodePathSegment($pathSegment) { - return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) { - - return '%' . sprintf('%02x', ord($match[0])); + return encodePathSegment($pathSegment); - }, $pathSegment); } /** * Decodes a url-encoded path * + * @deprecated use \Sabre\HTTP\decodePath * @param string $path * @return string */ static function decodePath($path) { - return self::decodePathSegment($path); + return decodePath($path); } /** * Decodes a url-encoded path segment * + * @deprecated use \Sabre\HTTP\decodePathSegment() * @param string $path * @return string */ static function decodePathSegment($path) { - $path = rawurldecode($path); - $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']); - - switch ($encoding) { - - case 'ISO-8859-1' : - $path = utf8_encode($path); - - } - - return $path; + return decodePathSegment($path); } diff --git a/lib/Util.php b/lib/Util.php index b13821d..eea63d3 100644 --- a/lib/Util.php +++ b/lib/Util.php @@ -8,191 +8,36 @@ * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). * @author Evert Pot (http://evertpot.com/) * @author Paul Voegler + * @deprecated All these functions moved to functions.php * @license http://sabre.io/license/ Modified BSD License */ class Util { /** - * This method can be used to aid with content negotiation. - * - * It takes 2 arguments, the $acceptHeaderValue, which usually comes from - * an Accept header, and $availableOptions, which contains an array of - * items that the server can support. - * - * The result of this function will be the 'best possible option'. If no - * best possible option could be found, null is returned. - * - * When it's null you can according to the spec either return a default, or - * you can choose to emit 406 Not Acceptable. - * - * The method also accepts sending 'null' for the $acceptHeaderValue, - * implying that no accept header was sent. + * Content negotiation * + * @deprecated Use \Sabre\HTTP\negotiateContentType * @param string|null $acceptHeaderValue * @param array $availableOptions * @return string|null */ static function negotiateContentType($acceptHeaderValue, array $availableOptions) { - if (!$acceptHeaderValue) { - // Grabbing the first in the list. - return reset($availableOptions); - } - - $proposals = array_map( - ['self', 'parseMimeType'], - explode(',', $acceptHeaderValue) - ); - - // Ensuring array keys are reset. - $availableOptions = array_values($availableOptions); - - $options = array_map( - ['self', 'parseMimeType'], - $availableOptions - ); - - $lastQuality = 0; - $lastSpecificity = 0; - $lastOptionIndex = 0; - $lastChoice = null; - - foreach ($proposals as $proposal) { - - // Ignoring broken values. - if (is_null($proposal)) continue; - - // If the quality is lower we don't have to bother comparing. - if ($proposal['quality'] < $lastQuality) { - continue; - } - - foreach ($options as $optionIndex => $option) { - - if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { - // no match on type. - continue; - } - if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { - // no match on subtype. - continue; - } - - // Any parameters appearing on the options must appear on - // proposals. - foreach ($option['parameters'] as $paramName => $paramValue) { - if (!array_key_exists($paramName, $proposal['parameters'])) { - continue 2; - } - if ($paramValue !== $proposal['parameters'][$paramName]) { - continue 2; - } - } - - // If we got here, we have a match on parameters, type and - // subtype. We need to calculate a score for how specific the - // match was. - $specificity = - ($proposal['type'] !== '*' ? 20 : 0) + - ($proposal['subType'] !== '*' ? 10 : 0) + - count($option['parameters']); - - - // Does this entry win? - if ( - ($proposal['quality'] > $lastQuality) || - ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || - ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) - ) { - - $lastQuality = $proposal['quality']; - $lastSpecificity = $specificity; - $lastOptionIndex = $optionIndex; - $lastChoice = $availableOptions[$optionIndex]; - - } - - } - - } - - return $lastChoice; - - } - - /** - * Parses a mime-type and splits it into: - * - * 1. type - * 2. subtype - * 3. quality - * 4. parameters - * - * @param string $str - * @return array - */ - private static function parseMimeType($str) { - - $parameters = []; - // If no q= parameter appears, then quality = 1. - $quality = 1; - - $parts = explode(';', $str); - - // The first part is the mime-type. - $mimeType = array_shift($parts); - - $mimeType = explode('/', trim($mimeType)); - if (count($mimeType) !== 2) { - // Illegal value - return null; - } - list($type, $subType) = $mimeType; - - foreach ($parts as $part) { - - $part = trim($part); - if (strpos($part, '=')) { - list($partName, $partValue) = - explode('=', $part, 2); - } else { - $partName = $part; - $partValue = null; - } - - // The quality parameter, if it appears, also marks the end of - // the parameter list. Anything after the q= counts as an - // 'accept extension' and could introduce new semantics in - // content-negotation. - if ($partName !== 'q') { - $parameters[$partName] = $part; - } else { - $quality = (float)$partValue; - break; // Stop parsing parts - } - - } - - return [ - 'type' => $type, - 'subType' => $subType, - 'quality' => $quality, - 'parameters' => $parameters, - ]; + return negotiateContentType($acceptHeaderValue, $availableOptions); } /** * Deprecated! Use negotiateContentType. * - * @deprecated + * @deprecated Use \Sabre\HTTP\NegotiateContentType * @param string|null $acceptHeader * @param array $availableOptions * @return string|null */ static function negotiate($acceptHeaderValue, array $availableOptions) { - return self::negotiateContentType($acceptHeaderValue, $availableOptions); + return negotiateContentType($acceptHeaderValue, $availableOptions); } diff --git a/lib/functions.php b/lib/functions.php index 8e42a9a..197fb1f 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -84,6 +84,114 @@ function toDate(DateTime $dateTime) { } +/** + * This function can be used to aid with content negotiation. + * + * It takes 2 arguments, the $acceptHeaderValue, which usually comes from + * an Accept header, and $availableOptions, which contains an array of + * items that the server can support. + * + * The result of this function will be the 'best possible option'. If no + * best possible option could be found, null is returned. + * + * When it's null you can according to the spec either return a default, or + * you can choose to emit 406 Not Acceptable. + * + * The method also accepts sending 'null' for the $acceptHeaderValue, + * implying that no accept header was sent. + * + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ +function negotiateContentType($acceptHeaderValue, array $availableOptions) { + + if (!$acceptHeaderValue) { + // Grabbing the first in the list. + return reset($availableOptions); + } + + $proposals = array_map( + 'Sabre\HTTP\parseMimeType', + explode(',', $acceptHeaderValue) + ); + + // Ensuring array keys are reset. + $availableOptions = array_values($availableOptions); + + $options = array_map( + 'Sabre\HTTP\parseMimeType', + $availableOptions + ); + + $lastQuality = 0; + $lastSpecificity = 0; + $lastOptionIndex = 0; + $lastChoice = null; + + foreach ($proposals as $proposal) { + + // Ignoring broken values. + if (is_null($proposal)) continue; + + // If the quality is lower we don't have to bother comparing. + if ($proposal['quality'] < $lastQuality) { + continue; + } + + foreach ($options as $optionIndex => $option) { + + if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { + // no match on type. + continue; + } + if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { + // no match on subtype. + continue; + } + + // Any parameters appearing on the options must appear on + // proposals. + foreach ($option['parameters'] as $paramName => $paramValue) { + if (!array_key_exists($paramName, $proposal['parameters'])) { + continue 2; + } + if ($paramValue !== $proposal['parameters'][$paramName]) { + continue 2; + } + } + + // If we got here, we have a match on parameters, type and + // subtype. We need to calculate a score for how specific the + // match was. + $specificity = + ($proposal['type'] !== '*' ? 20 : 0) + + ($proposal['subType'] !== '*' ? 10 : 0) + + count($option['parameters']); + + + // Does this entry win? + if ( + ($proposal['quality'] > $lastQuality) || + ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || + ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) + ) { + + $lastQuality = $proposal['quality']; + $lastSpecificity = $specificity; + $lastOptionIndex = $optionIndex; + $lastChoice = $availableOptions[$optionIndex]; + + } + + } + + } + + return $lastChoice; + +} + /** * Parses the Prefer header, as defined in RFC7240. * @@ -204,3 +312,134 @@ function getHeaderValues($values, $values2 = null) { return $result; } + +/** + * Parses a mime-type and splits it into: + * + * 1. type + * 2. subtype + * 3. quality + * 4. parameters + * + * @param string $str + * @return array + */ +function parseMimeType($str) { + + $parameters = []; + // If no q= parameter appears, then quality = 1. + $quality = 1; + + $parts = explode(';', $str); + + // The first part is the mime-type. + $mimeType = array_shift($parts); + + $mimeType = explode('/', trim($mimeType)); + if (count($mimeType) !== 2) { + // Illegal value + return null; + } + list($type, $subType) = $mimeType; + + foreach ($parts as $part) { + + $part = trim($part); + if (strpos($part, '=')) { + list($partName, $partValue) = + explode('=', $part, 2); + } else { + $partName = $part; + $partValue = null; + } + + // The quality parameter, if it appears, also marks the end of + // the parameter list. Anything after the q= counts as an + // 'accept extension' and could introduce new semantics in + // content-negotation. + if ($partName !== 'q') { + $parameters[$partName] = $part; + } else { + $quality = (float)$partValue; + break; // Stop parsing parts + } + + } + + return [ + 'type' => $type, + 'subType' => $subType, + 'quality' => $quality, + 'parameters' => $parameters, + ]; + +} + +/** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @param string $path + * @return string + */ +function encodePath($path) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $path); + +} + +/** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @param string $pathSegment + * @return string + */ +function encodePathSegment($pathSegment) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $pathSegment); +} + +/** + * Decodes a url-encoded path + * + * @param string $path + * @return string + */ +function decodePath($path) { + + return decodePathSegment($path); + +} + +/** + * Decodes a url-encoded path segment + * + * @param string $path + * @return string + */ +function decodePathSegment($path) { + + $path = rawurldecode($path); + $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']); + + switch ($encoding) { + + case 'ISO-8859-1' : + $path = utf8_encode($path); + + } + + return $path; + +}