X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/1dc17e37a83f08f963031df52b15d13221fcff6c0536157a619f7cbe5e6e5dcc..2f46e604cc45c467d4921f7dda43c879cc55964e479842774f06ac78a746bdd1:/gitweb.perl
diff --git a/gitweb.perl b/gitweb.perl
index f33524d..89af5f9 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 => {
@@ -176,6 +196,14 @@ our %known_snapshot_format_aliases = (
'x-zip' => undef, '' => undef,
);
+# Pixel sizes for icons and avatars. If the default font sizes or lineheights
+# are changed, it may be appropriate to change these values too via
+# $GITWEB_CONFIG.
+our %avatar_size = (
+ 'default' => 16,
+ 'double' => 32
+);
+
# You define site-wide feature defaults here; override them with
# $GITWEB_CONFIG as necessary.
our %feature = (
@@ -346,6 +374,24 @@ our %feature = (
'sub' => \&feature_patches,
'override' => 0,
'default' => [16]},
+
+ # Avatar support. When this feature is enabled, views such as
+ # shortlog or commit will display an avatar associated with
+ # the email of the committer(s) and/or author(s).
+
+ # Currently only the gravatar provider is available, and it
+ # depends on Digest::MD5. If an unknown provider is specified,
+ # the feature is disabled.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'avatar'}{'default'} = ['gravatar'];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'avatar'}{'override'} = 1;
+ # and in project config gitweb.avatar = gravatar;
+ 'avatar' => {
+ 'sub' => \&feature_avatar,
+ 'override' => 0,
+ 'default' => ['']},
);
sub gitweb_get_feature {
@@ -383,13 +429,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 {
@@ -414,6 +460,12 @@ sub feature_patches {
return ($_[0]);
}
+sub feature_avatar {
+ my @val = (git_get_project_config('avatar'));
+
+ return @val ? @val : @_;
+}
+
# checking HEAD file with -e is fragile if the repository was
# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
# and then pruned.
@@ -439,8 +491,8 @@ sub filter_snapshot_fmts {
@fmts = map {
exists $known_snapshot_format_aliases{$_} ?
$known_snapshot_format_aliases{$_} : $_} @fmts;
- @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
-
+ @fmts = grep {
+ exists $known_snapshot_formats{$_} } @fmts;
}
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
@@ -669,11 +721,12 @@ 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)$//;
- next unless $sfx = $1;
+ unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
+ next;
+ }
+ my $sfx = $1;
# a valid suffix was found, so set the snapshot format
# and reset the hash parameter
$input_params{'snapshot_format'} = $fmt;
@@ -794,6 +847,17 @@ $git_dir = "$projectroot/$project" if $project;
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; });
+} else {
+ $git_avatar = '';
+}
+
# dispatch
if (!defined $action) {
if (defined $hash) {
@@ -809,7 +873,7 @@ if (!defined $action) {
if (!defined($actions{$action})) {
die_error(400, "Unknown action");
}
-if ($action !~ m/^(opml|project_list|project_index)$/ &&
+if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
!$project) {
die_error(400, "Project needed");
}
@@ -819,7 +883,7 @@ exit;
## ======================================================================
## action links
-sub href (%) {
+sub href {
my %params = @_;
# default is to use -absolute url() i.e. $my_uri
my $href = $params{-full} ? $my_url : $my_uri;
@@ -1017,7 +1081,7 @@ sub esc_url {
}
# replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html ($;%) {
+sub esc_html {
my $str = shift;
my %opts = @_;
@@ -1216,7 +1280,7 @@ sub chop_and_escape_str {
if ($chopped eq $str) {
return esc_html($chopped);
} else {
- $str =~ s/([[:cntrl:]])/?/g;
+ $str =~ s/[[:cntrl:]]/?/g;
return $cgi->span({-title=>$str}, esc_html($chopped));
}
}
@@ -1277,7 +1341,7 @@ use constant {
};
# submodule/subproject, a commit object reference
-sub S_ISGITLINK($) {
+sub S_ISGITLINK {
my $mode = shift;
return (($mode & S_IFMT) == S_IFGITLINK)
@@ -1365,13 +1429,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;
}
@@ -1441,6 +1503,7 @@ sub format_subject_html {
$extra = '' unless defined($extra);
if (length($short) < length($long)) {
+ $long =~ s/[[:cntrl:]]/?/g;
return $cgi->a({-href => $href, -class => "list subject",
-title => to_utf8($long)},
esc_html($short) . $extra);
@@ -1450,6 +1513,46 @@ sub format_subject_html {
}
}
+# Insert an avatar for the given $email at the given $size if the feature
+# is enabled.
+sub git_get_avatar {
+ my ($email, %opts) = @_;
+ my $pre_white = ($opts{-pad_before} ? " " : "");
+ my $post_white = ($opts{-pad_after} ? " " : "");
+ $opts{-size} ||= 'default';
+ my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
+ my $url = "";
+ if ($git_avatar eq 'gravatar') {
+ $url = "http://www.gravatar.com/avatar/" .
+ Digest::MD5::md5_hex(lc $email) . "?s=$size";
+ }
+ # Currently only gravatars are supported, but other forms such as
+ # picons can be added by putting an else up here and defining $url
+ # as needed. If no variant puts something in $url, we assume avatars
+ # are completely disabled/unavailable.
+ if ($url) {
+ return $pre_white .
+ "" . $post_white;
+ } else {
+ return "";
+ }
+}
+
+# format the author name of the given commit with the given tag
+# the author name is chopped and escaped according to the other
+# optional parameters (see chop_str).
+sub format_author_html {
+ my $tag = shift;
+ my $co = shift;
+ my $author = chop_and_escape_str($co->{'author_name'}, @_);
+ return "<$tag class=\"author\">" .
+ git_get_avatar($co->{'author_email'}, -pad_after => 1) .
+ $author . "$tag>";
+}
+
# format git diff header line, i.e. "diff --(git|combined|cc) ..."
sub format_git_diff_header_line {
my $line = shift;
@@ -1821,7 +1924,7 @@ sub git_cmd {
# Try to avoid using this function wherever possible.
sub quote_command {
return join(' ',
- map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ ));
+ map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
}
# get HEAD ref of given project as hash
@@ -1895,18 +1998,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
}
@@ -1959,6 +2063,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"};
@@ -2029,7 +2136,7 @@ sub git_get_project_description {
my $path = shift;
$git_dir = "$projectroot/$path";
- open my $fd, "$git_dir/description"
+ open my $fd, '<', "$git_dir/description"
or return git_get_project_config('description');
my $descr = <$fd>;
close $fd;
@@ -2044,18 +2151,17 @@ sub git_get_project_ctags {
my $ctags = {};
$git_dir = "$projectroot/$path";
- unless (opendir D, "$git_dir/ctags") {
- return $ctags;
- }
- foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir(D)) {
- open CT, $_ or next;
- my $val = ;
+ opendir my $dh, "$git_dir/ctags"
+ or return $ctags;
+ foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
+ open my $ct, '<', $_ or next;
+ my $val = <$ct>;
chomp $val;
- close CT;
+ close $ct;
my $ctag = $_; $ctag =~ s#.*/##;
$ctags->{$ctag} = $val;
}
- closedir D;
+ closedir $dh;
$ctags;
}
@@ -2108,7 +2214,7 @@ sub git_get_project_url_list {
my $path = shift;
$git_dir = "$projectroot/$path";
- open my $fd, "$git_dir/cloneurl"
+ open my $fd, '<', "$git_dir/cloneurl"
or return wantarray ?
@{ config_to_multi(git_get_project_config('url')) } :
config_to_multi(git_get_project_config('url'));
@@ -2166,7 +2272,7 @@ sub git_get_projects_list {
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
my %paths;
- open my ($fd), $projects_list or return;
+ open my $fd, '<', $projects_list or return;
PROJECT:
while (my $line = <$fd>) {
chomp $line;
@@ -2229,7 +2335,7 @@ sub git_get_project_list_from_file {
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
if (-f $projects_list) {
- open (my $fd , $projects_list);
+ open(my $fd, '<', $projects_list);
while (my $line = <$fd>) {
chomp $line;
my ($pr, $ow) = split ' ', $line;
@@ -2377,8 +2483,14 @@ sub parse_tag {
$tag{'name'} = $1;
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
$tag{'author'} = $1;
- $tag{'epoch'} = $2;
- $tag{'tz'} = $3;
+ $tag{'author_epoch'} = $2;
+ $tag{'author_tz'} = $3;
+ if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+ $tag{'author_name'} = $1;
+ $tag{'author_email'} = $2;
+ } else {
+ $tag{'author_name'} = $tag{'author'};
+ }
} elsif ($line =~ m/--BEGIN/) {
push @comment, $line;
last;
@@ -2594,7 +2706,7 @@ sub parsed_difftree_line {
}
# parse line of git-ls-tree output
-sub parse_ls_tree_line ($;%) {
+sub parse_ls_tree_line {
my $line = shift;
my %opts = @_;
my %res;
@@ -2783,18 +2895,18 @@ sub mimetype_guess_file {
-r $mimemap or return undef;
my %mimemap;
- open(MIME, $mimemap) or return undef;
- while () {
+ open(my $mh, '<', $mimemap) or return undef;
+ while (<$mh>) {
next if m/^#/; # skip comments
- my ($mime, $exts) = split(/\t+/);
+ my ($mimetype, $exts) = split(/\t+/);
if (defined $exts) {
my @exts = split(/\s+/, $exts);
foreach my $ext (@exts) {
- $mimemap{$ext} = $mime;
+ $mimemap{$ext} = $mimetype;
}
}
}
- close(MIME);
+ close($mh);
$filename =~ /\.([^.]*)$/;
return $mimemap{$1};
@@ -2902,9 +3014,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) {
@@ -3187,22 +3304,54 @@ sub git_print_header_div {
"\n\n";
}
-#sub git_print_authorship (\%) {
+sub print_local_time {
+ my %date = @_;
+ if ($date{'hour_local'} < 6) {
+ printf(" (%02d:%02d %s)",
+ $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+ } else {
+ printf(" (%02d:%02d %s)",
+ $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+ }
+}
+
+# Outputs the author name and date in long form
sub git_print_authorship {
my $co = shift;
+ my %opts = @_;
+ my $tag = $opts{-tag} || 'div';
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
- print "
" .
+ print "<$tag class=\"author_date\">" .
esc_html($co->{'author_name'}) .
" [$ad{'rfc2822'}";
- if ($ad{'hour_local'} < 6) {
- printf(" (%02d:%02d %s)",
- $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
- } else {
- printf(" (%02d:%02d %s)",
- $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+ print_local_time(%ad) if ($opts{-localtime});
+ print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
+ . "$tag>\n";
+}
+
+# Outputs table rows containing the full author or committer information,
+# in the format expected for 'commit' view (& similia).
+# Parameters are a commit hash reference, followed by the list of people
+# to output information for. If the list is empty it defalts to both
+# author and committer.
+sub git_print_authorship_rows {
+ my $co = shift;
+ # too bad we can't use @people = @_ || ('author', 'committer')
+ my @people = @_;
+ @people = ('author', 'committer') unless @people;
+ foreach my $who (@people) {
+ my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
+ print "
" .
$cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
-class => "list subject"},
@@ -4499,7 +4639,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 "