X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FView.pm;h=354cdd93ef14939b567eef1538a54a76996af8e2;hb=94a70f57bf1b449192c9d68637cf985b8d5cfaca;hp=83b622fbde0cc43dd9f6b83ce26ef7ef5fe090ab;hpb=021b862ac94f1c226f0778a336d4f3d566b86f9b;p=public-inbox.git
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 83b622fb..354cdd93 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -1,119 +1,94 @@
-# Copyright (C) 2014-2019 all contributors
+# Copyright (C) all contributors
# License: AGPL-3.0+
#
# Used for displaying the HTML web interface.
# See Documentation/design_www.txt for this.
package PublicInbox::View;
use strict;
-use warnings;
-use bytes (); # only for bytes::length
+use v5.10.1;
+use List::Util qw(max);
+use Text::Wrap qw(wrap); # stdlib, we need Perl 5.6+ for $huge
use PublicInbox::MsgTime qw(msg_datestamp);
-use PublicInbox::Hval qw(ascii_html obfuscate_addrs prurl);
+use PublicInbox::Hval qw(ascii_html obfuscate_addrs prurl mid_href
+ ts2str fmt_ts);
use PublicInbox::Linkify;
-use PublicInbox::MID qw/id_compress mid_escape mids mids_for_index references/;
+use PublicInbox::MID qw(id_compress mids mids_for_index references
+ $MID_EXTRACT);
use PublicInbox::MsgIter;
use PublicInbox::Address;
-use PublicInbox::WwwStream;
+use PublicInbox::WwwStream qw(html_oneshot);
use PublicInbox::Reply;
use PublicInbox::ViewDiff qw(flush_diff);
+use PublicInbox::Eml;
use POSIX qw(strftime);
use Time::Local qw(timegm);
-use PublicInbox::SearchMsg qw(subject_normalized);
+use PublicInbox::Smsg qw(subject_normalized);
+use PublicInbox::ContentHash qw(content_hash);
use constant COLS => 72;
use constant INDENT => ' ';
use constant TCHILD => '` ';
sub th_pfx ($) { $_[0] == 0 ? '' : TCHILD };
-sub msg_html_i {
- my ($nr, $ctx) = @_;
- my $more = $ctx->{more};
- if ($nr == 1) {
- # $more cannot be true w/o $smsg being defined:
- my $upfx = $more ? '../'.mid_escape($ctx->{smsg}->mid).'/' : '';
- $ctx->{tip} .
- multipart_text_as_html(delete $ctx->{mime}, $upfx,
- $ctx) . ''
- } elsif ($more && @$more) {
- ++$ctx->{end_nr};
- msg_html_more($ctx, $more, $nr);
- } elsif ($nr == $ctx->{end_nr}) {
- # fake an EOF if generating the footer fails;
- # we want to at least show the message if something
- # here crashes:
- eval {
- my $hdr = delete($ctx->{hdr});
- '
' . html_footer($hdr, 1, $ctx) .
- '
' . msg_reply($ctx, $hdr)
- };
- } else {
- undef
+sub msg_page_i {
+ my ($ctx, $eml) = @_;
+ if ($eml) { # called by WwwStream::async_eml or getline
+ my $smsg = $ctx->{smsg};
+ my $over = $ctx->{ibx}->over;
+ $ctx->{smsg} = $over ? $over->next_by_mid(@{$ctx->{next_arg}})
+ : $ctx->gone('over');
+ $ctx->{mhref} = ($ctx->{nr} || $ctx->{smsg}) ?
+ "../${\mid_href($smsg->{mid})}/" : '';
+ my $obuf = _msg_page_prepare_obuf($eml, $ctx);
+ if (length($$obuf)) {
+ multipart_text_as_html($eml, $ctx);
+ $$obuf .= '';
+ }
+ delete $ctx->{obuf};
+ $$obuf .= html_footer($ctx, $ctx->{first_hdr}) if !$ctx->{smsg};
+ $$obuf;
+ } else { # called by WwwStream::async_next or getline
+ $ctx->{smsg}; # may be undef
}
}
-# public functions: (unstable)
-
-sub msg_html {
- my ($ctx, $mime, $more, $smsg) = @_;
- my $ibx = $ctx->{-inbox};
- $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
- my $hdr = $ctx->{hdr} = $mime->header_obj;
- $ctx->{tip} = _msg_html_prepare($hdr, $ctx, $more, 0);
- $ctx->{more} = $more;
- $ctx->{end_nr} = 2;
- $ctx->{smsg} = $smsg;
- $ctx->{mime} = $mime;
- PublicInbox::WwwStream->response($ctx, 200, \&msg_html_i);
+# /$INBOX/$MSGID/ for unindexed v1 inboxes
+sub no_over_html ($) {
+ my ($ctx) = @_;
+ my $bref = $ctx->{ibx}->msg_by_mid($ctx->{mid}) or return; # 404
+ my $eml = PublicInbox::Eml->new($bref);
+ $ctx->{mhref} = '';
+ PublicInbox::WwwStream::init($ctx);
+ my $obuf = _msg_page_prepare_obuf($eml, $ctx);
+ if (length($$obuf)) {
+ multipart_text_as_html($eml, $ctx);
+ $$obuf .= '';
+ }
+ delete $ctx->{obuf};
+ eval { $$obuf .= html_footer($ctx, $eml) };
+ html_oneshot($ctx, 200, $$obuf);
}
+# public functions: (unstable)
+
sub msg_page {
my ($ctx) = @_;
- my $mid = $ctx->{mid};
- my $ibx = $ctx->{-inbox};
- my ($first, $more);
- my $smsg;
- if (my $over = $ibx->over) {
- my ($id, $prev);
- $smsg = $over->next_by_mid($mid, \$id, \$prev);
- $first = $ibx->msg_by_smsg($smsg) if $smsg;
- if ($first) {
- my $next = $over->next_by_mid($mid, \$id, \$prev);
- $more = [ $id, $prev, $next ] if $next;
- }
- return unless $first;
- } else {
- $first = $ibx->msg_by_mid($mid) or return;
- }
- msg_html($ctx, PublicInbox::MIME->new($first), $more, $smsg);
-}
+ my $ibx = $ctx->{ibx};
+ $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
+ my $over = $ibx->over or return no_over_html($ctx);
+ my ($id, $prev);
+ my $next_arg = $ctx->{next_arg} = [ $ctx->{mid}, \$id, \$prev ];
-sub msg_html_more {
- my ($ctx, $more, $nr) = @_;
- my $str = eval {
- my ($id, $prev, $smsg) = @$more;
- my $mid = $ctx->{mid};
- my $ibx = $ctx->{-inbox};
- $smsg = $ibx->smsg_mime($smsg);
- my $next = $ibx->over->next_by_mid($mid, \$id, \$prev);
- @$more = $next ? ($id, $prev, $next) : ();
- if ($smsg) {
- my $upfx = '../' . mid_escape($smsg->mid) . '/';
- my $mime = delete $smsg->{mime};
- _msg_html_prepare($mime->header_obj, $ctx, $more, $nr) .
- multipart_text_as_html($mime, $upfx, $ctx) .
- ''
- } else {
- '';
- }
- };
- if ($@) {
- warn "Error lookup up additional messages: $@\n";
- $str = '
Error looking up additional messages
';
- }
- $str;
+ my $smsg = $ctx->{smsg} = $over->next_by_mid(@$next_arg) or
+ return; # undef == 404
+
+ # allow user to easily browse the range around this message if
+ # they have ->over
+ $ctx->{-t_max} = $smsg->{ts};
+ PublicInbox::WwwStream::aresponse($ctx, 200, \&msg_page_i);
}
# /$INBOX/$MESSAGE_ID/#R
-sub msg_reply {
+sub msg_reply ($$) {
my ($ctx, $hdr) = @_;
my $se_url =
'https://kernel.org/pub/software/scm/git/docs/git-send-email.html';
@@ -121,7 +96,7 @@ sub msg_reply {
'https://en.wikipedia.org/wiki/Posting_style#Interleaved_style';
my $info = '';
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
if (my $url = $ibx->{infourl}) {
$url = prurl($ctx->{env}, $url);
$info = qq(\n List information: $url\n);
@@ -169,6 +144,9 @@ $info
$se_url
$link
+
+ Be sure your reply has a Subject: header at the top and a blank line
+ before the message body.
EOF
}
@@ -181,7 +159,7 @@ sub in_reply_to {
sub fold_addresses ($) {
return $_[0] if length($_[0]) <= COLS;
# try to fold on commas after non-word chars before $lim chars,
- # Try to get the "," preceeded by ">" or ")", but avoid folding
+ # Try to get the "," preceded by ">" or ")", but avoid folding
# on the comma where somebody uses "Lastname, Firstname".
# We also try to keep the last and penultimate addresses in
# the list on the same line if possible, hence the extra \z
@@ -208,17 +186,15 @@ sub nr_to_s ($$$) {
$nr == 1 ? "$nr $singular" : "$nr $plural";
}
-# human-friendly format
-sub fmt_ts ($) { strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
-
+# Displays the text of of the message for /$INBOX/$MSGID/[Tt]/ endpoint
# this is already inside a
-sub index_entry {
- my ($smsg, $ctx, $more) = @_;
- my $subj = $smsg->subject;
- my $mid_raw = $smsg->mid;
+sub eml_entry {
+ my ($ctx, $eml) = @_;
+ my $smsg = delete $ctx->{smsg};
+ my $subj = delete $smsg->{subject};
+ my $mid_raw = $smsg->{mid};
my $id = id_compress($mid_raw, 1);
my $id_m = 'm'.$id;
-
my $root_anchor = $ctx->{root_anchor} || '';
my $irt;
my $obfs_ibx = $ctx->{-obfs_ibx};
@@ -231,21 +207,20 @@ sub index_entry {
$rv .= $subj . "\n";
$rv .= _th_index_lite($mid_raw, \$irt, $id, $ctx);
my @tocc;
- my $ds = $smsg->ds; # for v1 non-Xapian/SQLite users
- # deleting {mime} is critical to memory use,
- # the rest of the fields saves about 400K as we iterate across 1K msgs
- my ($mime) = delete @$smsg{qw(mime ds ts blob subject)};
+ my $ds = delete $smsg->{ds}; # for v1 non-Xapian/SQLite users
- my $hdr = $mime->header_obj;
- my $from = _hdr_names_html($hdr, 'From');
+ # Deleting these fields saves about 400K as we iterate across 1K msgs
+ delete @$smsg{qw(ts blob)};
+
+ my $from = _hdr_names_html($eml, 'From');
obfuscate_addrs($obfs_ibx, $from) if $obfs_ibx;
$rv .= "From: $from @ ".fmt_ts($ds)." UTC";
my $upfx = $ctx->{-upfx};
- my $mhref = $upfx . mid_escape($mid_raw) . '/';
+ my $mhref = $upfx . mid_href($mid_raw) . '/';
$rv .= qq{ (permalink / };
$rv .= qq{raw)\n};
- my $to = fold_addresses(_hdr_names_html($hdr, 'To'));
- my $cc = fold_addresses(_hdr_names_html($hdr, 'Cc'));
+ my $to = fold_addresses(_hdr_names_html($eml, 'To'));
+ my $cc = fold_addresses(_hdr_names_html($eml, 'Cc'));
my ($tlen, $clen) = (length($to), length($cc));
my $to_cc = '';
if (($tlen + $clen) > COLS) {
@@ -264,19 +239,19 @@ sub index_entry {
$rv .= $to_cc;
my $mapping = $ctx->{mapping};
- if (!$mapping && (defined($irt) || defined($irt = in_reply_to($hdr)))) {
- my $mirt = PublicInbox::Hval->new_msgid($irt);
- my $href = $upfx . $mirt->{href}. '/';
- my $html = $mirt->as_html;
+ if (!$mapping && (defined($irt) || defined($irt = in_reply_to($eml)))) {
+ my $href = $upfx . mid_href($irt) . '/';
+ my $html = ascii_html($irt);
$rv .= qq(In-Reply-To: <$html>\n)
}
$rv .= "\n";
# scan through all parts, looking for displayable text
$ctx->{mhref} = $mhref;
- $ctx->{rv} = \$rv;
- msg_iter($mime, \&add_text_body, $ctx, 1);
- delete $ctx->{rv};
+ $ctx->{end_id} = "e$id";
+ $ctx->{obuf} = \$rv;
+ $eml->each_part(\&add_text_body, $ctx, 1);
+ delete $ctx->{obuf};
# add the footer
$rv .= "\n^ ".
@@ -284,6 +259,9 @@ sub index_entry {
" raw" .
" reply";
+ delete($ctx->{-qry}) and
+ $rv .= qq[ related];
+
my $hr;
if (defined(my $pct = $smsg->{pct})) { # used by SearchView.pm
$rv .= "\t[relevance $pct%]";
@@ -291,7 +269,6 @@ sub index_entry {
} elsif ($mapping) {
my $nested = 'nested';
my $flat = 'flat';
- my $end = '';
if ($ctx->{flat}) {
$hr = 1;
$flat = "$flat";
@@ -305,15 +282,23 @@ sub index_entry {
$hr = $ctx->{-hr};
}
- $rv .= $more ? '
' : '
' if $hr;
+ # do we have more messages? start a new
if so
+ $rv .= scalar(@{$ctx->{msgs}}) ? '
' : '
' if $hr;
$rv;
}
sub pad_link ($$;$) {
my ($mid, $level, $s) = @_;
$s ||= '...';
- my $id = id_compress($mid, 1);
- (' 'x19).indent_for($level).th_pfx($level)."($s)\n";
+ my $href = defined($mid) ?
+ ("($s)\n") :
+ "($s)\n";
+ (' 'x19).indent_for($level).th_pfx($level).$href;
+}
+
+sub _skel_hdr {
+ # my ($mapping, $mid) = @_;
+ ($_[0]->{$_[1] // \'bogus'} // [ "(?)\n" ])->[0];
}
sub _th_index_lite {
@@ -329,11 +314,9 @@ sub _th_index_lite {
my $nr_c = scalar @$children;
my $nr_s = 0;
my $siblings;
- if (my $smsg = $node->{smsg}) {
- # delete saves about 200KB on a 1K message thread
- if (my $refs = delete $smsg->{references}) {
- ($$irt) = ($refs =~ m/<([^>]+)>\z/);
- }
+ # delete saves about 200KB on a 1K message thread
+ if (my $refs = delete $node->{references}) {
+ ($$irt) = ($refs =~ m/$MID_EXTRACT\z/o);
}
my $irt_map = $mapping->{$$irt} if defined $$irt;
if (defined $irt_map) {
@@ -342,50 +325,52 @@ sub _th_index_lite {
$rv .= $pad . $irt_map->[0];
if ($idx > 0) {
my $prev = $siblings->[$idx - 1];
- my $pmid = $prev->{id};
+ my $pmid = $prev->{mid};
if ($idx > 2) {
my $s = ($idx - 1). ' preceding siblings ...';
$rv .= pad_link($pmid, $level, $s);
} elsif ($idx == 2) {
- my $ppmid = $siblings->[0]->{id};
- $rv .= $pad . $mapping->{$ppmid}->[0];
+ $rv .= $pad . _skel_hdr($mapping,
+ $siblings->[0] ?
+ $siblings->[0]->{mid} : undef);
}
- $rv .= $pad . $mapping->{$pmid}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $pmid);
}
}
my $s_s = nr_to_s($nr_s, 'sibling', 'siblings');
my $s_c = nr_to_s($nr_c, 'reply', 'replies');
$attr =~ s!\n\z!\n!s;
- $attr =~ s! !!s; # no point in duplicating subject
+ $attr =~ s! (?:" )?!!s; # no point in dup subject
$attr =~ s!]+>([^<]+)!$1!s; # no point linking to self
$rv .= "@ $attr";
if ($nr_c) {
- my $cmid = $children->[0]->{id};
- $rv .= $pad . $mapping->{$cmid}->[0];
+ my $cmid = $children->[0] ? $children->[0]->{mid} : undef;
+ $rv .= $pad . _skel_hdr($mapping, $cmid);
if ($nr_c > 2) {
my $s = ($nr_c - 1). ' more replies';
$rv .= pad_link($cmid, $level + 1, $s);
} elsif (my $cn = $children->[1]) {
- $rv .= $pad . $mapping->{$cn->{id}}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $cn->{mid});
}
}
my $next = $siblings->[$idx+1] if $siblings && $idx >= 0;
if ($next) {
- my $nmid = $next->{id};
- $rv .= $pad . $mapping->{$nmid}->[0];
+ my $nmid = $next->{mid};
+ $rv .= $pad . _skel_hdr($mapping, $nmid);
my $nnext = $nr_s - $idx;
if ($nnext > 2) {
my $s = ($nnext - 1).' subsequent siblings';
$rv .= pad_link($nmid, $level, $s);
} elsif (my $nn = $siblings->[$idx + 2]) {
- $rv .= $pad . $mapping->{$nn->{id}}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $nn->{mid});
}
}
$rv .= $pad ."$s_s, $s_c; $ctx->{s_nr}\n";
}
-sub walk_thread {
+# non-recursive thread walker
+sub walk_thread ($$$) {
my ($rootset, $ctx, $cb) = @_;
my @q = map { (0, $_, -1) } @$rootset;
while (@q) {
@@ -398,78 +383,95 @@ sub walk_thread {
}
}
-sub pre_thread {
+sub pre_thread { # walk_thread callback
my ($ctx, $level, $node, $idx) = @_;
- $ctx->{mapping}->{$node->{id}} = [ '', $node, $idx, $level ];
+ $ctx->{mapping}->{$node->{mid}} = [ '', $node, $idx, $level ];
skel_dump($ctx, $level, $node);
}
-sub thread_index_entry {
- my ($ctx, $level, $smsg) = @_;
- my ($beg, $end) = thread_adj_level($ctx, $level);
- $beg . '