diff --git a/lib/Mojo/Cookie/Response.pm b/lib/Mojo/Cookie/Response.pm index ea1419c8f6..6465f27b49 100644 --- a/lib/Mojo/Cookie/Response.pm +++ b/lib/Mojo/Cookie/Response.pm @@ -4,9 +4,9 @@ use Mojo::Base 'Mojo::Cookie'; use Mojo::Date; use Mojo::Util qw(quote split_cookie_header); -has [qw(domain expires host_only httponly max_age path samesite secure)]; +has [qw(domain expires host_only httponly max_age path samesite secure partitioned)]; -my %ATTRS = map { $_ => 1 } qw(domain expires httponly max-age path samesite secure); +my %ATTRS = map { $_ => 1 } qw(domain expires httponly max-age path samesite secure partitioned); sub parse { my ($self, $str) = @_; @@ -21,7 +21,7 @@ sub parse { next unless $ATTRS{my $attr = lc $name}; $value =~ s/^\.// if $attr eq 'domain' && defined $value; $value = Mojo::Date->new($value // '')->epoch if $attr eq 'expires'; - $value = 1 if $attr eq 'secure' || $attr eq 'httponly'; + $value = 1 if $attr eq 'secure' || $attr eq 'httponly' || $attr eq 'partitioned'; $cookies[-1]{$attr eq 'max-age' ? 'max_age' : $attr} = $value; } } @@ -53,6 +53,9 @@ sub to_string { # "HttpOnly" $cookie .= "; HttpOnly" if $self->httponly; + # "Partitioned" + $cookie .= "; Partitioned" if $self->partitioned; + # "Same-Site" if (my $samesite = $self->samesite) { $cookie .= "; SameSite=$samesite" } diff --git a/lib/Mojolicious/Sessions.pm b/lib/Mojolicious/Sessions.pm index 989ad5faaa..77c6af3e67 100644 --- a/lib/Mojolicious/Sessions.pm +++ b/lib/Mojolicious/Sessions.pm @@ -11,6 +11,7 @@ has default_expiration => 3600; has deserialize => sub { \&_deserialize }; has samesite => 'Lax'; has serialize => sub { \&_serialize }; +has partitioned => 0; sub load { my ($self, $c) = @_; @@ -51,12 +52,13 @@ sub store { my $value = b64_encode $self->serialize->($session), ''; $value =~ y/=/-/; my $options = { - domain => $self->cookie_domain, - expires => $session->{expires}, - httponly => 1, - path => $self->cookie_path, - samesite => $self->samesite, - secure => $self->secure + domain => $self->cookie_domain, + expires => $session->{expires}, + httponly => 1, + path => $self->cookie_path, + samesite => $self->samesite, + secure => $self->secure, + partitioned => $self->partitioned }; $c->signed_cookie($self->cookie_name, $value, $options); } diff --git a/t/mojo/cookie.t b/t/mojo/cookie.t index 2a61029d7b..206c1685ed 100644 --- a/t/mojo/cookie.t +++ b/t/mojo/cookie.t @@ -173,8 +173,9 @@ subtest 'Full response cookie as string' => sub { $cookie->secure(1); $cookie->httponly(1); $cookie->samesite('Lax'); + $cookie->partitioned(1); is $cookie->to_string, '0="ba r"; expires=Thu, 07 Aug 2008 07:07:59 GMT; domain=example.com;' - . ' path=/test; secure; HttpOnly; SameSite=Lax; Max-Age=60', 'right format'; + . ' path=/test; secure; HttpOnly; Partitioned; SameSite=Lax; Max-Age=60', 'right format'; }; subtest 'Empty response cookie' => sub { @@ -216,6 +217,18 @@ subtest 'Parse response cookie (RFC 6265)' => sub { is $cookies->[1], undef, 'no more cookies'; }; +subtest 'Partitioned cookie (RFC 6265 CHIPS)' => sub { + my $cookies + = Mojo::Cookie::Response->parse( + 'foo="bar"; Domain=example.com; Path=/test; Max-Age=60; Partitioned; Expires=Thu, 07 Aug 2008 07:07:59 GMT; Secure;' + ); + is $cookies->[0]->partitioned, 1, 'partitioned set?'; + + $cookies = Mojo::Cookie::Response->parse( + 'foo="bar"; Domain=example.com; Path=/test; Max-Age=60; Expires=Thu, 07 Aug 2008 07:07:59 GMT; Secure;'); + is $cookies->[0]->partitioned, undef, 'partitioned not set?'; +}; + subtest 'Parse response cookie with invalid flag (RFC 6265)' => sub { my $cookies = Mojo::Cookie::Response->parse( 'foo="ba r"; Domain=.example.com; Path=/test; Max-Age=60;' . ' Expires=Thu, 07 Aug 2008 07:07:59 GMT; InSecure;');