X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FView.pm;h=e7e387d3069f0f5f04daf1873b6bd09ec40fa7c0;hb=4af9fd9c5e46eed341a535f37d54cf228303326c;hp=6c283ab084415c52db2cf22acdf6ea804c07e09e;hpb=48b296d68d554499656cf8447f5ca841a8541ee7;p=public-inbox.git diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 6c283ab0..e7e387d3 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -8,67 +8,62 @@ use strict; use warnings; use URI::Escape qw/uri_escape_utf8/; use Date::Parse qw/str2time/; -use Encode qw/find_encoding/; use Encode::MIME::Header; -use Email::MIME::ContentType qw/parse_content_type/; +use Plack::Util; use PublicInbox::Hval qw/ascii_html/; use PublicInbox::Linkify; use PublicInbox::MID qw/mid_clean id_compress mid2path mid_mime/; +use PublicInbox::MsgIter; +use PublicInbox::Address; +use PublicInbox::WwwStream; require POSIX; use constant INDENT => ' '; use constant TCHILD => '` '; sub th_pfx ($) { $_[0] == 0 ? '' : TCHILD }; -# public functions: +# public functions: (unstable) sub msg_html { my ($ctx, $mime, $footer) = @_; - $footer = defined($footer) ? "\n$footer" : ''; my $hdr = $mime->header_obj; - headers_to_html_header($hdr, $ctx) . - multipart_text_as_html($mime) . - '
' .
-		html_footer($hdr, 1, $ctx, 'R/') .
-		$footer .
-		'
'; + my $tip = _msg_html_prepare($hdr, $ctx); + PublicInbox::WwwStream->new($ctx, sub { + my ($nr, undef) = @_; + if ($nr == 1) { + $tip . multipart_text_as_html($mime, '') . + '
' + } elsif ($nr == 2) { + '
' . html_footer($hdr, 1, $ctx) .
+			'
' . msg_reply($ctx, $hdr) . '
' + } else { + undef + } + }); } -# /$INBOX/$MESSAGE_ID/R/ +# /$INBOX/$MESSAGE_ID/#R sub msg_reply { - my ($ctx, $hdr, $footer) = @_; - my $s = $hdr->header('Subject'); - $s = '(no subject)' if (!defined $s) || ($s eq ''); - my $f = $hdr->header('From'); - $f = '' unless defined $f; - my $mid = $hdr->header_raw('Message-ID'); - $mid = PublicInbox::Hval->new_msgid($mid); - my $t = ascii_html($s); + my ($ctx, $hdr) = @_; my $se_url = 'https://kernel.org/pub/software/scm/git/docs/git-send-email.html'; my ($arg, $link) = mailto_arg_link($hdr); push @$arg, '/path/to/YOUR_REPLY'; - "replying to \"$t\"
" .
-	"replying to message:\n\n" .
-	"Subject: $t\n" .
-	"From: ". ascii_html($f) .
-	"\nDate: " .  ascii_html($hdr->header('Date')) .
-	"\nMessage-ID: <" . $mid->as_html . ">\n\n" .
-	"There are multiple ways to reply:\n\n" .
+	"
". + "You may reply publically to this message via\n". + "plain-text email using any one of the following methods:\n\n" . "* Save the following mbox file, import it into your mail client,\n" . - " and reply-to-all from there: mbox\n\n" . + " and reply-to-all from there: mbox\n\n" . "* Reply to all the recipients using the --to, --cc,\n" . " and --in-reply-to switches of git-send-email(1):\n\n" . "\tgit send-email \\\n\t\t" . - join(" \\ \n\t\t", @$arg ). "\n\n" . + join(" \\\n\t\t", @$arg ). "\n\n" . qq( $se_url\n\n) . "* If your mail client supports setting the In-Reply-To" . " header\n via mailto: links, try the " . qq(mailto: link\n) . - "\nFor context, the original message or " . - qq(thread) . - '

