mirror of
https://github.com/mjl-/mox.git
synced 2025-06-27 22:28:16 +03:00

Once clients enable this extension, commands can no longer refer to "message sequence numbers" (MSNs), but can only refer to messages with UIDs. This means both sides no longer have to carefully keep their sequence numbers in sync (error-prone), and don't have to keep track of a mapping of sequence numbers to UIDs (saves resources). With UIDONLY enabled, all FETCH responses are replaced with UIDFETCH response.
687 lines
17 KiB
Go
687 lines
17 KiB
Go
package imapclient
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Capability is a known string for with the ENABLED and CAPABILITY command.
|
|
type Capability string
|
|
|
|
const (
|
|
CapIMAP4rev1 Capability = "IMAP4rev1"
|
|
CapIMAP4rev2 Capability = "IMAP4rev2"
|
|
CapLoginDisabled Capability = "LOGINDISABLED"
|
|
CapStarttls Capability = "STARTTLS"
|
|
CapAuthPlain Capability = "AUTH=PLAIN"
|
|
CapLiteralPlus Capability = "LITERAL+"
|
|
CapLiteralMinus Capability = "LITERAL-"
|
|
CapIdle Capability = "IDLE"
|
|
CapNamespace Capability = "NAMESPACE"
|
|
CapBinary Capability = "BINARY"
|
|
CapUnselect Capability = "UNSELECT"
|
|
CapUidplus Capability = "UIDPLUS"
|
|
CapEsearch Capability = "ESEARCH"
|
|
CapEnable Capability = "ENABLE"
|
|
CapSave Capability = "SAVE"
|
|
CapListExtended Capability = "LIST-EXTENDED"
|
|
CapSpecialUse Capability = "SPECIAL-USE"
|
|
CapMove Capability = "MOVE"
|
|
CapUTF8Only Capability = "UTF8=ONLY"
|
|
CapUTF8Accept Capability = "UTF8=ACCEPT"
|
|
CapID Capability = "ID" // ../rfc/2971:80
|
|
CapMetadata Capability = "METADATA" // ../rfc/5464:124
|
|
CapMetadataServer Capability = "METADATA-SERVER" // ../rfc/5464:124
|
|
CapSaveDate Capability = "SAVEDATE" // ../rfc/8514
|
|
CapCreateSpecialUse Capability = "CREATE-SPECIAL-USE" // ../rfc/6154:296
|
|
CapCompressDeflate Capability = "COMPRESS=DEFLATE" // ../rfc/4978:65
|
|
CapListMetadata Capability = "LIST-METADTA" // ../rfc/9590:73
|
|
CapMultiAppend Capability = "MULTIAPPEND" // ../rfc/3502:33
|
|
CapReplace Capability = "REPLACE" // ../rfc/8508:155
|
|
CapPreview Capability = "PREVIEW" // ../rfc/8970:114
|
|
CapMultiSearch Capability = "MULTISEARCH" // ../rfc/7377:187
|
|
CapNotify Capability = "NOTIFY" // ../rfc/5465:195
|
|
CapUIDOnly Capability = "UIDONLY" // ../rfc/9586:129
|
|
)
|
|
|
|
// Status is the tagged final result of a command.
|
|
type Status string
|
|
|
|
const (
|
|
BAD Status = "BAD" // Syntax error.
|
|
NO Status = "NO" // Command failed.
|
|
OK Status = "OK" // Command succeeded.
|
|
)
|
|
|
|
// Result is the final response for a command, indicating success or failure.
|
|
type Result struct {
|
|
Status Status
|
|
RespText
|
|
}
|
|
|
|
// CodeArg represents a response code with arguments, i.e. the data between [] in the response line.
|
|
type CodeArg interface {
|
|
CodeString() string
|
|
}
|
|
|
|
// CodeOther is a valid but unrecognized response code.
|
|
type CodeOther struct {
|
|
Code string
|
|
Args []string
|
|
}
|
|
|
|
func (c CodeOther) CodeString() string {
|
|
return c.Code + " " + strings.Join(c.Args, " ")
|
|
}
|
|
|
|
// CodeWords is a code with space-separated string parameters. E.g. CAPABILITY.
|
|
type CodeWords struct {
|
|
Code string
|
|
Args []string
|
|
}
|
|
|
|
func (c CodeWords) CodeString() string {
|
|
s := c.Code
|
|
for _, w := range c.Args {
|
|
s += " " + w
|
|
}
|
|
return s
|
|
}
|
|
|
|
// CodeList is a code with a list with space-separated strings as parameters. E.g. BADCHARSET, PERMANENTFLAGS.
|
|
type CodeList struct {
|
|
Code string
|
|
Args []string // If nil, no list was present. List can also be empty.
|
|
}
|
|
|
|
func (c CodeList) CodeString() string {
|
|
s := c.Code
|
|
if c.Args == nil {
|
|
return s
|
|
}
|
|
return s + "(" + strings.Join(c.Args, " ") + ")"
|
|
}
|
|
|
|
// CodeUint is a code with a uint32 parameter, e.g. UIDNEXT and UIDVALIDITY.
|
|
type CodeUint struct {
|
|
Code string
|
|
Num uint32
|
|
}
|
|
|
|
func (c CodeUint) CodeString() string {
|
|
return fmt.Sprintf("%s %d", c.Code, c.Num)
|
|
}
|
|
|
|
// "APPENDUID" response code.
|
|
type CodeAppendUID struct {
|
|
UIDValidity uint32
|
|
UIDs NumRange
|
|
}
|
|
|
|
func (c CodeAppendUID) CodeString() string {
|
|
return fmt.Sprintf("APPENDUID %d %s", c.UIDValidity, c.UIDs.String())
|
|
}
|
|
|
|
// "COPYUID" response code.
|
|
type CodeCopyUID struct {
|
|
DestUIDValidity uint32
|
|
From []NumRange
|
|
To []NumRange
|
|
}
|
|
|
|
func (c CodeCopyUID) CodeString() string {
|
|
str := func(l []NumRange) string {
|
|
s := ""
|
|
for i, e := range l {
|
|
if i > 0 {
|
|
s += ","
|
|
}
|
|
s += fmt.Sprintf("%d", e.First)
|
|
if e.Last != nil {
|
|
s += fmt.Sprintf(":%d", *e.Last)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
return fmt.Sprintf("COPYUID %d %s %s", c.DestUIDValidity, str(c.From), str(c.To))
|
|
}
|
|
|
|
// For CONDSTORE.
|
|
type CodeModified NumSet
|
|
|
|
func (c CodeModified) CodeString() string {
|
|
return fmt.Sprintf("MODIFIED %s", NumSet(c).String())
|
|
}
|
|
|
|
// For CONDSTORE.
|
|
type CodeHighestModSeq int64
|
|
|
|
func (c CodeHighestModSeq) CodeString() string {
|
|
return fmt.Sprintf("HIGHESTMODSEQ %d", c)
|
|
}
|
|
|
|
// "INPROGRESS" response code.
|
|
type CodeInProgress struct {
|
|
Tag string // Nil is empty string.
|
|
Current *uint32
|
|
Goal *uint32
|
|
}
|
|
|
|
func (c CodeInProgress) CodeString() string {
|
|
// ABNF allows inprogress-tag/state with all nil values. Doesn't seem useful enough
|
|
// to keep track of.
|
|
if c.Tag == "" && c.Current == nil && c.Goal == nil {
|
|
return "INPROGRESS"
|
|
}
|
|
|
|
// todo: quote tag properly
|
|
current := "nil"
|
|
goal := "nil"
|
|
if c.Current != nil {
|
|
current = fmt.Sprintf("%d", *c.Current)
|
|
}
|
|
if c.Goal != nil {
|
|
goal = fmt.Sprintf("%d", *c.Goal)
|
|
}
|
|
return fmt.Sprintf("INPROGRESS (%q %s %s)", c.Tag, current, goal)
|
|
}
|
|
|
|
// "BADEVENT" response code, with the events that are supported, for the NOTIFY
|
|
// extension.
|
|
type CodeBadEvent []string
|
|
|
|
func (c CodeBadEvent) CodeString() string {
|
|
return fmt.Sprintf("BADEVENT (%s)", strings.Join([]string(c), " "))
|
|
}
|
|
|
|
// RespText represents a response line minus the leading tag.
|
|
type RespText struct {
|
|
Code string // The first word between [] after the status.
|
|
CodeArg CodeArg // Set if code has a parameter.
|
|
More string // Any remaining text.
|
|
}
|
|
|
|
// atom or string.
|
|
func astring(s string) string {
|
|
if len(s) == 0 {
|
|
return stringx(s)
|
|
}
|
|
for _, c := range s {
|
|
if c <= ' ' || c >= 0x7f || c == '(' || c == ')' || c == '{' || c == '%' || c == '*' || c == '"' || c == '\\' {
|
|
return stringx(s)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// imap "string", i.e. double-quoted string or syncliteral.
|
|
func stringx(s string) string {
|
|
r := `"`
|
|
for _, c := range s {
|
|
if c == '\x00' || c == '\r' || c == '\n' {
|
|
return syncliteral(s)
|
|
}
|
|
if c == '\\' || c == '"' {
|
|
r += `\`
|
|
}
|
|
r += string(c)
|
|
}
|
|
r += `"`
|
|
return r
|
|
}
|
|
|
|
// sync literal, i.e. {<num>}\r\n<num bytes>.
|
|
func syncliteral(s string) string {
|
|
return fmt.Sprintf("{%d}\r\n", len(s)) + s
|
|
}
|
|
|
|
// Untagged is a parsed untagged response. See types starting with Untagged.
|
|
// todo: make an interface that the untagged responses implement?
|
|
type Untagged any
|
|
|
|
type UntaggedBye RespText
|
|
type UntaggedPreauth RespText
|
|
type UntaggedExpunge uint32
|
|
type UntaggedExists uint32
|
|
type UntaggedRecent uint32
|
|
type UntaggedCapability []string
|
|
type UntaggedEnabled []string
|
|
type UntaggedResult Result
|
|
type UntaggedFlags []string
|
|
type UntaggedList struct {
|
|
// ../rfc/9051:6690
|
|
Flags []string
|
|
Separator byte // 0 for NIL
|
|
Mailbox string
|
|
Extended []MboxListExtendedItem
|
|
OldName string // If present, taken out of Extended.
|
|
}
|
|
type UntaggedFetch struct {
|
|
Seq uint32
|
|
Attrs []FetchAttr
|
|
}
|
|
|
|
// UntaggedUIDFetch is like UntaggedFetch, but with UIDs instead of message
|
|
// sequence numbers, and returned instead of regular fetch responses when UIDONLY
|
|
// is enabled.
|
|
type UntaggedUIDFetch struct {
|
|
UID uint32
|
|
Attrs []FetchAttr
|
|
}
|
|
type UntaggedSearch []uint32
|
|
|
|
// ../rfc/7162:1101
|
|
type UntaggedSearchModSeq struct {
|
|
Nums []uint32
|
|
ModSeq int64
|
|
}
|
|
type UntaggedStatus struct {
|
|
Mailbox string
|
|
Attrs map[StatusAttr]int64 // Upper case status attributes.
|
|
}
|
|
|
|
// ../rfc/5464:716 Unsolicited response, indicating an annotation has changed.
|
|
type UntaggedMetadataKeys struct {
|
|
Mailbox string // Empty means not specific to mailbox.
|
|
|
|
// Keys that have changed. To get values (or determine absence), the server must be
|
|
// queried.
|
|
Keys []string
|
|
}
|
|
|
|
// Annotation is a metadata server of mailbox annotation.
|
|
type Annotation struct {
|
|
Key string
|
|
// Nil is represented by IsString false and a nil Value.
|
|
IsString bool
|
|
Value []byte
|
|
}
|
|
|
|
// ../rfc/5464:683
|
|
type UntaggedMetadataAnnotations struct {
|
|
Mailbox string // Empty means not specific to mailbox.
|
|
Annotations []Annotation
|
|
}
|
|
|
|
// ../rfc/9051:7059 ../9208:712
|
|
type StatusAttr string
|
|
|
|
const (
|
|
StatusMessages StatusAttr = "MESSAGES"
|
|
StatusUIDNext StatusAttr = "UIDNEXT"
|
|
StatusUIDValidity StatusAttr = "UIDVALIDITY"
|
|
StatusUnseen StatusAttr = "UNSEEN"
|
|
StatusDeleted StatusAttr = "DELETED"
|
|
StatusSize StatusAttr = "SIZE"
|
|
StatusRecent StatusAttr = "RECENT"
|
|
StatusAppendLimit StatusAttr = "APPENDLIMIT"
|
|
StatusHighestModSeq StatusAttr = "HIGHESTMODSEQ"
|
|
StatusDeletedStorage StatusAttr = "DELETED-STORAGE"
|
|
)
|
|
|
|
type UntaggedNamespace struct {
|
|
Personal, Other, Shared []NamespaceDescr
|
|
}
|
|
type UntaggedLsub struct {
|
|
// ../rfc/3501:4833
|
|
Flags []string
|
|
Separator byte
|
|
Mailbox string
|
|
}
|
|
|
|
// Fields are optional and zero if absent.
|
|
type UntaggedEsearch struct {
|
|
Tag string // ../rfc/9051:6546
|
|
Mailbox string // For MULTISEARCH. ../rfc/7377:437
|
|
UIDValidity uint32 // For MULTISEARCH, ../rfc/7377:438
|
|
|
|
UID bool
|
|
Min uint32
|
|
Max uint32
|
|
All NumSet
|
|
Count *uint32
|
|
ModSeq int64
|
|
Exts []EsearchDataExt
|
|
}
|
|
|
|
// UntaggedVanished is used in QRESYNC to send UIDs that have been removed.
|
|
type UntaggedVanished struct {
|
|
Earlier bool
|
|
UIDs NumSet
|
|
}
|
|
|
|
// UntaggedQuotaroot lists the roots for which quota can be present.
|
|
type UntaggedQuotaroot []string
|
|
|
|
// UntaggedQuota holds the quota for a quota root.
|
|
type UntaggedQuota struct {
|
|
Root string
|
|
|
|
// Always has at least one. Any QUOTA=RES-* capability not mentioned has no limit
|
|
// or this quota root.
|
|
Resources []QuotaResource
|
|
}
|
|
|
|
// Resource types ../rfc/9208:533
|
|
|
|
// QuotaResourceName is the name of a resource type. More can be defined in the
|
|
// future and encountered in the wild. Always in upper case.
|
|
type QuotaResourceName string
|
|
|
|
const (
|
|
QuotaResourceStorage = "STORAGE"
|
|
QuotaResourceMesssage = "MESSAGE"
|
|
QuotaResourceMailbox = "MAILBOX"
|
|
QuotaResourceAnnotationStorage = "ANNOTATION-STORAGE"
|
|
)
|
|
|
|
type QuotaResource struct {
|
|
Name QuotaResourceName
|
|
Usage int64 // Currently in use. Count or disk size in 1024 byte blocks.
|
|
Limit int64 // Maximum allowed usage.
|
|
}
|
|
|
|
// ../rfc/2971:184
|
|
|
|
type UntaggedID map[string]string
|
|
|
|
// Extended data in an ESEARCH response.
|
|
type EsearchDataExt struct {
|
|
Tag string
|
|
Value TaggedExtVal
|
|
}
|
|
|
|
type NamespaceDescr struct {
|
|
// ../rfc/9051:6769
|
|
Prefix string
|
|
Separator byte // If 0 then separator was absent.
|
|
Exts []NamespaceExtension
|
|
}
|
|
|
|
type NamespaceExtension struct {
|
|
// ../rfc/9051:6773
|
|
Key string
|
|
Values []string
|
|
}
|
|
|
|
// FetchAttr represents a FETCH response attribute.
|
|
type FetchAttr interface {
|
|
Attr() string // Name of attribute.
|
|
}
|
|
|
|
type NumSet struct {
|
|
SearchResult bool // True if "$", in which case Ranges is irrelevant.
|
|
Ranges []NumRange
|
|
}
|
|
|
|
func (ns NumSet) IsZero() bool {
|
|
return !ns.SearchResult && ns.Ranges == nil
|
|
}
|
|
|
|
func (ns NumSet) String() string {
|
|
if ns.SearchResult {
|
|
return "$"
|
|
}
|
|
var r string
|
|
for i, x := range ns.Ranges {
|
|
if i > 0 {
|
|
r += ","
|
|
}
|
|
r += x.String()
|
|
}
|
|
return r
|
|
}
|
|
|
|
func ParseNumSet(s string) (ns NumSet, rerr error) {
|
|
c := Conn{br: bufio.NewReader(strings.NewReader(s))}
|
|
defer c.recover(&rerr)
|
|
ns = c.xsequenceSet()
|
|
return
|
|
}
|
|
|
|
func ParseUIDRange(s string) (nr NumRange, rerr error) {
|
|
c := Conn{br: bufio.NewReader(strings.NewReader(s))}
|
|
defer c.recover(&rerr)
|
|
nr = c.xuidrange()
|
|
return
|
|
}
|
|
|
|
// NumRange is a single number or range.
|
|
type NumRange struct {
|
|
First uint32 // 0 for "*".
|
|
Last *uint32 // Nil if absent, 0 for "*".
|
|
}
|
|
|
|
func (nr NumRange) String() string {
|
|
var r string
|
|
if nr.First == 0 {
|
|
r += "*"
|
|
} else {
|
|
r += fmt.Sprintf("%d", nr.First)
|
|
}
|
|
if nr.Last == nil {
|
|
return r
|
|
}
|
|
r += ":"
|
|
v := *nr.Last
|
|
if v == 0 {
|
|
r += "*"
|
|
} else {
|
|
r += fmt.Sprintf("%d", v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
type TaggedExtComp struct {
|
|
String string
|
|
Comps []TaggedExtComp // Used for both space-separated and ().
|
|
}
|
|
|
|
type TaggedExtVal struct {
|
|
// ../rfc/9051:7111
|
|
Number *int64
|
|
SeqSet *NumSet
|
|
Comp *TaggedExtComp // If SimpleNumber and SimpleSeqSet is nil, this is a Comp. But Comp is optional and can also be nil. Not great.
|
|
}
|
|
|
|
type MboxListExtendedItem struct {
|
|
// ../rfc/9051:6699
|
|
Tag string
|
|
Val TaggedExtVal
|
|
}
|
|
|
|
// "FLAGS" fetch response.
|
|
type FetchFlags []string
|
|
|
|
func (f FetchFlags) Attr() string { return "FLAGS" }
|
|
|
|
// "ENVELOPE" fetch response.
|
|
type FetchEnvelope Envelope
|
|
|
|
func (f FetchEnvelope) Attr() string { return "ENVELOPE" }
|
|
|
|
// Envelope holds the basic email message fields.
|
|
type Envelope struct {
|
|
Date string
|
|
Subject string
|
|
From, Sender, ReplyTo, To, CC, BCC []Address
|
|
InReplyTo, MessageID string
|
|
}
|
|
|
|
// Address is an address field in an email message, e.g. To.
|
|
type Address struct {
|
|
Name, Adl, Mailbox, Host string
|
|
}
|
|
|
|
// "INTERNALDATE" fetch response.
|
|
type FetchInternalDate struct {
|
|
Date time.Time
|
|
}
|
|
|
|
func (f FetchInternalDate) Attr() string { return "INTERNALDATE" }
|
|
|
|
// "SAVEDATE" fetch response. ../rfc/8514:265
|
|
type FetchSaveDate struct {
|
|
SaveDate *time.Time // nil means absent for message.
|
|
}
|
|
|
|
func (f FetchSaveDate) Attr() string { return "SAVEDATE" }
|
|
|
|
// "RFC822.SIZE" fetch response.
|
|
type FetchRFC822Size int64
|
|
|
|
func (f FetchRFC822Size) Attr() string { return "RFC822.SIZE" }
|
|
|
|
// "RFC822" fetch response.
|
|
type FetchRFC822 string
|
|
|
|
func (f FetchRFC822) Attr() string { return "RFC822" }
|
|
|
|
// "RFC822.HEADER" fetch response.
|
|
type FetchRFC822Header string
|
|
|
|
func (f FetchRFC822Header) Attr() string { return "RFC822.HEADER" }
|
|
|
|
// "RFC82.TEXT" fetch response.
|
|
type FetchRFC822Text string
|
|
|
|
func (f FetchRFC822Text) Attr() string { return "RFC822.TEXT" }
|
|
|
|
// "BODYSTRUCTURE" fetch response.
|
|
type FetchBodystructure struct {
|
|
// ../rfc/9051:6355
|
|
RespAttr string
|
|
Body any // BodyType*
|
|
}
|
|
|
|
func (f FetchBodystructure) Attr() string { return f.RespAttr }
|
|
|
|
// "BODY" fetch response.
|
|
type FetchBody struct {
|
|
// ../rfc/9051:6756 ../rfc/9051:6985
|
|
RespAttr string
|
|
Section string // todo: parse more ../rfc/9051:6985
|
|
Offset int32
|
|
Body string
|
|
}
|
|
|
|
func (f FetchBody) Attr() string { return f.RespAttr }
|
|
|
|
// BodyFields is part of a FETCH BODY[] response.
|
|
type BodyFields struct {
|
|
Params [][2]string
|
|
ContentID, ContentDescr, CTE string
|
|
Octets int32
|
|
}
|
|
|
|
// BodyTypeMpart represents the body structure a multipart message, with
|
|
// subparts and the multipart media subtype. Used in a FETCH response.
|
|
type BodyTypeMpart struct {
|
|
// ../rfc/9051:6411
|
|
Bodies []any // BodyTypeBasic, BodyTypeMsg, BodyTypeText
|
|
MediaSubtype string
|
|
Ext *BodyExtensionMpart
|
|
}
|
|
|
|
// BodyTypeBasic represents basic information about a part, used in a FETCH
|
|
// response.
|
|
type BodyTypeBasic struct {
|
|
// ../rfc/9051:6407
|
|
MediaType, MediaSubtype string
|
|
BodyFields BodyFields
|
|
Ext *BodyExtension1Part
|
|
}
|
|
|
|
// BodyTypeMsg represents an email message as a body structure, used in a FETCH
|
|
// response.
|
|
type BodyTypeMsg struct {
|
|
// ../rfc/9051:6415
|
|
MediaType, MediaSubtype string
|
|
BodyFields BodyFields
|
|
Envelope Envelope
|
|
Bodystructure any // One of the BodyType*
|
|
Lines int64
|
|
Ext *BodyExtension1Part
|
|
}
|
|
|
|
// BodyTypeText represents a text part as a body structure, used in a FETCH
|
|
// response.
|
|
type BodyTypeText struct {
|
|
// ../rfc/9051:6418
|
|
MediaType, MediaSubtype string
|
|
BodyFields BodyFields
|
|
Lines int64
|
|
Ext *BodyExtension1Part
|
|
}
|
|
|
|
// BodyExtension1Part has the extensible form fields of a BODYSTRUCTURE for
|
|
// multiparts.
|
|
type BodyExtensionMpart struct {
|
|
// ../rfc/9051:5986 ../rfc/3501:4161 ../rfc/9051:6371 ../rfc/3501:4599
|
|
Params [][2]string
|
|
Disposition string
|
|
DispositionParams [][2]string
|
|
Language []string
|
|
Location string
|
|
More []BodyExtension
|
|
}
|
|
|
|
// BodyExtension1Part has the extensible form fields of a BODYSTRUCTURE for
|
|
// non-multiparts.
|
|
type BodyExtension1Part struct {
|
|
// ../rfc/9051:6023 ../rfc/3501:4191 ../rfc/9051:6366 ../rfc/3501:4584
|
|
MD5 string
|
|
Disposition string
|
|
DispositionParams [][2]string
|
|
Language []string
|
|
Location string
|
|
More []BodyExtension
|
|
}
|
|
|
|
// BodyExtension has the additional extension fields for future expansion of
|
|
// extensions.
|
|
type BodyExtension struct {
|
|
String *string
|
|
Number *int64
|
|
More []BodyExtension
|
|
}
|
|
|
|
// "BINARY" fetch response.
|
|
type FetchBinary struct {
|
|
RespAttr string
|
|
Parts []uint32 // Can be nil.
|
|
Data string
|
|
}
|
|
|
|
func (f FetchBinary) Attr() string { return f.RespAttr }
|
|
|
|
// "BINARY.SIZE" fetch response.
|
|
type FetchBinarySize struct {
|
|
RespAttr string
|
|
Parts []uint32
|
|
Size int64
|
|
}
|
|
|
|
func (f FetchBinarySize) Attr() string { return f.RespAttr }
|
|
|
|
// "UID" fetch response.
|
|
type FetchUID uint32
|
|
|
|
func (f FetchUID) Attr() string { return "UID" }
|
|
|
|
// "MODSEQ" fetch response.
|
|
type FetchModSeq int64
|
|
|
|
func (f FetchModSeq) Attr() string { return "MODSEQ" }
|
|
|
|
// "PREVIEW" fetch response.
|
|
type FetchPreview struct {
|
|
Preview *string
|
|
}
|
|
|
|
// ../rfc/8970:146
|
|
|
|
func (f FetchPreview) Attr() string { return "PREVIEW" }
|