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;