Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type FetchOptions struct {
ModSeq bool // requires CONDSTORE

ChangedSince uint64 // requires CONDSTORE
Vanished bool // requires QRESYNC, only for UID FETCH with ChangedSince
}

// FetchItemBodyStructure contains FETCH options for the body structure.
Expand Down
12 changes: 12 additions & 0 deletions imapclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,11 @@ func (c *Client) readResponseData(typ string) error {
return c.handleFetch(num)
case "EXPUNGE":
return c.handleExpunge(num)
case "VANISHED":
if !c.dec.ExpectSP() {
return c.dec.Err()
}
return c.handleVanished()
case "SEARCH":
return c.handleSearch()
case "ESEARCH":
Expand Down Expand Up @@ -1187,6 +1192,13 @@ type UnilateralDataHandler struct {

// requires ENABLE METADATA or ENABLE SERVER-METADATA
Metadata func(mailbox string, entries []string)

// Called when the server sends an untagged VANISHED response.
//
// Requires QRESYNC extension (RFC 4551/7162). The parameter earlier
// indicates whether this response covers earlier expunges (true for
// SELECT QRESYNC responses, false for UID FETCH VANISHED responses).
Vanished func(uids imap.UIDSet, earlier bool)
}

// command is an interface for IMAP commands.
Expand Down
2 changes: 1 addition & 1 deletion imapclient/enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (c *Client) Enable(caps ...imap.Cap) *EnableCommand {
// extensions we support here
for _, name := range caps {
switch name {
case imap.CapIMAP4rev2, imap.CapUTF8Accept, imap.CapMetadata, imap.CapMetadataServer:
case imap.CapIMAP4rev2, imap.CapUTF8Accept, imap.CapMetadata, imap.CapMetadataServer, imap.CapQResync, imap.CapCondStore:
// ok
default:
done := make(chan error)
Expand Down
6 changes: 5 additions & 1 deletion imapclient/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ func (c *Client) Fetch(numSet imap.NumSet, options *imap.FetchOptions) *FetchCom
enc.SP().NumSet(numSet).SP()
writeFetchItems(enc.Encoder, numKind, options)
if options.ChangedSince != 0 {
enc.SP().Special('(').Atom("CHANGEDSINCE").SP().ModSeq(options.ChangedSince).Special(')')
enc.SP().Special('(').Atom("CHANGEDSINCE").SP().ModSeq(options.ChangedSince)
if options.Vanished {
enc.SP().Atom("VANISHED")
}
enc.Special(')')
}
enc.end()
return cmd
Expand Down
21 changes: 19 additions & 2 deletions imapclient/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,25 @@ func (c *Client) Select(mailbox string, options *imap.SelectOptions) *SelectComm
cmd := &SelectCommand{mailbox: mailbox}
enc := c.beginCommand(cmdName, cmd)
enc.SP().Mailbox(mailbox)
if options != nil && options.CondStore {
enc.SP().Special('(').Atom("CONDSTORE").Special(')')
if options != nil {
if options.QResync != nil {
// QRESYNC implies CONDSTORE
enc.SP().Special('(').Atom("QRESYNC").SP()
enc.Special('(')
enc.Number(options.QResync.UIDValidity).SP().ModSeq(options.QResync.ModSeq)
if options.QResync.KnownUIDs != nil {
enc.SP().NumSet(*options.QResync.KnownUIDs)
if options.QResync.SeqMatchData != nil {
enc.SP().Special('(')
enc.NumSet(options.QResync.SeqMatchData.KnownSeqSet).SP()
enc.NumSet(options.QResync.SeqMatchData.KnownUIDSet)
enc.Special(')')
}
}
enc.Special(')').Special(')')
} else if options.CondStore {
enc.SP().Special('(').Atom("CONDSTORE").Special(')')
}
}
enc.end()
return cmd
Expand Down
34 changes: 34 additions & 0 deletions imapclient/vanished.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package imapclient

import (
"github.com/emersion/go-imap/v2"
)

func (c *Client) handleVanished() error {
var earlier bool
if c.dec.Special('(') {
var atom string
if !c.dec.ExpectAtom(&atom) || atom != "EARLIER" || !c.dec.ExpectSpecial(')') {
return c.dec.Err()
}
earlier = true
if !c.dec.ExpectSP() {
return c.dec.Err()
}
}

var uids imap.UIDSet
if !c.dec.ExpectUIDSet(&uids) {
return c.dec.Err()
}

// Check if this is part of a SELECT command response
cmd := findPendingCmdByType[*SelectCommand](c)
if cmd != nil {
cmd.data.VanishedUIDs = uids
} else if handler := c.options.unilateralDataHandler().Vanished; handler != nil {
handler(uids, earlier)
}

return nil
}
21 changes: 21 additions & 0 deletions select.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ package imap
type SelectOptions struct {
ReadOnly bool
CondStore bool // requires CONDSTORE

// QRESYNC parameters (requires QRESYNC extension, RFC 5162)
QResync *SelectQResyncOptions
}

// SelectQResyncOptions contains QRESYNC parameters for SELECT.
type SelectQResyncOptions struct {
UIDValidity uint32
ModSeq uint64
KnownUIDs *UIDSet // optional
SeqMatchData *SelectSeqMatchData // optional
}

// SelectSeqMatchData contains sequence match data for QRESYNC.
type SelectSeqMatchData struct {
KnownSeqSet SeqSet
KnownUIDSet UIDSet
}

// SelectData is the data returned by a SELECT command.
Expand All @@ -28,4 +45,8 @@ type SelectData struct {
List *ListData // requires IMAP4rev2

HighestModSeq uint64 // requires CONDSTORE

// UIDs of messages that were expunged.
// Requires QRESYNC extension (RFC 4551/7162).
VanishedUIDs UIDSet // requires QRESYNC
}