package imapclient import ( "bufio" "fmt" "strings" ) // 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 ) // 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 UID uint32 } func (c CodeAppendUID) CodeString() string { return fmt.Sprintf("APPENDUID %d %d", c.UIDValidity, c.UID) } // "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) } // 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. {}\r\n. 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 } 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/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 { // ../rfc/9051:6546 Correlator string 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{r: bufio.NewReader(strings.NewReader(s))} defer c.recover(&rerr) ns = c.xsequenceSet() 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 string // todo: parsed time func (f FetchInternalDate) Attr() string { return "INTERNALDATE" } // "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" }