X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/ce6f98e12cf2125f4650bd90edaf88a4c7c13817ac69e2e7b5bef58a455797d7..7b842a26b504d737eb37311899483eba076ae9dfccf9de49ffb92072490f99be:/gitweb.perl?ds=inline diff --git a/gitweb.perl b/gitweb.perl index 1e0af11..b47a42f 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -10,6 +10,8 @@ use 5.008; use strict; use warnings; +# handle ACL in file access tests +use filetest 'access'; use CGI qw(:standard :escapeHTML -nosticky); use CGI::Util qw(unescape); use CGI::Carp qw(fatalsToBrowser set_message); @@ -18,6 +20,8 @@ use Fcntl ':mode'; use File::Find qw(); use File::Basename qw(basename); use Time::HiRes qw(gettimeofday tv_interval); +use Digest::MD5 qw(md5_hex); + binmode STDOUT, ':utf8'; if (!defined($CGI::VERSION) || $CGI::VERSION < 4.08) { @@ -489,7 +493,6 @@ our %feature = ( # Currently available providers are gravatar and picon. # If an unknown provider is specified, the feature is disabled. - # Gravatar depends on Digest::MD5. # Picon currently relies on the indiana.edu database. # To enable system wide have in $GITWEB_CONFIG @@ -739,7 +742,7 @@ sub evaluate_gitweb_config { $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON); # Common system-wide settings for convenience. - # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM. + # Those settings can be overridden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM. read_config_file($GITWEB_CONFIG_COMMON); # Use first config file that exists. This means use the per-instance @@ -786,6 +789,38 @@ sub check_loadavg { # ====================================================================== # input validation and dispatch +# Various hash size-related values. +my $sha1_len = 40; +my $sha256_extra_len = 24; +my $sha256_len = $sha1_len + $sha256_extra_len; + +# A regex matching $len hex characters. $len may be a range (e.g. 7,64). +sub oid_nlen_regex { + my $len = shift; + my $hchr = qr/[0-9a-fA-F]/; + return qr/(?:(?:$hchr){$len})/; +} + +# A regex matching two sets of $nlen hex characters, prefixed by the literal +# string $prefix and with the literal string $infix between them. +sub oid_nlen_prefix_infix_regex { + my $nlen = shift; + my $prefix = shift; + my $infix = shift; + + my $rx = oid_nlen_regex($nlen); + + return qr/^\Q$prefix\E$rx\Q$infix\E$rx$/; +} + +# A regex matching a valid object ID. +our $oid_regex; +{ + my $x = oid_nlen_regex($sha1_len); + my $y = oid_nlen_regex($sha256_extra_len); + $oid_regex = qr/(?:$x(?:$y)?)/; +} + # input parameters can be collected from a variety of sources (presently, CGI # and PATH_INFO), so we define an %input_params hash that collects them all # together during validation: this allows subsequent uses (e.g. href()) to be @@ -1165,18 +1200,8 @@ sub configure_gitweb_features { our @snapshot_fmts = gitweb_get_feature('snapshot'); @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); - # check that the avatar feature is set to a known provider name, - # and for each provider check if the dependencies are satisfied. - # if the provider name is invalid or the dependencies are not met, - # reset $git_avatar to the empty string. our ($git_avatar) = gitweb_get_feature('avatar'); - if ($git_avatar eq 'gravatar') { - $git_avatar = '' unless (eval { require Digest::MD5; 1; }); - } elsif ($git_avatar eq 'picon') { - # no dependencies - } else { - $git_avatar = ''; - } + $git_avatar = '' unless $git_avatar =~ /^(?:gravatar|picon)$/s; our @extra_branch_refs = gitweb_get_feature('extra-branch-refs'); @extra_branch_refs = filter_and_validate_refs (@extra_branch_refs); @@ -1267,9 +1292,23 @@ our $is_last_request = sub { 1 }; our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook); our $CGI = 'CGI'; our $cgi; +our $FCGI_Stream_PRINT_raw = \&FCGI::Stream::PRINT; sub configure_as_fcgi { require CGI::Fast; our $CGI = 'CGI::Fast'; + # FCGI is not Unicode aware hence the UTF-8 encoding must be done manually. + # However no encoding must be done within git_blob_plain() and git_snapshot() + # which must still output in raw binary mode. + no warnings 'redefine'; + my $enc = Encode::find_encoding('UTF-8'); + *FCGI::Stream::PRINT = sub { + my @OUTPUT = @_; + for (my $i = 1; $i < @_; $i++) { + $OUTPUT[$i] = $enc->encode($_[$i], Encode::FB_CROAK|Encode::LEAVE_SRC); + } + @_ = @OUTPUT; + goto $FCGI_Stream_PRINT_raw; + }; my $request_number = 0; # let each child service 100 requests @@ -1524,7 +1563,7 @@ sub is_valid_refname { return undef unless defined $input; # textual hashes are O.K. - if ($input =~ m/^[0-9a-fA-F]{40}$/) { + if ($input =~ m/^$oid_regex$/) { return 1; } # it must be correct pathname @@ -1633,15 +1672,15 @@ sub quot_cec { my $cntrl = shift; my %opts = @_; my %es = ( # character escape codes, aka escape sequences - "\t" => '\t', # tab (HT) - "\n" => '\n', # line feed (LF) - "\r" => '\r', # carrige return (CR) - "\f" => '\f', # form feed (FF) - "\b" => '\b', # backspace (BS) - "\a" => '\a', # alarm (bell) (BEL) - "\e" => '\e', # escape (ESC) - "\013" => '\v', # vertical tab (VT) - "\000" => '\0', # nul character (NUL) + "\t" => '\t', # tab (HT) + "\n" => '\n', # line feed (LF) + "\r" => '\r', # carriage return (CR) + "\f" => '\f', # form feed (FF) + "\b" => '\b', # backspace (BS) + "\a" => '\a', # alarm (bell) (BEL) + "\e" => '\e', # escape (ESC) + "\013" => '\v', # vertical tab (VT) + "\000" => '\0', # nul character (NUL) ); my $chr = ( (exists $es{$cntrl}) ? $es{$cntrl} @@ -2036,6 +2075,9 @@ sub file_type_long { sub format_log_line_html { my $line = shift; + # Potentially abbreviated OID. + my $regex = oid_nlen_regex("7,64"); + $line = esc_html($line, -nbsp=>1); $line =~ s{ \b @@ -2045,10 +2087,10 @@ sub format_log_line_html { (?'; } # match - if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) { + if ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", ",") | + $line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", ",")) { # can match only for combined diff $line = 'index '; for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { @@ -2316,7 +2359,8 @@ sub format_extended_diff_header_line { $line .= '0' x 7; } - } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) { + } elsif ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", "..") | + $line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", "..")) { # can match only for ordinary diff my ($from_link, $to_link); if ($from->{'href'}) { @@ -2842,7 +2886,7 @@ sub git_get_hash_by_path { } #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/; + $line =~ m/^([0-9]+) (.+) ($oid_regex)\t/; if (defined $type && $type ne $2) { # type doesn't match return undef; @@ -3072,6 +3116,8 @@ sub git_get_projects_list { return if (m!^[/.]$!); # only directories can be git repositories return unless (-d $_); + # need search permission + return unless (-x $_); # don't traverse too deep (Find is super slow on os x) # $project_maxdepth excludes depth of $projectroot if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { @@ -3126,7 +3172,7 @@ sub git_get_projects_list { return @list; } -# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile) +# written with help of Tree::Trie module (Perl Artistic License, GPL compatible) # as side effects it sets 'forks' field to list of forks for forked projects sub filter_forks_from_projects_list { my $projects = shift; @@ -3339,7 +3385,7 @@ sub git_get_references { while (my $line = <$fd>) { chomp $line; - if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) { + if ($line =~ m!^($oid_regex)\srefs/($type.*)$!) { if (defined $refs{$1}) { push @{$refs{$1}}, $2; } else { @@ -3413,7 +3459,7 @@ sub parse_tag { $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; - if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { + if ($line =~ m/^object ($oid_regex)$/) { $tag{'object'} = $1; } elsif ($line =~ m/^type (.+)$/) { $tag{'type'} = $1; @@ -3457,15 +3503,15 @@ sub parse_commit_text { } my $header = shift @commit_lines; - if ($header !~ m/^[0-9a-fA-F]{40}/) { + if ($header !~ m/^$oid_regex/) { return; } ($co{'id'}, my @parents) = split ' ', $header; while (my $line = shift @commit_lines) { last if $line eq "\n"; - if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { + if ($line =~ m/^tree ($oid_regex)$/) { $co{'tree'} = $1; - } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) { + } elsif ((!defined $withparents) && ($line =~ m/^parent ($oid_regex)$/)) { push @parents, $1; } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { $co{'author'} = to_utf8($1); @@ -3597,7 +3643,7 @@ sub parse_difftree_raw_line { # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ($oid_regex) ($oid_regex) (.)([0-9]{0,3})\t(.*)$/) { $res{'from_mode'} = $1; $res{'to_mode'} = $2; $res{'from_id'} = $3; @@ -3612,7 +3658,7 @@ sub parse_difftree_raw_line { } # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' # combined diff (for merge commit) - elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) { + elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:$oid_regex )+)([a-zA-Z]+)\t(.*)$//) { $res{'nparents'} = length($1); $res{'from_mode'} = [ split(' ', $2) ]; $res{'to_mode'} = pop @{$res{'from_mode'}}; @@ -3622,7 +3668,7 @@ sub parse_difftree_raw_line { $res{'to_file'} = unquote($5); } # 'c512b523472485aef4fff9e57b229d9d243c967f' - elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + elsif ($line =~ m/^($oid_regex)$/) { $res{'commit'} = $1; } @@ -3650,7 +3696,7 @@ sub parse_ls_tree_line { if ($opts{'-l'}) { #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; + $line =~ m/^([0-9]+) (.+) ($oid_regex) +(-|[0-9]+)\t(.+)$/s; $res{'mode'} = $1; $res{'type'} = $2; @@ -3663,7 +3709,7 @@ sub parse_ls_tree_line { } } else { #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + $line =~ m/^([0-9]+) (.+) ($oid_regex)\t(.+)$/s; $res{'mode'} = $1; $res{'type'} = $2; @@ -4017,7 +4063,7 @@ sub print_feed_meta { $href_params{'extra_options'} = undef; $href_params{'action'} = $type; - $link_attr{'-href'} = href(%href_params); + $link_attr{'-href'} = esc_attr(href(%href_params)); print "\n"; $href_params{'extra_options'} = '--no-merges'; - $link_attr{'-href'} = href(%href_params); + $link_attr{'-href'} = esc_attr(href(%href_params)); $link_attr{'-title'} .= ' (no merges)'; print "'."\n", - esc_attr($site_name), href(project=>undef, action=>"project_index")); + esc_attr($site_name), + esc_attr(href(project=>undef, action=>"project_index"))); printf(''."\n", - esc_attr($site_name), href(project=>undef, action=>"opml")); + esc_attr($site_name), + esc_attr(href(project=>undef, action=>"opml"))); } } @@ -4256,8 +4304,8 @@ sub git_footer_html { if (defined $action && $action eq 'blame_incremental') { print qq!\n!; } else { my ($jstimezone, $tz_cookie, $datetime_class) = @@ -4377,7 +4425,7 @@ sub git_print_page_nav { "\n"; } -# returns a submenu for the nagivation of the refs views (tags, heads, +# returns a submenu for the navigation of the refs views (tags, heads, # remotes) with the current view disabled and the remotes view only # available if the feature is enabled sub format_ref_views { @@ -4594,7 +4642,7 @@ sub git_print_log { # print log my $skip_blank_line = 0; foreach my $line (@$log) { - if ($line =~ m/^\s*([A-Z][-A-Za-z]*-[Bb]y|C[Cc]): /) { + if ($line =~ m/^\s*([A-Z][-A-Za-z]*-([Bb]y|[Tt]o)|C[Cc]|(Clos|Fix)es): /) { if (! $opts{'-remove_signoff'}) { print "" . esc_html($line) . "
\n"; $skip_blank_line = 1; @@ -4805,7 +4853,7 @@ sub fill_from_file_info { sub is_deleted { my $diffinfo = shift; - return $diffinfo->{'to_id'} eq ('0' x 40); + return $diffinfo->{'to_id'} eq ('0' x 40) || $diffinfo->{'to_id'} eq ('0' x 64); } # does patch correspond to [previous] difftree raw line @@ -5252,7 +5300,7 @@ sub format_ctx_rem_add_lines { # + c # + d # - # Otherwise the highlightling would be confusing. + # Otherwise the highlighting would be confusing. if ($is_combined) { for (my $i = 0; $i < @$add; $i++) { my $prefix_rem = substr($rem->[$i], 0, $num_parents); @@ -5966,6 +6014,9 @@ sub git_history_body { $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); if ($ftype eq 'blob') { + print " | " . + $cgi->a({-href => href(action=>"blob_plain", hash_base=>$commit, file_name=>$file_name)}, "raw"); + my $blob_current = $file_hash; my $blob_parent = git_get_hash_by_path($commit, $file_name); if (defined $blob_current && defined $blob_parent && @@ -6288,7 +6339,7 @@ sub git_search_changes { -class => "list subject"}, chop_and_escape_str($co{'title'}, 50) . "
"); } elsif (defined $set{'to_id'}) { - next if ($set{'to_id'} =~ m/^0{40}$/); + next if is_deleted(\%set); print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, hash=>$set{'to_id'}, file_name=>$set{'to_file'}), @@ -6832,7 +6883,7 @@ sub git_blame_common { # the header: [] # no for subsequent lines in group of lines my ($full_rev, $orig_lineno, $lineno, $group_size) = - ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); + ($line =~ /^($oid_regex) (\d+) (\d+)(?: (\d+))?$/); if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = { 'nprevious' => 0 }; } @@ -6882,7 +6933,7 @@ sub git_blame_common { } # 'previous' if (exists $meta->{'previous'} && - $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { + $meta->{'previous'} =~ /^($oid_regex) (.*)$/) { $meta->{'parent'} = $1; $meta->{'file_parent'} = unquote($2); } @@ -6999,7 +7050,7 @@ sub git_blob_plain { } else { die_error(400, "No file name defined"); } - } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + } elsif ($hash =~ m/^$oid_regex$/) { # blobs defined by non-textual hash id's can be cached $expires = "+1d"; } @@ -7043,6 +7094,7 @@ sub git_blob_plain { ($sandbox ? 'attachment' : 'inline') . '; filename="' . $save_as . '"'); local $/ = undef; + local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -7060,7 +7112,7 @@ sub git_blob { } else { die_error(400, "No file name defined"); } - } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + } elsif ($hash =~ m/^$oid_regex$/) { # blobs defined by non-textual hash id's can be cached $expires = "+1d"; } @@ -7121,8 +7173,8 @@ sub git_blob { print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!; } print qq! src="! . - href(action=>"blob_plain", hash=>$hash, - hash_base=>$hash_base, file_name=>$file_name) . + esc_attr(href(action=>"blob_plain", hash=>$hash, + hash_base=>$hash_base, file_name=>$file_name)) . qq!" />\n!; } else { my $nr; @@ -7381,6 +7433,7 @@ sub git_snapshot { open my $fd, "-|", $cmd or die_error(500, "Execute git-archive failed"); + local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -7518,7 +7571,7 @@ sub git_commit { # non-textual hash id's can be cached my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + if ($hash =~ m/^$oid_regex$/) { $expires = "+1d"; } my $refs = git_get_references(); @@ -7612,7 +7665,7 @@ sub git_object { close $fd; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { + unless ($line && $line =~ m/^([0-9]+) (.+) ($oid_regex)\t/) { die_error(404, "File or directory for given base does not exist"); } $type = $2; @@ -7652,7 +7705,7 @@ sub git_blobdiff { or die_error(404, "Blob diff not found"); } elsif (defined $hash && - $hash =~ /[0-9a-fA-F]{40}/) { + $hash =~ $oid_regex) { # try to find filename from $hash # read filtered raw output @@ -7662,7 +7715,7 @@ sub git_blobdiff { @difftree = # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' # $hash == to_id - grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } + grep { /^:[0-7]{6} [0-7]{6} $oid_regex $hash/ } map { chomp; $_ } <$fd>; close $fd or die_error(404, "Reading git-diff-tree failed"); @@ -7685,8 +7738,8 @@ sub git_blobdiff { $hash ||= $diffinfo{'to_id'}; # non-textual hash id's can be cached - if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ && - $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) { + if ($hash_base =~ m/^$oid_regex$/ && + $hash_parent_base =~ m/^$oid_regex$/) { $expires = '+1d'; } @@ -7822,7 +7875,7 @@ sub git_commitdiff { $hash_parent ne '-c' && $hash_parent ne '--cc') { # commitdiff with two commits given my $hash_parent_short = $hash_parent; - if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) { + if ($hash_parent =~ m/^$oid_regex$/) { $hash_parent_short = substr($hash_parent, 0, 7); } $formats_nav .= @@ -7931,7 +7984,7 @@ sub git_commitdiff { # non-textual hash id's can be cached my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + if ($hash =~ m/^$oid_regex$/) { $expires = "+1d"; } @@ -8086,7 +8139,7 @@ sub git_search_help {

