# (C) 2005, Kay Sievers <kay.sievers@vrfy.org>
# (C) 2005, Christian Gierke <ch@gierke.de>
#
-# This program is licensed under the GPL v2, or a later version
+# This program is licensed under the GPLv2
use strict;
use warnings;
use Fcntl ':mode';
my $cgi = new CGI;
-my $version = "241";
+my $version = "248";
my $my_url = $cgi->url();
my $my_uri = $cgi->url(-absolute => 1);
my $rss_link = "";
# input validation and dispatch
my $action = $cgi->param('a');
if (defined $action) {
- if ($action =~ m/[^0-9a-zA-Z\.\-_]+/) {
+ if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
undef $action;
die_error(undef, "Invalid action parameter.");
}
my $order = $cgi->param('o');
if (defined $order) {
- if ($order =~ m/[^a-zA-Z0-9_]/) {
+ if ($order =~ m/[^0-9a-zA-Z_]/) {
undef $order;
die_error(undef, "Invalid order parameter.");
}
my $project = $cgi->param('p');
if (defined $project) {
- if ($project =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
- undef $project;
- die_error(undef, "Non-canonical project parameter.");
- }
- if ($project =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~]/) {
- undef $project;
- die_error(undef, "Invalid character in project parameter.");
+ $project = validate_input($project);
+ if (!defined($project)) {
+ die_error(undef, "Invalid project parameter.");
}
if (!(-d "$projectroot/$project")) {
undef $project;
my $file_name = $cgi->param('f');
if (defined $file_name) {
- if ($file_name =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
- undef $file_name;
- die_error(undef, "Non-canonical file parameter.");
- }
- if ($file_name =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~\:\!]/) {
- undef $file_name;
- die_error(undef, "Invalid character in file parameter.");
+ $file_name = validate_input($file_name);
+ if (!defined($file_name)) {
+ die_error(undef, "Invalid file parameter.");
}
}
my $hash = $cgi->param('h');
if (defined $hash) {
- if (!($hash =~ m/^[0-9a-fA-F]{40}$/)) {
- if ($hash =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
- undef $hash;
- die_error(undef, "Non-canonical hash parameter.");
- }
- if ($hash =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~\:\!]/) {
- undef $hash;
- die_error(undef, "Invalid character in hash parameter.");
- }
- # replace branch-name with hash
- my $branchlist = git_read_refs("refs/heads");
- foreach my $entry (@$branchlist) {
- my %branch = %$entry;
- if ($branch{'name'} eq $hash) {
- $hash = $branch{'id'};
- last;
- }
- }
+ $hash = validate_input($hash);
+ if (!defined($hash)) {
+ die_error(undef, "Invalid hash parameter.");
}
}
my $hash_parent = $cgi->param('hp');
-if (defined $hash_parent && !($hash_parent =~ m/^[0-9a-fA-F]{40}$/)) {
- undef $hash_parent;
- die_error(undef, "Invalid hash_parent parameter.");
+if (defined $hash_parent) {
+ $hash_parent = validate_input($hash_parent);
+ if (!defined($hash_parent)) {
+ die_error(undef, "Invalid hash parent parameter.");
+ }
}
my $hash_base = $cgi->param('hb');
-if (defined $hash_base && !($hash_base =~ m/^[0-9a-fA-F]{40}$/)) {
- undef $hash_base;
- die_error(undef, "Invalid parent hash parameter.");
+if (defined $hash_base) {
+ $hash_base = validate_input($hash_base);
+ if (!defined($hash_base)) {
+ die_error(undef, "Invalid hash base parameter.");
+ }
}
my $page = $cgi->param('pg');
if (defined $page) {
- if ($page =~ m/^[^0-9]+$/) {
+ if ($page =~ m/[^0-9]$/) {
undef $page;
die_error(undef, "Invalid page parameter.");
}
$searchtext = quotemeta $searchtext;
}
+sub validate_input {
+ my $input = shift;
+
+ if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+ return $input;
+ }
+ if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+ return undef;
+ }
+ if ($input =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~]/) {
+ return undef;
+ }
+ return $input;
+}
+
if (!defined $action || $action eq "summary") {
git_summary();
exit;
-} elsif ($action eq "branches") {
- git_branches();
+} elsif ($action eq "heads") {
+ git_heads();
exit;
} elsif ($action eq "tags") {
git_tags();
sub git_header_html {
my $status = shift || "200 OK";
+ my $expires = shift;
my $title = "git";
if (defined $project) {
$title .= "/$action";
}
}
- print $cgi->header(-type=>'text/html', -charset => 'utf-8', -status=> $status);
+ print $cgi->header(-type=>'text/html', -charset => 'utf-8', -status=> $status, -expires => $expires);
print <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
if (!defined $searchtext) {
$searchtext = "";
}
+ my $search_hash;
+ if (defined $hash) {
+ $search_hash = $hash;
+ } else {
+ $search_hash = "HEAD";
+ }
$cgi->param("a", "search");
+ $cgi->param("h", $search_hash);
print $cgi->startform(-method => "get", -action => "$my_uri") .
"<div class=\"search\">\n" .
$cgi->hidden(-name => "p") . "\n" .
$cgi->hidden(-name => "a") . "\n" .
+ $cgi->hidden(-name => "h") . "\n" .
$cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
"</div>" .
$cgi->end_form() . "\n";
my @commit_lines;
my %co;
- my @parents;
if (defined $commit_text) {
@commit_lines = @$commit_text;
} else {
- open my $fd, "-|", "$gitbin/git-cat-file commit $commit_id" or return;
- @commit_lines = map { chomp; $_ } <$fd>;
+ $/ = "\0";
+ open my $fd, "-|", "$gitbin/git-rev-list --header --parents --max-count=1 $commit_id" or return;
+ @commit_lines = split '\n', <$fd>;
close $fd or return;
+ $/ = "\n";
+ pop @commit_lines;
+ }
+ my $header = shift @commit_lines;
+ if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
+ return;
}
+ ($co{'id'}, my @parents) = split ' ', $header;
+ $co{'parents'} = \@parents;
+ $co{'parent'} = $parents[0];
while (my $line = shift @commit_lines) {
last if $line eq "\n";
if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
$co{'tree'} = $1;
- } elsif ($line =~ m/^parent ([0-9a-fA-F]{40})$/) {
- push @parents, $1;
} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
$co{'author'} = $1;
$co{'author_epoch'} = $2;
}
}
if (!defined $co{'tree'}) {
- return undef
+ return;
};
- $co{'id'} = $commit_id;
- $co{'parents'} = \@parents;
- $co{'parent'} = $parents[0];
- $co{'comment'} = \@commit_lines;
+
foreach my $title (@commit_lines) {
if ($title ne "") {
$co{'title'} = chop_str($title, 80, 5);
last;
}
}
+ # remove added spaces
+ foreach my $line (@commit_lines) {
+ $line =~ s/^ //;
+ }
+ $co{'comment'} = \@commit_lines;
my $age = time - $co{'committer_epoch'};
$co{'age'} = $age;
print $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"},
"<b>" . escapeHTML($co{'title'}) . "</b>");
}
- "</td>\n" .
+ print "</td>\n" .
"<td class=\"link\">" .
$cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$commit"}, "commitdiff") .
print "</table\n>";
}
- my $branchlist = git_read_refs("refs/heads");
- if (defined @$branchlist) {
+ my $headlist = git_read_refs("refs/heads");
+ if (defined @$headlist) {
print "<div>\n" .
- $cgi->a({-href => "$my_uri?p=$project;a=branches", -class => "title"}, "branches") .
+ $cgi->a({-href => "$my_uri?p=$project;a=heads", -class => "title"}, "heads") .
"</div>\n";
my $i = 16;
print "<table cellspacing=\"0\">\n";
my $alternate = 0;
- foreach my $entry (@$branchlist) {
+ foreach my $entry (@$headlist) {
my %tag = %$entry;
if ($alternate) {
print "<tr class=\"dark\">\n";
"</td>\n" .
"</tr>";
} else {
- print "<td>" . $cgi->a({-href => "$my_uri?p=$project;a=branches"}, "...") . "</td>\n" .
+ print "<td>" . $cgi->a({-href => "$my_uri?p=$project;a=heads"}, "...") . "</td>\n" .
"</tr>";
last;
}
git_footer_html();
}
-sub git_branches {
+sub git_heads {
my $head = git_read_hash("$project/HEAD");
git_header_html();
print "<div class=\"page_nav\">\n" .
$hash = git_get_hash_by_path($base, $file_name, "blob");
}
open my $fd, "-|", "$gitbin/git-cat-file blob $hash" or die_error(undef, "Open failed.");
- my $base = $file_name || "";
git_header_html();
if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
print "<div class=\"page_nav\">\n" .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base"}, "commit") .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash_base"}, "commitdiff") .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$hash_base"}, "tree") . "<br/>\n";
- print $cgi->a({-href => "$my_uri?p=$project;a=blob_plain;h=$hash"}, "plain") . "<br/>\n" .
- "</div>\n";
- print "<div>" .
+ if (defined $file_name) {
+ print $cgi->a({-href => "$my_uri?p=$project;a=blob_plain;h=$hash;f=$file_name"}, "plain") . "<br/>\n";
+ } else {
+ print $cgi->a({-href => "$my_uri?p=$project;a=blob_plain;h=$hash"}, "plain") . "<br/>\n";
+ }
+ print "</div>\n".
+ "<div>" .
$cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash_base", -class => "title"}, escapeHTML($co{'title'})) .
"</div>\n";
} else {
}
sub git_blob_plain {
- print $cgi->header(-type => "text/plain", -charset => 'utf-8');
+ my $save_as = "$hash.txt";
+ if (defined $file_name) {
+ $save_as = $file_name;
+ }
+ print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"$save_as\"");
open my $fd, "-|", "$gitbin/git-cat-file blob $hash" or return;
undef $/;
print <$fd>;
open my $fd, "-|", "$gitbin/git-diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed.");
@difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading diff-tree failed.");
- git_header_html();
+
+ # non-textual hash id's can be cached
+ my $expires;
+ if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = "+1d";
+ }
+ git_header_html(undef, $expires);
print "<div class=\"page_nav\">\n" .
$cgi->a({-href => "$my_uri?p=$project;a=summary"}, "summary") .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash"}, "shortlog") .
"</tr>\n";
print "<tr><td>committer</td><td>" . escapeHTML($co{'committer'}) . "</td></tr>\n";
print "<tr><td></td><td> $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n";
- print "<tr><td>commit</td><td style=\"font-family:monospace\">$hash</td></tr>\n";
+ print "<tr><td>commit</td><td style=\"font-family:monospace\">$co{'id'}</td></tr>\n";
print "<tr>" .
"<td>tree</td>" .
"<td style=\"font-family:monospace\">" .
$mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777);
}
print "<td>" .
- $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hp=$hash;f=$file", -class => "list"}, escapeHTML($file)) . "</td>\n" .
+ $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hb=$hash;f=$file", -class => "list"}, escapeHTML($file)) . "</td>\n" .
"<td><span style=\"color: #008000;\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" .
"<td class=\"link\">" . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id;hb=$hash;f=$file"}, "blob") . "</td>\n";
} elsif ($status eq "D") {
my (@difftree) = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading diff-tree failed.");
- git_header_html();
+ # non-textual hash id's can be cached
+ my $expires;
+ if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = "+1d";
+ }
+ git_header_html(undef, $expires);
print "<div class=\"page_nav\">\n" .
$cgi->a({-href => "$my_uri?p=$project;a=summary"}, "summary") .
" | " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash"}, "shortlog") .
}
close $fd;
- print $cgi->header(-type => "text/plain", -charset => 'utf-8');
+ print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
my %co = git_read_commit($hash);
my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
my $comment = $co{'comment'};
my $alternate = 0;
if ($commit_search) {
$/ = "\0";
- open my $fd, "-|", "$gitbin/git-rev-list --header $hash";
+ open my $fd, "-|", "$gitbin/git-rev-list --header --parents $hash" or next;
while (my $commit_text = <$fd>) {
if (!grep m/$searchtext/i, $commit_text) {
next;
next;
}
my @commit_lines = split "\n", $commit_text;
- my $commit = shift @commit_lines;
- my %co = git_read_commit($commit, \@commit_lines);
+ my %co = git_read_commit(undef, \@commit_lines);
if (!%co) {
next;
}
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
"<td>" .
- $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"}, "<b>" . escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
+ $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}", -class => "list"}, "<b>" . escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
my $comment = $co{'comment'};
foreach my $line (@$comment) {
if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
}
print "</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$commit"}, "tree");
+ $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}"}, "commit") .
+ " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}"}, "tree");
print "</td>\n" .
"</tr>\n";
}