From 91ae332610370d2a5da2cfb0bd1dff09463e5438 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 21 Apr 2021 23:50:52 +0000 Subject: [PATCH] lei: flesh out `forwarded' kw support for Maildir and IMAP Maildir and IMAP can both handle `forwarded'. Ensure we don't lose `forwarded' when reading from stores which do not support it, but ensure we can set it when reading from IMAP and Maildir stores. --- lib/PublicInbox/LeiSearch.pm | 12 ++++++++++-- lib/PublicInbox/LeiToMail.pm | 2 +- lib/PublicInbox/NetReader.pm | 1 + t/lei-q-kw.t | 31 +++++++++++++++++++++++++++++++ xt/net_writer-imap.t | 14 +++++++++++--- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/lib/PublicInbox/LeiSearch.pm b/lib/PublicInbox/LeiSearch.pm index 082176e7..ff615d89 100644 --- a/lib/PublicInbox/LeiSearch.pm +++ b/lib/PublicInbox/LeiSearch.pm @@ -103,8 +103,16 @@ sub kw_changed { my $xoids = xoids_for($self, $eml) // return; $docids //= []; @$docids = sort { $a <=> $b } values %$xoids; - my @cur_kw = msg_keywords($self, $docids->[0]); - join("\0", @$new_kw_sorted) eq join("\0", @cur_kw) ? 0 : 1; + my $cur_kw = msg_keywords($self, $docids->[0]); + + # RFC 5550 sec 5.9 on the $Forwarded keyword states: + # "Once set, the flag SHOULD NOT be cleared" + if (exists($cur_kw->{forwarded}) && + !grep(/\Aforwarded\z/, @$new_kw_sorted)) { + delete $cur_kw->{forwarded}; + } + $cur_kw = join("\0", sort keys %$cur_kw); + join("\0", @$new_kw_sorted) eq $cur_kw ? 0 : 1; } sub all_terms { diff --git a/lib/PublicInbox/LeiToMail.pm b/lib/PublicInbox/LeiToMail.pm index 46a82a4b..0fa0bd9a 100644 --- a/lib/PublicInbox/LeiToMail.pm +++ b/lib/PublicInbox/LeiToMail.pm @@ -38,7 +38,7 @@ sub _mbox_hdr_buf ($$$) { if (my $ent = $kw2status{$k}) { push @{$hdr{$ent->[0]}}, $ent->[1]; } else { # X-Label? - warn "TODO: keyword `$k' not supported for mbox\n"; + warn "# keyword `$k' not supported for mbox\n"; } } # Messages are always 'O' (non-\Recent in IMAP), it saves diff --git a/lib/PublicInbox/NetReader.pm b/lib/PublicInbox/NetReader.pm index 821e5d7f..0ef66fd8 100644 --- a/lib/PublicInbox/NetReader.pm +++ b/lib/PublicInbox/NetReader.pm @@ -8,6 +8,7 @@ use v5.10.1; use parent qw(Exporter PublicInbox::IPC); use PublicInbox::Eml; our %IMAPflags2kw = map {; "\\\u$_" => $_ } qw(seen answered flagged draft); +$IMAPflags2kw{'$Forwarded'} = 'forwarded'; # RFC 5550 our @EXPORT = qw(uri_section imap_uri nntp_uri); diff --git a/t/lei-q-kw.t b/t/lei-q-kw.t index c17411fb..c00a0a43 100644 --- a/t/lei-q-kw.t +++ b/t/lei-q-kw.t @@ -205,5 +205,36 @@ open $fh, '<', \$lei_out or BAIL_OUT $!; PublicInbox::MboxReader->mboxrd($fh, sub { push @another, shift }); is($another[0]->header('Status'), 'RO', 'seen kw set'); +# forwarded +{ + local $ENV{DBG} = 1; + $o = "$ENV{HOME}/forwarded"; + lei_ok(qw(q -o), $o, "m:$m"); + my @p = glob("$o/cur/*"); + scalar(@p) == 1 or xbail('multiple when 1 expected', \@p); + my $passed = $p[0]; + $passed =~ s/,S\z/,PS/ or xbail "failed to replace $passed"; + rename($p[0], $passed) or xbail "rename $!"; + lei_ok(qw(q -o), $o, 'm:bogus', \'clobber maildir'); + is_deeply([glob("$o/cur/*")], [], 'old results clobbered'); + lei_ok(qw(q -o), $o, "m:$m"); + @p = glob("$o/cur/*"); + scalar(@p) == 1 or xbail('multiple when 1 expected', \@p); + like($p[0], qr/,PS/, 'passed (Forwarded) flag kept'); + lei_ok(qw(q -o), "mboxrd:$o.mboxrd", "m:$m"); + open $fh, '<', "$o.mboxrd" or xbail $!; + my @res; + PublicInbox::MboxReader->mboxrd($fh, sub { push @res, shift }); + scalar(@res) == 1 or xbail('multiple when 1 expected', \@res); + is($res[0]->header('Status'), 'RO', 'seen kw set'); + is($res[0]->header('X-Status'), undef, 'no X-Status'); + + lei_ok(qw(q -o), "mboxrd:$o.mboxrd", 'bogus-for-import-before'); + lei_ok(qw(q -o), $o, "m:$m"); + @p = glob("$o/cur/*"); + scalar(@p) == 1 or xbail('multiple when 1 expected', \@p); + like($p[0], qr/,PS/, 'passed (Forwarded) flag still kept'); +} + }); # test_lei done_testing; diff --git a/xt/net_writer-imap.t b/xt/net_writer-imap.t index 11a10e74..007de35e 100644 --- a/xt/net_writer-imap.t +++ b/xt/net_writer-imap.t @@ -173,17 +173,18 @@ test_lei(sub { is_deeply([@$res{qw(m kw)}], ['testmessage@example.com', ['seen']], 'kw set'); + # prepare messages for watch $mic = $nwr->mic_for_folder($folder_uri); - for my $kw (qw(Deleted Seen Answered Draft)) { + for my $kw (qw(Deleted Seen Answered Draft forwarded)) { my $buf = < EOM - $mic->append_string($folder_uri->mailbox, $buf, "\\$kw") + my $f = $kw eq 'forwarded' ? '$Forwarded' : "\\$kw"; + $mic->append_string($folder_uri->mailbox, $buf, $f) or BAIL_OUT "append $kw $@"; } - # $mic->expunge or BAIL_OUT "expunge: $@"; $mic->disconnect; my $inboxdir = "$ENV{HOME}/wtest"; @@ -214,6 +215,13 @@ EOM '-watch ignored \\Deleted'); ok(!defined($mm->num_for('Draft@test.example.com')), '-watch ignored \\Draft'); + ok(defined($mm->num_for('forwarded@test.example.com')), + '-watch takes forwarded message'); + undef $w; # done with watch + lei_ok qw(import), $$folder_uri; + lei_ok qw(q m:forwarded@test.example.com); + is_deeply(json_utf8->decode($lei_out)->[0]->{kw}, ['forwarded'], + 'forwarded kw imported from IMAP'); }); undef $cleanup; # remove temporary folder -- 2.44.0