1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\HTTP; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse DateTime; 6*a1a3b679SAndreas Boehler 7*a1a3b679SAndreas Boehler/** 8*a1a3b679SAndreas Boehler * A collection of useful helpers for parsing or generating various HTTP 9*a1a3b679SAndreas Boehler * headers. 10*a1a3b679SAndreas Boehler * 11*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved. 12*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 13*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 14*a1a3b679SAndreas Boehler */ 15*a1a3b679SAndreas Boehler 16*a1a3b679SAndreas Boehler/** 17*a1a3b679SAndreas Boehler * Parses a HTTP date-string. 18*a1a3b679SAndreas Boehler * 19*a1a3b679SAndreas Boehler * This method returns false if the date is invalid. 20*a1a3b679SAndreas Boehler * 21*a1a3b679SAndreas Boehler * The following formats are supported: 22*a1a3b679SAndreas Boehler * Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate 23*a1a3b679SAndreas Boehler * Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format 24*a1a3b679SAndreas Boehler * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 25*a1a3b679SAndreas Boehler * 26*a1a3b679SAndreas Boehler * See: 27*a1a3b679SAndreas Boehler * http://tools.ietf.org/html/rfc7231#section-7.1.1.1 28*a1a3b679SAndreas Boehler * 29*a1a3b679SAndreas Boehler * @param string $dateString 30*a1a3b679SAndreas Boehler * @return bool|DateTime 31*a1a3b679SAndreas Boehler */ 32*a1a3b679SAndreas Boehlerfunction parseDate($dateString) { 33*a1a3b679SAndreas Boehler 34*a1a3b679SAndreas Boehler // Only the format is checked, valid ranges are checked by strtotime below 35*a1a3b679SAndreas Boehler $month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'; 36*a1a3b679SAndreas Boehler $weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)'; 37*a1a3b679SAndreas Boehler $wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'; 38*a1a3b679SAndreas Boehler $time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}'; 39*a1a3b679SAndreas Boehler $date3 = $month . ' ([12]\d|3[01]| [1-9])'; 40*a1a3b679SAndreas Boehler $date2 = '(0[1-9]|[12]\d|3[01])\-' . $month . '\-\d{2}'; 41*a1a3b679SAndreas Boehler // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 42*a1a3b679SAndreas Boehler $date1 = '(0[1-9]|[12]\d|3[01]) ' . $month . ' [1-9]\d{3}'; 43*a1a3b679SAndreas Boehler 44*a1a3b679SAndreas Boehler // ANSI C's asctime() format 45*a1a3b679SAndreas Boehler // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 46*a1a3b679SAndreas Boehler $asctime_date = $wkday . ' ' . $date3 . ' ' . $time . ' [1-9]\d{3}'; 47*a1a3b679SAndreas Boehler // RFC 850, obsoleted by RFC 1036 48*a1a3b679SAndreas Boehler $rfc850_date = $weekday . ', ' . $date2 . ' ' . $time . ' GMT'; 49*a1a3b679SAndreas Boehler // RFC 822, updated by RFC 1123 50*a1a3b679SAndreas Boehler $rfc1123_date = $wkday . ', ' . $date1 . ' ' . $time . ' GMT'; 51*a1a3b679SAndreas Boehler // allowed date formats by RFC 2616 52*a1a3b679SAndreas Boehler $HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)"; 53*a1a3b679SAndreas Boehler 54*a1a3b679SAndreas Boehler // allow for space around the string and strip it 55*a1a3b679SAndreas Boehler $dateString = trim($dateString, ' '); 56*a1a3b679SAndreas Boehler if (!preg_match('/^' . $HTTP_date . '$/', $dateString)) 57*a1a3b679SAndreas Boehler return false; 58*a1a3b679SAndreas Boehler 59*a1a3b679SAndreas Boehler // append implicit GMT timezone to ANSI C time format 60*a1a3b679SAndreas Boehler if (strpos($dateString, ' GMT') === false) 61*a1a3b679SAndreas Boehler $dateString .= ' GMT'; 62*a1a3b679SAndreas Boehler 63*a1a3b679SAndreas Boehler try { 64*a1a3b679SAndreas Boehler return new DateTime($dateString, new \DateTimeZone('UTC')); 65*a1a3b679SAndreas Boehler } catch (\Exception $e) { 66*a1a3b679SAndreas Boehler return false; 67*a1a3b679SAndreas Boehler } 68*a1a3b679SAndreas Boehler 69*a1a3b679SAndreas Boehler} 70*a1a3b679SAndreas Boehler 71*a1a3b679SAndreas Boehler/** 72*a1a3b679SAndreas Boehler * Transforms a DateTime object to a valid HTTP/1.1 Date header value 73*a1a3b679SAndreas Boehler * 74*a1a3b679SAndreas Boehler * @param DateTime $dateTime 75*a1a3b679SAndreas Boehler * @return string 76*a1a3b679SAndreas Boehler */ 77*a1a3b679SAndreas Boehlerfunction toDate(DateTime $dateTime) { 78*a1a3b679SAndreas Boehler 79*a1a3b679SAndreas Boehler // We need to clone it, as we don't want to affect the existing 80*a1a3b679SAndreas Boehler // DateTime. 81*a1a3b679SAndreas Boehler $dateTime = clone $dateTime; 82*a1a3b679SAndreas Boehler $dateTime->setTimeZone(new \DateTimeZone('GMT')); 83*a1a3b679SAndreas Boehler return $dateTime->format('D, d M Y H:i:s \G\M\T'); 84*a1a3b679SAndreas Boehler 85*a1a3b679SAndreas Boehler} 86*a1a3b679SAndreas Boehler 87*a1a3b679SAndreas Boehler/** 88*a1a3b679SAndreas Boehler * This function can be used to aid with content negotiation. 89*a1a3b679SAndreas Boehler * 90*a1a3b679SAndreas Boehler * It takes 2 arguments, the $acceptHeaderValue, which usually comes from 91*a1a3b679SAndreas Boehler * an Accept header, and $availableOptions, which contains an array of 92*a1a3b679SAndreas Boehler * items that the server can support. 93*a1a3b679SAndreas Boehler * 94*a1a3b679SAndreas Boehler * The result of this function will be the 'best possible option'. If no 95*a1a3b679SAndreas Boehler * best possible option could be found, null is returned. 96*a1a3b679SAndreas Boehler * 97*a1a3b679SAndreas Boehler * When it's null you can according to the spec either return a default, or 98*a1a3b679SAndreas Boehler * you can choose to emit 406 Not Acceptable. 99*a1a3b679SAndreas Boehler * 100*a1a3b679SAndreas Boehler * The method also accepts sending 'null' for the $acceptHeaderValue, 101*a1a3b679SAndreas Boehler * implying that no accept header was sent. 102*a1a3b679SAndreas Boehler * 103*a1a3b679SAndreas Boehler * @param string|null $acceptHeaderValue 104*a1a3b679SAndreas Boehler * @param array $availableOptions 105*a1a3b679SAndreas Boehler * @return string|null 106*a1a3b679SAndreas Boehler */ 107*a1a3b679SAndreas Boehlerfunction negotiateContentType($acceptHeaderValue, array $availableOptions) { 108*a1a3b679SAndreas Boehler 109*a1a3b679SAndreas Boehler if (!$acceptHeaderValue) { 110*a1a3b679SAndreas Boehler // Grabbing the first in the list. 111*a1a3b679SAndreas Boehler return reset($availableOptions); 112*a1a3b679SAndreas Boehler } 113*a1a3b679SAndreas Boehler 114*a1a3b679SAndreas Boehler $proposals = array_map( 115*a1a3b679SAndreas Boehler 'Sabre\HTTP\parseMimeType', 116*a1a3b679SAndreas Boehler explode(',', $acceptHeaderValue) 117*a1a3b679SAndreas Boehler ); 118*a1a3b679SAndreas Boehler 119*a1a3b679SAndreas Boehler // Ensuring array keys are reset. 120*a1a3b679SAndreas Boehler $availableOptions = array_values($availableOptions); 121*a1a3b679SAndreas Boehler 122*a1a3b679SAndreas Boehler $options = array_map( 123*a1a3b679SAndreas Boehler 'Sabre\HTTP\parseMimeType', 124*a1a3b679SAndreas Boehler $availableOptions 125*a1a3b679SAndreas Boehler ); 126*a1a3b679SAndreas Boehler 127*a1a3b679SAndreas Boehler $lastQuality = 0; 128*a1a3b679SAndreas Boehler $lastSpecificity = 0; 129*a1a3b679SAndreas Boehler $lastOptionIndex = 0; 130*a1a3b679SAndreas Boehler $lastChoice = null; 131*a1a3b679SAndreas Boehler 132*a1a3b679SAndreas Boehler foreach ($proposals as $proposal) { 133*a1a3b679SAndreas Boehler 134*a1a3b679SAndreas Boehler // Ignoring broken values. 135*a1a3b679SAndreas Boehler if (is_null($proposal)) continue; 136*a1a3b679SAndreas Boehler 137*a1a3b679SAndreas Boehler // If the quality is lower we don't have to bother comparing. 138*a1a3b679SAndreas Boehler if ($proposal['quality'] < $lastQuality) { 139*a1a3b679SAndreas Boehler continue; 140*a1a3b679SAndreas Boehler } 141*a1a3b679SAndreas Boehler 142*a1a3b679SAndreas Boehler foreach ($options as $optionIndex => $option) { 143*a1a3b679SAndreas Boehler 144*a1a3b679SAndreas Boehler if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { 145*a1a3b679SAndreas Boehler // no match on type. 146*a1a3b679SAndreas Boehler continue; 147*a1a3b679SAndreas Boehler } 148*a1a3b679SAndreas Boehler if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { 149*a1a3b679SAndreas Boehler // no match on subtype. 150*a1a3b679SAndreas Boehler continue; 151*a1a3b679SAndreas Boehler } 152*a1a3b679SAndreas Boehler 153*a1a3b679SAndreas Boehler // Any parameters appearing on the options must appear on 154*a1a3b679SAndreas Boehler // proposals. 155*a1a3b679SAndreas Boehler foreach ($option['parameters'] as $paramName => $paramValue) { 156*a1a3b679SAndreas Boehler if (!array_key_exists($paramName, $proposal['parameters'])) { 157*a1a3b679SAndreas Boehler continue 2; 158*a1a3b679SAndreas Boehler } 159*a1a3b679SAndreas Boehler if ($paramValue !== $proposal['parameters'][$paramName]) { 160*a1a3b679SAndreas Boehler continue 2; 161*a1a3b679SAndreas Boehler } 162*a1a3b679SAndreas Boehler } 163*a1a3b679SAndreas Boehler 164*a1a3b679SAndreas Boehler // If we got here, we have a match on parameters, type and 165*a1a3b679SAndreas Boehler // subtype. We need to calculate a score for how specific the 166*a1a3b679SAndreas Boehler // match was. 167*a1a3b679SAndreas Boehler $specificity = 168*a1a3b679SAndreas Boehler ($proposal['type'] !== '*' ? 20 : 0) + 169*a1a3b679SAndreas Boehler ($proposal['subType'] !== '*' ? 10 : 0) + 170*a1a3b679SAndreas Boehler count($option['parameters']); 171*a1a3b679SAndreas Boehler 172*a1a3b679SAndreas Boehler 173*a1a3b679SAndreas Boehler // Does this entry win? 174*a1a3b679SAndreas Boehler if ( 175*a1a3b679SAndreas Boehler ($proposal['quality'] > $lastQuality) || 176*a1a3b679SAndreas Boehler ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || 177*a1a3b679SAndreas Boehler ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) 178*a1a3b679SAndreas Boehler ) { 179*a1a3b679SAndreas Boehler 180*a1a3b679SAndreas Boehler $lastQuality = $proposal['quality']; 181*a1a3b679SAndreas Boehler $lastSpecificity = $specificity; 182*a1a3b679SAndreas Boehler $lastOptionIndex = $optionIndex; 183*a1a3b679SAndreas Boehler $lastChoice = $availableOptions[$optionIndex]; 184*a1a3b679SAndreas Boehler 185*a1a3b679SAndreas Boehler } 186*a1a3b679SAndreas Boehler 187*a1a3b679SAndreas Boehler } 188*a1a3b679SAndreas Boehler 189*a1a3b679SAndreas Boehler } 190*a1a3b679SAndreas Boehler 191*a1a3b679SAndreas Boehler return $lastChoice; 192*a1a3b679SAndreas Boehler 193*a1a3b679SAndreas Boehler} 194*a1a3b679SAndreas Boehler 195*a1a3b679SAndreas Boehler/** 196*a1a3b679SAndreas Boehler * Parses the Prefer header, as defined in RFC7240. 197*a1a3b679SAndreas Boehler * 198*a1a3b679SAndreas Boehler * Input can be given as a single header value (string) or multiple headers 199*a1a3b679SAndreas Boehler * (array of string). 200*a1a3b679SAndreas Boehler * 201*a1a3b679SAndreas Boehler * This method will return a key->value array with the various Prefer 202*a1a3b679SAndreas Boehler * parameters. 203*a1a3b679SAndreas Boehler * 204*a1a3b679SAndreas Boehler * Prefer: return=minimal will result in: 205*a1a3b679SAndreas Boehler * 206*a1a3b679SAndreas Boehler * [ 'return' => 'minimal' ] 207*a1a3b679SAndreas Boehler * 208*a1a3b679SAndreas Boehler * Prefer: foo, wait=10 will result in: 209*a1a3b679SAndreas Boehler * 210*a1a3b679SAndreas Boehler * [ 'foo' => true, 'wait' => '10'] 211*a1a3b679SAndreas Boehler * 212*a1a3b679SAndreas Boehler * This method also supports the formats from older drafts of RFC7240, and 213*a1a3b679SAndreas Boehler * it will automatically map them to the new values, as the older values 214*a1a3b679SAndreas Boehler * are still pretty common. 215*a1a3b679SAndreas Boehler * 216*a1a3b679SAndreas Boehler * Parameters are currently discarded. There's no known prefer value that 217*a1a3b679SAndreas Boehler * uses them. 218*a1a3b679SAndreas Boehler * 219*a1a3b679SAndreas Boehler * @param string|string[] $header 220*a1a3b679SAndreas Boehler * @return array 221*a1a3b679SAndreas Boehler */ 222*a1a3b679SAndreas Boehlerfunction parsePrefer($input) { 223*a1a3b679SAndreas Boehler 224*a1a3b679SAndreas Boehler $token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+'; 225*a1a3b679SAndreas Boehler 226*a1a3b679SAndreas Boehler // Work in progress 227*a1a3b679SAndreas Boehler $word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )'; 228*a1a3b679SAndreas Boehler 229*a1a3b679SAndreas Boehler $regex = <<<REGEX 230*a1a3b679SAndreas Boehler/ 231*a1a3b679SAndreas Boehler^ 232*a1a3b679SAndreas Boehler(?<name> $token) # Prefer property name 233*a1a3b679SAndreas Boehler\s* # Optional space 234*a1a3b679SAndreas Boehler(?: = \s* # Prefer property value 235*a1a3b679SAndreas Boehler (?<value> $word) 236*a1a3b679SAndreas Boehler)? 237*a1a3b679SAndreas Boehler(?: \s* ; (?: .*))? # Prefer parameters (ignored) 238*a1a3b679SAndreas Boehler$ 239*a1a3b679SAndreas Boehler/x 240*a1a3b679SAndreas BoehlerREGEX; 241*a1a3b679SAndreas Boehler 242*a1a3b679SAndreas Boehler $output = []; 243*a1a3b679SAndreas Boehler foreach (getHeaderValues($input) as $value) { 244*a1a3b679SAndreas Boehler 245*a1a3b679SAndreas Boehler if (!preg_match($regex, $value, $matches)) { 246*a1a3b679SAndreas Boehler // Ignore 247*a1a3b679SAndreas Boehler continue; 248*a1a3b679SAndreas Boehler } 249*a1a3b679SAndreas Boehler 250*a1a3b679SAndreas Boehler // Mapping old values to their new counterparts 251*a1a3b679SAndreas Boehler switch ($matches['name']) { 252*a1a3b679SAndreas Boehler case 'return-asynch' : 253*a1a3b679SAndreas Boehler $output['respond-async'] = true; 254*a1a3b679SAndreas Boehler break; 255*a1a3b679SAndreas Boehler case 'return-representation' : 256*a1a3b679SAndreas Boehler $output['return'] = 'representation'; 257*a1a3b679SAndreas Boehler break; 258*a1a3b679SAndreas Boehler case 'return-minimal' : 259*a1a3b679SAndreas Boehler $output['return'] = 'minimal'; 260*a1a3b679SAndreas Boehler break; 261*a1a3b679SAndreas Boehler case 'strict' : 262*a1a3b679SAndreas Boehler $output['handling'] = 'strict'; 263*a1a3b679SAndreas Boehler break; 264*a1a3b679SAndreas Boehler case 'lenient' : 265*a1a3b679SAndreas Boehler $output['handling'] = 'lenient'; 266*a1a3b679SAndreas Boehler break; 267*a1a3b679SAndreas Boehler default : 268*a1a3b679SAndreas Boehler if (isset($matches['value'])) { 269*a1a3b679SAndreas Boehler $value = trim($matches['value'], '"'); 270*a1a3b679SAndreas Boehler } else { 271*a1a3b679SAndreas Boehler $value = true; 272*a1a3b679SAndreas Boehler } 273*a1a3b679SAndreas Boehler $output[strtolower($matches['name'])] = empty($value) ? true : $value; 274*a1a3b679SAndreas Boehler break; 275*a1a3b679SAndreas Boehler } 276*a1a3b679SAndreas Boehler 277*a1a3b679SAndreas Boehler } 278*a1a3b679SAndreas Boehler 279*a1a3b679SAndreas Boehler return $output; 280*a1a3b679SAndreas Boehler 281*a1a3b679SAndreas Boehler} 282*a1a3b679SAndreas Boehler 283*a1a3b679SAndreas Boehler/** 284*a1a3b679SAndreas Boehler * This method splits up headers into all their individual values. 285*a1a3b679SAndreas Boehler * 286*a1a3b679SAndreas Boehler * A HTTP header may have more than one header, such as this: 287*a1a3b679SAndreas Boehler * Cache-Control: private, no-store 288*a1a3b679SAndreas Boehler * 289*a1a3b679SAndreas Boehler * Header values are always split with a comma. 290*a1a3b679SAndreas Boehler * 291*a1a3b679SAndreas Boehler * You can pass either a string, or an array. The resulting value is always 292*a1a3b679SAndreas Boehler * an array with each spliced value. 293*a1a3b679SAndreas Boehler * 294*a1a3b679SAndreas Boehler * If the second headers argument is set, this value will simply be merged 295*a1a3b679SAndreas Boehler * in. This makes it quicker to merge an old list of values with a new set. 296*a1a3b679SAndreas Boehler * 297*a1a3b679SAndreas Boehler * @param string|string[] $values 298*a1a3b679SAndreas Boehler * @param string|string[] $values2 299*a1a3b679SAndreas Boehler * @return string[] 300*a1a3b679SAndreas Boehler */ 301*a1a3b679SAndreas Boehlerfunction getHeaderValues($values, $values2 = null) { 302*a1a3b679SAndreas Boehler 303*a1a3b679SAndreas Boehler $values = (array)$values; 304*a1a3b679SAndreas Boehler if ($values2) { 305*a1a3b679SAndreas Boehler $values = array_merge($values, (array)$values2); 306*a1a3b679SAndreas Boehler } 307*a1a3b679SAndreas Boehler foreach ($values as $l1) { 308*a1a3b679SAndreas Boehler foreach (explode(',', $l1) as $l2) { 309*a1a3b679SAndreas Boehler $result[] = trim($l2); 310*a1a3b679SAndreas Boehler } 311*a1a3b679SAndreas Boehler } 312*a1a3b679SAndreas Boehler return $result; 313*a1a3b679SAndreas Boehler 314*a1a3b679SAndreas Boehler} 315*a1a3b679SAndreas Boehler 316*a1a3b679SAndreas Boehler/** 317*a1a3b679SAndreas Boehler * Parses a mime-type and splits it into: 318*a1a3b679SAndreas Boehler * 319*a1a3b679SAndreas Boehler * 1. type 320*a1a3b679SAndreas Boehler * 2. subtype 321*a1a3b679SAndreas Boehler * 3. quality 322*a1a3b679SAndreas Boehler * 4. parameters 323*a1a3b679SAndreas Boehler * 324*a1a3b679SAndreas Boehler * @param string $str 325*a1a3b679SAndreas Boehler * @return array 326*a1a3b679SAndreas Boehler */ 327*a1a3b679SAndreas Boehlerfunction parseMimeType($str) { 328*a1a3b679SAndreas Boehler 329*a1a3b679SAndreas Boehler $parameters = []; 330*a1a3b679SAndreas Boehler // If no q= parameter appears, then quality = 1. 331*a1a3b679SAndreas Boehler $quality = 1; 332*a1a3b679SAndreas Boehler 333*a1a3b679SAndreas Boehler $parts = explode(';', $str); 334*a1a3b679SAndreas Boehler 335*a1a3b679SAndreas Boehler // The first part is the mime-type. 336*a1a3b679SAndreas Boehler $mimeType = array_shift($parts); 337*a1a3b679SAndreas Boehler 338*a1a3b679SAndreas Boehler $mimeType = explode('/', trim($mimeType)); 339*a1a3b679SAndreas Boehler if (count($mimeType) !== 2) { 340*a1a3b679SAndreas Boehler // Illegal value 341*a1a3b679SAndreas Boehler return null; 342*a1a3b679SAndreas Boehler } 343*a1a3b679SAndreas Boehler list($type, $subType) = $mimeType; 344*a1a3b679SAndreas Boehler 345*a1a3b679SAndreas Boehler foreach ($parts as $part) { 346*a1a3b679SAndreas Boehler 347*a1a3b679SAndreas Boehler $part = trim($part); 348*a1a3b679SAndreas Boehler if (strpos($part, '=')) { 349*a1a3b679SAndreas Boehler list($partName, $partValue) = 350*a1a3b679SAndreas Boehler explode('=', $part, 2); 351*a1a3b679SAndreas Boehler } else { 352*a1a3b679SAndreas Boehler $partName = $part; 353*a1a3b679SAndreas Boehler $partValue = null; 354*a1a3b679SAndreas Boehler } 355*a1a3b679SAndreas Boehler 356*a1a3b679SAndreas Boehler // The quality parameter, if it appears, also marks the end of 357*a1a3b679SAndreas Boehler // the parameter list. Anything after the q= counts as an 358*a1a3b679SAndreas Boehler // 'accept extension' and could introduce new semantics in 359*a1a3b679SAndreas Boehler // content-negotation. 360*a1a3b679SAndreas Boehler if ($partName !== 'q') { 361*a1a3b679SAndreas Boehler $parameters[$partName] = $part; 362*a1a3b679SAndreas Boehler } else { 363*a1a3b679SAndreas Boehler $quality = (float)$partValue; 364*a1a3b679SAndreas Boehler break; // Stop parsing parts 365*a1a3b679SAndreas Boehler } 366*a1a3b679SAndreas Boehler 367*a1a3b679SAndreas Boehler } 368*a1a3b679SAndreas Boehler 369*a1a3b679SAndreas Boehler return [ 370*a1a3b679SAndreas Boehler 'type' => $type, 371*a1a3b679SAndreas Boehler 'subType' => $subType, 372*a1a3b679SAndreas Boehler 'quality' => $quality, 373*a1a3b679SAndreas Boehler 'parameters' => $parameters, 374*a1a3b679SAndreas Boehler ]; 375*a1a3b679SAndreas Boehler 376*a1a3b679SAndreas Boehler} 377*a1a3b679SAndreas Boehler 378*a1a3b679SAndreas Boehler/** 379*a1a3b679SAndreas Boehler * Encodes the path of a url. 380*a1a3b679SAndreas Boehler * 381*a1a3b679SAndreas Boehler * slashes (/) are treated as path-separators. 382*a1a3b679SAndreas Boehler * 383*a1a3b679SAndreas Boehler * @param string $path 384*a1a3b679SAndreas Boehler * @return string 385*a1a3b679SAndreas Boehler */ 386*a1a3b679SAndreas Boehlerfunction encodePath($path) { 387*a1a3b679SAndreas Boehler 388*a1a3b679SAndreas Boehler return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) { 389*a1a3b679SAndreas Boehler 390*a1a3b679SAndreas Boehler return '%' . sprintf('%02x', ord($match[0])); 391*a1a3b679SAndreas Boehler 392*a1a3b679SAndreas Boehler }, $path); 393*a1a3b679SAndreas Boehler 394*a1a3b679SAndreas Boehler} 395*a1a3b679SAndreas Boehler 396*a1a3b679SAndreas Boehler/** 397*a1a3b679SAndreas Boehler * Encodes a 1 segment of a path 398*a1a3b679SAndreas Boehler * 399*a1a3b679SAndreas Boehler * Slashes are considered part of the name, and are encoded as %2f 400*a1a3b679SAndreas Boehler * 401*a1a3b679SAndreas Boehler * @param string $pathSegment 402*a1a3b679SAndreas Boehler * @return string 403*a1a3b679SAndreas Boehler */ 404*a1a3b679SAndreas Boehlerfunction encodePathSegment($pathSegment) { 405*a1a3b679SAndreas Boehler 406*a1a3b679SAndreas Boehler return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) { 407*a1a3b679SAndreas Boehler 408*a1a3b679SAndreas Boehler return '%' . sprintf('%02x', ord($match[0])); 409*a1a3b679SAndreas Boehler 410*a1a3b679SAndreas Boehler }, $pathSegment); 411*a1a3b679SAndreas Boehler} 412*a1a3b679SAndreas Boehler 413*a1a3b679SAndreas Boehler/** 414*a1a3b679SAndreas Boehler * Decodes a url-encoded path 415*a1a3b679SAndreas Boehler * 416*a1a3b679SAndreas Boehler * @param string $path 417*a1a3b679SAndreas Boehler * @return string 418*a1a3b679SAndreas Boehler */ 419*a1a3b679SAndreas Boehlerfunction decodePath($path) { 420*a1a3b679SAndreas Boehler 421*a1a3b679SAndreas Boehler return decodePathSegment($path); 422*a1a3b679SAndreas Boehler 423*a1a3b679SAndreas Boehler} 424*a1a3b679SAndreas Boehler 425*a1a3b679SAndreas Boehler/** 426*a1a3b679SAndreas Boehler * Decodes a url-encoded path segment 427*a1a3b679SAndreas Boehler * 428*a1a3b679SAndreas Boehler * @param string $path 429*a1a3b679SAndreas Boehler * @return string 430*a1a3b679SAndreas Boehler */ 431*a1a3b679SAndreas Boehlerfunction decodePathSegment($path) { 432*a1a3b679SAndreas Boehler 433*a1a3b679SAndreas Boehler $path = rawurldecode($path); 434*a1a3b679SAndreas Boehler $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']); 435*a1a3b679SAndreas Boehler 436*a1a3b679SAndreas Boehler switch ($encoding) { 437*a1a3b679SAndreas Boehler 438*a1a3b679SAndreas Boehler case 'ISO-8859-1' : 439*a1a3b679SAndreas Boehler $path = utf8_encode($path); 440*a1a3b679SAndreas Boehler 441*a1a3b679SAndreas Boehler } 442*a1a3b679SAndreas Boehler 443*a1a3b679SAndreas Boehler return $path; 444*a1a3b679SAndreas Boehler 445*a1a3b679SAndreas Boehler} 446