]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/View.pm
HTML: ensure hrefs are quoted properly
[public-inbox.git] / lib / PublicInbox / View.pm
1 # Copyright (C) 2014, Eric Wong <normalperson@yhbt.net> and all contributors
2 # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
3 package PublicInbox::View;
4 use strict;
5 use warnings;
6 use URI::Escape qw/uri_escape/;
7 use CGI qw/escapeHTML/;
8 use Encode qw/decode encode/;
9 use Encode::MIME::Header;
10
11 # public functions:
12 sub as_html {
13         my ($class, $mime, $full_pfx) = @_;
14
15         headers_to_html_header($mime) .
16                 multipart_text_as_html($mime, $full_pfx) .
17                 '</pre>';
18 }
19
20 sub as_feed_entry {
21         my ($class, $mime, $full_pfx) = @_;
22
23         "<pre>" . multipart_text_as_html($mime, $full_pfx) . "</pre>";
24 }
25
26
27 # only private functions below.
28
29 sub multipart_text_as_html {
30         my ($mime, $full_pfx) = @_;
31         my $rv = "";
32         my $part_nr = 0;
33
34         # scan through all parts, looking for displayable text
35         $mime->walk_parts(sub {
36                 my ($part) = @_;
37                 return if $part->subparts; # walk_parts already recurses
38
39                 my $fn = $part->filename;
40
41                 if ($part_nr > 0) {
42                         defined($fn) or $fn = "part #" . ($part_nr + 1);
43                         $rv .= add_filename_line($fn);
44                 }
45
46                 if (defined $full_pfx) {
47                         $rv .= add_text_body_short($part, $part_nr,
48                                                 $full_pfx);
49                 } else {
50                         $rv .= add_text_body_full($part, $part_nr);
51                 }
52                 $rv .= "\n" unless $rv =~ /\n\z/s;
53                 ++$part_nr;
54         });
55         $rv;
56 }
57
58 sub add_filename_line {
59         my ($fn) = @_;
60         my $len = 72;
61         my $pad = "-";
62
63         $len -= length($fn);
64         $pad x= ($len/2) if ($len > 0);
65         "$pad " . escapeHTML($fn) . " $pad\n";
66 }
67
68 sub add_text_body_short {
69         my ($part, $part_nr, $full_pfx) = @_;
70         my $n = 0;
71         my $s = escapeHTML($part->body);
72         $s =~ s!^((?:(?:&gt;[^\n]+)\n)+)!
73                 my $cur = $1;
74                 my @lines = split(/\n/, $cur);
75                 if (@lines > 1) {
76                         # show a short snippet of quoted text
77                         $cur = join(' ', @lines);
78                         $cur =~ s/&gt; ?//g;
79
80                         my @sum = split(/\s+/, $cur);
81                         $cur = '';
82                         do {
83                                 $cur .= shift(@sum) . ' ';
84                         } while (@sum && length($cur) < 68);
85                         $cur=~ s/ \z/ .../;
86                         "&gt; &lt;<a href=\"${full_pfx}#q${part_nr}_" . $n++ .
87                                 "\">$cur<\/a>&gt;";
88                 } else {
89                         $cur;
90                 }
91         !emg;
92         $s;
93 }
94
95 sub add_text_body_full {
96         my ($part, $part_nr) = @_;
97         my $n = 0;
98         my $s = escapeHTML($part->body);
99         $s =~ s!^((?:(?:&gt;[^\n]+)\n)+)!
100                 my $cur = $1;
101                 my @lines = split(/\n/, $cur);
102                 if (@lines > 1) {
103                         "<a name=q${part_nr}_" . $n++ . ">$cur</a>";
104                 } else {
105                         $cur;
106                 }
107         !emg;
108         $s;
109 }
110
111 sub trim_message_id {
112         my ($mid) = @_;
113         $mid =~ s/\A<//;
114         $mid =~ s/>\z//;
115         my $html = escapeHTML($mid);
116         my $href = escapeHTML(uri_escape($mid));
117
118         ($html, $href);
119 }
120
121 sub headers_to_html_header {
122         my ($simple) = @_;
123
124         my $rv = "";
125         my @title;
126         foreach my $h (qw(From To Cc Subject Date)) {
127                 my $v = $simple->header($h);
128                 defined $v or next;
129                 $v = decode("MIME-Header", $v);
130                 $v = encode("utf8", $v);
131                 $v = escapeHTML($v);
132                 $v =~ tr/\n/ /;
133                 $rv .= "$h: $v\n";
134
135                 if ($h eq "From" || $h eq "Subject") {
136                         push @title, $v;
137                 }
138         }
139
140         my $mid = $simple->header('Message-ID');
141         if (defined $mid) {
142                 my ($html, $href) = trim_message_id($mid);
143                 $rv .= "Message-ID: &lt;<a href=\"$href.html\">$html</a>&gt; ";
144                 $rv .= "(<a href=\"$href.txt\">original</a>)\n";
145         }
146
147         my $irp = $simple->header('In-Reply-To');
148         if (defined $irp) {
149                 my ($html, $href) = trim_message_id($irp);
150                 $rv .= "In-Reply-To: &lt;";
151                 $rv .= "<a href=\"$href.html\">$html</a>&gt;\n";
152         }
153         $rv .= "\n";
154
155         ("<html><head><title>".  join(' - ', @title) .
156          '</title></head><body><pre style="white-space:pre-wrap">' .  $rv);
157 }
158
159 1;