diff options
-rw-r--r-- | lib/PublicInbox/Repobrowse.pm | 2 | ||||
-rw-r--r-- | lib/PublicInbox/RepobrowseGitFallback.pm | 71 | ||||
-rw-r--r-- | t/repobrowse_common_git.perl | 6 | ||||
-rw-r--r-- | t/repobrowse_git_fallback.t | 87 |
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; |