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;
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);
}
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');
{
}
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",
psgi => $psgi,
git_dir => $git_dir,
app => $app,
+ repobrowse_config => $repobrowse_config,
}, 'Repobrowse::TestGit';
--- /dev/null
+# 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;