diff options
Diffstat (limited to 'lib/PublicInbox/View.pm')
-rw-r--r-- | lib/PublicInbox/View.pm | 138 |
1 files changed, 118 insertions, 20 deletions
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 071a2093..44e1f2a8 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -38,7 +38,7 @@ sub msg_page_i { : $ctx->gone('over'); $ctx->{mhref} = ($ctx->{nr} || $ctx->{smsg}) ? "../${\mid_href($smsg->{mid})}/" : ''; - if (_msg_page_prepare($eml, $ctx)) { + if (_msg_page_prepare($eml, $ctx, $smsg->{ts})) { $eml->each_part(\&add_text_body, $ctx, 1); print { $ctx->{zfh} } '</pre><hr>'; } @@ -80,7 +80,7 @@ sub msg_page { # allow user to easily browse the range around this message if # they have ->over $ctx->{-t_max} = $smsg->{ts}; - $ctx->{-spfx} = '../' if $ibx->{coderepo}; + $ctx->{-spfx} = '../' if $ibx->{-repo_objs}; PublicInbox::WwwStream::aresponse($ctx, \&msg_page_i); } @@ -183,6 +183,59 @@ sub nr_to_s ($$$) { $nr == 1 ? "$nr $singular" : "$nr $plural"; } +sub addr2urlmap ($) { + my ($ctx) = @_; + # cache makes a huge difference with /[tT] and large threads + my $key = PublicInbox::Git::host_prefix_url($ctx->{env}, ''); + my $ent = $ctx->{www}->{pi_cfg}->{-addr2urlmap}->{$key} // do { + my $by_addr = $ctx->{www}->{pi_cfg}->{-by_addr}; + my (%addr2url, $url); + while (my ($addr, $ibx) = each %$by_addr) { + $url = $ibx->base_url // $ibx->base_url($ctx->{env}); + $addr2url{$addr} = ascii_html($url) if defined $url; + } + # don't allow attackers to randomly change Host: headers + # and OOM us if the server handles all hostnames: + my $tmp = $ctx->{www}->{pi_cfg}->{-addr2urlmap}; + my @k = keys %$tmp; # random order + delete @$tmp{@k[0..3]} if scalar(@k) > 7; + my $re = join('|', map { quotemeta } keys %addr2url); + $tmp->{$key} = [ qr/\b($re)\b/i, \%addr2url ]; + }; + @$ent; +} + +sub to_cc_html ($$$$) { + my ($ctx, $eml, $field, $t) = @_; + my @vals = $eml->header($field) or return ('', 0); + my (undef, $addr2url) = addr2urlmap($ctx); + my $pairs = PublicInbox::Address::pairs(join(', ', @vals)); + my ($len, $line_len, $html) = (0, 0, ''); + my ($pair, $url); + my ($cur_ibx, $env) = @$ctx{qw(ibx env)}; + # avoid excessive ascii_html calls (already hot in profiles): + my @html = split /\n/, ascii_html(join("\n", map { + $_->[0] // (split(/\@/, $_->[1]))[0]; # addr user if no name + } @$pairs)); + for my $n (@html) { + $pair = shift @$pairs; + if ($line_len) { # 9 = display width of ",\t": + if ($line_len + length($n) > COLS - 9) { + $html .= ",\n\t"; + $len += $line_len; + $line_len = 0; + } else { + $html .= ', '; + $line_len += 2; + } + } + $line_len += length($n); + $url = $addr2url->{lc($pair->[1] // '')}; + $html .= $url ? qq(<a\nhref="$url$t">$n</a>) : $n; + } + ($html, $len + $line_len); +} + # Displays the text of of the message for /$INBOX/$MSGID/[Tt]/ endpoint # this is already inside a <pre> sub eml_entry { @@ -207,7 +260,8 @@ sub eml_entry { my $ds = delete $smsg->{ds}; # for v1 non-Xapian/SQLite users # Deleting these fields saves about 400K as we iterate across 1K msgs - delete @$smsg{qw(ts blob)}; + my ($t, undef) = delete @$smsg{qw(ts blob)}; + $t = $t ? '?t='.ts2str($t) : ''; my $from = _hdr_names_html($eml, 'From'); obfuscate_addrs($obfs_ibx, $from) if $obfs_ibx; @@ -216,9 +270,8 @@ sub eml_entry { my $mhref = $upfx . mid_href($mid_raw) . '/'; $rv .= qq{ (<a\nhref="$mhref">permalink</a> / }; $rv .= qq{<a\nhref="${mhref}raw">raw</a>)\n}; - 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, $tlen) = to_cc_html($ctx, $eml, 'To', $t); + my ($cc, $clen) = to_cc_html($ctx, $eml, 'Cc', $t); my $to_cc = ''; if (($tlen + $clen) > COLS) { $to_cc .= ' To: '.$to."\n" if $tlen; @@ -443,11 +496,11 @@ sub thread_html { my $ibx = $ctx->{ibx}; my ($nr, $msgs) = $ibx->over->get_thread($mid); return missing_thread($ctx) if $nr == 0; - $ctx->{-spfx} = '../../' if $ibx->{coderepo}; + $ctx->{-spfx} = '../../' if $ibx->{-repo_objs}; # link $INBOX_DIR/description text to "index_topics" view around # the newest message in this thread - my $t = ts2str($ctx->{-t_max} = max(map { delete $_->{ts} } @$msgs)); + my $t = ts2str($ctx->{-t_max} = max(map { $_->{ts} } @$msgs)); my $t_fmt = fmt_ts($ctx->{-t_max}); my $skel = '<hr><pre>'; @@ -613,7 +666,7 @@ sub add_text_body { # callback for each_part } sub _msg_page_prepare { - my ($eml, $ctx) = @_; + my ($eml, $ctx, $ts) = @_; my $have_over = !!$ctx->{ibx}->over; my $mids = mids_for_index($eml); my $nr = $ctx->{nr}++; @@ -623,7 +676,8 @@ sub _msg_page_prepare { return; } $ctx->{-html_tip} = -"<pre>WARNING: multiple messages have this Message-ID\n</pre><pre>"; +qq[<pre>WARNING: multiple messages have this Message-ID (<a +href="d/">diff</a>)</pre><pre>]; } else { $ctx->{first_hdr} = $eml->header_obj; $ctx->{chash} = content_hash($eml) if $ctx->{smsg}; # reused MID @@ -648,6 +702,9 @@ sub _msg_page_prepare { $title[0] = $subj[0] // '(no subject)'; $hbuf .= "Date: $_\n" for $eml->header('Date'); $hbuf = ascii_html($hbuf); + my $t = $ts ? '?t='.ts2str($ts) : ''; + my ($re, $addr2url) = addr2urlmap($ctx); + $hbuf =~ s!$re!qq(<a\nhref=").$addr2url->{lc $1}.qq($t">$1</a>)!sge; $ctx->{-title_html} = ascii_html(join(' - ', @title)); if (my $obfs_ibx = $ctx->{-obfs_ibx}) { obfuscate_addrs($obfs_ibx, $hbuf); @@ -687,7 +744,11 @@ sub _msg_page_prepare { } my @irt = $eml->header_raw('In-Reply-To'); my $refs; - if (!@irt) { + if (@irt) { # ("so-and-so's message of $DATE") added by some MUAs + for (grep(/=\?/, @irt)) { + s/(=\?.*)\z/PublicInbox::Eml::mhdr_decode $1/se; + } + } else { $refs = references($eml); $irt[0] = pop(@$refs) if scalar @$refs; } @@ -778,6 +839,9 @@ href=#t>this message</a>: <input type=submit value=search />\t(<a href=${upfx}_/text/help/#search>help</a>)</pre></form> EOM + # TODO: related codesearch + # my $csrchv = $ctx->{ibx}->{-csrch} // []; + # push @related, '<pre>'.ascii_html(Dumper($csrchv)).'</pre>'; } if ($ctx->{ibx}->over) { my $t = ts2str($ctx->{-t_max}); @@ -881,8 +945,8 @@ sub thread_results { my $tip = splice(@$rootset, $idx, 1); @$rootset = reverse @$rootset; unshift @$rootset, $tip; - $ctx->{sl_note} = strict_loose_note($nr); } + $ctx->{sl_note} = strict_loose_note($nr); } $rootset } @@ -1018,6 +1082,8 @@ sub _skel_ghost { 1; } +# note: we favor Date: here because git-send-email increments it +# to preserve [PATCH $N/$M] ordering in series (it can't control Received:) sub sort_ds { @{$_[0]} = sort { (eval { $a->topmost->{ds} } || 0) <=> @@ -1039,9 +1105,10 @@ sub acc_topic { # walk_thread callback if ($has_blob) { my $subj = subject_normalized($smsg->{subject}); $subj = '(no subject)' if $subj eq ''; + my $ts = $smsg->{ts}; my $ds = $smsg->{ds}; if ($level == 0) { # new, top-level topic - my $topic = [ $ds, 1, { $subj => $mid }, $subj ]; + my $topic = [ $ts, $ds, 1, { $subj => $mid }, $subj ]; $ctx->{-cur_topic} = $topic; push @{$ctx->{order}}, $topic; return 1; @@ -1049,10 +1116,11 @@ sub acc_topic { # walk_thread callback # continue existing topic my $topic = $ctx->{-cur_topic}; # should never be undef - $topic->[0] = $ds if $ds > $topic->[0]; - $topic->[1]++; # bump N+ message counter - my $seen = $topic->[2]; - if (scalar(@$topic) == 3) { # parent was a ghost + $topic->[0] = $ts if $ts > $topic->[0]; + $topic->[1] = $ds if $ds > $topic->[1]; + $topic->[2]++; # bump N+ message counter + my $seen = $topic->[3]; + if (scalar(@$topic) == 4) { # parent was a ghost push @$topic, $subj; } elsif (!defined($seen->{$subj})) { push @$topic, $level, $subj; # @extra messages @@ -1060,7 +1128,7 @@ sub acc_topic { # walk_thread callback $seen->{$subj} = $mid; # latest for subject } else { # ghost message return 1 if $level != 0; # ignore child ghosts - my $topic = $ctx->{-cur_topic} = [ -666, 0, {} ]; + my $topic = $ctx->{-cur_topic} = [ -666, -666, 0, {} ]; push @{$ctx->{order}}, $topic; } 1; @@ -1081,7 +1149,7 @@ sub dump_topics { } # sort by recency, this allows new posts to "bump" old topics... foreach my $topic (sort { $b->[0] <=> $a->[0] } @$order) { - my ($ds, $n, $seen, $top_subj, @extra) = @$topic; + my ($ts, $ds, $n, $seen, $top_subj, @extra) = @$topic; @$topic = (); next unless defined $top_subj; # ghost topic my $mid = delete $seen->{$top_subj}; @@ -1137,7 +1205,11 @@ sub pagination_footer ($$) { $next = $next ? "$next | " : ' | '; $prev .= qq[ | <a\nhref="$latest">latest</a>]; } - ($next || $prev) ? "<hr><pre id=nav>page: $next$prev</pre>" : ''; + my $rv = '<hr><pre id=nav>'; + $rv .= "page: $next$prev\n" if $next || $prev; + $rv .= q{- recent:[<b>subjects (threaded)</b>|<a +href="./topics_new.html">topics (new)</a>|<a +href="./topics_active.html">topics (active)</a>]</pre>}; } sub paginate_recent ($$) { @@ -1225,4 +1297,30 @@ sub ghost_index_entry { . '</pre>' . $end; } +# /$INBOX/$MSGID/d/ endpoint +sub diff_msg { + my ($ctx) = @_; + require PublicInbox::MailDiff; + my $ibx = $ctx->{ibx}; + my $over = $ibx->over or return no_over_html($ctx); + my ($id, $prev); + my $md = bless { ctx => $ctx }, 'PublicInbox::MailDiff'; + my $next_arg = $md->{next_arg} = [ $ctx->{mid}, \$id, \$prev ]; + my $smsg = $md->{smsg} = $over->next_by_mid(@$next_arg) or + return; # undef == 404 + $ctx->{-t_max} = $smsg->{ts}; + $ctx->{-upfx} = '../../'; + $ctx->{-apfx} = '//'; # fail on to_attr() + $ctx->{-linkify} = PublicInbox::Linkify->new; + my $mid = ascii_html($smsg->{mid}); + $ctx->{-title_html} = "diff for duplicates of <$mid>"; + PublicInbox::WwwStream::html_init($ctx); + print { $ctx->{zfh} } '<pre>diff for duplicates of <<a href="../">', + $mid, "</a>>\n\n"; + sub { + $ctx->attach($_[0]->([200, delete $ctx->{-res_hdr}])); + $md->begin_mail_diff; + }; +} + 1; |