Pattern is by default a normal string that is matched precisely (but without regard to case, except in the case of pickaxe). However, when you check the re checkbox, the pattern entered is recognized as the POSIX extended -regular expression (also case +regular expression (also case insensitive).

commit
@@ -8205,6 +8258,7 @@ sub git_feed { } else { $alt_url = href(-full=>1, action=>"summary"); } + $alt_url = esc_attr($alt_url); print qq!\n!; if ($format eq 'rss') { print <' . "\n" . '' . "\n" . - "" . href(-full=>1) . "\n" . + "" . esc_url(href(-full=>1)) . "\n" . # use project owner for feed author "$owner\n"; if (defined $favicon) { @@ -8288,7 +8342,7 @@ XML "" . esc_html($co{'author'}) . "\n" . "$cd{'rfc2822'}\n" . "$co_url\n" . - "$co_url\n" . + "" . esc_html($co_url) . "\n" . "" . esc_html($co{'title'}) . "\n" . "" . "\n" . "$cd{'iso-8601'}\n" . - "\n" . - "$co_url\n" . + "\n" . + "" . esc_html($co_url) . "\n" . "\n" . "
\n"; } @@ -8418,8 +8472,8 @@ XML } my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1); - my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1); + my $rss = esc_attr(href('project' => $proj{'path'}, 'action' => 'rss', -full => 1)); + my $html = esc_attr(href('project' => $proj{'path'}, 'action' => 'summary', -full => 1)); print "\n"; } print <