@@ -443,4 +443,239 @@ public function testNonRawCookieSafelyEncodesCRLF(): void
443443 $ this ->assertStringNotContainsString ("\r" , $ result );
444444 $ this ->assertStringNotContainsString ("\n" , $ result );
445445 }
446+
447+ #[DataProvider('provideValidationOfCookiePath ' )]
448+ public function testValidationOfCookiePath (string $ path ): void
449+ {
450+ $ this ->expectException (CookieException::class);
451+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookiePath ' ));
452+ new Cookie ('test ' , 'value ' , ['path ' => $ path ]);
453+ }
454+
455+ #[DataProvider('provideValidationOfCookiePath ' )]
456+ public function testValidationOfCookiePathInWithPath (string $ path ): void
457+ {
458+ $ this ->expectException (CookieException::class);
459+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookiePath ' ));
460+ $ cookie = new Cookie ('test ' , 'value ' );
461+ $ cookie ->withPath ($ path );
462+ }
463+
464+ /**
465+ * @return iterable<string, array{string}>
466+ */
467+ public static function provideValidationOfCookiePath (): iterable
468+ {
469+ yield 'comma ' => ['/path,comma ' ];
470+
471+ yield 'semicolon ' => ['/path;semicolon ' ];
472+
473+ yield 'space ' => ['/path with space ' ];
474+
475+ yield 'tab ' => ["/path \twith_tab " ];
476+
477+ yield 'carriage return ' => ["/path \rcarriage " ];
478+
479+ yield 'newline ' => ["/path \nnewline " ];
480+
481+ yield 'vertical tab ' => ["/path \vvertical_tab " ];
482+
483+ yield 'form feed ' => ["/path \fform_feed " ];
484+
485+ yield 'null byte ' => ["/path \0null_byte " ];
486+
487+ yield 'CRLF ' => ["/path \r\nwith_crlf " ];
488+ }
489+
490+ #[DataProvider('provideFromHeaderStringValidationOfCookiePath ' )]
491+ public function testFromHeaderStringValidationOfCookiePath (string $ path ): void
492+ {
493+ $ this ->expectException (CookieException::class);
494+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookiePath ' ));
495+ Cookie::fromHeaderString ("test=value; Path= {$ path }" );
496+ }
497+
498+ /**
499+ * @return iterable<string, array{string}>
500+ */
501+ public static function provideFromHeaderStringValidationOfCookiePath (): iterable
502+ {
503+ foreach (self ::provideValidationOfCookiePath () as $ name => $ case ) {
504+ if ($ name === 'semicolon ' ) {
505+ continue ;
506+ }
507+
508+ yield $ name => $ case ;
509+ }
510+ }
511+
512+ #[DataProvider('provideValidationOfCookieDomain ' )]
513+ public function testValidationOfCookieDomain (string $ domain ): void
514+ {
515+ $ this ->expectException (CookieException::class);
516+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieDomain ' ));
517+ new Cookie ('test ' , 'value ' , ['domain ' => $ domain ]);
518+ }
519+
520+ #[DataProvider('provideValidationOfCookieDomain ' )]
521+ public function testValidationOfCookieDomainInWithDomain (string $ domain ): void
522+ {
523+ $ this ->expectException (CookieException::class);
524+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieDomain ' ));
525+ $ cookie = new Cookie ('test ' , 'value ' );
526+ $ cookie ->withDomain ($ domain );
527+ }
528+
529+ /**
530+ * @return iterable<string, array{string}>
531+ */
532+ public static function provideValidationOfCookieDomain (): iterable
533+ {
534+ yield 'comma ' => ['domain,comma.com ' ];
535+
536+ yield 'semicolon ' => ['domain;semicolon.com ' ];
537+
538+ yield 'space ' => ['domain with space.com ' ];
539+
540+ yield 'tab ' => ["domain \twith_tab.com " ];
541+
542+ yield 'carriage return ' => ["domain \rcarriage.com " ];
543+
544+ yield 'newline ' => ["domain \nnewline.com " ];
545+
546+ yield 'vertical tab ' => ["domain \vvertical_tab.com " ];
547+
548+ yield 'form feed ' => ["domain \fform_feed.com " ];
549+
550+ yield 'null byte ' => ["domain \0null_byte.com " ];
551+
552+ yield 'CRLF ' => ["domain \r\nwith_crlf.com " ];
553+ }
554+
555+ #[DataProvider('provideFromHeaderStringValidationOfCookieDomain ' )]
556+ public function testFromHeaderStringValidationOfCookieDomain (string $ domain ): void
557+ {
558+ $ this ->expectException (CookieException::class);
559+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieDomain ' ));
560+ Cookie::fromHeaderString ("test=value; Domain= {$ domain }" );
561+ }
562+
563+ /**
564+ * @return iterable<string, array{string}>
565+ */
566+ public static function provideFromHeaderStringValidationOfCookieDomain (): iterable
567+ {
568+ foreach (self ::provideValidationOfCookieDomain () as $ name => $ case ) {
569+ if ($ name === 'semicolon ' ) {
570+ continue ;
571+ }
572+
573+ yield $ name => $ case ;
574+ }
575+ }
576+
577+ public function testNullPathAndDomainDefaultProperly (): void
578+ {
579+ $ cookie = new Cookie ('test ' , 'val ' , ['path ' => null , 'domain ' => null , 'prefix ' => null ]);
580+
581+ $ this ->assertSame ('/ ' , $ cookie ->getPath ());
582+ $ this ->assertSame ('' , $ cookie ->getDomain ());
583+ $ this ->assertSame ('' , $ cookie ->getPrefix ());
584+
585+ $ cookie2 = $ cookie ->withPath (null )->withDomain (null )->withPrefix ('' );
586+ $ this ->assertSame ('/ ' , $ cookie2 ->getPath ());
587+ $ this ->assertSame ('' , $ cookie2 ->getDomain ());
588+ $ this ->assertSame ('' , $ cookie2 ->getPrefix ());
589+ }
590+
591+ public function testValidCookiePathAndDomain (): void
592+ {
593+ $ cookie = new Cookie ('test ' , 'val ' , ['path ' => '/sub/dir/ ' , 'domain ' => 'example.com ' ]);
594+ $ this ->assertSame ('/sub/dir/ ' , $ cookie ->getPath ());
595+ $ this ->assertSame ('example.com ' , $ cookie ->getDomain ());
596+
597+ $ cookie2 = $ cookie ->withPath ('/another/path ' )->withDomain ('.example.com ' );
598+ $ this ->assertSame ('/another/path ' , $ cookie2 ->getPath ());
599+ $ this ->assertSame ('.example.com ' , $ cookie2 ->getDomain ());
600+
601+ $ cookie3 = new Cookie ('test ' , 'val ' , ['path ' => '/ ' , 'domain ' => '' ]);
602+ $ this ->assertSame ('/ ' , $ cookie3 ->getPath ());
603+ $ this ->assertSame ('' , $ cookie3 ->getDomain ());
604+ }
605+
606+ #[DataProvider('provideValidationOfCookiePrefix ' )]
607+ public function testValidationOfCookiePrefix (string $ prefix ): void
608+ {
609+ $ this ->expectException (CookieException::class);
610+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieName ' , [$ prefix ]));
611+ new Cookie ('test ' , 'val ' , ['prefix ' => $ prefix ]);
612+ }
613+
614+ #[DataProvider('provideValidationOfCookiePrefix ' )]
615+ public function testValidationOfCookiePrefixInWithPrefix (string $ prefix ): void
616+ {
617+ $ this ->expectException (CookieException::class);
618+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieName ' , [$ prefix ]));
619+ $ cookie = new Cookie ('test ' , 'val ' );
620+ $ cookie ->withPrefix ($ prefix );
621+ }
622+
623+ #[DataProvider('provideValidationOfCookiePrefix ' )]
624+ public function testValidationOfRawCookiePrefix (string $ prefix ): void
625+ {
626+ $ this ->expectException (CookieException::class);
627+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieName ' , [$ prefix ]));
628+ new Cookie ('test ' , 'val ' , ['prefix ' => $ prefix , 'raw ' => true ]);
629+ }
630+
631+ #[DataProvider('provideValidationOfCookiePrefix ' )]
632+ public function testValidationOfRawCookiePrefixInWithPrefix (string $ prefix ): void
633+ {
634+ $ this ->expectException (CookieException::class);
635+ $ this ->expectExceptionMessage (lang ('Cookie.invalidCookieName ' , [$ prefix ]));
636+ $ cookie = new Cookie ('test ' , 'val ' , ['raw ' => true ]);
637+ $ cookie ->withPrefix ($ prefix );
638+ }
639+
640+ /**
641+ * @return iterable<string, array{string}>
642+ */
643+ public static function provideValidationOfCookiePrefix (): iterable
644+ {
645+ yield 'equals ' => ['prefix= ' ];
646+
647+ yield 'comma ' => ['prefix, ' ];
648+
649+ yield 'semicolon ' => ['prefix; ' ];
650+
651+ yield 'space ' => ['prefix ' ];
652+
653+ yield 'tab ' => ["prefix \t" ];
654+
655+ yield 'carriage return ' => ["prefix \r" ];
656+
657+ yield 'newline ' => ["prefix \n" ];
658+
659+ yield 'vertical tab ' => ["prefix \v" ];
660+
661+ yield 'form feed ' => ["prefix \f" ];
662+
663+ yield 'null byte ' => ["prefix \0" ];
664+
665+ yield 'CRLF ' => ["prefix \r\n" ];
666+ }
667+
668+ public function testValidCookiePrefixAllowedSeparators (): void
669+ {
670+ $ cookie = new Cookie ('test ' , 'val ' , ['prefix ' => 'ci:session/ ' ]);
671+ $ this ->assertSame ('ci:session/ ' , $ cookie ->getPrefix ());
672+ $ this ->assertSame ('ci:session/test ' , $ cookie ->getPrefixedName ());
673+
674+ $ cookie2 = $ cookie ->withPrefix ('my-app:v1/ ' );
675+ $ this ->assertSame ('my-app:v1/ ' , $ cookie2 ->getPrefix ());
676+ $ this ->assertSame ('my-app:v1/test ' , $ cookie2 ->getPrefixedName ());
677+
678+ $ cookie3 = new Cookie ('test ' , 'val ' , ['prefix ' => 'ci:session/ ' , 'raw ' => false ]);
679+ $ this ->assertSame ('ci:session/test=val; Path=/; HttpOnly; SameSite=Lax ' , $ cookie3 ->toHeaderString ());
680+ }
446681}
0 commit comments