1 # Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
2 # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
7 use File::Temp qw/tempdir/;
10 use PublicInbox::MID qw(mid2path);
12 my $mda = "blib/script/public-inbox-mda";
13 my $learn = "blib/script/public-inbox-learn";
14 my $tmpdir = tempdir('pi-mda-XXXXXX', TMPDIR => 1, CLEANUP => 1);
15 my $home = "$tmpdir/pi-home";
16 my $pi_home = "$home/.public-inbox";
17 my $pi_config = "$pi_home/config";
18 my $maindir = "$tmpdir/main.git";
19 my $main_bin = getcwd()."/t/main-bin";
20 my $main_path = "$main_bin:$ENV{PATH}"; # for spamc ham mock
21 my $fail_bin = getcwd()."/t/fail-bin";
22 my $fail_path = "$fail_bin:$ENV{PATH}"; # for spamc spam mock
23 my $addr = 'test-public@example.com';
24 my $cfgpfx = "publicinbox.test";
25 my $failbox = "$home/fail.mbox";
29 ok(-x "$main_bin/spamc",
30 "spamc ham mock found (run in top of source tree");
31 ok(-x "$fail_bin/spamc",
32 "spamc mock found (run in top of source tree");
33 ok(-x $mda, "$mda is executable");
34 is(1, mkdir($home, 0755), "setup ~/ for testing");
35 is(1, mkdir($pi_home, 0755), "setup ~/.public-inbox");
36 is(0, system(qw(git init -q --bare), $maindir), "git init (main)");
39 "$cfgpfx.address" => $addr,
40 "$cfgpfx.mainrepo" => $maindir,
42 while (my ($k,$v) = each %cfg) {
43 is(0, system(qw(git config --file), $pi_config, $k, $v),
48 local $ENV{GIT_COMMITTER_NAME} = eval {
50 use PublicInbox::Address;
51 use Encode qw/encode/;
52 my $mbox = 't/utf8.mbox';
53 open(my $fh, '<', $mbox) or die "failed to open mbox: $mbox\n";
54 my $str = eval { local $/; <$fh> };
56 my $msg = Email::MIME->new($str);
58 my $from = $msg->header('From');
59 my $author = PublicInbox::Address::from_name($from);
60 my ($email) = PublicInbox::Address::emails($from);
61 my $date = $msg->header('Date');
64 encode('us-ascii', my $tmp = $author, Encode::HTMLCREF),
65 'HTML conversion is correct');
66 is($email, 'e@example.com', 'email parsed correctly');
67 is($date, 'Thu, 01 Jan 1970 00:00:00 +0000',
68 'message date parsed correctly');
75 local $ENV{PI_EMERGENCY} = $failbox;
76 local $ENV{HOME} = $home;
77 local $ENV{ORIGINAL_RECIPIENT} = $addr;
78 my $simple = Email::Simple->new(<<EOF);
79 From: Me <me\@example.com>
80 To: You <you\@example.com>
82 Message-Id: <blah\@example.com>
84 Date: Thu, 01 Jan 1970 00:00:00 +0000
87 my $in = $simple->as_string;
89 # ensure successful message delivery
91 local $ENV{PATH} = $main_path;
93 my $rev = `git --git-dir=$maindir rev-list HEAD`;
94 like($rev, qr/\A[a-f0-9]{40}/, "good revision committed");
96 my $cmt = `git --git-dir=$maindir cat-file commit $rev`;
97 like($cmt, qr/^author Me <me\@example\.com> 0 \+0000\n/m,
98 "author info set correctly");
99 like($cmt, qr/^committer test <test-public\@example\.com>/m,
100 "committer info set correctly");
104 # ensure failures work, fail with bad spamc
106 ok(!-e $failbox, "nothing in PI_EMERGENCY before");
107 local $ENV{PATH} = $fail_path;
109 my @revs = `git --git-dir=$maindir rev-list HEAD`;
110 is(scalar @revs, 1, "bad revision not committed");
111 ok(-s $failbox > 0, "PI_EMERGENCY is written to");
114 fail_bad_header($good_rev, "bad recipient", <<"");
115 From: Me <me\@example.com>
116 To: You <you\@example.com>
117 Message-Id: <bad-recipient\@example.com>
119 Date: Thu, 01 Jan 1970 00:00:00 +0000
121 my $fail = fail_bad_header($good_rev, "duplicate Message-ID", <<"");
122 From: Me <me\@example.com>
123 To: You <you\@example.com>
125 Message-ID: <blah\@example.com>
127 Date: Thu, 01 Jan 1970 00:00:00 +0000
129 like($fail->[2], qr/CONFLICT/, "duplicate Message-ID message");
131 fail_bad_header($good_rev, "missing From:", <<"");
133 Message-ID: <missing-from\@example.com>
135 Date: Thu, 01 Jan 1970 00:00:00 +0000
137 fail_bad_header($good_rev, "short subject:", <<"");
139 From: cat\@example.com
140 Message-ID: <short-subject\@example.com>
142 Date: Thu, 01 Jan 1970 00:00:00 +0000
144 fail_bad_header($good_rev, "no date", <<"");
147 Message-ID: <no-date\@example.com>
150 fail_bad_header($good_rev, "bad date", <<"");
153 Message-ID: <bad-date\@example.com>
161 local $ENV{PI_EMERGENCY} = $failbox;
162 local $ENV{HOME} = $home;
163 local $ENV{ORIGINAL_RECIPIENT} = $addr;
164 local $ENV{PATH} = $main_path;
165 my $mid = 'spam-train@example.com';
166 my $simple = Email::Simple->new(<<EOF);
167 From: Spammer <spammer\@example.com>
168 To: You <you\@example.com>
171 Subject: this message will be trained as spam
172 Date: Thu, 01 Jan 1970 00:00:00 +0000
175 my $in = $simple->as_string;
178 # deliver the spam message, first
180 my $path = mid2path($mid);
181 my $msg = `git --git-dir=$maindir cat-file blob HEAD:$path`;
182 like($msg, qr/\Q$mid\E/, "message delivered");
185 local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
186 local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
187 run([$learn, "spam"], \$msg);
188 is($?, 0, "no failure from learning spam");
189 run([$learn, "spam"], \$msg);
190 is($?, 0, "no failure from learning spam idempotently");
196 local $ENV{PI_EMERGENCY} = $failbox;
197 local $ENV{HOME} = $home;
198 local $ENV{ORIGINAL_RECIPIENT} = $addr;
199 local $ENV{PATH} = $main_path;
200 my $mid = 'ham-train@example.com';
201 my $simple = Email::Simple->new(<<EOF);
202 From: False-positive <hammer\@example.com>
203 To: You <you\@example.com>
206 Subject: this message will be trained as spam
207 Date: Thu, 01 Jan 1970 00:00:00 +0000
210 my $in = $simple->as_string;
213 # these should be overridden
214 local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
215 local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
217 run([$learn, "ham"], \$in);
218 is($?, 0, "learned ham without failure");
219 my $path = mid2path($mid);
220 my $msg = `git --git-dir=$maindir cat-file blob HEAD:$path`;
221 like($msg, qr/\Q$mid\E/, "ham message delivered");
222 run([$learn, "ham"], \$in);
223 is($?, 0, "learned ham idempotently ");
225 # ensure trained email is filtered, too
226 my $html_body = "<html><body>hi</body></html>";
230 content_type => 'text/html; charset=UTF-8',
231 encoding => 'base64',
237 content_type => 'text/plain',
238 encoding => 'quoted-printable',
240 body => 'hi = "bye"',
243 $mid = 'multipart-html-sucks@11';
244 $mime = Email::MIME->create(
246 From => 'a@example.com',
249 'Message-ID' => "<$mid>",
250 'Content-Type' => 'multipart/alternative',
256 $in = $mime->as_string;
257 run([$learn, "ham"], \$in);
258 is($?, 0, "learned ham without failure");
259 my $path = mid2path($mid);
260 $msg = `git --git-dir=$maindir cat-file blob HEAD:$path`;
261 like($msg, qr/<\Q$mid\E>/, "ham message delivered");
262 unlike($msg, qr/<html>/i, '<html> filtered');
266 # faildir - emergency destination is maildir
268 my $faildir= "$home/faildir/";
269 local $ENV{PI_EMERGENCY} = $faildir;
270 local $ENV{HOME} = $home;
271 local $ENV{ORIGINAL_RECIPIENT} = $addr;
272 local $ENV{PATH} = $fail_path;
274 From: Faildir <faildir\@example.com>
275 To: You <you\@example.com>
277 Message-ID: <faildir\@example.com>
278 Subject: faildir subject
279 Date: Thu, 01 Jan 1970 00:00:00 +0000
283 ok(-d $faildir, "emergency exists");
284 my @new = glob("$faildir/new/*");
285 is(scalar(@new), 1, "message delivered");
286 is(unlink(@new), 1, "removed emergency message");
288 local $ENV{PATH} = $main_path;
290 From: Faildir <faildir\@example.com>
292 Content-Type: text/html
293 Message-ID: <faildir\@example.com>
294 Subject: faildir subject
295 Date: Thu, 01 Jan 1970 00:00:00 +0000
297 <html><body>bad</body></html>
301 run([$mda], \$in, \$out, \$err);
302 isnt($?, 0, "mda exited with failure");
303 is(length $out, 0, 'nothing in stdout');
304 isnt(length $err, 0, 'error message in stderr');
306 @new = glob("$faildir/new/*");
307 is(scalar(@new), 0, "new message did not show up");
309 # reject multipart again
310 $in = $mime->as_string;
312 run([$mda], \$in, \$out, \$err);
313 isnt($?, 0, "mda exited with failure");
314 is(length $out, 0, 'nothing in stdout');
315 isnt(length $err, 0, 'error message in stderr');
316 @new = glob("$faildir/new/*");
317 is(scalar(@new), 0, "new message did not show up");
322 sub fail_bad_header {
323 my ($good_rev, $msg, $in) = @_;
324 open my $fh, '>', $failbox or die "failed to open $failbox: $!\n";
325 close $fh or die "failed to close $failbox: $!\n";
326 my ($out, $err) = ("", "");
327 local $ENV{PATH} = $main_path;
328 run([$mda], \$in, \$out, \$err);
329 my $rev = `git --git-dir=$maindir rev-list HEAD`;
331 is($rev, $good_rev, "bad revision not commited ($msg)");
332 ok(-s $failbox > 0, "PI_EMERGENCY is written to ($msg)");