[BACK]Return to cvsweb.cgi CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / cvsweb

Diff for /cvsweb/cvsweb.cgi between version 1.1.1.13 and 1.14

version 1.1.1.13, 2000/12/07 12:45:50 version 1.14, 1998/01/12 16:27:14
Line 1 
Line 1 
 #!/usr/bin/perl5 -ws  #!/usr/bin/perl -s
 #  #
 # cvsweb - a CGI interface to CVS trees.  # cvsweb - a CGI interface to the CVS tree.
 #  #
 # Written in their spare time by  # Written by Bill Fenner <fenner@parc.xerox.com> on his own time.
 #             Bill Fenner      <fenner@FreeBSD.org>   (original work)  # Insert BSD copyright here.
 # extended by Henner Zeller    <zeller@think.de>,  
 #             Henrik Nordstrom <hno@hem.passagen.se>  
 #             Ken Coar         <coar@Apache.Org>  
 #             Dick Balaska     <dick@buckosoft.com>  
 #             Akinori MUSHA    <knu@FreeBSD.org>  
 #             Jens-Uwe Mager   <jum@helios.de>  
 #  #
 # Based on:  #HTTP_USER_AGENT: Mozilla/1.1N (X11; I; SunOS 4.1.3_U1 sun4m) via proxy gateway CERN-HTTPD/3.0 libwww/2.17
 # * Bill Fenners cvsweb.cgi revision 1.28 available from:  #SERVER_NAME: www.freebsd.org
 #   http://www.FreeBSD.org/cgi/cvsweb.cgi/www/en/cgi/cvsweb.cgi  #QUERY_STRING: baz
   #SCRIPT_FILENAME: /usr/local/www/cgi-bin/env.pl
   #SERVER_PORT: 80
   #HTTP_ACCEPT: */*, image/gif, image/x-xbitmap, image/jpeg
   #SERVER_PROTOCOL: HTTP/1.0
   #HTTP_COOKIE: s=beta26429821397802167
   #PATH_INFO: /foo/bar
   #REMOTE_ADDR: 13.1.64.94
   #DOCUMENT_ROOT: /usr/local/www/data/
   #PATH: /sbin:/bin:/usr/sbin:/usr/bin
   #PATH_TRANSLATED: /usr/local/www/data//foo/bar
   #GATEWAY_INTERFACE: CGI/1.1
   #REQUEST_METHOD: GET
   #SCRIPT_NAME: /cgi-bin/env.pl
   #SERVER_SOFTWARE: Apache/1.0.0
   #REMOTE_HOST: beta.xerox.com
   #SERVER_ADMIN: webmaster@freebsd.org
 #  #
 # Copyright (c) 1996-1998 Bill Fenner  require 'timelocal.pl';
 #           (c) 1998-1999 Henner Zeller  require 'ctime.pl';
 #           (c) 1999      Henrik Nordstrom  
 #           (c) 2000      Akinori MUSHA  
 # All rights reserved.  
 #  
 # Redistribution and use in source and binary forms, with or without  
 # modification, are permitted provided that the following conditions  
 # are met:  
 # 1. Redistributions of source code must retain the above copyright  
 #    notice, this list of conditions and the following disclaimer.  
 # 2. Redistributions in binary form must reproduce the above copyright  
 #    notice, this list of conditions and the following disclaimer in the  
 #    documentation and/or other materials provided with the distribution.  
 #  
 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND  
 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE  
 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE  
 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE  
 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL  
 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS  
 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)  
 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT  
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY  
 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  
 # SUCH DAMAGE.  
 #  
 # $zId: cvsweb.cgi,v 1.104 2000/11/01 22:05:12 hnordstrom Exp $  
 # $kId: cvsweb.cgi,v 1.41 2000/12/06 18:19:12 knu Exp $  
 #  
 ###  
   
 use strict;  $hsty_base = "";
   require 'cgi-style.pl';
   #&get_the_source;
   
 use vars qw (  %CVSROOT = (
     $config $allow_version_select $verbose              'freebsd', '/home/ncvs',
     %CVSROOT %CVSROOTdescr %MIRRORS %DEFAULTVALUE %ICONS %MTYPES              'openbsd', '/home/OpenBSD/cvs',
     @DIFFTYPES %DIFFTYPES @LOGSORTKEYS %LOGSORTKEYS              'learn', '/c/learncvs',
     %alltags @tabcolors %fileinfo %tags @branchnames %nameprinted              );
     %symrev %revsym @allrevisions %date %author @revdisplayorder  
     @revisions %state %difflines %log %branchpoint @revorder  
     $prcgi @prcategories $prcategories $mancgi  
     $checkoutMagic $doCheckout $scriptname $scriptwhere  
     $where $pathinfo $Browser $nofilelinks $maycompress @stickyvars  
     %funcline_regexp $is_mod_perl  
     $is_links $is_lynx $is_w3m $is_msie $is_mozilla3 $is_textbased  
     %input $query $barequery $sortby $bydate $byrev $byauthor  
     $bylog $byfile $defaultDiffType $logsort $cvstree $cvsroot  
     $mimetype $charset $defaultTextPlain $defaultViewable  
     $allow_compress $GZIPBIN $backicon $diricon $fileicon  
     $fullname $newname $cvstreedefault  
     $body_tag $body_tag_for_src $logo $defaulttitle $address  
     $long_intro $short_instruction $shortLogLen  
     $show_author $dirtable $tablepadding $columnHeaderColorDefault  
     $columnHeaderColorSorted $hr_breakable $showfunc $hr_ignwhite  
     $hr_ignkeysubst $diffcolorHeading $diffcolorEmpty $diffcolorRemove  
     $diffcolorChange $diffcolorAdd $diffcolorDarkChange $difffontface  
     $difffontsize $inputTextSize $mime_types $allow_annotate  
     $allow_markup $use_java_script $open_extern_window  
     $extern_window_width $extern_window_height $edit_option_form  
     $show_subdir_lastmod $show_log_in_markup $v  
     $navigationHeaderColor $tableBorderColor $markupLogColor  
     $tabstop $state $annTable $sel $curbranch @HideModules  
     $module $use_descriptions %descriptions @mytz $dwhere $moddate  
     $use_moddate $has_zlib $gzip_open  
     $LOG_FILESEPARATOR $LOG_REVSEPARATOR  
 );  
   
 sub printDiffSelect($);  $cvstreedefault = 'freebsd';
 sub printDiffLinks($$);  $cvstree = $cvstreedefault;
 sub printLogSortSelect($);  $cvsroot = $CVSROOT{"$cvstree"} || "/home/ncvs";
 sub findLastModifiedSubdirs(@);  
 sub htmlify_sub(&$);  
 sub htmlify($;$);  
 sub spacedHtmlText($;$);  
 sub link($$);  
 sub revcmp($$);  
 sub fatal($$);  
 sub redirect($);  
 sub safeglob($);  
 sub getMimeTypeFromSuffix($);  
 sub head($;$);  
 sub scan_directives(@);  
 sub doAnnotate($$);  
 sub doCheckout($$);  
 sub cvswebMarkup($$$);  
 sub viewable($);  
 sub doDiff($$$$$$);  
 sub getDirLogs($$@);  
 sub readLog($;$);  
 sub printLog($;$);  
 sub doLog($);  
 sub flush_diff_rows($$$$);  
 sub human_readable_diff($);  
 sub navigateHeader($$$$$);  
 sub plural_write($$);  
 sub readableTime($$);  
 sub clickablePath($$);  
 sub chooseCVSRoot();  
 sub chooseMirror();  
 sub fileSortCmp();  
 sub download_url($$;$);  
 sub download_link($$$;$);  
 sub toggleQuery($$);  
 sub urlencode($);  
 sub htmlquote($);  
 sub htmlunquote($);  
 sub http_header(;$);  
 sub html_header($);  
 sub html_footer();  
 sub link_tags($);  
 sub forbidden_module($);  
   
 ##### Start of Configuration Area ########  
 use Cwd;  
   
 # == EDIT this ==  $intro = "
 # Locations to search for user configuration, in order:  This is a WWW interface to the FreeBSD CVS tree.
 for (  You can browse the file hierarchy by picking directories
      $ENV{CVSWEB_CONFIG},  (which have slashes after them, e.g. <b>src/</b>).
      '/usr/local/etc/cvsweb.conf',  If you pick a file, you will see the revision history
      getcwd() . '/cvsweb.conf'  for that file.
     ) {  Selecting a revision number will download that revision of
     $config = $_ if defined($_) && -r $_;  the file.  There is a link at each revision to display
 }  diffs between that revision and the previous one, and
   a form at the bottom of the page that allows you to
   display diffs between arbitrary revisions.
   <p>
   If you would like to use this CGI script on your own web server and
   CVS tree, see <A HREF=\"http://www.freebsd.org/~fenner/cvsweb/\">
   the CVSWeb distribution site</A>.
   <p>
   Please send any suggestions, comments, etc. to
   <A HREF=\"mailto:fenner\@freebsd.org\">Bill Fenner &lt;fenner\@freebsd.org&gt;</A>
   ";
   $shortinstr = "
   Click on a directory to enter that directory. Click on a file to display
   its revision history and to get a
   chance to display diffs between revisions.
   ";
   
 # == Configuration defaults ==  
 # Defaults for configuration variables that shouldn't need  
 # to be configured..  
 $allow_version_select = 1;  
   
 ##### End of Configuration Area   ########  
   
 ######## Configuration variables #########  
 # These are defined to allow checking with perl -cw  
 %CVSROOT = %MIRRORS = %DEFAULTVALUE = %ICONS = %MTYPES =  
 %tags = %alltags = @tabcolors = ();  
 $cvstreedefault = $body_tag = $body_tag_for_src =  
 $logo = $defaulttitle = $address =  
 $long_intro = $short_instruction = $shortLogLen =  
 $show_author = $dirtable = $tablepadding = $columnHeaderColorDefault =  
 $columnHeaderColorSorted = $hr_breakable = $showfunc = $hr_ignwhite =  
 $hr_ignkeysubst = $diffcolorHeading = $diffcolorEmpty = $diffcolorRemove =  
 $diffcolorChange = $diffcolorAdd = $diffcolorDarkChange = $difffontface =  
 $difffontsize = $inputTextSize = $mime_types = $allow_annotate =  
 $allow_markup = $use_java_script = $open_extern_window =  
 $extern_window_width = $extern_window_height = $edit_option_form =  
 $show_subdir_lastmod = $show_log_in_markup = $v =  
 $navigationHeaderColor = $tableBorderColor = $markupLogColor =  
 $tabstop = $use_moddate = $moddate = $gzip_open = undef;  
   
 $LOG_FILESEPARATOR = q/^={77}$/;  
 $LOG_REVSEPARATOR = q/^-{28}$/;  
   
 @DIFFTYPES = qw(h H u c s);  
 @DIFFTYPES{@DIFFTYPES} = (  
                           {  
                            'descr'   => 'colored',  
                            'opts'    => [ '-u' ],  
                            'colored' => 1,  
                           },  
                           {  
                            'descr'   => 'long colored',  
                            'opts'    => [ '--unified=15' ],  
                            'colored' => 1,  
                           },  
                           {  
                            'descr'   => 'unified',  
                            'opts'    => [ '-u' ],  
                            'colored' => 0,  
                           },  
                           {  
                            'descr'   => 'context',  
                            'opts'    => [ '-c' ],  
                            'colored' => 0,  
                           },  
                           {  
                            'descr'   => 'side by side',  
                            'opts'    => [ '--side-by-side', '--width=164' ],  
                            'colored' => 0,  
                           },  
                          );  
   
 @LOGSORTKEYS = qw(cvs date rev);  
 @LOGSORTKEYS{@LOGSORTKEYS} = (  
                               {  
                                'descr' => 'Not sorted',  
                               },  
                               {  
                                'descr' => 'Commit date',  
                               },  
                               {  
                                'descr' => 'Revision',  
                               },  
                              );  
   
   
 ##### End of configuration variables #####  
   
 use Time::Local;  
 use IPC::Open2;  
   
 # Check if the zlib C library interface is installed, and if yes  
 # we can avoid using the extra gzip process.  
 eval {  
         require Compress::Zlib;  
 };  
 $has_zlib = !$@;  
   
 $verbose = $v;  $verbose = $v;
 $checkoutMagic = "~checkout~";  ($where = $ENV{'PATH_INFO'}) =~ s|^/||;
 $pathinfo = defined($ENV{PATH_INFO}) ? $ENV{PATH_INFO} : '';  $where =~ s|/$||;
 $where = $pathinfo;  ($scriptname = $ENV{'SCRIPT_NAME'}) =~ s|^/?|/|;
 $doCheckout = ($where =~ /^\/$checkoutMagic/);  $scriptname =~ s|/$||;
 $where =~ s|^/($checkoutMagic)?||;  $scriptwhere = $scriptname . '/' . $where;
 $where =~ s|/+$||;  $scriptwhere =~ s|/$||;
 $scriptname = defined($ENV{SCRIPT_NAME}) ? $ENV{SCRIPT_NAME} : '';  
 $scriptname =~ s|^/?|/|;  
 $scriptname =~ s|/+$||;  
 $scriptwhere = $scriptname;  
 if ($where) {  
     $scriptwhere .= '/' . urlencode($where);  
 }  
   
 $is_mod_perl = defined($ENV{MOD_PERL});  if ($query = $ENV{'QUERY_STRING'}) {
   
 # in lynx, it it very annoying to have two links  
 # per file, so disable the link at the icon  
 # in this case:  
 $Browser = $ENV{HTTP_USER_AGENT};  
 $is_links = ($Browser =~ m`^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} =~ m`gzip`)  
                  || $is_mozilla3)  
                 && !$is_msie  
                 && !($is_mod_perl && !$has_zlib));  
   
 # put here the variables we need in order  
 # to hold our state - they will be added (with  
 # their current value) to any link/query string  
 # you construct  
 @stickyvars = qw(cvsroot hideattic sortby logsort f only_with_tag);  
   
 if (-f $config) {  
    do $config  
      || &fatal("500 Internal Error",  
                sprintf('Error in loading configuration file: %s<BR><BR>%s<BR>',  
                        $config, &htmlify($@)));  
 } else {  
    &fatal("500 Internal Error",  
           'Configuration not found.  Set the variable <code>$config</code> '  
           . 'in cvsweb.cgi, or the environment variable '  
           . '<code>CVSWEB_CONFIG</code>, to your <b>cvsweb.conf</b> '  
           . 'configuration file first.');  
 }  
   
 undef %input;  
 $query = $ENV{QUERY_STRING};  
   
 if (defined($query) && $query ne '') {  
     foreach (split(/&/, $query)) {      foreach (split(/&/, $query)) {
         s/%(..)/sprintf("%c", hex($1))/ge;      # unquote %-quoted          s/%(..)/sprintf("%c", hex($1))/ge;      # unquote %-quoted
         if (/(\S+)=(.*)/) {          if (/(\S+)=(.*)/) {
             $input{$1} = $2 if ($2 ne "");              $input{$1} = $2;
         }          } else {
         else {  
             $input{$_}++;              $input{$_}++;
         }          }
     }      }
       $query = "?" . $query;
 }  }
   
 # For backwards compability, set only_with_tag to only_on_branch if set.  
 $input{only_with_tag} = $input{only_on_branch}  
     if (defined($input{only_on_branch}));  
   
 $DEFAULTVALUE{'cvsroot'} = $cvstreedefault;  $config = '/usr/local/etc/cvsweb';
   do "$config" if -f $config;
   
 foreach (keys %DEFAULTVALUE)  if ($input{'cvsroot'}) {
 {      if ($CVSROOT{$input{'cvsroot'}}) {
     # replace not given parameters with the default parameters          $cvstree = $input{'cvsroot'};
     if (!defined($input{$_}) || $input{$_} eq "") {          $cvsroot = $CVSROOT{"$cvstree"};
         # 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 (!defined($input{"copt"})) {  
             # 'copt' isn't defined --> empty input is not the result  
             # of empty input checkbox --> set default  
             $input{$_} = $DEFAULTVALUE{$_} if (defined($DEFAULTVALUE{$_}));  
         }  
         else {  
             # 'copt' is defined -> the result of empty input checkbox  
             # -> set to zero (disable) if default is a boolean (0|1).  
             $input{$_} = 0  
                 if (defined($DEFAULTVALUE{$_})  
                     && ($DEFAULTVALUE{$_} eq "0" || $DEFAULTVALUE{$_} eq "1"));  
         }  
     }      }
 }  }
   do "$config-$cvstree" if -f "$config-$cvstree";
   
 $barequery = "";  
 my @barequery;  
 foreach (@stickyvars) {  
     # construct a query string with the sticky non default parameters set  
     if (defined($input{$_}) && $input{$_} ne '' &&  
         !(defined($DEFAULTVALUE{$_}) && $input{$_} eq $DEFAULTVALUE{$_})) {  
         push @barequery, join('=', urlencode($_), urlencode($input{$_}));  
     }  
 }  
 # is there any query ?  
 if (@barequery) {  
     $barequery = join('&', @barequery);  
     $query = "?$barequery";  
     $barequery = "&$barequery";  
 }  
 else {  
     $query = "";  
 }  
 undef @barequery;  
   
 # get actual parameters  
 $sortby = $input{"sortby"};  
 $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'};  
   
   
 ## Default CVS-Tree  
 if (!defined($CVSROOT{$cvstreedefault})) {  
    &fatal("500 Internal Error",  
           "<code>\$cvstreedefault</code> points to a repository "  
           . "not defined in <code>%CVSROOT</code> "  
           . "(edit your configuration file $config)");  
 }  
   
 # 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  
 my $k;  
 foreach $k (keys %ICONS) {  
     no strict 'refs';  
     my ($itxt,$ipath,$iwidth,$iheight) = @{$ICONS{$k}};  
     if ($ipath) {  
         ${"${k}icon"} = sprintf('<IMG SRC="%s" ALT="%s" BORDER="0" WIDTH="%d" HEIGHT="%d">',  
                                 htmlquote($ipath), htmlquote($itxt), $iwidth, $iheight)  
     }  
     else {  
         ${"${k}icon"} = $itxt;  
     }  
 }  
 undef $k;  
   
 my $config_cvstree = "$config-$cvstree";  
   
 # Do some special configuration for cvstrees  
 if (-f $config_cvstree) {  
    do $config_cvstree  
      || &fatal("500 Internal Error",  
                sprintf('Error in loading configuration file: %s<BR><BR>%s<BR>',  
                        $config_cvstree, &htmlify($@)));  
 }  
 undef $config_cvstree;  
   
 $prcategories = '(?:' . join('|', @prcategories) . ')';  
 $prcgi .= '%s' if defined($prcgi) && $prcgi !~ /%s/;  
   
 $fullname = $cvsroot . '/' . $where;  $fullname = $cvsroot . '/' . $where;
 $mimetype = &getMimeTypeFromSuffix ($fullname);  
 $defaultTextPlain = ($mimetype eq "text/plain");  
 $defaultViewable = $allow_markup && viewable($mimetype);  
   
 # search for GZIP if compression allowed  
 # We've to find out if the GZIP-binary exists .. otherwise  
 # ge get an Internal Server Error if we try to pipe the  
 # output through the nonexistent gzip ..  
 # any more elegant ways to prevent this are welcome!  
 if ($allow_compress && $maycompress && !$has_zlib) {  
     foreach (split(/:/, $ENV{PATH})) {  
         if (-x "$_/gzip") {  
             $GZIPBIN = "$_/gzip";  
             last;  
         }  
     }  
 }  
   
 if (-d $fullname) {  
     #  
     # ensure, that directories always end with (exactly) one '/'  
     # to allow relative URL's. If they're not, make a redirect.  
     ##  
     if (!($pathinfo =~ m|/$|) || ($pathinfo =~ m |/{2,}$|)) {  
         redirect ($scriptwhere . '/' . $query);  
     }  
     else {  
         $where .= '/';  
         $scriptwhere .= '/';  
     }  
 }  
   
 if (!-d $cvsroot) {  if (!-d $cvsroot) {
     &fatal("500 Internal Error",'$CVSROOT not found!<P>The server on which the CVS tree lives is probably down.  Please try again in a few minutes.');          &fatal("500 Internal Error",'$CVSROOT not found!<P>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 $where forbidden.");  
 }  
 ##############################  
 # View a directory  
 ###############################  
 elsif (-d $fullname) {  
         my $dh = do {local(*DH);};  
         opendir($dh, $fullname) || &fatal("404 Not Found","$where: $!");  
         my @dir = readdir($dh);  
         closedir($dh);  
         my @subLevelFiles = findLastModifiedSubdirs(@dir)  
             if ($show_subdir_lastmod);  
         getDirLogs($cvsroot,$where,@subLevelFiles);  
   
         if ($where eq '/') {  if (-d $fullname) {
             html_header($defaulttitle);          opendir(DIR, $fullname) || &fatal("404 Not Found","$where: $!");
             $long_intro =~ s/!!CVSROOTdescr!!/$CVSROOTdescr{$cvstree}/g;          @dir = readdir(DIR);
             print $long_intro;          closedir(DIR);
           if ($where eq '') {
               print &html_header("FreeBSD CVS Repository");
               print $intro;
           } else {
               print &html_header("/$where");
               print $shortinstr;
         }          }
         else {          print "<p>Current directory: <b>/$where</b>\n";
             html_header($where);          print "<P><HR NOSHADE>\n";
             print $short_instruction;  
         }  
   
         my $descriptions;  
         if (($use_descriptions) && open (DESC, "<$cvsroot/CVSROOT/descriptions")) {  
             while (<DESC>) {  
                 chomp;  
                 my ($dir,$description) = /(\S+)\s+(.*)/;  
                 $descriptions{$dir} = $description;  
             }  
         }  
   
         print "<P><a name=\"dirlist\"></a>\n";  
         # give direct access to dirs  
         if ($where eq '/') {  
             chooseMirror();  
             chooseCVSRoot();  
         }  
         else {  
             print "<p>Current directory: <b>", &clickablePath($where,0), "</b>\n";  
   
             print "<P>Current tag: <B>", $input{only_with_tag}, "</b>\n" if  
                 $input{only_with_tag};  
   
         }  
   
   
         print "<HR NOSHADE>\n";  
         # Using <MENU> in this manner violates the HTML2.0 spec but          # Using <MENU> in this manner violates the HTML2.0 spec but
         # provides the results that I want in most browsers.  Another          # provides the results that I want in most browsers.  Another
         # case of layout spooging up HTML.          # case of layout spooging up HTML.
           print "<MENU>\n";
         my $infocols = 0;  
         if ($dirtable) {  
             if (defined($tableBorderColor)) {  
                 # Can't this be done by defining the border for the inner table?  
                 print "<table border=0 cellpadding=0 width=\"100%\"><tr><td bgcolor=\"$tableBorderColor\">";  
             }  
             print "<table  width=\"100%\" border=0 cellspacing=1 cellpadding=$tablepadding>\n";  
             $infocols++;  
             printf '<tr><th align=left bgcolor="%s">',  
               $byfile ? $columnHeaderColorSorted : $columnHeaderColorDefault;  
             if ($byfile) {  
                 print 'File';  
             } else {  
                 print &link('File', sprintf('./%s#dirlist',  
                                             &toggleQuery("sortby", "file")));  
             }  
             print "</th>";  
             # do not display the other column-headers, if we do not have any files  
             # with revision information:  
             if (scalar(%fileinfo)) {  
                 $infocols++;  
                 printf '<th align=left bgcolor="%s">',  
                   $byrev ? $columnHeaderColorSorted : $columnHeaderColorDefault;  
                 if ($byrev) {  
                     print 'Rev.';  
                 } else {  
                     print &link('Rev.', sprintf('./%s#dirlist',  
                                                 &toggleQuery("sortby", "rev")));  
                 }  
                 print "</th>";  
                 $infocols++;  
                 printf '<th align=left bgcolor="%s">',  
                   $bydate ? $columnHeaderColorSorted : $columnHeaderColorDefault;  
                 if ($bydate) {  
                     print 'Age';  
                 } else {  
                     print &link('Age', sprintf('./%s#dirlist',  
                                                &toggleQuery("sortby", "date")));  
                 }  
                 print "</th>";  
                 if ($show_author) {  
                     $infocols++;  
                     printf '<th align=left bgcolor="%s">',  
                       $byauthor ? $columnHeaderColorSorted : $columnHeaderColorDefault;  
                     if ($byauthor) {  
                         print 'Author';  
                     } else {  
                         print &link('Author', sprintf('./%s#dirlist',  
                                                       &toggleQuery("sortby", "author")));  
                     }  
                     print "</th>";  
                 }  
                 $infocols++;  
                 printf '<th align=left bgcolor="%s">',  
                   $bylog ? $columnHeaderColorSorted : $columnHeaderColorDefault;  
                 if ($bylog) {  
                     print 'Last log entry';  
                 } else {  
                     print &link('Last log entry', sprintf('./%s#dirlist',  
                                                           &toggleQuery("sortby", "log")));  
                 }  
                 print "</th>";  
             }  
             elsif ($use_descriptions) {  
                 printf '<th align=left bgcolor="%s">', $columnHeaderColorDefault;  
                 print "Description";  
                 $infocols++;  
             }  
             print "</tr>\n";  
         }  
         else {  
             print "<menu>\n";  
         }  
         my $dirrow = 0;  
   
         my $i;  
         lookingforattic:          lookingforattic:
         for ($i = 0; $i <= $#dir; $i++) {          for ($i = 0; $i <= $#dir; $i++) {
                 if ($dir[$i] eq "Attic") {                  if ($dir[$i] eq "Attic") {
                     last lookingforattic;                          last lookingforattic;
                 }                  }
         }          }
         if (!$input{'hideattic'} && ($i <= $#dir) &&          $haveattic = 1 if ($i <= $#dir);
             opendir($dh, $fullname . "/Attic")) {          if (!$input{"showattic"} && ($i <= $#dir) &&
             splice(@dir, $i, 1,                                  opendir(DIR, $fullname . "/Attic")) {
                         grep((s|^|Attic/|,!m|/\.|), readdir($dh)));                  splice(@dir, $i, 1,
             closedir($dh);                          grep((s|^|Attic/|,!m|/\.|), readdir(DIR)));
                   closedir(DIR);
         }          }
   
         my $hideAtticToggleLink = $input{'hideattic'} ? '' :  
           &link('[Hide]', sprintf('./%s#dirlist',  
                                   &toggleQuery ("hideattic")));  
   
         # Sort without the Attic/ pathname.          # Sort without the Attic/ pathname.
         # place directories first          foreach (sort {($c=$a)=~s|.*/||;($d=$b)=~s|.*/||;($c cmp $d)} @dir) {
   
         my $attic;  
         my $url;  
         my $fileurl;  
         my $filesexists;  
         my $filesfound;  
   
         foreach (sort { &fileSortCmp } @dir) {  
             if ($_ eq '.') {              if ($_ eq '.') {
                 next;                  next;
             }              }
             # ignore CVS lock and stale NFS files  
             next if (/^#cvs\.|^,|^\.nfs/);  
   
             # Check whether to show the CVSROOT path  
             next if ($input{'hidecvsroot'} && ($_ eq 'CVSROOT'));  
   
             # Check whether the module is in the restricted list  
             next if ($_ && &forbidden_module($_));  
   
             # Ignore non-readable files  
             next if ($input{'hidenonreadable'} && !(-r "$fullname/$_"));  
   
             if (s|^Attic/||) {              if (s|^Attic/||) {
                 $attic  = " (in the Attic)&nbsp;" . $hideAtticToggleLink;                  $attic = " (in the Attic)";
             }              } else {
             else {  
                 $attic = "";                  $attic = "";
             }              }
               if ($_ eq '..') {
             if ($_ eq '..' || -d "$fullname/$_") {                  next if ($where eq '');
                 next if ($_ eq '..' && $where eq '/');                  ($updir = $scriptwhere) =~ s|[^/]+$||;
                 my ($rev,$date,$log,$author,$filename) = @{$fileinfo{$_}}                  print "<IMG SRC=\"/icons/back.gif\"> ",
                     if (defined($fileinfo{$_}));                      &link("Previous Directory",$updir . $query), "<BR>";
                 printf '<tr bgcolor="%s"><td>', $tabcolors[$dirrow % 2] if $dirtable;  #               print "<IMG SRC=???> ",
                 if ($_ eq '..') {  #                   &link("Directory-wide diffs", $scriptwhere . '/*'), "<BR>";
                     $url = "../$query";              } elsif (-d $fullname . "/" . $_) {
                     if ($nofilelinks) {                  print "<IMG SRC=\"/icons/dir.gif\"> ",
                         print $backicon;                      &link($_ . "/", $scriptwhere . '/' . $_ . '/' . $query),
                     }                              $attic, "<BR>";
                     else {              } elsif (s/,v$//) {
                         print &link($backicon, $url);  # TODO: add date/time?  How about sorting?
                     }                  print "<IMG SRC=\"/icons/text.gif\"> ",
                     print " ", &link("Previous Directory", $url);                      &link($_, $scriptwhere . '/' .
                 }                              ($attic ? "Attic/" : "") . $_ . $query),
                 else {                              $attic, "<BR>";
                     $url = urlencode($_) . "/$query";  
                     print "<A NAME=\"$_\"></A>";  
                     if ($nofilelinks) {  
                         print $diricon;  
                     }  
                     else {  
                         print &link($diricon, $url);  
                     }  
                     print " ", &link("$_/", $url), $attic;  
                     if ($_ eq "Attic") {  
                         print "&nbsp; ";  
                         print &link("[Don't hide]", sprintf('./%s#dirlist',  
                                                             &toggleQuery ("hideattic")));  
                     }  
                 }  
                 # Show last change in dir  
                 if ($filename) {  
                     print "</td><td>&nbsp;</td><td>&nbsp;" if ($dirtable);  
                     if ($date) {  
                         print " <i>", readableTime(time() - $date,0), "</i>";  
                     }  
                     if ($show_author) {  
                         print "</td><td>&nbsp;" if ($dirtable);  
                         print $author;  
                     }  
                     print "</td><td>&nbsp;" if ($dirtable);  
                     $filename =~ s%^[^/]+/%%;  
                     print "$filename/$rev";  
                     print "<BR>" if ($dirtable);  
                     if ($log) {  
                         print "&nbsp;<font size=-1>",  
                           &htmlify(substr($log,0,$shortLogLen));  
                         if (length $log > 80) {  
                             print "...";  
                         }  
                         print "</font>";  
                     }  
                 }  
                 else {  
                     my ($dwhere) = ($where ne "/" ? $where : "") . $_;  
                     if ($use_descriptions && defined $descriptions{$dwhere}) {  
                         print "<TD COLSPAN=", ($infocols-1), ">&nbsp;" if $dirtable;  
                         print $descriptions{$dwhere};  
                     } elsif ($dirtable && $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 "</td><td>&nbsp;";  
                             $cols--;  
                         }  
                     }  
                 }  
                 if ($dirtable) {  
                     print "</td></tr>\n";  
                 }  
                 else {  
                     print "<br>\n";  
                 }  
                 $dirrow++;  
             }              }
             elsif (s/,v$//) {  
                 $fileurl = ($attic ? "Attic/" : "") . urlencode($_);  
                 $url = $fileurl . $query;  
                 my $rev = '';  
                 my $date = '';  
                 my $log = '';  
                 my $author = '';  
                 $filesexists++;  
                 next if (!defined($fileinfo{$_}));  
                 ($rev,$date,$log,$author) = @{$fileinfo{$_}};  
                 $filesfound++;  
                 printf '<tr bgcolor="%s"><td>', $tabcolors[$dirrow % 2] if $dirtable;  
                 print "<A NAME=\"$_\"></A>";  
                 if ($nofilelinks) {  
                     print $fileicon;  
                 }  
                 else {  
                     print &link($fileicon,$url);  
                 }  
                 print " ", &link($_, $url), $attic;  
                 print "</td><td>&nbsp;" if ($dirtable);  
                 download_link($fileurl,  
                               $rev, $rev,  
                               $defaultViewable ? "text/x-cvsweb-markup" : undef);  
                 print "</td><td>&nbsp;" if ($dirtable);  
                 if ($date) {  
                     print " <i>", readableTime(time() - $date,0), "</i>";  
                 }  
                 if ($show_author) {  
                     print "</td><td>&nbsp;" if ($dirtable);  
                     print $author;  
                 }  
                 print "</td><td>&nbsp;" if ($dirtable);  
                 if ($log) {  
                     print " <font size=-1>", &htmlify(substr($log,0,$shortLogLen));  
                     if (length $log > 80) {  
                         print "...";  
                     }  
                     print "</font>";  
                 }  
                 print "</td>" if ($dirtable);  
                 print (($dirtable) ? "</tr>" : "<br>");  
                 $dirrow++;  
             }  
             print "\n";  
         }          }
         if ($dirtable && defined($tableBorderColor)) {          print "</MENU>\n";
             print "</td></tr></table>";          if ($input{"only_on_branch"}) {
         }              print "<HR><FORM METHOD=\"GET\" ACTION=\"${scriptwhere}\">\n";
         print( $dirtable == 1 ? "</table>\n" : "</menu>\n" );              print "Currently showing only branch $input{'only_on_branch'}.\n";
               $input{"only_on_branch"}="";
         if ($filesexists && !$filesfound) {              foreach $k (keys %input) {
             print "<P><B>NOTE:</B> There are $filesexists files, but none matches the current tag ($input{only_with_tag})\n";                  print "<INPUT TYPE=hidden NAME=$k VALUE=$input{$k}>\n" if $input{$k};
         }  
         if ($input{only_with_tag} && (!%tags || !$tags{$input{only_with_tag}})) {  
             %tags = %alltags  
         }  
         if (scalar %tags  
             || $input{only_with_tag}  
             || $edit_option_form  
             || defined($input{"options"})) {  
             print "<hr size=1 NOSHADE>";  
         }  
   
         if (scalar %tags || $input{only_with_tag}) {  
             print "<FORM METHOD=\"GET\" ACTION=\"./\">\n";  
             foreach my $var (@stickyvars) {  
                 print "<INPUT TYPE=HIDDEN NAME=\"$var\" VALUE=\"$input{$var}\">\n"  
                     if (defined($input{$var})  
                         && (!defined($DEFAULTVALUE{$var})  
                             || $input{$var} ne $DEFAULTVALUE{$var})  
                         && $input{$var} ne ""  
                         && $var ne "only_with_tag");  
             }              }
             print "Show only files with tag:\n";              print "<INPUT TYPE=SUBMIT VALUE=\"Show all branches\">\n";
             print "<SELECT NAME=only_with_tag";  
             print " onchange=\"submit()\"" if ($use_java_script);  
             print ">";  
             print "<OPTION VALUE=\"\">All tags / default branch\n";  
             foreach my $tag (reverse sort { lc $a cmp lc $b } keys %tags) {  
                 print "<OPTION",defined($input{only_with_tag}) &&  
                        $input{only_with_tag} eq $tag ? " SELECTED" : "",  
                        ">$tag\n";  
             }  
             print "</SELECT>\n";  
             print "<INPUT TYPE=SUBMIT VALUE=\"Go\">\n";  
             print "</FORM>\n";              print "</FORM>\n";
         }          }
         my $formwhere = $scriptwhere;          $formwhere = $scriptwhere;
         $formwhere =~ s|Attic/?$|| if ($input{'hideattic'});          $formwhere =~ s|Attic/?$|| if ($input{"showattic"});
           if ($haveattic) {
         if ($edit_option_form || defined($input{"options"})) {                  print "<HR><FORM METHOD=\"GET\" ACTION=\"${formwhere}\">\n";
             print "<FORM METHOD=\"GET\" ACTION=\"${formwhere}\">\n";                  $input{"showattic"}=!$input{"showattic"};
             print "<INPUT TYPE=HIDDEN NAME=\"copt\" VALUE=\"1\">\n";                  foreach $k (keys %input) {
             if ($cvstree ne $cvstreedefault) {                      print "<INPUT TYPE=hidden NAME=$k VALUE=$input{$k}>\n" if $input{$k};
                 print "<INPUT TYPE=HIDDEN NAME=\"cvsroot\" VALUE=\"$cvstree\">\n";                  }
             }                  print "<INPUT TYPE=SUBMIT VALUE=\"";
             print "<center><table cellpadding=0 cellspacing=0>";                  print ($input{"showattic"} ? "Show" : "Hide");
             print "<tr bgcolor=\"$columnHeaderColorDefault\"><th colspan=2>Preferences</th></tr>";                  print " attic directories\">\n";
             print "<tr><td>Sort files by <SELECT name=\"sortby\">";                  print "</FORM>\n";
             print "<OPTION VALUE=\"\">File";  
             print "<OPTION",$bydate ? " SELECTED" : ""," VALUE=date>Age";  
             print "<OPTION",$byauthor ? " SELECTED" : ""," VALUE=author>Author"  
                 if ($show_author);  
             print "<OPTION",$byrev ? " SELECTED" : ""," VALUE=rev>Revision";  
             print "<OPTION",$bylog ? " SELECTED" : ""," VALUE=log>Log message";  
             print "</SELECT></td>";  
             print "<td>Sort log by: ";  
             printLogSortSelect(0);  
             print "</td></tr>";  
             print "<tr><td>Diff format: ";  
             printDiffSelect(0);  
             print "</td>";  
             print "<td>Show Attic files: ";  
             print "<INPUT NAME=hideattic TYPE=CHECKBOX", $input{'hideattic'} ? " CHECKED" : "",  
             "></td></tr>\n";  
             print "<tr><td align=center colspan=2><input type=submit value=\"Change Options\">";  
             print "</td></tr></table></center></FORM>\n";  
         }          }
         print &html_footer;          print &html_footer;
         print "</BODY></HTML>\n";          print "</BODY></HTML>\n";
     }  } elsif (-f $fullname . ',v') {
           if ($input{'rev'} =~ /^[\d\.]+$/) {
 ###############################                  &checkout($fullname, $input{'rev'});
 # View Files                  exit;
 ###############################  
     elsif (-f $fullname . ',v') {  
         if (defined($input{'rev'}) || $doCheckout) {  
             &doCheckout($fullname, $input{'rev'});  
             gzipclose();  
             exit;  
         }          }
         if (defined($input{'annotate'}) && $allow_annotate) {          if ($input{'r1'} && $input{'r2'}) {
             &doAnnotate($input{'annotate'});                  &dodiff($fullname, $input{'r1'}, $input{'tr1'},
             gzipclose();                          $input{'r2'}, $input{'tr2'}, $input{'f'});
             exit;                  exit;
         }          }
         if (defined($input{'r1'}) && defined($input{'r2'})) {  print("going to dolog($fullname)\n") if ($verbose);
             &doDiff($fullname, $input{'r1'}, $input{'tr1'},          &dolog($fullname);
                     $input{'r2'}, $input{'tr2'}, $input{'f'});  } elsif ($fullname =~ s/\.diff$// && -f $fullname . ",v" &&
             gzipclose();                                  $input{'r1'} && $input{'r2'}) {
             exit;  
         }  
         print("going to dolog($fullname)\n") if ($verbose);  
         &doLog($fullname);  
 ##############################  
 # View Diff  
 ##############################  
     }  
     elsif ($fullname =~ s/\.diff$// && -f $fullname . ",v" &&  
            $input{'r1'} && $input{'r2'}) {  
   
         # $where-diff-removal if 'cvs rdiff' is used  
         # .. but 'cvs rdiff'doesn't support some options  
         # rcsdiff does (-w and -p), so it is disabled  
         # $where =~ s/\.diff$//;  
   
         # Allow diffs using the ".diff" extension          # Allow diffs using the ".diff" extension
         # so that browsers that default to the URL          # so that browsers that default to the URL
         # for a save filename don't save diff's as          # for a save filename don't save diff's as
         # e.g. foo.c          # e.g. foo.c
         &doDiff($fullname, $input{'r1'}, $input{'tr1'},          &dodiff($fullname, $input{'r1'}, $input{'tr1'},
                 $input{'r2'}, $input{'tr2'}, $input{'f'});                  $input{'r2'}, $input{'tr2'}, $input{'f'});
         gzipclose();  
         exit;          exit;
     }  } elsif (($newname = $fullname) =~ s|/([^/]+)$|/Attic/$1| &&
     elsif (($newname = $fullname) =~ s|/([^/]+)$|/Attic/$1| &&                                   -f $newname . ",v") {
            -f $newname . ",v") {  
         # The file has been removed and is in the Attic.          # The file has been removed and is in the Attic.
         # Send a redirect pointing to the file in the Attic.          # Send a redirect pointing to the file in the Attic.
         (my $newplace = $scriptwhere) =~ s|/([^/]+)$|/Attic/$1|;          ($newplace = $scriptwhere) =~ s|/([^/]+)$|/Attic/$1|;
         &redirect($newplace);          &redirect($newplace);
         exit;          exit;
     }  } elsif (0 && (@files = &safeglob($fullname . ",v"))) {
     elsif (0 && (my @files = &safeglob($fullname . ",v"))) {          print "Content-type: text/plain\n\n";
         http_header("text/plain");  
         print "You matched the following files:\n";          print "You matched the following files:\n";
         print join("\n", @files);          print join("\n", @files);
         # Find the tags from each file          # Find the tags from each file
         # Display a form offering diffs between said tags          # Display a form offering diffs between said tags
     }  } else {
     else {  
         my $fh = do {local(*FH);};  
         my ($xtra, $module);  
         # Assume it's a module name with a potential path following it.          # Assume it's a module name with a potential path following it.
         $xtra = $& if (($module = $where) =~ s|/.*||);          $xtra = $& if (($module = $where) =~ s|/.*||);
         # Is there an indexed version of modules?          # Is there an indexed version of modules?
         if (open($fh, "$cvsroot/CVSROOT/modules")) {          if (open(MODULES, "$cvsroot/CVSROOT/modules")) {
             while (<$fh>) {                  while (<MODULES>) {
                 if (/^(\S+)\s+(\S+)/o && $module eq $1                          if (/^(\S+)\s+(\S+)/o && $module eq $1
                     && -d "${cvsroot}/$2" && $module ne $2) {                                  && -d "${cvsroot}/$2" && $module ne $2) {
                     &redirect($scriptname . '/' . $2 . $xtra);                                  &redirect($scriptname . '/' . $2 . $xtra);
                           }
                 }                  }
             }  
         }          }
         &fatal("404 Not Found","$where: no such file or directory");          &fatal("404 Not Found","$where: no such file or directory");
     }  
   
 gzipclose();  
 ## End MAIN  
   
 sub printDiffSelect($) {  
     my ($use_java_script) = @_;  
     my $f = $input{'f'};  
   
     print '<SELECT NAME="f"';  
     print ' onchange="submit()"' if $use_java_script;  
     print '>';  
   
     local $_;  
     for (@DIFFTYPES) {  
         printf('<OPTION VALUE="%s"%s>%s',  
                $_,  
                $f eq $_ ? ' SELECTED' : '',  
                "\u$DIFFTYPES{$_}{'descr'}"  
               );  
     }  
   
     print "</SELECT>";  
 }  }
   
 sub printLogSortSelect($) {  sub htmlify {
     my ($use_java_script) = @_;          local($string, $pr) = @_;
   
     print '<SELECT NAME="logsort"';          $string =~ s/&/&amp;/g;
     print ' onchange="submit()"' if $use_java_script;          $string =~ s/</&lt;/g;
     print '>';          $string =~ s/>/&gt;/g;
   
     local $_;          if ($pr) {
     for (@LOGSORTKEYS) {                  $string =~ s|\bpr(\W+[a-z]+/\W*)(\d+)|<A HREF=/cgi/query-pr.cgi?pr=$2>$&</A>|ig;
         printf('<OPTION VALUE="%s"%s>%s',  
                $_,  
                $logsort eq $_ ? ' SELECTED' : '',  
                "\u$LOGSORTKEYS{$_}{'descr'}"  
               );  
     }  
   
     print "</SELECT>";  
 }  
   
 sub findLastModifiedSubdirs(@) {  
     my (@dirs) = @_;  
     my ($dirname, @files);  
   
     foreach $dirname (@dirs) {  
         next if ($dirname eq ".");  
         next if ($dirname eq "..");  
         my ($dir) = "$fullname/$dirname";  
         next if (!-d $dir);  
   
         my ($lastmod) = undef;  
         my ($lastmodtime) = undef;  
         my $dh = do {local(*DH);};  
   
         opendir($dh,$dir) || next;  
         my (@filenames) = readdir($dh);  
         closedir($dh);  
   
         foreach my $filename (@filenames) {  
             $filename = "$dirname/$filename";  
             my ($file) = "$fullname/$filename";  
             next if ($filename !~ /,v$/ || !-f $file);  
             $filename =~ s/,v$//;  
             my $modtime = -M $file;  
             if (!defined($lastmod) || $modtime < $lastmodtime) {  
                 $lastmod = $filename;  
                 $lastmodtime = $modtime;  
             }  
         }          }
         push(@files, $lastmod) if (defined($lastmod));  
     }  
     return @files;  
 }  
   
 sub htmlify_sub(&$) {          $string;
     (my $proc, local $_) = @_;  
     local @_ = split(m`(<a [^>]+>[^<]*</a>)`i);  
     my $linked;  
     my $result = '';  
   
     while (($_, $linked) = splice(@_, 0, 2)) {  
         &$proc();  
         $result .= $_ if defined($_);  
         $result .= $linked if defined($linked);  
     }  
   
     $result;  
 }  }
   
 sub htmlify($;$) {  sub link {
     (local $_, my $extra) = @_;          local($name, $where) = @_;
   
     $_ = htmlquote($_);          "<A HREF=\"$where\">$name</A>\n";
   
     # get URL's as link  
     s{  
       (http|ftp|https)://\S+  
      }{  
          &link($&, htmlunquote($&))  
      }egx;  
   
     # get e-mails as link  
     $_ = htmlify_sub {  
         s<  
           [\w+=\-.!]+@[\w\-]+(\.[\w\-]+)+  
             ><  
               &link($&, "mailto:$&")  
                 >egix;  
     } $_;  
   
     if ($extra) {  
         # get PR #'s as link: "PR#nnnn" "PR: nnnn, ..." "PR nnnn, ..." "bin/nnnn"  
         if (defined($prcgi)) {  
             my $prev;  
   
             do {  
                 $prev = $_;  
   
                 $_ = htmlify_sub {  
                     s{  
                       (\bPR[:\#]?\s*  
                        (?:  
                         \#?  
                         \d+[,\s]\s*  
                        )*  
                        \#?)  
                       (\d+)\b  
                      }{  
                          $1 . &link($2, sprintf($prcgi, $2)) . $3  
                      }egix;  
                 } $_;  
             } while ($_ ne $prev);  
   
             $_ = htmlify_sub {  
                 s{  
                   (\b$prcategories/(\d+)\b)  
                  }{  
                      &link($1, sprintf($prcgi, $2)) . $3  
                  }egox;  
             } $_;  
         }  
   
         # get manpage specs as link: "foo.1" "foo(1)"  
         if (defined($mancgi)) {  
             $_ = htmlify_sub {  
                 s{  
                   (\b([a-zA-Z][\w_.]+)  
                    (?:  
                     \( ([0-9n]) \)\B  
                     |  
                     \.([0-9n])\b  
                    )  
                   )  
                  }{  
                      &link($1, sprintf($mancgi, $3 ne '' ? $3 : $4, $2)) . $5  
                  }egx;  
             } $_;  
         }  
     }  
   
     $_;  
 }  }
   
 sub spacedHtmlText($;$) {  sub revcmp {
         local $_ = $_[0];          local($rev1, $rev2) = @_;
         my $ts = $_[1] || $tabstop;          local(@r1) = split(/\./, $rev1);
           local(@r2) = split(/\./, $rev2);
           local($a,$b);
   
         # Cut trailing spaces and tabs  
         s/[ \t]+$//;  
   
         if (defined($ts)) {  
             # Expand tabs  
             1 while s/\t+/' ' x (length($&) * $ts - length($`) % $ts)/e  
         }  
   
         # replace <tab> and <space> (\001 is to protect us from htmlify)  
         # gzip can make excellent use of this repeating pattern :-)  
         if ($hr_breakable) {  
             # make every other space 'breakable'  
             s/  / \001nbsp;/g;                              # 2 * <space>  
             # leave single space as it is  
         } else {  
             s/ /\001nbsp;/g;  
         }  
   
         $_ = htmlify($_);  
   
         # unescape  
         y/\001/&/;  
   
         return $_;  
 }  
   
 sub link($$) {  
         my($name, $where) = @_;  
   
         sprintf '<A HREF="%s">%s</A>', htmlquote($where), $name;  
 }  
   
 sub revcmp($$) {  
         my($rev1, $rev2) = @_;  
   
         # make no comparison for a tag or a branch  
         return 0 if $rev1 =~ /[^\d.]/ || $rev2 =~ /[^\d.]/;  
   
         my(@r1) = split(/\./, $rev1);  
         my(@r2) = split(/\./, $rev2);  
         my($a,$b);  
   
         while (($a = shift(@r1)) && ($b = shift(@r2))) {          while (($a = shift(@r1)) && ($b = shift(@r2))) {
             if ($a != $b) {              if ($a != $b) {
                 return $a <=> $b;                  return $a <=> $b;
Line 1143  sub revcmp($$) {
Line 277  sub revcmp($$) {
         return 0;          return 0;
 }  }
   
 sub fatal($$) {  sub fatal {
         my($errcode, $errmsg) = @_;          local($errcode, $errmsg) = @_;
         if ($is_mod_perl) {          print "Status: $errcode\n";
                 Apache->request->status((split(/ /, $errcode))[0]);          print &html_header("Error");
         }  #       print "Content-type: text/html\n";
         else {  #       print "\n";
                 print "Status: $errcode\r\n";  #       print "<HTML><HEAD><TITLE>Error</TITLE></HEAD>\n";
         }  #       print "<BODY>Error: $errmsg</BODY></HTML>\n";
         html_header("Error");  
         print "Error: $errmsg\n";          print "Error: $errmsg\n";
         print &html_footer;          print &html_footer;
         exit(1);          exit(1);
 }  }
   
 sub redirect($) {  sub redirect {
         my($url) = @_;          local($url) = @_;
         if ($is_mod_perl) {          print "Status: 301 Moved\n";
                 Apache->request->status(301);          print "Location: $url\n";
                 Apache->request->header_out(Location => $url);          print &html_header("Moved");
         }  #       print "Content-type: text/html\n";
         else {  #       print "\n";
                 print "Status: 301 Moved\r\n";  #       print "<HTML><HEAD><TITLE>Moved</TITLE></HEAD>\n";
                 print "Location: $url\r\n";  #       print "<BODY>This document is located <A HREF=$url>here</A>.</BODY></HTML>\n";
         }          print "This document is located <A HREF=$url>here</A>.\n";
         html_header("Moved");  
         print "This document is located ", &link('here', $url), "\n";  
         print &html_footer;          print &html_footer;
         exit(1);          exit(1);
 }  }
   
 sub safeglob($) {  sub safeglob {
         my ($filename) = @_;          local($filename) = @_;
         my ($dirname);          local($dirname);
         my (@results);          local(@results);
         my $dh = do {local(*DH);};  
   
         ($dirname = $filename) =~ s|/[^/]+$||;          ($dirname = $filename) =~ s|/[^/]+$||;
         $filename =~ s|.*/||;          $filename =~ s|.*/||;
   
         if (opendir($dh, $dirname)) {          if (opendir(DIR, $dirname)) {
                 my $glob = $filename;                  $glob = $filename;
                 my $t;  
         #       transform filename from glob to regex.  Deal with:          #       transform filename from glob to regex.  Deal with:
         #       [, {, ?, * as glob chars          #       [, {, ?, * as glob chars
         #       make sure to escape all other regex chars          #       make sure to escape all other regex chars
Line 1192  sub safeglob($) {
Line 321  sub safeglob($) {
                 $glob =~ s/\*/.*/g;                  $glob =~ s/\*/.*/g;
                 $glob =~ s/\?/./g;                  $glob =~ s/\?/./g;
                 $glob =~ s/{([^}]+)}/($t = $1) =~ s-,-|-g; "($t)"/eg;                  $glob =~ s/{([^}]+)}/($t = $1) =~ s-,-|-g; "($t)"/eg;
                 foreach (readdir($dh)) {                  foreach (readdir(DIR)) {
                         if (/^${glob}$/) {                          if (/^${glob}$/) {
                                 push(@results, "$dirname/" .$_);                                  push(@results, $dirname . "/" .$_);
                         }                          }
                 }                  }
         }          }
Line 1202  sub safeglob($) {
Line 331  sub safeglob($) {
         @results;          @results;
 }  }
   
 sub getMimeTypeFromSuffix($) {  sub checkout {
     my ($fullname) = @_;          local($fullname, $rev) = @_;
     my ($mimetype, $suffix);  
     my $fh = do {local(*FH);};  
   
     ($suffix = $fullname) =~ s/^.*\.([^.]*)$/$1/;          open(RCS, "co -p$rev '$fullname' 2>&1 |") ||
     $mimetype = $MTYPES{$suffix};              &fail("500 Internal Error", "Couldn't co: $!");
     $mimetype = $MTYPES{'*'} if (!$mimetype);  # /home/ncvs/src/sys/netinet/igmp.c,v  -->  standard output
   # or
     if (!$mimetype && -f $mime_types) {  # /home/ncvs/src/sys/netinet/igmp.c,v  -->  stdout
         # okey, this is something special - search the  # revision 1.1.1.2
         # mime.types database  # /*
         open ($fh, "<$mime_types");          $_ = <RCS>;
         while (<$fh>) {          if (/^(\S+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/o && $1 eq $fullname) {
             if ($_ =~ /^\s*(\S+\/\S+).*\b$suffix\b/) {              # As expected
                 $mimetype = $1;          } else {
                 last;              &fatal("500 Internal Error",
             }                  "Unexpected output from co: $_");
         }          }
         close ($fh);          $_ = <RCS>;
     }          if (/^revision\s+$rev\s*$/) {
               # As expected
 # okey, didn't find anything useful ..          } else {
     if (!($mimetype =~ /\S\/\S/)) {              &fatal("500 Internal Error",
         $mimetype = "text/plain";                  "Unexpected output from co: $_");
     }  
     return $mimetype;  
 }  
   
 ###############################  
 # read first lines like head(1)  
 ###############################  
 sub head($;$) {  
     my $fh = $_[0];  
     my $linecount = $_[1] || 10;  
   
     my @buf;  
   
     if ($linecount > 0) {  
         my $i;  
         for ($i = 0; !eof($fh) && $i < $linecount; $i++) {  
             push @buf, scalar <$fh>;  
         }          }
     } else {          $| = 1;
         @buf = <$fh>;          print "Content-type: text/plain\n\n";
     }          print <RCS>;
           close(RCS);
     @buf;  
 }  }
   
 ###############################  sub dodiff {
 # scan vim and Emacs directives          local($fullname, $r1, $tr1, $r2, $tr2, $f) = @_;
 ###############################  
 sub scan_directives(@) {  
     my $ts = undef;  
   
     for (@_) {  
         $ts = $1 if /\b(?:ts|tabstop|tab-width)[:=]\s*([1-9]\d*)\b/;  
     }  
   
     ('tabstop' => $ts);  
 }  
   
 ###############################  
 # show Annotation  
 ###############################  
 sub doAnnotate($$) {  
     my ($rev) = @_;  
     my ($pid);  
     my ($pathname, $filename);  
     my $reader = do {local(*FH);};  
     my $writer = do {local(*FH);};  
   
     # make sure the revisions a wellformed, for security  
     # reasons ..  
     if ($rev =~ /[^\w.]/) {  
         &fatal("404 Not Found",  
                 "Malformed query \"$ENV{QUERY_STRING}\"");  
     }  
   
     ($pathname = $where) =~ s/(Attic\/)?[^\/]*$//;  
     ($filename = $where) =~ s/^.*\///;  
   
     # this seems to be necessary  
     $| = 1; $| = 0; # Flush  
   
     # 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 cvsserver in a similiar way one day (..after rewrite)  
     $pid = open2($reader, $writer, "cvs -Rl server") || fatal ("500 Internal Error",  
                                                                "Fatal Error - unable to open cvs for annotation");  
   
     # OK, first send the request to the server.  A simplified example is:  
     #     Root /home/kingdon/zwork/cvsroot  
     #     Argument foo/xx  
     #     Directory foo  
     #     /home/kingdon/zwork/cvsroot/foo  
     #     Directory .  
     #     /home/kingdon/zwork/cvsroot  
     #     annotate  
     # although as you can see there are a few more details.  
   
     print $writer "Root $cvsroot\n";  
     print $writer "Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E\n";  
     # Don't worry about sending valid-requests, the server just needs to  
     # support "annotate" and if it doesn't, there isn't anything to be done.  
     print $writer "UseUnchanged\n";  
     print $writer "Argument -r\n";  
     print $writer "Argument $rev\n";  
     print $writer "Argument $where\n";  
   
     # The protocol requires us to fully fake a working directory (at  
     # least to the point of including the directories down to the one  
     # containing the file in question).  
     # So if $where is "dir/sdir/file", then @dirs will be ("dir","sdir","file")  
     my @dirs = split('/', $where);  
     my $path = "";  
     foreach (@dirs) {  
         if ($path eq "") {  
             # In our example, $_ is "dir".  
             $path = $_;  
         }  
         else {  
             print $writer "Directory $path\n";  
             print $writer "$cvsroot/$path\n";  
             # In our example, $_ is "sdir" and $path becomes "dir/sdir"  
             # And the next time, "file" and "dir/sdir/file" (which then gets  
             # ignored, because we don't need to send Directory for the file).  
             $path .= "/$_";  
         }  
     }  
     # And the last "Directory" before "annotate" is the top level.  
     print $writer "Directory .\n";  
     print $writer "$cvsroot\n";  
   
     print $writer "annotate\n";  
     # OK, we've sent our command to the server.  Thing to do is to  
     # close the writer side and get all the responses.  If "cvs server"  
     # were nicer about buffering, then we could just leave it open, I think.  
     close ($writer) || die "cannot close: $!";  
   
     http_header();  
   
     navigateHeader($scriptwhere,$pathname,$filename,$rev, "annotate");  
     print "<h3 align=center>Annotation of $pathname$filename, Revision $rev</h3>\n";  
   
     # Ready to get the responses from the server.  
     # For example:  
     #     E Annotations for foo/xx  
     #     E ***************  
     #     M 1.3          (kingdon  06-Sep-97): hello  
     #     ok  
     my ($lineNr) = 0;  
     my ($oldLrev, $oldLusr) = ("", "");  
     my ($revprint, $usrprint);  
     if ($annTable) {  
         print "<table border=0 cellspacing=0 cellpadding=0>\n";  
     }  
     else {  
         print "<pre>";  
     }  
   
     # prefetch several lines  
     my @buf = head($reader);  
   
     my %d = scan_directives(@buf);  
   
     while (@buf || !eof($reader)) {  
         $_ = @buf ? shift @buf : <$reader>;  
   
         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 "<b>" if ($isCurrentRev && $is_textbased);  
   
             printf "%s%s %-8s %4d:",  
                     $revprint,  
                     $isCurrentRev ? '!' : ' ',  
                     $usrprint,  
                     $lineNr;  
             print spacedHtmlText($line, $d{'tabstop'});  
   
             print "</b>" 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: <b>$_</b>");  
         }  
     }  
     if ($annTable) {  
         print "</table>";  
     }  
     else {  
         print "</pre>";  
     }  
     close ($reader) || warn "cannot close: $!";  
     wait;  
 }  
   
 ###############################  
 # make Checkout  
 ###############################  
 sub doCheckout($$) {  
     my ($fullname, $rev) = @_;  
     my ($mimetype,$revopt);  
     my $fh = do {local(*FH);};  
   
     if ($rev eq 'HEAD' || $rev eq '.') {  
         $rev = undef;  
     }  
   
     # make sure the revisions a wellformed, for security  
     # reasons ..  
     if (defined($rev) && $rev =~ /[^\w.]/) {  
         &fatal("404 Not Found",  
                 "Malformed query \"$ENV{QUERY_STRING}\"");  
     }  
   
     # get mimetype  
     if (defined($input{"content-type"}) && ($input{"content-type"} =~ /\S\/\S/)) {  
         $mimetype = $input{"content-type"}  
     }  
     else {  
         $mimetype = &getMimeTypeFromSuffix($fullname);  
     }  
   
     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}};  
         }  
     }  
   
     ### just for the record:  
     ### 'cvs co' seems to have a bug regarding single checkout of  
     ### directories/files having spaces in it;  
     ### this is an issue that should be resolved on cvs's side  
     #  
     # Safely for a child process to read from.  
     if (! open($fh, "-|")) { # child  
       open(STDERR, ">&STDOUT"); # Redirect stderr to stdout  
       exec("cvs", "-Rld", $cvsroot, "co", "-p", $revopt, $where);  
     }  
   
     if (eof($fh)) {  
         &fatal("404 Not Found",  
                "$where is not (any longer) pertinent");  
     }  
 #===================================================================  
 #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);  
     while(<$fh>) {  
         last if (/^\*\*\*\*/);  
         $revision = $1 if (/^VERS: (.*)$/);  
         if (/^Checking out (.*)$/) {  
                 $filename = $1;  
                 $filename =~ s/^\.\/*//;  
         }  
         $cvsheader .= $_;  
     }  
     if ($filename ne $where) {  
         &fatal("500 Internal Error",  
                "Unexpected output from cvs co: $cvsheader");  
     }  
     $| = 1;  
   
     if ($mimetype eq "text/x-cvsweb-markup") {  
         &cvswebMarkup($fh,$fullname,$revision);  
     }  
     else {  
         http_header($mimetype);  
         print <$fh>;  
     }  
     close($fh);  
 }  
   
 sub cvswebMarkup($$$) {  
     my ($filehandle,$fullname,$revision) = @_;  
     my ($pathname, $filename);  
   
     ($pathname = $where) =~ s/(Attic\/)?[^\/]*$//;  
     ($filename = $where) =~ s/^.*\///;  
     my ($fileurl) = urlencode($filename);  
   
     http_header();  
   
     navigateHeader($scriptwhere, $pathname, $filename, $revision, "view");  
     print "<HR noshade>";  
     print "<table width=\"100%\"><tr><td bgcolor=\"$markupLogColor\">";  
     print "File: ", &clickablePath($where, 1);  
     print "&nbsp;(";  
     &download_link($fileurl, $revision, "download");  
     print ")";  
     if (!$defaultTextPlain) {  
         print "&nbsp;(";  
         &download_link($fileurl, $revision, "as text",  
                "text/plain");  
         print ")";  
     }  
     print "<BR>\n";  
     if ($show_log_in_markup) {  
         readLog($fullname); #,$revision);  
         printLog($revision,0);  
     }  
     else {  
         print "Version: <B>$revision</B><BR>\n";  
         print "Tag: <B>", $input{only_with_tag}, "</b><br>\n" if  
             $input{only_with_tag};  
     }  
     print "</td></tr></table>";  
     my $url = download_url($fileurl, $revision, $mimetype);  
     print "<HR noshade>";  
     if ($mimetype =~ /^image/) {  
         printf '<IMG SRC="%s"><BR>', htmlquote("$url$barequery");  
     }  
     elsif ($mimetype =~ m%^application/pdf%) {  
         printf '<EMBED SRC="%s" WIDTH="100%"><BR>', htmlquote("$url$barequery");  
     }  
     else {  
         print "<PRE>";  
   
         # prefetch several lines  
         my @buf = head($filehandle);  
   
         my %d = scan_directives(@buf);  
   
         while (@buf || !eof($filehandle)) {  
             $_ = @buf ? shift @buf : <$filehandle>;  
   
             print spacedHtmlText($_, $d{'tabstop'});  
         }  
         print "</PRE>";  
     }  
 }  
   
 sub viewable($) {  
     my ($mimetype) = @_;  
   
     $mimetype =~ m%^((text|image)/|application/pdf)% ;  
 }  
   
 ###############################  
 # Show Colored Diff  
 ###############################  
 sub doDiff($$$$$$) {  
         my($fullname, $r1, $tr1, $r2, $tr2, $f) = @_;  
         my $fh = do {local(*FH);};  
         my ($rev1, $rev2, $sym1, $sym2, $f1, $f2);  
   
         if ($r1 =~ /([^:]+)(:(.+))?/) {          if ($r1 =~ /([^:]+)(:(.+))?/) {
             $rev1 = $1;              $rev1 = $1;
             $sym1 = $3;              $sym1 = $3;
         }          }
         if ($r1 eq 'text') {          if ($rev1 eq 'text') {
             $rev1 = $tr1;              $rev1 = $tr1;
             $sym1 = "";  
         }          }
         if ($r2 =~ /([^:]+)(:(.+))?/) {          if ($r2 =~ /([^:]+)(:(.+))?/) {
             $rev2 = $1;              $rev2 = $1;
             $sym2 = $3;              $sym2 = $3;
         }          }
         if ($r2 eq 'text') {          if ($rev2 eq 'text') {
             $rev2 = $tr2;              $rev2 = $tr2;
             $sym2 = "";  
         }          }
           if (!($rev1 =~ /^[\d\.]+$/) || !($rev2 =~ /^[\d\.]+$/)) {
         # make sure the revisions a wellformed, for security  
         # reasons ..  
         if ($rev1 =~ /[^\w.]/ || $rev2 =~ /[^\w.]/) {  
             &fatal("404 Not Found",              &fatal("404 Not Found",
                     "Malformed query \"$ENV{QUERY_STRING}\"");                      "Malformed query \"$ENV{'QUERY_STRING'}\"");
         }          }
 #  #
 # rev1 and rev2 are now both numeric revisions.  # rev1 and rev2 are now both numeric revisions.
 # Thus we do a DWIM here and swap them if rev1 is after rev2.  # Thus we do a DWIM here and swap them if rev1 is after rev2.
 # XXX should we warn about the fact that we do this?  # XXX should we warn about the fact that we do this?
         if (&revcmp($rev1,$rev2) > 0) {          if (&revcmp($rev1,$rev2) > 0) {
             my ($tmp1, $tmp2) = ($rev1, $sym1);              ($tmp1, $tmp2) = ($rev1, $sym1);
             ($rev1, $sym1) = ($rev2, $sym2);              ($rev1, $sym1) = ($rev2, $sym2);
             ($rev2, $sym2) = ($tmp1, $tmp2);              ($rev2, $sym2) = ($tmp1, $tmp2);
         }          }
         my $difftype = $DIFFTYPES{$f};  #
   #       XXX Putting '-p' here is a personal preference
         if (!$difftype) {          if ($f eq 'c') {
             fatal ("400 Bad arguments", "Diff format $f not understood");              $difftype = '-p -c';
               $diffname = "Context diff";
           } elsif ($f eq 's') {
               $difftype = '--side-by-side --width=164';
               $diffname = "Side by Side";
           } else {
               $difftype = '-p -u';
               $diffname = "Unidiff";
         }          }
   # XXX should this just be text/plain
         my @difftype       = @{$difftype->{'opts'}};  # or should it have an HTML header and then a <pre>
         my $human_readable = $difftype->{'colored'};          print "Content-type: text/plain\n\n";
           open(RCSDIFF, "rcsdiff $difftype -r$rev1 -r$rev2 '$fullname' 2>&1 |") ||
         # apply special options              &fail("500 Internal Error", "Couldn't rcsdiff: $!");
         if ($showfunc) {  
             push @difftype, '-p' if $f ne 's';  
   
             my($re1, $re2);  
   
             while (($re1, $re2) = each %funcline_regexp) {  
                 if ($fullname =~ /$re1/) {  
                     push @difftype, '-F', '$re2';  
                     last;  
                 }  
             }  
         }  
         if ($human_readable) {  
             if ($hr_ignwhite) {  
                 push @difftype, '-w';  
             }  
             if ($hr_ignkeysubst) {  
                 push @difftype, '-kk';  
             }  
         }  
         if (! open($fh, "-|")) { # child  
             open(STDERR, ">&STDOUT"); # Redirect stderr to stdout  
             exec("rcsdiff",@difftype,"-r$rev1","-r$rev2",$fullname);  
         }  
         if ($human_readable) {  
             http_header();  
             &human_readable_diff($fh, $rev2);  
             gzipclose();  
             exit;  
         }  
         else {  
             http_header("text/plain");  
         }  
 #  #
 #===================================================================  #===================================================================
 #RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v  #RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v
Line 1692  sub doDiff($$$$$$) {
Line 425  sub doDiff($$$$$$) {
 #--- src/sys/netinet/tcp_output.c     1995/12/05 17:46:35     1.17 RELENG_2_1_0  #--- src/sys/netinet/tcp_output.c     1995/12/05 17:46:35     1.17 RELENG_2_1_0
 # (bogus example, but...)  # (bogus example, but...)
 #  #
         if (grep { $_ eq '-u'} @difftype) {          if ($difftype eq '-u') {
             $f1 = '---';              $f1 = '---';
             $f2 = '\+\+\+';              $f2 = '\+\+\+';
         }          } else {
         else {  
             $f1 = '\*\*\*';              $f1 = '\*\*\*';
             $f2 = '---';              $f2 = '---';
         }          }
         while (<$fh>) {          while (<RCSDIFF>) {
             if (m|^$f1 $cvsroot|o) {              if (m|^$f1 $cvsroot|o) {
                 s|$cvsroot/||o;                  s|$cvsroot/||o;
                 if ($sym1) {                  if ($sym1) {
                     chop;                      chop;
                     $_ .= " $sym1\n";                      $_ .= " " . $sym1 . "\n";
                 }                  }
             }              } elsif (m|^$f2 $cvsroot|o) {
             elsif (m|^$f2 $cvsroot|o) {  
                 s|$cvsroot/||o;                  s|$cvsroot/||o;
                 if ($sym2) {                  if ($sym2) {
                     chop;                      chop;
                     $_ .= " $sym2\n";                      $_ .= " " . $sym2 . "\n";
                 }                  }
             }              }
             print $_;              print $_;
         }          }
         close($fh);          close(RCSDIFF);
 }  }
   
 ###############################  sub dolog {
 # Show Logs ..          local($fullname) = @_;
 ###############################          local($curbranch,$symnames);    #...
 sub getDirLogs($$@) {  
     my ($cvsroot,$dirname,@otherFiles) = @_;  
     my ($state,$otherFiles,$tag, $file, $date, $branchpoint, $branch, $log);  
     my ($rev, $revision, $revwanted, $filename, $head, $author);  
   
     $tag = $input{only_with_tag};  
   
     my ($DirName) = "$cvsroot/$where";  
     my (@files, @filetags);  
     my $fh = do {local(*FH);};  
   
     push(@files, &safeglob("$DirName/*,v"));  
     push(@files, &safeglob("$DirName/Attic/*,v")) if (!$input{'hideattic'});  
     foreach $file (@otherFiles) {  
         push(@files, "$DirName/$file");  
     }  
   
     # just execute rlog if there are any files  
     if ($#files < 0) {  
         return;  
     }  
   
     if ($tag) {  
         #can't use -r<tag> as - is allowed in tagnames, but misinterpreated by rlog..  
         if (! open($fh, "-|")) {  
                 open(STDERR, '>/dev/null'); # rlog may complain; ignore.  
                 exec('rlog', @files);  
         }  
     }  
     else {  
         my $kidpid = open($fh, "-|");  
         if (! $kidpid) {  
                 open(STDERR, '>/dev/null'); # rlog may complain; ignore.  
                 exec('rlog', '-r', @files);  
         }  
     }  
     $state = "start";  
     while (<$fh>) {  
         if ($state eq "start") {  
             #Next file. Initialize file variables  
             $rev = undef;  
             $revwanted = undef;  
             $branch = undef;  
             $branchpoint = undef;  
             $filename = undef;  
             $log = undef;  
             $revision = undef;  
             $branch = undef;  
             %symrev = ();  
             @filetags = ();  
             #jump to head state  
             $state = "head";  
         }  
         print "$state:$_" if ($verbose);  
 again:  
         if ($state eq "head") {  
             #$rcsfile = $1 if (/^RCS file: (.+)$/); #not used (yet)  
   
             if (/^Working file: (.+)$/) {  
                 $filename = $1;  
             } elsif (/^head: (.+)$/) {  
                 $head = $1;  
             } elsif (/^branch: (.+)$/) {  
                 $branch = $1  
             } elsif (/^symbolic names:/) {  
                 $state = "tags";  
                 ($branch = $head) =~ s/\.\d+$// if (!defined($branch));  
                 $branch =~ s/(\.?)(\d+)$/${1}0.$2/;  
                 $symrev{MAIN} = $branch;  
                 $symrev{HEAD} = $branch;  
                 $alltags{MAIN} = 1;  
                 $alltags{HEAD} = 1;  
                 push (@filetags, "MAIN", "HEAD");  
             } elsif (/$LOG_REVSEPARATOR/o) {  
                 $state = "log";  
                 $rev = undef;  
                 $date = undef;  
                 $log = "";  
                 # Try to reconstruct the relative filename if RCS spits out a full path  
                 $filename =~ s%^\Q$DirName\E/%%;  
             }  
             next;  
         }  
         if ($state eq "tags") {  
             if (/^\s+(.+):\s+([\d\.]+)\s+$/) {  
                 push (@filetags, $1);  
                 $symrev{$1} = $2;  
                 $alltags{$1} = 1;  
                 next;  
             } elsif (/^\S/) {  
                 if (defined($tag)) {  
                     if(defined($symrev{$tag}) || $tag eq "HEAD") {  
                         $revwanted = $symrev{$tag eq "HEAD" ? "MAIN" : $tag};  
                         ($branch = $revwanted) =~ s/\.0\././;  
                         ($branchpoint = $branch) =~ s/\.?\d+$//;  
                         $revwanted = undef if ($revwanted ne $branch);  
 #print "\n[revwanted=$revwanted]";  
                     } elsif ($tag ne "HEAD") {  
                         print "Tag not found, skip this file" if ($verbose);  
                         $state = "skip";  
                         next;  
                     }  
                 }  
                 foreach my $tagfound (@filetags) {  
                     $tags{$tagfound} = 1;  
                 }  
                 $state = "head";  
                 goto again;  
             }  
         }  
         if ($state eq "log") {  
             if (/$LOG_REVSEPARATOR/o || /$LOG_FILESEPARATOR/o) {  
                 # End of a log entry.  
                 my $revbranch;  
                 ($revbranch = $rev) =~ s/\.\d+$//;  
                 print "$filename $rev Wanted: $revwanted ",  
                   "Revbranch: $revbranch Branch: $branch ",  
                     "Branchpoint: $branchpoint\n" if ($verbose);  
                 if (!defined($revwanted) && defined($branch)  
                     && $branch eq $revbranch || !defined($tag)) {  
                     print "File revision $rev found for branch $branch\n"  
                         if ($verbose);  
                     $revwanted = $rev;  
                 }  
                 if (defined($revwanted) ? $rev eq $revwanted :  
                     defined($branchpoint) ? $rev eq $branchpoint :  
                     0 && ($rev eq $head)) { # Don't think head is needed here..  
                     print "File info $rev found for $filename\n" if ($verbose);  
                     my @finfo = ($rev,$date,$log,$author,$filename);  
                     my ($name);  
                     ($name = $filename) =~ s%/.*%%;  
                     $fileinfo{$name} = [ @finfo ];  
                     $state = "done" if ($rev eq $revwanted);  
                 }  
                 $rev = undef;  
                 $date = undef;  
                 $log = "";  
             }  
             elsif (!defined($date) && m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);|) {  
                 my $yr = $1;  
                 # damn 2-digit year routines :-)  
                 if ($yr > 100) {  
                     $yr -= 1900;  
                 }  
                 $date = &Time::Local::timegm($6,$5,$4,$3,$2 - 1,$yr);  
                 ($author) = /author: ([^;]+)/;  
                 $state = "log";  
                 $log = '';  
                 next;  
             }  
             elsif (!defined($rev) && m/^revision (.*)$/) {  
                 $rev = $1;  
                 next;  
             }  
             else {  
                 $log .= $_;  
             }  
         }  
         if (/$LOG_FILESEPARATOR/o) {  
             $state = "start";  
             next;  
         }  
     }  
     if ($. == 0) {  
         fatal("500 Internal Error",  
               "Failed to spawn GNU rlog on <em>'".join(", ", @files)."'</em><p>did you set the <b>\$ENV{PATH}</b> in your configuration file correctly ?");  
     }  
     close($fh);  
 }  
   
 sub readLog($;$) {  
         my($fullname,$revision) = @_;  
         my ($symnames, $head, $rev, $br, $brp, $branch, $branchrev);  
         my $fh = do {local(*FH);};  
   
         if (defined($revision)) {  
             $revision = "-r$revision";  
         }  
         else {  
             $revision = "";  
         }  
   
         undef %symrev;  
         undef %revsym;  
         undef @allrevisions;  
         undef %date;  
         undef %author;  
         undef %state;  
         undef %difflines;  
         undef %log;  
   
         print("Going to rlog '$fullname'\n") if ($verbose);          print("Going to rlog '$fullname'\n") if ($verbose);
         if (! open($fh, "-|")) { # child          open(RCS, "rlog '$fullname'|") || &fatal("500 Internal Error",
                 if ($revision ne '') {                                                  "Failed to spawn rlog");
                         exec("rlog",$revision,$fullname);          while (<RCS>) {
                 }  
                 else {  
                         exec("rlog",$fullname);  
                 }  
         }  
         while (<$fh>) {  
             print if ($verbose);              print if ($verbose);
               if (/^branch:\s+([\d\.]+)/) {
                   $curbranch = $1;
               }
             if ($symnames) {              if ($symnames) {
                 if (/^\s+([^:]+):\s+([\d\.]+)/) {                  if (/^\s+([^:]+):\s+([\d\.]+)/) {
                     $symrev{$1} = $2;                      $symrev{$1} = $2;
                 }                      if ($revsym{$2}) {
                 else {                          $revsym{$2} .= ", ";
                       }
                       $revsym{$2} .= $1;
                   } else {
                     $symnames = 0;                      $symnames = 0;
                 }                  }
             }              } elsif (/^symbolic names/) {
             elsif (/^head:\s+([\d\.]+)/) {  
                 $head = $1;  
             }  
             elsif (/^branch:\s+([\d\.]+)/) {  
                 $curbranch = $1;  
             }  
             elsif (/^symbolic names/) {  
                 $symnames = 1;                  $symnames = 1;
             }              } elsif (/^-----/) {
             elsif (/^-----/) {  
                 last;                  last;
             }              }
         }          }
         ($curbranch = $head) =~ s/\.\d+$// if (!defined($curbranch));  
   
           if ($onlyonbranch = $input{'only_on_branch'}) {
               ($onlyonbranch = $symrev{$onlyonbranch}) =~ s/\.0\././;
               ($onlybranchpoint = $onlyonbranch) =~ s/\.\d+$//;
           }
   
 # each log entry is of the form:  # each log entry is of the form:
 # ----------------------------  # ----------------------------
 # revision 3.7.1.1  # revision 3.7.1.1
Line 1955  sub readLog($;$) {
Line 492  sub readLog($;$) {
 # log info  # log info
 # ----------------------------  # ----------------------------
         logentry:          logentry:
         while (!/$LOG_FILESEPARATOR/o) {          while (!/^=========/) {
             $_ = <$fh>;              $_ = <RCS>;
             last logentry if (!defined($_));    # EOF              last logentry if (!defined($_));    # EOF
             print "R:", $_ if ($verbose);              print "R:", $_ if ($verbose);
             if (/^revision ([\d\.]+)/) {              if (/^revision ([\d\.]+)/) {
                 $rev = $1;                  $rev = $1;
                 unshift(@allrevisions,$rev);              } elsif (/^========/ || /^----------------------------$/) {
             }  
             elsif (/$LOG_FILESEPARATOR/o || /$LOG_REVSEPARATOR/o) {  
                 next logentry;                  next logentry;
             }              } else {
             else {  
                 # The rlog output is syntactically ambiguous.  We must                  # The rlog output is syntactically ambiguous.  We must
                 # have guessed wrong about where the end of the last log                  # have guessed wrong about where the end of the last log
                 # message was.                  # message was.
Line 1977  sub readLog($;$) {
Line 511  sub readLog($;$) {
                 next logentry;                  next logentry;
 #               &fatal("500 Internal Error","Error parsing RCS output: $_");  #               &fatal("500 Internal Error","Error parsing RCS output: $_");
             }              }
             $_ = <$fh>;              $_ = <RCS>;
             print "D:", $_ if ($verbose);              print "D:", $_ if ($verbose);
             if (m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);\s+state:\s+(\S+);\s+(lines:\s+([0-9\s+-]+))?|) {              if (m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);\s+state:\s+(\S+);|) {
                 my $yr = $1;                  $yr = $1;
                 # damn 2-digit year routines :-)                  # damn 2-digit year routines
                 if ($yr > 100) {                  if ($yr > 100) {
                     $yr -= 1900;                      $yr -= 1900;
                 }                  }
                 $date{$rev} = &Time::Local::timegm($6,$5,$4,$3,$2 - 1,$yr);                  $date{$rev} = &timelocal($6,$5,$4,$3,$2 - 1,$yr);
                 $author{$rev} = $7;                  $author{$rev} = $7;
                 $state{$rev} = $8;                  $state{$rev} = $8;
                 $difflines{$rev} = $10;              } else {
             }  
             else {  
                 &fatal("500 Internal Error", "Error parsing RCS output: $_");                  &fatal("500 Internal Error", "Error parsing RCS output: $_");
             }              }
             line:              line:
             while (<$fh>) {              while (<RCS>) {
                 print "L:", $_ if ($verbose);                  print "L:", $_ if ($verbose);
                 next line if (/^branches:\s/);                  next line if (/^branches:\s/);
                 last line if (/$LOG_FILESEPARATOR/o || /$LOG_REVSEPARATOR/o);                  last line if (/^----------------------------$/ || /^=========/);
                 $log{$rev} .= $_;                  $log{$rev} .= $_;
             }              }
             print "E:", $_ if ($verbose);              print "E:", $_ if ($verbose);
         }          }
         close($fh);          close(RCS);
         print "Done reading RCS file\n" if ($verbose);          print "Done reading RCS file\n" if ($verbose);
   
         @revorder = reverse sort {revcmp($a,$b)} @allrevisions;  
         print "Done sorting revisions",join(" ",@revorder),"\n" if ($verbose);  
   
 #  #
   # Sort the revisions into commit-date order.
           @revorder = sort {$date{$b} <=> $date{$a}} keys %date;
           print "Done sorting revisions\n" if ($verbose);
   #
 # HEAD is an artificial tag which is simply the highest tag number on the main  # HEAD is an artificial tag which is simply the highest tag number on the main
 # branch, unless there is a branch tag in the RCS file in which case it's the  # branch, unless there is a branch tag in the RCS file in which case it's the
 # highest revision on that branch.  Find it by looking through @revorder; it  # highest revision on that branch.  Find it by looking through @revorder; it
 # is the first commit listed on the appropriate branch.  # is the first commit listed on the appropriate branch.
 # This is not neccesary the same revision as marked as head in the RCS file.          $headrev = $curbranch || "1";
         my $headrev = $curbranch || "1";          revision:
         ($symrev{"MAIN"} = $headrev) =~ s/(\.?)(\d+)$/${1}0.$2/;          for ($i = 0; $i <= $#revorder; $i++) {
         foreach $rev (@revorder) {              if ($revorder[$i] =~ /^(\S*)\.\d+$/ && $headrev eq $1) {
             if ($rev =~ /^(\S*)\.\d+$/ && $headrev eq $1) {                  if ($revsym{$revorder[$i]}) {
                 $symrev{"HEAD"} = $rev;                      $revsym{$revorder[$i]} .= ", ";
                 last;                  }
                   $revsym{$revorder[$i]} .= "HEAD";
                   $symrev{"HEAD"} = $revorder[$i];
                   last revision;
             }              }
         }          }
         ($symrev{"HEAD"} = $headrev) =~ s/\.\d+$//  
             if (!defined($symrev{"HEAD"}));  
         print "Done finding HEAD\n" if ($verbose);          print "Done finding HEAD\n" if ($verbose);
 #  #
 # Now that we know all of the revision numbers, we can associate  # Now that we know all of the revision numbers, we can associate
Line 2031  sub readLog($;$) {
Line 564  sub readLog($;$) {
 # pass them to the form so that the same association doesn't have  # pass them to the form so that the same association doesn't have
 # to be built then.  # to be built then.
 #  #
         undef @branchnames;  # should make this a case-insensitive sort
         undef %branchpoint;          foreach (sort keys %symrev) {
         undef $sel;  
   
         foreach (reverse sort keys %symrev) {  
             $rev = $symrev{$_};              $rev = $symrev{$_};
             if ($rev =~ /^((.*)\.)0\.(\d+)$/) {              if ($rev =~ /^(\d+(\.\d+)+)\.0\.(\d+)$/) {
                 push(@branchnames, $_);                  push(@branchnames, $_);
                 #                  #
                 # A revision number of A.B.0.D really translates into                  # A revision number of A.B.0.D really translates into
Line 2046  sub readLog($;$) {
Line 576  sub readLog($;$) {
                 # If there is no branch A.B.D, then it translates into                  # If there is no branch A.B.D, then it translates into
                 # the head A.B .                  # the head A.B .
                 #                  #
                 # This reasoning also applies to the main branch A.B,                  $head = $1;
                 # with the branch number 0.A, with the exception that  
                 # it has no head to translate to if there is nothing on  
                 # the branch, but I guess this can never happen?  
                 #  
                 # Since some stupid people actually import/check in  
                 # files with version 0.X we assume that the above cannot  
                 # happen, and regard 0.X(.*) as a revision and not a branch.  
                 #  
                 $head = defined($2) ? $2 : "";  
                 $branch = $3;                  $branch = $3;
                 $branchrev = $head . ($head ne "" ? "." : "") . $branch;                  $regex = $head . "." . $branch;
                 my $regex;                  $regex =~ s/\./\./g;
                 $regex = quotemeta $branchrev;                  #             <
                   #           \____/
                 $rev = $head;                  $rev = $head;
   
                 foreach my $r (@revorder) {                  revision:
                     if ($r =~ /^${regex}\b/) {                  foreach $r (@revorder) {
                         $rev = $branchrev;                      if ($r =~ /^${regex}/) {
                         last;                          $rev = $head . "." . $branch;
                           last revision;
                     }                      }
                 }                  }
                 next if ($rev eq "");                  $revsym{$rev} .= ", " if ($revsym{$rev});
                 if ($rev ne $head && $head ne "") {                  $revsym{$rev} .= $_;
                   if ($rev ne $head) {
                     $branchpoint{$head} .= ", " if ($branchpoint{$head});                      $branchpoint{$head} .= ", " if ($branchpoint{$head});
                     $branchpoint{$head} .= $_;                      $branchpoint{$head} .= $_;
                 }                  }
             }              }
             $revsym{$rev} .= ", " if ($revsym{$rev});  
             $revsym{$rev} .= $_;  
             $sel .= "<OPTION VALUE=\"${rev}:${_}\">$_\n";              $sel .= "<OPTION VALUE=\"${rev}:${_}\">$_\n";
         }          }
         print "Done associating revisions with branches\n" if ($verbose);          print "Done associating revisions with branches\n" if ($verbose);
           print &html_header("CVS log for $where");
         my ($onlyonbranch, $onlybranchpoint);          ($upwhere = $where) =~ s|(Attic/)?[^/]+$||;
         if ($onlyonbranch = $input{'only_with_tag'}) {          print "Up to ", &link($upwhere,$scriptname . "/" . $upwhere . $query);
             $onlyonbranch = $symrev{$onlyonbranch};          print "<BR>\n";
             if ($onlyonbranch =~ s/\.0\././) {          print "<A HREF=\"#diff\">Request diff between arbitrary revisions</A>\n";
                 ($onlybranchpoint = $onlyonbranch) =~ s/\.\d+$//;          print "<HR NOSHADE>\n";
             }          if ($curbranch) {
             else {              print "Default branch is ";
                 $onlybranchpoint = $onlyonbranch;              print ($revsym{$curbranch} || $curbranch);
             }          } else {
             if (!defined($onlyonbranch) || $onlybranchpoint eq "") {              print "No default branch";
                 fatal("404 Tag not found","Tag $input{'only_with_tag'} not defined");  
             }  
         }          }
           print "<BR><HR NOSHADE>\n";
   # The other possible U.I. I can see is to have each revision be hot
   # and have the first one you click do ?r1=foo
   # and since there's no r2 it keeps going & the next one you click
   # adds ?r2=foo and performs the query.
   # I suppose there's no reason we can't try both and see which one
   # people prefer...
   
         undef @revisions;          for ($i = 0; $i <= $#revorder; $i++) {
               $_ = $revorder[$i];
         foreach (@allrevisions) {  
             ($br = $_) =~ s/\.\d+$//;              ($br = $_) =~ s/\.\d+$//;
             ($brp = $br) =~ s/\.\d+$//;  
             next if ($onlyonbranch && $br ne $onlyonbranch &&              next if ($onlyonbranch && $br ne $onlyonbranch &&
                                         $_ ne $onlybranchpoint);                                              $_ ne $onlybranchpoint);
             unshift(@revisions,$_);  
         }  
   
         if ($logsort eq "date") {  
             # Sort the revisions in commit order an secondary sort on revision  
             # (secondary sort needed for imported sources, or the first main  
             # revision gets before the same revision on the 1.1.1 branch)  
             @revdisplayorder = sort {$date{$b} <=> $date{$a} || -revcmp($a, $b)} @revisions;  
         }  
         elsif ($logsort eq "rev") {  
             # Sort the revisions in revision order, highest first  
             @revdisplayorder = reverse sort {revcmp($a,$b)} @revisions;  
         }  
         else {  
             # No sorting. Present in the same order as rlog / cvs log  
             @revdisplayorder = @revisions;  
         }  
   
 }  
   
 sub printDiffLinks($$) {  
     my($text, $url) = @_;  
     my @extra;  
   
     local $_;  
     for ($DIFFTYPES{$defaultDiffType}{'colored'} ? qw(u) : qw(h)) {  
         my $f = $_ eq $defaultDiffType ? '' : $_;  
   
         push @extra, &link(lc $DIFFTYPES{$_}{'descr'}, "$url&f=$f");  
     }  
   
     print &link($text, $url), ' (', join(', ', @extra), ')';  
 }  
   
 sub printLog($;$) {  
         my ($link, $br, $brp);  
         ($_,$link) = @_;  
         ($br = $_) =~ s/\.\d+$//;  
         ($brp = $br) =~ s/\.?\d+$//;  
         my ($isDead, $prev);  
   
         $link = 1 if (!defined($link));  
         $isDead = ($state{$_} eq "dead");  
   
         if ($link && !$isDead) {  
             my ($filename);  
             ($filename = $where) =~ s/^.*\///;  
             my ($fileurl) = urlencode($filename);  
             print "<a NAME=\"rev$_\"></a>";              print "<a NAME=\"rev$_\"></a>";
             if (defined($revsym{$_})) {              foreach $sym (split(", ", $revsym{$_})) {
                 foreach my $sym (split(", ", $revsym{$_})) {                  print "<a NAME=\"$sym\"></a>";
                     print "<a NAME=\"$sym\"></a>";  
                 }  
             }              }
             if (defined($revsym{$br}) && $revsym{$br} && !defined($nameprinted{$br})) {              if ($revsym{$br} && !$nameprinted{$br}) {
                 foreach my $sym (split(", ", $revsym{$br})) {                  foreach $sym (split(", ", $revsym{$br})) {
                     print "<a NAME=\"$sym\"></a>";                      print "<a NAME=\"$sym\"></a>";
                 }                  }
                 $nameprinted{$br} = 1;                  $nameprinted{$br}++;
             }              }
             print "\n Revision ";              print "\n";
             &download_link($fileurl, $_, $_,              print "<A HREF=\"$scriptwhere?rev=$_" .
                            $defaultViewable ? "text/x-cvsweb-markup" : undef);                  &cvsroot . "\"><b>$_</b></A>";
             if ($defaultViewable) {              if (/^1\.1\.1\.\d+$/) {
                 print " / (";                  print " <i>(vendor branch)</i>";
                 &download_link($fileurl, $_, "download", $mimetype);  
                 print ")";  
             }              }
             if (not $defaultTextPlain) {              print " <i>" . &ctime($date{$_}) . " UTC</i> by ";
                 print " / (";              print "<i>" . $author{$_} . "</i>\n";
                 &download_link($fileurl, $_, "as text", "text/plain");              if ($revsym{$_}) {
                 print ")";                  print "<BR>CVS Tags: <b>$revsym{$_}</b>";
             }              }
             if (!$defaultViewable) {              if ($revsym{$br})  {
                 print " / (";                  if ($revsym{$_}) {
                 &download_link($fileurl, $_, "view", "text/x-cvsweb-markup");                      print "; ";
                 print ")";                  } else {
                       print "<BR>";
                   }
                   print "Branch: <b>$revsym{$br}</b>\n";
             }              }
             if ($allow_annotate) {              if ($branchpoint{$_}) {
                 print " - ";                  if ($revsym{$br} || $revsym{$_}) {
                 print &link('annotate',                      print "; ";
                             sprintf('%s/%s?annotate=%s%s',                  } else {
                                     $scriptname,                      print "<BR>";
                                     urlencode($where),  
                                     $_,  
                                     $barequery));  
             }  
             # Plus a select link if enabled, and this version isn't selected  
             if ($allow_version_select) {  
                 if ((!defined($input{"r1"}) || $input{"r1"} ne $_)) {  
                     print " - ";  
                     print &link('[select for diffs]',  
                                 sprintf('%s?r1=%s%s',  
                                         $scriptwhere,  
                                         $_,  
                                         $barequery));  
                 }                  }
                 else {                  print "Branch point for: <b>$branchpoint{$_}</b>\n";
                     print " - <b>[selected]</b>";  
                 }  
             }              }
         }              # Find the previous revision on this branch.
         else {              @prevrev = split(/\./, $_);
             print "Revision <B>$_</B>";              if (--$prevrev[$#prevrev] == 0) {
         }  
         if (/^1\.1\.1\.\d+$/) {  
             print " <i>(vendor branch)</i>";  
         }  
         if (defined @mytz) {  
             my ($est) = $mytz[(localtime($date{$_}))[8]];  
             print ", <i>", scalar localtime($date{$_}), " $est</i> (";  
         } else {  
             print ", <i>", scalar gmtime($date{$_}), " UTC</i> (";  
         }  
         print readableTime(time() - $date{$_},1), " ago)";  
         print " by ";  
         print "<i>", $author{$_}, "</i>\n";  
         print "<BR>Branch: <b>",$link?link_tags($revsym{$br}):$revsym{$br},"</b>\n"  
             if ($revsym{$br});  
         print "<BR>CVS Tags: <b>",$link?link_tags($revsym{$_}):$revsym{$_},"</b>"  
             if ($revsym{$_});  
         print "<BR>Branch point for: <b>",$link?link_tags($branchpoint{$_}):$branchpoint{$_},"</b>\n"  
             if ($branchpoint{$_});  
         # Find the previous revision  
         my @prevrev = split(/\./, $_);  
         do {  
             if (--$prevrev[$#prevrev] <= 0) {  
                 # If it was X.Y.Z.1, just make it X.Y                  # If it was X.Y.Z.1, just make it X.Y
                 pop(@prevrev);                  if ($#prevrev > 1) {
                 pop(@prevrev);                      pop(@prevrev);
                       pop(@prevrev);
                   } else {
                       # It was rev 1.1 (XXX does CVS use revisions
                       # greater than 1.x?)
                       if ($prevrev[0] != 1) {
                           print "<i>* I can't figure out the previous revision! *</i>\n";
                       }
                   }
             }              }
             $prev = join(".", @prevrev);              if ($prevrev[$#prevrev] != 0) {
         } until (defined($date{$prev}) || $prev eq "");                  $prev = join(".", @prevrev);
         if ($prev ne "") {                  print "<BR><A HREF=\"${scriptwhere}.diff?r1=$prev";
             if ($difflines{$_}) {                  print "&r2=$_" . &cvsroot . "\">Diffs to $prev</A>\n";
                 print "<BR>Changes since <b>$prev: $difflines{$_} lines</b>";                  #
             }                  # Plus, if it's on a branch, and it's not a vendor branch,
         }                  # offer to diff with the immediately-preceding commit if it
         if ($isDead) {                  # is not the previous revision as calculated above
             print "<BR><B><I>FILE REMOVED</I></B>\n";                  # and if it is on the HEAD (or at least on a higher branch)
         }                  # (e.g. change gets committed and then brought
         elsif ($link) {                  # over to -stable)
             my %diffrev = ();                  if (!/^1\.1\.1\.\d+$/ && ($i != $#revorder) &&
             $diffrev{$_} = 1;                                          ($prev ne $revorder[$i+1])) {
             $diffrev{""} = 1;                      @tmp1 = split(/\./, $revorder[$i+1]);
             print "<BR>Diff";                      @tmp2 = split(/\./, $_);
             #  
             # Offer diff to previous revision  
             if ($prev) {  
                 $diffrev{$prev} = 1;  
   
                 my $url = sprintf('%s.diff?r1=%s&r2=%s%s',  
                                   $scriptwhere,  
                                   $prev,  
                                   $_,  
                                   $barequery);  
   
                 print " to previous ";  
                 printDiffLinks($prev, $url);  
             }  
             #  
             # Plus, if it's on a branch, and it's not a vendor branch,  
             # offer a diff with the branch point.  
             if ($revsym{$brp} && !/^1\.1\.1\.\d+$/ && !defined($diffrev{$brp})) {  
                 my $url = sprintf('%s.diff?r1=%s&r2=%s%s',  
                                   $scriptwhere,  
                                   $brp,  
                                   $_,  
                                   $barequery);  
   
                 print " to branchpoint ";  
                 printDiffLinks($brp, $url);  
             }  
             #  
             # Plus, if it's on a branch, and it's not a vendor branch,  
             # offer to diff with the next revision of the higher branch.  
             # (e.g. change gets committed and then brought  
             # over to -stable)  
             if (/^\d+\.\d+\.\d+/ && !/^1\.1\.1\.\d+$/) {  
                 my ($i,$nextmain);  
                 for ($i = 0; $i < $#revorder && $revorder[$i] ne $_; $i++){}  
                 my (@tmp2) = split(/\./, $_);  
                 for ($nextmain = ""; $i > 0; $i--) {  
                     my ($next) = $revorder[$i-1];  
                     my (@tmp1) = split(/\./, $next);  
                     if ($#tmp1 < $#tmp2) {                      if ($#tmp1 < $#tmp2) {
                         $nextmain = $next;                          print "; <A HREF=\"${scriptwhere}.diff?r1=$revorder[$i+1]";
                         last;                          print "&r2=$_" . &cvsroot .
                               "\">Diffs to $revorder[$i+1]</A>\n";
                     }                      }
                     # Only the highest version on a branch should have  
                     # a diff for the "next main".  
                     last if (join(".",@tmp1[0..$#tmp1-1])  
                              eq join(".",@tmp2[0..$#tmp1-1]));  
                 }                  }
                 if (!defined($diffrev{$nextmain})) {  
                     $diffrev{$nextmain} = 1;  
   
                     my $url = sprintf('%s.diff?r1=%s&r2=%s%s',  
                                       $scriptwhere,  
                                       $nextmain,  
                                       $_,  
                                       $barequery);  
   
                     print " next main ";  
                     printDiffLinks($nextmain, $url);  
                 }  
             }              }
             # Plus if user has selected only r1, then present a link              if ($state{$_} eq "dead") {
             # to make a diff to that revision                  print "<BR><B><I>FILE REMOVED</I></B>\n";
             if (defined($input{"r1"}) && !defined($diffrev{$input{"r1"}})) {  
                 $diffrev{$input{"r1"}} = 1;  
   
                 my $url = sprintf('%s.diff?r1=%s&r2=%s%s',  
                                   $scriptwhere,  
                                   $input{'r1'},  
                                   $_,  
                                   $barequery);  
   
                 print " to selected ";  
                 printDiffLinks($input{'r1'}, $url);  
             }              }
               print "<PRE>\n";
               print &htmlify($log{$_}, 1);
               print "</PRE><HR NOSHADE>\n";
         }          }
         print "<PRE>\n";  
         print &htmlify($log{$_}, 1);  
         print "</PRE>\n";  
 }  
   
 sub doLog($) {  
         my($fullname) = @_;  
         my ($diffrev, $upwhere, $filename, $backurl);  
   
         readLog($fullname);  
   
         html_header("CVS log for $where");  
         ($upwhere = $where) =~ s|(Attic/)?[^/]+$||;  
         ($filename = $where) =~ s|^.*/||;  
         $backurl = $scriptname . "/" . urlencode($upwhere) . $query;  
         print &link($backicon, "$backurl#$filename"),  
           " <b>Up to ", &clickablePath($upwhere, 1), "</b><p>\n";  
         print &link('Request diff between arbitrary revisions', '#diff');  
         print '<HR NOSHADE>';  
   
         if ($curbranch) {  
             print "Default branch: ", ($revsym{$curbranch} || $curbranch);  
         }  
         else {  
             print "No default branch";  
         }  
         print "<BR>\n";  
         if ($input{only_with_tag}) {  
             print "Current tag: $input{only_with_tag}<BR>\n";  
         }  
   
         undef %nameprinted;  
   
         for (my $i = 0; $i <= $#revdisplayorder; $i++) {  
             print "<HR size=1 NOSHADE>";  
             printLog($revdisplayorder[$i]);  
         }  
   
         print "<HR NOSHADE>";  
         print "<A NAME=diff>\n";          print "<A NAME=diff>\n";
         print "This form allows you to request diff's between any two\n";          print "This form allows you to request diff's between any two\n";
         print "revisions of a file.  You may select a symbolic revision\n";          print "revisions of a file.  You may select a symbolic revision\n";
         print "name using the selection box or you may type in a numeric\n";          print "name using the selection box or you may type in a numeric\n";
         print "name using the type-in text box.\n";          print "name using the type-in text box.\n";
         print "</A><P>\n";          print "</A><P>\n";
         print "<FORM METHOD=\"GET\" ACTION=\"${scriptwhere}.diff\" NAME=\"diff_select\">\n";          print "<FORM METHOD=\"GET\" ACTION=\"${scriptwhere}.diff\">\n";
         foreach (@stickyvars) {          print "<INPUT TYPE=HIDDEN NAME=\"cvsroot\" VALUE=\"$cvstree\">\n"
             printf('<INPUT TYPE=HIDDEN NAME="%s" VALUE="%s">',  $_, $input{$_})               if &cvsroot;
                 if (defined($input{$_})          print "Diffs between \n";
                     && ((!defined($DEFAULTVALUE{$_})  
                          || $input{$_} ne $DEFAULTVALUE{$_})  
                         && $input{$_} ne ""));  
         }  
         print "<TABLE><TR>\n";  
         print "<TD align=right>Diffs between \n";  
         print "<SELECT NAME=\"r1\">\n";          print "<SELECT NAME=\"r1\">\n";
         print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";          print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";
         print $sel;          print $sel;
         print "</SELECT>\n";          print "</SELECT>\n";
         $diffrev = $revdisplayorder[$#revdisplayorder];          print "<INPUT TYPE=\"TEXT\" NAME=\"tr1\" VALUE=\"$revorder[$#revorder]\">\n";
         $diffrev = $input{"r1"} if (defined($input{"r1"}));          print " and \n";
         print "<INPUT TYPE=\"TEXT\" SIZE=\"$inputTextSize\" NAME=\"tr1\" VALUE=\"$diffrev\" onChange='document.diff_select.r1.selectedIndex=0'></TD>";  
         print "<TD><BR></TD></TR>\n";  
         print "<TR><TD align=right>and \n";  
         print "<SELECT NAME=\"r2\">\n";          print "<SELECT NAME=\"r2\">\n";
         print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";          print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";
         print $sel;          print $sel;
         print "</SELECT>\n";          print "</SELECT>\n";
         $diffrev = $revdisplayorder[0];          print "<INPUT TYPE=\"TEXT\" NAME=\"tr2\" VALUE=\"$revorder[0]\">\n";
         $diffrev = $input{"r2"} if (defined($input{"r2"}));          print "<BR><INPUT TYPE=RADIO NAME=\"f\" VALUE=u CHECKED>Unidiff<br>\n";
         print "<INPUT TYPE=\"TEXT\" SIZE=\"$inputTextSize\" NAME=\"tr2\" VALUE=\"$diffrev\" onChange='document.diff_select.r2.selectedIndex=0'></TD>";          print "<INPUT TYPE=RADIO NAME=\"f\" VALUE=c>Context diff<br>\n";
         print "<TD><INPUT TYPE=SUBMIT VALUE=\"  Get Diffs  \"></TD>\n";          print "<INPUT TYPE=RADIO NAME=\"f\" VALUE=s>Side-by-Side<br>\n";
           print "<INPUT TYPE=SUBMIT VALUE=\"Get Diffs\">\n";
         print "</FORM>\n";          print "</FORM>\n";
         print "</TR></TABLE>\n";  
         print "<HR noshade>\n";          print "<HR noshade>\n";
         print "<TABLE>";          print "<A name=branch>\n";
           print "You may select to see revision information from only\n";
           print "a single branch.\n";
           print "</A><P>\n";
         print "<FORM METHOD=\"GET\" ACTION=\"$scriptwhere\">\n";          print "<FORM METHOD=\"GET\" ACTION=\"$scriptwhere\">\n";
         print "<TR><TD align=right>Preferred Diff type:</TD>";          print qq{<input type=hidden name=cvsroot value=$cvstree>\n}
         print "<TD>";               if &cvsroot;
         printDiffSelect($use_java_script);          print "Branch: \n";
         print "</TD><TD></TD></TR>\n";          print "<SELECT NAME=\"only_on_branch\">\n";
         if (@branchnames) {          print "<OPTION VALUE=\"\"";
             print "<TR><TD align=right>View only Branch:</TD>";          print " SELECTED" if ($input{"only_on_branch"} eq "");
             print "<TD>";          print ">Show all branches\n";
             print "<A name=branch></A>\n";          foreach (sort @branchnames) {
             print "<SELECT NAME=\"only_with_tag\"";  
             print " onchange=\"submit()\"" if ($use_java_script);  
             print ">\n";  
             print "<OPTION VALUE=\"\"";  
             print " SELECTED" if (defined($input{"only_with_tag"}) &&  
                 $input{"only_with_tag"} eq "");  
             print ">Show all branches\n";  
             foreach (reverse sort @branchnames) {  
                 print "<OPTION";                  print "<OPTION";
                 print " SELECTED" if (defined($input{"only_with_tag"})                  print " SELECTED" if ($input{"only_on_branch"} eq $_);
                         && $input{"only_with_tag"} eq $_);  
                 print ">${_}\n";                  print ">${_}\n";
             }  
             print "</SELECT></TD><TD></TD></TR>\n";  
         }          }
         foreach (@stickyvars) {          print "</SELECT>\n";
             next if ($_ eq "f");          print "<INPUT TYPE=SUBMIT VALUE=\"View Branch\">\n";
             next if ($_ eq "only_with_tag");  
             next if ($_ eq "logsort");  
             print "<INPUT TYPE=HIDDEN NAME=\"$_\" VALUE=\"$input{$_}\">\n"  
                 if (defined($input{$_})  
                     && (!defined($DEFAULTVALUE{$_})  
                         || $input{$_} ne $DEFAULTVALUE{$_})  
                     && $input{$_} ne "");  
         }  
         print "<TR><TD align=right>";  
         print "<A name=logsort></A>\n";  
         print "Sort log by:</TD>";  
         print "<TD>";  
         printLogSortSelect($use_java_script);  
         print "</TD>";  
         print "<TD><INPUT TYPE=SUBMIT VALUE=\"  Set  \"></TD>";  
         print "</FORM>\n";          print "</FORM>\n";
         print "</TR></TABLE>";  
         print &html_footer;          print &html_footer;
         print "</BODY></HTML>\n";          print "</BODY></HTML>\n";
 }  }
   
 sub flush_diff_rows($$$$) {  sub cvsroot {
     my $j;      return '' if $cvstree eq $cvstreedefault;
     my ($leftColRef,$rightColRef,$leftRow,$rightRow) = @_;      return "&cvsroot=" . $cvstree;
     if ($state eq "PreChangeRemove") {          # we just got remove-lines before  
       for ($j = 0 ; $j < $leftRow; $j++) {  
           print  "<tr><td bgcolor=\"$diffcolorRemove\">@$leftColRef[$j]</td>";  
           print  "<td bgcolor=\"$diffcolorEmpty\">&nbsp;</td></tr>\n";  
       }  
     }  
     elsif ($state eq "PreChange") {             # state eq "PreChange"  
       # we got removes with subsequent adds  
       for ($j = 0; $j < $leftRow || $j < $rightRow ; $j++) {  # dump out both cols  
           print  "<tr>";  
           if ($j < $leftRow) {  
               print  "<td bgcolor=\"$diffcolorChange\">@$leftColRef[$j]</td>";  
           }  
           else {  
               print  "<td bgcolor=\"$diffcolorDarkChange\">&nbsp;</td>";  
           }  
           if ($j < $rightRow) {  
               print  "<td bgcolor=\"$diffcolorChange\">@$rightColRef[$j]</td>";  
           }  
           else {  
               print  "<td bgcolor=\"$diffcolorDarkChange\">&nbsp;</td>";  
           }  
           print  "</tr>\n";  
       }  
     }  
 }  
   
 ##  
 # Function to generate Human readable diff-files  
 # human_readable_diff(String revision_to_return_to);  
 ##  
 sub human_readable_diff($){  
   my ($difftxt, $where_nd, $filename, $pathname, $scriptwhere_nd);  
   my ($fh, $rev) = @_;  
   my ($date1, $date2, $r1d, $r2d, $r1r, $r2r, $rev1, $rev2, $sym1, $sym2);  
   my (@rightCol, @leftCol);  
   
   ($where_nd = $where) =~ s/.diff$//;  
   ($filename = $where_nd) =~ s/^.*\///;  
   ($pathname = $where_nd) =~ s/(Attic\/)?[^\/]*$//;  
   ($scriptwhere_nd = $scriptwhere) =~ s/.diff$//;  
   
   navigateHeader($scriptwhere_nd, $pathname, $filename, $rev, "diff");  
   
   # Read header to pick up read revision and date, if possible  
   while (<$fh>) {  
       ($r1d,$r1r) = /\t(.*)\t(.*)$/ if (/^--- /);  
       ($r2d,$r2r) = /\t(.*)\t(.*)$/ if (/^\+\+\+ /);  
       last if (/^\+\+\+ /);  
   }  
   if (defined($r1r) && $r1r =~ /^(\d+\.)+\d+$/) {  
     $rev1 = $r1r;  
     $date1 = $r1d;  
   }  
   if (defined($r2r) && $r2r =~ /^(\d+\.)+\d+$/) {  
     $rev2 = $r2r;  
     $date2 = $r2d;  
   }  
   
   print "<h3 align=center>Diff for /$where_nd between version $rev1 and $rev2</h3>\n",  
     "<table border=0 cellspacing=0 cellpadding=0 width=\"100%\">\n",  
       "<tr bgcolor=\"#ffffff\">\n",  
         "<th width=\"50%\" valign=TOP>",  
           "version $rev1";  
   print ", $date1" if (defined($date1));  
   print "<br>Tag: $sym1\n" if ($sym1);  
   print "</th>\n",  
     "<th width=\"50%\" valign=TOP>",  
       "version $rev2";  
   print ", $date2" if (defined($date2));  
   print "<br>Tag: $sym2\n" if ($sym1);  
   print "</th>\n";  
   
   my $fs = "<font face=\"$difffontface\" size=\"$difffontsize\">";  
   my $fe = "</font>";  
   
   my $leftRow = 0;  
   my $rightRow = 0;  
   my ($oldline, $newline, $funname, $diffcode, $rest);  
   
   # Process diff text  
   # The diffrows are could make excellent use of  
   # cascading style sheets because we've to set the  
   # font and color for each row. anyone ...?  
   ####  
   
   # prefetch several lines  
   my @buf = head($fh);  
   
   my %d = scan_directives(@buf);  
   
   while (@buf || !eof($fh)) {  
       $difftxt = @buf ? shift @buf : <$fh>;  
   
       if ($difftxt =~ /^@@/) {  
           ($oldline,$newline,$funname) = $difftxt =~ /@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/;  
           print  "<tr bgcolor=\"$diffcolorHeading\"><td width=\"50%\">";  
           print  "<table width=\"100%\" border=1 cellpadding=5><tr><td><b>Line $oldline</b>";  
           print  "&nbsp;<font size=-1>$funname</font></td></tr></table>";  
           print  "</td><td width=\"50%\">";  
           print  "<table width=\"100%\" border=1 cellpadding=5><tr><td><b>Line $newline</b>";  
           print  "&nbsp;<font size=-1>$funname</font></td></tr></table>";  
           print  "</td>\n";  
           $state = "dump";  
           $leftRow = 0;  
           $rightRow = 0;  
       }  
       else {  
           ($diffcode,$rest) = $difftxt =~ /^([-+ ])(.*)/;  
           $_ = spacedHtmlText($rest, $d{'tabstop'});  
   
           # Add fontface, size  
           $_ = "$fs&nbsp;$_$fe";  
   
           #########  
           # little state machine to parse unified-diff output (Hen, zeller@think.de)  
           # in order to get some nice 'ediff'-mode output  
           # states:  
           #  "dump"             - just dump the value  
           #  "PreChangeRemove"  - we began with '-' .. so this could be the start of a 'change' area or just remove  
           #  "PreChange"        - okey, we got several '-' lines and moved to '+' lines -> this is a change block  
           ##########  
   
           if ($diffcode eq '+') {  
               if ($state eq "dump") {  # 'change' never begins with '+': just dump out value  
                   print  "<tr><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td><td bgcolor=\"$diffcolorAdd\">$_</td></tr>\n";  
               }  
               else {                   # we got minus before  
                   $state = "PreChange";  
                   $rightCol[$rightRow++] = $_;  
               }  
           }  
           elsif ($diffcode eq '-') {  
               $state = "PreChangeRemove";  
               $leftCol[$leftRow++] = $_;  
         }  
         else {  # empty diffcode  
             flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;  
               print  "<tr><td>$_</td><td>$_</td></tr>\n";  
               $state = "dump";  
               $leftRow = 0;  
               $rightRow = 0;  
           }  
       }  
   }  
   flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;  
   
   # state is empty if we didn't have any change  
   if (!$state) {  
       print "<tr><td colspan=2>&nbsp;</td></tr>";  
       print "<tr bgcolor=\"$diffcolorEmpty\" >";  
       print "<td colspan=2 align=center><b>- No viewable Change -</b></td></tr>";  
   }  
   print  "</table>";  
   close($fh);  
   
   print "<br><hr noshade width=\"100%\">\n";  
   
   print "<table border=0>";  
   
   print "<tr><td>";  
   # print legend  
   print "<table border=1><tr><td>";  
   print  "Legend:<br><table border=0 cellspacing=0 cellpadding=1>\n";  
   print  "<tr><td align=center bgcolor=\"$diffcolorRemove\">Removed from v.$rev1</td><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td></tr>";  
   print  "<tr bgcolor=\"$diffcolorChange\"><td align=center colspan=2>changed lines</td></tr>";  
   print  "<tr><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td><td align=center bgcolor=\"$diffcolorAdd\">Added in v.$rev2</td></tr>";  
   print  "</table></td></tr></table>\n";  
   
   print "<td>";  
   # Print format selector  
   print "<FORM METHOD=\"GET\" ACTION=\"${scriptwhere}\">\n";  
   foreach my $var (keys %input) {  
     next if ($var eq "f");  
     next if (defined($DEFAULTVALUE{$var})  
              && $DEFAULTVALUE{$var} eq $input{$var});  
     print "<INPUT TYPE=HIDDEN NAME=\"",urlencode($var),"\" VALUE=\"",  
             urlencode($input{$var}),"\">\n";  
   }  
   printDiffSelect($use_java_script);  
   print "<INPUT TYPE=SUBMIT VALUE=\"Show\">\n";  
   print "</FORM>\n";  
   print "</td>";  
   
   print "</tr></table>";  
 }  
   
 sub navigateHeader($$$$$) {  
     my ($swhere,$path,$filename,$rev,$title) = @_;  
     $swhere = "" if ($swhere eq $scriptwhere);  
     $swhere = urlencode($filename) if ($swhere eq "");  
     print "<\!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">";  
     print "<HTML>\n<HEAD>\n";  
     print '<!-- CVSweb $zRevision: 1.104 $  $kRevision: 1.41 $ -->';  
     print "\n<TITLE>$path$filename - $title - $rev</TITLE></HEAD>\n";  
     print  "$body_tag_for_src\n";  
     print "<table width=\"100%\" border=0 cellspacing=0 cellpadding=1 bgcolor=\"$navigationHeaderColor\">";  
     print "<tr valign=bottom><td>";  
     print &link($backicon, "$swhere$query#rev$rev");  
     print "</a> <b>Return to ", &link("$filename","$swhere$query#rev$rev")," CVS log";  
     print "</b> $fileicon</td>";  
   
     print "<td align=right>$diricon <b>Up to ", &clickablePath($path, 1), "</b></td>";  
     print "</tr></table>";  
 }  
   
 sub plural_write($$) {  
     my ($num,$text) = @_;  
     if ($num != 1) {  
         $text .= "s";  
     }  
     if ($num > 0) {  
         return join(' ', $num, $text);  
     }  
     else {  
         return "";  
     }  
 }  
   
 ##  
 # print readable timestamp in terms of  
 # '..time ago'  
 # H. Zeller <zeller@think.de>  
 ##  
 sub readableTime($$) {  
     my ($i, $break, $retval);  
     my ($secs,$long) = @_;  
   
     # this function works correct for time >= 2 seconds  
     if ($secs < 2) {  
         return "very little time";  
     }  
   
     my %desc = (1 , 'second',  
                    60, 'minute',  
                    3600, 'hour',  
                    86400, 'day',  
                    604800, 'week',  
                    2628000, 'month',  
                    31536000, 'year');  
     my @breaks = sort {$a <=> $b} keys %desc;  
     $i = 0;  
     while ($i <= $#breaks && $secs >= 2 * $breaks[$i]) {  
         $i++;  
     }  
     $i--;  
     $break = $breaks[$i];  
     $retval = plural_write(int ($secs / $break), $desc{$break});  
   
     if ($long == 1 && $i > 0) {  
         my $rest = $secs % $break;  
         $i--;  
         $break = $breaks[$i];  
         my $resttime = plural_write(int ($rest / $break),  
                                 $desc{$break});  
         if ($resttime) {  
             $retval .= ", $resttime";  
         }  
     }  
   
     return $retval;  
 }  
   
 ##  
 # clickablePath(String pathname, boolean last_item_clickable)  
 #  
 # returns a html-ified path whereas each directory is a link for  
 # faster navigation. last_item_clickable controls whether the  
 # basename (last directory/file) is a link as well  
 ##  
 sub clickablePath($$) {  
     my ($pathname,$clickLast) = @_;  
     my $retval = '';  
   
     if ($pathname eq '/') {  
         # this should never happen - chooseCVSRoot() is  
         # intended to do this  
         $retval = "[$cvstree]";  
     }  
     else {  
         $retval .= ' ' . &link("[$cvstree]", sprintf('%s/%s#dirlist',  
                                                      $scriptname,  
                                                      $query));  
         my $wherepath = '';  
         my ($lastslash) = $pathname =~ m|/$|;  
         foreach (split(/\//, $pathname)) {  
             $retval .= " / ";  
             $wherepath .= "/$_";  
             my ($last) = "$wherepath/" eq "/$pathname"  
                 || $wherepath eq "/$pathname";  
             if ($clickLast || !$last) {  
                 $retval .= &link($_, join('',  
                                           $scriptname,  
                                           urlencode($wherepath),  
                                           (!$last || $lastslash ? '/' : ''),  
                                           $query,  
                                           (!$last || $lastslash ? "#dirlist" : "")));  
             }  
             else { # do not make a link to the current dir  
                 $retval .= $_;  
             }  
         }  
     }  
     return $retval;  
 }  
   
 sub chooseCVSRoot() {  
     my @foo;  
     foreach (sort keys %CVSROOT) {  
         if (-d $CVSROOT{$_}) {  
             push(@foo, $_);  
         }  
     }  
     if (@foo > 1) {  
         my ($k);  
         print "<form method=\"GET\" action=\"${scriptwhere}\">\n";  
         foreach $k (keys %input) {  
             print "<input type=hidden NAME=$k VALUE=$input{$k}>\n"  
                 if ($input{$k}) && ($k ne "cvsroot");  
         }  
         # Form-Elements look wierd in Netscape if the background  
         # isn't gray and the form elements are not placed  
         # within a table ...  
         print "<table><tr>";  
         print "<td>CVS Root:</td>";  
         print "<td>\n<select name=\"cvsroot\"";  
         print " onchange=\"submit()\"" if ($use_java_script);  
         print ">\n";  
         foreach $k (@foo) {  
             print "<option value=\"$k\"";  
             print " selected" if ($k eq $cvstree);  
             print ">", ($CVSROOTdescr{$k} ? $CVSROOTdescr{$k} : $k), "</option>\n";  
         }  
         print "</select>\n</td>";  
         print "<td><input type=submit value=\"Go\"></td>";  
         print "</tr></table></form>";  
     }  
     else {  
         # no choice ..  
         print "CVS Root: <b>[$cvstree]</b>";  
     }  
 }  
   
 sub chooseMirror() {  
     my ($mirror,$moremirrors);  
     $moremirrors = 0;  
     # This code comes from the original BSD-cvsweb  
     # and may not be useful for your site; If you don't  
     # set %MIRRORS this won't show up, anyway  
     #  
     # Should perhaps exlude the current site somehow..  
     if (keys %MIRRORS) {  
         print "\nThis cvsweb is mirrored in:\n";  
         foreach $mirror (keys %MIRRORS) {  
             print ", " if ($moremirrors);  
             print &link(htmlquote($mirror),$MIRRORS{$mirror});  
             $moremirrors = 1;  
         }  
         print "<p>\n";  
     }  
 }  
   
 sub fileSortCmp() {  
     my ($comp) = 0;  
     my ($c,$d,$af,$bf);  
   
     ($af = $a) =~ s/,v$//;  
     ($bf = $b) =~ s/,v$//;  
     my ($rev1,$date1,$log1,$author1,$filename1) = @{$fileinfo{$af}}  
         if (defined($fileinfo{$af}));  
     my ($rev2,$date2,$log2,$author2,$filename2) = @{$fileinfo{$bf}}  
         if (defined($fileinfo{$bf}));  
   
     if (defined($filename1) && defined($filename2) && $af eq $filename1 && $bf eq $filename2) {  
         # Two files  
         $comp = -revcmp($rev1, $rev2) if ($byrev && $rev1 && $rev2);  
         $comp = ($date2 <=> $date1) if ($bydate && $date1 && $date2);  
         $comp = ($log1 cmp $log2) if ($bylog && $log1 && $log2);  
         $comp = ($author1 cmp $author2) if ($byauthor && $author1 && $author2);  
     }  
     if ($comp == 0) {  
         # Directories first, then sorted on name if no other sort critera  
         # available.  
         my $ad = ((-d "$fullname/$a")?"D":"F");  
         my $bd = ((-d "$fullname/$b")?"D":"F");  
         ($c=$a) =~ s|.*/||;  
         ($d=$b) =~ s|.*/||;  
         $comp = ("$ad$c" cmp "$bd$d");  
     }  
     return $comp;  
 }  
   
 # make A url for downloading  
 sub download_url($$;$) {  
     my ($url,$revision,$mimetype) = @_;  
   
     $revision =~ s/\.0\././;  
   
     if (defined($checkoutMagic)  
         && (!defined($mimetype) || $mimetype ne "text/x-cvsweb-markup")) {  
         my $path = $where;  
         $path =~ s|/[^/]*$|/|;  
         $url = "$scriptname/$checkoutMagic/${path}$url";  
     }  
     $url .= "?rev=$revision";  
     $url .= '&content-type=' . urlencode($mimetype) if (defined($mimetype));  
   
     $url;  
 }  
   
 # Presents a link to download the  
 # selected revision  
 sub download_link($$$;$) {  
     my ($url, $revision, $textlink, $mimetype) = @_;  
     my ($fullurl) = download_url($url, $revision, $mimetype);  
   
     printf '<A HREF="%s"', htmlquote("$fullurl$barequery");  
   
     if ($open_extern_window && (!defined($mimetype) || $mimetype ne "text/x-cvsweb-markup")) {  
         print ' target="cvs_checkout"';  
         # we should have  
         #   'if (document.cvswin==null) document.cvswin=window.open(...'  
         # in order to allow the user to resize the window; otherwise  
         # the user may resize the window, but on next checkout - zap -  
         # its original (configured s. cvsweb.conf) size is back again  
         # .. annoying (if $extern_window_(width|height) is defined)  
         # but this if (..) solution is far from perfect  
         # what we need to do as well is  
         # 1) save cvswin in an invisible frame that always exists  
         #    (document.cvswin will be void on next load)  
         # 2) on close of the cvs_checkout - window set the cvswin  
         #    variable to 'null' again - so that it will be  
         #    reopenend with the configured size  
         # anyone a JavaScript programmer ?  
         # .. so here without if (..):  
         # currently, the best way is to comment out the size parameters  
         # ($extern_window...) in cvsweb.conf.  
         if ($use_java_script) {  
             my @attr = qw(resizeable scrollbars);  
   
             push @attr, qw(status toolbar)  
               if (defined($mimetype) && $mimetype eq "text/html");  
   
             push @attr, "width=$extern_window_width"  
               if (defined($extern_window_width));  
   
             push @attr, "height=$extern_window_height"  
               if (defined($extern_window_height));  
   
             printf q` onClick="window.open('%s','cvs_checkout','%s');"`,  
               htmlquote($fullurl), join(',', @attr);  
         }  
     }  
     print "><b>$textlink</b></A>";  
 }  
   
 # Returns a Query string with the  
 # specified parameter toggled  
 sub toggleQuery($$) {  
     my ($toggle,$value) = @_;  
     my ($newquery,$var);  
     my (%vars);  
     %vars = %input;  
     if (defined($value)) {  
         $vars{$toggle} = $value;  
     }  
     else {  
         $vars{$toggle} = $vars{$toggle} ? 0 : 1;  
     }  
     # Build a new query of non-default paramenters  
     $newquery = "";  
     foreach $var (@stickyvars) {  
         my ($value) = defined($vars{$var}) ? $vars{$var} : "";  
         my ($default) = defined($DEFAULTVALUE{$var}) ? $DEFAULTVALUE{$var} : "";  
         if ($value ne $default) {  
             $newquery .= "&" if ($newquery ne "");  
             $newquery .= urlencode($var) . "=" . urlencode($value);  
         }  
     }  
     if ($newquery) {  
         return '?' . $newquery;  
     }  
     return "";  
 }  
   
 sub urlencode($) {  
     local($_) = @_;  
   
     s/[\000-+{-\377]/sprintf("%%%02x", ord($&))/ge;  
   
   
        $_;  
 }  
   
 sub htmlquote($) {  
     local($_) = @_;  
   
     # Special Characters; RFC 1866  
     s/&/&amp;/g;  
     s/\"/&quot;/g;  
     s/</&lt;/g;  
     s/>/&gt;/g;  
   
     $_;  
 }  
   
 sub htmlunquote($) {  
     local($_) = @_;  
   
     # Special Characters; RFC 1866  
     s/&quot;/\"/g;  
     s/&lt;/</g;  
     s/&gt;/>/g;  
     s/&amp;/&/g;  
   
     $_;  
 }  
   
 sub http_header(;$) {  
     my $content_type = shift || "text/html";  
     if (defined($moddate)) {  
         if ($is_mod_perl) {  
             Apache->request->header_out("Last-Modified" => scalar gmtime($moddate) . " GMT");  
         }  
         else {  
             print "Last-Modified: ", scalar gmtime($moddate), " GMT\r\n";  
         }  
     }  
     if ($is_mod_perl) {  
         Apache->request->content_type($content_type);  
     }  
     else {  
             print "Content-type: $content_type\r\n";  
     }  
     if ($allow_compress && $maycompress) {  
         if ($has_zlib || (defined($GZIPBIN) && open(GZIP, "|$GZIPBIN -1 -c"))) {  
             if ($is_mod_perl) {  
                     Apache->request->content_encoding("x-gzip");  
                     Apache->request->header_out(Vary => "Accept-Encoding");  
                     Apache->request->send_http_header;  
             }  
             else {  
                     print "Content-encoding: x-gzip\r\n";  
                     print "Vary: Accept-Encoding\r\n";  #RFC 2068, 14.43  
                     print "\r\n"; # Close headers  
             }  
             $| = 1; $| = 0; # Flush header output  
             if ($has_zlib) {  
                 tie *GZIP, __PACKAGE__, \*STDOUT;  
             }  
             select(GZIP);  
             $gzip_open = 1;  
 #           print "<!-- gzipped -->" if ($content_type =~ m|^text/html\b|);  
         }  
         else {  
             if ($is_mod_perl) {  
                     Apache->request->send_http_header;  
             }  
             else {  
                     print "\r\n"; # Close headers  
             }  
             print "<font size=-1>Unable to find gzip binary in the \$PATH to compress output</font><br>";  
         }  
     }  
     else {  
             if ($is_mod_perl) {  
                     Apache->request->send_http_header;  
             }  
             else {  
                     print "\r\n"; # Close headers  
             }  
     }  
 }  
   
 sub html_header($) {  
     my ($title) = @_;  
     my $version = '$zRevision: 1.104 $  $kRevision: 1.41 $'; #'  
     http_header($charset ne "" ? "text/html; charset=$charset" : "text/html");  
     print <<EOH;  
 <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"  
  "http://www.w3.org/TR/REC-html40/loose.dtd">  
 <html>  
 <head>  
 <title>$title</title>  
 <!-- CVSweb $version -->  
 </head>  
 $body_tag  
 $logo <h1 align="center">$title</h1>  
 EOH  
 }  
   
 sub html_footer() {  
     return "<hr noshade><address>$address</address>\n";  
 }  
   
 sub link_tags($) {  
     my ($tags) = @_;  
     my ($ret) = "";  
     my ($fileurl,$filename);  
   
     ($filename = $where) =~ s/^.*\///;  
     $fileurl = urlencode($filename);  
   
     foreach my $sym (split(", ", $tags)) {  
         $ret .= ",\n" if ($ret ne "");  
         $ret .= &link($sym, $fileurl . toggleQuery('only_with_tag',$sym));  
     }  
     return "$ret\n";  
 }  
   
 #  
 # See if a module is listed in the config file's @HideModule list.  
 #  
 sub forbidden_module($) {  
     my($module) = @_;  
   
     for (my $i=0; $i < @HideModules; $i++) {  
         return 1 if $module eq $HideModules[$i];  
     }  
   
     return 0;  
 }  
   
 # Close the GZIP handle remove the tie.  
   
 sub gzipclose {  
         if ($gzip_open) {  
             select(STDOUT);  
             close(GZIP);  
             untie *GZIP;  
             $gzip_open = 0;  
         }  
 }  
   
 # implement a gzipped file handle via the Compress:Zlib compression  
 # library.  
   
 sub MAGIC1() { 0x1f }  
 sub MAGIC2() { 0x8b }  
 sub OSCODE() { 3    }  
   
 sub TIEHANDLE {  
         my ($class, $out) = @_;  
         my ($d) = Compress::Zlib::deflateInit(-Level => Compress::Zlib::Z_BEST_COMPRESSION(),  
                 -WindowBits => -Compress::Zlib::MAX_WBITS()) or return undef;  
         my ($o) = {  
                 handle => $out,  
                 dh => $d,  
                 crc => 0,  
                 len => 0,  
         };  
         my ($header) = pack("c10", MAGIC1, MAGIC2, Compress::Zlib::Z_DEFLATED(), 0,0,0,0,0,0, OSCODE);  
         print {$o->{handle}} $header;  
         return bless($o, $class);  
 }  
   
 sub PRINT {  
         my ($o) = shift;  
         my ($buf) = join(defined $, ? $, : "",@_);  
         my ($len) = length($buf);  
         my ($compressed, $status) = $o->{dh}->deflate($buf);  
         print {$o->{handle}} $compressed if defined($compressed);  
         $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc});  
         $o->{len} += $len;  
         return $len;  
 }  
   
 sub PRINTF {  
         my ($o) = shift;  
         my ($fmt) = shift;  
         my ($buf) = sprintf($fmt, @_);  
         my ($len) = length($buf);  
         my ($compressed, $status) = $o->{dh}->deflate($buf);  
         print {$o->{handle}} $compressed if defined($compressed);  
         $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc});  
         $o->{len} += $len;  
         return $len;  
 }  
   
 sub WRITE {  
         my ($o, $buf, $len, $off) = @_;  
         my ($compressed, $status) = $o->{dh}->deflate(substr($buf, 0, $len));  
         print {$o->{handle}} $compressed if defined($compressed);  
         $o->{crc} = Compress::Zlib::crc32(substr($buf, 0, $len), $o->{crc});  
         $o->{len} += $len;  
         return $len;  
 }  
   
 sub CLOSE {  
         my ($o) = @_;  
         return if !defined( $o->{dh});  
         my ($buf) = $o->{dh}->flush();  
         $buf .= pack("V V", $o->{crc}, $o->{len});  
         print {$o->{handle}} $buf;  
         undef $o->{dh};  
 }  
   
 sub DESTROY {  
         my ($o) = @_;  
         CLOSE($o);  
 }  }

Legend:
Removed from v.1.1.1.13  
changed lines
  Added in v.1.14

CVSweb