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