]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
repobrowse: add fallback module for git to serve static files
authorEric Wong <e@80x24.org>
Fri, 25 Dec 2015 08:37:45 +0000 (08:37 +0000)
committerEric Wong <e@80x24.org>
Tue, 5 Apr 2016 18:58:27 +0000 (18:58 +0000)
This will eventually be expanded to support git-http-backend(1)

lib/PublicInbox/RepoBrowse.pm
lib/PublicInbox/RepoBrowseBase.pm
lib/PublicInbox/RepoBrowseGitFallback.pm [new file with mode: 0644]

index 41422193326d733fa41118ceabd703910b062351..63228249036dea70b5ad5693d7e4c9361fc88458 100644 (file)
@@ -66,8 +66,11 @@ sub run {
        if (defined $cmd && length $cmd) {
                my $vcs_lc = $repo_info->{vcs};
                my $vcs = $VCS{$vcs_lc} or return r404();
-               my $mod = $CMD{$cmd} or return r404();
-               return r404() unless defined $mod && defined $vcs;
+               my $mod = $CMD{$cmd};
+               unless ($mod) {
+                       unshift @extra, $cmd;
+                       $mod = 'Fallback';
+               }
                $mod = load_once("PublicInbox::RepoBrowse$vcs$mod");
                $vcs = load_once("PublicInbox::$vcs");
                $repo_info->{$vcs_lc} ||= $vcs->new($repo_info->{path});
index 7f69108b42d7c87c827a64f43912c79c2ec92a7d..8e45bac8d3c923ef393ef037e5ad102faf39cdc8 100644 (file)
@@ -27,9 +27,7 @@ sub mime_load {
                next if /^#/; # no comments
                my ($type, @ext) = split(/\s+/);
 
-               # XSS protection.  Assume the browser knows what to do
-               # with images/audio/video...
-               if (defined $type && $type =~ m!\A(?:image|audio|video)/!) {
+               if (defined $type) {
                        $rv{$_} = $type foreach @ext;
                }
        }
@@ -37,13 +35,22 @@ sub mime_load {
 }
 
 # returns undef if missing, so users can scan the blob if needed
-sub mime_type {
+sub mime_type_unsafe {
        my ($self, $fn) = @_;
        $fn =~ /\.([^\.]+)\z/ or return;
        my $ext = $1;
        my $m = $self->{mime_types} ||= $self->mime_load('/etc/mime.types');
-
        $m->{$ext};
 }
 
+sub mime_type {
+       my ($self, $fn) = @_;
+       my $ct = $self->mime_type_unsafe($fn);
+
+       # XSS protection.  Assume the browser knows what to do
+       # with images/audio/video; but don't allow random HTML from
+       # a repository to be served
+       (defined($ct) && $ct =~ m!\A(?:image|audio|video)/!) ? $ct : undef;
+}
+
 1;
diff --git a/lib/PublicInbox/RepoBrowseGitFallback.pm b/lib/PublicInbox/RepoBrowseGitFallback.pm
new file mode 100644 (file)
index 0000000..03b282e
--- /dev/null
@@ -0,0 +1,87 @@
+# Copyright (C) 2015 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ (https://www.gnu.org/licenses/agpl-3.0.txt)
+
+# when no endpoints match, fallback to this and serve a static file
+# This can serve Smart HTTP in the future.
+package PublicInbox::RepoBrowseGitFallback;
+use strict;
+use warnings;
+use base qw(PublicInbox::RepoBrowseBase);
+use Fcntl qw(:seek);
+
+# overrides PublicInbox::RepoBrowseBase::call
+sub call {
+       my ($self, undef, $req) = @_;
+       my $expath = $req->{expath};
+       return if index($expath, '..') >= 0; # prevent path traversal
+
+       my $git = $req->{repo_info}->{git};
+       my $f = "$git->{git_dir}/$expath";
+       return unless -f $f && -r _;
+       my @st = stat(_);
+       my ($size, $mtime) = ($st[7], $st[9]);
+       # TODO: if-modified-since and last-modified...
+       open my $in, '<', $f or return;
+       my $code = 200;
+       my $len = $size;
+       my @h;
+
+       # FIXME: this is Plack-only
+       my $range = eval { $req->{cgi}->{env}->{HTTP_RANGE} };
+       if (defined $range && $range =~ /\bbytes=(\d*)-(\d*)\z/) {
+               ($code, $len) = prepare_range($req, $in, \@h, $1, $2, $size);
+       }
+
+       # we use the unsafe variant since we assume the server admin
+       # would not place untrusted HTML/JS/CSS in the git directory
+       my $type = $self->mime_type_unsafe($expath) || 'text/plain';
+       push @h, 'Content-Type', $type, 'Content-Length', $len;
+       sub {
+               my ($res) = @_; # Plack callback
+               my $fh = $res->([ $code, \@h ]);
+               my $buf;
+               my $n = 8192;
+               while ($size > 0) {
+                       $n = $size if $size < $n;
+                       my $r = read($in, $buf, $n);
+                       last if (!defined($r) || $r <= 0);
+                       $fh->write($buf);
+               }
+               $fh->close;
+       }
+}
+
+sub bad_range { [ 416, [], [] ] }
+
+sub prepare_range {
+       my ($req, $in, $h, $beg, $end, $size) = @_;
+       my $code = 200;
+       my $len = $size;
+       if ($beg eq '') {
+               if ($end ne '') { # last N bytes
+                       $beg = $size - $end;
+                       $beg = 0 if $beg < 0;
+                       $end = $size - 1;
+                       $code = 206;
+               }
+       } else {
+               if ($end eq '' || $end >= $size) {
+                       $end = $size - 1;
+                       $code = 206;
+               } elsif ($end < $size) {
+                       $code = 206;
+               }
+       }
+       if ($code == 206) {
+               $len = $end - $beg + 1;
+               seek($in, $beg, SEEK_SET) or return [ 500, [], [] ];
+               push @$h, qw(Accept-Ranges bytes),
+                               'Content-Range', "bytes $beg-$end/$size";
+
+               # FIXME: Plack::Middleware::Deflater bug?
+               $req->{cgi}->{env}->{'psgix.no-compress'} = 1;
+       }
+       ($code, $len);
+}
+
+1;