]> Lady’s Gitweb - Gitweb/blobdiff - gitweb.perl
gitweb: fix 'ctags' feature check and others
[Gitweb] / gitweb.perl
index 625425a6192a4d3165d5d53351cdf93394cb35e41ef59fb17c14824ebd1151ca..d1c345f8c5f463f84b9c43204a16d7d6b85e036ca60e148f6b1a02a32d242bca 100755 (executable)
@@ -30,7 +30,7 @@ our $my_uri = $cgi->url(-absolute => 1);
 # if we're called with PATH_INFO, we have to strip that
 # from the URL to find our real URL
 # we make $path_info global because it's also used later on
-my $path_info = $ENV{"PATH_INFO"};
+our $path_info = $ENV{"PATH_INFO"};
 if ($path_info) {
        $my_url =~ s,\Q$path_info\E$,,;
        $my_uri =~ s,\Q$path_info\E$,,;
@@ -96,6 +96,11 @@ our $default_projects_order = "project";
 # (only effective if this variable evaluates to true)
 our $export_ok = "++GITWEB_EXPORT_OK++";
 
+# show repository only if this subroutine returns true
+# when given the path to the project, for example:
+#    sub { return -e "$_[0]/git-daemon-export-ok"; }
+our $export_auth_hook = undef;
+
 # only allow viewing of repositories also shown on the overview page
 our $strict_export = "++GITWEB_STRICT_EXPORT++";
 
@@ -401,7 +406,8 @@ sub check_head_link {
 sub check_export_ok {
        my ($dir) = @_;
        return (check_head_link($dir) &&
-               (!$export_ok || -e "$dir/$export_ok"));
+               (!$export_ok || -e "$dir/$export_ok") &&
+               (!$export_auth_hook || $export_auth_hook->($dir)));
 }
 
 # process alternate names for backward compatibility
@@ -437,7 +443,7 @@ $projects_list ||= $projectroot;
 # together during validation: this allows subsequent uses (e.g. href()) to be
 # agnostic of the parameter origin
 
-my %input_params = ();
+our %input_params = ();
 
 # input parameters are stored with the long parameter name as key. This will
 # also be used in the href subroutine to convert parameters to their CGI
@@ -447,7 +453,7 @@ my %input_params = ();
 # XXX: Warning: If you touch this, check the search form for updating,
 # too.
 
-my @cgi_param_mapping = (
+our @cgi_param_mapping = (
        project => "p",
        action => "a",
        file_name => "f",
@@ -464,10 +470,10 @@ my @cgi_param_mapping = (
        extra_options => "opt",
        search_use_regexp => "sr",
 );
-my %cgi_param_mapping = @cgi_param_mapping;
+our %cgi_param_mapping = @cgi_param_mapping;
 
 # we will also need to know the possible actions, for validation
-my %actions = (
+our %actions = (
        "blame" => \&git_blame,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
@@ -499,7 +505,7 @@ my %actions = (
 
 # finally, we have the hash of allowed extra_options for the commands that
 # allow them
-my %allowed_options = (
+our %allowed_options = (
        "--no-merges" => [ qw(rss atom log shortlog history) ],
 );
 
@@ -550,7 +556,12 @@ sub evaluate_path_info {
                'history',
        );
 
-       my ($refname, $pathname) = split(/:/, $path_info, 2);
+       # we want to catch
+       # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
+       my ($parentrefname, $parentpathname, $refname, $pathname) =
+               ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
+
+       # first, analyze the 'current' part
        if (defined $pathname) {
                # we got "branch:filename" or "branch:dir/"
                # we could use git_get_type(branch:pathname), but:
@@ -565,7 +576,13 @@ sub evaluate_path_info {
                        $input_params{'action'} ||= "tree";
                        $pathname =~ s,/$,,;
                } else {
-                       $input_params{'action'} ||= "blob_plain";
+                       # the default action depends on whether we had parent info
+                       # or not
+                       if ($parentrefname) {
+                               $input_params{'action'} ||= "blobdiff_plain";
+                       } else {
+                               $input_params{'action'} ||= "blob_plain";
+                       }
                }
                $input_params{'hash_base'} ||= $refname;
                $input_params{'file_name'} ||= $pathname;
@@ -585,6 +602,66 @@ sub evaluate_path_info {
                        $input_params{'hash'} ||= $refname;
                }
        }
+
+       # next, handle the 'parent' part, if present
+       if (defined $parentrefname) {
+               # a missing pathspec defaults to the 'current' filename, allowing e.g.
+               # someproject/blobdiff/oldrev..newrev:/filename
+               if ($parentpathname) {
+                       $parentpathname =~ s,^/+,,;
+                       $parentpathname =~ s,/$,,;
+                       $input_params{'file_parent'} ||= $parentpathname;
+               } else {
+                       $input_params{'file_parent'} ||= $input_params{'file_name'};
+               }
+               # we assume that hash_parent_base is wanted if a path was specified,
+               # or if the action wants hash_base instead of hash
+               if (defined $input_params{'file_parent'} ||
+                       grep { $_ eq $input_params{'action'} } @wants_base) {
+                       $input_params{'hash_parent_base'} ||= $parentrefname;
+               } else {
+                       $input_params{'hash_parent'} ||= $parentrefname;
+               }
+       }
+
+       # for the snapshot action, we allow URLs in the form
+       # $project/snapshot/$hash.ext
+       # where .ext determines the snapshot and gets removed from the
+       # passed $refname to provide the $hash.
+       #
+       # To be able to tell that $refname includes the format extension, we
+       # require the following two conditions to be satisfied:
+       # - the hash input parameter MUST have been set from the $refname part
+       #   of the URL (i.e. they must be equal)
+       # - the snapshot format MUST NOT have been defined already (e.g. from
+       #   CGI parameter sf)
+       # It's also useless to try any matching unless $refname has a dot,
+       # so we check for that too
+       if (defined $input_params{'action'} &&
+               $input_params{'action'} eq 'snapshot' &&
+               defined $refname && index($refname, '.') != -1 &&
+               $refname eq $input_params{'hash'} &&
+               !defined $input_params{'snapshot_format'}) {
+               # We loop over the known snapshot formats, checking for
+               # extensions. Allowed extensions are both the defined suffix
+               # (which includes the initial dot already) and the snapshot
+               # format key itself, with a prepended dot
+               while (my ($fmt, %opt) = each %known_snapshot_formats) {
+                       my $hash = $refname;
+                       my $sfx;
+                       $hash =~ s/(\Q$opt{'suffix'}\E|\Q.$fmt\E)$//;
+                       next unless $sfx = $1;
+                       # a valid suffix was found, so set the snapshot format
+                       # and reset the hash parameter
+                       $input_params{'snapshot_format'} = $fmt;
+                       $input_params{'hash'} = $hash;
+                       # we also set the format suffix to the one requested
+                       # in the URL: this way a request for e.g. .tgz returns
+                       # a .tgz instead of a .tar.gz
+                       $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
+                       last;
+               }
+       }
 }
 evaluate_path_info();
 
@@ -690,6 +767,10 @@ if (defined $searchtext) {
 our $git_dir;
 $git_dir = "$projectroot/$project" if $project;
 
+# list of supported snapshot formats
+our @snapshot_fmts = gitweb_check_feature('snapshot');
+@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+
 # dispatch
 if (!defined $action) {
        if (defined $hash) {
@@ -735,7 +816,9 @@ sub href (%) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
+               #   - hash_parent or hash_parent_base:/file_parent
                #   - hash or hash_base:/filename
+               #   - the snapshot_format as an appropriate suffix
 
                # When the script is the root DirectoryIndex for the domain,
                # $href here would be something like http://gitweb.example.com/
@@ -747,6 +830,10 @@ sub href (%) {
                $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
                delete $params{'project'};
 
+               # since we destructively absorb parameters, we keep this
+               # boolean that remembers if we're handling a snapshot
+               my $is_snapshot = $params{'action'} eq 'snapshot';
+
                # Summary just uses the project path URL, any other action is
                # added to the URL
                if (defined $params{'action'}) {
@@ -754,19 +841,50 @@ sub href (%) {
                        delete $params{'action'};
                }
 
-               # Finally, we put either hash_base:/file_name or hash
+               # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
+               # stripping nonexistent or useless pieces
+               $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
+                       || $params{'hash_parent'} || $params{'hash'});
                if (defined $params{'hash_base'}) {
-                       $href .= "/".esc_url($params{'hash_base'});
-                       if (defined $params{'file_name'}) {
+                       if (defined $params{'hash_parent_base'}) {
+                               $href .= esc_url($params{'hash_parent_base'});
+                               # skip the file_parent if it's the same as the file_name
+                               delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
+                               if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
+                                       $href .= ":/".esc_url($params{'file_parent'});
+                                       delete $params{'file_parent'};
+                               }
+                               $href .= "..";
+                               delete $params{'hash_parent'};
+                               delete $params{'hash_parent_base'};
+                       } elsif (defined $params{'hash_parent'}) {
+                               $href .= esc_url($params{'hash_parent'}). "..";
+                               delete $params{'hash_parent'};
+                       }
+
+                       $href .= esc_url($params{'hash_base'});
+                       if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
                                $href .= ":/".esc_url($params{'file_name'});
                                delete $params{'file_name'};
                        }
                        delete $params{'hash'};
                        delete $params{'hash_base'};
                } elsif (defined $params{'hash'}) {
-                       $href .= "/".esc_url($params{'hash'});
+                       $href .= esc_url($params{'hash'});
                        delete $params{'hash'};
                }
+
+               # If the action was a snapshot, we can absorb the
+               # snapshot_format parameter too
+               if ($is_snapshot) {
+                       my $fmt = $params{'snapshot_format'};
+                       # snapshot_format should always be defined when href()
+                       # is called, but just in case some code forgets, we
+                       # fall back to the default
+                       $fmt ||= $snapshot_fmts[0];
+                       $href .= $known_snapshot_formats{$fmt}{'suffix'};
+                       delete $params{'snapshot_format'};
+               }
        }
 
        # now encode the parameters explicitly
@@ -802,8 +920,7 @@ sub validate_project {
        my $input = shift || return undef;
        if (!validate_pathname($input) ||
                !(-d "$projectroot/$input") ||
-               !check_head_link("$projectroot/$input") ||
-               ($export_ok && !(-e "$projectroot/$input/$export_ok")) ||
+               !check_export_ok("$projectroot/$input") ||
                ($strict_export && !project_in_list($input))) {
                return undef;
        } else {
@@ -1596,8 +1713,6 @@ sub format_diff_line {
 # linked.  Pass the hash of the tree/commit to snapshot.
 sub format_snapshot_links {
        my ($hash) = @_;
-       my @snapshot_fmts = gitweb_check_feature('snapshot');
-       @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
        my $num_fmts = @snapshot_fmts;
        if ($num_fmts > 1) {
                # A parenthesized list of links bearing format names.
@@ -3800,7 +3915,7 @@ sub fill_project_list_info {
        my ($projlist, $check_forks) = @_;
        my @projects;
 
-       my $show_ctags = gitweb_check_feature('ctags');
+       my ($show_ctags) = gitweb_check_feature('ctags');
  PROJECT:
        foreach my $pr (@$projlist) {
                my (@activity) = git_get_last_activity($pr->{'path'});
@@ -3874,7 +3989,7 @@ sub git_project_list_body {
                @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
        }
 
-       my $show_ctags = gitweb_check_feature('ctags');
+       my ($show_ctags) = gitweb_check_feature('ctags');
        if ($show_ctags) {
                my %ctags;
                foreach my $p (@projects) {
@@ -4343,7 +4458,7 @@ sub git_summary {
        }
 
        # Tag cloud
-       my $show_ctags = (gitweb_check_feature('ctags'))[0];
+       my ($show_ctags) = gitweb_check_feature('ctags');
        if ($show_ctags) {
                my $ctags = git_get_project_ctags($project);
                my $cloud = git_populate_project_tagcloud($ctags);
@@ -4445,7 +4560,7 @@ sub git_blame {
        my $fd;
        my $ftype;
 
-       gitweb_check_feature('blame')
+       gitweb_check_feature('blame')[0]
            or die_error(403, "Blame view not allowed");
 
        die_error(400, "No file name given") unless $file_name;
@@ -4804,20 +4919,17 @@ sub git_tree {
 }
 
 sub git_snapshot {
-       my @supported_fmts = gitweb_check_feature('snapshot');
-       @supported_fmts = filter_snapshot_fmts(@supported_fmts);
-
        my $format = $input_params{'snapshot_format'};
-       if (!@supported_fmts) {
+       if (!@snapshot_fmts) {
                die_error(403, "Snapshots not allowed");
        }
        # default to first supported snapshot format
-       $format ||= $supported_fmts[0];
+       $format ||= $snapshot_fmts[0];
        if ($format !~ m/^[a-z0-9]+$/) {
                die_error(400, "Invalid snapshot format parameter");
        } elsif (!exists($known_snapshot_formats{$format})) {
                die_error(400, "Unknown snapshot format");
-       } elsif (!grep($_ eq $format, @supported_fmts)) {
+       } elsif (!grep($_ eq $format, @snapshot_fmts)) {
                die_error(403, "Unsupported snapshot format");
        }
 
@@ -5499,7 +5611,7 @@ sub git_history {
 }
 
 sub git_search {
-       gitweb_check_feature('search') or die_error(403, "Search is disabled");
+       gitweb_check_feature('search')[0] or die_error(403, "Search is disabled");
        if (!defined $searchtext) {
                die_error(400, "Text field is empty");
        }
@@ -5518,11 +5630,11 @@ sub git_search {
        if ($searchtype eq 'pickaxe') {
                # pickaxe may take all resources of your box and run for several minutes
                # with every query - so decide by yourself how public you make this feature
-               gitweb_check_feature('pickaxe')
+               gitweb_check_feature('pickaxe')[0]
                    or die_error(403, "Pickaxe is disabled");
        }
        if ($searchtype eq 'grep') {
-               gitweb_check_feature('grep')
+               gitweb_check_feature('grep')[0]
                    or die_error(403, "Grep is disabled");
        }
 
This page took 0.325347 seconds and 4 git commands to generate.