X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FView.pm;h=e7e387d3069f0f5f04daf1873b6bd09ec40fa7c0;hb=4af9fd9c5e46eed341a535f37d54cf228303326c;hp=2513cd0b912326cc5a2d28d1e7cdc8697cbf3dfd;hpb=803af0b41639ce7f4976c7dca77367485473bb30;p=public-inbox.git diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 2513cd0b..e7e387d3 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -8,69 +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 }; -my $enc_utf8 = find_encoding('UTF-8'); - -# 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 { @@ -92,9 +85,7 @@ 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 $enc = enc_for($hdr->header("Content-Type")); my $subj = $hdr->header('Subject'); my $mid_raw = mid_clean(mid_mime($mime)); @@ -103,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 ? '../../' : ''; @@ -128,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], $enc, \$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) { @@ -198,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: @@ -217,8 +202,8 @@ sub emit_thread_html { } sub index_walk { - my ($fh, $part, $enc, $part_nr) = @_; - my $s = add_text_body($enc, $part, $part_nr); + my ($fh, $upfx, $p) = @_; + my $s = add_text_body($upfx, $p); return if $s eq ''; @@ -227,49 +212,22 @@ sub index_walk { $fh->write($s); } -sub enc_for { - my ($ct, $default) = @_; - $default ||= $enc_utf8; - defined $ct or return $default; - my $ct_parsed = parse_content_type($ct); - if ($ct_parsed) { - if (my $charset = $ct_parsed->{attributes}->{charset}) { - my $enc = find_encoding($charset); - return $enc if $enc; - } - } - $default; -} - sub multipart_text_as_html { - my ($mime) = @_; + my ($mime, $upfx) = @_; my $rv = ""; - my $part_nr = 0; - my $enc = enc_for($mime->header("Content-Type")); # scan through all parts, looking for displayable text - $mime->walk_parts(sub { - my ($part) = @_; - $part = add_text_body($enc, $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 ($enc, $fn) = @_; - my $len = 72; - my $pad = "-"; - $fn = $enc->decode($fn); - $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) @@ -282,35 +240,59 @@ sub flush_quote { $$s .= qq() . $rv . '' } -sub add_text_body { - my ($enc_msg, $part, $part_nr) = @_; - return '' if $part->subparts; +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 ($upfx, $p) = @_; # from msg_iter: [ Email::MIME, depth, @idx ] + my ($part, $depth, @idx) = @$p; my $ct = $part->content_type; - # account for filter bugs... + my $fn = $part->filename; + if (defined $ct && $ct =~ m!\btext/x?html\b!i) { - $part->body_set(''); - return ''; + return attach_link($upfx, $ct, $p, $fn); } - my $enc = enc_for($ct, $enc_msg); - my $s = $part->body; - $part->body_set(''); - $s = $enc->decode($s); + + my $s = eval { $part->body_str }; + + # badly-encoded message? tell the world about it! + 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($enc, $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); @@ -321,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); @@ -351,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) { @@ -364,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 { @@ -461,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') || ''; @@ -519,8 +490,9 @@ sub html_footer { } else { $irt = ''; } - - $irt . qq(reply) . $idx; + $rhref ||= '#R'; + $irt .= qq(reply); + $irt .= $idx; } sub linkify_ref_nosrch { @@ -852,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;