PageRenderTime 54ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

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

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