Git 1.7.2.5
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Dec 2010 19:45:36 +0000 (11:45 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Dec 2010 19:45:36 +0000 (11:45 -0800)
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1  2 
Documentation/RelNotes/1.7.2.5.txt
GIT-VERSION-GEN
RelNotes
gitweb/gitweb.perl
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bf976c40db7abd2fb4475e449e5d2194c4bc0cdc
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,8 @@@
++Git v1.7.2.5 Release Notes
++==========================
++
++Fixes since v1.7.2.4
++--------------------
++
++ * "gitweb" can sometimes be tricked into parrotting a filename argument
++   given in a request without properly quoting.
diff --combined GIT-VERSION-GEN
index 1c0f85c2bd459d384205222120286a554c9bd968,5e8077ebf47ebf749adbc63ee550028fb74d5378..ec4c49a90daacc11072f8285e5879378156c22a8
@@@ -1,7 -1,7 +1,7 @@@
  #!/bin/sh
  
  GVF=GIT-VERSION-FILE
- DEF_VER=v1.7.2.4
 -DEF_VER=v1.7.1.4
++DEF_VER=v1.7.2.5
  
  LF='
  '
diff --combined RelNotes
index c81a98e070a1d9e8ef2ca56e1b18fcbffba19cf7,86af99bd3774d6e35785c12725e1134da023a223..17d37af54951e3179282cad837ee0beb271eb4e9
+++ b/RelNotes
@@@ -1,1 -1,1 +1,1 @@@
- Documentation/RelNotes/1.7.2.4.txt
 -Documentation/RelNotes/1.7.1.4.txt
++Documentation/RelNotes/1.7.2.5.txt
diff --combined gitweb/gitweb.perl
index 84261bba34dcdf436bb3b87ab88c67353ea4f4dd,b51f5bfb2f3e1b440a1815ad6163441ba97c8c70..8b8ab2960793d591bcd8bf572e882339b9674a04
@@@ -11,7 -11,7 +11,7 @@@ use strict
  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();
@@@ -28,42 -28,34 +28,42 @@@ BEGIN 
        CGI->compile() if $ENV{'MOD_PERL'};
  }
  
 -our $cgi = new CGI;
  our $version = "++GIT_VERSION++";
 -our $my_url = $cgi->url();
 -our $my_uri = $cgi->url(-absolute => 1);
  
 -# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
 -# needed and used only for URLs with nonempty PATH_INFO
 -our $base_url = $my_url;
 +our ($my_url, $my_uri, $base_url, $path_info, $home_link);
 +sub evaluate_uri {
 +      our $cgi;
  
 -# When the script is used as DirectoryIndex, the URL does not contain the name
 -# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
 -# have to do it ourselves. We make $path_info global because it's also used
 -# later on.
 -#
 -# Another issue with the script being the DirectoryIndex is that the resulting
 -# $my_url data is not the full script URL: this is good, because we want
 -# generated links to keep implying the script name if it wasn't explicitly
 -# indicated in the URL we're handling, but it means that $my_url cannot be used
 -# as base URL.
 -# Therefore, if we needed to strip PATH_INFO, then we know that we have
 -# to build the base URL ourselves:
 -our $path_info = $ENV{"PATH_INFO"};
 -if ($path_info) {
 -      if ($my_url =~ s,\Q$path_info\E$,, &&
 -          $my_uri =~ s,\Q$path_info\E$,, &&
 -          defined $ENV{'SCRIPT_NAME'}) {
 -              $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
 +      our $my_url = $cgi->url();
 +      our $my_uri = $cgi->url(-absolute => 1);
 +
 +      # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
 +      # needed and used only for URLs with nonempty PATH_INFO
 +      our $base_url = $my_url;
 +
 +      # When the script is used as DirectoryIndex, the URL does not contain the name
 +      # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
 +      # have to do it ourselves. We make $path_info global because it's also used
 +      # later on.
 +      #
 +      # Another issue with the script being the DirectoryIndex is that the resulting
 +      # $my_url data is not the full script URL: this is good, because we want
 +      # generated links to keep implying the script name if it wasn't explicitly
 +      # indicated in the URL we're handling, but it means that $my_url cannot be used
 +      # as base URL.
 +      # Therefore, if we needed to strip PATH_INFO, then we know that we have
 +      # to build the base URL ourselves:
 +      our $path_info = $ENV{"PATH_INFO"};
 +      if ($path_info) {
 +              if ($my_url =~ s,\Q$path_info\E$,, &&
 +                  $my_uri =~ s,\Q$path_info\E$,, &&
 +                  defined $ENV{'SCRIPT_NAME'}) {
 +                      $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
 +              }
        }
 +
 +      # target of the home link on top of all pages
 +      our $home_link = $my_uri || "/";
  }
  
  # core git executable to use
@@@ -78,6 -70,9 +78,6 @@@ our $projectroot = "++GITWEB_PROJECTROO
  # the number is relative to the projectroot
  our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
  
 -# target of the home link on top of all pages
 -our $home_link = $my_uri || "/";
 -
  # string of the home link on top of all pages
  our $home_link_str = "++GITWEB_HOME_LINK_STR++";
  
@@@ -245,7 -240,7 +245,7 @@@ our %feature = 
        # return value of feature-sub indicates if to enable specified feature
        #
        # if there is no 'sub' key (no feature-sub), then feature cannot be
 -      # overriden
 +      # overridden
        #
        # use gitweb_get_feature(<feature>) to retrieve the <feature> value
        # (an array) or gitweb_check_feature(<feature>) to check if <feature>
        '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 {
@@@ -571,18 -553,15 +571,18 @@@ sub filter_snapshot_fmts 
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
  }
  
 -our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
 -our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
 -# die if there are errors parsing config file
 -if (-e $GITWEB_CONFIG) {
 -      do $GITWEB_CONFIG;
 -      die $@ if $@;
 -} elsif (-e $GITWEB_CONFIG_SYSTEM) {
 -      do $GITWEB_CONFIG_SYSTEM;
 -      die $@ if $@;
 +our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
 +sub evaluate_gitweb_config {
 +      our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
 +      our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
 +      # die if there are errors parsing config file
 +      if (-e $GITWEB_CONFIG) {
 +              do $GITWEB_CONFIG;
 +              die $@ if $@;
 +      } elsif (-e $GITWEB_CONFIG_SYSTEM) {
 +              do $GITWEB_CONFIG_SYSTEM;
 +              die $@ if $@;
 +      }
  }
  
  # Get loadavg of system, to compare against $maxload.
@@@ -608,16 -587,13 +608,16 @@@ sub get_loadavg 
  }
  
  # version of the core git binary
 -our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 -$number_of_git_cmds++;
 -
 -$projects_list ||= $projectroot;
 +our $git_version;
 +sub evaluate_git_version {
 +      our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 +      $number_of_git_cmds++;
 +}
  
 -if (defined $maxload && get_loadavg() > $maxload) {
 -      die_error(503, "The load average on the server is too high");
 +sub check_loadavg {
 +      if (defined $maxload && get_loadavg() > $maxload) {
 +              die_error(503, "The load average on the server is too high");
 +      }
  }
  
  # ======================================================================
@@@ -704,15 -680,11 +704,15 @@@ our %allowed_options = 
  # should be single values, but opt can be an array. We should probably
  # build an array of parameters that can be multi-valued, but since for the time
  # being it's only this one, we just single it out
 -while (my ($name, $symbol) = each %cgi_param_mapping) {
 -      if ($symbol eq 'opt') {
 -              $input_params{$name} = [ $cgi->param($symbol) ];
 -      } else {
 -              $input_params{$name} = $cgi->param($symbol);
 +sub evaluate_query_params {
 +      our $cgi;
 +
 +      while (my ($name, $symbol) = each %cgi_param_mapping) {
 +              if ($symbol eq 'opt') {
 +                      $input_params{$name} = [ $cgi->param($symbol) ];
 +              } else {
 +                      $input_params{$name} = $cgi->param($symbol);
 +              }
        }
  }
  
@@@ -859,277 -831,152 +859,277 @@@ sub evaluate_path_info 
                }
        }
  }
 -evaluate_path_info();
  
 -our $action = $input_params{'action'};
 -if (defined $action) {
 -      if (!validate_action($action)) {
 -              die_error(400, "Invalid action parameter");
 +our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
 +     $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
 +     $searchtext, $search_regexp);
 +sub evaluate_and_validate_params {
 +      our $action = $input_params{'action'};
 +      if (defined $action) {
 +              if (!validate_action($action)) {
 +                      die_error(400, "Invalid action parameter");
 +              }
        }
 -}
  
 -# parameters which are pathnames
 -our $project = $input_params{'project'};
 -if (defined $project) {
 -      if (!validate_project($project)) {
 -              undef $project;
 -              die_error(404, "No such project");
 +      # parameters which are pathnames
 +      our $project = $input_params{'project'};
 +      if (defined $project) {
 +              if (!validate_project($project)) {
 +                      undef $project;
 +                      die_error(404, "No such project");
 +              }
        }
 -}
  
 -our $file_name = $input_params{'file_name'};
 -if (defined $file_name) {
 -      if (!validate_pathname($file_name)) {
 -              die_error(400, "Invalid file parameter");
 +      our $file_name = $input_params{'file_name'};
 +      if (defined $file_name) {
 +              if (!validate_pathname($file_name)) {
 +                      die_error(400, "Invalid file parameter");
 +              }
        }
 -}
  
 -our $file_parent = $input_params{'file_parent'};
 -if (defined $file_parent) {
 -      if (!validate_pathname($file_parent)) {
 -              die_error(400, "Invalid file parent parameter");
 +      our $file_parent = $input_params{'file_parent'};
 +      if (defined $file_parent) {
 +              if (!validate_pathname($file_parent)) {
 +                      die_error(400, "Invalid file parent parameter");
 +              }
        }
 -}
  
 -# parameters which are refnames
 -our $hash = $input_params{'hash'};
 -if (defined $hash) {
 -      if (!validate_refname($hash)) {
 -              die_error(400, "Invalid hash parameter");
 +      # parameters which are refnames
 +      our $hash = $input_params{'hash'};
 +      if (defined $hash) {
 +              if (!validate_refname($hash)) {
 +                      die_error(400, "Invalid hash parameter");
 +              }
        }
 -}
  
 -our $hash_parent = $input_params{'hash_parent'};
 -if (defined $hash_parent) {
 -      if (!validate_refname($hash_parent)) {
 -              die_error(400, "Invalid hash parent parameter");
 +      our $hash_parent = $input_params{'hash_parent'};
 +      if (defined $hash_parent) {
 +              if (!validate_refname($hash_parent)) {
 +                      die_error(400, "Invalid hash parent parameter");
 +              }
        }
 -}
  
 -our $hash_base = $input_params{'hash_base'};
 -if (defined $hash_base) {
 -      if (!validate_refname($hash_base)) {
 -              die_error(400, "Invalid hash base parameter");
 +      our $hash_base = $input_params{'hash_base'};
 +      if (defined $hash_base) {
 +              if (!validate_refname($hash_base)) {
 +                      die_error(400, "Invalid hash base parameter");
 +              }
        }
 -}
  
 -our @extra_options = @{$input_params{'extra_options'}};
 -# @extra_options is always defined, since it can only be (currently) set from
 -# CGI, and $cgi->param() returns the empty array in array context if the param
 -# is not set
 -foreach my $opt (@extra_options) {
 -      if (not exists $allowed_options{$opt}) {
 -              die_error(400, "Invalid option parameter");
 -      }
 -      if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
 -              die_error(400, "Invalid option parameter for this action");
 +      our @extra_options = @{$input_params{'extra_options'}};
 +      # @extra_options is always defined, since it can only be (currently) set from
 +      # CGI, and $cgi->param() returns the empty array in array context if the param
 +      # is not set
 +      foreach my $opt (@extra_options) {
 +              if (not exists $allowed_options{$opt}) {
 +                      die_error(400, "Invalid option parameter");
 +              }
 +              if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
 +                      die_error(400, "Invalid option parameter for this action");
 +              }
        }
 -}
  
 -our $hash_parent_base = $input_params{'hash_parent_base'};
 -if (defined $hash_parent_base) {
 -      if (!validate_refname($hash_parent_base)) {
 -              die_error(400, "Invalid hash parent base parameter");
 +      our $hash_parent_base = $input_params{'hash_parent_base'};
 +      if (defined $hash_parent_base) {
 +              if (!validate_refname($hash_parent_base)) {
 +                      die_error(400, "Invalid hash parent base parameter");
 +              }
        }
 -}
  
 -# other parameters
 -our $page = $input_params{'page'};
 -if (defined $page) {
 -      if ($page =~ m/[^0-9]/) {
 -              die_error(400, "Invalid page parameter");
 +      # other parameters
 +      our $page = $input_params{'page'};
 +      if (defined $page) {
 +              if ($page =~ m/[^0-9]/) {
 +                      die_error(400, "Invalid page parameter");
 +              }
        }
 -}
  
 -our $searchtype = $input_params{'searchtype'};
 -if (defined $searchtype) {
 -      if ($searchtype =~ m/[^a-z]/) {
 -              die_error(400, "Invalid searchtype parameter");
 +      our $searchtype = $input_params{'searchtype'};
 +      if (defined $searchtype) {
 +              if ($searchtype =~ m/[^a-z]/) {
 +                      die_error(400, "Invalid searchtype parameter");
 +              }
        }
 -}
  
 -our $search_use_regexp = $input_params{'search_use_regexp'};
 +      our $search_use_regexp = $input_params{'search_use_regexp'};
  
 -our $searchtext = $input_params{'searchtext'};
 -our $search_regexp;
 -if (defined $searchtext) {
 -      if (length($searchtext) < 2) {
 -              die_error(403, "At least two characters are required for search parameter");
 +      our $searchtext = $input_params{'searchtext'};
 +      our $search_regexp;
 +      if (defined $searchtext) {
 +              if (length($searchtext) < 2) {
 +                      die_error(403, "At least two characters are required for search parameter");
 +              }
 +              $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
        }
 -      $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
  }
  
  # path to the current git repository
  our $git_dir;
 -$git_dir = "$projectroot/$project" if $project;
 -
 -# list of supported snapshot formats
 -our @snapshot_fmts = gitweb_get_feature('snapshot');
 -@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
 -
 -# check that the avatar feature is set to a known provider name,
 -# and for each provider check if the dependencies are satisfied.
 -# if the provider name is invalid or the dependencies are not met,
 -# reset $git_avatar to the empty string.
 -our ($git_avatar) = gitweb_get_feature('avatar');
 -if ($git_avatar eq 'gravatar') {
 -      $git_avatar = '' unless (eval { require Digest::MD5; 1; });
 -} elsif ($git_avatar eq 'picon') {
 -      # no dependencies
 -} else {
 -      $git_avatar = '';
 +sub evaluate_git_dir {
 +      our $git_dir = "$projectroot/$project" if $project;
  }
  
 -# dispatch
 -if (!defined $action) {
 -      if (defined $hash) {
 -              $action = git_get_type($hash);
 -      } elsif (defined $hash_base && defined $file_name) {
 -              $action = git_get_type("$hash_base:$file_name");
 -      } elsif (defined $project) {
 -              $action = 'summary';
 +our (@snapshot_fmts, $git_avatar);
 +sub configure_gitweb_features {
 +      # list of supported snapshot formats
 +      our @snapshot_fmts = gitweb_get_feature('snapshot');
 +      @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
 +
 +      # check that the avatar feature is set to a known provider name,
 +      # and for each provider check if the dependencies are satisfied.
 +      # if the provider name is invalid or the dependencies are not met,
 +      # reset $git_avatar to the empty string.
 +      our ($git_avatar) = gitweb_get_feature('avatar');
 +      if ($git_avatar eq 'gravatar') {
 +              $git_avatar = '' unless (eval { require Digest::MD5; 1; });
 +      } elsif ($git_avatar eq 'picon') {
 +              # no dependencies
        } else {
 -              $action = 'project_list';
 +              $git_avatar = '';
        }
  }
 -if (!defined($actions{$action})) {
 -      die_error(400, "Unknown action");
 +
 +# 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);
  }
 -if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
 -    !$project) {
 -      die_error(400, "Project needed");
 +set_message(\&handle_errors_html);
 +
 +# dispatch
 +sub dispatch {
 +      if (!defined $action) {
 +              if (defined $hash) {
 +                      $action = git_get_type($hash);
 +              } elsif (defined $hash_base && defined $file_name) {
 +                      $action = git_get_type("$hash_base:$file_name");
 +              } elsif (defined $project) {
 +                      $action = 'summary';
 +              } else {
 +                      $action = 'project_list';
 +              }
 +      }
 +      if (!defined($actions{$action})) {
 +              die_error(400, "Unknown action");
 +      }
 +      if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
 +          !$project) {
 +              die_error(400, "Project needed");
 +      }
 +      $actions{$action}->();
 +}
 +
 +sub reset_timer {
 +      our $t0 = [Time::HiRes::gettimeofday()]
 +              if defined $t0;
 +      our $number_of_git_cmds = 0;
 +}
 +
 +sub run_request {
 +      reset_timer();
 +
 +      evaluate_uri();
 +      check_loadavg();
 +
 +      evaluate_query_params();
 +      evaluate_path_info();
 +      evaluate_and_validate_params();
 +      evaluate_git_dir();
 +
 +      configure_gitweb_features();
 +
 +      dispatch();
 +}
 +
 +our $is_last_request = sub { 1 };
 +our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
 +our $CGI = 'CGI';
 +our $cgi;
 +sub configure_as_fcgi {
 +      require CGI::Fast;
 +      our $CGI = 'CGI::Fast';
 +
 +      my $request_number = 0;
 +      # let each child service 100 requests
 +      our $is_last_request = sub { ++$request_number > 100 };
 +}
 +sub evaluate_argv {
 +      my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
 +      configure_as_fcgi()
 +              if $script_name =~ /\.fcgi$/;
 +
 +      return unless (@ARGV);
 +
 +      require Getopt::Long;
 +      Getopt::Long::GetOptions(
 +              'fastcgi|fcgi|f' => \&configure_as_fcgi,
 +              'nproc|n=i' => sub {
 +                      my ($arg, $val) = @_;
 +                      return unless eval { require FCGI::ProcManager; 1; };
 +                      my $proc_manager = FCGI::ProcManager->new({
 +                              n_processes => $val,
 +                      });
 +                      our $pre_listen_hook    = sub { $proc_manager->pm_manage()        };
 +                      our $pre_dispatch_hook  = sub { $proc_manager->pm_pre_dispatch()  };
 +                      our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
 +              },
 +      );
 +}
 +
 +sub run {
 +      evaluate_argv();
 +      evaluate_gitweb_config();
 +      evaluate_git_version();
 +
 +      # $projectroot and $projects_list might be set in gitweb config file
 +      $projects_list ||= $projectroot;
 +
 +      $pre_listen_hook->()
 +              if $pre_listen_hook;
 +
 + REQUEST:
 +      while ($cgi = $CGI->new()) {
 +              $pre_dispatch_hook->()
 +                      if $pre_dispatch_hook;
 +
 +              run_request();
 +
 +              $pre_dispatch_hook->()
 +                      if $post_dispatch_hook;
 +
 +              last REQUEST if ($is_last_request->());
 +      }
 +
 + DONE_GITWEB:
 +      1;
 +}
 +
 +run();
 +
 +if (defined caller) {
 +      # wrapped in a subroutine processing requests,
 +      # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
 +      return;
 +} else {
 +      # pure CGI script, serving single request
 +      exit;
  }
 -$actions{$action}->();
 -exit;
  
  ## ======================================================================
  ## 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
        }
  
        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
@@@ -1323,7 -1169,7 +1323,7 @@@ sub esc_param 
        return $str;
  }
  
 -# quote unsafe chars in whole URL, so some charactrs cannot be quoted
 +# quote unsafe chars in whole URL, so some characters cannot be quoted
  sub esc_url {
        my $str = shift;
        return undef unless defined $str;
        return $str;
  }
  
+ # quote unsafe characters in HTML attributes
+ sub esc_attr {
+       # for XHTML conformance escaping '"' to '&quot;' is not enough
+       return esc_html(@_);
+ }
  # replace invalid utf8 character with SUBSTITUTION sequence
  sub esc_html {
        my $str = shift;
@@@ -1737,7 -1590,7 +1744,7 @@@ sub format_ref_marker 
                                        hash=>$dest
                                )}, $name);
  
-                       $markers .= " <span class=\"$class\" title=\"$ref\">" .
+                       $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
                }
        }
@@@ -1821,7 -1674,7 +1828,7 @@@ sub git_get_avatar 
                return $pre_white .
                       "<img width=\"$size\" " .
                            "class=\"avatar\" " .
-                           "src=\"$url\" " .
+                           "src=\"".esc_url($url)."\" " .
                            "alt=\"\" " .
                       "/>" . $post_white;
        } else {
@@@ -2532,7 -2385,7 +2539,7 @@@ sub git_show_project_tagcloud 
        } else {
                my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
                return '<p align="center">' . join (', ', map {
-                       "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
+                       $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname})
                } splice(@tags, 0, $count)) . '</p>';
        }
  }
@@@ -2573,9 -2426,6 +2580,9 @@@ sub git_get_projects_list 
                        follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
 +                              # global variables
 +                              our $project_maxdepth;
 +                              our $projectroot;
                                # skip project-list toplevel, if we get it.
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
@@@ -3311,88 -3161,26 +3318,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'.
                $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"?>
        # print out each stylesheet that exist, providing backwards capability
        # for those people who defined $stylesheet in a config file
        if (defined $stylesheet) {
-               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
                        next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
                }
        }
        if (defined $project) {
                        my $type = lc($format);
                        my %link_attr = (
                                '-rel' => 'alternate',
-                               '-title' => "$project - $href_params{'-title'} - $format feed",
+                               '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
                                '-type' => "application/$type+xml"
                        );
  
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8" />'."\n",
-                      $site_name, href(project=>undef, action=>"project_index"));
+                      esc_attr($site_name), href(project=>undef, action=>"project_index"));
                printf('<link rel="alternate" title="%s projects feeds" '.
                       'href="%s" type="text/x-opml" />'."\n",
-                      $site_name, href(project=>undef, action=>"opml"));
+                      esc_attr($site_name), href(project=>undef, action=>"opml"));
        }
        if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
