From 23c3f7e59beafc832e39c4d0ee44ea3aaff0e353 Mon Sep 17 00:00:00 2001 From: Bron Gondwana <brong@fastmailteam.com> Date: Fri, 27 Dec 2024 22:51:19 +1100 Subject: [PATCH 1/2] index: parse '$' in vanished as well --- imap/index.c | 51 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/imap/index.c b/imap/index.c index 5516bdbd1f..fa65732019 100644 --- a/imap/index.c +++ b/imap/index.c @@ -426,7 +426,7 @@ EXPORTED int index_expunge(struct index_state *state, const char *sequence, continue; /* no \Deleted flag */ /* if there is a sequence list, check it */ - if (sequence && !seqset_ismember(seq, im->uid)) + if (seq && !seqset_ismember(seq, im->uid)) continue; /* not in the list */ /* load first once we know we have to process this one */ @@ -469,7 +469,7 @@ EXPORTED int index_expunge(struct index_state *state, const char *sequence, mboxevent_extract_record(mboxevent, state->mailbox, &record); } - seqset_free(&seq); + if (seq != state->searchres) seqset_free(&seq); mboxevent_extract_mailbox(mboxevent, state->mailbox); mboxevent_set_access(mboxevent, NULL, NULL, state->userid, mailbox_name(state->mailbox), 1); @@ -877,16 +877,16 @@ EXPORTED void index_select(struct index_state *state, struct index_init *init) } sequence = init->vanished.sequence; - if (sequence) seq = _parse_sequence(state, sequence, 1); + seq = _parse_sequence(state, sequence, 1); for (msgno = 1; msgno <= state->exists; msgno++) { im = &state->map[msgno-1]; - if (sequence && !seqset_ismember(seq, im->uid)) + if (seq && !seqset_ismember(seq, im->uid)) continue; if (im->modseq <= init->vanished.modseq) continue; index_printflags(state, msgno, TELL_UID); } - seqset_free(&seq); + if (seq != state->searchres) seqset_free(&seq); } } @@ -972,7 +972,7 @@ seqset_t *index_vanished(struct index_state *state, const struct index_record *record = msg_record(msg); if (!(record->internal_flags & FLAG_INTERNAL_EXPUNGED)) continue; - if (!params->sequence || seqset_ismember(seq, record->uid)) + if (!seq || seqset_ismember(seq, record->uid)) seqset_add(outlist, record->uid, 1); } mailbox_iter_done(&iter); @@ -1036,7 +1036,7 @@ seqset_t *index_vanished(struct index_state *state, } } - seqset_free(&seq); + if (seq != state->searchres) seqset_free(&seq); return outlist; } @@ -1211,11 +1211,11 @@ EXPORTED int index_fetch(struct index_state *state, r = index_lock(state, readonly); if (r) return r; - if (!strcmp("$", sequence)) { - seq = state->searchres; + seq = _parse_sequence(state, sequence, usinguid); + if (!strcmpsafe("$", sequence)) { usinguid = 1; - if (!seqset_first(state->searchres)) { + if (!seqset_first(seq)) { /* RFC 5182: 2.1 * Note that even if the "$" marker contains the empty list of * messages, it must be treated by all commands accepting message @@ -1227,9 +1227,6 @@ EXPORTED int index_fetch(struct index_state *state, goto done; } } - else { - seq = _parse_sequence(state, sequence, usinguid); - } /* set the \Seen flag if necessary - while we still have the lock */ if (!readonly) { @@ -1365,11 +1362,11 @@ EXPORTED int index_store(struct index_state *state, const char *sequence, mailbox = state->mailbox; + seq = _parse_sequence(state, sequence, storeargs->usinguid); if (!strcmp("$", sequence)) { - seq = state->searchres; storeargs->usinguid = 1; - if (!seqset_first(state->searchres)) { + if (!seqset_first(seq)) { /* RFC 5182: 2.1 * Note that even if the "$" marker contains the empty list of * messages, it must be treated by all commands accepting message @@ -1378,9 +1375,6 @@ EXPORTED int index_store(struct index_state *state, const char *sequence, goto done; } } - else { - seq = _parse_sequence(state, sequence, storeargs->usinguid); - } for (i = 0; i < flags->count ; i++) { r = mailbox_user_flag(mailbox, flags->data[i], &userflag, 1); @@ -1615,7 +1609,7 @@ EXPORTED int index_run_annotator(struct index_state *state, } out: - seqset_free(&seq); + if (seq != state->searchres) seqset_free(&seq); if (msgrec) msgrecord_unref(&msgrec); if (!r) { @@ -3147,7 +3141,9 @@ EXPORTED int index_copy(struct index_state *state, srcmailbox = state->mailbox; - if (!strcmp("$", sequence)) { + seq = _parse_sequence(state, sequence, usinguid); + if (!strcmpsafe("$", sequence)) { + usinguid = 1; if (!seqset_first(state->searchres)) { /* RFC 5182: 2.1 * Note that even if the "$" marker contains the empty list of @@ -3156,12 +3152,6 @@ EXPORTED int index_copy(struct index_state *state, */ return 0; } - - seq = state->searchres; - usinguid = 1; - } - else { - seq = _parse_sequence(state, sequence, usinguid); } for (msgno = 1; msgno <= state->exists; msgno++) { @@ -3369,7 +3359,7 @@ EXPORTED int index_copy_remote(struct index_state *state, const char *sequence, index_appendremote(state, msgno, pout); } - seqset_free(&seq); + if (seq != state->searchres) seqset_free(&seq); return 0; } @@ -8249,6 +8239,13 @@ static seqset_t *_parse_sequence(struct index_state *state, { unsigned maxval; + // handle no sequence + if (!sequence) return NULL; + + // handle saved sequences + if (!strcmpsafe("$", sequence)) + return state->searchres; + /* Per RFC 3501, seq-number ABNF: "*" represents the largest number in use. In the case of message sequence numbers, From f27fa5afe80a764e7ee790396b45f6aacd59e613 Mon Sep 17 00:00:00 2001 From: Bron Gondwana <brong@fastmailteam.com> Date: Fri, 3 Jan 2025 15:06:50 +1100 Subject: [PATCH 2/2] QResync: add a test that saved fetch returns correctly --- cassandane/Cassandane/Cyrus/QResync.pm | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cassandane/Cassandane/Cyrus/QResync.pm b/cassandane/Cassandane/Cyrus/QResync.pm index b87465dfb1..813d11c170 100644 --- a/cassandane/Cassandane/Cyrus/QResync.pm +++ b/cassandane/Cassandane/Cyrus/QResync.pm @@ -104,4 +104,36 @@ sub test_qresync_simple $self->assert_equals("5:10,25:45", $vanished[0][1]); } +sub test_qresync_saved_search +{ + my ($self) = @_; + + xlog $self, "Make some messages"; + my $uid = 1; + my %msgs; + for (1..3) + { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } + + my $talk = $self->{store}->get_client(); + $talk->uid(1); + $talk->enable("qresync"); + $talk->select("INBOX"); + my $since = $talk->get_response_code('highestmodseq'); + for (4..6) + { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } + $talk->store('5', '+flags', '(\\Deleted)'); + $talk->expunge(); + $talk->search('RETURN', ['SAVE'], 'SINCE', '1-Feb-1994'); + my $res = $talk->fetch('$', ['FLAGS'], ['CHANGEDSINCE', $since, 'VANISHED']); + $self->assert_str_equals("4,6", join(',', sort keys %$res)); +} + 1;