Documentation/public-inbox-config.pod | 37 +++++++++++++++++++++++++++++++++++++ MANIFEST | 1 + lib/PublicInbox/Config.pm | 2 +- lib/PublicInbox/Inbox.pm | 9 +++++++++ lib/PublicInbox/WWW.pm | 14 ++++++++++++-- lib/PublicInbox/WwwListing.pm | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/PublicInbox/WwwStream.pm | 10 +++++++--- diff --git a/Documentation/public-inbox-config.pod b/Documentation/public-inbox-config.pod index f894eb3d7ff7d148fe275e86dd6027c6e7bc69aa..17b8bac70987dab9ede2fef3e6d29bae4f2eb79d 100644 --- a/Documentation/public-inbox-config.pod +++ b/Documentation/public-inbox-config.pod @@ -188,6 +188,14 @@ be treated as the default value. Default: 25 +=item publicinbox..hide + +A comma-delimited list of listings to hide the inbox from. + +Valid values are currently "www". + +Default: none + =item coderepo..dir The path to a git repository for "publicinbox..coderepo" @@ -216,6 +224,35 @@ interface can spawn cgit as a fallback if the publicinbox.cgitrc directive is configured. Default: /var/www/htdocs/cgit/cgit.cgi or /usr/lib/cgit/cgit.cgi + +=item publicinbox.wwwlisting + +Enable a HTML listing style when the root path of the URL '/' is accessed. +Valid values are: + +=over 8 + +=item all + +Show all inboxes + +=item 404 + +Return a 404 page. This is useful to allow customization with +L + +=item match=domain + +Only show inboxes with URLs which belong to the domain of the HTTP +request + +=for TODO comment + +support showing cgit listing + +=back + +Default: 404 =back diff --git a/MANIFEST b/MANIFEST index 150e337fca28196c2c72f41f9806e58d3a72ccc7..881d2f07c5711859e20896ca3732e1ba47e6dfdb 100644 --- a/MANIFEST +++ b/MANIFEST @@ -127,6 +127,7 @@ lib/PublicInbox/WatchMaildir.pm lib/PublicInbox/WwwAtomStream.pm lib/PublicInbox/WwwAttach.pm lib/PublicInbox/WwwHighlight.pm +lib/PublicInbox/WwwListing.pm lib/PublicInbox/WwwStream.pm lib/PublicInbox/WwwText.pm sa_config/Makefile diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm index 631c7880f331de489ea654e22825fa4e86be5819..09f9179b085a4dd90aed20e0d8b277ede66cc870 100644 --- a/lib/PublicInbox/Config.pm +++ b/lib/PublicInbox/Config.pm @@ -389,7 +389,7 @@ } } # TODO: more arrays, we should support multi-value for # more things to encourage decentralization - foreach my $k (qw(address altid nntpmirror coderepo)) { + foreach my $k (qw(address altid nntpmirror coderepo hide)) { if (defined(my $v = $self->{"$pfx.$k"})) { $ibx->{$k} = _array($v); } diff --git a/lib/PublicInbox/Inbox.pm b/lib/PublicInbox/Inbox.pm index 0d28dd04023e95338e456c8ac2f7403f71bcca12..286555f65cb3b4c8f4a7820b0489e4f6f6005e72 100644 --- a/lib/PublicInbox/Inbox.pm +++ b/lib/PublicInbox/Inbox.pm @@ -95,6 +95,15 @@ my $dir = $opts->{mainrepo}; if (defined $dir && -f "$dir/inbox.lock") { $opts->{version} = 2; } + + # allow any combination of multi-line or comma-delimited hide entries + my $hide = {}; + if (defined(my $h = $opts->{hide})) { + foreach my $v (@$h) { + $hide->{$_} = 1 foreach (split(/\s*,\s*/, $v)); + } + $opts->{-hide} = $hide; + } bless $opts, $class; } diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm index 268c5b8c7de8e49872cb6333b869b5f3c3170fdb..1f3ca1574aa0c5c84c43dcc31d8797f08b6de5e6 100644 --- a/lib/PublicInbox/WWW.pm +++ b/lib/PublicInbox/WWW.pm @@ -68,8 +68,9 @@ ($k, $v) } split(/[&;]+/, $env->{QUERY_STRING}); $ctx->{qp} = \%qp; - # not using $env->{PATH_INFO} here since that's already decoded + # avoiding $env->{PATH_INFO} here since that's already decoded my ($path_info) = ($env->{REQUEST_URI} =~ path_re($env)); + $path_info //= $env->{PATH_INFO}; my $method = $env->{REQUEST_METHOD}; if ($method eq 'POST') { @@ -87,7 +88,7 @@ } # top-level indices and feeds if ($path_info eq '/') { - r404(); + www_listing($self)->call($env); } elsif ($path_info =~ m!$INBOX_RE\z!o) { invalid_inbox($ctx, $1) || r301($ctx, $1); } elsif ($path_info =~ m!$INBOX_RE(?:/|/index\.html)?\z!o) { @@ -157,6 +158,7 @@ } if (ref($self)) { $self->cgit; $self->stylesheets_prepare($_) for ('', '../', '../../'); + $self->www_listing; } } @@ -486,6 +488,14 @@ PublicInbox::Cgit->new($pi_config); } else { Plack::Util::inline_object(call => sub { r404() }); } + } +} + +sub www_listing { + my ($self) = @_; + $self->{www_listing} ||= do { + require PublicInbox::WwwListing; + PublicInbox::WwwListing->new($self); } } diff --git a/lib/PublicInbox/WwwListing.pm b/lib/PublicInbox/WwwListing.pm new file mode 100644 index 0000000000000000000000000000000000000000..e8dad4b892f50aceac866ef0956338edbb569ab0 --- /dev/null +++ b/lib/PublicInbox/WwwListing.pm @@ -0,0 +1,104 @@ +# Copyright (C) 2019 all contributors +# License: AGPL-3.0+ + +# Provide an HTTP-accessible listing of inboxes. +# Used by PublicInbox::WWW +package PublicInbox::WwwListing; +use strict; +use warnings; +use PublicInbox::Hval qw(ascii_html); +use PublicInbox::Linkify; +use PublicInbox::View; + +sub list_all ($$) { + my ($self, undef) = @_; + my @list; + $self->{pi_config}->each_inbox(sub { + my ($ibx) = @_; + push @list, $ibx unless $ibx->{-hide}->{www}; + }); + \@list; +} + +sub list_match_domain ($$) { + my ($self, $env) = @_; + my @list; + my $host = $env->{HTTP_HOST} // $env->{SERVER_NAME}; + $host =~ s/:\d+\z//; + my $re = qr!\A(?:https?:)?//\Q$host\E(?::\d+)?/!i; + $self->{pi_config}->each_inbox(sub { + my ($ibx) = @_; + push @list, $ibx if !$ibx->{-hide}->{www} && $ibx->{url} =~ $re; + }); + \@list; +} + +sub list_404 ($$) { [] } + +# TODO: +cgit +my %VALID = ( + all => *list_all, + 'match=domain' => *list_match_domain, + 404 => *list_404, +); + +sub new { + my ($class, $www) = @_; + my $k = 'publicinbox.wwwListing'; + my $pi_config = $www->{pi_config}; + my $v = $pi_config->{lc($k)} // 404; + bless { + pi_config => $pi_config, + style => $www->style("\0"), + list_cb => $VALID{$v} || do { + warn <<""; +`$v' is not a valid value for `$k' +$k be one of `all', `match=domain', or `404' + + *list_404; + }, + }, $class; +} + +sub ibx_entry { + my ($mtime, $ibx, $env) = @_; + my $ts = PublicInbox::View::fmt_ts($mtime); + my $url = PublicInbox::Hval::prurl($env, $ibx->{url}); + my $tmp = <<""; +* $ts - $url + ${\$ibx->description} + + if (defined(my $info_url = $ibx->{info_url})) { + $tmp .= "\n$info_url"; + } + $tmp; +} + +# not really a stand-alone PSGI app, but maybe it could be... +sub call { + my ($self, $env) = @_; + my $h = [ 'Content-Type', 'text/html; charset=UTF-8' ]; + my $list = $self->{list_cb}->($self, $env); + my $code = 404; + my $title = 'public-inbox'; + my $out = ''; + if (@$list) { + # Swartzian transform since ->modified is expensive + @$list = sort { + $b->[0] <=> $a->[0] + } map { [ $_->modified, $_ ] } @$list; + + $code = 200; + $title .= ' - listing'; + my $tmp = join("\n", map { ibx_entry(@$_, $env) } @$list); + my $l = PublicInbox::Linkify->new; + $l->linkify_1($tmp); + $out = '
'.$l->linkify_2(ascii_html($tmp)).'

