1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
7 use PublicInbox::MID qw(mid2path);
9 use PublicInbox::InboxWritable;
10 use PublicInbox::TestCommon;
11 use PublicInbox::Import;
12 my ($tmpdir, $for_destroy) = tmpdir();
13 my $home = "$tmpdir/pi-home";
14 my $pi_home = "$home/.public-inbox";
15 my $pi_config = "$pi_home/config";
16 my $maindir = "$tmpdir/main.git";
17 my $main_bin = getcwd()."/t/main-bin";
18 my $main_path = "$main_bin:$ENV{PATH}"; # for spamc ham mock
19 my $fail_bin = getcwd()."/t/fail-bin";
20 my $fail_path = "$fail_bin:$ENV{PATH}"; # for spamc spam mock
21 my $addr = 'test-public@example.com';
22 my $cfgpfx = "publicinbox.test";
23 my $faildir = "$home/faildir/";
24 my $git = PublicInbox::Git->new($maindir);
26 my $fail_bad_header = sub ($$$) {
27 my ($good_rev, $msg, $in) = @_;
28 my @f = glob("$faildir/*/*");
30 my ($out, $err) = ("", "");
31 my $opt = { 0 => \$in, 1 => \$out, 2 => \$err };
32 local $ENV{PATH} = $main_path;
33 ok(run_script(['-mda'], undef, $opt),
34 "no error on undeliverable ($msg)");
35 my $rev = $git->qx(qw(rev-list HEAD));
37 is($rev, $good_rev, "bad revision not committed ($msg)");
38 @f = glob("$faildir/*/*");
39 is(scalar @f, 1, "faildir written to");
44 ok(-x "$main_bin/spamc",
45 "spamc ham mock found (run in top of source tree");
46 ok(-x "$fail_bin/spamc",
47 "spamc mock found (run in top of source tree");
48 is(1, mkdir($home, 0755), "setup ~/ for testing");
49 is(1, mkdir($pi_home, 0755), "setup ~/.public-inbox");
50 PublicInbox::Import::init_bare($maindir);
52 open my $fh, '>>', $pi_config or die;
53 print $fh <<EOF or die;
61 local $ENV{GIT_COMMITTER_NAME} = eval {
63 use PublicInbox::Address;
64 use Encode qw/encode/;
65 my $msg = eml_load 't/utf8.eml';
66 my $from = $msg->header('From');
67 my ($author) = PublicInbox::Address::names($from);
68 my ($email) = PublicInbox::Address::emails($from);
69 my $date = $msg->header('Date');
72 encode('us-ascii', my $tmp = $author, Encode::HTMLCREF),
73 'HTML conversion is correct');
74 is($email, 'e@example.com', 'email parsed correctly');
75 is($date, 'Thu, 01 Jan 1970 00:00:00 +0000',
76 'message date parsed correctly');
83 local $ENV{PI_EMERGENCY} = $faildir;
84 local $ENV{HOME} = $home;
85 local $ENV{ORIGINAL_RECIPIENT} = $addr;
87 From: Me <me\@example.com>
88 To: You <you\@example.com>
90 Message-Id: <blah\@example.com>
92 Date: Thu, 01 Jan 1970 00:00:00 +0000
95 # ensure successful message delivery
97 local $ENV{PATH} = $main_path;
98 ok(run_script(['-mda'], undef, { 0 => \$in }));
99 my $rev = $git->qx(qw(rev-list HEAD));
100 like($rev, qr/\A[a-f0-9]{40,64}/, "good revision committed");
102 my $cmt = $git->cat_file($rev);
103 like($$cmt, qr/^author Me <me\@example\.com> 0 \+0000\n/m,
104 "author info set correctly");
105 like($$cmt, qr/^committer test <test-public\@example\.com>/m,
106 "committer info set correctly");
110 # ensure failures work, fail with bad spamc
112 my @prev = <$faildir/new/*>;
113 is(scalar @prev, 0 , "nothing in PI_EMERGENCY before");
114 local $ENV{PATH} = $fail_path;
115 ok(run_script(['-mda'], undef, { 0 => \$in }));
116 my @revs = $git->qx(qw(rev-list HEAD));
117 is(scalar @revs, 1, "bad revision not committed");
118 my @new = <$faildir/new/*>;
119 is(scalar @new, 1, "PI_EMERGENCY is written to");
122 $fail_bad_header->($good_rev, "bad recipient", <<"");
123 From: Me <me\@example.com>
124 To: You <you\@example.com>
125 Message-Id: <bad-recipient\@example.com>
127 Date: Thu, 01 Jan 1970 00:00:00 +0000
129 my $fail = $fail_bad_header->($good_rev, "duplicate Message-ID", <<"");
130 From: Me <me\@example.com>
131 To: You <you\@example.com>
133 Message-ID: <blah\@example.com>
135 Date: Thu, 01 Jan 1970 00:00:00 +0000
137 like($fail->[2], qr/CONFLICT/, "duplicate Message-ID message");
139 $fail_bad_header->($good_rev, "missing From:", <<"");
141 Message-ID: <missing-from\@example.com>
143 Date: Thu, 01 Jan 1970 00:00:00 +0000
145 $fail_bad_header->($good_rev, "short subject:", <<"");
147 From: cat\@example.com
148 Message-ID: <short-subject\@example.com>
150 Date: Thu, 01 Jan 1970 00:00:00 +0000
152 $fail_bad_header->($good_rev, "no date", <<"");
155 Message-ID: <no-date\@example.com>
158 $fail_bad_header->($good_rev, "bad date", <<"");
161 Message-ID: <bad-date\@example.com>
169 local $ENV{PI_EMERGENCY} = $faildir;
170 local $ENV{HOME} = $home;
171 local $ENV{ORIGINAL_RECIPIENT} = $addr;
172 local $ENV{PATH} = $main_path;
173 my $mid = 'spam-train@example.com';
175 From: Spammer <spammer\@example.com>
176 To: You <you\@example.com>
179 Subject: this message will be trained as spam
180 Date: Thu, 01 Jan 1970 00:00:00 +0000
184 # deliver the spam message, first
185 ok(run_script(['-mda'], undef, { 0 => \$in }));
186 my $path = mid2path($mid);
187 my $msg = $git->cat_file("HEAD:$path");
188 like($$msg, qr/\Q$mid\E/, "message delivered");
191 local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
192 local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
193 local $ENV{GIT_COMMITTER_NAME};
194 delete $ENV{GIT_COMMITTER_NAME};
195 ok(run_script(['-learn', 'spam'], undef, { 0 => $msg }),
196 "no failure from learning spam");
197 ok(run_script(['-learn', 'spam'], undef, { 0 => $msg }),
198 "no failure from learning spam idempotently");
204 local $ENV{PI_EMERGENCY} = $faildir;
205 local $ENV{HOME} = $home;
206 local $ENV{ORIGINAL_RECIPIENT} = $addr;
207 local $ENV{PATH} = $main_path;
208 my $mid = 'ham-train@example.com';
210 From: False-positive <hammer\@example.com>
211 To: You <you\@example.com>
214 Subject: this message will be trained as spam
215 Date: Thu, 01 Jan 1970 00:00:00 +0000
219 # these should be overridden
220 local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
221 local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
223 ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
224 "learned ham without failure");
225 my $path = mid2path($mid);
226 my $msg = $git->cat_file("HEAD:$path");
227 like($$msg, qr/\Q$mid\E/, "ham message delivered");
228 ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
229 "learned ham idempotently ");
231 # ensure trained email is filtered, too
232 my $mime = eml_load 't/mda-mime.eml';
233 ($mid) = ($mime->header_raw('message-id') =~ /<([^>]+)>/);
235 $in = $mime->as_string;
236 ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
237 "learned ham without failure");
238 my $path = mid2path($mid);
239 $msg = $git->cat_file("HEAD:$path");
240 like($$msg, qr/<\Q$mid\E>/, "ham message delivered");
241 unlike($$msg, qr/<html>/i, '<html> filtered');
245 # List-ID based delivery
247 local $ENV{PI_EMERGENCY} = $faildir;
248 local $ENV{HOME} = $home;
249 local $ENV{ORIGINAL_RECIPIENT} = undef;
250 delete $ENV{ORIGINAL_RECIPIENT};
251 local $ENV{PATH} = $main_path;
252 my $list_id = 'foo.example.com';
253 my $mid = 'list-id-delivery@example.com';
255 From: user <user\@example.com>
256 To: You <you\@example.com>
260 Subject: this message will be trained as spam
261 Date: Thu, 01 Jan 1970 00:00:00 +0000
264 xsys(qw(git config --file), $pi_config, "$cfgpfx.listid", uc $list_id);
265 $? == 0 or die "failed to set listid $?";
266 ok(run_script(['-mda'], undef, { 0 => \$in }),
267 'mda OK with List-Id match');
268 my $path = mid2path($mid);
269 my $msg = $git->cat_file("HEAD:$path");
270 like($$msg, qr/\Q$list_id\E/, 'delivered message w/ List-ID matches');
272 # try a message w/o precheck
274 To: You <you\@example.com>
277 this message would not be accepted without --no-precheck
279 my ($out, $err) = ('', '');
280 my $rdr = { 0 => \$in, 1 => \$out, 2 => \$err };
281 ok(run_script(['-mda', '--no-precheck'], undef, $rdr),
282 'mda OK with List-Id match and --no-precheck');
283 my $cur = $git->qx(qw(diff HEAD~1..HEAD));
284 like($cur, qr/this message would not be accepted without --no-precheck/,
285 '--no-precheck delivered message anyways');
287 # try a message with multiple List-ID headers
291 Message-ID: <2lids\@example>
292 Subject: two List-IDs
293 From: user <user\@example.com>
295 Date: Fri, 02 Oct 1993 00:00:00 +0000
298 ($out, $err) = ('', '');
299 ok(run_script(['-mda'], undef, $rdr),
300 'mda OK with multiple List-Id matches');
301 $cur = $git->qx(qw(diff HEAD~1..HEAD));
302 like($cur, qr/^\+Message-ID: <2lids\@example>/sm,
303 'multi List-ID match delivered');
304 like($err, qr/multiple List-ID/, 'warned about multiple List-ID');
306 # ensure -learn rm works after inbox address is updated
307 ($out, $err) = ('', '');
308 xsys(qw(git config --file), $pi_config, "$cfgpfx.address",
309 'updated-address@example.com');
310 ok(run_script(['-learn', 'rm'], undef, $rdr), 'rm-ed via -learn');
311 $cur = $git->qx(qw(diff HEAD~1..HEAD));
312 like($cur, qr/^-Message-ID: <2lids\@example>/sm, 'changed in git');