1 # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2 # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
7 use PublicInbox::Filter;
10 my ($bodies, $part) = @_;
11 my $body = $part->body_raw;
14 $bodies->{$body} ||= 0;
18 # plain-text email is passed through unchanged
20 my $s = Email::MIME->create(
22 From => 'a@example.com',
23 To => 'b@example.com',
24 'Content-Type' => 'text/plain',
25 Subject => 'this is a subject',
27 body => "hello world\n",
29 is(1, PublicInbox::Filter->run($s), "run was a success");
32 # convert single-part HTML to plain-text
34 my $s = Email::MIME->create(
36 From => 'a@example.com',
37 To => 'b@example.com',
38 'Content-Type' => 'text/html',
39 Subject => 'HTML only badness',
41 body => "<html><body>bad body</body></html>\n",
43 is(1, PublicInbox::Filter->run($s), "run was a success");
44 unlike($s->as_string, qr/<html>/, "HTML removed");
45 is("text/plain", $s->header("Content-Type"),
46 "content-type changed");
47 like($s->body, qr/\A\s*bad body\s*\z/, "body");
48 like($s->header("X-Content-Filtered-By"),
49 qr/PublicInbox::Filter/, "XCFB header added");
52 # multipart/alternative: HTML and plain-text, keep the plain-text
54 my $html_body = "<html><body>hi</body></html>";
58 content_type => 'text/html; charset=UTF-8',
65 content_type => 'text/plain',
70 my $email = Email::MIME->create(
72 From => 'a@example.com',
74 'Content-Type' => 'multipart/alternative'
78 is(1, PublicInbox::Filter->run($email), "run was a success");
79 my $parsed = Email::MIME->new($email->as_string);
80 is("text/plain", $parsed->header("Content-Type"));
81 is(scalar $parsed->parts, 1, "HTML part removed");
83 $parsed->walk_parts(sub {
85 return if $part->subparts; # walk_parts already recurses
86 count_body_parts(\%bodies, $part);
88 is(scalar keys %bodies, 1, "one bodies");
89 is($bodies{"hi"}, 1, "plain text part unchanged");
92 # multi-part plain-text-only
96 attributes => { content_type => 'text/plain', },
100 attributes => { content_type => 'text/plain', },
104 my $email = Email::MIME->create(
105 header_str => [ From => 'a@example.com', Subject => 'blah' ],
108 is(1, PublicInbox::Filter->run($email), "run was a success");
109 my $parsed = Email::MIME->new($email->as_string);
110 is(scalar $parsed->parts, 2, "still 2 parts");
112 $parsed->walk_parts(sub {
114 return if $part->subparts; # walk_parts already recurses
115 count_body_parts(\%bodies, $part);
117 is(scalar keys %bodies, 2, "two bodies");
118 is($bodies{"bye"}, 1, "bye part exists");
119 is($bodies{"hi"}, 1, "hi part exists");
120 is($parsed->header("X-Content-Filtered-By"), undef,
121 "XCFB header unset");
124 # multi-part HTML, several HTML parts
129 content_type => 'text/html',
130 encoding => 'base64',
132 body => '<html><body>b64 body</body></html>',
136 content_type => 'text/html',
137 encoding => 'quoted-printable',
139 body => '<html><body>qp body</body></html>',
142 my $email = Email::MIME->create(
143 header_str => [ From => 'a@example.com', Subject => 'blah' ],
146 is(1, PublicInbox::Filter->run($email), "run was a success");
147 my $parsed = Email::MIME->new($email->as_string);
148 is(scalar $parsed->parts, 2, "still 2 parts");
150 $parsed->walk_parts(sub {
152 return if $part->subparts; # walk_parts already recurses
153 count_body_parts(\%bodies, $part);
155 is(scalar keys %bodies, 2, "two body parts");
156 is($bodies{"b64 body"}, 1, "base64 part converted");
157 is($bodies{"qp body"}, 1, "qp part converted");
158 like($parsed->header("X-Content-Filtered-By"), qr/PublicInbox::Filter/,
159 "XCFB header added");
162 # plain-text with image attachments, kill images
166 attributes => { content_type => 'text/plain' },
171 content_type => 'image/jpeg',
172 filename => 'scary.jpg',
173 encoding => 'base64',
178 my $email = Email::MIME->create(
179 header_str => [ From => 'a@example.com', Subject => 'blah' ],
182 is(1, PublicInbox::Filter->run($email), "run was a success");
183 my $parsed = Email::MIME->new($email->as_string);
184 is(scalar $parsed->parts, 1, "image part removed");
186 $parsed->walk_parts(sub {
188 return if $part->subparts; # walk_parts already recurses
189 count_body_parts(\%bodies, $part);
191 is(scalar keys %bodies, 1, "one body");
192 is($bodies{'see image'}, 1, 'original body exists');
193 like($parsed->header("X-Content-Filtered-By"), qr/PublicInbox::Filter/,
194 "XCFB header added");
202 content_type => 'image/jpeg',
203 filename => 'scary.jpg',
204 encoding => 'base64',
210 content_type => 'text/plain',
211 filename => 'scary.exe',
212 encoding => 'base64',
217 my $email = Email::MIME->create(
218 header_str => [ From => 'a@example.com', Subject => 'blah' ],
221 is(0, PublicInbox::Filter->run($email),
222 "run signaled to stop delivery");
223 my $parsed = Email::MIME->new($email->as_string);
224 is(scalar $parsed->parts, 1, "bad parts removed");
226 $parsed->walk_parts(sub {
228 return if $part->subparts; # walk_parts already recurses
229 count_body_parts(\%bodies, $part);
231 is(scalar keys %bodies, 1, "one body");
232 is($bodies{"all attachments scrubbed by PublicInbox::Filter"}, 1,
233 "attachment scrubber left its mark");
234 like($parsed->header("X-Content-Filtered-By"), qr/PublicInbox::Filter/,
235 "XCFB header added");
239 my $s = Email::MIME->create(
241 From => 'a@example.com',
242 To => 'b@example.com',
243 'Content-Type' => 'test/pain',
244 Subject => 'this is a subject',
246 body => "hello world\n",
248 is(0, PublicInbox::Filter->run($s), "run was a failure");
249 like($s->as_string, qr/scrubbed/, "scrubbed message");
253 my $s = Email::MIME->create(
255 From => 'a@example.com',
256 To => 'b@example.com',
257 'Content-Type' => 'text/plain',
258 'Mail-Followup-To' => 'c@example.com',
259 Subject => 'mfttest',
264 is('c@example.com', $s->header("Mail-Followup-To"),
265 "mft set correctly");
266 is(1, PublicInbox::Filter->run($s), "run succeeded for mft");
267 is(undef, $s->header("Mail-Followup-To"), "mft stripped");
270 # multi-part with application/octet-stream
272 my $os = 'application/octet-stream';
275 attributes => { content_type => $os },
280 printf("Hello world\\n");
284 /* some folks like ^L */
289 filename => 'zero.data',
290 encoding => 'base64',
293 body => ("\0" x 4096),
296 my $email = Email::MIME->create(
297 header_str => [ From => 'a@example.com', Subject => 'blah' ],
300 is(1, PublicInbox::Filter->run($email), "run was a success");
301 my $parsed = Email::MIME->new($email->as_string);
302 is(scalar $parsed->parts, 1, "only one remaining part");
303 like($parsed->header("X-Content-Filtered-By"),
304 qr/PublicInbox::Filter/, "XCFB header added");