'; + } + $out = "$title" . $out; + $out .= '
'. PublicInbox::WwwStream::code_footer($env) .
+		'
'; + [ $code, $h, [ $out ] ] +} + +1; diff --git a/lib/PublicInbox/WwwStream.pm b/lib/PublicInbox/WwwStream.pm index 8b79923b64efeab0946e18b1b3bf03094dd6a5fc..c708c21f21741b43f04b51caab59e6d63f6cd966 100644 --- a/lib/PublicInbox/WwwStream.pm +++ b/lib/PublicInbox/WwwStream.pm @@ -71,6 +71,12 @@ $ctx->{www}->style($upfx) . "". $top . $tip; } +sub code_footer ($) { + my ($env) = @_; + my $u = PublicInbox::Hval::prurl($env, $CODE_URL); + qq(AGPL code for this site: git clone $u $PROJECT) +} + sub _html_end { my ($self) = @_; my $urls = 'Archives are clonable:'; @@ -139,12 +145,10 @@ $urls .= "\n or Tor2web: "; $urls .= qq[$TOR2WEB_URL]; } } - my $url = PublicInbox::Hval::prurl($ctx->{env}, $CODE_URL); '
'.join("\n\n",
 		$desc,
 		$urls,
-		'AGPL code for this site: '.
-		qq(git clone $url $PROJECT)
+		code_footer($ctx->{env})
 	).'
'; }