-# Copyright (C) 2016-2018 all contributors <meta@public-inbox.org>
+# Copyright (C) 2016-2019 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
use HTTP::Status qw(status_message);
use Plack::Util;
use PublicInbox::Qspawn;
+use PublicInbox::Tmpfile;
# 32 is same as the git-daemon connection limit
my $default_limiter = PublicInbox::Qspawn::Limiter->new(32);
# Documentation/technical/http-protocol.txt in git.git
# requires one and exactly one query parameter:
- if ($env->{QUERY_STRING} =~ /\Aservice=git-\w+-pack\z/ ||
- $path =~ /\Agit-\w+-pack\z/) {
+ if ($env->{QUERY_STRING} =~ /\Aservice=git-[A-Za-z0-9_]+-pack\z/ ||
+ $path =~ /\Agit-[A-Za-z0-9_]+-pack\z/) {
my $ok = serve_smart($env, $git, $path);
return $ok if $ok;
}
sub drop_client ($) {
if (my $io = $_[0]->{'psgix.io'}) {
- $io->close; # this is Danga::Socket::close
+ $io->close; # this is PublicInbox::DS::close
}
}
'Cache-Control', 'public, max-age=31536000';
}
-sub serve_dumb {
- my ($env, $git, $path) = @_;
-
- my @h;
- my $type;
- if ($path =~ m!\Aobjects/[a-f0-9]{2}/[a-f0-9]{38}\z!) {
- $type = 'application/x-git-loose-object';
- cache_one_year(\@h);
- } elsif ($path =~ m!\Aobjects/pack/pack-[a-f0-9]{40}\.pack\z!) {
- $type = 'application/x-git-packed-objects';
- cache_one_year(\@h);
- } elsif ($path =~ m!\Aobjects/pack/pack-[a-f0-9]{40}\.idx\z!) {
- $type = 'application/x-git-packed-objects-toc';
- cache_one_year(\@h);
- } elsif ($path =~ /\A(?:$TEXT)\z/o) {
- $type = 'text/plain';
- push @h, @no_cache;
- } else {
- return r(404);
- }
-
- my $f = $git->{git_dir} . '/' . $path;
+sub static_result ($$$$) {
+ my ($env, $h, $f, $type) = @_;
return r(404) unless -f $f && -r _; # just in case it's a FIFO :P
- my $size = -s _;
# TODO: If-Modified-Since and Last-Modified?
open my $in, '<', $f or return r(404);
+ my $size = -s $in;
my $len = $size;
my $code = 200;
- push @h, 'Content-Type', $type;
- if (($env->{HTTP_RANGE} || '') =~ /\bbytes=(\d*)-(\d*)\z/) {
- ($code, $len) = prepare_range($env, $in, \@h, $1, $2, $size);
+ push @$h, 'Content-Type', $type;
+ if (($env->{HTTP_RANGE} || '') =~ /\bbytes=([0-9]*)-([0-9]*)\z/) {
+ ($code, $len) = prepare_range($env, $in, $h, $1, $2, $size);
if ($code == 416) {
- push @h, 'Content-Range', "bytes */$size";
- return [ 416, \@h, [] ];
+ push @$h, 'Content-Range', "bytes */$size";
+ return [ 416, $h, [] ];
}
}
- push @h, 'Content-Length', $len;
+ push @$h, 'Content-Length', $len;
my $n = 65536;
- [ $code, \@h, Plack::Util::inline_object(close => sub { close $in },
+ [ $code, $h, Plack::Util::inline_object(close => sub { close $in },
getline => sub {
return if $len == 0;
$n = $len if $len < $n;
})]
}
+sub serve_dumb {
+ my ($env, $git, $path) = @_;
+
+ my $h = [];
+ my $type;
+ if ($path =~ m!\Aobjects/[a-f0-9]{2}/[a-f0-9]{38}\z!) {
+ $type = 'application/x-git-loose-object';
+ cache_one_year($h);
+ } elsif ($path =~ m!\Aobjects/pack/pack-[a-f0-9]{40}\.pack\z!) {
+ $type = 'application/x-git-packed-objects';
+ cache_one_year($h);
+ } elsif ($path =~ m!\Aobjects/pack/pack-[a-f0-9]{40}\.idx\z!) {
+ $type = 'application/x-git-packed-objects-toc';
+ cache_one_year($h);
+ } elsif ($path =~ /\A(?:$TEXT)\z/o) {
+ $type = 'text/plain';
+ push @$h, @no_cache;
+ } else {
+ return r(404);
+ }
+
+ static_result($env, $h, "$git->{git_dir}/$path", $type);
+}
+
sub prepare_range {
my ($env, $in, $h, $beg, $end, $size) = @_;
my $code = 200;
$env{PATH_TRANSLATED} = "$git->{git_dir}/$path";
my $rdr = input_prepare($env) or return r(500);
my $qsp = PublicInbox::Qspawn->new([qw(git http-backend)], \%env, $rdr);
- $qsp->psgi_return($env, $limiter, sub {
+ $qsp->psgi_return($env, $limiter, sub { # parse_hdr
my ($r, $bref) = @_;
my $res = parse_cgi_headers($r, $bref) or return; # incomplete
$res->[0] == 403 ? serve_dumb($env, $git, $path) : $res;
if (defined $fd && $fd >= 0) {
return { 0 => $fd };
}
- open(my $in, '+>', undef);
+ my $id = "git-http.input.$env->{REMOTE_ADDR}:$env->{REMOTE_PORT}";
+ my $in = tmpfile($id);
unless (defined $in) {
err($env, "could not open temporary file: $!");
return;
return;
}
last if $r == 0;
- my $off = 0;
- while ($r > 0) {
- my $w = syswrite($in, $buf, $r, $off);
- if (defined $w) {
- $r -= $w;
- $off += $w;
- } else {
- err($env, "error writing temporary file: $!");
- return;
- }
+ unless (print $in $buf) {
+ err($env, "error writing temporary file: $!");
+ return;
}
}
+ # ensure it's visible to git-http-backend(1):
+ unless ($in->flush) {
+ err($env, "error writing temporary file: $!");
+ return;
+ }
unless (defined(sysseek($in, 0, SEEK_SET))) {
err($env, "error seeking temporary file: $!");
return;
foreach my $l (split(/\r?\n/, $h)) {
my ($k, $v) = split(/:\s*/, $l, 2);
if ($k =~ /\AStatus\z/i) {
- ($code) = ($v =~ /\b(\d+)\b/);
+ ($code) = ($v =~ /\b([0-9]+)\b/);
} else {
push @h, $k, $v;
}