X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/6b484399dbbff42df392f0a1921debc7c7dbf44b663c8a0c081c016ff890deba..92c325a48bdcbb34278537bf0d74f77bf9ca571959af68294e6e620d92561803:/gitweb.perl
diff --git a/gitweb.perl b/gitweb.perl
index e984746..1bbe41c 100755
--- a/gitweb.perl
+++ b/gitweb.perl
@@ -27,13 +27,29 @@ our $version = "++GIT_VERSION++";
our $my_url = $cgi->url();
our $my_uri = $cgi->url(-absolute => 1);
-# if we're called with PATH_INFO, we have to strip that
-# from the URL to find our real URL
-# we make $path_info global because it's also used later on
+# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+# needed and used only for URLs with nonempty PATH_INFO
+our $base_url = $my_url;
+
+# When the script is used as DirectoryIndex, the URL does not contain the name
+# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+# have to do it ourselves. We make $path_info global because it's also used
+# later on.
+#
+# Another issue with the script being the DirectoryIndex is that the resulting
+# $my_url data is not the full script URL: this is good, because we want
+# generated links to keep implying the script name if it wasn't explicitly
+# indicated in the URL we're handling, but it means that $my_url cannot be used
+# as base URL.
+# Therefore, if we needed to strip PATH_INFO, then we know that we have
+# to build the base URL ourselves:
our $path_info = $ENV{"PATH_INFO"};
if ($path_info) {
- $my_url =~ s,\Q$path_info\E$,,;
- $my_uri =~ s,\Q$path_info\E$,,;
+ if ($my_url =~ s,\Q$path_info\E$,, &&
+ $my_uri =~ s,\Q$path_info\E$,, &&
+ defined $ENV{'SCRIPT_NAME'}) {
+ $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+ }
}
# core git executable to use
@@ -133,6 +149,10 @@ our $fallback_encoding = 'latin1';
# - one might want to include '-B' option, e.g. '-B', '-M'
our @diff_opts = ('-M'); # taken from git_commit
+# Disables features that would allow repository owners to inject script into
+# the gitweb domain.
+our $prevent_xss = 0;
+
# information about snapshot formats that gitweb is capable of serving
our %known_snapshot_formats = (
# name => {
@@ -383,13 +403,13 @@ sub feature_bool {
my $key = shift;
my ($val) = git_get_project_config($key, '--bool');
- if ($val eq 'true') {
+ if (!defined $val) {
+ return ($_[0]);
+ } elsif ($val eq 'true') {
return (1);
} elsif ($val eq 'false') {
return (0);
}
-
- return ($_[0]);
}
sub feature_snapshot {
@@ -508,6 +528,7 @@ our %actions = (
"history" => \&git_history,
"log" => \&git_log,
"patch" => \&git_patch,
+ "patches" => \&git_patches,
"rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search,
@@ -668,10 +689,10 @@ sub evaluate_path_info {
# extensions. Allowed extensions are both the defined suffix
# (which includes the initial dot already) and the snapshot
# format key itself, with a prepended dot
- while (my ($fmt, %opt) = each %known_snapshot_formats) {
+ while (my ($fmt, $opt) = each %known_snapshot_formats) {
my $hash = $refname;
my $sfx;
- $hash =~ s/(\Q$opt{'suffix'}\E|\Q.$fmt\E)$//;
+ $hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//;
next unless $sfx = $1;
# a valid suffix was found, so set the snapshot format
# and reset the hash parameter
@@ -834,7 +855,7 @@ sub href (%) {
}
my $use_pathinfo = gitweb_check_feature('pathinfo');
- if ($use_pathinfo) {
+ if ($use_pathinfo and defined $params{'project'}) {
# try to put as many parameters as possible in PATH_INFO:
# - project name
# - action
@@ -849,7 +870,7 @@ sub href (%) {
$href =~ s,/$,,;
# Then add the project name, if present
- $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
+ $href .= "/".esc_url($params{'project'});
delete $params{'project'};
# since we destructively absorb parameters, we keep this
@@ -1364,13 +1385,11 @@ sub format_log_line_html {
my $line = shift;
$line = esc_html($line, -nbsp=>1);
- if ($line =~ m/([0-9a-fA-F]{8,40})/) {
- my $hash_text = $1;
- my $link =
- $cgi->a({-href => href(action=>"object", hash=>$hash_text),
- -class => "text"}, $hash_text);
- $line =~ s/$hash_text/$link/;
- }
+ $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+ $cgi->a({-href => href(action=>"object", hash=>$1),
+ -class => "text"}, $1);
+ }eg;
+
return $line;
}
@@ -1894,18 +1913,19 @@ sub git_parse_project_config {
return %config;
}
-# convert config value to boolean, 'true' or 'false'
+# convert config value to boolean: 'true' or 'false'
# no value, number > 0, 'true' and 'yes' values are true
# rest of values are treated as false (never as error)
sub config_to_bool {
my $val = shift;
+ return 1 if !defined $val; # section.key
+
# strip leading and trailing whitespace
$val =~ s/^\s+//;
$val =~ s/\s+$//;
- return (!defined $val || # section.key
- ($val =~ /^\d+$/ && $val) || # section.key = 1
+ return (($val =~ /^\d+$/ && $val) || # section.key = 1
($val =~ /^(?:true|yes)$/i)); # section.key = true
}
@@ -1958,6 +1978,9 @@ sub git_get_project_config {
$config_file = "$git_dir/config";
}
+ # check if config variable (key) exists
+ return unless exists $config{"gitweb.$key"};
+
# ensure given type
if (!defined $type) {
return $config{"gitweb.$key"};
@@ -2151,8 +2174,9 @@ sub git_get_projects_list {
my $subdir = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- if (check_export_ok("$projectroot/$filter/$subdir")) {
- push @list, { path => ($filter ? "$filter/" : '') . $subdir };
+ my $path = ($filter ? "$filter/" : '') . $subdir;
+ if (check_export_ok("$projectroot/$path")) {
+ push @list, { path => $path };
$File::Find::prune = 1;
}
},
@@ -2900,9 +2924,14 @@ sub git_header_html {
$title
EOF
-# print out each stylesheet that exist
+ # the stylesheet, favicon etc urls won't work correctly with path_info
+ # unless we set the appropriate base URL
+ if ($ENV{'PATH_INFO'}) {
+ print "\n";
+ }
+ # print out each stylesheet that exist, providing backwards capability
+ # for those people who defined $stylesheet in a config file
if (defined $stylesheet) {
-#provides backwards capability for those people who define style sheet in a config file
print ''."\n";
} else {
foreach my $stylesheet (@stylesheets) {
@@ -4497,7 +4526,9 @@ sub git_summary {
print "\n";
- if (-s "$projectroot/$project/README.html") {
+ # If XSS prevention is on, we don't include README.html.
+ # TODO: Allow a readme in some safe format.
+ if (!$prevent_xss && -s "$projectroot/$project/README.html") {
print "
readme
\n" .
"
\n";
insert_file("$projectroot/$project/README.html");
@@ -4758,10 +4789,21 @@ sub git_blob_plain {
$save_as .= '.txt';
}
+ # With XSS prevention on, blobs of all types except a few known safe
+ # ones are served with "Content-Disposition: attachment" to make sure
+ # they don't run in our security domain. For certain image types,
+ # blob view writes an tag referring to blob_plain view, and we
+ # want to be sure not to break that by serving the image as an
+ # attachment (though Firefox 3 doesn't seem to care).
+ my $sandbox = $prevent_xss &&
+ $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+
print $cgi->header(
-type => $type,
-expires => $expires,
- -content_disposition => 'inline; filename="' . $save_as . '"');
+ -content_disposition =>
+ ($sandbox ? 'attachment' : 'inline')
+ . '; filename="' . $save_as . '"');
undef $/;
binmode STDOUT, ':raw';
print <$fd>;
@@ -5017,6 +5059,15 @@ sub git_log {
my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
+ my ($patch_max) = gitweb_get_feature('patches');
+ if ($patch_max) {
+ if ($patch_max < 0 || @commitlist <= $patch_max) {
+ $paging_nav .= " ⋅ " .
+ $cgi->a({-href => href(action=>"patches", -replay=>1)},
+ "patches");
+ }
+ }
+
git_header_html();
git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
@@ -5096,6 +5147,11 @@ sub git_commit {
} @$parents ) .
')';
}
+ if (gitweb_check_feature('patches')) {
+ $formats_nav .= " | " .
+ $cgi->a({-href => href(action=>"patch", -replay=>1)},
+ "patch");
+ }
if (!defined $parent) {
$parent = "--root";
@@ -5372,11 +5428,11 @@ sub git_blobdiff_plain {
}
sub git_commitdiff {
- my $format = shift || 'html';
+ my %params = @_;
+ my $format = $params{-format} || 'html';
- my $patch_max;
+ my ($patch_max) = gitweb_get_feature('patches');
if ($format eq 'patch') {
- ($patch_max) = gitweb_get_feature('patches');
die_error(403, "Patch view not allowed") unless $patch_max;
}
@@ -5394,6 +5450,11 @@ sub git_commitdiff {
$formats_nav =
$cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
"raw");
+ if ($patch_max) {
+ $formats_nav .= " | " .
+ $cgi->a({-href => href(action=>"patch", -replay=>1)},
+ "patch");
+ }
if (defined $hash_parent &&
$hash_parent ne '-c' && $hash_parent ne '--cc') {
@@ -5489,7 +5550,15 @@ sub git_commitdiff {
}
push @commit_spec, '-n', "$hash_parent..$hash";
} else {
- push @commit_spec, '-1', '--root', $hash;
+ if ($params{-single}) {
+ push @commit_spec, '-1';
+ } else {
+ if ($patch_max > 0) {
+ push @commit_spec, "-$patch_max";
+ }
+ push @commit_spec, "-n";
+ }
+ push @commit_spec, '--root', $hash;
}
open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
'--stdout', @commit_spec
@@ -5580,12 +5649,16 @@ sub git_commitdiff {
}
sub git_commitdiff_plain {
- git_commitdiff('plain');
+ git_commitdiff(-format => 'plain');
}
# format-patch-style patches
sub git_patch {
- git_commitdiff('patch');
+ git_commitdiff(-format => 'patch', -single=> 1);
+}
+
+sub git_patches {
+ git_commitdiff(-format => 'patch');
}
sub git_history {
@@ -5938,6 +6011,14 @@ sub git_shortlog {
$cgi->a({-href => href(-replay=>1, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
}
+ my $patch_max = gitweb_check_feature('patches');
+ if ($patch_max) {
+ if ($patch_max < 0 || @commitlist <= $patch_max) {
+ $paging_nav .= " ⋅ " .
+ $cgi->a({-href => href(action=>"patches", -replay=>1)},
+ "patches");
+ }
+ }
git_header_html();
git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
@@ -5975,7 +6056,25 @@ sub git_feed {
}
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
- %latest_date = parse_date($latest_commit{'author_epoch'});
+ my $latest_epoch = $latest_commit{'committer_epoch'};
+ %latest_date = parse_date($latest_epoch);
+ 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',
@@ -6034,7 +6133,24 @@ XML
print "$title\n" .
"$alt_url\n" .
"$descr\n" .
- "en\n";
+ "en\n" .
+ # project owner is responsible for 'editorial' content
+ "$owner\n";
+ if (defined $logo || defined $favicon) {
+ # prefer the logo to the favicon, since RSS
+ # doesn't allow both
+ my $img = esc_url($logo || $favicon);
+ print "\n" .
+ "$img\n" .
+ "$title\n" .
+ "$alt_url\n" .
+ "\n";
+ }
+ if (%latest_date) {
+ print "$latest_date{'rfc2822'}\n";
+ print "$latest_date{'rfc2822'}\n";
+ }
+ print "gitweb v.$version/$git_version\n";
} elsif ($format eq 'atom') {
print <
@@ -6061,6 +6177,7 @@ XML
} else {
print "$latest_date{'iso-8601'}\n";
}
+ print "gitweb\n";
}
# contents
@@ -6182,7 +6299,11 @@ sub git_atom {
sub git_opml {
my @list = git_get_projects_list();
- print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+ print $cgi->header(
+ -type => 'text/xml',
+ -charset => 'utf-8',
+ -content_disposition => 'inline; filename="opml.xml"');
+
print <
@@ -6206,8 +6327,8 @@ XML
}
my $path = esc_html(chop_str($proj{'path'}, 25, 5));
- my $rss = "$my_url?p=$proj{'path'};a=rss";
- my $html = "$my_url?p=$proj{'path'};a=summary";
+ my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
+ my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
print "\n";
}
print <