[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Cookie storage object 4 * 5 * @package Requests 6 * @subpackage Cookies 7 */ 8 9 /** 10 * Cookie storage object 11 * 12 * @package Requests 13 * @subpackage Cookies 14 */ 15 class Requests_Cookie { 16 /** 17 * Cookie name. 18 * 19 * @var string 20 */ 21 public $name; 22 23 /** 24 * Cookie value. 25 * 26 * @var string 27 */ 28 public $value; 29 30 /** 31 * Cookie attributes 32 * 33 * Valid keys are (currently) path, domain, expires, max-age, secure and 34 * httponly. 35 * 36 * @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object 37 */ 38 public $attributes = array(); 39 40 /** 41 * Cookie flags 42 * 43 * Valid keys are (currently) creation, last-access, persistent and 44 * host-only. 45 * 46 * @var array 47 */ 48 public $flags = array(); 49 50 /** 51 * Reference time for relative calculations 52 * 53 * This is used in place of `time()` when calculating Max-Age expiration and 54 * checking time validity. 55 * 56 * @var int 57 */ 58 public $reference_time = 0; 59 60 /** 61 * Create a new cookie object 62 * 63 * @param string $name 64 * @param string $value 65 * @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data 66 */ 67 public function __construct($name, $value, $attributes = array(), $flags = array(), $reference_time = null) { 68 $this->name = $name; 69 $this->value = $value; 70 $this->attributes = $attributes; 71 $default_flags = array( 72 'creation' => time(), 73 'last-access' => time(), 74 'persistent' => false, 75 'host-only' => true, 76 ); 77 $this->flags = array_merge($default_flags, $flags); 78 79 $this->reference_time = time(); 80 if ($reference_time !== null) { 81 $this->reference_time = $reference_time; 82 } 83 84 $this->normalize(); 85 } 86 87 /** 88 * Check if a cookie is expired. 89 * 90 * Checks the age against $this->reference_time to determine if the cookie 91 * is expired. 92 * 93 * @return boolean True if expired, false if time is valid. 94 */ 95 public function is_expired() { 96 // RFC6265, s. 4.1.2.2: 97 // If a cookie has both the Max-Age and the Expires attribute, the Max- 98 // Age attribute has precedence and controls the expiration date of the 99 // cookie. 100 if (isset($this->attributes['max-age'])) { 101 $max_age = $this->attributes['max-age']; 102 return $max_age < $this->reference_time; 103 } 104 105 if (isset($this->attributes['expires'])) { 106 $expires = $this->attributes['expires']; 107 return $expires < $this->reference_time; 108 } 109 110 return false; 111 } 112 113 /** 114 * Check if a cookie is valid for a given URI 115 * 116 * @param Requests_IRI $uri URI to check 117 * @return boolean Whether the cookie is valid for the given URI 118 */ 119 public function uri_matches(Requests_IRI $uri) { 120 if (!$this->domain_matches($uri->host)) { 121 return false; 122 } 123 124 if (!$this->path_matches($uri->path)) { 125 return false; 126 } 127 128 return empty($this->attributes['secure']) || $uri->scheme === 'https'; 129 } 130 131 /** 132 * Check if a cookie is valid for a given domain 133 * 134 * @param string $string Domain to check 135 * @return boolean Whether the cookie is valid for the given domain 136 */ 137 public function domain_matches($string) { 138 if (!isset($this->attributes['domain'])) { 139 // Cookies created manually; cookies created by Requests will set 140 // the domain to the requested domain 141 return true; 142 } 143 144 $domain_string = $this->attributes['domain']; 145 if ($domain_string === $string) { 146 // The domain string and the string are identical. 147 return true; 148 } 149 150 // If the cookie is marked as host-only and we don't have an exact 151 // match, reject the cookie 152 if ($this->flags['host-only'] === true) { 153 return false; 154 } 155 156 if (strlen($string) <= strlen($domain_string)) { 157 // For obvious reasons, the string cannot be a suffix if the domain 158 // is shorter than the domain string 159 return false; 160 } 161 162 if (substr($string, -1 * strlen($domain_string)) !== $domain_string) { 163 // The domain string should be a suffix of the string. 164 return false; 165 } 166 167 $prefix = substr($string, 0, strlen($string) - strlen($domain_string)); 168 if (substr($prefix, -1) !== '.') { 169 // The last character of the string that is not included in the 170 // domain string should be a %x2E (".") character. 171 return false; 172 } 173 174 // The string should be a host name (i.e., not an IP address). 175 return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string); 176 } 177 178 /** 179 * Check if a cookie is valid for a given path 180 * 181 * From the path-match check in RFC 6265 section 5.1.4 182 * 183 * @param string $request_path Path to check 184 * @return boolean Whether the cookie is valid for the given path 185 */ 186 public function path_matches($request_path) { 187 if (empty($request_path)) { 188 // Normalize empty path to root 189 $request_path = '/'; 190 } 191 192 if (!isset($this->attributes['path'])) { 193 // Cookies created manually; cookies created by Requests will set 194 // the path to the requested path 195 return true; 196 } 197 198 $cookie_path = $this->attributes['path']; 199 200 if ($cookie_path === $request_path) { 201 // The cookie-path and the request-path are identical. 202 return true; 203 } 204 205 if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) { 206 if (substr($cookie_path, -1) === '/') { 207 // The cookie-path is a prefix of the request-path, and the last 208 // character of the cookie-path is %x2F ("/"). 209 return true; 210 } 211 212 if (substr($request_path, strlen($cookie_path), 1) === '/') { 213 // The cookie-path is a prefix of the request-path, and the 214 // first character of the request-path that is not included in 215 // the cookie-path is a %x2F ("/") character. 216 return true; 217 } 218 } 219 220 return false; 221 } 222 223 /** 224 * Normalize cookie and attributes 225 * 226 * @return boolean Whether the cookie was successfully normalized 227 */ 228 public function normalize() { 229 foreach ($this->attributes as $key => $value) { 230 $orig_value = $value; 231 $value = $this->normalize_attribute($key, $value); 232 if ($value === null) { 233 unset($this->attributes[$key]); 234 continue; 235 } 236 237 if ($value !== $orig_value) { 238 $this->attributes[$key] = $value; 239 } 240 } 241 242 return true; 243 } 244 245 /** 246 * Parse an individual cookie attribute 247 * 248 * Handles parsing individual attributes from the cookie values. 249 * 250 * @param string $name Attribute name 251 * @param string|boolean $value Attribute value (string value, or true if empty/flag) 252 * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped) 253 */ 254 protected function normalize_attribute($name, $value) { 255 switch (strtolower($name)) { 256 case 'expires': 257 // Expiration parsing, as per RFC 6265 section 5.2.1 258 if (is_int($value)) { 259 return $value; 260 } 261 262 $expiry_time = strtotime($value); 263 if ($expiry_time === false) { 264 return null; 265 } 266 267 return $expiry_time; 268 269 case 'max-age': 270 // Expiration parsing, as per RFC 6265 section 5.2.2 271 if (is_int($value)) { 272 return $value; 273 } 274 275 // Check that we have a valid age 276 if (!preg_match('/^-?\d+$/', $value)) { 277 return null; 278 } 279 280 $delta_seconds = (int) $value; 281 if ($delta_seconds <= 0) { 282 $expiry_time = 0; 283 } 284 else { 285 $expiry_time = $this->reference_time + $delta_seconds; 286 } 287 288 return $expiry_time; 289 290 case 'domain': 291 // Domains are not required as per RFC 6265 section 5.2.3 292 if (empty($value)) { 293 return null; 294 } 295 296 // Domain normalization, as per RFC 6265 section 5.2.3 297 if ($value[0] === '.') { 298 $value = substr($value, 1); 299 } 300 301 return $value; 302 303 default: 304 return $value; 305 } 306 } 307 308 /** 309 * Format a cookie for a Cookie header 310 * 311 * This is used when sending cookies to a server. 312 * 313 * @return string Cookie formatted for Cookie header 314 */ 315 public function format_for_header() { 316 return sprintf('%s=%s', $this->name, $this->value); 317 } 318 319 /** 320 * Format a cookie for a Cookie header 321 * 322 * @codeCoverageIgnore 323 * @deprecated Use {@see Requests_Cookie::format_for_header} 324 * @return string 325 */ 326 public function formatForHeader() { 327 return $this->format_for_header(); 328 } 329 330 /** 331 * Format a cookie for a Set-Cookie header 332 * 333 * This is used when sending cookies to clients. This isn't really 334 * applicable to client-side usage, but might be handy for debugging. 335 * 336 * @return string Cookie formatted for Set-Cookie header 337 */ 338 public function format_for_set_cookie() { 339 $header_value = $this->format_for_header(); 340 if (!empty($this->attributes)) { 341 $parts = array(); 342 foreach ($this->attributes as $key => $value) { 343 // Ignore non-associative attributes 344 if (is_numeric($key)) { 345 $parts[] = $value; 346 } 347 else { 348 $parts[] = sprintf('%s=%s', $key, $value); 349 } 350 } 351 352 $header_value .= '; ' . implode('; ', $parts); 353 } 354 return $header_value; 355 } 356 357 /** 358 * Format a cookie for a Set-Cookie header 359 * 360 * @codeCoverageIgnore 361 * @deprecated Use {@see Requests_Cookie::format_for_set_cookie} 362 * @return string 363 */ 364 public function formatForSetCookie() { 365 return $this->format_for_set_cookie(); 366 } 367 368 /** 369 * Get the cookie value 370 * 371 * Attributes and other data can be accessed via methods. 372 */ 373 public function __toString() { 374 return $this->value; 375 } 376 377 /** 378 * Parse a cookie string into a cookie object 379 * 380 * Based on Mozilla's parsing code in Firefox and related projects, which 381 * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 382 * specifies some of this handling, but not in a thorough manner. 383 * 384 * @param string Cookie header value (from a Set-Cookie header) 385 * @return Requests_Cookie Parsed cookie object 386 */ 387 public static function parse($string, $name = '', $reference_time = null) { 388 $parts = explode(';', $string); 389 $kvparts = array_shift($parts); 390 391 if (!empty($name)) { 392 $value = $string; 393 } 394 elseif (strpos($kvparts, '=') === false) { 395 // Some sites might only have a value without the equals separator. 396 // Deviate from RFC 6265 and pretend it was actually a blank name 397 // (`=foo`) 398 // 399 // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 400 $name = ''; 401 $value = $kvparts; 402 } 403 else { 404 list($name, $value) = explode('=', $kvparts, 2); 405 } 406 $name = trim($name); 407 $value = trim($value); 408 409 // Attribute key are handled case-insensitively 410 $attributes = new Requests_Utility_CaseInsensitiveDictionary(); 411 412 if (!empty($parts)) { 413 foreach ($parts as $part) { 414 if (strpos($part, '=') === false) { 415 $part_key = $part; 416 $part_value = true; 417 } 418 else { 419 list($part_key, $part_value) = explode('=', $part, 2); 420 $part_value = trim($part_value); 421 } 422 423 $part_key = trim($part_key); 424 $attributes[$part_key] = $part_value; 425 } 426 } 427 428 return new Requests_Cookie($name, $value, $attributes, array(), $reference_time); 429 } 430 431 /** 432 * Parse all Set-Cookie headers from request headers 433 * 434 * @param Requests_Response_Headers $headers Headers to parse from 435 * @param Requests_IRI|null $origin URI for comparing cookie origins 436 * @param int|null $time Reference time for expiration calculation 437 * @return array 438 */ 439 public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) { 440 $cookie_headers = $headers->getValues('Set-Cookie'); 441 if (empty($cookie_headers)) { 442 return array(); 443 } 444 445 $cookies = array(); 446 foreach ($cookie_headers as $header) { 447 $parsed = self::parse($header, '', $time); 448 449 // Default domain/path attributes 450 if (empty($parsed->attributes['domain']) && !empty($origin)) { 451 $parsed->attributes['domain'] = $origin->host; 452 $parsed->flags['host-only'] = true; 453 } 454 else { 455 $parsed->flags['host-only'] = false; 456 } 457 458 $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/'); 459 if (!$path_is_valid && !empty($origin)) { 460 $path = $origin->path; 461 462 // Default path normalization as per RFC 6265 section 5.1.4 463 if (substr($path, 0, 1) !== '/') { 464 // If the uri-path is empty or if the first character of 465 // the uri-path is not a %x2F ("/") character, output 466 // %x2F ("/") and skip the remaining steps. 467 $path = '/'; 468 } 469 elseif (substr_count($path, '/') === 1) { 470 // If the uri-path contains no more than one %x2F ("/") 471 // character, output %x2F ("/") and skip the remaining 472 // step. 473 $path = '/'; 474 } 475 else { 476 // Output the characters of the uri-path from the first 477 // character up to, but not including, the right-most 478 // %x2F ("/"). 479 $path = substr($path, 0, strrpos($path, '/')); 480 } 481 $parsed->attributes['path'] = $path; 482 } 483 484 // Reject invalid cookie domains 485 if (!empty($origin) && !$parsed->domain_matches($origin->host)) { 486 continue; 487 } 488 489 $cookies[$parsed->name] = $parsed; 490 } 491 492 return $cookies; 493 } 494 495 /** 496 * Parse all Set-Cookie headers from request headers 497 * 498 * @codeCoverageIgnore 499 * @deprecated Use {@see Requests_Cookie::parse_from_headers} 500 * @return array 501 */ 502 public static function parseFromHeaders(Requests_Response_Headers $headers) { 503 return self::parse_from_headers($headers); 504 } 505 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Dec 15 01:00:02 2022 | Cross-referenced by PHPXref 0.7.1 |