' . $footer .  '
'; + ''; } sub in_reply_to { @@ -90,7 +85,6 @@ sub index_entry { my $midx = $state->{anchor_idx}++; my $ctx = $state->{ctx}; my $srch = $ctx->{srch}; - my $part_nr = 0; my $hdr = $mime->header_obj; my $subj = $hdr->header('Subject'); @@ -100,9 +94,7 @@ sub index_entry { $seen->{$id} = "#$id"; # save the anchor for children, later my $mid = PublicInbox::Hval->new_msgid($mid_raw); - my $from = $hdr->header('From'); - my @from = Email::Address->parse($from); - $from = $from[0]->name; + my $from = PublicInbox::Address::from_name($hdr->header('From')); my $root_anchor = $state->{root_anchor} || ''; my $path = $root_anchor ? '../../' : ''; @@ -125,11 +117,8 @@ sub index_entry { my $mhref = "${path}$href/"; # scan through all parts, looking for displayable text - $mime->walk_parts(sub { - index_walk($fh, $_[0], \$part_nr); - }); - $mime->body_set(''); - $rv = "\n" . html_footer($hdr, 0, $ctx, "$path$href/R/"); + msg_iter($mime, sub { index_walk($fh, $mhref, $_[0]) }); + $rv = "\n" . html_footer($hdr, 0, $ctx, "$path$href/#R"); if (defined $irt) { unless (defined $parent_anchor) { @@ -195,7 +184,6 @@ sub emit_thread_html { ('' x ($max - 1)) . ''); } } - Email::Address->purge_cache; # there could be a race due to a message being deleted in git # but still being in the Xapian index: @@ -214,8 +202,8 @@ sub emit_thread_html { } sub index_walk { - my ($fh, $part, $part_nr) = @_; - my $s = add_text_body($part, $part_nr); + my ($fh, $upfx, $p) = @_; + my $s = add_text_body($upfx, $p); return if $s eq ''; @@ -225,32 +213,21 @@ sub index_walk { } sub multipart_text_as_html { - my ($mime) = @_; + my ($mime, $upfx) = @_; my $rv = ""; - my $part_nr = 0; # scan through all parts, looking for displayable text - $mime->walk_parts(sub { - my ($part) = @_; - $part = add_text_body($part, \$part_nr); - $rv .= $part; - $rv .= "\n" if $part ne ''; + msg_iter($mime, sub { + my ($p) = @_; + $p = add_text_body($upfx, $p); + $rv .= $p; + $rv .= "\n" if $p ne ''; }); - $mime->body_set(''); $rv; } -sub add_filename_line { - my ($fn) = @_; - my $len = 72; - my $pad = "-"; - $len -= length($fn); - $pad x= ($len/2) if ($len > 0); - "$pad " . ascii_html($fn) . " $pad\n"; -} - sub flush_quote { - my ($s, $l, $quot, $part_nr) = @_; + my ($s, $l, $quot) = @_; # show everything in the full version with anchor from # short version (see above) @@ -263,41 +240,59 @@ sub flush_quote { $$s .= qq() . $rv . '' } -sub attach ($$) { - my ($ct, $n) = @_; - my $nl = $n ? "\n" : ''; - "$nl[-- Attachment #$n: " . ascii_html($ct) . " --]\n". - "[-- TODO not shown --]"; +sub attach_link ($$$$) { + my ($upfx, $ct, $p, $fn) = @_; + my ($part, $depth, @idx) = @$p; + my $nl = $idx[-1] > 1 ? "\n" : ''; + my $idx = join('.', @idx); + my $size = bytes::length($part->body); + $ct ||= 'text/plain'; + $ct =~ s/;.*//; # no attributes + $ct = ascii_html($ct); + my $desc = $part->header('Content-Description'); + $desc = $fn unless defined $desc; + $desc = '' unless defined $desc; + my $sfn; + if (defined $fn && $fn =~ /\A[[:alnum:]][\w\.-]+[[:alnum:]]\z/) { + $sfn = $fn; + } elsif ($ct eq 'text/plain') { + $sfn = 'a.txt'; + } else { + $sfn = 'a.bin'; + } + my @ret = qq($nl[-- Attachment #$idx: ); + my $ts = "Type: $ct, Size: $size bytes"; + push(@ret, ($desc eq '') ? "$ts --]" : "$desc --]\n[-- $ts --]"); + join('', @ret, ''); } sub add_text_body { - my ($part, $part_nr) = @_; - return '' if $part->subparts; + my ($upfx, $p) = @_; # from msg_iter: [ Email::MIME, depth, @idx ] + my ($part, $depth, @idx) = @$p; my $ct = $part->content_type; + my $fn = $part->filename; if (defined $ct && $ct =~ m!\btext/x?html\b!i) { - return attach($ct, $$part_nr); + return attach_link($upfx, $ct, $p, $fn); } my $s = eval { $part->body_str }; # badly-encoded message? tell the world about it! - return attach($ct, $$part_nr) if $@; + return attach_link($upfx, $ct, $p, $fn) if $@; my @lines = split(/^/m, $s); $s = ''; - if ($$part_nr > 0) { - my $fn = $part->filename; - defined($fn) or $fn = "part #" . ($$part_nr + 1); - $s .= add_filename_line($fn); + if (defined($fn) || $depth > 0) { + $s .= attach_link($upfx, $ct, $p, $fn); + $s .= "\n\n"; } - my @quot; my $l = PublicInbox::Linkify->new; while (defined(my $cur = shift @lines)) { if ($cur !~ /^>/) { # show the previously buffered quote inline - flush_quote(\$s, $l, \@quot, $$part_nr) if @quot; + flush_quote(\$s, $l, \@quot) if @quot; # regular line, OK $cur = $l->linkify_1($cur); @@ -308,27 +303,22 @@ sub add_text_body { } } - flush_quote(\$s, $l, \@quot, $$part_nr) if @quot; - ++$$part_nr; - + flush_quote(\$s, $l, \@quot) if @quot; $s =~ s/[ \t]+$//sgm; # kill per-line trailing whitespace $s =~ s/\A\n+//s; # kill leading blank lines $s =~ s/\s+\z//s; # kill all trailing spaces (final "\n" added if ne '') $s; } -sub headers_to_html_header { +sub _msg_html_prepare { my ($hdr, $ctx) = @_; my $srch = $ctx->{srch} if $ctx; my $atom = ''; - my $rv = ''; - my $upfx = ''; + my $rv = ""; # anchor for body start if ($srch) { - $atom = qq{!; + $ctx->{-upfx} = '../'; } - my @title; my $mid = $hdr->header_raw('Message-ID'); $mid = PublicInbox::Hval->new_msgid($mid); @@ -338,8 +328,8 @@ sub headers_to_html_header { $v = PublicInbox::Hval->new($v); if ($h eq 'From') { - my @from = Email::Address->parse($v->raw); - $title[1] = ascii_html($from[0]->name); + my $n = PublicInbox::Address::from_name($v->raw); + $title[1] = ascii_html($n); } elsif ($h eq 'Subject') { $title[0] = $v->as_html; if ($srch) { @@ -351,15 +341,11 @@ sub headers_to_html_header { $rv .= "$h: " . $v->as_html . "\n"; } + $ctx->{-title_html} = join(' - ', @title); $rv .= 'Message-ID: <' . $mid->as_html . '> '; - $rv .= "(raw)\n"; + $rv .= "(raw)\n"; $rv .= _parent_headers($hdr, $srch); $rv .= "\n"; - - ("". join(' - ', @title) . "$atom". - PublicInbox::Hval::STYLE . - "" . # anchor for body start - $rv); } sub thread_skel { @@ -448,15 +434,13 @@ sub mailto_arg_link { foreach my $h (qw(From To Cc)) { my $v = $hdr->header($h); defined($v) && ($v ne '') or next; - my @addrs = Email::Address->parse($v); - foreach my $recip (@addrs) { - my $address = $recip->address; + my @addrs = PublicInbox::Address::emails($v); + foreach my $address (@addrs) { my $dst = lc($address); $cc{$dst} ||= $address; $to ||= $dst; } } - Email::Address->purge_cache; my @arg; my $subj = $hdr->header('Subject') || ''; @@ -506,8 +490,9 @@ sub html_footer { } else { $irt = ''; } - - $irt . qq(reply) . $idx; + $rhref ||= '#R'; + $irt .= qq(reply); + $irt .= $idx; } sub linkify_ref_nosrch { @@ -839,13 +824,12 @@ sub emit_topics { sub emit_index_topics { my ($state) = @_; - my $off = $state->{ctx}->{cgi}->param('o'); - $off = 0 unless defined $off; + my ($off) = (($state->{ctx}->{cgi}->param('o') || '0') =~ /(\d+)/); $state->{order} = []; $state->{subjs} = {}; $state->{latest} = {}; my $max = 25; - my %opts = ( offset => int $off, limit => $max * 4 ); + my %opts = ( offset => $off, limit => $max * 4 ); while (scalar @{$state->{order}} < $max) { my $sres = $state->{srch}->query('', \%opts); my $nr = scalar @{$sres->{msgs}} or last;