This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository fateserver.
commit a5920b91589250ec07afe2676ea67b1845d1623c Author: Kacper Michajłow <[email protected]> AuthorDate: Thu Jul 30 01:05:53 2026 +0200 Commit: Kacper Michajłow <[email protected]> CommitDate: Wed Aug 12 00:42:16 2026 +0200 index: allow "/" and "%" in search criteria CGI already percent-decodes each parameter, so decoding the query again turned an escaped "/" inside a value back into the "//" separator and tore the criterion in two. Pass one criterion per parameter instead: CGI splits on "&" before decoding, so a value can no longer be mistaken for a separator. Escape values as bytes, as uri_escape_utf8 double-encodes the bytes CGI and the report files actually hand out. Signed-off-by: Kacper Michajłow <[email protected]> --- index.cgi | 95 ++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/index.cgi b/index.cgi index 377651e..d3dc943 100755 --- a/index.cgi +++ b/index.cgi @@ -20,7 +20,7 @@ use warnings; use lib "/var/www/fateweb"; -use CGI qw/param/; +use CGI qw/param multi_param/; use HTML::Entities; use FATE; use Time::Zone; @@ -28,12 +28,26 @@ use URI::Escape; cgi_path_is_trustworthy; -# Format for /?query= : /?query=type:value//type:value// (URI encoded). -# Trailing // does not matter (i.e. may be added). -# @queries contains an array of 'type:value' strings. -# Every member of @queries can be further parsed with another simple -# split(/:/, $this_query, 2); -my @queries = split(/\/\//, uri_unescape scalar param 'query') if (param 'query'); +# Format for /?filter= : /?filter=type:value, repeated once per criterion, as +# an array of [type, value] pairs. CGI splits the parameters before decoding +# them, so a value may contain any character. Decoding again here would let a +# '/' in a value act as a separator. +# +# Links generated before this format used /?query=type:value//type:value//, +# URI-encoded once more. Keep accepting those, a legacy separator cannot be +# told apart from '//' in a value, which is why the new format has a new name. +my @raw_queries = multi_param 'filter'; +if (!@raw_queries) { + my $legacy = scalar param 'query'; + @raw_queries = split /\/\//, uri_unescape $legacy if defined $legacy; +} +my @queries; +for my $this_query (@raw_queries) { + my ($type, $text) = split(/:/, $this_query, 2); + defined $text or next; + ($type) = $type =~ /^([a-z]{1,16})\z/ or next; + push @queries, [$type, $text]; +} my $sort = safeparam_sort; $sort = "subarch" if defined($sort) && $sort eq "arch"; @@ -55,8 +69,8 @@ for my $slot (@slots) { my $not_matched = 0; $$rep{subarch} = $$rep{arch} if not $$rep{subarch}; for my $this_query (@queries) { - my ($type, $text) = split(/:/, $this_query, 2); - $not_matched = 1 if ($$rep{$type} ne $text); + my ($type, $text) = @$this_query; + $not_matched = 1 if not defined $$rep{$type} or $$rep{$type} ne $text; } next if $not_matched; @@ -102,16 +116,29 @@ sub repcmp { return 0; }; -sub lsort { - my $params = ''; +# Every parameter except the criteria and, optionally, $skip, in HTTP format. +# The criteria may occur several times and are regenerated by query_params(). +sub other_params { + my ($skip) = @_; + my @params; for my $thisparam (param) { next if $thisparam =~ /[^a-z0-9_]/; - next if $thisparam =~ 'sort'; - $params .= '&' if $params ne ''; - $params .= "$thisparam=" . uri_escape(param($thisparam)); + next if $thisparam eq 'filter' or $thisparam eq 'query'; + next if defined $skip and $thisparam eq $skip; + push @params, "$thisparam=" . uri_escape(scalar param($thisparam)); } - $params .= '&' if $params; + return @params; +} + +# Serialise [type, value] pairs as repeated 'filter' parameters. Values are +# bytes, as CGI and the report files give them, so escape them as bytes. +sub query_params { + return map "filter=" . uri_escape("$$_[0]:" . ($$_[1] // '')), @_; +} + +sub lsort { my ($text, $key) = @_; + my @params = (other_params('sort'), query_params(@queries)); my $newkey = ''; if ($sort eq $key) { # $key = $sort @@ -130,39 +157,21 @@ sub lsort { } $key = $newkey if $newkey ne ''; - anchor $text, href => "?${params}sort=$key"; + push @params, "sort=$key"; + anchor $text, href => '?' . join '&', @params; } sub category { my ($category, $rep) = @_; - my $head_printed = 0; - # $params will contain parameters else than query, if any, in HTTP format. - my $params = ''; - for my $thisparam (param) { - next if $thisparam =~ /[^a-z0-9_]/; - next if $thisparam eq 'query'; - $params .= '&' if $params ne ''; - $params .= "$thisparam=" . uri_escape(param($thisparam)); - } - my $head = ($params ? '&' : '') . 'query='; - - if (@queries) { - for my $this_query (@queries) { - my ($type, $text) = split(/:/, $this_query, 2); - if ($type ne $category) { - $params .= $head if (!$head_printed); - $params .= $this_query . '//'; - $head_printed = 1; - } - } - } - $params .= $head if (!$head_printed); - $params .= "$category:" . uri_escape_utf8 "$$rep{$category}" . '//'; - $head_printed = 1; # for the sake of completeness + # Keep every search criterion except the one for $category, which the + # value in this cell replaces. + my @params = (other_params(), + query_params(grep($$_[0] ne $category, @queries), + [$category, $$rep{$category}])); start 'td'; - anchor $$rep{$category}, href => "?$params"; + anchor $$rep{$category}, href => '?' . join '&', @params; end 'td'; } @@ -197,8 +206,8 @@ if (@queries) { start 'p'; print 'Search patterns: '; for my $this_query (@queries) { - my ($type, $text) = split(/:/, $this_query, 2); - print "$type: $text; "; + my ($type, $text) = @$this_query; + print encode_entities("$type: $text", '<>&"'), '; '; } anchor 'clear all.', href => ""; end 'p'; _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
