git-cvsserver: typo in a comment: bas -> has
[gitweb.git] / gitweb / gitweb.perl
index 4e64fc86bd29b6bf3762be6034be5662cccf18c1..2365311d94e78b894b42526c3181e45f23ce14ac 100755 (executable)
@@ -11,7 +11,7 @@
 use warnings;
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
-use CGI::Carp qw(fatalsToBrowser);
+use CGI::Carp qw(fatalsToBrowser set_message);
 use Encode;
 use Fcntl ':mode';
 use File::Find qw();
@@ -445,6 +445,19 @@ BEGIN
        'javascript-actions' => {
                'override' => 0,
                'default' => [0]},
+
+       # Syntax highlighting support. This is based on Daniel Svensson's
+       # and Sham Chukoury's work in gitweb-xmms2.git.
+       # It requires the 'highlight' program present in $PATH,
+       # and therefore is disabled by default.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'highlight'}{'default'} = [1];
+
+       'highlight' => {
+               'sub' => sub { feature_bool('highlight', @_) },
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -952,6 +965,21 @@ sub evaluate_path_info {
        $git_avatar = '';
 }
 
+# custom error handler: 'die <message>' is Internal Server Error
+sub handle_errors_html {
+       my $msg = shift; # it is already HTML escaped
+
+       # to avoid infinite loop where error occurs in die_error,
+       # change handler to default handler, disabling handle_errors_html
+       set_message("Error occured when inside die_error:\n$msg");
+
+       # you cannot jump out of die_error when called as error handler;
+       # the subroutine set via CGI::Carp::set_message is called _after_
+       # HTTP headers are already written, so it cannot write them itself
+       die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
+}
+set_message(\&handle_errors_html);
+
 # dispatch
 if (!defined $action) {
        if (defined $hash) {
@@ -972,11 +1000,16 @@ sub evaluate_path_info {
        die_error(400, "Project needed");
 }
 $actions{$action}->();
-exit;
+DONE_GITWEB:
+1;
 
 ## ======================================================================
 ## action links
 
+# possible values of extra options
+# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
+# -replay => 1      - start from a current view (replay with modifications)
+# -path_info => 0|1 - don't use/use path_info URL (if possible)
 sub href {
        my %params = @_;
        # default is to use -absolute url() i.e. $my_uri
@@ -993,7 +1026,8 @@ sub href {
        }
 
        my $use_pathinfo = gitweb_check_feature('pathinfo');
-       if ($use_pathinfo and defined $params{'project'}) {
+       if (defined $params{'project'} &&
+           (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
@@ -3158,26 +3192,88 @@ sub blob_contenttype {
        return $type;
 }
 
+# guess file syntax for syntax highlighting; return undef if no highlighting
+# the name of syntax can (in the future) depend on syntax highlighter used
+sub guess_file_syntax {
+       my ($highlight, $mimetype, $file_name) = @_;
+       return undef unless ($highlight && defined $file_name);
+
+       # configuration for 'highlight' (http://www.andre-simon.de/)
+       # match by basename
+       my %highlight_basename = (
+               #'Program' => 'py',
+               #'Library' => 'py',
+               'SConstruct' => 'py', # SCons equivalent of Makefile
+               'Makefile' => 'make',
+       );
+       # match by extension
+       my %highlight_ext = (
+               # main extensions, defining name of syntax;
+               # see files in /usr/share/highlight/langDefs/ directory
+               map { $_ => $_ }
+                       qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+               # alternate extensions, see /etc/highlight/filetypes.conf
+               'h' => 'c',
+               map { $_ => 'cpp' } qw(cxx c++ cc),
+               map { $_ => 'php' } qw(php3 php4),
+               map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+               'mak' => 'make',
+               map { $_ => 'xml' } qw(xhtml html htm),
+       );
+
+       my $basename = basename($file_name, '.in');
+       return $highlight_basename{$basename}
+               if exists $highlight_basename{$basename};
+
+       $basename =~ /\.([^.]*)$/;
+       my $ext = $1 or return undef;
+       return $highlight_ext{$ext}
+               if exists $highlight_ext{$ext};
+
+       return undef;
+}
+
+# run highlighter and return FD of its output,
+# or return original FD if no highlighting
+sub run_highlighter {
+       my ($fd, $highlight, $syntax) = @_;
+       return $fd unless ($highlight && defined $syntax);
+
+       close $fd
+               or die_error(404, "Reading blob failed");
+       open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 "highlight --xhtml --fragment --syntax $syntax |"
+               or die_error(500, "Couldn't open file or run syntax highlighter");
+       return $fd;
+}
+
 ## ======================================================================
 ## functions printing HTML: header, footer, error page
 
+sub get_page_title {
+       my $title = to_utf8($site_name);
+
+       return $title unless (defined $project);
+       $title .= " - " . to_utf8($project);
+
+       return $title unless (defined $action);
+       $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
+
+       return $title unless (defined $file_name);
+       $title .= " - " . esc_path($file_name);
+       if ($action eq "tree" && $file_name !~ m|/$|) {
+               $title .= "/";
+       }
+
+       return $title;
+}
+
 sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
+       my %opts = @_;
 
-       my $title = "$site_name";
-       if (defined $project) {
-               $title .= " - " . to_utf8($project);
-               if (defined $action) {
-                       $title .= "/$action";
-                       if (defined $file_name) {
-                               $title .= " - " . esc_path($file_name);
-                               if ($action eq "tree" && $file_name !~ m|/$|) {
-                                       $title .= "/";
-                               }
-                       }
-               }
-       }
+       my $title = get_page_title();
        my $content_type;
        # require explicit support from the UA if we are to send the page as
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
@@ -3191,7 +3287,8 @@ sub git_header_html {
                $content_type = 'text/html';
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-                          -status=> $status, -expires => $expires);
+                          -status=> $status, -expires => $expires)
+               unless ($opts{'-no_http_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
@@ -3408,6 +3505,7 @@ sub die_error {
        my $status = shift || 500;
        my $error = esc_html(shift) || "Internal Server Error";
        my $extra = shift;
+       my %opts = @_;
 
        my %http_responses = (
                400 => '400 Bad Request',
@@ -3416,7 +3514,7 @@ sub die_error {
                500 => '500 Internal Server Error',
                503 => '503 Service Unavailable',
        );
-       git_header_html($http_responses{$status});
+       git_header_html($http_responses{$status}, undef, %opts);
        print <<EOF;
 <div class="page_body">
 <br /><br />
@@ -3430,7 +3528,8 @@ sub die_error {
        print "</div>\n";
 
        git_footer_html();
-       exit;
+       goto DONE_GITWEB
+               unless ($opts{'-error_handler'});
 }
 
 ## ----------------------------------------------------------------------
@@ -5349,6 +5448,7 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
+       # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
        if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
@@ -5356,6 +5456,11 @@ sub git_blob {
        # we can have blame only for text/* mimetype
        $have_blame &&= ($mimetype =~ m!^text/!);
 
+       my $highlight = gitweb_check_feature('highlight');
+       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax)
+               if $syntax;
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -5405,9 +5510,8 @@ sub git_blob {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
-                               . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                              $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd