annotate vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @ 11:bfffd8d7479a

Move drupal/core from "replace" to "require" section, to ensure Composer updates it
author Chris Cannam
date Fri, 23 Feb 2018 15:51:18 +0000
parents 4c8ae668cc8c
children 5fb285c0d0e3
rev   line source
Chris@0 1 <?php
Chris@0 2 namespace GuzzleHttp\Cookie;
Chris@0 3
Chris@0 4 /**
Chris@0 5 * Set-Cookie object
Chris@0 6 */
Chris@0 7 class SetCookie
Chris@0 8 {
Chris@0 9 /** @var array */
Chris@0 10 private static $defaults = [
Chris@0 11 'Name' => null,
Chris@0 12 'Value' => null,
Chris@0 13 'Domain' => null,
Chris@0 14 'Path' => '/',
Chris@0 15 'Max-Age' => null,
Chris@0 16 'Expires' => null,
Chris@0 17 'Secure' => false,
Chris@0 18 'Discard' => false,
Chris@0 19 'HttpOnly' => false
Chris@0 20 ];
Chris@0 21
Chris@0 22 /** @var array Cookie data */
Chris@0 23 private $data;
Chris@0 24
Chris@0 25 /**
Chris@0 26 * Create a new SetCookie object from a string
Chris@0 27 *
Chris@0 28 * @param string $cookie Set-Cookie header string
Chris@0 29 *
Chris@0 30 * @return self
Chris@0 31 */
Chris@0 32 public static function fromString($cookie)
Chris@0 33 {
Chris@0 34 // Create the default return array
Chris@0 35 $data = self::$defaults;
Chris@0 36 // Explode the cookie string using a series of semicolons
Chris@0 37 $pieces = array_filter(array_map('trim', explode(';', $cookie)));
Chris@0 38 // The name of the cookie (first kvp) must include an equal sign.
Chris@0 39 if (empty($pieces) || !strpos($pieces[0], '=')) {
Chris@0 40 return new self($data);
Chris@0 41 }
Chris@0 42
Chris@0 43 // Add the cookie pieces into the parsed data array
Chris@0 44 foreach ($pieces as $part) {
Chris@0 45
Chris@0 46 $cookieParts = explode('=', $part, 2);
Chris@0 47 $key = trim($cookieParts[0]);
Chris@0 48 $value = isset($cookieParts[1])
Chris@0 49 ? trim($cookieParts[1], " \n\r\t\0\x0B")
Chris@0 50 : true;
Chris@0 51
Chris@0 52 // Only check for non-cookies when cookies have been found
Chris@0 53 if (empty($data['Name'])) {
Chris@0 54 $data['Name'] = $key;
Chris@0 55 $data['Value'] = $value;
Chris@0 56 } else {
Chris@0 57 foreach (array_keys(self::$defaults) as $search) {
Chris@0 58 if (!strcasecmp($search, $key)) {
Chris@0 59 $data[$search] = $value;
Chris@0 60 continue 2;
Chris@0 61 }
Chris@0 62 }
Chris@0 63 $data[$key] = $value;
Chris@0 64 }
Chris@0 65 }
Chris@0 66
Chris@0 67 return new self($data);
Chris@0 68 }
Chris@0 69
Chris@0 70 /**
Chris@0 71 * @param array $data Array of cookie data provided by a Cookie parser
Chris@0 72 */
Chris@0 73 public function __construct(array $data = [])
Chris@0 74 {
Chris@0 75 $this->data = array_replace(self::$defaults, $data);
Chris@0 76 // Extract the Expires value and turn it into a UNIX timestamp if needed
Chris@0 77 if (!$this->getExpires() && $this->getMaxAge()) {
Chris@0 78 // Calculate the Expires date
Chris@0 79 $this->setExpires(time() + $this->getMaxAge());
Chris@0 80 } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
Chris@0 81 $this->setExpires($this->getExpires());
Chris@0 82 }
Chris@0 83 }
Chris@0 84
Chris@0 85 public function __toString()
Chris@0 86 {
Chris@0 87 $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
Chris@0 88 foreach ($this->data as $k => $v) {
Chris@0 89 if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
Chris@0 90 if ($k === 'Expires') {
Chris@0 91 $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
Chris@0 92 } else {
Chris@0 93 $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
Chris@0 94 }
Chris@0 95 }
Chris@0 96 }
Chris@0 97
Chris@0 98 return rtrim($str, '; ');
Chris@0 99 }
Chris@0 100
Chris@0 101 public function toArray()
Chris@0 102 {
Chris@0 103 return $this->data;
Chris@0 104 }
Chris@0 105
Chris@0 106 /**
Chris@0 107 * Get the cookie name
Chris@0 108 *
Chris@0 109 * @return string
Chris@0 110 */
Chris@0 111 public function getName()
Chris@0 112 {
Chris@0 113 return $this->data['Name'];
Chris@0 114 }
Chris@0 115
Chris@0 116 /**
Chris@0 117 * Set the cookie name
Chris@0 118 *
Chris@0 119 * @param string $name Cookie name
Chris@0 120 */
Chris@0 121 public function setName($name)
Chris@0 122 {
Chris@0 123 $this->data['Name'] = $name;
Chris@0 124 }
Chris@0 125
Chris@0 126 /**
Chris@0 127 * Get the cookie value
Chris@0 128 *
Chris@0 129 * @return string
Chris@0 130 */
Chris@0 131 public function getValue()
Chris@0 132 {
Chris@0 133 return $this->data['Value'];
Chris@0 134 }
Chris@0 135
Chris@0 136 /**
Chris@0 137 * Set the cookie value
Chris@0 138 *
Chris@0 139 * @param string $value Cookie value
Chris@0 140 */
Chris@0 141 public function setValue($value)
Chris@0 142 {
Chris@0 143 $this->data['Value'] = $value;
Chris@0 144 }
Chris@0 145
Chris@0 146 /**
Chris@0 147 * Get the domain
Chris@0 148 *
Chris@0 149 * @return string|null
Chris@0 150 */
Chris@0 151 public function getDomain()
Chris@0 152 {
Chris@0 153 return $this->data['Domain'];
Chris@0 154 }
Chris@0 155
Chris@0 156 /**
Chris@0 157 * Set the domain of the cookie
Chris@0 158 *
Chris@0 159 * @param string $domain
Chris@0 160 */
Chris@0 161 public function setDomain($domain)
Chris@0 162 {
Chris@0 163 $this->data['Domain'] = $domain;
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * Get the path
Chris@0 168 *
Chris@0 169 * @return string
Chris@0 170 */
Chris@0 171 public function getPath()
Chris@0 172 {
Chris@0 173 return $this->data['Path'];
Chris@0 174 }
Chris@0 175
Chris@0 176 /**
Chris@0 177 * Set the path of the cookie
Chris@0 178 *
Chris@0 179 * @param string $path Path of the cookie
Chris@0 180 */
Chris@0 181 public function setPath($path)
Chris@0 182 {
Chris@0 183 $this->data['Path'] = $path;
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Maximum lifetime of the cookie in seconds
Chris@0 188 *
Chris@0 189 * @return int|null
Chris@0 190 */
Chris@0 191 public function getMaxAge()
Chris@0 192 {
Chris@0 193 return $this->data['Max-Age'];
Chris@0 194 }
Chris@0 195
Chris@0 196 /**
Chris@0 197 * Set the max-age of the cookie
Chris@0 198 *
Chris@0 199 * @param int $maxAge Max age of the cookie in seconds
Chris@0 200 */
Chris@0 201 public function setMaxAge($maxAge)
Chris@0 202 {
Chris@0 203 $this->data['Max-Age'] = $maxAge;
Chris@0 204 }
Chris@0 205
Chris@0 206 /**
Chris@0 207 * The UNIX timestamp when the cookie Expires
Chris@0 208 *
Chris@0 209 * @return mixed
Chris@0 210 */
Chris@0 211 public function getExpires()
Chris@0 212 {
Chris@0 213 return $this->data['Expires'];
Chris@0 214 }
Chris@0 215
Chris@0 216 /**
Chris@0 217 * Set the unix timestamp for which the cookie will expire
Chris@0 218 *
Chris@0 219 * @param int $timestamp Unix timestamp
Chris@0 220 */
Chris@0 221 public function setExpires($timestamp)
Chris@0 222 {
Chris@0 223 $this->data['Expires'] = is_numeric($timestamp)
Chris@0 224 ? (int) $timestamp
Chris@0 225 : strtotime($timestamp);
Chris@0 226 }
Chris@0 227
Chris@0 228 /**
Chris@0 229 * Get whether or not this is a secure cookie
Chris@0 230 *
Chris@0 231 * @return null|bool
Chris@0 232 */
Chris@0 233 public function getSecure()
Chris@0 234 {
Chris@0 235 return $this->data['Secure'];
Chris@0 236 }
Chris@0 237
Chris@0 238 /**
Chris@0 239 * Set whether or not the cookie is secure
Chris@0 240 *
Chris@0 241 * @param bool $secure Set to true or false if secure
Chris@0 242 */
Chris@0 243 public function setSecure($secure)
Chris@0 244 {
Chris@0 245 $this->data['Secure'] = $secure;
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * Get whether or not this is a session cookie
Chris@0 250 *
Chris@0 251 * @return null|bool
Chris@0 252 */
Chris@0 253 public function getDiscard()
Chris@0 254 {
Chris@0 255 return $this->data['Discard'];
Chris@0 256 }
Chris@0 257
Chris@0 258 /**
Chris@0 259 * Set whether or not this is a session cookie
Chris@0 260 *
Chris@0 261 * @param bool $discard Set to true or false if this is a session cookie
Chris@0 262 */
Chris@0 263 public function setDiscard($discard)
Chris@0 264 {
Chris@0 265 $this->data['Discard'] = $discard;
Chris@0 266 }
Chris@0 267
Chris@0 268 /**
Chris@0 269 * Get whether or not this is an HTTP only cookie
Chris@0 270 *
Chris@0 271 * @return bool
Chris@0 272 */
Chris@0 273 public function getHttpOnly()
Chris@0 274 {
Chris@0 275 return $this->data['HttpOnly'];
Chris@0 276 }
Chris@0 277
Chris@0 278 /**
Chris@0 279 * Set whether or not this is an HTTP only cookie
Chris@0 280 *
Chris@0 281 * @param bool $httpOnly Set to true or false if this is HTTP only
Chris@0 282 */
Chris@0 283 public function setHttpOnly($httpOnly)
Chris@0 284 {
Chris@0 285 $this->data['HttpOnly'] = $httpOnly;
Chris@0 286 }
Chris@0 287
Chris@0 288 /**
Chris@0 289 * Check if the cookie matches a path value.
Chris@0 290 *
Chris@0 291 * A request-path path-matches a given cookie-path if at least one of
Chris@0 292 * the following conditions holds:
Chris@0 293 *
Chris@0 294 * - The cookie-path and the request-path are identical.
Chris@0 295 * - The cookie-path is a prefix of the request-path, and the last
Chris@0 296 * character of the cookie-path is %x2F ("/").
Chris@0 297 * - The cookie-path is a prefix of the request-path, and the first
Chris@0 298 * character of the request-path that is not included in the cookie-
Chris@0 299 * path is a %x2F ("/") character.
Chris@0 300 *
Chris@0 301 * @param string $requestPath Path to check against
Chris@0 302 *
Chris@0 303 * @return bool
Chris@0 304 */
Chris@0 305 public function matchesPath($requestPath)
Chris@0 306 {
Chris@0 307 $cookiePath = $this->getPath();
Chris@0 308
Chris@0 309 // Match on exact matches or when path is the default empty "/"
Chris@0 310 if ($cookiePath === '/' || $cookiePath == $requestPath) {
Chris@0 311 return true;
Chris@0 312 }
Chris@0 313
Chris@0 314 // Ensure that the cookie-path is a prefix of the request path.
Chris@0 315 if (0 !== strpos($requestPath, $cookiePath)) {
Chris@0 316 return false;
Chris@0 317 }
Chris@0 318
Chris@0 319 // Match if the last character of the cookie-path is "/"
Chris@0 320 if (substr($cookiePath, -1, 1) === '/') {
Chris@0 321 return true;
Chris@0 322 }
Chris@0 323
Chris@0 324 // Match if the first character not included in cookie path is "/"
Chris@0 325 return substr($requestPath, strlen($cookiePath), 1) === '/';
Chris@0 326 }
Chris@0 327
Chris@0 328 /**
Chris@0 329 * Check if the cookie matches a domain value
Chris@0 330 *
Chris@0 331 * @param string $domain Domain to check against
Chris@0 332 *
Chris@0 333 * @return bool
Chris@0 334 */
Chris@0 335 public function matchesDomain($domain)
Chris@0 336 {
Chris@0 337 // Remove the leading '.' as per spec in RFC 6265.
Chris@0 338 // http://tools.ietf.org/html/rfc6265#section-5.2.3
Chris@0 339 $cookieDomain = ltrim($this->getDomain(), '.');
Chris@0 340
Chris@0 341 // Domain not set or exact match.
Chris@0 342 if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
Chris@0 343 return true;
Chris@0 344 }
Chris@0 345
Chris@0 346 // Matching the subdomain according to RFC 6265.
Chris@0 347 // http://tools.ietf.org/html/rfc6265#section-5.1.3
Chris@0 348 if (filter_var($domain, FILTER_VALIDATE_IP)) {
Chris@0 349 return false;
Chris@0 350 }
Chris@0 351
Chris@0 352 return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
Chris@0 353 }
Chris@0 354
Chris@0 355 /**
Chris@0 356 * Check if the cookie is expired
Chris@0 357 *
Chris@0 358 * @return bool
Chris@0 359 */
Chris@0 360 public function isExpired()
Chris@0 361 {
Chris@0 362 return $this->getExpires() && time() > $this->getExpires();
Chris@0 363 }
Chris@0 364
Chris@0 365 /**
Chris@0 366 * Check if the cookie is valid according to RFC 6265
Chris@0 367 *
Chris@0 368 * @return bool|string Returns true if valid or an error message if invalid
Chris@0 369 */
Chris@0 370 public function validate()
Chris@0 371 {
Chris@0 372 // Names must not be empty, but can be 0
Chris@0 373 $name = $this->getName();
Chris@0 374 if (empty($name) && !is_numeric($name)) {
Chris@0 375 return 'The cookie name must not be empty';
Chris@0 376 }
Chris@0 377
Chris@0 378 // Check if any of the invalid characters are present in the cookie name
Chris@0 379 if (preg_match(
Chris@0 380 '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
Chris@0 381 $name)
Chris@0 382 ) {
Chris@0 383 return 'Cookie name must not contain invalid characters: ASCII '
Chris@0 384 . 'Control characters (0-31;127), space, tab and the '
Chris@0 385 . 'following characters: ()<>@,;:\"/?={}';
Chris@0 386 }
Chris@0 387
Chris@0 388 // Value must not be empty, but can be 0
Chris@0 389 $value = $this->getValue();
Chris@0 390 if (empty($value) && !is_numeric($value)) {
Chris@0 391 return 'The cookie value must not be empty';
Chris@0 392 }
Chris@0 393
Chris@0 394 // Domains must not be empty, but can be 0
Chris@0 395 // A "0" is not a valid internet domain, but may be used as server name
Chris@0 396 // in a private network.
Chris@0 397 $domain = $this->getDomain();
Chris@0 398 if (empty($domain) && !is_numeric($domain)) {
Chris@0 399 return 'The cookie domain must not be empty';
Chris@0 400 }
Chris@0 401
Chris@0 402 return true;
Chris@0 403 }
Chris@0 404 }