Ei kuvausta

SetCookie.php 10KB

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