]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/GitHTTPBackend.pm
githttpbackend: require IO::File explicitly
[public-inbox.git] / lib / PublicInbox / GitHTTPBackend.pm
index 9c32535bb70ad414195017729e73fd448d84054e..2c81d4c8d8ecf864d76cc8ceb1b4820d54ef7b1d 100644 (file)
@@ -7,7 +7,8 @@ package PublicInbox::GitHTTPBackend;
 use strict;
 use warnings;
 use Fcntl qw(:seek);
-use POSIX qw(dup2);
+use IO::File;
+use PublicInbox::Spawn qw(spawn);
 
 # n.b. serving "description" and "cloneurl" should be innocuous enough to
 # not cause problems.  serving "config" might...
@@ -73,7 +74,7 @@ sub serve {
                my $n = 8192;
                while ($len > 0) {
                        $n = $len if $len < $n;
-                       my $r = read($in, $buf, $n);
+                       my $r = sysread($in, $buf, $n);
                        last if (!defined($r) || $r <= 0);
                        $len -= $r;
                        $fh->write($buf);
@@ -132,52 +133,37 @@ sub serve_smart {
        my $buf;
        my $in;
        my $err = $env->{'psgi.errors'};
-       if (fileno($input) >= 0) {
+       my $fd = eval { fileno($input) };
+       if (defined $fd && $fd >= 0) {
                $in = $input;
-       } else { # FIXME untested
-               $in = IO::File->new_tmpfile;
-               while (1) {
-                       my $r = $input->read($buf, 8192);
-                       unless (defined $r) {
-                               $err->print('error reading input: ', $!, "\n");
-                               return r(500);
-                       }
-                       last if ($r == 0);
-                       $in->write($buf);
-               }
-               $in->flush;
-               $in->sysseek(0, SEEK_SET);
+       } else {
+               $in = input_to_file($env) or return r(500);
        }
        my ($rpipe, $wpipe);
        unless (pipe($rpipe, $wpipe)) {
-               $err->print('error creating pipe', $!, "\n");
+               $err->print("error creating pipe: $!\n");
                return r(500);
        }
-       my $pid = fork; # TODO: vfork under Linux...
-       unless (defined $pid) {
-               $err->print('error forking: ', $!, "\n");
-               return r(500);
+       my %env = %ENV;
+       # GIT_HTTP_EXPORT_ALL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL
+       # may be set in the server-process and are passed as-is
+       foreach my $name (qw(QUERY_STRING
+                               REMOTE_USER REMOTE_ADDR
+                               HTTP_CONTENT_ENCODING
+                               CONTENT_TYPE
+                               SERVER_PROTOCOL
+                               REQUEST_METHOD)) {
+               my $val = $env->{$name};
+               $env{$name} = $val if defined $val;
        }
        my $git_dir = $git->{git_dir};
-       if ($pid == 0) {
-               # GIT_HTTP_EXPORT_ALL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL
-               # may be set in the server-process and are passed as-is
-               foreach my $name (qw(QUERY_STRING
-                                       REMOTE_USER REMOTE_ADDR
-                                       HTTP_CONTENT_ENCODING
-                                       CONTENT_TYPE
-                                       SERVER_PROTOCOL
-                                       REQUEST_METHOD)) {
-                       my $val = $env->{$name};
-                       $ENV{$name} = $val if defined $val;
-               }
-               # $ENV{GIT_PROJECT_ROOT} = $git->{git_dir};
-               $ENV{GIT_HTTP_EXPORT_ALL} = '1';
-               $ENV{PATH_TRANSLATED} = "$git_dir/$path";
-               dup2(fileno($in), 0) or die "redirect stdin failed: $!\n";
-               dup2(fileno($wpipe), 1) or die "redirect stdout failed: $!\n";
-               my @cmd = qw(git http-backend);
-               exec(@cmd) or die 'exec `' . join(' ', @cmd). "' failed: $!\n";
+       $env{GIT_HTTP_EXPORT_ALL} = '1';
+       $env{PATH_TRANSLATED} = "$git_dir/$path";
+       my %rdr = ( 0 => fileno($in), 1 => fileno($wpipe) );
+       my $pid = spawn([qw(git http-backend)], \%env, \%rdr);
+       unless (defined $pid) {
+               $err->print("error spawning: $!\n");
+               return r(500);
        }
        $wpipe = $in = undef;
        $buf = '';
@@ -217,14 +203,14 @@ sub serve_smart {
                if ($fh) { # stream body from git-http-backend to HTTP client
                        $fh->write($buf);
                        $buf = '';
-               } elsif ($buf =~ s/\A(.*?)\r?\n\r?\n//s) { # parse headers
+               } elsif ($buf =~ s/\A(.*?)\r\n\r\n//s) { # parse headers
                        my $h = $1;
                        my $code = 200;
                        my @h;
-                       foreach my $l (split(/\r?\n/, $h)) {
+                       foreach my $l (split(/\r\n/, $h)) {
                                my ($k, $v) = split(/:\s*/, $l, 2);
                                if ($k =~ /\AStatus\z/i) {
-                                       $code = int($v);
+                                       ($code) = ($v =~ /\b(\d+)\b/);
                                } else {
                                        push @h, $k, $v;
                                }
@@ -239,7 +225,7 @@ sub serve_smart {
        if (my $async = $env->{'pi-httpd.async'}) {
                $rpipe = $async->($rpipe, $cb);
                sub { ($res) = @_ } # let Danga::Socket handle the rest.
-       } else { # synchronous loop
+       } else { # synchronous loop for other PSGI servers
                $vin = '';
                vec($vin, fileno($rpipe), 1) = 1;
                sub {
@@ -249,4 +235,24 @@ sub serve_smart {
        }
 }
 
+sub input_to_file {
+       my ($env) = @_;
+       my $in = IO::File->new_tmpfile;
+       my $input = $env->{'psgi.input'};
+       my $buf;
+       while (1) {
+               my $r = $input->read($buf, 8192);
+               unless (defined $r) {
+                       my $err = $env->{'psgi.errors'};
+                       $err->print("error reading input: $!\n");
+                       return;
+               }
+               last if ($r == 0);
+               $in->write($buf);
+       }
+       $in->flush;
+       $in->sysseek(0, SEEK_SET);
+       return $in;
+}
+
 1;