X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/0e8c2e1d97cde0d2549466c258a5b065b80fd03a68d75a5d176626fb725d3ff3..7bcc1216c86a336f35582d8e3ffac8b484927b99d869bedf766b636f78728a2d:/gitweb.perl
diff --git a/gitweb.perl b/gitweb.perl
index 3398de3..351f524 100755
--- a/gitweb.perl
+++ b/gitweb.perl
@@ -1082,7 +1082,16 @@ sub evaluate_and_validate_params {
if (length($searchtext) < 2) {
die_error(403, "At least two characters are required for search parameter");
}
- $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
+ if ($search_use_regexp) {
+ $search_regexp = $searchtext;
+ if (!eval { qr/$search_regexp/; 1; }) {
+ (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
+ die_error(400, "Invalid search regexp '$search_regexp'",
+ esc_html($error));
+ }
+ } else {
+ $search_regexp = quotemeta $searchtext;
+ }
}
}
@@ -1743,20 +1752,61 @@ sub esc_html_hl_regions {
return $out;
}
-# highlight match (if any), and escape HTML
-sub esc_html_match_hl {
+# return positions of beginning and end of each match
+sub matchpos_list {
my ($str, $regexp) = @_;
- return esc_html($str) unless defined $regexp;
+ return unless (defined $str && defined $regexp);
my @matches;
while ($str =~ /$regexp/g) {
push @matches, [$-[0], $+[0]];
}
+ return @matches;
+}
+
+# highlight match (if any), and escape HTML
+sub esc_html_match_hl {
+ my ($str, $regexp) = @_;
+ return esc_html($str) unless defined $regexp;
+
+ my @matches = matchpos_list($str, $regexp);
return esc_html($str) unless @matches;
return esc_html_hl_regions($str, 'match', @matches);
}
+
+# highlight match (if any) of shortened string, and escape HTML
+sub esc_html_match_hl_chopped {
+ my ($str, $chopped, $regexp) = @_;
+ return esc_html_match_hl($str, $regexp) unless defined $chopped;
+
+ my @matches = matchpos_list($str, $regexp);
+ return esc_html($chopped) unless @matches;
+
+ # filter matches so that we mark chopped string
+ my $tail = "... "; # see chop_str
+ unless ($chopped =~ s/\Q$tail\E$//) {
+ $tail = '';
+ }
+ my $chop_len = length($chopped);
+ my $tail_len = length($tail);
+ my @filtered;
+
+ for my $m (@matches) {
+ if ($m->[0] > $chop_len) {
+ push @filtered, [ $chop_len, $chop_len + $tail_len ] if ($tail_len > 0);
+ last;
+ } elsif ($m->[1] > $chop_len) {
+ push @filtered, [ $m->[0], $chop_len + $tail_len ];
+ last;
+ }
+ push @filtered, $m;
+ }
+
+ return esc_html_hl_regions($chopped . $tail, 'match', @filtered);
+}
+
## ----------------------------------------------------------------------
## functions returning short strings
@@ -3024,15 +3074,15 @@ sub filter_forks_from_projects_list {
sub search_projects_list {
my ($projlist, %opts) = @_;
my $tagfilter = $opts{'tagfilter'};
- my $searchtext = $opts{'searchtext'};
+ my $search_re = $opts{'search_regexp'};
return @$projlist
- unless ($tagfilter || $searchtext);
+ unless ($tagfilter || $search_re);
# searching projects require filling to be run before it;
fill_project_list_info($projlist,
$tagfilter ? 'ctags' : (),
- $searchtext ? ('path', 'descr') : ());
+ $search_re ? ('path', 'descr') : ());
my @projects;
PROJECT:
foreach my $pr (@$projlist) {
@@ -3043,10 +3093,10 @@ sub search_projects_list {
grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
}
- if ($searchtext) {
+ if ($search_re) {
next unless
- $pr->{'path'} =~ /$searchtext/ ||
- $pr->{'descr_long'} =~ /$searchtext/;
+ $pr->{'path'} =~ /$search_re/ ||
+ $pr->{'descr_long'} =~ /$search_re/;
}
push @projects, $pr;
@@ -5207,7 +5257,7 @@ sub git_patchset_body {
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
sub git_project_search_form {
- my ($searchtext, $search_use_regexp);
+ my ($searchtext, $search_use_regexp) = @_;
my $limit = '';
if ($project_filter) {
@@ -5401,10 +5451,17 @@ sub git_project_list_rows {
print "\n";
}
print "
" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list"}, esc_html($pr->{'path'})) . " | \n" .
+ -class => "list"},
+ esc_html_match_hl($pr->{'path'}, $search_regexp)) .
+ "\n" .
"" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list", -title => $pr->{'descr_long'}},
- esc_html($pr->{'descr'})) . " | \n" .
+ -class => "list",
+ -title => $pr->{'descr_long'}},
+ $search_regexp
+ ? esc_html_match_hl_chopped($pr->{'descr_long'},
+ $pr->{'descr'}, $search_regexp)
+ : esc_html($pr->{'descr'})) .
+ "\n" .
"" . chop_and_escape_str($pr->{'owner'}, 15) . " | \n";
print "{'age'}) . "\">" .
(defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . " | \n" .
@@ -5428,16 +5485,16 @@ sub git_project_list_body {
my $show_ctags = gitweb_check_feature('ctags');
my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
$check_forks = undef
- if ($tagfilter || $searchtext);
+ if ($tagfilter || $search_regexp);
# filtering out forks before filling info allows to do less work
@projects = filter_forks_from_projects_list(\@projects)
if ($check_forks);
# search_projects_list pre-fills required info
@projects = search_projects_list(\@projects,
- 'searchtext' => $searchtext,
+ 'search_regexp' => $search_regexp,
'tagfilter' => $tagfilter)
- if ($tagfilter || $searchtext);
+ if ($tagfilter || $search_regexp);
# fill the rest
@projects = fill_project_list_info(\@projects);
@@ -6947,6 +7004,28 @@ sub snapshot_name {
return wantarray ? ($name, $name) : $name;
}
+sub exit_if_unmodified_since {
+ my ($latest_epoch) = @_;
+ our $cgi;
+
+ my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+ if (defined $if_modified) {
+ my $since;
+ if (eval { require HTTP::Date; 1; }) {
+ $since = HTTP::Date::str2time($if_modified);
+ } elsif (eval { require Time::ParseDate; 1; }) {
+ $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+ }
+ if (defined $since && $latest_epoch <= $since) {
+ my %latest_date = parse_date($latest_epoch);
+ print $cgi->header(
+ -last_modified => $latest_date{'rfc2822'},
+ -status => '304 Not Modified');
+ goto DONE_GITWEB;
+ }
+ }
+}
+
sub git_snapshot {
my $format = $input_params{'snapshot_format'};
if (!@snapshot_fmts) {
@@ -6973,6 +7052,10 @@ sub git_snapshot {
my ($name, $prefix) = snapshot_name($project, $hash);
my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+
+ my %co = parse_commit($hash);
+ exit_if_unmodified_since($co{'committer_epoch'}) if %co;
+
my $cmd = quote_command(
git_cmd(), 'archive',
"--format=$known_snapshot_formats{$format}{'format'}",
@@ -6982,9 +7065,15 @@ sub git_snapshot {
}
$filename =~ s/(["\\])/\\$1/g;
+ my %latest_date;
+ if (%co) {
+ %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+ }
+
print $cgi->header(
-type => $known_snapshot_formats{$format}{'type'},
-content_disposition => 'inline; filename="' . $filename . '"',
+ %co ? (-last_modified => $latest_date{'rfc2822'}) : (),
-status => '200 OK');
open my $fd, "-|", $cmd
@@ -7764,33 +7853,14 @@ sub git_feed {
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
my $latest_epoch = $latest_commit{'committer_epoch'};
- %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
- my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
- if (defined $if_modified) {
- my $since;
- if (eval { require HTTP::Date; 1; }) {
- $since = HTTP::Date::str2time($if_modified);
- } elsif (eval { require Time::ParseDate; 1; }) {
- $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
- }
- if (defined $since && $latest_epoch <= $since) {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'},
- -status => '304 Not Modified');
- return;
- }
- }
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'});
- } else {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8');
+ exit_if_unmodified_since($latest_epoch);
+ %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
}
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8',
+ %latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
+ -status => '200 OK');
# Optimization: skip generating the body if client asks only
# for Last-Modified date.