FU: Add fu->set_cookie() (+ uri_unescape '+' fix)
This commit is contained in:
parent
a7bfe146b1
commit
d8ecc71abb
2 changed files with 62 additions and 2 deletions
63
FU.pm
63
FU.pm
|
|
@ -6,6 +6,7 @@ use POSIX ();
|
||||||
use Time::HiRes 'time';
|
use Time::HiRes 'time';
|
||||||
use FU::Log 'log_write';
|
use FU::Log 'log_write';
|
||||||
use FU::Util;
|
use FU::Util;
|
||||||
|
use FU::Validate;
|
||||||
|
|
||||||
|
|
||||||
sub import($pkg, @opt) {
|
sub import($pkg, @opt) {
|
||||||
|
|
@ -641,7 +642,6 @@ sub _getfield($data, @a) {
|
||||||
fu->error(400, "Expected top-level to be a hash") if ref $data ne 'HASH';
|
fu->error(400, "Expected top-level to be a hash") if ref $data ne 'HASH';
|
||||||
return $data->{$a[0]};
|
return $data->{$a[0]};
|
||||||
}
|
}
|
||||||
require FU::Validate;
|
|
||||||
my $schema = FU::Validate->compile(@a > 1 ? { keys => {@a} } : $a[0]);
|
my $schema = FU::Validate->compile(@a > 1 ? { keys => {@a} } : $a[0]);
|
||||||
my $res = eval { $schema->validate($data) };
|
my $res = eval { $schema->validate($data) };
|
||||||
fu->error(400, "Input validation failed: $@") if $@;
|
fu->error(400, "Input validation failed: $@") if $@;
|
||||||
|
|
@ -729,6 +729,39 @@ sub set_header($, $hdr, $val=undef) {
|
||||||
$FU::REQ->{reshdr}{ lc $hdr } = $val;
|
$FU::REQ->{reshdr}{ lc $hdr } = $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub set_cookie($, $name, $val=undef, %opt) {
|
||||||
|
confess "Invalid cookie name '$name'" if $name !~ /^$FU::hdrname_re$/;
|
||||||
|
return delete $FU::REQ->{rescookie}{$name} if !defined $val;
|
||||||
|
confess "Invalid cookie value: $val" if $val =~ /[\0-\x1f\x7f-\x{10ffff}\s\r\n\t",;\\]/;
|
||||||
|
my $c = "$name=$val";
|
||||||
|
for my ($k,$v) (%opt) {
|
||||||
|
$k = lc $k; # attributes are case-insensitive
|
||||||
|
if ($k eq 'domain') {
|
||||||
|
confess "Invalid cookie domain: $v" if $v !~ $FU::Validate::re_domain;
|
||||||
|
} elsif ($k eq 'expires') {
|
||||||
|
confess "Cookie 'Expires' attribute should be a UNIX timestamp" if defined $v && $v !~ /^[0-9]+$/;
|
||||||
|
$v = FU::Util::httpdate_format($v || 0);
|
||||||
|
} elsif ($k eq 'httponly') {
|
||||||
|
$c .= "; $k" if $v;
|
||||||
|
next;
|
||||||
|
} elsif ($k eq 'max-age') {
|
||||||
|
confess "Invalid 'Max-Age' cookie attribute: $v" if $v !~ /^[0-9]+$/;
|
||||||
|
} elsif ($k eq 'partitioned') {
|
||||||
|
$c .= "; $k" if $v;
|
||||||
|
next;
|
||||||
|
} elsif ($k eq 'path') {
|
||||||
|
confess "Invalid 'Path' cookie attribute: $v" if $v =~ /[\0-\x1f\x7f-\x{10ffff}\s\r\n\t",;\\]/;
|
||||||
|
} elsif ($k eq 'secure') {
|
||||||
|
$c .= "; $k" if $v;
|
||||||
|
next;
|
||||||
|
} elsif ($k eq 'samesite') {
|
||||||
|
confess "Invalid 'SameSite' cookie attribute: $v" if $v !~ /^(?:Strict|Lax|None)$/;
|
||||||
|
}
|
||||||
|
$c .= "; $k=$v";
|
||||||
|
}
|
||||||
|
$FU::REQ->{rescookie}{$name} = $c;
|
||||||
|
}
|
||||||
|
|
||||||
sub send_json($, $data) {
|
sub send_json($, $data) {
|
||||||
fu->set_header('content-type', 'application/json');
|
fu->set_header('content-type', 'application/json');
|
||||||
fu->set_body(FU::Util::json_format($data, canonical => 1, utf8 => 1));
|
fu->set_body(FU::Util::json_format($data, canonical => 1, utf8 => 1));
|
||||||
|
|
@ -804,6 +837,8 @@ sub _finalize {
|
||||||
state $haszlib = eval { require Compress::Raw::Zlib; 1 };
|
state $haszlib = eval { require Compress::Raw::Zlib; 1 };
|
||||||
my $r = $FU::REQ;
|
my $r = $FU::REQ;
|
||||||
|
|
||||||
|
fu->add_header('set-cookie', $_) for $r->{rescookie} ? sort values $r->{rescookie}->%* : ();
|
||||||
|
|
||||||
if ($r->{status} == 204 || $r->{status} == 304) {
|
if ($r->{status} == 204 || $r->{status} == 304) {
|
||||||
delete $r->{reshdr}{'content-length'};
|
delete $r->{reshdr}{'content-length'};
|
||||||
delete $r->{reshdr}{'content-encoding'};
|
delete $r->{reshdr}{'content-encoding'};
|
||||||
|
|
@ -1335,6 +1370,31 @@ Add a response header, can be used to add multiple headers with the same name.
|
||||||
Add a response header or overwrite the header with a new value if it already
|
Add a response header or overwrite the header with a new value if it already
|
||||||
exists. Set C<$value> to undef to remove a previously set header.
|
exists. Set C<$value> to undef to remove a previously set header.
|
||||||
|
|
||||||
|
=item fu->set_cookie($name, $value, %attributes)
|
||||||
|
|
||||||
|
Set or overwrite a cookie. Set C<$value> to undef to remove a previously set
|
||||||
|
cookie. To fully remove a cookie from the user's browser, set the cookie with
|
||||||
|
an empty value and zero C<Max-Age>:
|
||||||
|
|
||||||
|
fu->set_cookie(my_cookie => '', 'Max-Age' => 0);
|
||||||
|
|
||||||
|
C<%attributes> can be any of the supported L<cookie
|
||||||
|
attributes|https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie>.
|
||||||
|
The C<Expires> attribute, when given, must be a UNIX timestamp. Boolean
|
||||||
|
attributes are interpreted according to Perl's idea of truthiness. For example:
|
||||||
|
|
||||||
|
fu->set_cookie(auth => $auth_token,
|
||||||
|
Expires => time()+30*24*3600,
|
||||||
|
Domain => 'example.com',
|
||||||
|
Secure => 1,
|
||||||
|
SameSite => 'Lax'
|
||||||
|
);
|
||||||
|
|
||||||
|
This method does not encode or escape the cookie value in any way. If you want
|
||||||
|
to set a non-ASCII value or a value containing characters that are not
|
||||||
|
permitted in the C<Set-Cookie> header, use C<uri_escape()> in L<FU::Util> or
|
||||||
|
your favorite alternative cookie-safe encoding.
|
||||||
|
|
||||||
=item fu->set_body($data)
|
=item fu->set_body($data)
|
||||||
|
|
||||||
Set the (raw, binary) body of the response to C<$data>. This method is not very
|
Set the (raw, binary) body of the response to C<$data>. This method is not very
|
||||||
|
|
@ -1408,7 +1468,6 @@ one of the following status codes or an alias:
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
I<TODO:> Setting cookies.
|
|
||||||
|
|
||||||
|
|
||||||
=head2 Running the Site
|
=head2 Running the Site
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ sub uri_escape :prototype($) ($s) {
|
||||||
sub uri_unescape :prototype($) ($s) {
|
sub uri_unescape :prototype($) ($s) {
|
||||||
return if !defined $s;
|
return if !defined $s;
|
||||||
utf8::encode($s);
|
utf8::encode($s);
|
||||||
|
$s =~ tr/+/ /;
|
||||||
$s =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
|
$s =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
|
||||||
utf8_decode $s;
|
utf8_decode $s;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue