X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FWWW.pm;h=30a7a436df3d12ecce2bb96c980b058e38132975;hb=1761fba7befab2681276ac8f123593610ad27e58;hp=6d9550b49cbc3db63f5dcb39c14e2f02e0c4c261;hpb=a6134a0ca3ac69f2dfe353019c35eb36db3d831e;p=public-inbox.git diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm index 6d9550b4..30a7a436 100644 --- a/lib/PublicInbox/WWW.pm +++ b/lib/PublicInbox/WWW.pm @@ -2,7 +2,7 @@ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt) # # We focus on the lowest common denominators here: -# - targeted at text-only console browsers (lynx, w3m, etc..) +# - targeted at text-only console browsers (w3m, links, etc..) # - Only basic HTML, CSS only for line-wrapping
text content for GUIs # - No JavaScript, graphics or icons allowed. # - Must not rely on static content @@ -13,6 +13,8 @@ use strict; use warnings; use PublicInbox::Config; use URI::Escape qw(uri_escape_utf8 uri_unescape); +use constant SSOMA_URL => 'http://ssoma.public-inbox.org/'; +use constant PI_URL => 'http://public-inbox.org/'; our $LISTNAME_RE = qr!\A/([\w\.\-]+)!; our $pi_config; BEGIN { @@ -33,9 +35,9 @@ sub run { } elsif ($path_info =~ m!$LISTNAME_RE\z!o) { invalid_list(\%ctx, $1) || redirect_list_index(\%ctx, $cgi); } elsif ($path_info =~ m!$LISTNAME_RE(?:/|/index\.html)?\z!o) { - invalid_list(\%ctx, $1) || get_index(\%ctx, $cgi, 0); + invalid_list(\%ctx, $1) || get_index(\%ctx, $cgi); } elsif ($path_info =~ m!$LISTNAME_RE/atom\.xml\z!o) { - invalid_list(\%ctx, $1) || get_atom(\%ctx, $cgi, 0); + invalid_list(\%ctx, $1) || get_atom(\%ctx, $cgi); # single-message pages } elsif ($path_info =~ m!$LISTNAME_RE/m/(\S+)\.txt\z!o) { @@ -47,9 +49,23 @@ sub run { } elsif ($path_info =~ m!$LISTNAME_RE/f/(\S+)\.html\z!o) { invalid_list_mid(\%ctx, $1, $2) || get_full_html(\%ctx, $cgi); + # thread display + } elsif ($path_info =~ m!$LISTNAME_RE/t/(\S+)\.html\z!o) { + invalid_list_mid(\%ctx, $1, $2) || get_thread(\%ctx, $cgi); + + } elsif ($path_info =~ m!$LISTNAME_RE/t/(\S+)\.mbox\.gz!o) { + my $sfx = $3; + invalid_list_mid(\%ctx, $1, $2) || get_thread_mbox(\%ctx, $cgi); + + } elsif ($path_info =~ m!$LISTNAME_RE/f/\S+\.txt\z!o) { + invalid_list_mid(\%ctx, $1, $2) || + redirect_mid_txt(\%ctx, $cgi); + # convenience redirects, order matters - } elsif ($path_info =~ m!$LISTNAME_RE/(?:m|f)/(\S+)\z!o) { - invalid_list_mid(\%ctx, $1, $2) || redirect_mid(\%ctx, $cgi); + } elsif ($path_info =~ m!$LISTNAME_RE/(m|f|t|s)/(\S+)\z!o) { + my $pfx = $2; + invalid_list_mid(\%ctx, $1, $3) || + redirect_mid(\%ctx, $cgi, $2); } else { r404(); @@ -60,11 +76,17 @@ sub run { sub preload { require PublicInbox::Feed; require PublicInbox::View; - require Mail::Thread; + require PublicInbox::Thread; + require PublicInbox::GitCatFile; require Email::MIME; require Digest::SHA; require POSIX; - require XML::Atom::SimpleFeed; + + eval { + require PublicInbox::Search; + require PublicInbox::Mbox; + require IO::Compress::Gzip; + }; } # private functions below @@ -96,44 +118,31 @@ sub invalid_list_mid { # /$LISTNAME/atom.xml -> Atom feed, includes replies sub get_atom { - my ($ctx, $cgi, $top) = @_; + my ($ctx, $cgi) = @_; + $ctx->{pi_config} = $pi_config; + $ctx->{cgi} = $cgi; require PublicInbox::Feed; - [ 200, [ 'Content-Type' => 'application/xml' ], - [ PublicInbox::Feed->generate({ - git_dir => $ctx->{git_dir}, - listname => $ctx->{listname}, - pi_config => $pi_config, - cgi => $cgi, - top => $top, - }) ] - ]; + PublicInbox::Feed::generate($ctx); } # /$LISTNAME/?r=$GIT_COMMIT -> HTML only sub get_index { - my ($ctx, $cgi, $top) = @_; + my ($ctx, $cgi) = @_; require PublicInbox::Feed; - [ 200, [ 'Content-Type' => 'text/html; charset=UTF-8' ], - [ PublicInbox::Feed->generate_html_index({ - git_dir => $ctx->{git_dir}, - listname => $ctx->{listname}, - pi_config => $pi_config, - cgi => $cgi, - top => $top, - }) ] - ]; + my $srch = searcher($ctx); + $ctx->{pi_config} = $pi_config; + $ctx->{cgi} = $cgi; + footer($ctx); + PublicInbox::Feed::generate_html_index($ctx); } # just returns a string ref for the blob in the current ctx sub mid2blob { my ($ctx) = @_; - require Digest::SHA; - my $hex = Digest::SHA::sha1_hex($ctx->{mid}); - $hex =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/i or - die "BUG: not a SHA-1 hex: $hex"; - + require PublicInbox::MID; + my $path = PublicInbox::MID::mid2path($ctx->{mid}); my @cmd = ('git', "--git-dir=$ctx->{git_dir}", - qw(cat-file blob), "HEAD:$1/$2"); + qw(cat-file blob), "HEAD:$path"); my $cmd = join(' ', @cmd); my $pid = open my $fh, '-|'; defined $pid or die "fork failed: $!\n"; @@ -161,12 +170,13 @@ sub get_mid_html { return r404() unless $x; require PublicInbox::View; - my $mid_href = PublicInbox::Hval::ascii_html( - uri_escape_utf8($ctx->{mid})); - my $pfx = "../f/$mid_href.html"; + my $pfx = msg_pfx($ctx); + my $foot = footer($ctx); require Email::MIME; + my $mime = Email::MIME->new($x); + my $srch = searcher($ctx); [ 200, [ 'Content-Type' => 'text/html; charset=UTF-8' ], - [ PublicInbox::View->as_html(Email::MIME->new($$x), $pfx) ] ]; + [ PublicInbox::View->msg_html($mime, $pfx, $foot, $srch) ] ]; } # /$LISTNAME/f/$MESSAGE_ID.html -> HTML content (fullquotes) @@ -175,9 +185,21 @@ sub get_full_html { my $x = mid2blob($ctx); return r404() unless $x; require PublicInbox::View; + my $foot = footer($ctx); require Email::MIME; - [ 200, [ 'Content-Type' => 'text/html' ], - [ PublicInbox::View->as_html(Email::MIME->new($$x))] ]; + my $mime = Email::MIME->new($x); + my $srch = searcher($ctx); + [ 200, [ 'Content-Type' => 'text/html; charset=UTF-8' ], + [ PublicInbox::View->msg_html($mime, undef, $foot, $srch)] ]; +} + +# /$LISTNAME/t/$MESSAGE_ID.html +sub get_thread { + my ($ctx, $cgi) = @_; + my $srch = searcher($ctx) or return need_search($ctx); + require PublicInbox::View; + my $foot = footer($ctx); + PublicInbox::View::thread_html($ctx, $foot, $srch); } sub self_url { @@ -191,10 +213,22 @@ sub redirect_list_index { } sub redirect_mid { - my ($ctx, $cgi) = @_; + my ($ctx, $cgi, $pfx) = @_; + my $url = self_url($cgi); + my $anchor = ''; + if (lc($pfx) eq 't') { + $anchor = '#u'; # is used to highlight in View.pm + } + do_redirect($url . ".html$anchor"); +} + +# only hit when somebody tries to guess URLs manually: +sub redirect_mid_txt { + my ($ctx, $cgi, $pfx) = @_; + my $listname = $ctx->{listname}; my $url = self_url($cgi); - $url =~ s!/f/!/m/!; - do_redirect($url . '.html'); + $url =~ s!/$listname/f/(\S+\.txt)\z!/$listname/m/$1!; + do_redirect($url); } sub do_redirect { @@ -205,4 +239,107 @@ sub do_redirect { ] } +sub ctx_get { + my ($ctx, $key) = @_; + my $val = $ctx->{$key}; + (defined $val && length $val) or die "BUG: bad ctx, $key unusable\n"; + $val; +} + +sub try_cat { + my ($path) = @_; + my $rv; + if (open(my $fh, '<', $path)) { + local $/; + $rv = <$fh>; + close $fh; + } + $rv; +} + +sub footer { + my ($ctx) = @_; + return '' unless $ctx; + my $git_dir = ctx_get($ctx, 'git_dir'); + + # favor user-supplied footer + my $footer = try_cat("$git_dir/public-inbox/footer.html"); + if (defined $footer) { + chomp $footer; + $ctx->{footer} = $footer; + return $footer; + } + + # auto-generate a footer + my $listname = ctx_get($ctx, 'listname'); + my $desc = try_cat("$git_dir/description"); + $desc = '$GIT_DIR/description missing' unless defined $desc; + chomp $desc; + + my $urls = try_cat("$git_dir/cloneurl"); + my @urls = split(/\r?\n/, $urls || ''); + my $nurls = scalar @urls; + if ($nurls == 0) { + $urls = '($GIT_DIR/cloneurl missing)'; + } elsif ($nurls == 1) { + $urls = "git URL for ssoma: ' . $urls[0]; + } else { + $urls = "git URLs for ssoma:\n" . join("\n", map { "\t$_" } @urls); + } + + my $addr = $pi_config->get($listname, 'address'); + if (ref($addr) eq 'ARRAY') { + $addr = $addr->[0]; # first address is primary + } + + $addr = "$addr"; + + $ctx->{footer} = join("\n", + '- ' . $desc, + "A public-inbox, ' . + 'anybody may post in plain-text (not HTML):', + $addr, + $urls + ); +} + +# search support is optional, returns undef if Xapian is not installed +# or not configured for the given GIT_DIR +sub searcher { + my ($ctx) = @_; + eval { + require PublicInbox::Search; + $ctx->{srch} = PublicInbox::Search->new($ctx->{git_dir}); + }; +} + +sub need_search { + my ($ctx) = @_; + my $msg = <Search not available for this +public-inbox Search is not available for this public-inbox +Return to index+EOF + [ 501, [ 'Content-Type' => 'text/html; charset=UTF-8' ], [ $msg ] ]; +} + +sub msg_pfx { + my ($ctx) = @_; + my $href = PublicInbox::Hval::ascii_html(uri_escape_utf8($ctx->{mid})); + "../f/$href.html"; +} + +# /$LISTNAME/t/$MESSAGE_ID.mbox.gz -> search results as gzipped mbox +# note: I'm not a big fan of other compression formats since they're +# significantly more expensive on CPU than gzip and less-widely available, +# especially on older systems. Stick to zlib since that's what git uses. +sub get_thread_mbox { + my ($ctx, $cgi) = @_; + my $srch = searcher($ctx) or return need_search($ctx); + require PublicInbox::Mbox; + PublicInbox::Mbox::thread_mbox($ctx, $srch); +} + 1;