about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--lib/PublicInbox/Repobrowse.pm2
-rw-r--r--lib/PublicInbox/RepobrowseGitFallback.pm71
-rw-r--r--t/repobrowse_common_git.perl6
-rw-r--r--t/repobrowse_git_fallback.t87
4 files changed, 94 insertions, 72 deletions
diff --git a/lib/PublicInbox/Repobrowse.pm b/lib/PublicInbox/Repobrowse.pm
index 82d38f90..51ec14c8 100644
--- a/lib/PublicInbox/Repobrowse.pm
+++ b/lib/PublicInbox/Repobrowse.pm
@@ -68,7 +68,7 @@ sub root_index {
 
 sub run {
         my ($self, $cgi, $method) = @_;
-        return r(405, 'Method Not Allowed') if ($method !~ /\AGET|HEAD\z/);
+        return r(405, 'Method Not Allowed') if ($method !~ /\AGET|HEAD|POST\z/);
 
         # URL syntax: / repo [ / cmd [ / path ] ]
         # cmd: log | commit | diff | tree | view | blob | snapshot
diff --git a/lib/PublicInbox/RepobrowseGitFallback.pm b/lib/PublicInbox/RepobrowseGitFallback.pm
index 79cc2fe4..696e5b94 100644
--- a/lib/PublicInbox/RepobrowseGitFallback.pm
+++ b/lib/PublicInbox/RepobrowseGitFallback.pm
@@ -7,81 +7,16 @@ package PublicInbox::RepobrowseGitFallback;
 use strict;
 use warnings;
 use base qw(PublicInbox::RepobrowseBase);
-use Fcntl qw(:seek);
+use PublicInbox::GitHTTPBackend;
 
 # 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);
+        my $cgi = $req->{cgi};
+        PublicInbox::GitHTTPBackend::serve($cgi, $git, $expath);
 }
 
 1;
diff --git a/t/repobrowse_common_git.perl b/t/repobrowse_common_git.perl
index 116ae5ab..9c62a261 100644
--- a/t/repobrowse_common_git.perl
+++ b/t/repobrowse_common_git.perl
@@ -4,7 +4,6 @@
 use strict;
 use warnings;
 use Test::More;
-use Data::Dumper;
 use File::Temp qw/tempdir/;
 use Cwd qw/getcwd/;
 my @mods = qw(HTTP::Request::Common Plack::Request Plack::Test URI::Escape);
@@ -31,8 +30,9 @@ sub dechunk ($) {
 }
 
 use_ok $_ foreach @mods;
-my $git_dir = tempdir(CLEANUP => 1);
+my $git_dir = tempdir('repobrowse-XXXXXX', CLEANUP => 1, TMPDIR => 1);
 my $psgi = "examples/repobrowse.psgi";
+my $repobrowse_config = "$git_dir/repobrowse_config";
 my $app;
 ok(-f $psgi, 'psgi example for repobrowse.psgi found');
 {
@@ -49,7 +49,6 @@ ok(-f $psgi, 'psgi example for repobrowse.psgi found');
         }
         waitpid $pid, 0;
         is($?, 0, 'fast-import succeeded');
-        my $repobrowse_config = "$git_dir/pi_repobrowse_config";
         my $fh;
         ok((open $fh, '>', $repobrowse_config and
                 print $fh '[repo "test.git"]', "\n",
@@ -64,4 +63,5 @@ bless {
         psgi => $psgi,
         git_dir => $git_dir,
         app => $app,
+        repobrowse_config => $repobrowse_config,
 }, 'Repobrowse::TestGit';
diff --git a/t/repobrowse_git_fallback.t b/t/repobrowse_git_fallback.t
new file mode 100644
index 00000000..99f2ee70
--- /dev/null
+++ b/t/repobrowse_git_fallback.t
@@ -0,0 +1,87 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use warnings;
+use Test::More;
+my $test = require './t/repobrowse_common_git.perl';
+foreach my $mod (qw(Danga::Socket HTTP::Parser::XS HTTP::Date HTTP::Status)) {
+        eval "require $mod";
+        plan skip_all => "$mod missing for repobrowse_git_fallback.t" if $@;
+}
+use File::Temp qw/tempdir/;
+use Cwd qw/getcwd/;
+use IO::Socket;
+use Fcntl qw(F_SETFD);
+use POSIX qw(dup2);
+my $tmpdir = tempdir('repobrowse_git_fallback-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $err = "$tmpdir/stderr.log";
+my $out = "$tmpdir/stdout.log";
+my $httpd = 'blib/script/public-inbox-httpd';
+my $psgi = getcwd() . '/' . $test->{psgi};
+my %opts = (
+        LocalAddr => '127.0.0.1',
+        ReuseAddr => 1,
+        Proto => 'tcp',
+        Type => SOCK_STREAM,
+        Listen => 1024,
+);
+my $sock = IO::Socket::INET->new(%opts);
+my $pid;
+END { kill 'TERM', $pid if defined $pid };
+my $spawn_httpd = sub {
+        $pid = fork;
+        if ($pid == 0) {
+                # pretend to be systemd:
+                dup2(fileno($sock), 3) or die "dup2 failed: $!\n";
+                my $t = IO::Handle->new_from_fd(3, 'r');
+                $t->fcntl(F_SETFD, 0);
+                $ENV{REPOBROWSE_CONFIG} = $test->{repobrowse_config};
+                $ENV{LISTEN_PID} = $$;
+                $ENV{LISTEN_FDS} = 1;
+                exec $httpd, '-W0', $psgi;
+                # exec $httpd, '-W0', "--stdout=$out", "--stderr=$err", $psgi;
+                die "FAIL: $!\n";
+        }
+        ok(defined $pid, 'forked httpd process successfully');
+};
+
+$spawn_httpd->();
+
+{
+        my $host = $sock->sockhost;
+        my $port = $sock->sockport;
+        my $url = "http://$host:$port/test.git";
+        is(system(qw(git clone -q --mirror), $url, "$tmpdir/smart.git"),
+                0, 'smart clone successful');
+        is(system('git', "--git-dir=$tmpdir/smart.git", 'fsck'), 0, 'fsck OK');
+
+        is(system('git', "--git-dir=$test->{git_dir}",
+                qw(config http.uploadpack 0)), 0, 'disabled smart HTTP');
+        is(system('git', "--git-dir=$test->{git_dir}",
+                qw(update-server-info)), 0, 'enable dumb HTTP');
+        is(system(qw(git clone -q --mirror), $url, "$tmpdir/dumb.git"),
+                0, 'dumb clone successful');
+        is(system('git', "--git-dir=$tmpdir/dumb.git", 'fsck'), 0, 'fsck dumb OK');
+
+        # allow reading description file
+        my %conn = ( PeerAddr => $host, PeerPort => $port, Proto => 'tcp',
+                Type => SOCK_STREAM);
+        my $conn = IO::Socket::INET->new(%conn);
+        ok($conn, "connected for description check");
+        $conn->write("GET /test.git/description HTTP/1.0\r\n\r\n");
+        ok($conn->read(my $buf, 8192), 'read response');
+        my ($head, $body) = split(/\r\n\r\n/, $buf, 2);
+        like($head, qr!\AHTTP/1\.0 200 !s, 'got 200 response for description');
+
+        $conn = IO::Socket::INET->new(%conn);
+        ok($conn, "connected for range check");
+        $conn->write("GET /test.git/description HTTP/1.0\r\n" .
+                        "Range: bytes=5-\r\n\r\n");
+        ok($conn->read($buf, 8192), 'read partial response');
+        my ($h2, $b2) = split(/\r\n\r\n/, $buf, 2);
+        like($h2, qr!\AHTTP/1\.0 206 !s, 'got 206 response for range');
+        is($b2, substr($body, 5), 'substring matches on 206');
+}
+
+done_testing();
+1;