#!/usr/bin/perl -T
#
# cvsweb - a CGI interface to CVS trees.
#
# Written in their spare time by
# Bill Fenner The server on which the CVS tree lives is probably down. Please try again in a few minutes.');
}
#
# See if the module is in our forbidden list.
#
$where =~ m:([^/]*):;
$module = $1;
if ($module && &forbidden_module($module)) {
fatal("403 Forbidden", 'Access to %s forbidden.', $where);
}
#
# Handle tarball downloads before any headers are output.
#
if ($input{tarball}) {
fatal('403 Forbidden', 'Downloading tarballs is prohibited.')
unless $allow_tar;
my ($module) = ($where =~ m,^/?(.*),); # untaint
$module =~ s,/([^/]*)$,,;
my ($ext) = ($1 =~ /(\.t(?:ar\.)?gz|\.zip)$/);
my ($basedir) = ($module =~ m,([^/]+)$,);
if ($basedir eq '' || $module eq '') {
fatal('500 Internal Error',
'You cannot download the top level directory.');
}
my $istar = ($ext eq '.tar.gz' || $ext eq '.tgz');
if ($istar) {
fatal('500 Internal Error', 'tar command not found.') unless $CMD{tar};
fatal('500 Internal Error', 'gzip command not found.') unless $CMD{gzip};
}
my $iszip = ($ext eq '.zip');
if ($iszip && !$CMD{zip}) {
fatal('500 Internal Error', 'zip command not found.');
}
my $tmpexportdir;
eval {
local $SIG{__DIE__};
# Don't use the CLEANUP argument to tempdir() here, since we might be under
# mod_perl (the process runs for a long time), unlink explicitly later.
$tmpexportdir = tempdir('.cvsweb.XXXXXXXX', TMPDIR => 1);
};
if ($@) {
fatal('500 Internal Error', 'Unable to make temporary directory: %s', $@);
}
if (!chdir($tmpexportdir)) {
fatal('500 Internal Error',
"Can't cd to temporary directory %s: %s", $tmpexportdir, $!);
}
my @fatal;
my $tag = $input{only_with_tag} || 'HEAD';
$tag = 'HEAD' if ($tag eq 'MAIN');
if (system($CMD{cvs},
@cvs_options, '-Qd', $cvsroot, 'export', '-r', $tag, $module)) {
@fatal = ('500 Internal Error', 'cvs export failure: %s: %s', $!, $module);
} elsif ($istar || $iszip) {
$| = 1; # Essential to get the buffering right.
local (*TAR_OUT);
my (@cmd, $ctype);
if ($istar) {
my @tar = ($CMD{tar}, @tar_options, '-cf', '-', $basedir);
my @gzip = ($CMD{gzip}, @gzip_options, '-c');
push(@cmd, \@tar, '|', \@gzip);
$ctype = 'application/x-gzip';
} elsif ($iszip) {
my @zip = ($CMD{zip}, @zip_options, '-r', '-', $basedir);
push(@cmd, \@zip, \'');
$ctype = 'application/zip';
}
push(@cmd, '>pipe', \*TAR_OUT, timeout(30));
my ($h, $err) = startproc(@cmd);
if ($h) {
print "Content-Type: $ctype\r\n\r\n";
local $/ = undef;
print Current directory: ", &clickablePath($where, 0), " Current tag: ", htmlquote($input{only_with_tag}), "%s
',
$ENV{PATH_INFO});
}
if ($ENV{SCRIPT_NAME}) {
($scriptname) = ($ENV{SCRIPT_NAME} =~ VALID_PATH)
or fatal('500 Internal Error',
'Illegal SCRIPT_NAME in environment: %s
',
$ENV{SCRIPT_NAME});
}
$pathinfo = '' unless defined($pathinfo);
$scriptname = '' unless defined($scriptname);
$checkoutMagic = "~checkout~";
$where = $pathinfo;
$doCheckout = ($where =~ m|^/$checkoutMagic/|);
$where =~ s|^/$checkoutMagic/|/|;
$where =~ s|^/||;
$scriptname =~ s|^/*|/|;
# Let's workaround thttpd's stupidity..
if ($scriptname =~ m|/$|) {
$pathinfo .= '/';
my $re = quotemeta $pathinfo;
$scriptname =~ s/$re$//;
}
$scriptwhere = $scriptname;
$scriptwhere .= '/' . urlencode($where);
$where = '/' if ($where eq '');
# In text-based browsers, it's very annoying to have two links per file;
# skip linking the image for them.
$Browser = $ENV{HTTP_USER_AGENT} || '';
$is_links = ($Browser =~ m`^E?Links `);
$is_lynx = ($Browser =~ m`^Lynx/`i);
$is_w3m = ($Browser =~ m`^w3m/`i);
$is_msie = ($Browser =~ m`MSIE`);
$is_mozilla3 = ($Browser =~ m`^Mozilla/[3-9]`);
$is_textbased = ($is_links || $is_lynx || $is_w3m);
$nofilelinks = $is_textbased;
# newer browsers accept gzip content encoding
# and state this in a header
# (netscape did always but didn't state it)
# It has been reported that these
# braindamaged MS-Internet Exploders claim that they
# accept gzip .. but don't in fact and
# display garbage then :-/
# Turn off gzip if running under mod_perl and no zlib is available,
# piping does not work as expected inside the server.
$maycompress = (
((defined($ENV{HTTP_ACCEPT_ENCODING})
&& $ENV{HTTP_ACCEPT_ENCODING} =~ /gzip/)
|| $is_mozilla3)
&& !$is_msie
&& !(defined($ENV{MOD_PERL}) && !$has_zlib)
);
# Variables that will be sticky in all constructed links/query strings.
@stickyvars =
qw(cvsroot hideattic ignorecase sortby logsort f only_with_tag ln);
if (-f $config) {
do "$config"
or fatal("500 Internal Error",
'Error in loading configuration file: %s
%s
',
$config, $@);
} else {
fatal("500 Internal Error",
'Configuration not found. Set the variable $config
in cvsweb.cgi to your cvsweb.conf configuration file first.');
}
# Try to find a readable dir where we can cd into. Some abs_path()
# implementations as well as various cvs operations require such a dir to
# work properly.
{
local $^W = 0;
if (!-r cwd()) {
for my $dir (tmpdir(), rootdir()) {
last if (-r $dir && chdir($dir));
}
}
}
$CSS = $cssurl ?
sprintf("\n",
htmlquote($cssurl)) : '';
# --- input parameters
my %query = ();
if (defined($ENV{QUERY_STRING})) {
for my $p (split(/[;&]+/, $ENV{QUERY_STRING})) {
next unless $p;
$p =~ y/+/ /;
my ($key, $val) = split(/=/, $p, 2);
next unless defined($key);
$val = 1 unless defined($val);
($key = uri_unescape($key)) =~ /[[:graph:]]/o or next;
($val = uri_unescape($val)) =~ /[[:graph:]]/o or next;
$query{$key} = $val;
}
}
undef %input;
my $t;
for my $p (qw(graph hideattic hidecvsroot hidenonreadable ignorecase ln copt
makeimage options tarball)) {
$t = $query{$p};
if (defined($t)) {
($input{$p}) = ($t =~ /^([01]|on)$/o)
or fatal('500 Internal Error',
'Invalid boolean value: %s=%s
', $p, $t);
}
}
for my $p (qw(annotate r1 r2 rev tr1 tr2)) {
$t = $query{$p};
if (defined($t)) {
if (($p eq 'r1' || $p eq 'r2') && $t eq 'text') {
# Special case for the "Use text field" option in the log view diff form.
$input{$p} = $t;
next;
}
my ($rev, $tag) = split(/:/, $t, 2);
($input{$p}) = ($rev =~ /^(\d+(?:\.\d+)+)$/o)
or fatal('500 Internal Error',
'Invalid revision: %s=%s
', $p, $t);
if (defined($tag)) {
($tag) = ($tag =~ VALID_TAG1)
or fatal('500 Internal Error',
'Invalid revision: %s=%s
', $p, $t);
($tag) = ($tag =~ VALID_TAG2)
or fatal('500 Internal Error',
'Invalid revision: %s=%s
', $p, $t);
$input{$p} .= ':' . $tag;
}
}
}
$t = defined($query{only_with_tag}) ?
$query{only_with_tag} : $query{only_on_branch}; # Backwards compatibility.
if (defined($t)) {
($input{cvsroot}) = ($t =~ VALID_TAG1)
or fatal('500 Internal Error',
'Invalid tag/branch name: %s
', $t);
($input{cvsroot}) = ($t =~ VALID_TAG2)
or fatal('500 Internal Error',
'Invalid tag/branch name: %s
', $t);
}
$t = $query{logsort};
if (defined($t)) {
($input{logsort}) = ($t =~ /^(cvs|date|rev)$/o)
or fatal('500 Internal Error',
'Unsupported log sort key: %s
', $t);
}
$t = $query{f};
if (defined($t)) {
($input{f}) = ($t =~ /^([hH]|[ucs]c?)$/o)
or fatal('500 Internal Error',
'Unsupported diff format: %s
', $t);
}
$t = $query{sortby};
if (defined($t)) {
($input{sortby}) = ($t =~ /^(file|date|rev|author|log)$/o)
or fatal('500 Internal Error',
'Unsupported dir sort key: %s
', $t);
}
$t = $query{'content-type'};
if (defined($t)) {
($input{'content-type'}) = ($t =~ /^([-0-9A-Za-z]+\/[-0-9A-Za-z\.]+)$/o)
or fatal('500 Internal Error',
'Unsupported content type: %s
', $t);
}
$t = $query{cvsroot};
if (defined($t)) {
($input{cvsroot}) = ($t =~ /^([[:print:]]+)$/o)
or fatal('500 Internal Error',
'Invalid symbolic CVS root name: %s
', $t);
}
$t = $query{path};
if (defined($t)) {
($input{path}) = ($t =~ VALID_PATH)
or fatal('500 Internal Error',
'Invalid path: %s
', $t);
}
undef($t);
undef(%query);
# --- end input parameters
#
# CVS roots
#
my $rootfound = 0;
for (my $i = 0; $i < scalar(@CVSrepositories); $i += 2) {
my $key = $CVSrepositories[$i];
my ($descr, $cvsroot) = @{$CVSrepositories[$i+1]};
unless (-d $cvsroot) {
warn("Root '$cvsroot' defined in \@CVSrepositories is not a directory, " .
'entry ignored');
next;
}
$rootfound ||= 1;
$cvstreedefault = $key unless defined($cvstreedefault);
$CVSROOTdescr{$key} = $descr;
$CVSROOT{$key} = $cvsroot;
push(@CVSROOT, $key);
}
unless ($rootfound) {
fatal('500 Internal Error',
'No valid CVS roots found! See @CVSrepositories
in ' .
'your configuration file (%s
).',
$config);
}
undef $rootfound;
#
# Default CVS root
#
if (!defined($CVSROOT{$cvstreedefault})) {
fatal("500 Internal Error",
'$cvstreedefault
points to a repository (%s) not ' .
'defined in @CVSrepositories
in your configuration ' .
'file (%s
).',
$cvstreedefault,
$config);
}
$DEFAULTVALUE{'cvsroot'} = $cvstreedefault;
while (my ($key, $defval) = each %DEFAULTVALUE) {
# Replace not given parameters with defaults.
next unless (defined($defval) && $defval =~ /\S/ && !defined($input{$key}));
# Empty checkboxes in forms return nothing, so we define a helper variable
# in these forms (copt) which indicates that we just set parameters with a
# checkbox.
if ($input{copt}) {
# 'copt' is set -> the result of empty input checkbox
# -> set to zero (disable) if default is a boolean (0|1).
$input{$key} = 0 if ($defval eq '0' || $defval eq '1');
} else {
# 'copt' isn't set --> empty input is not the result
# of empty input checkbox --> set default.
$input{$key} = $defval;
}
}
$barequery = "";
my @barequery;
foreach (@stickyvars) {
# construct a query string with the sticky non default parameters set
if (defined($input{$_})
&& !(defined($DEFAULTVALUE{$_}) && $input{$_} eq $DEFAULTVALUE{$_}))
{
push(@barequery, join('=', uri_escape($_), uri_escape($input{$_})));
}
}
if ($allow_enscript) {
push(@DIFFTYPES, qw(uc cc sc));
@DIFFTYPES{qw(uc cc sc)} = (
{
'descr' => 'unified, colored',
'opts' => ['-u'],
'colored' => 0,
},
{
'descr' => 'context, colored',
'opts' => ['-c'],
'colored' => 0,
},
{
'descr' => 'side by side, colored',
'opts' => ['--side-by-side', '--width=164'],
'colored' => 0,
},
);
} else {
# No Enscript -> respect difftype, but don't offer colorization.
if ($input{f} && $input{f} =~ /^([ucs])c$/) {
$input{f} = $1;
}
}
# is there any query ?
if (@barequery) {
$barequery = join (';', @barequery);
$query = "?$barequery";
$barequery = ";$barequery";
} else {
$query = "";
}
undef @barequery;
if (defined($input{'path'})) {
redirect("$scriptname/$input{path}$query");
}
# get actual parameters
{
my $sortby = $input{sortby} || 'file';
$bydate = 0;
$byrev = 0;
$byauthor = 0;
$bylog = 0;
$byfile = 0;
if ($sortby eq 'date') {
$bydate = 1;
} elsif ($sortby eq 'rev') {
$byrev = 1;
} elsif ($sortby eq 'author') {
$byauthor = 1;
} elsif ($sortby eq 'log') {
$bylog = 1;
} else {
$byfile = 1;
}
}
$defaultDiffType = $input{'f'};
$logsort = $input{'logsort'};
# alternate CVS-Tree, configured in cvsweb.conf
if ($input{'cvsroot'} && $CVSROOT{$input{'cvsroot'}}) {
$cvstree = $input{'cvsroot'};
} else {
$cvstree = $cvstreedefault;
}
$cvsroot = $CVSROOT{$cvstree};
# create icons out of description
foreach my $k (keys %ICONS) {
my ($itxt, $ipath, $iwidth, $iheight) = @{$ICONS{$k}};
no strict 'refs';
if ($ipath) {
${"${k}icon"} =
sprintf('',
hrefquote($ipath), htmlquote($itxt), $iwidth, $iheight);
} else {
${"${k}icon"} = $itxt;
}
}
my $config_cvstree = "$config-$cvstree";
# Do some special configuration for cvstrees
if (-f $config_cvstree) {
do "$config_cvstree"
or fatal("500 Internal Error",
'Error in loading configuration file: %s
%s
',
$config_cvstree, $@);
}
undef $config_cvstree;
$re_prcategories = '(?:' . join ('|', @prcategories) . ')' if @prcategories;
$re_prkeyword = quotemeta($prkeyword) if defined($prkeyword);
$prcgi .= '%s' if defined($prcgi) && $prcgi !~ /%s/;
$fullname = "$cvsroot/$where";
my $rewrite = 0;
if ($pathinfo =~ m|//|) {
$pathinfo =~ y|/|/|s;
$rewrite = 1;
}
if (-d $fullname) {
if ($pathinfo !~ m|/$|) {
$pathinfo .= '/';
$rewrite = 1;
}
} else {
if ($pathinfo =~ m|/$|) {
chop $pathinfo;
$rewrite = 1;
}
}
if ($rewrite) {
redirect($scriptname . urlencode($pathinfo) . $query);
}
undef $rewrite;
if (!-d $cvsroot) {
fatal("500 Internal Error",
'$CVSROOT not found!
\n";
my $infocols = 1;
printf(<
EOF
printf(' \n";
my $dirrow = 0;
my $i;
lookingforattic:
for ($i = 0; $i <= $#dir; $i++) {
if ($dir[$i] eq "Attic") {
last lookingforattic;
}
}
if (!$input{'hideattic'}
&& ($i <= $#dir)
&& opendir($dh, $fullname . '/Attic'))
{
splice(@dir, $i, 1, grep((s|^|Attic/|, !m|/\.|), readdir($dh)));
closedir($dh);
}
my $hideAtticToggleLink =
$input{'hideattic'}
? ''
: &link('[Hide]', sprintf('./%s#dirlist', &toggleQuery('hideattic')));
# Sort without the Attic/ pathname.
# place directories first
my $attic;
my $url;
my $fileurl;
my $filesexists;
my $filesfound;
foreach my $file (sort { &fileSortCmp } @dir) {
next if ($file eq curdir());
# ignore CVS lock and stale NFS files
next if ($file =~ /^\#cvs\.|^,|^\.nfs/); # \# for XEmacs cperl-mode...
# Check whether to show the CVSROOT path
next if ($input{'hidecvsroot'} && $file eq 'CVSROOT');
# Check whether the module is in the restricted list
next if ($file && &forbidden_module($file));
# Is it a directory?
my $isdir = -d "$fullname/$file";
# Ignore non-readable files and directories?
next if ($input{'hidenonreadable'} && (! -r _ || ($isdir && ! -x _)));
if ($file =~ s|^Attic/||) {
$attic = ' (in the Attic) ' . $hideAtticToggleLink;
} else {
$attic = '';
}
if ($file eq updir() || $isdir) {
next if ($file eq updir() && $where eq '/');
my ($rev, $date, $log, $author, $filename, $keywordsubst) =
@{$fileinfo{$file}} if (defined($fileinfo{$file}));
printf "', ($byfile ? ' class="sorted"' : ''));
if ($byfile) {
print 'File';
} else {
print &link('File',
sprintf('./%s#dirlist', toggleQuery('sortby', 'file')));
}
print " \n";
# Do not display the other column headers if we do not have any files
# with revision information.
if (scalar(%fileinfo)) {
$infocols++;
printf('', ($byrev ? ' class="sorted"' : ''));
if ($byrev) {
print 'Rev.';
} else {
print &link('Rev.',
sprintf('./%s#dirlist', toggleQuery('sortby', 'rev')));
}
print " \n";
$infocols++;
printf('', ($bydate ? ' class="sorted"' : ''));
if ($bydate) {
print 'Age';
} else {
print &link('Age',
sprintf('./%s#dirlist', toggleQuery('sortby', 'date')));
}
print " \n";
if ($show_author) {
$infocols++;
printf('', ($byauthor ? ' class="sorted"' : ''));
if ($byauthor) {
print 'Author';
} else {
print
&link('Author',
sprintf('./%s#dirlist', toggleQuery('sortby', 'author')));
}
print " \n";
}
$infocols++;
printf('', ($bylog ? ' class="sorted"' : ''));
if ($bylog) {
print 'Last log entry';
} else {
print &link('Last log entry',
sprintf('./%s#dirlist', toggleQuery('sortby', 'log')));
}
print " \n";
} elsif ($use_descriptions) {
print "Description \n";
$infocols++;
}
print "\n \n";
$dirrow++;
} elsif ($file =~ s/,v$//) {
# Skip forbidden files now so we'll give no hint
# about their existence. This should probably have
# been done earlier, but it's straightforward here.
next if forbidden_file("$fullname/$file");
$fileurl = ($attic ? 'Attic/' : '') . urlencode($file);
$url = './' . $fileurl . $query;
$filesexists++;
next if (!defined($fileinfo{$file}));
my ($rev, $date, $log, $author, $filename, $keywordsubst) =
@{$fileinfo{$file}};
my $isbinary = $keywordsubst eq 'b' ? 1 : 0;
$filesfound++;
printf "",
($dirrow % 2) ? 'even' : 'odd';
if ($file eq updir()) {
$url = "../$query";
if ($nofilelinks) {
print $backicon;
} else {
print &link($backicon, $url);
}
print ' ', &link("Parent Directory", $url);
} else {
$url = './' . urlencode($file) . "/$query";
print '';
if ($nofilelinks) {
print $diricon;
} else {
print &link($diricon, $url);
}
print ' ', &link(htmlquote("$file/"), $url), $attic;
if ($file eq "Attic") {
print " ";
print &link('[Don\'t hide]',
sprintf('./%s#dirlist', &toggleQuery('hideattic')));
}
}
# Show last change in dir
if ($filename) {
print " \n \n";
print readableTime(time() - $date, 0) if $date;
print " \n", htmlquote($author)
if $show_author;
print " \n";
$filename =~ s%^[^/]+/%%;
print &link(htmlquote("$filename/$rev"),
sprintf('%s/%s%s#rev%s',
uri_escape($file), uri_escape($filename),
$query, $rev)), '
';
if ($log) {
print htmlify(substr($log, 0, $shortLogLen), $allow_dir_extra);
print '...' if (length($log) > 80);
}
} else {
my $dwhere = ($where ne '/' ? $where : '') . $file;
if ($use_descriptions && defined $descriptions{$dwhere}) {
print '';
print $descriptions{$dwhere};
} elsif ($infocols > 1) {
# close the row with the appropriate number of
# columns, so that the vertical seperators are visible
my ($cols) = $infocols;
while ($cols > 1) {
print " \n ";
$cols--;
}
}
}
print " \n\n", ($dirrow % 2) ? 'even' : 'odd';
printf ' ";
$dirrow++;
}
print "\n";
}
print "\n";
if ((my $num = scalar(@unreadable)) && ! $input{hidenonreadable}) {
printf(<', $allow_cvsgraph ? '' : ' colspan="2"';
my $icon = $isbinary ? $binfileicon : $fileicon;
if ($nofilelinks) {
print $icon;
} else {
print &link($icon, $url);
}
print ' ', &link(htmlquote($file), $url), $attic;
print ' ', graph_link($fileurl) if $allow_cvsgraph;
print " \n";
download_link($fileurl, $rev, $rev,
viewable(getMimeType($file, $isbinary)) ?
'text/x-cvsweb-markup' : undef);
print " \n";
print readableTime(time() - $date, 0) if $date;
print " \n", htmlquote($author) if $show_author;
print " \n";
if ($log) {
print htmlify(substr($log, 0, $shortLogLen), $allow_dir_extra);
print '...' if (length $log > 80);
}
print " \n
%s
Error: ", sprintf($format, map(htmlquote($_), @args)), "
\n"; html_footer(); exit(1); } sub redirect($) { my ($url) = @_; print "Status: 301 Moved\r\n", "Location: $url\r\n"; html_header('Moved'); print "This document is located ", &link('here', $url), "
\n"; html_footer(); exit(1); } sub safeglob($) { my ($filename) = @_; (my $dirname = $filename) =~ s|/[^/]+$||; $filename =~ s|.*/||; my @results; my $dh = do { local (*DH); }; if (opendir($dh, $dirname)) { my $glob = $filename; my $t; # transform filename from glob to regex. Deal with: # [, {, ?, * as glob chars # make sure to escape all other regex chars $glob =~ s/([\.\(\)\|\+])/\\$1/g; $glob =~ s/\*/.*/g; $glob =~ s/\?/./g; $glob =~ s/{([^}]+)}/($t = $1) =~ s-,-|-g; "($t)"/eg; foreach (readdir($dh)) { if (/^${glob}$/ && $_ =~ VALID_PATH) { push(@results, catfile($dirname, $1)); # untaint } } closedir($dh); } return @results; } sub search_path($) { my ($command) = @_; for my $d (split (/:/, $command_path)) { return "$d/$command" if -x "$d/$command"; } return ''; } sub getMimeType($;$) { my ($fullname, $binary) = @_; $binary = ($keywordsubstitution && $keywordsubstitution =~ /b/) unless defined($binary); (my $suffix = $fullname) =~ s/^.*\.([^.]*)$/$1/; my $mimetype = $MTYPES{$suffix}; $mimetype ||= $MimeTypes->mimeTypeOf($fullname) if defined($MimeTypes); if (!$mimetype && $suffix ne '*' && -f $mime_types && -r _) { my $fh = do { local (*FH); }; if (open($fh, $mime_types)) { my $re = sprintf('^\s*(\S+\/\S+)\s.+\b%s\b', quotemeta($suffix)); while (my $line = <$fh>) { if ($line =~ $re) { $mimetype = $1; $MTYPES{$suffix} = $mimetype; last; } } close($fh); } else { warn("Can't open MIME types file $mime_types for reading: $!"); } } $mimetype ||= $MTYPES{'*'}; $mimetype ||= $binary ? 'application/octet-stream' : 'text/plain'; return $mimetype; } ############################### # read first lines like head(1) ############################### sub head($;$) { my ($fh, $linecount) = @_; $linecount ||= 10; my @buf; if ($linecount > 0) { for (my $i = 0; !eof($fh) && $i < $linecount; $i++) { push @buf, scalar <$fh>; } } else { @buf = <$fh>; } return @buf; } ############################### # scan vim and Emacs directives ############################### sub scan_directives(@) { my $ts = undef; for (@_) { $ts = $1 if /\b(?:ts|tabstop|tab-width)[:=]\s*([1-9]\d*)\b/; } ('tabstop' => $ts); } sub openOutputFilter() { return unless $output_filter; open(STDOUT, "|-") and return; # child of child open(STDERR, '>', devnull()); exec($output_filter) or exit -1; } ############################### # show Annotation ############################### sub doAnnotate($) { my ($rev) = @_; (my $pathname = $where) =~ s/(Attic\/)?[^\/]*$//; (my $filename = $where) =~ s/^.*\///; # This annotate version is based on the cvs annotate-demo Perl script by # Cyclic Software. It was written by Cyclic Software, # http://www.cyclic.com/, and is in the public domain. # We could abandon the use of rlog, rcsdiff and co using # the cvs server in a similiar way one day (..after rewrite). local (*CVS_IN, *CVS_OUT); my ($h, $err) = startproc([ $CMD{cvs}, @annotate_options, 'server' ], '"; } # prefetch several lines my @buf = head(*CVS_OUT); my %d = scan_directives(@buf); while (@buf || !eof(*CVS_OUT)) { $_ = @buf ? shift @buf :"; } html_footer(); finish($h); } ############################### # make Checkout ############################### sub doCheckout($$) { my ($fullname, $rev) = @_; $rev = undef if ($rev eq 'HEAD' || $rev eq '.'); # get mimetype my $mimetype = defined($input{'content-type'}) ? $input{'content-type'} : getMimeType($fullname); my $moddate = undef; my $revopt; if (defined($rev)) { $revopt = "-r$rev"; if ($use_moddate) { readLog($fullname, $rev); $moddate = $date{$rev}; } } else { $revopt = "-rHEAD"; if ($use_moddate) { readLog($fullname); $moddate = $date{$symrev{HEAD}}; } } my $cr = abs_path($cvsroot) || $cvsroot; # abs_path() taints when run as a CGI... if ($cr =~ VALID_PATH) { $cr = $1; } else { fatal('500 Internal Error', 'Illegal CVS root:; my @words = split; # Adding one is for the (single) space which follows $words[0]. my $rest = substr($_, length($words[0]) + 1); if ($words[0] eq "E") { next; } elsif ($words[0] eq "M") { $lineNr++; (my $lrev = substr($_, 2, 13)) =~ y/ //d; (my $lusr = substr($_, 16, 9)) =~ y/ //d; my $line = substr($_, 36); my $isCurrentRev = ($rev eq $lrev); # we should parse the date here .. if ($lrev eq $oldLrev) { $revprint = sprintf('%-8s', ''); } else { $revprint = sprintf('%-8s', $lrev); $revprint =~ s`\S+`&link($&, "$scriptwhere$query#rev$&")`e; # ` $oldLusr = ''; } if ($lusr eq $oldLusr) { $usrprint = ''; } else { $usrprint = $lusr; } $oldLrev = $lrev; $oldLusr = $lusr; # Set bold for text-based browsers only - graphical # browsers show bold fonts a bit wider than regular fonts, # so it looks irregular. print "" if ($isCurrentRev && $is_textbased); $usrprint = sprintf('%-8s', $usrprint); printf '%s%s %s %4d:', $revprint, $isCurrentRev ? '!' : ' ', htmlquote($usrprint), $lineNr; print spacedHtmlText($line, $d{'tabstop'}); print "" if ($isCurrentRev && $is_textbased); } elsif ($words[0] eq "ok") { # We could complain about any text received after this, like the # CVS command line client. But for simplicity, we don't. } elsif ($words[0] eq "error") { fatal("500 Internal Error", 'Error occured during annotate: %s', $_); } } if ($annTable) { print ""; } else { print "
%s
', $cr);
}
# Use abs_path() to work around a bug of cvs -p; expand symlinks if we can.
my @cmd = ($CMD{cvs}, @cvs_options, '-d', $cr, 'co', '-p', $revopt, $where);
local (*CVS_OUT, *CVS_ERR);
my ($h, $err) =
startproc(\@cmd, \"", '>pipe', \*CVS_OUT, '2>pipe', \*CVS_ERR,timeout(30));
fatal('500 Internal Error', $err) unless $h;
if (eof(CVS_ERR)) {
finish($h);
fatal("404 Not Found", '%s is not (any longer) pertinent', $where);
}
#===================================================================
#Checking out squid/src/ftp.c
#RCS: /usr/src/CVS/squid/src/ftp.c,v
#VERS: 1.1.1.28.6.2
#***************
# Parse CVS header
my ($revision, $filename, $cvsheader);
$filename = "";
while (