diff -Nru libmojolicious-perl-9.34+dfsg/Changes libmojolicious-perl-9.35+dfsg/Changes --- libmojolicious-perl-9.34+dfsg/Changes 2023-09-11 18:33:12.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/Changes 2023-10-27 17:10:16.000000000 +0000 @@ -1,4 +1,11 @@ +9.35 2023-10-27 + - Added EXPERIMENTAL support for persistent cookies in Netscape format. + - Added EXPERIMENTAL file attribute to Mojo::UserAgent::CookieJar. + - Added EXPERIMENTAL load, save and to_string methods to Mojo::UserAgent::CookieJar. + - Fixed error reporting when loading applicartions with syntax errors. (haarg) + - Fixed absolute URL support in url_for_file and url_for_asset methods. (rawleyfowler) + 9.34 2023-09-11 - Added support for serving static files with a prefix. - Deprecated Mojo::File::spurt in favor of Mojo::File::spew. diff -Nru libmojolicious-perl-9.34+dfsg/debian/changelog libmojolicious-perl-9.35+dfsg/debian/changelog --- libmojolicious-perl-9.34+dfsg/debian/changelog 2023-09-29 22:15:30.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/debian/changelog 2023-10-28 22:48:52.000000000 +0000 @@ -1,3 +1,9 @@ +libmojolicious-perl (9.35+dfsg-1) unstable; urgency=medium + + * Import upstream version 9.35+dfsg. + + -- gregor herrmann Sun, 29 Oct 2023 00:48:52 +0200 + libmojolicious-perl (9.34+dfsg-1) unstable; urgency=medium * Import upstream version 9.34+dfsg. diff -Nru libmojolicious-perl-9.34+dfsg/lib/Mojo/Headers.pm libmojolicious-perl-9.35+dfsg/lib/Mojo/Headers.pm --- libmojolicious-perl-9.34+dfsg/lib/Mojo/Headers.pm 2023-03-08 18:43:29.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/lib/Mojo/Headers.pm 2023-09-20 16:02:47.000000000 +0000 @@ -236,6 +236,126 @@ L inherits all methods from L and implements the following new ones. +=head2 add + + $headers = $headers->add(Foo => 'one value'); + $headers = $headers->add(Foo => 'first value', 'second value'); + +Add header with one or more lines. + + # "Vary: Accept + # Vary: Accept-Encoding" + $headers->add(Vary => 'Accept')->add(Vary => 'Accept-Encoding')->to_string; + +=head2 append + + $headers = $headers->append(Vary => 'Accept-Encoding'); + +Append value to header and flatten it if necessary. + + # "Vary: Accept" + $headers->append(Vary => 'Accept')->to_string; + + # "Vary: Accept, Accept-Encoding" + $headers->vary('Accept')->append(Vary => 'Accept-Encoding')->to_string; + +=head2 clone + + my $clone = $headers->clone; + +Return a new L object cloned from these headers. + +=head2 dehop + + $headers = $headers->dehop; + +Remove hop-by-hop headers that should not be retransmitted. + +=head2 every_header + + my $all = $headers->every_header('Location'); + +Similar to L, but returns all headers sharing the same name as an array reference. + + # Get first header value + say $headers->every_header('Location')->[0]; + +=head2 from_hash + + $headers = $headers->from_hash({'Cookie' => 'a=b'}); + $headers = $headers->from_hash({'Cookie' => ['a=b', 'c=d']}); + $headers = $headers->from_hash({}); + +Parse headers from a hash reference, an empty hash removes all headers. + +=head2 header + + my $value = $headers->header('Foo'); + $headers = $headers->header(Foo => 'one value'); + $headers = $headers->header(Foo => 'first value', 'second value'); + +Get or replace the current header values. + +=head2 is_finished + + my $bool = $headers->is_finished; + +Check if header parser is finished. + +=head2 is_limit_exceeded + + my $bool = $headers->is_limit_exceeded; + +Check if headers have exceeded L or L. + +=head2 leftovers + + my $bytes = $headers->leftovers; + +Get and remove leftover data from header parser. + +=head2 names + + my $names = $headers->names; + +Return an array reference with all currently defined headers. + + # Names of all headers + say for @{$headers->names}; + +=head2 parse + + $headers = $headers->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a"); + +Parse formatted headers. + +=head2 remove + + $headers = $headers->remove('Foo'); + +Remove a header. + +=head2 to_hash + + my $single = $headers->to_hash; + my $multi = $headers->to_hash(1); + +Turn headers into hash reference, array references to represent multiple headers with the same name are disabled by +default. + + say $headers->to_hash->{DNT}; + +=head2 to_string + + my $str = $headers->to_string; + +Turn headers into a string, suitable for HTTP messages. + + +=head1 ADDITIONAL METHODS + +Additionally, the following shortcuts are available, for accessing and manipulating commonly-used headers: + =head2 accept my $accept = $headers->accept; @@ -279,17 +399,6 @@ Get or replace current header value, shortcut for the C header from L. -=head2 add - - $headers = $headers->add(Foo => 'one value'); - $headers = $headers->add(Foo => 'first value', 'second value'); - -Add header with one or more lines. - - # "Vary: Accept - # Vary: Accept-Encoding" - $headers->add(Vary => 'Accept')->add(Vary => 'Accept-Encoding')->to_string; - =head2 allow my $allow = $headers->allow; @@ -297,18 +406,6 @@ Get or replace current header value, shortcut for the C header. -=head2 append - - $headers = $headers->append(Vary => 'Accept-Encoding'); - -Append value to header and flatten it if necessary. - - # "Vary: Accept" - $headers->append(Vary => 'Accept')->to_string; - - # "Vary: Accept, Accept-Encoding" - $headers->vary('Accept')->append(Vary => 'Accept-Encoding')->to_string; - =head2 authorization my $authorization = $headers->authorization; @@ -323,12 +420,6 @@ Get or replace current header value, shortcut for the C header. -=head2 clone - - my $clone = $headers->clone; - -Return a new L object cloned from these headers. - =head2 connection my $connection = $headers->connection; @@ -408,12 +499,6 @@ Get or replace current header value, shortcut for the C header. -=head2 dehop - - $headers = $headers->dehop; - -Remove hop-by-hop headers that should not be retransmitted. - =head2 dnt my $dnt = $headers->dnt; @@ -429,15 +514,6 @@ Get or replace current header value, shortcut for the C header. -=head2 every_header - - my $all = $headers->every_header('Location'); - -Similar to L, but returns all headers sharing the same name as an array reference. - - # Get first header value - say $headers->every_header('Location')->[0]; - =head2 expect my $expect = $headers->expect; @@ -452,22 +528,6 @@ Get or replace current header value, shortcut for the C header. -=head2 from_hash - - $headers = $headers->from_hash({'Cookie' => 'a=b'}); - $headers = $headers->from_hash({'Cookie' => ['a=b', 'c=d']}); - $headers = $headers->from_hash({}); - -Parse headers from a hash reference, an empty hash removes all headers. - -=head2 header - - my $value = $headers->header('Foo'); - $headers = $headers->header(Foo => 'one value'); - $headers = $headers->header(Foo => 'first value', 'second value'); - -Get or replace the current header values. - =head2 host my $host = $headers->host; @@ -489,18 +549,6 @@ Get or replace current header value, shortcut for the C header. -=head2 is_finished - - my $bool = $headers->is_finished; - -Check if header parser is finished. - -=head2 is_limit_exceeded - - my $bool = $headers->is_limit_exceeded; - -Check if headers have exceeded L or L. - =head2 last_modified my $date = $headers->last_modified; @@ -508,12 +556,6 @@ Get or replace current header value, shortcut for the C header. -=head2 leftovers - - my $bytes = $headers->leftovers; - -Get and remove leftover data from header parser. - =head2 link my $link = $headers->link; @@ -540,15 +582,6 @@ Get or replace current header value, shortcut for the C header. -=head2 names - - my $names = $headers->names; - -Return an array reference with all currently defined headers. - - # Names of all headers - say for @{$headers->names}; - =head2 origin my $origin = $headers->origin; @@ -557,12 +590,6 @@ Get or replace current header value, shortcut for the C header from L. -=head2 parse - - $headers = $headers->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a"); - -Parse formatted headers. - =head2 proxy_authenticate my $authenticate = $headers->proxy_authenticate; @@ -599,12 +626,6 @@ Get or replace current header value, shortcut for the C header, there was a typo in L which resulted in C becoming an official header. -=head2 remove - - $headers = $headers->remove('Foo'); - -Remove a header. - =head2 sec_websocket_accept my $accept = $headers->sec_websocket_accept; @@ -691,22 +712,6 @@ Get or replace current header value, shortcut for the C header. -=head2 to_hash - - my $single = $headers->to_hash; - my $multi = $headers->to_hash(1); - -Turn headers into hash reference, array references to represent multiple headers with the same name are disabled by -default. - - say $headers->to_hash->{DNT}; - -=head2 to_string - - my $str = $headers->to_string; - -Turn headers into a string, suitable for HTTP messages. - =head2 trailer my $trailer = $headers->trailer; diff -Nru libmojolicious-perl-9.34+dfsg/lib/Mojo/Server.pm libmojolicious-perl-9.35+dfsg/lib/Mojo/Server.pm --- libmojolicious-perl-9.34+dfsg/lib/Mojo/Server.pm 2023-08-25 16:41:26.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/lib/Mojo/Server.pm 2023-09-20 16:02:47.000000000 +0000 @@ -54,9 +54,11 @@ local @ARGS_OVERRIDE = @args; # Try to load application from script into sandbox - my $app = eval "package Mojo::Server::Sandbox::@{[md5_sum $path]}; do \$path"; - my $err = $app ? undef : $@ || $! || "$path did not return a true value"; - die qq{Can't load application from file "$path": $err} if $err; + my $app = eval sprintf <<'END_CODE', md5_sum($path); + package Mojo::Server::Sandbox::%s; + do $path or die $@ || $! || "$path did not return a true value"; +END_CODE + die qq{Can't load application from file "$path": $@} if $@; die qq{File "$path" did not return an application object.\n} unless blessed $app && $app->can('handler'); $self->app($app); }; diff -Nru libmojolicious-perl-9.34+dfsg/lib/Mojo/UserAgent/CookieJar.pm libmojolicious-perl-9.35+dfsg/lib/Mojo/UserAgent/CookieJar.pm --- libmojolicious-perl-9.34+dfsg/lib/Mojo/UserAgent/CookieJar.pm 2023-03-08 18:43:30.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/lib/Mojo/UserAgent/CookieJar.pm 2023-10-27 17:08:16.000000000 +0000 @@ -2,12 +2,15 @@ use Mojo::Base -base; use Mojo::Cookie::Request; +use Mojo::File qw(path); use Mojo::Path; use Scalar::Util qw(looks_like_number); -has 'ignore'; +has [qw(file ignore)]; has max_cookie_size => 4096; +my $COMMENT = "# Netscape HTTP Cookie File\n# This file was generated by Mojolicious! Edit at your own risk.\n\n"; + sub add { my ($self, @cookies) = @_; @@ -58,7 +61,11 @@ } } -sub empty { delete shift->{jar} } +sub empty { + my $self = shift; + delete $self->{jar}; + return $self; +} sub find { my ($self, $url) = @_; @@ -93,6 +100,36 @@ return \@found; } +sub load { + my $self = shift; + + my $file = $self->file; + return $self unless $file && -r $file; + + for my $line (split "\n", path($file)->slurp) { + + # Prefix used by curl for HttpOnly cookies + my $httponly = $line =~ s/^#HttpOnly_// ? 1 : 0; + next if $line =~ /^#/; + + my @values = split "\t", $line; + next if @values != 7; + + $self->add(Mojo::Cookie::Response->new({ + domain => $values[0] =~ s/^\.//r, + host_only => $values[1] eq 'FALSE' ? 1 : 0, + path => $values[2], + secure => $values[3] eq 'FALSE' ? 0 : 1, + expires => $values[4] eq '0' ? undef : $values[4], + name => $values[5], + value => $values[6], + httponly => $httponly + })); + } + + return $self; +} + sub prepare { my ($self, $tx) = @_; return unless keys %{$self->{jar}}; @@ -100,6 +137,34 @@ $req->cookies(@{$self->find($req->url)}); } +sub save { + my $self = shift; + return $self unless my $file = $self->file; + + my $final = path($file); + my $tmp = path("$file.$$"); + $tmp->spew($COMMENT . $self->to_string)->move_to($final); + + return $self; +} + +sub to_string { + my $self = shift; + + my @lines; + for my $cookie (@{$self->all}) { + my $line = [ + $cookie->domain, $cookie->host_only ? 'FALSE' : 'TRUE', + $cookie->path, $cookie->secure ? 'TRUE' : 'FALSE', + $cookie->expires // 0, $cookie->name, + $cookie->value + ]; + push @lines, join "\t", @$line; + } + + return join "\n", @lines, ''; +} + sub _compare { my ($cookie, $path, $name, $domain) = @_; return $cookie->path ne $path || $cookie->name ne $name || $cookie->domain ne $domain; @@ -145,6 +210,20 @@ L implements the following attributes. +=head2 file + + my $file = $jar->file; + $jar = $jar->file('/home/sri/cookies.txt'); + +File to L cookies from and L cookies to in Netscape format. Note that this attribute is +B and might change without warning! + + # Save cookies to file + $jar->file('cookies.txt')->save; + + # Empty cookie jar and load cookies from file + $jar->file('cookies.txt')->empty->load; + =head2 ignore my $ignore = $jar->ignore; @@ -195,7 +274,7 @@ =head2 empty - $jar->empty; + $jar = $jar->empty; Empty the jar. @@ -208,12 +287,30 @@ # Names of all cookies found say $_->name for @{$jar->find(Mojo::URL->new('http://example.com/foo'))}; +=head2 load + + $jar = $jar->load; + +Load cookies from L. Note that this method is B and might change without warning! + =head2 prepare $jar->prepare(Mojo::Transaction::HTTP->new); Prepare request cookies for transaction. +=head2 save + + $jar = $jar->save; + +Save cookies to L. Note that this method is B and might change without warning! + +=head2 to_string + + my $string = $jar->to_string; + +Stringify cookies in Netscape format. Note that this method is B and might change without warning! + =head1 SEE ALSO L, L, L. diff -Nru libmojolicious-perl-9.34+dfsg/lib/Mojolicious/Controller.pm libmojolicious-perl-9.35+dfsg/lib/Mojolicious/Controller.pm --- libmojolicious-perl-9.34+dfsg/lib/Mojolicious/Controller.pm 2023-08-15 14:31:19.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/lib/Mojolicious/Controller.pm 2023-10-06 00:17:40.000000000 +0000 @@ -11,6 +11,8 @@ use Mojolicious::Routes::Match; use Scalar::Util (); +my $ABSOLUTE = qr!^(?:[^:/?#]+:|//|#)!; + has [qw(app tx)] => undef, weak => 1; has match => sub { Mojolicious::Routes::Match->new(root => shift->app->routes) }; @@ -239,7 +241,7 @@ # Absolute URL return $target if Scalar::Util::blessed $target && $target->isa('Mojo::URL'); - return Mojo::URL->new($target) if $target =~ m!^(?:[^:/?#]+:|//|#)!; + return Mojo::URL->new($target) if $target =~ $ABSOLUTE; # Base my $url = Mojo::URL->new; @@ -274,12 +276,12 @@ sub url_for_asset { my ($self, $asset) = @_; - return $self->url_for($self->app->static->asset_path($asset)); + return $self->url_for($asset =~ $ABSOLUTE ? $asset : $self->app->static->asset_path($asset)); } sub url_for_file { my ($self, $file) = @_; - return $self->url_for($self->app->static->file_path($file)); + return $self->url_for($file =~ $ABSOLUTE ? $file : $self->app->static->file_path($file)); } sub write { diff -Nru libmojolicious-perl-9.34+dfsg/lib/Mojolicious.pm libmojolicious-perl-9.35+dfsg/lib/Mojolicious.pm --- libmojolicious-perl-9.34+dfsg/lib/Mojolicious.pm 2023-08-15 14:34:29.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/lib/Mojolicious.pm 2023-09-11 18:34:57.000000000 +0000 @@ -57,7 +57,7 @@ has validator => sub { Mojolicious::Validator->new }; our $CODENAME = 'Waffle'; -our $VERSION = '9.34'; +our $VERSION = '9.35'; sub BUILD_DYNAMIC { my ($class, $method, $dyn_methods) = @_; diff -Nru libmojolicious-perl-9.34+dfsg/MANIFEST libmojolicious-perl-9.35+dfsg/MANIFEST --- libmojolicious-perl-9.34+dfsg/MANIFEST 2023-09-11 18:34:08.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/MANIFEST 2023-10-27 17:11:04.000000000 +0000 @@ -186,6 +186,7 @@ t/mojo/content.t t/mojo/cookie.t t/mojo/cookiejar.t +t/mojo/cookies/curl.txt t/mojo/daemon.t t/mojo/daemon_ipv6_tls.t t/mojo/date.t diff -Nru libmojolicious-perl-9.34+dfsg/META.json libmojolicious-perl-9.35+dfsg/META.json --- libmojolicious-perl-9.34+dfsg/META.json 2023-09-11 18:34:08.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/META.json 2023-10-27 17:11:04.000000000 +0000 @@ -64,6 +64,6 @@ "web" : "https://web.libera.chat/#mojo" } }, - "version" : "9.34", + "version" : "9.35", "x_serialization_backend" : "JSON::PP version 4.07" } diff -Nru libmojolicious-perl-9.34+dfsg/META.yml libmojolicious-perl-9.35+dfsg/META.yml --- libmojolicious-perl-9.34+dfsg/META.yml 2023-09-11 18:34:08.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/META.yml 2023-10-27 17:11:04.000000000 +0000 @@ -35,5 +35,5 @@ homepage: https://mojolicious.org license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/mojolicious/mojo.git -version: '9.34' +version: '9.35' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff -Nru libmojolicious-perl-9.34+dfsg/t/mojo/cookiejar.t libmojolicious-perl-9.35+dfsg/t/mojo/cookiejar.t --- libmojolicious-perl-9.34+dfsg/t/mojo/cookiejar.t 2023-03-08 18:43:32.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/t/mojo/cookiejar.t 2023-10-27 16:43:38.000000000 +0000 @@ -2,6 +2,7 @@ use Test::More; use Mojo::Cookie::Response; +use Mojo::File qw(curfile tempdir); use Mojo::Transaction::HTTP; use Mojo::URL; use Mojo::UserAgent::CookieJar; @@ -379,4 +380,165 @@ is_deeply $jar->all, [], 'no cookies'; }; +subtest 'Load cookies from Netscape cookies.txt file' => sub { + my $cookies = curfile->dirname->child('cookies'); + + subtest 'Not configured' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + is_deeply $jar->load->all, [], 'no cookies'; + }; + + subtest 'Missing file' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + is_deeply $jar->file($cookies->child('missing.txt')->to_string)->load->all, [], 'no cookies'; + }; + + subtest 'Load file created by curl' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + my $cookies = $jar->file($cookies->child('curl.txt')->to_string)->load->all; + + is $cookies->[0]->name, 'AEC', 'right name'; + is $cookies->[0]->value, 'Ack', 'right value'; + is $cookies->[0]->domain, 'google.com', 'right domain'; + is $cookies->[0]->path, '/', 'right path'; + is $cookies->[0]->expires, 4713964099, 'expires'; + is $cookies->[0]->secure, 1, 'is secure'; + is $cookies->[0]->httponly, 1, 'is HttpOnly'; + is $cookies->[0]->host_only, 0, 'allows subdomains'; + + is $cookies->[1]->name, '__Secure-ENID', 'right name'; + is $cookies->[1]->value, '15.SE', 'right value'; + is $cookies->[1]->domain, 'google.com', 'right domain'; + is $cookies->[1]->path, '/', 'right path'; + is $cookies->[1]->expires, 4732598797, 'expires'; + is $cookies->[1]->secure, 1, 'is secure'; + is $cookies->[1]->httponly, 1, 'is HttpOnly'; + is $cookies->[1]->host_only, 0, 'allows subdomains'; + + is $cookies->[2]->name, 'csv', 'right name'; + is $cookies->[2]->value, '2', 'right value'; + is $cookies->[2]->domain, 'reddit.com', 'right domain'; + is $cookies->[2]->path, '/', 'right path'; + is $cookies->[2]->expires, 4761486052, 'expires'; + is $cookies->[2]->secure, 1, 'is secure'; + is $cookies->[2]->httponly, 0, 'not HttpOnly'; + is $cookies->[2]->host_only, 0, 'allows subdomains'; + + is $cookies->[3]->name, 'csrf_token', 'right name'; + is $cookies->[3]->value, '3329d93c563f6a017045f516c5c515fc', 'right value'; + is $cookies->[3]->domain, 'reddit.com', 'right domain'; + is $cookies->[3]->path, '/', 'right path'; + ok !$cookies->[3]->expires, 'does not expire'; + is $cookies->[3]->secure, 1, 'is secure'; + is $cookies->[3]->httponly, 0, 'not HttpOnly'; + is $cookies->[3]->host_only, 0, 'allows subdomains'; + + is $cookies->[4]->name, 'CONSENT', 'right name'; + is $cookies->[4]->value, 'PENDING+648', 'right value'; + is $cookies->[4]->domain, 'whatever.youtube.com', 'right domain'; + is $cookies->[4]->path, '/about', 'right path'; + is $cookies->[4]->expires, 4761484436, 'expires'; + is $cookies->[4]->secure, 1, 'is secure'; + is $cookies->[4]->httponly, 0, 'not HttpOnly'; + is $cookies->[4]->host_only, 0, 'allows subdomains'; + + is $cookies->[5]->name, 'susecom-cookie', 'right name'; + is $cookies->[5]->value, '50fbf56aa575290e', 'right value'; + is $cookies->[5]->domain, 'www.suse.com', 'right domain'; + is $cookies->[5]->path, '/', 'right path'; + ok !$cookies->[5]->expires, 'does not expire'; + is $cookies->[5]->secure, 0, 'not secure'; + is $cookies->[5]->httponly, 0, 'not HttpOnly'; + is $cookies->[5]->host_only, 1, 'does not allow subdomains'; + }; +}; + +subtest 'Save cookies to Netscape cookies.txt file' => sub { + my $tmp = tempdir; + + subtest 'Not configured' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + is_deeply $jar->save->all, [], 'no cookies'; + }; + + subtest 'Empty jar' => sub { + my $file = $tmp->child('empty.txt'); + my $jar = Mojo::UserAgent::CookieJar->new(file => $file->to_string); + + ok !-e $file, 'file does not exist'; + is_deeply $jar->save->all, [], 'no cookies'; + ok -e $file, 'file exists'; + is_deeply $jar->load->all, [], 'no cookies'; + + my $content = $file->slurp; + like $content, qr/# Netscape HTTP Cookie File/, 'Netscape comment is present'; + like $content, qr/# This file was generated by Mojolicious! Edit at your own risk./, 'warning comment is present'; + }; + + subtest 'Store standard cookies' => sub { + my $file = $tmp->child('session.txt'); + my $jar = Mojo::UserAgent::CookieJar->new(file => $file->to_string); + + $jar->add(Mojo::Cookie::Response->new(domain => 'example.com', path => '/foo', name => 'a', value => 'b')); + + ok !-e $file, 'file does not exist'; + $jar->save; + ok -e $file, 'file exists'; + my $content = $file->slurp; + + like $content, qr/# Netscape HTTP Cookie File/, 'Netscape comment is present'; + like $content, qr/# This file was generated by Mojolicious! Edit at your own risk./, 'warning comment is present'; + like $content, qr/example\.com\tTRUE\t\/foo\tFALSE\t0\ta\tb/, 'cookie is present'; + + my $jar2 = Mojo::UserAgent::CookieJar->new(file => $file->to_string)->load; + my $cookies = $jar2->all; + is $cookies->[0]->name, 'a', 'right name'; + is $cookies->[0]->value, 'b', 'right value'; + is $cookies->[0]->domain, 'example.com', 'right domain'; + is $cookies->[0]->path, '/foo', 'right path'; + ok !$cookies->[0]->expires, 'does not expire'; + ok !$cookies->[1], 'no more cookies'; + + $jar2->empty->add(Mojo::Cookie::Response->new(domain => 'mojolicious.org', path => '/', name => 'c', value => 'd')) + ->save; + + my $jar3 = Mojo::UserAgent::CookieJar->new(file => $file->to_string)->load; + $cookies = $jar3->all; + is $cookies->[0]->name, 'c', 'right name'; + is $cookies->[0]->value, 'd', 'right value'; + is $cookies->[0]->domain, 'mojolicious.org', 'right domain'; + is $cookies->[0]->path, '/', 'right path'; + ok !$cookies->[0]->expires, 'does not expire'; + ok !$cookies->[1], 'no more cookies'; + }; +}; + +subtest 'Stringify cookies in Netscape format' => sub { + subtest 'Session cookies' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + $jar->add( + Mojo::Cookie::Response->new(domain => 'mojolicious.org', path => '/', name => 'c', value => 'd'), + Mojo::Cookie::Response->new(domain => 'example.com', path => '/foo', name => 'foo', value => 'bar') + ); + my $content = $jar->to_string; + like $content, qr/mojolicious\.org\tTRUE\t\/\tFALSE\t0\tc\td/, 'first cookie'; + like $content, qr/example\.com\tTRUE\t\/foo\tFALSE\t0\tfoo\tbar/, 'second cookie'; + }; + + subtest 'Secure cookies' => sub { + my $jar = Mojo::UserAgent::CookieJar->new; + $jar->add(Mojo::Cookie::Response->new( + domain => 'www.mojolicious.org', + path => '/', + secure => 1, + host_only => 1, + expires => 4732598797, + name => 'one', + value => 'One' + )); + my $content = $jar->to_string; + like $content, qr/www.mojolicious.org\tFALSE\t\/\tTRUE\t4732598797\tone\tOne/, 'first cookie'; + }; +}; + done_testing(); diff -Nru libmojolicious-perl-9.34+dfsg/t/mojo/cookies/curl.txt libmojolicious-perl-9.35+dfsg/t/mojo/cookies/curl.txt --- libmojolicious-perl-9.34+dfsg/t/mojo/cookies/curl.txt 1970-01-01 00:00:00.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/t/mojo/cookies/curl.txt 2023-10-27 15:17:18.000000000 +0000 @@ -0,0 +1,10 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +www.suse.com FALSE / FALSE 0 susecom-cookie 50fbf56aa575290e +.reddit.com TRUE / TRUE 4761486052 csv 2 +.reddit.com TRUE / TRUE 0 csrf_token 3329d93c563f6a017045f516c5c515fc +#HttpOnly_.google.com TRUE / TRUE 4713964099 AEC Ack +#HttpOnly_.google.com TRUE / TRUE 4732598797 __Secure-ENID 15.SE +.whatever.youtube.com TRUE /about TRUE 4761484436 CONSENT PENDING+648 diff -Nru libmojolicious-perl-9.34+dfsg/t/mojo/daemon.t libmojolicious-perl-9.35+dfsg/t/mojo/daemon.t --- libmojolicious-perl-9.34+dfsg/t/mojo/daemon.t 2023-08-25 16:41:33.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/t/mojo/daemon.t 2023-09-20 16:02:47.000000000 +0000 @@ -88,7 +88,8 @@ eval { Mojo::Server::Daemon->new->load_app("$bin/lib/Mojo/LoaderTest/A.pm") }; like $@, qr/did not return an application object/, 'right error'; eval { Mojo::Server::Daemon->new->load_app("$bin/lib/Mojo/LoaderException.pm") }; - like $@, qr/^Can't load application/, 'right error'; + like $@, qr/Missing right curly or square bracket/, 'right error'; + like $@, qr/^Can't load application/, 'right error'; }; subtest 'Load app using module_true' => sub { diff -Nru libmojolicious-perl-9.34+dfsg/t/mojolicious/static_prefix_lite_app.t libmojolicious-perl-9.35+dfsg/t/mojolicious/static_prefix_lite_app.t --- libmojolicious-perl-9.34+dfsg/t/mojolicious/static_prefix_lite_app.t 2023-08-15 16:00:13.000000000 +0000 +++ libmojolicious-perl-9.35+dfsg/t/mojolicious/static_prefix_lite_app.t 2023-10-06 00:17:40.000000000 +0000 @@ -77,6 +77,10 @@ my $c = $t->app->build_controller; is $c->url_for_file('/unknown.css')->path, '/static/unknown.css', 'right file path'; is $c->url_for_file('/foo/bar.css')->path, '/static/foo/bar.css', 'right file path'; + is $c->url_for_file('https://somesite.com/file.css')->to_string, 'https://somesite.com/file.css', + 'right absolute file path?'; + is $c->url_for_asset('https://somesite.com/file.css')->to_string, 'https://somesite.com/file.css', + 'right absolute asset path?'; }; done_testing();