From: Eric Wong Date: Fri, 25 Dec 2015 08:37:45 +0000 (+0000) Subject: repobrowse: add fallback module for git to serve static files X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4c83e8b408703fae11fb90f86b39803c58584dd6;p=thirdparty%2Fpublic-inbox.git repobrowse: add fallback module for git to serve static files This will eventually be expanded to support git-http-backend(1) --- diff --git a/lib/PublicInbox/RepoBrowse.pm b/lib/PublicInbox/RepoBrowse.pm index 414221933..632282490 100644 --- a/lib/PublicInbox/RepoBrowse.pm +++ b/lib/PublicInbox/RepoBrowse.pm @@ -66,8 +66,11 @@ sub run { if (defined $cmd && length $cmd) { my $vcs_lc = $repo_info->{vcs}; my $vcs = $VCS{$vcs_lc} or return r404(); - my $mod = $CMD{$cmd} or return r404(); - return r404() unless defined $mod && defined $vcs; + my $mod = $CMD{$cmd}; + unless ($mod) { + unshift @extra, $cmd; + $mod = 'Fallback'; + } $mod = load_once("PublicInbox::RepoBrowse$vcs$mod"); $vcs = load_once("PublicInbox::$vcs"); $repo_info->{$vcs_lc} ||= $vcs->new($repo_info->{path}); diff --git a/lib/PublicInbox/RepoBrowseBase.pm b/lib/PublicInbox/RepoBrowseBase.pm index 7f69108b4..8e45bac8d 100644 --- a/lib/PublicInbox/RepoBrowseBase.pm +++ b/lib/PublicInbox/RepoBrowseBase.pm @@ -27,9 +27,7 @@ sub mime_load { next if /^#/; # no comments my ($type, @ext) = split(/\s+/); - # XSS protection. Assume the browser knows what to do - # with images/audio/video... - if (defined $type && $type =~ m!\A(?:image|audio|video)/!) { + if (defined $type) { $rv{$_} = $type foreach @ext; } } @@ -37,13 +35,22 @@ sub mime_load { } # returns undef if missing, so users can scan the blob if needed -sub mime_type { +sub mime_type_unsafe { my ($self, $fn) = @_; $fn =~ /\.([^\.]+)\z/ or return; my $ext = $1; my $m = $self->{mime_types} ||= $self->mime_load('/etc/mime.types'); - $m->{$ext}; } +sub mime_type { + my ($self, $fn) = @_; + my $ct = $self->mime_type_unsafe($fn); + + # XSS protection. Assume the browser knows what to do + # with images/audio/video; but don't allow random HTML from + # a repository to be served + (defined($ct) && $ct =~ m!\A(?:image|audio|video)/!) ? $ct : undef; +} + 1; diff --git a/lib/PublicInbox/RepoBrowseGitFallback.pm b/lib/PublicInbox/RepoBrowseGitFallback.pm new file mode 100644 index 000000000..03b282eff --- /dev/null +++ b/lib/PublicInbox/RepoBrowseGitFallback.pm @@ -0,0 +1,87 @@ +# Copyright (C) 2015 all contributors +# 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 +# This can serve Smart HTTP in the future. +package PublicInbox::RepoBrowseGitFallback; +use strict; +use warnings; +use base qw(PublicInbox::RepoBrowseBase); +use Fcntl qw(:seek); + +# 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); +} + +1;