lib/PublicInbox/Cgit.pm | 41 ++++++++++++++++++++++++++++++++++++++--- lib/PublicInbox/Config.pm | 10 ++++++++++ lib/PublicInbox/GitHTTPBackend.pm | 62 ++++++++++++++++++++++++++++------------------------- diff --git a/lib/PublicInbox/Cgit.pm b/lib/PublicInbox/Cgit.pm index 9ba9e14dea0efb21602381f87d851115af67125b..8922ec567c902d0cf4c3a5907cc6551021990aad 100644 --- a/lib/PublicInbox/Cgit.pm +++ b/lib/PublicInbox/Cgit.pm @@ -13,17 +13,45 @@ *r = *PublicInbox::GitHTTPBackend::r; *input_prepare = *PublicInbox::GitHTTPBackend::input_prepare; *parse_cgi_headers = *PublicInbox::GitHTTPBackend::parse_cgi_headers; *serve = *PublicInbox::GitHTTPBackend::serve; +*static_result = *PublicInbox::GitHTTPBackend::static_result; use warnings; use PublicInbox::Qspawn; +use Plack::MIME; + +sub locate_cgit ($) { + my ($pi_config) = @_; + my $cgit_bin = $pi_config->{'publicinbox.cgitbin'}; + my $cgit_data = $pi_config->{'publicinbox.cgitdata'}; + + # /var/www/htdocs/cgit is the default install path from cgit.git + # /usr/{lib,share}/cgit is where Debian puts cgit + # TODO: check other distros for common paths + unless (defined $cgit_bin) { + foreach (qw(/var/www/htdocs/cgit /usr/lib/cgit)) { + my $x = "$_/cgit.cgi"; + next unless -x $x; + $cgit_bin = $x; + last; + } + } + unless (defined $cgit_data) { + foreach my $d (qw(/var/www/htdocs/cgit /usr/share/cgit)) { + my $f = "$d/cgit.css"; + next unless -f $f; + $cgit_data = $d; + last; + } + } + ($cgit_bin, $cgit_data); +} sub new { my ($class, $pi_config) = @_; - my $cgit_bin = $pi_config->{'publicinbox.cgitbin'} || - # Debian default location: - '/usr/lib/cgit/cgit.cgi'; + my ($cgit_bin, $cgit_data) = locate_cgit($pi_config); my $self = bless { cmd => [ $cgit_bin ], + cgit_data => $cgit_data, pi_config => $pi_config, }, $class; @@ -39,6 +67,9 @@ } while (my ($nick, $repo) = each %$code_repos) { $self->{"\0$nick"} = $repo; } + my $cgit_static = $pi_config->{-cgit_static}; + my $static = join('|', map { quotemeta $_ } keys %$cgit_static); + $self->{static} = qr/\A($static)\z/; $self; } @@ -66,6 +97,10 @@ my ($nick, $path) = ($1, $2); if (my $git = $self->{"\0$nick"}) { return serve($env, $git, $path); } + } elsif ($path_info =~ m!$self->{static}!) { + my $f = $1; + my $type = Plack::MIME->mime_type($f); + return static_result($env, [], "$self->{cgit_data}$f", $type); } my $cgi_env = { PATH_INFO => $path_info }; diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm index 9f1e57ac7554dc58764b3bb4cf3504a6803b0adc..2c1c51115894a61d79da72da25e205c840fff803 100644 --- a/lib/PublicInbox/Config.pm +++ b/lib/PublicInbox/Config.pm @@ -234,6 +234,12 @@ } sub parse_cgitrc { my ($self, $cgitrc, $nesting) = @_; + if ($nesting == 0) { + # defaults: + my %s = map { $_ => 1 } qw(/cgit.css /cgit.png + /favicon.ico /robots.txt); + $self->{-cgit_static} = \%s; + } # same limit as cgit/configfile.c::parse_configfile return if $nesting > 8; @@ -263,6 +269,10 @@ } elsif (m!\Ascan-hidden-path=(\d+)\z!) { $self->{-cgit_scan_hidden_path} = $1; } elsif (m!\Ascan-path=(.+)\z!) { scan_path_coderepo($self, '', $1); + + } elsif (m!\A(?:css|favicon|logo|repo\.logo)=(/.+)\z!) { + # absolute paths for static files via PublicInbox::Cgit + $self->{-cgit_static}->{$1} = 1; } } cgit_repo_merge($self, $repo) if $repo; diff --git a/lib/PublicInbox/GitHTTPBackend.pm b/lib/PublicInbox/GitHTTPBackend.pm index cd8cdf84abde7addb167af5f0fec59c5030ed87d..57944a06be1365426cbec24a39d3e6f3ebed55fb 100644 --- a/lib/PublicInbox/GitHTTPBackend.pm +++ b/lib/PublicInbox/GitHTTPBackend.pm @@ -80,46 +80,26 @@ push @$h, 'Expires', $t == $prev ? $exp : ($exp = time2str($prev = $t)), '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; + push @$h, 'Content-Type', $type; if (($env->{HTTP_RANGE} || '') =~ /\bbytes=(\d*)-(\d*)\z/) { - ($code, $len) = prepare_range($env, $in, \@h, $1, $2, $size); + ($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; @@ -136,6 +116,30 @@ } drop_client($env); return; })] +} + +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 {