]> Lady’s Gitweb - Gitweb/blobdiff - gitweb.perl
gitweb: rename gitweb_check_feature to gitweb_get_feature
[Gitweb] / gitweb.perl
index 22de2e127dd6457350d0cfba798793e5be9ee3840bd92721378cca0a1fe971f6..a0cdd4bdbc47dc2144d6f4f0d6601317a9acf1a51fac15be352f23307243a401 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++";
 
@@ -186,7 +191,7 @@ our %feature = (
        # if there is no 'sub' key (no feature-sub), then feature cannot be
        # overriden
        #
-       # use gitweb_check_feature(<feature>) to check if <feature> is enabled
+       #
 
        # Enable the 'blame' blob view, showing the last commit that modified
        # each line in the file. This can be very CPU-intensive.
@@ -325,7 +330,7 @@ our %feature = (
                'default' => [0]},
 );
 
-sub gitweb_check_feature {
+sub gitweb_get_feature {
        my ($name) = @_;
        return unless exists $feature{$name};
        my ($sub, $override, @defaults) = (
@@ -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_get_feature('snapshot');
+@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+
 # dispatch
 if (!defined $action) {
        if (defined $hash) {
@@ -730,12 +811,14 @@ sub href (%) {
                }
        }
 
-       my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+       my ($use_pathinfo) = gitweb_get_feature('pathinfo');
        if ($use_pathinfo) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
-               #   - hash or hash_base:filename
+               #   - 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'}) {
-                               $href .= ":".esc_url($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.
@@ -1987,7 +2102,7 @@ sub git_get_projects_list {
        $filter ||= '';
        $filter =~ s/\.git$//;
 
-       my ($check_forks) = gitweb_check_feature('forks');
+       my ($check_forks) = gitweb_get_feature('forks');
 
        if (-d $projects_list) {
                # search in directory
@@ -2833,7 +2948,7 @@ EOF
        }
        print "</div>\n";
 
-       my ($have_search) = gitweb_check_feature('search');
+       my ($have_search) = gitweb_get_feature('search');
        if (defined $project && $have_search) {
                if (!defined $searchtext) {
                        $searchtext = "";
@@ -2847,7 +2962,7 @@ EOF
                        $search_hash = "HEAD";
                }
                my $action = $my_uri;
-               my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+               my ($use_pathinfo) = gitweb_get_feature('pathinfo');
                if ($use_pathinfo) {
                        $action .= "/".esc_url($project);
                }
@@ -2970,7 +3085,7 @@ sub git_print_page_nav {
        $arg{'tree'}{'hash'} = $treehead if defined $treehead;
        $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
 
-       my @actions = gitweb_check_feature('actions');
+       my @actions = gitweb_get_feature('actions');
        my %repl = (
                '%' => '%',
                'n' => $project,         # project name
@@ -3340,7 +3455,7 @@ sub is_patch_split {
 sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
        my ($parent) = $parents[0];
-       my ($have_blame) = gitweb_check_feature('blame');
+       my ($have_blame) = gitweb_get_feature('blame');
        print "<div class=\"list_head\">\n";
        if ($#{$difftree} > 10) {
                print(($#{$difftree} + 1) . " files changed:\n");
@@ -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_get_feature('ctags');
  PROJECT:
        foreach my $pr (@$projlist) {
                my (@activity) = git_get_last_activity($pr->{'path'});
@@ -3854,7 +3969,7 @@ sub git_project_list_body {
        # actually uses global variable $project
        my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
 
-       my ($check_forks) = gitweb_check_feature('forks');
+       my ($check_forks) = gitweb_get_feature('forks');
        my @projects = fill_project_list_info($projlist, $check_forks);
 
        $order ||= $default_projects_order;
@@ -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_get_feature('ctags');
        if ($show_ctags) {
                my %ctags;
                foreach my $p (@projects) {
@@ -4314,7 +4429,7 @@ sub git_summary {
        my @taglist  = git_get_tags_list(16);
        my @headlist = git_get_heads_list(16);
        my @forklist;
-       my ($check_forks) = gitweb_check_feature('forks');
+       my ($check_forks) = gitweb_get_feature('forks');
 
        if ($check_forks) {
                @forklist = git_get_projects_list($project);
@@ -4343,7 +4458,7 @@ sub git_summary {
        }
 
        # Tag cloud
-       my $show_ctags = (gitweb_check_feature('ctags'))[0];
+       my ($show_ctags) = gitweb_get_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_get_feature('blame')[0]
            or die_error(403, "Blame view not allowed");
 
        die_error(400, "No file name given") unless $file_name;
@@ -4633,7 +4748,7 @@ sub git_blob {
                $expires = "+1d";
        }
 
-       my ($have_blame) = gitweb_check_feature('blame');
+       my ($have_blame) = gitweb_get_feature('blame');
        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);
@@ -4726,7 +4841,7 @@ sub git_tree {
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
        my $basedir = '';
-       my ($have_blame) = gitweb_check_feature('blame');
+       my ($have_blame) = gitweb_get_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
                if (defined $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_get_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_get_feature('pickaxe')[0]
                    or die_error(403, "Pickaxe is disabled");
        }
        if ($searchtype eq 'grep') {
-               gitweb_check_feature('grep')
+               gitweb_get_feature('grep')[0]
                    or die_error(403, "Grep is disabled");
        }
 
@@ -5727,7 +5839,7 @@ insensitive).</p>
 <dt><b>commit</b></dt>
 <dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
 EOT
-       my ($have_grep) = gitweb_check_feature('grep');
+       my ($have_grep) = gitweb_get_feature('grep');
        if ($have_grep) {
                print <<EOT;
 <dt><b>grep</b></dt>
@@ -5744,7 +5856,7 @@ EOT
 <dt><b>committer</b></dt>
 <dd>Name and e-mail of the committer and date of commit will be scanned for the given pattern.</dd>
 EOT
-       my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+       my ($have_pickaxe) = gitweb_get_feature('pickaxe');
        if ($have_pickaxe) {
                print <<EOT;
 <dt><b>pickaxe</b></dt>
@@ -5796,7 +5908,7 @@ sub git_shortlog {
 
 sub git_feed {
        my $format = shift || 'atom';
-       my ($have_blame) = gitweb_check_feature('blame');
+       my ($have_blame) = gitweb_get_feature('blame');
 
        # Atom: http://www.atomenabled.org/developers/syndication/
        # RSS:  http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
This page took 0.371413 seconds and 4 git commands to generate.