+               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
        }
  
        print "</head>\n" .
        print "<div class=\"page_header\">\n" .
              $cgi->a({-href => esc_url($logo_url),
                       -title => $logo_label},
-                     qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+                     qq(<img src=").esc_url($logo).qq(" width="72" height="27" alt="git" class="logo"/>));
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        if (defined $project) {
                print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
@@@ -3590,7 -3377,7 +3597,7 @@@ sub git_footer_html 
                insert_file($site_footer);
        }
  
-       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
        if (defined $action &&
            $action eq 'blame_incremental') {
                print qq!<script type="text/javascript">\n!.
@@@ -3624,7 -3411,6 +3631,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',
                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 />
@@@ -3647,8 -3433,7 +3654,8 @@@ EO
        print "</div>\n";
  
        git_footer_html();
 -      exit;
 +      goto DONE_GITWEB
 +              unless ($opts{'-error_handler'});
  }
  
  ## ----------------------------------------------------------------------
@@@ -3782,9 -3567,9 +3789,9 @@@ sub git_print_authorship 
  }
  
  # Outputs table rows containing the full author or committer information,
 -# in the format expected for 'commit' view (& similia).
 +# in the format expected for 'commit' view (& similar).
  # Parameters are a commit hash reference, followed by the list of people
 -# to output information for. If the list is empty it defalts to both
 +# to output information for. If the list is empty it defaults to both
  # author and committer.
  sub git_print_authorship_rows {
        my $co = shift;
@@@ -4513,8 -4298,8 +4520,8 @@@ sub git_patchset_body 
                print "</div>\n"; # class="patch"
        }
  
 -      # for compact combined (--cc) format, with chunk and patch simpliciaction
 -      # patchset might be empty, but there might be unprocessed raw lines
 +      # for compact combined (--cc) format, with chunk and patch simplification
 +      # the patchset might be empty, but there might be unprocessed raw lines
        for (++$patch_idx if $patch_number > 0;
             $patch_idx < @$difftree;
             ++$patch_idx) {
@@@ -5567,7 -5352,6 +5574,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);
        # 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))) {
        } else {
                print "<div class=\"page_nav\">\n" .
                      "<br/><br/></div>\n" .
-                     "<div class=\"title\">$hash</div>\n";
+                     "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
-               print qq!<img type="$mimetype"!;
+               print qq!<img type="!.esc_attr($mimetype).qq!"!;
                if ($file_name) {
-                       print qq! alt="$file_name" title="$file_name"!;
+                       print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
                print qq! src="! .
                      href(action=>"blob_plain", hash=>$hash,
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
 -                      printf "<div class=\"pre\"><a id=\"l%i\" href=\""
 -                              . esc_attr(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);
++                             $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@@ -5692,7 -5473,7 +5699,7 @@@ sub git_tree 
                undef $hash_base;
                print "<div class=\"page_nav\">\n";
                print "<br/><br/></div>\n";
-               print "<div class=\"title\">$hash</div>\n";
+               print "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        if (defined $file_name) {
                $basedir = $file_name;
@@@ -6160,7 -5941,7 +6167,7 @@@ sub git_blobdiff 
                        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
                } else {
                        print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
-                       print "<div class=\"title\">$hash vs $hash_parent</div>\n";
+                       print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
                }
                if (defined $file_name) {
                        git_print_page_path($file_name, "blob", $hash_base);
@@@ -6343,8 -6124,8 +6350,8 @@@ sub git_commitdiff 
                        }
                        push @commit_spec, '--root', $hash;
                }
 -              open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
 -                      '--stdout', @commit_spec
 +              open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
 +                      '--encoding=utf8', '--stdout', @commit_spec
                        or die_error(500, "Open git-format-patch failed");
        } else {
                die_error(400, "Unknown commitdiff format");
@@@ -6522,13 -6303,12 +6529,13 @@@ sub git_search 
                        $paging_nav .= " &sdot; next";
                }
  
 -              if ($#commitlist >= 100) {
 -              }
 -
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
                git_print_header_div('commit', esc_html($co{'title'}), $hash);
 -              git_search_grep_body(\@commitlist, 0, 99, $next_link);
 +              if ($page == 0 && !@commitlist) {
 +                      print "<p>No match.</p>\n";
 +              } else {
 +                      git_search_grep_body(\@commitlist, 0, 99, $next_link);
 +              }
        }
  
        if ($searchtype eq 'pickaxe') {