Documentation
¶
Overview ¶
Package engine keeps each account's local copy of a mail provider: the folders, the messages with their flags, and a cache of the messages clients read. A sync engine per account keeps the copy current, whether or not a client is connected, and a queue takes the changes clients make to the provider. The client protocols (imapproxy, jmapproxy) serve from it, and the providers (IMAP, JMAP) feed it.
Clients log in with their provider credentials. The engine keeps them in memory only, while it needs them to reach the provider.
Index ¶
- Constants
- Variables
- func ApplyFlags(flags []imap.Flag, flagOp imap.StoreFlagsOp, change []imap.Flag) []imap.Flag
- func BodyStructure(raw []byte) imap.BodyStructure
- func CanonicalName(name string) string
- func DecodeHeader(v string) string
- func DecodePart(body []byte, encoding string) ([]byte, error)
- func EmailID(key string) string
- func HasAttr(attrs []imap.MailboxAttr, a imap.MailboxAttr) bool
- func HasFlag(flags []imap.Flag, f imap.Flag) bool
- func Match(m MatchMessage, raw []byte, c *imap.SearchCriteria) bool
- func NeedsBody(c *imap.SearchCriteria) bool
- func NonNil(flags []imap.Flag) []imap.Flag
- func PartEncoding(bs imap.BodyStructure, part []int) (string, bool)
- func Preview(raw []byte) string
- func SetOpHook(h func(kinds []string) error)
- func WithAuthResult(ctx context.Context, r AuthResult) context.Context
- type Account
- func (a *Account) Append(name string, data []byte, opts *imap.AppendOptions) (*Message, error)
- func (a *Account) AuthState() AuthState
- func (a *Account) Backfilling() bool
- func (a *Account) Capacity() int
- func (a *Account) Changed() <-chan struct{}
- func (a *Account) Changes(kind, since string, max int) (*Changes, error)
- func (a *Account) Copy(msgs []*Message, in, dest string) ([]Copied, uint32, error)
- func (a *Account) CreateFolder(name string, opts *imap.CreateOptions) error
- func (a *Account) DeleteFolder(name string) error
- func (a *Account) Delim() rune
- func (a *Account) Download(msgs []*Message, st *FetchStats) (map[*Message][]byte, error)
- func (a *Account) Email(id string) []*Message
- func (a *Account) EmailIDs() []string
- func (a *Account) Expunge(m *Mailbox, gone func(*Message) bool) []*Message
- func (a *Account) HasPassword() bool
- func (a *Account) HeaderEntries(msgs []*Message, st *FetchStats) (out map[*Message]*HeaderEntry, rest []*Message, err error)
- func (a *Account) Held() int
- func (a *Account) HighestModSeq(m *Mailbox) uint64
- func (a *Account) Lock()
- func (a *Account) Login(password string) error
- func (a *Account) Mailbox(name string) *Mailbox
- func (a *Account) MailboxByID(id string) *Mailbox
- func (a *Account) MailboxFor(name string) (*Mailbox, error)
- func (a *Account) Mailboxes() []*Mailbox
- func (a *Account) Message(id uuid.UUID) *Message
- func (a *Account) ModSeq(msg *Message) uint64
- func (a *Account) Move(m *Mailbox, in func(*Message) bool, dest string) ([]Copied, uint32, error)
- func (a *Account) NoteRead(name string, uids []imap.UID)
- func (a *Account) Pieces(msgs []*Message, secs []Section, encodings func(*Message, Section) string, ...) (map[*Message]map[string][]byte, error)
- func (a *Account) Prefetch(msgs []*Message, background bool) error
- func (a *Account) Queued() int
- func (a *Account) RecordReads(msgs []*Message)
- func (a *Account) RenameFolder(name, newName string, opts *imap.RenameOptions) error
- func (a *Account) Running() bool
- func (a *Account) SearchAtProvider(msgs []*Message, c *imap.SearchCriteria) (map[*Message]bool, error)
- func (a *Account) Send(ctx context.Context, from string, to []string, data []byte) error
- func (a *Account) SetCredentials(password string, u Upstream)
- func (a *Account) SetFlags(msgs []*Message, in string, flagOp imap.StoreFlagsOp, flags []imap.Flag, ...)
- func (a *Account) Since() time.Time
- func (a *Account) State() string
- func (a *Account) String() string
- func (a *Account) Subscribe(name string, on bool) error
- func (a *Account) Sweeps() int
- func (a *Account) SyncNow(name string) error
- func (a *Account) Unlock()
- func (a *Account) User() string
- type AuthAction
- type AuthMode
- type AuthResult
- type AuthState
- type AuthStatus
- type Cache
- func (c *Cache) Close() error
- func (c *Cache) Delete(account, key string) error
- func (c *Cache) DropNonFinal()
- func (c *Cache) Get(account, key string) []byte
- func (c *Cache) Has(account, key string) bool
- func (c *Cache) Prune(maxAge time.Duration) (int, error)
- func (c *Cache) Put(account, key string, data []byte, persist bool) error
- func (c *Cache) Trim() (int, error)
- type Changes
- type Copied
- type Engine
- type FetchStats
- type HeaderEntry
- type Mailbox
- func (m *Mailbox) Attrs() []imap.MailboxAttr
- func (m *Mailbox) Delim() rune
- func (m *Mailbox) Flags() []imap.Flag
- func (m *Mailbox) ID() string
- func (m *Mailbox) Index(uid imap.UID) (int, bool)
- func (m *Mailbox) Msgs() []*Message
- func (m *Mailbox) Name() string
- func (m *Mailbox) PermanentFlags() []imap.Flag
- func (m *Mailbox) Selectable() bool
- func (m *Mailbox) Subscribed() bool
- func (m *Mailbox) Synced() time.Time
- func (m *Mailbox) Tracker() *imapserver.MailboxTracker
- func (m *Mailbox) UIDNext() imap.UID
- func (m *Mailbox) UIDValidity() uint32
- type MatchMessage
- type Message
- type ReadStatus
- type Section
- type Store
- func (s *Store) Close() error
- func (s *Store) ForgetReads(dir string, keys []string) error
- func (s *Store) HeaderKeys(account string) (map[string]bool, error)
- func (s *Store) Headers(account string, keys []string) map[string]*HeaderEntry
- func (s *Store) NoteReadsIn(dir string, keys []string) error
- func (s *Store) Prune(maxAge time.Duration) (int, error)
- func (s *Store) ReadKeys(account string) (map[string]bool, error)
- type Transform
- type TrimReads
- type Upstream
Constants ¶
const ( KindEmail = "Email" KindMailbox = "Mailbox" )
The kinds of object the log stamps.
const FetchBatch = 25
FetchBatch is how many messages one upstream FETCH of full bodies asks for.
const PreviewLen = 256
PreviewLen is how long a preview may be, in characters (RFC 8621 section 4.1.4: up to 256).
Variables ¶
var ( // ErrNoAccount means no provider serves the login. ErrNoAccount = errors.New("no account matches the login") // ErrNoMailbox means the folder does not exist, or cannot hold // messages. ErrNoMailbox = errors.New("no such mailbox") )
var ( // SyncPoll is how often the other folders are checked, and INBOX on // providers without IDLE. SyncPoll = time.Minute // SyncPollNotified replaces SyncPoll for a provider that reports // every change itself (watchConn.Notifies). SyncPollNotified = 15 * time.Minute // SyncListEvery is how often the folder list is read again. SyncListEvery = 10 * time.Minute )
var ErrCannotCalculateChanges = errors.New("cannot calculate changes")
ErrCannotCalculateChanges means the log cannot answer from a state: it is from another epoch, or older than the log keeps.
var (
ErrLoginRejected = errors.New("login rejected")
)
var ErrNoCredentials = errors.New("no credentials for the account")
ErrNoCredentials means no client has logged in since the provider last rejected the password, or since the proxy started.
var ErrNoSearch = errNoSearch
ErrNoSearch means the provider cannot search: read the messages.
var ErrNoSending = errors.New("the provider does not send through the engine")
ErrNoSending means the account's provider has no way to send through the engine: an IMAP provider's mail goes to its submission server.
var ErrSendRefused = errors.New("the provider refused the message")
ErrSendRefused means the provider refused to send a message.
var ErrTransform = errors.New("message could not be stored")
ErrTransform means the Transform refused a message a client stores.
var NewMailWait = 8 * time.Second
NewMailWait is the longest a resync waits for new mail's download before it tells clients. 0 does not wait.
var PieceWhole int64 = 32 << 10
PieceWhole is the size up to which a message is read whole when a client asks for a piece of it.
var (
// ReadAheadCount is how many messages one read-ahead downloads.
ReadAheadCount = 100
)
var SelectFresh = 5 * time.Second
SelectFresh is how old a folder's state may be when a client opens it before the engine checks the provider again.
var SelectWaits = false
SelectWaits makes opening a folder synced before wait for the check; tests set it. Otherwise the client gets the engine's state at once, and what the check finds reaches it as updates, as for any change at the provider. A client that switches folders often, as Apple Mail does, would otherwise wait on the provider every time.
Functions ¶
func ApplyFlags ¶
ApplyFlags returns flags after a STORE. \Recent is the server's.
func CanonicalName ¶
CanonicalName spells INBOX in capitals (RFC 9051 section 5.1).
func DecodeHeader ¶
DecodeHeader decodes RFC 2047 encoded words in a header value.
func DecodePart ¶
DecodePart decodes a part's body with its Content-Transfer-Encoding.
func HasAttr ¶
func HasAttr(attrs []imap.MailboxAttr, a imap.MailboxAttr) bool
func Match ¶
func Match(m MatchMessage, raw []byte, c *imap.SearchCriteria) bool
Match reports whether a message matches c. raw is the message clients see; it is needed only if NeedsBody(c). The matching follows go-imap's imapmemserver (MIT licensed).
func NeedsBody ¶
func NeedsBody(c *imap.SearchCriteria) bool
NeedsBody reports whether c looks at anything but flags, dates and numbers.
func PartEncoding ¶
func PartEncoding(bs imap.BodyStructure, part []int) (string, bool)
PartEncoding returns the Content-Transfer-Encoding of a part in a body structure, and whether the part is there.
func Preview ¶
Preview returns the start of a message's text, whitespace collapsed: the first text/plain part that is not an attachment, or else the first text/html part without its markup.
func SetOpHook ¶
SetOpHook makes h run before each group of queued changes goes to the provider, with their kinds ("flags", "copy", "move", "expunge"). An error from h counts as the provider's. nil removes the hook.
func WithAuthResult ¶ added in v0.0.5
func WithAuthResult(ctx context.Context, r AuthResult) context.Context
WithAuthResult returns ctx carrying r, for Transform tests.
Types ¶
type Account ¶
type Account struct {
// contains filtered or unexported fields
}
Account is one provider Account.
func (*Account) Append ¶
Append stores a message in a folder at the provider, after the Transform turns it into the message the provider stores, and syncs the folder. It returns the message clients see, or nil if the provider did not say which it is.
func (*Account) Backfilling ¶
Backfilling reports whether a backfill runs.
func (*Account) Capacity ¶ added in v0.0.4
Capacity returns the requests the account's provider takes at once now, 0 for no known limit.
func (*Account) Changed ¶
func (a *Account) Changed() <-chan struct{}
Changed returns a channel that is closed at the next change (lock held).
func (*Account) Changes ¶
Changes returns what changed in objects of kind (KindEmail, KindMailbox) since state, at most max (0 for no limit). It returns ErrCannotCalculateChanges for a state it cannot answer from (lock held).
func (*Account) Copy ¶
Copy copies msgs to folder dest. Messages no longer in folder in are skipped; with in "", those expunged are. It returns the copies, and the UIDVALIDITY of dest.
func (*Account) CreateFolder ¶
func (a *Account) CreateFolder(name string, opts *imap.CreateOptions) error
func (*Account) DeleteFolder ¶
func (*Account) Download ¶
Download returns the messages clients see, from the cache or by fetching and transforming them. A message the provider no longer has is missing from the result. st, if not nil, counts where they came from. It counts as a client's read (RecordReads).
func (*Account) Email ¶
Email returns the messages clients see that make the email with JMAP ID id (EmailID): the same message in one or more folders. nil if there are none (lock held).
func (*Account) Expunge ¶
Expunge removes the messages of folder m that gone reports true for, and returns them. gone runs with the lock held.
func (*Account) HasPassword ¶
HasPassword reports whether the account has a password the provider accepted.
func (*Account) HeaderEntries ¶
func (a *Account) HeaderEntries(msgs []*Message, st *FetchStats) (out map[*Message]*HeaderEntry, rest []*Message, err error)
HeaderEntries finds the header entries of msgs: in the Store, from their cached copies (whose entries are then saved), or, for messages the proxy does not change, from the provider, which is asked for the entries alone. The messages left to answer from whole messages (downloaded and transformed, which saves their entries) are returned as rest.
func (*Account) HighestModSeq ¶
HighestModSeq is a folder's HIGHESTMODSEQ for CONDSTORE clients (lock held): the highest ModSeq of its messages, or the folder's own last change (a message added or expunged), whichever is higher. It rises with every change in the folder.
func (*Account) Lock ¶
func (a *Account) Lock()
Lock takes the account lock, which the read accessors need.
func (*Account) Login ¶
Login checks the credentials against the provider, unless the provider already accepted the same password for this account. The first login after a restart, or after a password change, always goes to the provider. It returns ErrNoAccount, an error that wraps ErrLoginRejected, or the error that kept it from the provider.
func (*Account) MailboxByID ¶
MailboxByID finds a folder by its JMAP ID, or nil (lock held).
func (*Account) MailboxFor ¶
MailboxFor returns the folder, checking it at the provider if its state is older than selectFresh: before returning for a folder never synced (or if SelectWaits), in the background otherwise. It returns ErrNoMailbox for a folder that does not exist or cannot hold messages, or the provider's error for one never synced.
func (*Account) Message ¶
Message returns a message by its ID, or nil (lock held). An expunged message the provider has not expunged yet has Box "".
func (*Account) ModSeq ¶
ModSeq is a message's mod-sequence for IMAP CONDSTORE clients (lock held): its email's last change in the log. A change to any copy of the email counts, so a copy's ModSeq may rise with no change of its own, which CONDSTORE allows. It is at least 1.
func (*Account) Move ¶
Move moves the messages of folder m that in reports true for to folder dest, lowest UID first. in runs with the lock held. It returns the moved messages (From and To are the same message), and the UIDVALIDITY of dest.
func (*Account) Pieces ¶
func (a *Account) Pieces(msgs []*Message, secs []Section, encodings func(*Message, Section) string, st *FetchStats) (map[*Message]map[string][]byte, error)
Pieces gets sections of direct messages: from the cache, or from the provider, which is asked for just them, and they are cached. A message the provider cannot give pieces of (a JMAP provider, a queued copy) is left out of the result: the caller reads it whole. Sections are the same for every message, except their Encoding, which encodings gives by message (for Binary sections; "" when unknown).
func (*Account) Prefetch ¶
Prefetch downloads msgs into the cache, as the engine does ahead of clients; background as read-ahead and the backfill do.
func (*Account) RecordReads ¶ added in v0.0.3
RecordReads records that a client read msgs whole: the backfill downloads a message read within the Store's Prune age again if the cache loses it. Download records its own; a caller that reads the Cache directly records them here.
func (*Account) RenameFolder ¶
func (a *Account) RenameFolder(name, newName string, opts *imap.RenameOptions) error
RenameFolder renames a folder and the folders under it. Their messages keep their UIDs.
func (*Account) SearchAtProvider ¶
func (a *Account) SearchAtProvider(msgs []*Message, c *imap.SearchCriteria) (map[*Message]bool, error)
SearchAtProvider asks the provider which of msgs match c, for messages the proxy does not change: the provider has the same text. It returns ErrNoSearch if the provider cannot search; then the caller reads the messages.
func (*Account) Send ¶
Send sends a message through the account's provider, for providers that send without a submission server (JMAP, ActiveSync). It needs credentials the provider accepted (Login). An ActiveSync provider keeps a copy in the Sent folder; a JMAP provider does not.
func (*Account) SetCredentials ¶
SetCredentials records credentials as if the provider accepted them, and starts the engine.
func (*Account) SetFlags ¶
func (a *Account) SetFlags(msgs []*Message, in string, flagOp imap.StoreFlagsOp, flags []imap.Flag, source *imapserver.SessionTracker)
SetFlags changes the flags of msgs and queues the change. Messages no longer in folder in are skipped; with in "", those expunged are. Every view but source hears of the change through its mailbox tracker; source, if not nil, reports it itself.
func (*Account) Since ¶
Since returns the account's cutoff: messages that arrived at or after it are new. Zero before the account's first sync.
func (*Account) State ¶
State returns the account's state: it changes whenever anything a client sees does (lock held).
type AuthAction ¶ added in v0.0.5
type AuthAction string
AuthAction is what happens to a new message.
const ( AuthNone AuthAction = "" // shown AuthJunk AuthAction = "junk" // moved to the folder with \Junk AuthTrash AuthAction = "trash" // moved to the folder with \Trash AuthDelete AuthAction = "delete" // deleted at the provider )
type AuthMode ¶ added in v0.0.5
type AuthMode string
AuthMode is how hard the engine acts on the provider's verdicts.
type AuthResult ¶ added in v0.0.5
type AuthResult struct {
Status AuthStatus
From string // lowercased; "" unless exactly one
DKIMAligned, SPFAligned bool
DMARC, DKIM, SPF string // as reported; "" if none
DKIMDomains []string
SPFDomain string
DMARCPolicy string // on failure; "" if unreadable
Rescued bool // moved back by the user
Learning bool // stamp not known yet
// New marks mail not yet shown, which ReadJunk and the others can move
// away. Otherwise they act as ReadRetry.
New bool
Reason string // for logs
}
AuthResult is the verdict on one message.
func AuthResultFrom ¶ added in v0.0.5
func AuthResultFrom(ctx context.Context) (r AuthResult, ok bool)
AuthResultFrom returns the verdict on the message being read, if any.
type AuthState ¶ added in v0.0.5
type AuthState struct {
Stamp []string // trusted names; empty while learning
// Candidates are names on authShare of a full window that are not
// learned for want of senders: the provider stamps its verdicts, but
// its mail comes from too few domains to tell it from a forger's.
Candidates []string
Examined int
LastStamped time.Time
}
AuthState is what the engine knows of an account's provider stamp.
type AuthStatus ¶ added in v0.0.5
type AuthStatus string
AuthStatus is the verdict on a message.
const ( AuthPass AuthStatus = "pass" // or rescued by the user AuthFail AuthStatus = "fail" AuthNoPolicy AuthStatus = "none" // no DMARC record, or permerror AuthUnknown AuthStatus = "unknown" // no usable verdict )
type Cache ¶
type Cache struct {
Dir string // cache_dir
MaxBytes int64 // memory bound
// Plain stores chunks uncompressed, for a file system that compresses
// them. It applies to new chunk databases: one keeps what it was made
// with.
Plain bool
// The free space to leave on Dir's file system (see Trim): a
// percentage of it, or bytes, whichever is more. Zero for neither.
MinFreePercent float64
MinFreeBytes int64
Log *slog.Logger // for Trim; nil for none
// Reads, if not nil, keeps the clients' reads (the Store): Trim forgets
// those of the messages it deletes, so the backfill does not download
// them again.
Reads TrimReads
// contains filtered or unexported fields
}
Cache holds messages as clients see them, so each is fetched and transformed once. Entries are keyed by message fingerprint (fingerprintKey), not by folder and UID, so a message keeps its entry when it moves, and a copy in two folders shares one.
Entries live in memory (least recently used out first) and, if Dir is set, on disk in the account's chunk database (chunkstore.go), where what messages share is stored once. The disk store is not encrypted: with a Transform that decrypts, protect Dir like the private keys.
func (*Cache) DropNonFinal ¶
func (c *Cache) DropNonFinal()
DropNonFinal forgets the results Read gave as ReadRetry, so they are read again.
type Changes ¶
type Changes struct {
Created, Updated, Destroyed []string
// CountsOnly says every update changed folder counts alone.
CountsOnly bool
// NewState is the state the changes lead to; with HasMore, a state
// before the current one, to ask from next.
NewState string
HasMore bool
}
Changes is what changed since a state.
type Copied ¶
type Copied struct {
From, To *Message
// FromUID and ToUID are the UIDs before and after.
FromUID, ToUID imap.UID
}
Copied is a message and its copy, or a message before and after a move.
type Engine ¶
type Engine struct {
// Route picks the provider for a login name. ok is false for a login
// the engine does not serve, which is then rejected.
Route func(login string) (u Upstream, ok bool)
Timeout time.Duration // per upstream operation
// Transform changes messages between the provider and clients; nil
// serves them unchanged.
Transform Transform
// Authentication is the sender authentication mode; "" is off.
Authentication AuthMode
// LookupTXT overrides DNS for DMARC lookups in tests.
LookupTXT func(ctx context.Context, name string) ([]string, error)
// Authorize, if set, runs after each login; an error refuses it.
Authorize func(a *Account) error
Cache *Cache
// Store keeps the mailbox state on disk across restarts; nil keeps it
// in memory only.
Store *Store
// Backfill fills every folder's message list in the background after
// a login (backfill.go). It needs a Store.
Backfill bool
Log *slog.Logger
// Name and Version identify the proxy to providers that ask (the IMAP
// ID command).
Name, Version string
// contains filtered or unexported fields
}
Engine holds the accounts.
func (*Engine) Close ¶
func (b *Engine) Close()
Close ends every account's engine, drops every password, and waits for the read times to be written.
func (*Engine) Downloading ¶
Downloading reports whether the message with cache key key is being downloaded.
func (*Engine) Preload ¶ added in v0.0.4
func (b *Engine) Preload()
Preload loads, in the background, the saved state of every account that has some and that a route still covers, so a client's first login after a start does not wait for it. An account whose folder is named by a hash is loaded at its first login instead.
type FetchStats ¶
type FetchStats struct {
Memory, Disk int // from the cache
Provider int // fetched by this request
Waited int // fetched by another request, waited for
Headers int // answered from header entries
Pieces int // answered from pieces (piece.go)
}
FetchStats says where a FETCH got its messages, for the fetch trace.
type HeaderEntry ¶
type HeaderEntry struct {
Header []byte
Size int64
Structure imap.BodyStructure
// Preview is the start of the message's text, for JMAP; nil if
// unknown (an entry the provider gave without the body).
Preview *string
}
HeaderEntry is what clients fill a message list with, kept in the Store so a client's list is answered without reading whole messages: the header, the size and the structure (which the header does not give). For a message the proxy does not change (message.direct) the provider is asked for these alone. For any other, they come from the changed message, when it is transformed or read from the disk cache.
func EntryOf ¶
func EntryOf(raw []byte) *HeaderEntry
EntryOf makes the header entry of a whole message.
type Mailbox ¶
type Mailbox struct {
// contains filtered or unexported fields
}
Mailbox is the proxy's copy of one folder.
func (*Mailbox) Attrs ¶
func (m *Mailbox) Attrs() []imap.MailboxAttr
func (*Mailbox) Msgs ¶
Msgs returns the messages clients see, by UID. A sequence number is the index plus one. The caller must not change the slice.
func (*Mailbox) PermanentFlags ¶
func (*Mailbox) Selectable ¶
func (*Mailbox) Subscribed ¶
func (*Mailbox) Tracker ¶
func (m *Mailbox) Tracker() *imapserver.MailboxTracker
func (*Mailbox) UIDValidity ¶
type MatchMessage ¶
type MatchMessage struct {
Seq uint32 // sequence number in the client's view
UID imap.UID
Flags []imap.Flag
Date time.Time // internal date
}
MatchMessage is what a search knows of a message besides its contents.
type Message ¶
type Message struct {
// contains filtered or unexported fields
}
func (*Message) AtProvider ¶
AtProvider returns where the provider has the message: a folder and its UID there, or "" and 0 (lock held).
type ReadStatus ¶
type ReadStatus int
ReadStatus says what to do with the result of Transform.Read.
const ( // ReadFinal: the result is what clients see from now on. It is // cached on disk. ReadFinal ReadStatus = iota // ReadRetry: clients see the result, but a later Read may give // another (a key that is missing now). It is kept in memory only, // until Engine.Recheck. ReadRetry // ReadHold: clients must not see the message yet. A new message (one // that arrived at or after Account.Since) is held: clients do not see // it at all until a later Read gives ReadFinal or ReadRetry. It is // tried again with every sweep and on Engine.Recheck. For an older // message, ReadHold is ReadRetry. ReadHold // ReadJunk, ReadTrash and ReadDelete move new mail away; for older // mail they act as ReadRetry. ReadJunk ReadTrash ReadDelete // ReadDefer is ReadJunk, released once the stamp is known. ReadDefer )
type Section ¶
type Section struct {
Binary bool
Part []int
Specifier imap.PartSpecifier
Fields []string // HEADER.FIELDS, or with NotFields HEADER.FIELDS.NOT
NotFields bool
Partial *imap.SectionPartial
// Encoding is the part's Content-Transfer-Encoding, for a Binary
// section from a provider without BINARY.
Encoding string
}
Section is a piece of a message: a body section, or with Binary the decoded content of a part.
type Store ¶
type Store struct {
Dir string // cache_dir
// contains filtered or unexported fields
}
Store keeps each account's mailbox state on disk, in one SQLite database per account: folders, messages with the proxy's IDs and UIDs and where the provider has them, the queue of changes not yet made at the provider, the header entries clients fill message lists with (headerEntry), and when clients last read each message, so a lost cache can be filled again. Never whole messages: those are in the Cache. It lets the proxy answer clients right after a restart, before the sync has caught up with the provider, and finish the queue after a shutdown.
Each account has its own database, so accounts never wait on each other's writes, and an account is removed by deleting its folder (accountDir).
func (*Store) ForgetReads ¶ added in v0.0.3
ForgetReads deletes the records of reads of keys in the account folder dir: Trim deleted their messages (Cache.Reads), and the backfill must not download them again.
func (*Store) HeaderKeys ¶
HeaderKeys returns the cache keys with a header entry.
func (*Store) Headers ¶
func (s *Store) Headers(account string, keys []string) map[string]*HeaderEntry
Headers returns the header entries saved for keys. A read counts as use for prune.
func (*Store) NoteReadsIn ¶ added in v0.0.3
NoteReadsIn records that a client read keys now, in the account folder dir: Trim kept their messages, which a client read meanwhile, after ForgetReads.
type Transform ¶
type Transform interface {
// Fields lists the header fields Changes needs. The sync fetches them
// for every message, with Message-ID.
Fields() []string
// Changes reports, from a message's Fields, whether Read changes it.
// A message it does not change is served as the provider has it,
// without downloading it whole.
Changes(h textproto.MIMEHeader) bool
// Read turns a message from the provider into the one clients see,
// and says how sure the result is (ReadStatus).
Read(ctx context.Context, raw []byte) (out []byte, status ReadStatus)
// Write turns a message a client stores with APPEND (a Sent copy, a
// draft) into the one the provider stores. An error refuses the
// APPEND.
Write(ctx context.Context, raw []byte) ([]byte, error)
}
Transform changes messages between the provider and clients, for example to decrypt mail on its way to clients and encrypt what they store. Without one, the proxy serves every message as the provider has it.
type TrimReads ¶ added in v0.0.3
type TrimReads interface {
ForgetReads(dir string, keys []string) error
NoteReadsIn(dir string, keys []string) error
}
TrimReads is where Trim forgets the clients' reads of the messages it deletes, by account folder (the Store).
type Upstream ¶
type Upstream struct {
Addr string // IMAP: host:port
TLS string // IMAP: config.TLSImplicit, TLSStartTLS or TLSNone
TLSConfig *tls.Config
Timeout time.Duration
// Sources are where connections to the provider may leave from:
// interface names or local IP addresses. IMAP keeps a count and a
// limit for each; HTTP connections take them in turn. None: wherever
// routing says.
Sources []string
// URL is a JMAP provider's session resource, such as
// https://example.com/.well-known/jmap. It makes the provider JMAP.
URL string
// Bearer sends the client's password to a JMAP provider as a Bearer
// token (an API token), not with Basic auth.
Bearer bool
// ActiveSync is an Exchange ActiveSync provider's endpoint, such as
// https://example.com/Microsoft-Server-ActiveSync. It makes the
// provider ActiveSync.
ActiveSync string
// MaxConns caps the connections (IMAP) or the requests at a time
// (JMAP, ActiveSync) to the provider, counting the engine's own. 0:
// no cap but the provider's, which an IMAP provider shows by refusing
// a connection and a JMAP one gives in its session.
MaxConns int
}
Upstream is a provider's server: IMAP (Addr), JMAP (URL), or ActiveSync (ActiveSync).
Source Files
¶
- account.go
- api.go
- authenticate.go
- backfill.go
- cache.go
- cachefree.go
- changes.go
- chunk.go
- chunkstore.go
- dmarc.go
- easprov.go
- engine.go
- fetch.go
- flags.go
- handshake.go
- header.go
- hold.go
- imapprov.go
- inspect.go
- jmapprov.go
- match.go
- newmail.go
- notifytap.go
- piece.go
- preview.go
- provider.go
- queue.go
- readahead.go
- statfs_unix.go
- store.go
- sync.go
- tcp_linux.go
- transform.go
- watchdog.go