PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/ZF2/library/Zend/Http/Header/SetCookie.php

https://github.com/XataWork/zf2-project
PHP | 635 lines | 321 code | 83 blank | 231 comment | 56 complexity | a9b6db7f99fdc54b2f93ecc1ba8bdb8f MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Http\Header;
  10. use Zend\Uri\UriFactory;
  11. /**
  12. * @throws Exception\InvalidArgumentException
  13. * @see http://www.ietf.org/rfc/rfc2109.txt
  14. * @see http://www.w3.org/Protocols/rfc2109/rfc2109
  15. */
  16. class SetCookie implements MultipleHeaderInterface
  17. {
  18. /**
  19. * Cookie name
  20. *
  21. * @var string|null
  22. */
  23. protected $name = null;
  24. /**
  25. * Cookie value
  26. *
  27. * @var string|null
  28. */
  29. protected $value = null;
  30. /**
  31. * Version
  32. *
  33. * @var int|null
  34. */
  35. protected $version = null;
  36. /**
  37. * Max Age
  38. *
  39. * @var int|null
  40. */
  41. protected $maxAge = null;
  42. /**
  43. * Cookie expiry date
  44. *
  45. * @var int|null
  46. */
  47. protected $expires = null;
  48. /**
  49. * Cookie domain
  50. *
  51. * @var string|null
  52. */
  53. protected $domain = null;
  54. /**
  55. * Cookie path
  56. *
  57. * @var string|null
  58. */
  59. protected $path = null;
  60. /**
  61. * Whether the cookie is secure or not
  62. *
  63. * @var bool|null
  64. */
  65. protected $secure = null;
  66. /**
  67. * If the value need to be quoted or not
  68. *
  69. * @var bool
  70. */
  71. protected $quoteFieldValue = false;
  72. /**
  73. * @var bool|null
  74. */
  75. protected $httponly = null;
  76. /**
  77. * @static
  78. * @throws Exception\InvalidArgumentException
  79. * @param $headerLine
  80. * @param bool $bypassHeaderFieldName
  81. * @return array|SetCookie
  82. */
  83. public static function fromString($headerLine, $bypassHeaderFieldName = false)
  84. {
  85. static $setCookieProcessor = null;
  86. if ($setCookieProcessor === null) {
  87. $setCookieClass = get_called_class();
  88. $setCookieProcessor = function ($headerLine) use ($setCookieClass) {
  89. $header = new $setCookieClass;
  90. $keyValuePairs = preg_split('#;\s*#', $headerLine);
  91. foreach ($keyValuePairs as $keyValue) {
  92. if (preg_match('#^(?P<headerKey>[^=]+)=\s*("?)(?P<headerValue>[^"]*)\2#', $keyValue, $matches)) {
  93. $headerKey = $matches['headerKey'];
  94. $headerValue= $matches['headerValue'];
  95. } else {
  96. $headerKey = $keyValue;
  97. $headerValue = null;
  98. }
  99. // First K=V pair is always the cookie name and value
  100. if ($header->getName() === NULL) {
  101. $header->setName($headerKey);
  102. $header->setValue(urldecode($headerValue));
  103. continue;
  104. }
  105. // Process the remaining elements
  106. switch (str_replace(array('-', '_'), '', strtolower($headerKey))) {
  107. case 'expires' : $header->setExpires($headerValue); break;
  108. case 'domain' : $header->setDomain($headerValue); break;
  109. case 'path' : $header->setPath($headerValue); break;
  110. case 'secure' : $header->setSecure(true); break;
  111. case 'httponly': $header->setHttponly(true); break;
  112. case 'version' : $header->setVersion((int) $headerValue); break;
  113. case 'maxage' : $header->setMaxAge((int) $headerValue); break;
  114. default:
  115. // Intentionally omitted
  116. }
  117. }
  118. return $header;
  119. };
  120. }
  121. list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
  122. // some sites return set-cookie::value, this is to get rid of the second :
  123. $name = (strtolower($name) =='set-cookie:') ? 'set-cookie' : $name;
  124. // check to ensure proper header type for this factory
  125. if (strtolower($name) !== 'set-cookie') {
  126. throw new Exception\InvalidArgumentException('Invalid header line for Set-Cookie string: "' . $name . '"');
  127. }
  128. $multipleHeaders = preg_split('#(?<!Sun|Mon|Tue|Wed|Thu|Fri|Sat),\s*#', $value);
  129. if (count($multipleHeaders) <= 1) {
  130. return $setCookieProcessor(array_pop($multipleHeaders));
  131. } else {
  132. $headers = array();
  133. foreach ($multipleHeaders as $headerLine) {
  134. $headers[] = $setCookieProcessor($headerLine);
  135. }
  136. return $headers;
  137. }
  138. }
  139. /**
  140. * Cookie object constructor
  141. *
  142. * @todo Add validation of each one of the parameters (legal domain, etc.)
  143. *
  144. * @param string $name
  145. * @param string $value
  146. * @param int|string $expires
  147. * @param string $path
  148. * @param string $domain
  149. * @param bool $secure
  150. * @param bool $httponly
  151. * @param string $maxAge
  152. * @param int $version
  153. * @return SetCookie
  154. */
  155. public function __construct($name = null, $value = null, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false, $maxAge = null, $version = null)
  156. {
  157. $this->type = 'Cookie';
  158. $this->setName($name)
  159. ->setValue($value)
  160. ->setVersion($version)
  161. ->setMaxAge($maxAge)
  162. ->setDomain($domain)
  163. ->setExpires($expires)
  164. ->setPath($path)
  165. ->setSecure($secure)
  166. ->setHttpOnly($httponly);
  167. }
  168. /**
  169. * @return string 'Set-Cookie'
  170. */
  171. public function getFieldName()
  172. {
  173. return 'Set-Cookie';
  174. }
  175. /**
  176. * @throws Exception\RuntimeException
  177. * @return string
  178. */
  179. public function getFieldValue()
  180. {
  181. if ($this->getName() == '') {
  182. return '';
  183. }
  184. $value = urlencode($this->getValue());
  185. if ( $this->hasQuoteFieldValue() ) {
  186. $value = '"'. $value . '"';
  187. }
  188. $fieldValue = $this->getName() . '=' . $value;
  189. $version = $this->getVersion();
  190. if ($version!==null) {
  191. $fieldValue .= '; Version=' . $version;
  192. }
  193. $maxAge = $this->getMaxAge();
  194. if ($maxAge!==null) {
  195. $fieldValue .= '; Max-Age=' . $maxAge;
  196. }
  197. $expires = $this->getExpires();
  198. if ($expires) {
  199. $fieldValue .= '; Expires=' . $expires;
  200. }
  201. $domain = $this->getDomain();
  202. if ($domain) {
  203. $fieldValue .= '; Domain=' . $domain;
  204. }
  205. $path = $this->getPath();
  206. if ($path) {
  207. $fieldValue .= '; Path=' . $path;
  208. }
  209. if ($this->isSecure()) {
  210. $fieldValue .= '; Secure';
  211. }
  212. if ($this->isHttponly()) {
  213. $fieldValue .= '; HttpOnly';
  214. }
  215. return $fieldValue;
  216. }
  217. /**
  218. * @param string $name
  219. * @throws Exception\InvalidArgumentException
  220. * @return SetCookie
  221. */
  222. public function setName($name)
  223. {
  224. $this->name = $name;
  225. return $this;
  226. }
  227. /**
  228. * @return string
  229. */
  230. public function getName()
  231. {
  232. return $this->name;
  233. }
  234. /**
  235. * @param string $value
  236. * @return SetCookie
  237. */
  238. public function setValue($value)
  239. {
  240. $this->value = $value;
  241. return $this;
  242. }
  243. /**
  244. * @return string
  245. */
  246. public function getValue()
  247. {
  248. return $this->value;
  249. }
  250. /**
  251. * Set version
  252. *
  253. * @param int $version
  254. * @throws Exception\InvalidArgumentException
  255. * @return SetCookie
  256. */
  257. public function setVersion($version)
  258. {
  259. if ($version !== null && !is_int($version)) {
  260. throw new Exception\InvalidArgumentException('Invalid Version number specified');
  261. }
  262. $this->version = $version;
  263. return $this;
  264. }
  265. /**
  266. * Get version
  267. *
  268. * @return int
  269. */
  270. public function getVersion()
  271. {
  272. return $this->version;
  273. }
  274. /**
  275. * Set Max-Age
  276. *
  277. * @param int $maxAge
  278. * @throws Exception\InvalidArgumentException
  279. * @return SetCookie
  280. */
  281. public function setMaxAge($maxAge)
  282. {
  283. if ($maxAge !== null && (!is_int($maxAge) || ($maxAge < 0))) {
  284. throw new Exception\InvalidArgumentException('Invalid Max-Age number specified');
  285. }
  286. $this->maxAge = $maxAge;
  287. return $this;
  288. }
  289. /**
  290. * Get Max-Age
  291. *
  292. * @return int
  293. */
  294. public function getMaxAge()
  295. {
  296. return $this->maxAge;
  297. }
  298. /**
  299. * @param int|string $expires
  300. * @throws Exception\InvalidArgumentException
  301. * @return SetCookie
  302. */
  303. public function setExpires($expires)
  304. {
  305. if ($expires === null) {
  306. $this->expires = null;
  307. return $this;
  308. }
  309. $tsExpires = $expires;
  310. if (is_string($expires)) {
  311. $tsExpires = strtotime($expires);
  312. // if $tsExpires is invalid and PHP is compiled as 32bit. Check if it fail reason is the 2038 bug
  313. if (!is_int($tsExpires) && PHP_INT_SIZE === 4) {
  314. $dateTime = new \DateTime($expires);
  315. if ( $dateTime->format('Y') > 2038) {
  316. $tsExpires = PHP_INT_MAX;
  317. }
  318. }
  319. }
  320. if (!is_int($tsExpires) || $tsExpires < 0) {
  321. throw new Exception\InvalidArgumentException('Invalid expires time specified');
  322. }
  323. $this->expires = $tsExpires;
  324. return $this;
  325. }
  326. /**
  327. * @param bool $inSeconds
  328. * @return int|string
  329. */
  330. public function getExpires($inSeconds = false)
  331. {
  332. if ($this->expires === null) {
  333. return;
  334. }
  335. if ($inSeconds) {
  336. return $this->expires;
  337. }
  338. return gmdate('D, d-M-Y H:i:s', $this->expires) . ' GMT';
  339. }
  340. /**
  341. * @param string $domain
  342. * @return SetCookie
  343. */
  344. public function setDomain($domain)
  345. {
  346. $this->domain = $domain;
  347. return $this;
  348. }
  349. /**
  350. * @return string
  351. */
  352. public function getDomain()
  353. {
  354. return $this->domain;
  355. }
  356. /**
  357. * @param string $path
  358. * @return SetCookie
  359. */
  360. public function setPath($path)
  361. {
  362. $this->path = $path;
  363. return $this;
  364. }
  365. /**
  366. * @return string
  367. */
  368. public function getPath()
  369. {
  370. return $this->path;
  371. }
  372. /**
  373. * @param bool $secure
  374. * @return SetCookie
  375. */
  376. public function setSecure($secure)
  377. {
  378. $this->secure = $secure;
  379. return $this;
  380. }
  381. /**
  382. * Set whether the value for this cookie should be quoted
  383. *
  384. * @param bool $quotedValue
  385. * @return SetCookie
  386. */
  387. public function setQuoteFieldValue($quotedValue)
  388. {
  389. $this->quoteFieldValue = (bool) $quotedValue;
  390. return $this;
  391. }
  392. /**
  393. * @return bool
  394. */
  395. public function isSecure()
  396. {
  397. return $this->secure;
  398. }
  399. /**
  400. * @param bool $httponly
  401. * @return SetCookie
  402. */
  403. public function setHttponly($httponly)
  404. {
  405. $this->httponly = $httponly;
  406. return $this;
  407. }
  408. /**
  409. * @return bool
  410. */
  411. public function isHttponly()
  412. {
  413. return $this->httponly;
  414. }
  415. /**
  416. * Check whether the cookie has expired
  417. *
  418. * Always returns false if the cookie is a session cookie (has no expiry time)
  419. *
  420. * @param int $now Timestamp to consider as "now"
  421. * @return bool
  422. */
  423. public function isExpired($now = null)
  424. {
  425. if ($now === null) {
  426. $now = time();
  427. }
  428. if (is_int($this->expires) && $this->expires < $now) {
  429. return true;
  430. }
  431. return false;
  432. }
  433. /**
  434. * Check whether the cookie is a session cookie (has no expiry time set)
  435. *
  436. * @return bool
  437. */
  438. public function isSessionCookie()
  439. {
  440. return ($this->expires === null);
  441. }
  442. /**
  443. * Check whether the value for this cookie should be quoted
  444. *
  445. * @return bool
  446. */
  447. public function hasQuoteFieldValue()
  448. {
  449. return $this->quoteFieldValue;
  450. }
  451. public function isValidForRequest($requestDomain, $path, $isSecure = false)
  452. {
  453. if ($this->getDomain() && (strrpos($requestDomain, $this->getDomain()) === false)) {
  454. return false;
  455. }
  456. if ($this->getPath() && (strpos($path, $this->getPath()) !== 0)) {
  457. return false;
  458. }
  459. if ($this->secure && $this->isSecure()!==$isSecure) {
  460. return false;
  461. }
  462. return true;
  463. }
  464. /**
  465. * Checks whether the cookie should be sent or not in a specific scenario
  466. *
  467. * @param string|\Zend\Uri\Uri $uri URI to check against (secure, domain, path)
  468. * @param bool $matchSessionCookies Whether to send session cookies
  469. * @param int $now Override the current time when checking for expiry time
  470. * @return bool
  471. * @throws Exception\InvalidArgumentException If URI does not have HTTP or HTTPS scheme.
  472. */
  473. public function match($uri, $matchSessionCookies = true, $now = null)
  474. {
  475. if (is_string ($uri)) {
  476. $uri = UriFactory::factory($uri);
  477. }
  478. // Make sure we have a valid Zend_Uri_Http object
  479. if (! ($uri->isValid() && ($uri->getScheme() == 'http' || $uri->getScheme() =='https'))) {
  480. throw new Exception\InvalidArgumentException('Passed URI is not a valid HTTP or HTTPS URI');
  481. }
  482. // Check that the cookie is secure (if required) and not expired
  483. if ($this->secure && $uri->getScheme() != 'https') {
  484. return false;
  485. }
  486. if ($this->isExpired($now)) {
  487. return false;
  488. }
  489. if ($this->isSessionCookie() && ! $matchSessionCookies) {
  490. return false;
  491. }
  492. // Check if the domain matches
  493. if (! self::matchCookieDomain($this->getDomain(), $uri->getHost())) {
  494. return false;
  495. }
  496. // Check that path matches using prefix match
  497. if (! self::matchCookiePath($this->getPath(), $uri->getPath())) {
  498. return false;
  499. }
  500. // If we didn't die until now, return true.
  501. return true;
  502. }
  503. /**
  504. * Check if a cookie's domain matches a host name.
  505. *
  506. * Used by Zend\Http\Cookies for cookie matching
  507. *
  508. * @param string $cookieDomain
  509. * @param string $host
  510. *
  511. * @return bool
  512. */
  513. public static function matchCookieDomain($cookieDomain, $host)
  514. {
  515. $cookieDomain = strtolower($cookieDomain);
  516. $host = strtolower($host);
  517. // Check for either exact match or suffix match
  518. return ($cookieDomain == $host ||
  519. preg_match('/' . preg_quote($cookieDomain) . '$/', $host));
  520. }
  521. /**
  522. * Check if a cookie's path matches a URL path
  523. *
  524. * Used by Zend\Http\Cookies for cookie matching
  525. *
  526. * @param string $cookiePath
  527. * @param string $path
  528. * @return bool
  529. */
  530. public static function matchCookiePath($cookiePath, $path)
  531. {
  532. return (strpos($path, $cookiePath) === 0);
  533. }
  534. public function toString()
  535. {
  536. return 'Set-Cookie: ' . $this->getFieldValue();
  537. }
  538. public function toStringMultipleHeaders(array $headers)
  539. {
  540. $headerLine = $this->toString();
  541. /* @var $header SetCookie */
  542. foreach ($headers as $header) {
  543. if (!$header instanceof SetCookie) {
  544. throw new Exception\RuntimeException(
  545. 'The SetCookie multiple header implementation can only accept an array of SetCookie headers'
  546. );
  547. }
  548. $headerLine .= "\n" . $header->toString();
  549. }
  550. return $headerLine;
  551. }
  552. }