initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import)
This commit is contained in:
445
vendor/sabre/http/lib/functions.php
vendored
Normal file
445
vendor/sabre/http/lib/functions.php
vendored
Normal file
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace Sabre\HTTP;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* A collection of useful helpers for parsing or generating various HTTP
|
||||
* headers.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses a HTTP date-string.
|
||||
*
|
||||
* This method returns false if the date is invalid.
|
||||
*
|
||||
* The following formats are supported:
|
||||
* Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
|
||||
* Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
|
||||
* Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
*
|
||||
* See:
|
||||
* http://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
||||
*
|
||||
* @param string $dateString
|
||||
* @return bool|DateTime
|
||||
*/
|
||||
function parseDate($dateString) {
|
||||
|
||||
// Only the format is checked, valid ranges are checked by strtotime below
|
||||
$month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)';
|
||||
$weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)';
|
||||
$wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
|
||||
$time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}';
|
||||
$date3 = $month . ' ([12]\d|3[01]| [1-9])';
|
||||
$date2 = '(0[1-9]|[12]\d|3[01])\-' . $month . '\-\d{2}';
|
||||
// 4-digit year cannot begin with 0 - unix timestamp begins in 1970
|
||||
$date1 = '(0[1-9]|[12]\d|3[01]) ' . $month . ' [1-9]\d{3}';
|
||||
|
||||
// ANSI C's asctime() format
|
||||
// 4-digit year cannot begin with 0 - unix timestamp begins in 1970
|
||||
$asctime_date = $wkday . ' ' . $date3 . ' ' . $time . ' [1-9]\d{3}';
|
||||
// RFC 850, obsoleted by RFC 1036
|
||||
$rfc850_date = $weekday . ', ' . $date2 . ' ' . $time . ' GMT';
|
||||
// RFC 822, updated by RFC 1123
|
||||
$rfc1123_date = $wkday . ', ' . $date1 . ' ' . $time . ' GMT';
|
||||
// allowed date formats by RFC 2616
|
||||
$HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)";
|
||||
|
||||
// allow for space around the string and strip it
|
||||
$dateString = trim($dateString, ' ');
|
||||
if (!preg_match('/^' . $HTTP_date . '$/', $dateString))
|
||||
return false;
|
||||
|
||||
// append implicit GMT timezone to ANSI C time format
|
||||
if (strpos($dateString, ' GMT') === false)
|
||||
$dateString .= ' GMT';
|
||||
|
||||
try {
|
||||
return new DateTime($dateString, new \DateTimeZone('UTC'));
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a DateTime object to a valid HTTP/1.1 Date header value
|
||||
*
|
||||
* @param DateTime $dateTime
|
||||
* @return string
|
||||
*/
|
||||
function toDate(DateTime $dateTime) {
|
||||
|
||||
// We need to clone it, as we don't want to affect the existing
|
||||
// DateTime.
|
||||
$dateTime = clone $dateTime;
|
||||
$dateTime->setTimeZone(new \DateTimeZone('GMT'));
|
||||
return $dateTime->format('D, d M Y H:i:s \G\M\T');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Input can be given as a single header value (string) or multiple headers
|
||||
* (array of string).
|
||||
*
|
||||
* This method will return a key->value array with the various Prefer
|
||||
* parameters.
|
||||
*
|
||||
* Prefer: return=minimal will result in:
|
||||
*
|
||||
* [ 'return' => 'minimal' ]
|
||||
*
|
||||
* Prefer: foo, wait=10 will result in:
|
||||
*
|
||||
* [ 'foo' => true, 'wait' => '10']
|
||||
*
|
||||
* This method also supports the formats from older drafts of RFC7240, and
|
||||
* it will automatically map them to the new values, as the older values
|
||||
* are still pretty common.
|
||||
*
|
||||
* Parameters are currently discarded. There's no known prefer value that
|
||||
* uses them.
|
||||
*
|
||||
* @param string|string[] $header
|
||||
* @return array
|
||||
*/
|
||||
function parsePrefer($input) {
|
||||
|
||||
$token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+';
|
||||
|
||||
// Work in progress
|
||||
$word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )';
|
||||
|
||||
$regex = <<<REGEX
|
||||
/
|
||||
^
|
||||
(?<name> $token) # Prefer property name
|
||||
\s* # Optional space
|
||||
(?: = \s* # Prefer property value
|
||||
(?<value> $word)
|
||||
)?
|
||||
(?: \s* ; (?: .*))? # Prefer parameters (ignored)
|
||||
$
|
||||
/x
|
||||
REGEX;
|
||||
|
||||
$output = [];
|
||||
foreach (getHeaderValues($input) as $value) {
|
||||
|
||||
if (!preg_match($regex, $value, $matches)) {
|
||||
// Ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mapping old values to their new counterparts
|
||||
switch ($matches['name']) {
|
||||
case 'return-asynch' :
|
||||
$output['respond-async'] = true;
|
||||
break;
|
||||
case 'return-representation' :
|
||||
$output['return'] = 'representation';
|
||||
break;
|
||||
case 'return-minimal' :
|
||||
$output['return'] = 'minimal';
|
||||
break;
|
||||
case 'strict' :
|
||||
$output['handling'] = 'strict';
|
||||
break;
|
||||
case 'lenient' :
|
||||
$output['handling'] = 'lenient';
|
||||
break;
|
||||
default :
|
||||
if (isset($matches['value'])) {
|
||||
$value = trim($matches['value'], '"');
|
||||
} else {
|
||||
$value = true;
|
||||
}
|
||||
$output[strtolower($matches['name'])] = empty($value) ? true : $value;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method splits up headers into all their individual values.
|
||||
*
|
||||
* A HTTP header may have more than one header, such as this:
|
||||
* Cache-Control: private, no-store
|
||||
*
|
||||
* Header values are always split with a comma.
|
||||
*
|
||||
* You can pass either a string, or an array. The resulting value is always
|
||||
* an array with each spliced value.
|
||||
*
|
||||
* If the second headers argument is set, this value will simply be merged
|
||||
* in. This makes it quicker to merge an old list of values with a new set.
|
||||
*
|
||||
* @param string|string[] $values
|
||||
* @param string|string[] $values2
|
||||
* @return string[]
|
||||
*/
|
||||
function getHeaderValues($values, $values2 = null) {
|
||||
|
||||
$values = (array)$values;
|
||||
if ($values2) {
|
||||
$values = array_merge($values, (array)$values2);
|
||||
}
|
||||
foreach ($values as $l1) {
|
||||
foreach (explode(',', $l1) as $l2) {
|
||||
$result[] = trim($l2);
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user