SetCookieTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <?php
  2. namespace GuzzleHttp\Tests\CookieJar;
  3. use GuzzleHttp\Cookie\SetCookie;
  4. use PHPUnit\Framework\TestCase;
  5. /**
  6. * @covers \GuzzleHttp\Cookie\SetCookie
  7. */
  8. class SetCookieTest extends TestCase
  9. {
  10. public function testInitializesDefaultValues()
  11. {
  12. $cookie = new SetCookie();
  13. self::assertSame('/', $cookie->getPath());
  14. }
  15. public function testConvertsDateTimeMaxAgeToUnixTimestamp()
  16. {
  17. $cookie = new SetCookie(['Expires' => 'November 20, 1984']);
  18. self::assertIsInt($cookie->getExpires());
  19. }
  20. public function testAddsExpiresBasedOnMaxAge()
  21. {
  22. $t = \time();
  23. $cookie = new SetCookie(['Max-Age' => 100]);
  24. self::assertEquals($t + 100, $cookie->getExpires());
  25. }
  26. public function testHoldsValues()
  27. {
  28. $t = \time();
  29. $data = [
  30. 'Name' => 'foo',
  31. 'Value' => 'baz',
  32. 'Path' => '/bar',
  33. 'Domain' => 'baz.com',
  34. 'Expires' => $t,
  35. 'Max-Age' => 100,
  36. 'Secure' => true,
  37. 'Discard' => true,
  38. 'HttpOnly' => true,
  39. 'foo' => 'baz',
  40. 'bar' => 'bam',
  41. ];
  42. $cookie = new SetCookie($data);
  43. self::assertEquals($data, $cookie->toArray());
  44. self::assertSame('foo', $cookie->getName());
  45. self::assertSame('baz', $cookie->getValue());
  46. self::assertSame('baz.com', $cookie->getDomain());
  47. self::assertSame('/bar', $cookie->getPath());
  48. self::assertSame($t, $cookie->getExpires());
  49. self::assertSame(100, $cookie->getMaxAge());
  50. self::assertTrue($cookie->getSecure());
  51. self::assertTrue($cookie->getDiscard());
  52. self::assertTrue($cookie->getHttpOnly());
  53. self::assertSame('baz', $cookie->toArray()['foo']);
  54. self::assertSame('bam', $cookie->toArray()['bar']);
  55. $cookie->setName('a');
  56. $cookie->setValue('b');
  57. $cookie->setPath('c');
  58. $cookie->setDomain('bar.com');
  59. $cookie->setExpires(10);
  60. $cookie->setMaxAge(200);
  61. $cookie->setSecure(false);
  62. $cookie->setHttpOnly(false);
  63. $cookie->setDiscard(false);
  64. self::assertSame('a', $cookie->getName());
  65. self::assertSame('b', $cookie->getValue());
  66. self::assertSame('c', $cookie->getPath());
  67. self::assertSame('bar.com', $cookie->getDomain());
  68. self::assertSame(10, $cookie->getExpires());
  69. self::assertSame(200, $cookie->getMaxAge());
  70. self::assertFalse($cookie->getSecure());
  71. self::assertFalse($cookie->getDiscard());
  72. self::assertFalse($cookie->getHttpOnly());
  73. }
  74. public function testDeterminesIfExpired()
  75. {
  76. $c = new SetCookie();
  77. $c->setExpires(10);
  78. self::assertTrue($c->isExpired());
  79. $c->setExpires(\time() + 10000);
  80. self::assertFalse($c->isExpired());
  81. }
  82. public function testMatchesDomain()
  83. {
  84. $cookie = new SetCookie();
  85. self::assertTrue($cookie->matchesDomain('baz.com'));
  86. $cookie->setDomain('baz.com');
  87. self::assertTrue($cookie->matchesDomain('baz.com'));
  88. self::assertFalse($cookie->matchesDomain('bar.com'));
  89. $cookie->setDomain('.baz.com');
  90. self::assertTrue($cookie->matchesDomain('.baz.com'));
  91. self::assertTrue($cookie->matchesDomain('foo.baz.com'));
  92. self::assertFalse($cookie->matchesDomain('baz.bar.com'));
  93. self::assertTrue($cookie->matchesDomain('baz.com'));
  94. $cookie->setDomain('.127.0.0.1');
  95. self::assertTrue($cookie->matchesDomain('127.0.0.1'));
  96. $cookie->setDomain('127.0.0.1');
  97. self::assertTrue($cookie->matchesDomain('127.0.0.1'));
  98. $cookie->setDomain('.com.');
  99. self::assertFalse($cookie->matchesDomain('baz.com'));
  100. $cookie->setDomain('.local');
  101. self::assertTrue($cookie->matchesDomain('example.local'));
  102. $cookie->setDomain('example.com/'); // malformed domain
  103. self::assertFalse($cookie->matchesDomain('example.com'));
  104. }
  105. public function pathMatchProvider()
  106. {
  107. return [
  108. ['/foo', '/foo', true],
  109. ['/foo', '/Foo', false],
  110. ['/foo', '/fo', false],
  111. ['/foo', '/foo/bar', true],
  112. ['/foo', '/foo/bar/baz', true],
  113. ['/foo', '/foo/bar//baz', true],
  114. ['/foo', '/foobar', false],
  115. ['/foo/bar', '/foo', false],
  116. ['/foo/bar', '/foobar', false],
  117. ['/foo/bar', '/foo/bar', true],
  118. ['/foo/bar', '/foo/bar/', true],
  119. ['/foo/bar', '/foo/bar/baz', true],
  120. ['/foo/bar/', '/foo/bar', false],
  121. ['/foo/bar/', '/foo/bar/', true],
  122. ['/foo/bar/', '/foo/bar/baz', true],
  123. ];
  124. }
  125. /**
  126. * @dataProvider pathMatchProvider
  127. */
  128. public function testMatchesPath($cookiePath, $requestPath, $isMatch)
  129. {
  130. $cookie = new SetCookie();
  131. $cookie->setPath($cookiePath);
  132. self::assertSame($isMatch, $cookie->matchesPath($requestPath));
  133. }
  134. public function cookieValidateProvider()
  135. {
  136. return [
  137. ['foo', 'baz', 'bar', true],
  138. ['0', '0', '0', true],
  139. ['foo[bar]', 'baz', 'bar', true],
  140. ['foo', '', 'bar', true],
  141. ['', 'baz', 'bar', 'The cookie name must not be empty'],
  142. ['foo', null, 'bar', 'The cookie value must not be empty'],
  143. ['foo', 'baz', '', 'The cookie domain must not be empty'],
  144. ["foo\r", 'baz', '0', 'Cookie name must not contain invalid characters: ASCII Control characters (0-31;127), space, tab and the following characters: ()<>@,;:\"/?={}'],
  145. ];
  146. }
  147. /**
  148. * @dataProvider cookieValidateProvider
  149. */
  150. public function testValidatesCookies($name, $value, $domain, $result)
  151. {
  152. $cookie = new SetCookie([
  153. 'Name' => $name,
  154. 'Value' => $value,
  155. 'Domain' => $domain,
  156. ]);
  157. self::assertSame($result, $cookie->validate());
  158. }
  159. public function testDoesNotMatchIp()
  160. {
  161. $cookie = new SetCookie(['Domain' => '192.168.16.']);
  162. self::assertFalse($cookie->matchesDomain('192.168.16.121'));
  163. }
  164. public function testConvertsToString()
  165. {
  166. $t = 1382916008;
  167. $cookie = new SetCookie([
  168. 'Name' => 'test',
  169. 'Value' => '123',
  170. 'Domain' => 'foo.com',
  171. 'Expires' => $t,
  172. 'Path' => '/abc',
  173. 'HttpOnly' => true,
  174. 'Secure' => true,
  175. ]);
  176. self::assertSame(
  177. 'test=123; Domain=foo.com; Path=/abc; Expires=Sun, 27 Oct 2013 23:20:08 GMT; Secure; HttpOnly',
  178. (string) $cookie
  179. );
  180. }
  181. /**
  182. * Provides the parsed information from a cookie
  183. *
  184. * @return array
  185. */
  186. public function cookieParserDataProvider()
  187. {
  188. return [
  189. [
  190. 'ASIHTTPRequestTestCookie=This+is+the+value; expires=Sat, 26-Jul-2008 17:00:42 GMT; path=/tests; domain=allseeing-i.com; PHPSESSID=6c951590e7a9359bcedde25cda73e43c; path=/;',
  191. [
  192. 'Domain' => 'allseeing-i.com',
  193. 'Path' => '/',
  194. 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c',
  195. 'Max-Age' => null,
  196. 'Expires' => 'Sat, 26-Jul-2008 17:00:42 GMT',
  197. 'Secure' => null,
  198. 'Discard' => null,
  199. 'Name' => 'ASIHTTPRequestTestCookie',
  200. 'Value' => 'This+is+the+value',
  201. 'HttpOnly' => false,
  202. ],
  203. ],
  204. ['', []],
  205. ['foo', []],
  206. ['; foo', []],
  207. [
  208. 'foo="bar"',
  209. [
  210. 'Name' => 'foo',
  211. 'Value' => '"bar"',
  212. 'Discard' => null,
  213. 'Domain' => null,
  214. 'Expires' => null,
  215. 'Max-Age' => null,
  216. 'Path' => '/',
  217. 'Secure' => null,
  218. 'HttpOnly' => false,
  219. ],
  220. ],
  221. // Test setting a blank value for a cookie
  222. [[
  223. 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= ', ],
  224. [
  225. 'Name' => 'foo',
  226. 'Value' => '',
  227. 'Discard' => null,
  228. 'Domain' => null,
  229. 'Expires' => null,
  230. 'Max-Age' => null,
  231. 'Path' => '/',
  232. 'Secure' => null,
  233. 'HttpOnly' => false,
  234. ],
  235. ],
  236. // Test setting a value and removing quotes
  237. [[
  238. 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', ],
  239. [
  240. 'Name' => 'foo',
  241. 'Value' => '1',
  242. 'Discard' => null,
  243. 'Domain' => null,
  244. 'Expires' => null,
  245. 'Max-Age' => null,
  246. 'Path' => '/',
  247. 'Secure' => null,
  248. 'HttpOnly' => false,
  249. ],
  250. ],
  251. // Some of the following tests are based on https://github.com/zendframework/zf1/blob/master/tests/Zend/Http/CookieTest.php
  252. [
  253. 'justacookie=foo; domain=example.com',
  254. [
  255. 'Name' => 'justacookie',
  256. 'Value' => 'foo',
  257. 'Domain' => 'example.com',
  258. 'Discard' => null,
  259. 'Expires' => null,
  260. 'Max-Age' => null,
  261. 'Path' => '/',
  262. 'Secure' => null,
  263. 'HttpOnly' => false,
  264. ],
  265. ],
  266. [
  267. 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com',
  268. [
  269. 'Name' => 'expires',
  270. 'Value' => 'tomorrow',
  271. 'Domain' => '.example.com',
  272. 'Path' => '/Space Out/',
  273. 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
  274. 'Discard' => null,
  275. 'Secure' => true,
  276. 'Max-Age' => null,
  277. 'HttpOnly' => false,
  278. ],
  279. ],
  280. [
  281. 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/',
  282. [
  283. 'Name' => 'domain',
  284. 'Value' => 'unittests',
  285. 'Domain' => 'example.com',
  286. 'Path' => '/some value/',
  287. 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
  288. 'Secure' => false,
  289. 'Discard' => null,
  290. 'Max-Age' => null,
  291. 'HttpOnly' => false,
  292. ],
  293. ],
  294. [
  295. 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT',
  296. [
  297. 'Name' => 'path',
  298. 'Value' => 'indexAction',
  299. 'Domain' => '.foo.com',
  300. 'Path' => '/',
  301. 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
  302. 'Secure' => false,
  303. 'Discard' => null,
  304. 'Max-Age' => null,
  305. 'HttpOnly' => false,
  306. ],
  307. ],
  308. [
  309. 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400',
  310. [
  311. 'Name' => 'secure',
  312. 'Value' => 'sha1',
  313. 'Domain' => 'some.really.deep.domain.com',
  314. 'Path' => '/',
  315. 'Secure' => true,
  316. 'Discard' => null,
  317. 'Expires' => \time() + 86400,
  318. 'Max-Age' => 86400,
  319. 'HttpOnly' => false,
  320. 'version' => '1',
  321. ],
  322. ],
  323. [
  324. 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;',
  325. [
  326. 'Name' => 'PHPSESSID',
  327. 'Value' => '123456789+abcd%2Cef',
  328. 'Domain' => '.localdomain',
  329. 'Path' => '/foo/baz',
  330. 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT',
  331. 'Secure' => true,
  332. 'Discard' => true,
  333. 'Max-Age' => null,
  334. 'HttpOnly' => false,
  335. ],
  336. ],
  337. [
  338. 'fr=synced; Max-Age=604800 Expires=Mon, 12 Dec 2022 13:27:50 GMT; Domain=.example.com; Path=/; SameSite=None; Secure; HttpOnly',
  339. [
  340. 'Name' => 'fr',
  341. 'Value' => 'synced',
  342. 'Domain' => '.example.com',
  343. 'Path' => '/',
  344. 'Expires' => null,
  345. 'Secure' => true,
  346. 'Discard' => false,
  347. 'Max-Age' => null,
  348. 'HttpOnly' => true,
  349. 'SameSite' => 'None',
  350. ],
  351. ],
  352. [
  353. 'SESS3a6f27284c4d8b34b6f4ff98cb87703e=Ts-5YeSyvOCMS%2CzkEb9eDfW4C4ZNFOcRYdu-3JpEAXIm58aH; expires=Wed, 07-Jun-2023 15:56:35 GMT; Max-Age=2000000; path=/; domain=.example.com; HttpOnly; SameSite=Lax',
  354. [
  355. 'Name' => 'SESS3a6f27284c4d8b34b6f4ff98cb87703e',
  356. 'Value' => 'Ts-5YeSyvOCMS%2CzkEb9eDfW4C4ZNFOcRYdu-3JpEAXIm58aH',
  357. 'Domain' => '.example.com',
  358. 'Path' => '/',
  359. 'Expires' => 'Wed, 07-Jun-2023 15:56:35 GMT',
  360. 'Secure' => false,
  361. 'Discard' => false,
  362. 'Max-Age' => 2000000,
  363. 'HttpOnly' => true,
  364. 'SameSite' => 'Lax',
  365. ],
  366. ],
  367. [
  368. 'SESS3a6f27284c4d8b34b6f4ff98cb87703e=Ts-5YeSyvOCMS%2CzkEb9eDfW4C4ZNFOcRYdu-3JpEAXIm58aH; expires=Wed, 07-Jun-2023 15:56:35 GMT; Max-Age=qwerty; path=/; domain=.example.com; HttpOnly; SameSite=Lax',
  369. [
  370. 'Name' => 'SESS3a6f27284c4d8b34b6f4ff98cb87703e',
  371. 'Value' => 'Ts-5YeSyvOCMS%2CzkEb9eDfW4C4ZNFOcRYdu-3JpEAXIm58aH',
  372. 'Domain' => '.example.com',
  373. 'Path' => '/',
  374. 'Expires' => 'Wed, 07-Jun-2023 15:56:35 GMT',
  375. 'Secure' => false,
  376. 'Discard' => false,
  377. 'Max-Age' => null,
  378. 'HttpOnly' => true,
  379. 'SameSite' => 'Lax',
  380. ],
  381. ],
  382. ];
  383. }
  384. /**
  385. * @dataProvider cookieParserDataProvider
  386. */
  387. public function testParseCookie($cookie, $parsed)
  388. {
  389. foreach ((array) $cookie as $v) {
  390. $c = SetCookie::fromString($v);
  391. $p = $c->toArray();
  392. if (isset($p['Expires'])) {
  393. $delta = 40;
  394. $parsedExpires = \is_numeric($parsed['Expires']) ? $parsed['Expires'] : \strtotime($parsed['Expires']);
  395. self::assertLessThan($delta, \abs($p['Expires'] - $parsedExpires), 'Comparing Expires '.\var_export($p['Expires'], true).' : '.\var_export($parsed, true).' | '.\var_export($p, true));
  396. unset($p['Expires']);
  397. unset($parsed['Expires']);
  398. }
  399. if (!empty($parsed)) {
  400. foreach ($parsed as $key => $value) {
  401. self::assertEquals($parsed[$key], $p[$key], 'Comparing '.$key.' '.\var_export($value, true).' : '.\var_export($parsed, true).' | '.\var_export($p, true));
  402. }
  403. foreach ($p as $key => $value) {
  404. self::assertEquals($p[$key], $parsed[$key], 'Comparing '.$key.' '.\var_export($value, true).' : '.\var_export($parsed, true).' | '.\var_export($p, true));
  405. }
  406. } else {
  407. self::assertSame([
  408. 'Name' => null,
  409. 'Value' => null,
  410. 'Domain' => null,
  411. 'Path' => '/',
  412. 'Max-Age' => null,
  413. 'Expires' => null,
  414. 'Secure' => false,
  415. 'Discard' => false,
  416. 'HttpOnly' => false,
  417. ], $p);
  418. }
  419. }
  420. }
  421. /**
  422. * Provides the data for testing isExpired
  423. *
  424. * @return array
  425. */
  426. public function isExpiredProvider()
  427. {
  428. return [
  429. [
  430. 'FOO=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT;',
  431. true,
  432. ],
  433. [
  434. 'FOO=bar; expires=Thu, 01 Jan 1970 00:00:01 GMT;',
  435. true,
  436. ],
  437. [
  438. 'FOO=bar; expires='.\date(\DateTime::RFC1123, \time() + 10).';',
  439. false,
  440. ],
  441. [
  442. 'FOO=bar; expires='.\date(\DateTime::RFC1123, \time() - 10).';',
  443. true,
  444. ],
  445. [
  446. 'FOO=bar;',
  447. false,
  448. ],
  449. ];
  450. }
  451. /**
  452. * @dataProvider isExpiredProvider
  453. */
  454. public function testIsExpired($cookie, $expired)
  455. {
  456. self::assertSame(
  457. $expired,
  458. SetCookie::fromString($cookie)->isExpired()
  459. );
  460. }
  461. }