Initialize module and dependencies
This commit is contained in:
19
vendor/github.com/emersion/go-imap/v2/.build.yml
generated
vendored
Normal file
19
vendor/github.com/emersion/go-imap/v2/.build.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
image: alpine/latest
|
||||
packages:
|
||||
- dovecot
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/emersion/go-imap#v2
|
||||
tasks:
|
||||
- build: |
|
||||
cd go-imap
|
||||
go build -race -v ./...
|
||||
- test: |
|
||||
cd go-imap
|
||||
go test -race ./...
|
||||
- test-dovecot: |
|
||||
cd go-imap
|
||||
GOIMAP_TEST_DOVECOT=1 go test -race ./imapclient
|
||||
- gofmt: |
|
||||
cd go-imap
|
||||
test -z $(gofmt -l .)
|
||||
23
vendor/github.com/emersion/go-imap/v2/LICENSE
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/v2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 The Go-IMAP Authors
|
||||
Copyright (c) 2016 Proton Technologies AG
|
||||
Copyright (c) 2023 Simon Ser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
29
vendor/github.com/emersion/go-imap/v2/README.md
generated
vendored
Normal file
29
vendor/github.com/emersion/go-imap/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# go-imap
|
||||
|
||||
[](https://pkg.go.dev/github.com/emersion/go-imap/v2)
|
||||
|
||||
An [IMAP4rev2] library for Go.
|
||||
|
||||
> **Note**
|
||||
> This is the README for go-imap v2. This new major version is still in
|
||||
> development. For go-imap v1, see the [v1 branch].
|
||||
|
||||
## Usage
|
||||
|
||||
To add go-imap to your project, run:
|
||||
|
||||
go get github.com/emersion/go-imap/v2
|
||||
|
||||
Documentation and examples for the module are available here:
|
||||
|
||||
- [Client docs]
|
||||
- [Server docs]
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
[IMAP4rev2]: https://www.rfc-editor.org/rfc/rfc9051.html
|
||||
[v1 branch]: https://github.com/emersion/go-imap/tree/v1
|
||||
[Client docs]: https://pkg.go.dev/github.com/emersion/go-imap/v2/imapclient
|
||||
[Server docs]: https://pkg.go.dev/github.com/emersion/go-imap/v2/imapserver
|
||||
104
vendor/github.com/emersion/go-imap/v2/acl.go
generated
vendored
Normal file
104
vendor/github.com/emersion/go-imap/v2/acl.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IMAP4 ACL extension (RFC 2086)
|
||||
|
||||
// Right describes a set of operations controlled by the IMAP ACL extension.
|
||||
type Right byte
|
||||
|
||||
const (
|
||||
// Standard rights
|
||||
RightLookup = Right('l') // mailbox is visible to LIST/LSUB commands
|
||||
RightRead = Right('r') // SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH, COPY from mailbox
|
||||
RightSeen = Right('s') // keep seen/unseen information across sessions (STORE SEEN flag)
|
||||
RightWrite = Right('w') // STORE flags other than SEEN and DELETED
|
||||
RightInsert = Right('i') // perform APPEND, COPY into mailbox
|
||||
RightPost = Right('p') // send mail to submission address for mailbox, not enforced by IMAP4 itself
|
||||
RightCreate = Right('c') // CREATE new sub-mailboxes in any implementation-defined hierarchy
|
||||
RightDelete = Right('d') // STORE DELETED flag, perform EXPUNGE
|
||||
RightAdminister = Right('a') // perform SETACL
|
||||
)
|
||||
|
||||
// RightSetAll contains all standard rights.
|
||||
var RightSetAll = RightSet("lrswipcda")
|
||||
|
||||
// RightsIdentifier is an ACL identifier.
|
||||
type RightsIdentifier string
|
||||
|
||||
// RightsIdentifierAnyone is the universal identity (matches everyone).
|
||||
const RightsIdentifierAnyone = RightsIdentifier("anyone")
|
||||
|
||||
// NewRightsIdentifierUsername returns a rights identifier referring to a
|
||||
// username, checking for reserved values.
|
||||
func NewRightsIdentifierUsername(username string) (RightsIdentifier, error) {
|
||||
if username == string(RightsIdentifierAnyone) || strings.HasPrefix(username, "-") {
|
||||
return "", fmt.Errorf("imap: reserved rights identifier")
|
||||
}
|
||||
return RightsIdentifier(username), nil
|
||||
}
|
||||
|
||||
// RightModification indicates how to mutate a right set.
|
||||
type RightModification byte
|
||||
|
||||
const (
|
||||
RightModificationReplace = RightModification(0)
|
||||
RightModificationAdd = RightModification('+')
|
||||
RightModificationRemove = RightModification('-')
|
||||
)
|
||||
|
||||
// A RightSet is a set of rights.
|
||||
type RightSet []Right
|
||||
|
||||
// String returns a string representation of the right set.
|
||||
func (r RightSet) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// Add returns a new right set containing rights from both sets.
|
||||
func (r RightSet) Add(rights RightSet) RightSet {
|
||||
newRights := make(RightSet, len(r), len(r)+len(rights))
|
||||
copy(newRights, r)
|
||||
|
||||
for _, right := range rights {
|
||||
if !strings.ContainsRune(string(r), rune(right)) {
|
||||
newRights = append(newRights, right)
|
||||
}
|
||||
}
|
||||
|
||||
return newRights
|
||||
}
|
||||
|
||||
// Remove returns a new right set containing all rights in r except these in
|
||||
// the provided set.
|
||||
func (r RightSet) Remove(rights RightSet) RightSet {
|
||||
newRights := make(RightSet, 0, len(r))
|
||||
|
||||
for _, right := range r {
|
||||
if !strings.ContainsRune(string(rights), rune(right)) {
|
||||
newRights = append(newRights, right)
|
||||
}
|
||||
}
|
||||
|
||||
return newRights
|
||||
}
|
||||
|
||||
// Equal returns true if both right sets contain exactly the same rights.
|
||||
func (rs1 RightSet) Equal(rs2 RightSet) bool {
|
||||
for _, r := range rs1 {
|
||||
if !strings.ContainsRune(string(rs2), rune(r)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rs2 {
|
||||
if !strings.ContainsRune(string(rs1), rune(r)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/v2/append.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/v2/append.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppendOptions contains options for the APPEND command.
|
||||
type AppendOptions struct {
|
||||
Flags []Flag
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// AppendData is the data returned by an APPEND command.
|
||||
type AppendData struct {
|
||||
// requires UIDPLUS or IMAP4rev2
|
||||
UID UID
|
||||
UIDValidity uint32
|
||||
}
|
||||
212
vendor/github.com/emersion/go-imap/v2/capability.go
generated
vendored
Normal file
212
vendor/github.com/emersion/go-imap/v2/capability.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Cap represents an IMAP capability.
|
||||
type Cap string
|
||||
|
||||
// Registered capabilities.
|
||||
//
|
||||
// See: https://www.iana.org/assignments/imap-capabilities/
|
||||
const (
|
||||
CapIMAP4rev1 Cap = "IMAP4rev1" // RFC 3501
|
||||
CapIMAP4rev2 Cap = "IMAP4rev2" // RFC 9051
|
||||
|
||||
CapStartTLS Cap = "STARTTLS"
|
||||
CapLoginDisabled Cap = "LOGINDISABLED"
|
||||
|
||||
// Folded in IMAP4rev2
|
||||
CapNamespace Cap = "NAMESPACE" // RFC 2342
|
||||
CapUnselect Cap = "UNSELECT" // RFC 3691
|
||||
CapUIDPlus Cap = "UIDPLUS" // RFC 4315
|
||||
CapESearch Cap = "ESEARCH" // RFC 4731
|
||||
CapSearchRes Cap = "SEARCHRES" // RFC 5182
|
||||
CapEnable Cap = "ENABLE" // RFC 5161
|
||||
CapIdle Cap = "IDLE" // RFC 2177
|
||||
CapSASLIR Cap = "SASL-IR" // RFC 4959
|
||||
CapListExtended Cap = "LIST-EXTENDED" // RFC 5258
|
||||
CapListStatus Cap = "LIST-STATUS" // RFC 5819
|
||||
CapMove Cap = "MOVE" // RFC 6851
|
||||
CapLiteralMinus Cap = "LITERAL-" // RFC 7888
|
||||
CapStatusSize Cap = "STATUS=SIZE" // RFC 8438
|
||||
CapChildren Cap = "CHILDREN" // RFC 3348
|
||||
|
||||
CapACL Cap = "ACL" // RFC 4314
|
||||
CapAppendLimit Cap = "APPENDLIMIT" // RFC 7889
|
||||
CapBinary Cap = "BINARY" // RFC 3516
|
||||
CapCatenate Cap = "CATENATE" // RFC 4469
|
||||
CapCondStore Cap = "CONDSTORE" // RFC 7162
|
||||
CapConvert Cap = "CONVERT" // RFC 5259
|
||||
CapCreateSpecialUse Cap = "CREATE-SPECIAL-USE" // RFC 6154
|
||||
CapESort Cap = "ESORT" // RFC 5267
|
||||
CapFilters Cap = "FILTERS" // RFC 5466
|
||||
CapID Cap = "ID" // RFC 2971
|
||||
CapLanguage Cap = "LANGUAGE" // RFC 5255
|
||||
CapListMyRights Cap = "LIST-MYRIGHTS" // RFC 8440
|
||||
CapLiteralPlus Cap = "LITERAL+" // RFC 7888
|
||||
CapLoginReferrals Cap = "LOGIN-REFERRALS" // RFC 2221
|
||||
CapMailboxReferrals Cap = "MAILBOX-REFERRALS" // RFC 2193
|
||||
CapMetadata Cap = "METADATA" // RFC 5464
|
||||
CapMetadataServer Cap = "METADATA-SERVER" // RFC 5464
|
||||
CapMultiAppend Cap = "MULTIAPPEND" // RFC 3502
|
||||
CapMultiSearch Cap = "MULTISEARCH" // RFC 7377
|
||||
CapNotify Cap = "NOTIFY" // RFC 5465
|
||||
CapObjectID Cap = "OBJECTID" // RFC 8474
|
||||
CapPreview Cap = "PREVIEW" // RFC 8970
|
||||
CapQResync Cap = "QRESYNC" // RFC 7162
|
||||
CapQuota Cap = "QUOTA" // RFC 9208
|
||||
CapQuotaSet Cap = "QUOTASET" // RFC 9208
|
||||
CapReplace Cap = "REPLACE" // RFC 8508
|
||||
CapSaveDate Cap = "SAVEDATE" // RFC 8514
|
||||
CapSearchFuzzy Cap = "SEARCH=FUZZY" // RFC 6203
|
||||
CapSort Cap = "SORT" // RFC 5256
|
||||
CapSortDisplay Cap = "SORT=DISPLAY" // RFC 5957
|
||||
CapSpecialUse Cap = "SPECIAL-USE" // RFC 6154
|
||||
CapUnauthenticate Cap = "UNAUTHENTICATE" // RFC 8437
|
||||
CapURLPartial Cap = "URL-PARTIAL" // RFC 5550
|
||||
CapURLAuth Cap = "URLAUTH" // RFC 4467
|
||||
CapUTF8Accept Cap = "UTF8=ACCEPT" // RFC 6855
|
||||
CapUTF8Only Cap = "UTF8=ONLY" // RFC 6855
|
||||
CapWithin Cap = "WITHIN" // RFC 5032
|
||||
CapUIDOnly Cap = "UIDONLY" // RFC 9586
|
||||
CapListMetadata Cap = "LIST-METADATA" // RFC 9590
|
||||
CapInProgress Cap = "INPROGRESS" // RFC 9585
|
||||
)
|
||||
|
||||
var imap4rev2Caps = CapSet{
|
||||
CapNamespace: {},
|
||||
CapUnselect: {},
|
||||
CapUIDPlus: {},
|
||||
CapESearch: {},
|
||||
CapSearchRes: {},
|
||||
CapEnable: {},
|
||||
CapIdle: {},
|
||||
CapSASLIR: {},
|
||||
CapListExtended: {},
|
||||
CapListStatus: {},
|
||||
CapMove: {},
|
||||
CapLiteralMinus: {},
|
||||
CapStatusSize: {},
|
||||
CapChildren: {},
|
||||
}
|
||||
|
||||
// AuthCap returns the capability name for an SASL authentication mechanism.
|
||||
func AuthCap(mechanism string) Cap {
|
||||
return Cap("AUTH=" + mechanism)
|
||||
}
|
||||
|
||||
// CapSet is a set of capabilities.
|
||||
type CapSet map[Cap]struct{}
|
||||
|
||||
func (set CapSet) has(c Cap) bool {
|
||||
_, ok := set[c]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (set CapSet) Copy() CapSet {
|
||||
newSet := make(CapSet, len(set))
|
||||
for c := range set {
|
||||
newSet[c] = struct{}{}
|
||||
}
|
||||
return newSet
|
||||
}
|
||||
|
||||
// Has checks whether a capability is supported.
|
||||
//
|
||||
// Some capabilities are implied by others, as such Has may return true even if
|
||||
// the capability is not in the map.
|
||||
func (set CapSet) Has(c Cap) bool {
|
||||
if set.has(c) {
|
||||
return true
|
||||
}
|
||||
|
||||
if set.has(CapIMAP4rev2) && imap4rev2Caps.has(c) {
|
||||
return true
|
||||
}
|
||||
|
||||
if c == CapLiteralMinus && set.has(CapLiteralPlus) {
|
||||
return true
|
||||
}
|
||||
if c == CapCondStore && set.has(CapQResync) {
|
||||
return true
|
||||
}
|
||||
if c == CapUTF8Accept && set.has(CapUTF8Only) {
|
||||
return true
|
||||
}
|
||||
if c == CapAppendLimit {
|
||||
_, ok := set.AppendLimit()
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthMechanisms returns the list of supported SASL mechanisms for
|
||||
// authentication.
|
||||
func (set CapSet) AuthMechanisms() []string {
|
||||
var l []string
|
||||
for c := range set {
|
||||
if !strings.HasPrefix(string(c), "AUTH=") {
|
||||
continue
|
||||
}
|
||||
mech := strings.TrimPrefix(string(c), "AUTH=")
|
||||
l = append(l, mech)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// AppendLimit checks the APPENDLIMIT capability.
|
||||
//
|
||||
// If the server supports APPENDLIMIT, ok is true. If the server doesn't have
|
||||
// the same upload limit for all mailboxes, limit is nil and per-mailbox
|
||||
// limits must be queried via STATUS.
|
||||
func (set CapSet) AppendLimit() (limit *uint32, ok bool) {
|
||||
if set.has(CapAppendLimit) {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
for c := range set {
|
||||
if !strings.HasPrefix(string(c), "APPENDLIMIT=") {
|
||||
continue
|
||||
}
|
||||
|
||||
limitStr := strings.TrimPrefix(string(c), "APPENDLIMIT=")
|
||||
limit64, err := strconv.ParseUint(limitStr, 10, 32)
|
||||
if err == nil && limit64 > 0 {
|
||||
limit32 := uint32(limit64)
|
||||
return &limit32, true
|
||||
}
|
||||
}
|
||||
|
||||
limit32 := ^uint32(0)
|
||||
return &limit32, false
|
||||
}
|
||||
|
||||
// QuotaResourceTypes returns the list of supported QUOTA resource types.
|
||||
func (set CapSet) QuotaResourceTypes() []QuotaResourceType {
|
||||
var l []QuotaResourceType
|
||||
for c := range set {
|
||||
if !strings.HasPrefix(string(c), "QUOTA=RES-") {
|
||||
continue
|
||||
}
|
||||
t := strings.TrimPrefix(string(c), "QUOTA=RES-")
|
||||
l = append(l, QuotaResourceType(t))
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// ThreadAlgorithms returns the list of supported threading algorithms.
|
||||
func (set CapSet) ThreadAlgorithms() []ThreadAlgorithm {
|
||||
var l []ThreadAlgorithm
|
||||
for c := range set {
|
||||
if !strings.HasPrefix(string(c), "THREAD=") {
|
||||
continue
|
||||
}
|
||||
alg := strings.TrimPrefix(string(c), "THREAD=")
|
||||
l = append(l, ThreadAlgorithm(alg))
|
||||
}
|
||||
return l
|
||||
}
|
||||
9
vendor/github.com/emersion/go-imap/v2/copy.go
generated
vendored
Normal file
9
vendor/github.com/emersion/go-imap/v2/copy.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package imap
|
||||
|
||||
// CopyData is the data returned by a COPY command.
|
||||
type CopyData struct {
|
||||
// requires UIDPLUS or IMAP4rev2
|
||||
UIDValidity uint32
|
||||
SourceUIDs UIDSet
|
||||
DestUIDs UIDSet
|
||||
}
|
||||
6
vendor/github.com/emersion/go-imap/v2/create.go
generated
vendored
Normal file
6
vendor/github.com/emersion/go-imap/v2/create.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package imap
|
||||
|
||||
// CreateOptions contains options for the CREATE command.
|
||||
type CreateOptions struct {
|
||||
SpecialUse []MailboxAttr // requires CREATE-SPECIAL-USE
|
||||
}
|
||||
284
vendor/github.com/emersion/go-imap/v2/fetch.go
generated
vendored
Normal file
284
vendor/github.com/emersion/go-imap/v2/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FetchOptions contains options for the FETCH command.
|
||||
type FetchOptions struct {
|
||||
// Fields to fetch
|
||||
BodyStructure *FetchItemBodyStructure
|
||||
Envelope bool
|
||||
Flags bool
|
||||
InternalDate bool
|
||||
RFC822Size bool
|
||||
UID bool
|
||||
BodySection []*FetchItemBodySection
|
||||
BinarySection []*FetchItemBinarySection // requires IMAP4rev2 or BINARY
|
||||
BinarySectionSize []*FetchItemBinarySectionSize // requires IMAP4rev2 or BINARY
|
||||
ModSeq bool // requires CONDSTORE
|
||||
|
||||
ChangedSince uint64 // requires CONDSTORE
|
||||
}
|
||||
|
||||
// FetchItemBodyStructure contains FETCH options for the body structure.
|
||||
type FetchItemBodyStructure struct {
|
||||
Extended bool
|
||||
}
|
||||
|
||||
// PartSpecifier describes whether to fetch a part's header, body, or both.
|
||||
type PartSpecifier string
|
||||
|
||||
const (
|
||||
PartSpecifierNone PartSpecifier = ""
|
||||
PartSpecifierHeader PartSpecifier = "HEADER"
|
||||
PartSpecifierMIME PartSpecifier = "MIME"
|
||||
PartSpecifierText PartSpecifier = "TEXT"
|
||||
)
|
||||
|
||||
// SectionPartial describes a byte range when fetching a message's payload.
|
||||
type SectionPartial struct {
|
||||
Offset, Size int64
|
||||
}
|
||||
|
||||
// FetchItemBodySection is a FETCH BODY[] data item.
|
||||
//
|
||||
// To fetch the whole body of a message, use the zero FetchItemBodySection:
|
||||
//
|
||||
// imap.FetchItemBodySection{}
|
||||
//
|
||||
// To fetch only a specific part, use the Part field:
|
||||
//
|
||||
// imap.FetchItemBodySection{Part: []int{1, 2, 3}}
|
||||
//
|
||||
// To fetch only the header of the message, use the Specifier field:
|
||||
//
|
||||
// imap.FetchItemBodySection{Specifier: imap.PartSpecifierHeader}
|
||||
type FetchItemBodySection struct {
|
||||
Specifier PartSpecifier
|
||||
Part []int
|
||||
HeaderFields []string
|
||||
HeaderFieldsNot []string
|
||||
Partial *SectionPartial
|
||||
Peek bool
|
||||
}
|
||||
|
||||
// FetchItemBinarySection is a FETCH BINARY[] data item.
|
||||
type FetchItemBinarySection struct {
|
||||
Part []int
|
||||
Partial *SectionPartial
|
||||
Peek bool
|
||||
}
|
||||
|
||||
// FetchItemBinarySectionSize is a FETCH BINARY.SIZE[] data item.
|
||||
type FetchItemBinarySectionSize struct {
|
||||
Part []int
|
||||
}
|
||||
|
||||
// Envelope is the envelope structure of a message.
|
||||
//
|
||||
// The subject and addresses are UTF-8 (ie, not in their encoded form). The
|
||||
// In-Reply-To and Message-ID values contain message identifiers without angle
|
||||
// brackets.
|
||||
type Envelope struct {
|
||||
Date time.Time
|
||||
Subject string
|
||||
From []Address
|
||||
Sender []Address
|
||||
ReplyTo []Address
|
||||
To []Address
|
||||
Cc []Address
|
||||
Bcc []Address
|
||||
InReplyTo []string
|
||||
MessageID string
|
||||
}
|
||||
|
||||
// Address represents a sender or recipient of a message.
|
||||
type Address struct {
|
||||
Name string
|
||||
Mailbox string
|
||||
Host string
|
||||
}
|
||||
|
||||
// Addr returns the e-mail address in the form "foo@example.org".
|
||||
//
|
||||
// If the address is a start or end of group, the empty string is returned.
|
||||
func (addr *Address) Addr() string {
|
||||
if addr.Mailbox == "" || addr.Host == "" {
|
||||
return ""
|
||||
}
|
||||
return addr.Mailbox + "@" + addr.Host
|
||||
}
|
||||
|
||||
// IsGroupStart returns true if this address is a start of group marker.
|
||||
//
|
||||
// In that case, Mailbox contains the group name phrase.
|
||||
func (addr *Address) IsGroupStart() bool {
|
||||
return addr.Host == "" && addr.Mailbox != ""
|
||||
}
|
||||
|
||||
// IsGroupEnd returns true if this address is a end of group marker.
|
||||
func (addr *Address) IsGroupEnd() bool {
|
||||
return addr.Host == "" && addr.Mailbox == ""
|
||||
}
|
||||
|
||||
// BodyStructure describes the body structure of a message.
|
||||
//
|
||||
// A BodyStructure value is either a *BodyStructureSinglePart or a
|
||||
// *BodyStructureMultiPart.
|
||||
type BodyStructure interface {
|
||||
// MediaType returns the MIME type of this body structure, e.g. "text/plain".
|
||||
MediaType() string
|
||||
// Walk walks the body structure tree, calling f for each part in the tree,
|
||||
// including bs itself. The parts are visited in DFS pre-order.
|
||||
Walk(f BodyStructureWalkFunc)
|
||||
// Disposition returns the body structure disposition, if available.
|
||||
Disposition() *BodyStructureDisposition
|
||||
|
||||
bodyStructure()
|
||||
}
|
||||
|
||||
var (
|
||||
_ BodyStructure = (*BodyStructureSinglePart)(nil)
|
||||
_ BodyStructure = (*BodyStructureMultiPart)(nil)
|
||||
)
|
||||
|
||||
// BodyStructureSinglePart is a body structure with a single part.
|
||||
type BodyStructureSinglePart struct {
|
||||
Type, Subtype string
|
||||
Params map[string]string
|
||||
ID string
|
||||
Description string
|
||||
Encoding string
|
||||
Size uint32
|
||||
|
||||
MessageRFC822 *BodyStructureMessageRFC822 // only for "message/rfc822"
|
||||
Text *BodyStructureText // only for "text/*"
|
||||
Extended *BodyStructureSinglePartExt
|
||||
}
|
||||
|
||||
func (bs *BodyStructureSinglePart) MediaType() string {
|
||||
return strings.ToLower(bs.Type) + "/" + strings.ToLower(bs.Subtype)
|
||||
}
|
||||
|
||||
func (bs *BodyStructureSinglePart) Walk(f BodyStructureWalkFunc) {
|
||||
f([]int{1}, bs)
|
||||
}
|
||||
|
||||
func (bs *BodyStructureSinglePart) Disposition() *BodyStructureDisposition {
|
||||
if bs.Extended == nil {
|
||||
return nil
|
||||
}
|
||||
return bs.Extended.Disposition
|
||||
}
|
||||
|
||||
// Filename decodes the body structure's filename, if any.
|
||||
func (bs *BodyStructureSinglePart) Filename() string {
|
||||
var filename string
|
||||
if bs.Extended != nil && bs.Extended.Disposition != nil {
|
||||
filename = bs.Extended.Disposition.Params["filename"]
|
||||
}
|
||||
if filename == "" {
|
||||
// Note: using "name" in Content-Type is discouraged
|
||||
filename = bs.Params["name"]
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
func (*BodyStructureSinglePart) bodyStructure() {}
|
||||
|
||||
// BodyStructureMessageRFC822 contains metadata specific to RFC 822 parts for
|
||||
// BodyStructureSinglePart.
|
||||
type BodyStructureMessageRFC822 struct {
|
||||
Envelope *Envelope
|
||||
BodyStructure BodyStructure
|
||||
NumLines int64
|
||||
}
|
||||
|
||||
// BodyStructureText contains metadata specific to text parts for
|
||||
// BodyStructureSinglePart.
|
||||
type BodyStructureText struct {
|
||||
NumLines int64
|
||||
}
|
||||
|
||||
// BodyStructureSinglePartExt contains extended body structure data for
|
||||
// BodyStructureSinglePart.
|
||||
type BodyStructureSinglePartExt struct {
|
||||
Disposition *BodyStructureDisposition
|
||||
Language []string
|
||||
Location string
|
||||
}
|
||||
|
||||
// BodyStructureMultiPart is a body structure with multiple parts.
|
||||
type BodyStructureMultiPart struct {
|
||||
Children []BodyStructure
|
||||
Subtype string
|
||||
|
||||
Extended *BodyStructureMultiPartExt
|
||||
}
|
||||
|
||||
func (bs *BodyStructureMultiPart) MediaType() string {
|
||||
return "multipart/" + strings.ToLower(bs.Subtype)
|
||||
}
|
||||
|
||||
func (bs *BodyStructureMultiPart) Walk(f BodyStructureWalkFunc) {
|
||||
bs.walk(f, nil)
|
||||
}
|
||||
|
||||
func (bs *BodyStructureMultiPart) walk(f BodyStructureWalkFunc, path []int) {
|
||||
if !f(path, bs) {
|
||||
return
|
||||
}
|
||||
|
||||
pathBuf := make([]int, len(path))
|
||||
copy(pathBuf, path)
|
||||
for i, part := range bs.Children {
|
||||
num := i + 1
|
||||
partPath := append(pathBuf, num)
|
||||
|
||||
switch part := part.(type) {
|
||||
case *BodyStructureSinglePart:
|
||||
f(partPath, part)
|
||||
case *BodyStructureMultiPart:
|
||||
part.walk(f, partPath)
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported body structure type %T", part))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *BodyStructureMultiPart) Disposition() *BodyStructureDisposition {
|
||||
if bs.Extended == nil {
|
||||
return nil
|
||||
}
|
||||
return bs.Extended.Disposition
|
||||
}
|
||||
|
||||
func (*BodyStructureMultiPart) bodyStructure() {}
|
||||
|
||||
// BodyStructureMultiPartExt contains extended body structure data for
|
||||
// BodyStructureMultiPart.
|
||||
type BodyStructureMultiPartExt struct {
|
||||
Params map[string]string
|
||||
Disposition *BodyStructureDisposition
|
||||
Language []string
|
||||
Location string
|
||||
}
|
||||
|
||||
// BodyStructureDisposition describes the content disposition of a part
|
||||
// (specified in the Content-Disposition header field).
|
||||
type BodyStructureDisposition struct {
|
||||
Value string
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
// BodyStructureWalkFunc is a function called for each body structure visited
|
||||
// by BodyStructure.Walk.
|
||||
//
|
||||
// The path argument contains the IMAP part path.
|
||||
//
|
||||
// The function should return true to visit all of the part's children or false
|
||||
// to skip them.
|
||||
type BodyStructureWalkFunc func(path []int, part BodyStructure) (walkChildren bool)
|
||||
15
vendor/github.com/emersion/go-imap/v2/id.go
generated
vendored
Normal file
15
vendor/github.com/emersion/go-imap/v2/id.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package imap
|
||||
|
||||
type IDData struct {
|
||||
Name string
|
||||
Version string
|
||||
OS string
|
||||
OSVersion string
|
||||
Vendor string
|
||||
SupportURL string
|
||||
Address string
|
||||
Date string
|
||||
Command string
|
||||
Arguments string
|
||||
Environment string
|
||||
}
|
||||
105
vendor/github.com/emersion/go-imap/v2/imap.go
generated
vendored
Normal file
105
vendor/github.com/emersion/go-imap/v2/imap.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Package imap implements IMAP4rev2.
|
||||
//
|
||||
// IMAP4rev2 is defined in RFC 9051.
|
||||
//
|
||||
// This package contains types and functions common to both the client and
|
||||
// server. See the imapclient and imapserver sub-packages.
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ConnState describes the connection state.
|
||||
//
|
||||
// See RFC 9051 section 3.
|
||||
type ConnState int
|
||||
|
||||
const (
|
||||
ConnStateNone ConnState = iota
|
||||
ConnStateNotAuthenticated
|
||||
ConnStateAuthenticated
|
||||
ConnStateSelected
|
||||
ConnStateLogout
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (state ConnState) String() string {
|
||||
switch state {
|
||||
case ConnStateNone:
|
||||
return "none"
|
||||
case ConnStateNotAuthenticated:
|
||||
return "not authenticated"
|
||||
case ConnStateAuthenticated:
|
||||
return "authenticated"
|
||||
case ConnStateSelected:
|
||||
return "selected"
|
||||
case ConnStateLogout:
|
||||
return "logout"
|
||||
default:
|
||||
panic(fmt.Errorf("imap: unknown connection state %v", int(state)))
|
||||
}
|
||||
}
|
||||
|
||||
// MailboxAttr is a mailbox attribute.
|
||||
//
|
||||
// Mailbox attributes are defined in RFC 9051 section 7.3.1.
|
||||
type MailboxAttr string
|
||||
|
||||
const (
|
||||
// Base attributes
|
||||
MailboxAttrNonExistent MailboxAttr = "\\NonExistent"
|
||||
MailboxAttrNoInferiors MailboxAttr = "\\Noinferiors"
|
||||
MailboxAttrNoSelect MailboxAttr = "\\Noselect"
|
||||
MailboxAttrHasChildren MailboxAttr = "\\HasChildren"
|
||||
MailboxAttrHasNoChildren MailboxAttr = "\\HasNoChildren"
|
||||
MailboxAttrMarked MailboxAttr = "\\Marked"
|
||||
MailboxAttrUnmarked MailboxAttr = "\\Unmarked"
|
||||
MailboxAttrSubscribed MailboxAttr = "\\Subscribed"
|
||||
MailboxAttrRemote MailboxAttr = "\\Remote"
|
||||
|
||||
// Role (aka. "special-use") attributes
|
||||
MailboxAttrAll MailboxAttr = "\\All"
|
||||
MailboxAttrArchive MailboxAttr = "\\Archive"
|
||||
MailboxAttrDrafts MailboxAttr = "\\Drafts"
|
||||
MailboxAttrFlagged MailboxAttr = "\\Flagged"
|
||||
MailboxAttrJunk MailboxAttr = "\\Junk"
|
||||
MailboxAttrSent MailboxAttr = "\\Sent"
|
||||
MailboxAttrTrash MailboxAttr = "\\Trash"
|
||||
MailboxAttrImportant MailboxAttr = "\\Important" // RFC 8457
|
||||
)
|
||||
|
||||
// Flag is a message flag.
|
||||
//
|
||||
// Message flags are defined in RFC 9051 section 2.3.2.
|
||||
type Flag string
|
||||
|
||||
const (
|
||||
// System flags
|
||||
FlagSeen Flag = "\\Seen"
|
||||
FlagAnswered Flag = "\\Answered"
|
||||
FlagFlagged Flag = "\\Flagged"
|
||||
FlagDeleted Flag = "\\Deleted"
|
||||
FlagDraft Flag = "\\Draft"
|
||||
|
||||
// Widely used flags
|
||||
FlagForwarded Flag = "$Forwarded"
|
||||
FlagMDNSent Flag = "$MDNSent" // Message Disposition Notification sent
|
||||
FlagJunk Flag = "$Junk"
|
||||
FlagNotJunk Flag = "$NotJunk"
|
||||
FlagPhishing Flag = "$Phishing"
|
||||
FlagImportant Flag = "$Important" // RFC 8457
|
||||
|
||||
// Permanent flags
|
||||
FlagWildcard Flag = "\\*"
|
||||
)
|
||||
|
||||
// LiteralReader is a reader for IMAP literals.
|
||||
type LiteralReader interface {
|
||||
io.Reader
|
||||
Size() int64
|
||||
}
|
||||
|
||||
// UID is a message unique identifier.
|
||||
type UID uint32
|
||||
138
vendor/github.com/emersion/go-imap/v2/imapclient/acl.go
generated
vendored
Normal file
138
vendor/github.com/emersion/go-imap/v2/imapclient/acl.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// MyRights sends a MYRIGHTS command.
|
||||
//
|
||||
// This command requires support for the ACL extension.
|
||||
func (c *Client) MyRights(mailbox string) *MyRightsCommand {
|
||||
cmd := &MyRightsCommand{}
|
||||
enc := c.beginCommand("MYRIGHTS", cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetACL sends a SETACL command.
|
||||
//
|
||||
// This command requires support for the ACL extension.
|
||||
func (c *Client) SetACL(mailbox string, ri imap.RightsIdentifier, rm imap.RightModification, rs imap.RightSet) *SetACLCommand {
|
||||
cmd := &SetACLCommand{}
|
||||
enc := c.beginCommand("SETACL", cmd)
|
||||
enc.SP().Mailbox(mailbox).SP().String(string(ri)).SP()
|
||||
enc.String(internal.FormatRights(rm, rs))
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetACLCommand is a SETACL command.
|
||||
type SetACLCommand struct {
|
||||
commandBase
|
||||
}
|
||||
|
||||
func (cmd *SetACLCommand) Wait() error {
|
||||
return cmd.wait()
|
||||
}
|
||||
|
||||
// GetACL sends a GETACL command.
|
||||
//
|
||||
// This command requires support for the ACL extension.
|
||||
func (c *Client) GetACL(mailbox string) *GetACLCommand {
|
||||
cmd := &GetACLCommand{}
|
||||
enc := c.beginCommand("GETACL", cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetACLCommand is a GETACL command.
|
||||
type GetACLCommand struct {
|
||||
commandBase
|
||||
data GetACLData
|
||||
}
|
||||
|
||||
func (cmd *GetACLCommand) Wait() (*GetACLData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
func (c *Client) handleMyRights() error {
|
||||
data, err := readMyRights(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in myrights-response: %v", err)
|
||||
}
|
||||
if cmd := findPendingCmdByType[*MyRightsCommand](c); cmd != nil {
|
||||
cmd.data = *data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleGetACL() error {
|
||||
data, err := readGetACL(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in getacl-response: %v", err)
|
||||
}
|
||||
if cmd := findPendingCmdByType[*GetACLCommand](c); cmd != nil {
|
||||
cmd.data = *data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MyRightsCommand is a MYRIGHTS command.
|
||||
type MyRightsCommand struct {
|
||||
commandBase
|
||||
data MyRightsData
|
||||
}
|
||||
|
||||
func (cmd *MyRightsCommand) Wait() (*MyRightsData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
// MyRightsData is the data returned by the MYRIGHTS command.
|
||||
type MyRightsData struct {
|
||||
Mailbox string
|
||||
Rights imap.RightSet
|
||||
}
|
||||
|
||||
func readMyRights(dec *imapwire.Decoder) (*MyRightsData, error) {
|
||||
var (
|
||||
rights string
|
||||
data MyRightsData
|
||||
)
|
||||
if !dec.ExpectMailbox(&data.Mailbox) || !dec.ExpectSP() || !dec.ExpectAString(&rights) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
data.Rights = imap.RightSet(rights)
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// GetACLData is the data returned by the GETACL command.
|
||||
type GetACLData struct {
|
||||
Mailbox string
|
||||
Rights map[imap.RightsIdentifier]imap.RightSet
|
||||
}
|
||||
|
||||
func readGetACL(dec *imapwire.Decoder) (*GetACLData, error) {
|
||||
data := &GetACLData{Rights: make(map[imap.RightsIdentifier]imap.RightSet)}
|
||||
|
||||
if !dec.ExpectMailbox(&data.Mailbox) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
for dec.SP() {
|
||||
var rsStr, riStr string
|
||||
if !dec.ExpectAString(&riStr) || !dec.ExpectSP() || !dec.ExpectAString(&rsStr) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
data.Rights[imap.RightsIdentifier(riStr)] = imap.RightSet(rsStr)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
58
vendor/github.com/emersion/go-imap/v2/imapclient/append.go
generated
vendored
Normal file
58
vendor/github.com/emersion/go-imap/v2/imapclient/append.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
)
|
||||
|
||||
// Append sends an APPEND command.
|
||||
//
|
||||
// The caller must call AppendCommand.Close.
|
||||
//
|
||||
// The options are optional.
|
||||
func (c *Client) Append(mailbox string, size int64, options *imap.AppendOptions) *AppendCommand {
|
||||
cmd := &AppendCommand{}
|
||||
cmd.enc = c.beginCommand("APPEND", cmd)
|
||||
cmd.enc.SP().Mailbox(mailbox).SP()
|
||||
if options != nil && len(options.Flags) > 0 {
|
||||
cmd.enc.List(len(options.Flags), func(i int) {
|
||||
cmd.enc.Flag(options.Flags[i])
|
||||
}).SP()
|
||||
}
|
||||
if options != nil && !options.Time.IsZero() {
|
||||
cmd.enc.String(options.Time.Format(internal.DateTimeLayout)).SP()
|
||||
}
|
||||
// TODO: literal8 for BINARY
|
||||
// TODO: UTF8 data ext for UTF8=ACCEPT, with literal8
|
||||
cmd.wc = cmd.enc.Literal(size)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AppendCommand is an APPEND command.
|
||||
//
|
||||
// Callers must write the message contents, then call Close.
|
||||
type AppendCommand struct {
|
||||
commandBase
|
||||
enc *commandEncoder
|
||||
wc io.WriteCloser
|
||||
data imap.AppendData
|
||||
}
|
||||
|
||||
func (cmd *AppendCommand) Write(b []byte) (int, error) {
|
||||
return cmd.wc.Write(b)
|
||||
}
|
||||
|
||||
func (cmd *AppendCommand) Close() error {
|
||||
err := cmd.wc.Close()
|
||||
if cmd.enc != nil {
|
||||
cmd.enc.end()
|
||||
cmd.enc = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (cmd *AppendCommand) Wait() (*imap.AppendData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
100
vendor/github.com/emersion/go-imap/v2/imapclient/authenticate.go
generated
vendored
Normal file
100
vendor/github.com/emersion/go-imap/v2/imapclient/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-sasl"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
)
|
||||
|
||||
// Authenticate sends an AUTHENTICATE command.
|
||||
//
|
||||
// Unlike other commands, this method blocks until the SASL exchange completes.
|
||||
func (c *Client) Authenticate(saslClient sasl.Client) error {
|
||||
mech, initialResp, err := saslClient.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// c.Caps may send a CAPABILITY command, so check it before c.beginCommand
|
||||
var hasSASLIR bool
|
||||
if initialResp != nil {
|
||||
hasSASLIR = c.Caps().Has(imap.CapSASLIR)
|
||||
}
|
||||
|
||||
cmd := &authenticateCommand{}
|
||||
contReq := c.registerContReq(cmd)
|
||||
enc := c.beginCommand("AUTHENTICATE", cmd)
|
||||
enc.SP().Atom(mech)
|
||||
if initialResp != nil && hasSASLIR {
|
||||
enc.SP().Atom(internal.EncodeSASL(initialResp))
|
||||
initialResp = nil
|
||||
}
|
||||
enc.flush()
|
||||
defer enc.end()
|
||||
|
||||
for {
|
||||
challengeStr, err := contReq.Wait()
|
||||
if err != nil {
|
||||
return cmd.wait()
|
||||
}
|
||||
|
||||
if challengeStr == "" {
|
||||
if initialResp == nil {
|
||||
return fmt.Errorf("imapclient: server requested SASL initial response, but we don't have one")
|
||||
}
|
||||
|
||||
contReq = c.registerContReq(cmd)
|
||||
if err := c.writeSASLResp(initialResp); err != nil {
|
||||
return err
|
||||
}
|
||||
initialResp = nil
|
||||
continue
|
||||
}
|
||||
|
||||
challenge, err := internal.DecodeSASL(challengeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := saslClient.Next(challenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contReq = c.registerContReq(cmd)
|
||||
if err := c.writeSASLResp(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type authenticateCommand struct {
|
||||
commandBase
|
||||
}
|
||||
|
||||
func (c *Client) writeSASLResp(resp []byte) error {
|
||||
respStr := internal.EncodeSASL(resp)
|
||||
if _, err := c.bw.WriteString(respStr + "\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unauthenticate sends an UNAUTHENTICATE command.
|
||||
//
|
||||
// This command requires support for the UNAUTHENTICATE extension.
|
||||
func (c *Client) Unauthenticate() *Command {
|
||||
cmd := &unauthenticateCommand{}
|
||||
c.beginCommand("UNAUTHENTICATE", cmd).end()
|
||||
return &cmd.Command
|
||||
}
|
||||
|
||||
type unauthenticateCommand struct {
|
||||
Command
|
||||
}
|
||||
56
vendor/github.com/emersion/go-imap/v2/imapclient/capability.go
generated
vendored
Normal file
56
vendor/github.com/emersion/go-imap/v2/imapclient/capability.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// Capability sends a CAPABILITY command.
|
||||
func (c *Client) Capability() *CapabilityCommand {
|
||||
cmd := &CapabilityCommand{}
|
||||
c.beginCommand("CAPABILITY", cmd).end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleCapability() error {
|
||||
caps, err := readCapabilities(c.dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setCaps(caps)
|
||||
if cmd := findPendingCmdByType[*CapabilityCommand](c); cmd != nil {
|
||||
cmd.caps = caps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CapabilityCommand is a CAPABILITY command.
|
||||
type CapabilityCommand struct {
|
||||
commandBase
|
||||
caps imap.CapSet
|
||||
}
|
||||
|
||||
func (cmd *CapabilityCommand) Wait() (imap.CapSet, error) {
|
||||
err := cmd.wait()
|
||||
return cmd.caps, err
|
||||
}
|
||||
|
||||
func readCapabilities(dec *imapwire.Decoder) (imap.CapSet, error) {
|
||||
caps := make(imap.CapSet)
|
||||
for dec.SP() {
|
||||
// Some IMAP servers send multiple SP between caps:
|
||||
// https://github.com/emersion/go-imap/pull/652
|
||||
for dec.SP() {
|
||||
}
|
||||
|
||||
cap, err := internal.ExpectCap(dec)
|
||||
if err != nil {
|
||||
return caps, fmt.Errorf("in capability-data: %w", err)
|
||||
}
|
||||
caps[cap] = struct{}{}
|
||||
}
|
||||
return caps, nil
|
||||
}
|
||||
1234
vendor/github.com/emersion/go-imap/v2/imapclient/client.go
generated
vendored
Normal file
1234
vendor/github.com/emersion/go-imap/v2/imapclient/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37
vendor/github.com/emersion/go-imap/v2/imapclient/copy.go
generated
vendored
Normal file
37
vendor/github.com/emersion/go-imap/v2/imapclient/copy.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// Copy sends a COPY command.
|
||||
func (c *Client) Copy(numSet imap.NumSet, mailbox string) *CopyCommand {
|
||||
cmd := &CopyCommand{}
|
||||
enc := c.beginCommand(uidCmdName("COPY", imapwire.NumSetKind(numSet)), cmd)
|
||||
enc.SP().NumSet(numSet).SP().Mailbox(mailbox)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CopyCommand is a COPY command.
|
||||
type CopyCommand struct {
|
||||
commandBase
|
||||
data imap.CopyData
|
||||
}
|
||||
|
||||
func (cmd *CopyCommand) Wait() (*imap.CopyData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
func readRespCodeCopyUID(dec *imapwire.Decoder) (uidValidity uint32, srcUIDs, dstUIDs imap.UIDSet, err error) {
|
||||
if !dec.ExpectNumber(&uidValidity) || !dec.ExpectSP() || !dec.ExpectUIDSet(&srcUIDs) || !dec.ExpectSP() || !dec.ExpectUIDSet(&dstUIDs) {
|
||||
return 0, nil, nil, dec.Err()
|
||||
}
|
||||
if srcUIDs.Dynamic() || dstUIDs.Dynamic() {
|
||||
return 0, nil, nil, fmt.Errorf("imapclient: server returned dynamic number set in COPYUID response")
|
||||
}
|
||||
return uidValidity, srcUIDs, dstUIDs, nil
|
||||
}
|
||||
21
vendor/github.com/emersion/go-imap/v2/imapclient/create.go
generated
vendored
Normal file
21
vendor/github.com/emersion/go-imap/v2/imapclient/create.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
// Create sends a CREATE command.
|
||||
//
|
||||
// A nil options pointer is equivalent to a zero options value.
|
||||
func (c *Client) Create(mailbox string, options *imap.CreateOptions) *Command {
|
||||
cmd := &Command{}
|
||||
enc := c.beginCommand("CREATE", cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
if options != nil && len(options.SpecialUse) > 0 {
|
||||
enc.SP().Special('(').Atom("USE").SP().List(len(options.SpecialUse), func(i int) {
|
||||
enc.MailboxAttr(options.SpecialUse[i])
|
||||
}).Special(')')
|
||||
}
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
69
vendor/github.com/emersion/go-imap/v2/imapclient/enable.go
generated
vendored
Normal file
69
vendor/github.com/emersion/go-imap/v2/imapclient/enable.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
// Enable sends an ENABLE command.
|
||||
//
|
||||
// This command requires support for IMAP4rev2 or the ENABLE extension.
|
||||
func (c *Client) Enable(caps ...imap.Cap) *EnableCommand {
|
||||
// Enabling an extension may change the IMAP syntax, so only allow the
|
||||
// extensions we support here
|
||||
for _, name := range caps {
|
||||
switch name {
|
||||
case imap.CapIMAP4rev2, imap.CapUTF8Accept, imap.CapMetadata, imap.CapMetadataServer:
|
||||
// ok
|
||||
default:
|
||||
done := make(chan error)
|
||||
close(done)
|
||||
err := fmt.Errorf("imapclient: cannot enable %q: not supported", name)
|
||||
return &EnableCommand{commandBase: commandBase{done: done, err: err}}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &EnableCommand{}
|
||||
enc := c.beginCommand("ENABLE", cmd)
|
||||
for _, c := range caps {
|
||||
enc.SP().Atom(string(c))
|
||||
}
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleEnabled() error {
|
||||
caps, err := readCapabilities(c.dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
for name := range caps {
|
||||
c.enabled[name] = struct{}{}
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
if cmd := findPendingCmdByType[*EnableCommand](c); cmd != nil {
|
||||
cmd.data.Caps = caps
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableCommand is an ENABLE command.
|
||||
type EnableCommand struct {
|
||||
commandBase
|
||||
data EnableData
|
||||
}
|
||||
|
||||
func (cmd *EnableCommand) Wait() (*EnableData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
// EnableData is the data returned by the ENABLE command.
|
||||
type EnableData struct {
|
||||
// Capabilities that were successfully enabled
|
||||
Caps imap.CapSet
|
||||
}
|
||||
84
vendor/github.com/emersion/go-imap/v2/imapclient/expunge.go
generated
vendored
Normal file
84
vendor/github.com/emersion/go-imap/v2/imapclient/expunge.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
// Expunge sends an EXPUNGE command.
|
||||
func (c *Client) Expunge() *ExpungeCommand {
|
||||
cmd := &ExpungeCommand{seqNums: make(chan uint32, 128)}
|
||||
c.beginCommand("EXPUNGE", cmd).end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// UIDExpunge sends a UID EXPUNGE command.
|
||||
//
|
||||
// This command requires support for IMAP4rev2 or the UIDPLUS extension.
|
||||
func (c *Client) UIDExpunge(uids imap.UIDSet) *ExpungeCommand {
|
||||
cmd := &ExpungeCommand{seqNums: make(chan uint32, 128)}
|
||||
enc := c.beginCommand("UID EXPUNGE", cmd)
|
||||
enc.SP().NumSet(uids)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleExpunge(seqNum uint32) error {
|
||||
c.mutex.Lock()
|
||||
if c.state == imap.ConnStateSelected && c.mailbox.NumMessages > 0 {
|
||||
c.mailbox = c.mailbox.copy()
|
||||
c.mailbox.NumMessages--
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
cmd := findPendingCmdByType[*ExpungeCommand](c)
|
||||
if cmd != nil {
|
||||
cmd.seqNums <- seqNum
|
||||
} else if handler := c.options.unilateralDataHandler().Expunge; handler != nil {
|
||||
handler(seqNum)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpungeCommand is an EXPUNGE command.
|
||||
//
|
||||
// The caller must fully consume the ExpungeCommand. A simple way to do so is
|
||||
// to defer a call to FetchCommand.Close.
|
||||
type ExpungeCommand struct {
|
||||
commandBase
|
||||
seqNums chan uint32
|
||||
}
|
||||
|
||||
// Next advances to the next expunged message sequence number.
|
||||
//
|
||||
// On success, the message sequence number is returned. On error or if there
|
||||
// are no more messages, 0 is returned. To check the error value, use Close.
|
||||
func (cmd *ExpungeCommand) Next() uint32 {
|
||||
return <-cmd.seqNums
|
||||
}
|
||||
|
||||
// Close releases the command.
|
||||
//
|
||||
// Calling Close unblocks the IMAP client decoder and lets it read the next
|
||||
// responses. Next will always return nil after Close.
|
||||
func (cmd *ExpungeCommand) Close() error {
|
||||
for cmd.Next() != 0 {
|
||||
// ignore
|
||||
}
|
||||
return cmd.wait()
|
||||
}
|
||||
|
||||
// Collect accumulates expunged sequence numbers into a list.
|
||||
//
|
||||
// This is equivalent to calling Next repeatedly and then Close.
|
||||
func (cmd *ExpungeCommand) Collect() ([]uint32, error) {
|
||||
var l []uint32
|
||||
for {
|
||||
seqNum := cmd.Next()
|
||||
if seqNum == 0 {
|
||||
break
|
||||
}
|
||||
l = append(l, seqNum)
|
||||
}
|
||||
return l, cmd.Close()
|
||||
}
|
||||
1326
vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go
generated
vendored
Normal file
1326
vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
163
vendor/github.com/emersion/go-imap/v2/imapclient/id.go
generated
vendored
Normal file
163
vendor/github.com/emersion/go-imap/v2/imapclient/id.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// ID sends an ID command.
|
||||
//
|
||||
// The ID command is introduced in RFC 2971. It requires support for the ID
|
||||
// extension.
|
||||
//
|
||||
// An example ID command:
|
||||
//
|
||||
// ID ("name" "go-imap" "version" "1.0" "os" "Linux" "os-version" "7.9.4" "vendor" "Yahoo")
|
||||
func (c *Client) ID(idData *imap.IDData) *IDCommand {
|
||||
cmd := &IDCommand{}
|
||||
enc := c.beginCommand("ID", cmd)
|
||||
|
||||
if idData == nil {
|
||||
enc.SP().NIL()
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
enc.SP().Special('(')
|
||||
isFirstKey := true
|
||||
if idData.Name != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "name", idData.Name)
|
||||
}
|
||||
if idData.Version != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "version", idData.Version)
|
||||
}
|
||||
if idData.OS != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "os", idData.OS)
|
||||
}
|
||||
if idData.OSVersion != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "os-version", idData.OSVersion)
|
||||
}
|
||||
if idData.Vendor != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "vendor", idData.Vendor)
|
||||
}
|
||||
if idData.SupportURL != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "support-url", idData.SupportURL)
|
||||
}
|
||||
if idData.Address != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "address", idData.Address)
|
||||
}
|
||||
if idData.Date != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "date", idData.Date)
|
||||
}
|
||||
if idData.Command != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "command", idData.Command)
|
||||
}
|
||||
if idData.Arguments != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "arguments", idData.Arguments)
|
||||
}
|
||||
if idData.Environment != "" {
|
||||
addIDKeyValue(enc, &isFirstKey, "environment", idData.Environment)
|
||||
}
|
||||
|
||||
enc.Special(')')
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func addIDKeyValue(enc *commandEncoder, isFirstKey *bool, key, value string) {
|
||||
if isFirstKey == nil {
|
||||
panic("isFirstKey cannot be nil")
|
||||
} else if !*isFirstKey {
|
||||
enc.SP().Quoted(key).SP().Quoted(value)
|
||||
} else {
|
||||
enc.Quoted(key).SP().Quoted(value)
|
||||
}
|
||||
*isFirstKey = false
|
||||
}
|
||||
|
||||
func (c *Client) handleID() error {
|
||||
data, err := c.readID(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in id: %v", err)
|
||||
}
|
||||
|
||||
if cmd := findPendingCmdByType[*IDCommand](c); cmd != nil {
|
||||
cmd.data = *data
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) readID(dec *imapwire.Decoder) (*imap.IDData, error) {
|
||||
var data = imap.IDData{}
|
||||
|
||||
if !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
if dec.ExpectNIL() {
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
currKey := ""
|
||||
err := dec.ExpectList(func() error {
|
||||
var keyOrValue string
|
||||
if !dec.String(&keyOrValue) {
|
||||
return fmt.Errorf("in id key-val list: %v", dec.Err())
|
||||
}
|
||||
|
||||
if currKey == "" {
|
||||
currKey = keyOrValue
|
||||
return nil
|
||||
}
|
||||
|
||||
switch currKey {
|
||||
case "name":
|
||||
data.Name = keyOrValue
|
||||
case "version":
|
||||
data.Version = keyOrValue
|
||||
case "os":
|
||||
data.OS = keyOrValue
|
||||
case "os-version":
|
||||
data.OSVersion = keyOrValue
|
||||
case "vendor":
|
||||
data.Vendor = keyOrValue
|
||||
case "support-url":
|
||||
data.SupportURL = keyOrValue
|
||||
case "address":
|
||||
data.Address = keyOrValue
|
||||
case "date":
|
||||
data.Date = keyOrValue
|
||||
case "command":
|
||||
data.Command = keyOrValue
|
||||
case "arguments":
|
||||
data.Arguments = keyOrValue
|
||||
case "environment":
|
||||
data.Environment = keyOrValue
|
||||
default:
|
||||
// Ignore unknown key
|
||||
// Yahoo server sends "host" and "remote-host" keys
|
||||
// which are not defined in RFC 2971
|
||||
}
|
||||
currKey = ""
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
type IDCommand struct {
|
||||
commandBase
|
||||
data imap.IDData
|
||||
}
|
||||
|
||||
func (r *IDCommand) Wait() (*imap.IDData, error) {
|
||||
return &r.data, r.wait()
|
||||
}
|
||||
157
vendor/github.com/emersion/go-imap/v2/imapclient/idle.go
generated
vendored
Normal file
157
vendor/github.com/emersion/go-imap/v2/imapclient/idle.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const idleRestartInterval = 28 * time.Minute
|
||||
|
||||
// Idle sends an IDLE command.
|
||||
//
|
||||
// Unlike other commands, this method blocks until the server acknowledges it.
|
||||
// On success, the IDLE command is running and other commands cannot be sent.
|
||||
// The caller must invoke IdleCommand.Close to stop IDLE and unblock the
|
||||
// client.
|
||||
//
|
||||
// This command requires support for IMAP4rev2 or the IDLE extension. The IDLE
|
||||
// command is restarted automatically to avoid getting disconnected due to
|
||||
// inactivity timeouts.
|
||||
func (c *Client) Idle() (*IdleCommand, error) {
|
||||
child, err := c.idle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &IdleCommand{
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go cmd.run(c, child)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// IdleCommand is an IDLE command.
|
||||
//
|
||||
// Initially, the IDLE command is running. The server may send unilateral
|
||||
// data. The client cannot send any command while IDLE is running.
|
||||
//
|
||||
// Close must be called to stop the IDLE command.
|
||||
type IdleCommand struct {
|
||||
stopped atomic.Bool
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
|
||||
err error
|
||||
lastChild *idleCommand
|
||||
}
|
||||
|
||||
func (cmd *IdleCommand) run(c *Client, child *idleCommand) {
|
||||
defer close(cmd.done)
|
||||
|
||||
timer := time.NewTimer(idleRestartInterval)
|
||||
defer timer.Stop()
|
||||
|
||||
defer func() {
|
||||
if child != nil {
|
||||
if err := child.Close(); err != nil && cmd.err == nil {
|
||||
cmd.err = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.Reset(idleRestartInterval)
|
||||
|
||||
if cmd.err = child.Close(); cmd.err != nil {
|
||||
return
|
||||
}
|
||||
if child, cmd.err = c.idle(); cmd.err != nil {
|
||||
return
|
||||
}
|
||||
case <-c.decCh:
|
||||
cmd.lastChild = child
|
||||
return
|
||||
case <-cmd.stop:
|
||||
cmd.lastChild = child
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops the IDLE command.
|
||||
//
|
||||
// This method blocks until the command to stop IDLE is written, but doesn't
|
||||
// wait for the server to respond. Callers can use Wait for this purpose.
|
||||
func (cmd *IdleCommand) Close() error {
|
||||
if cmd.stopped.Swap(true) {
|
||||
return fmt.Errorf("imapclient: IDLE already closed")
|
||||
}
|
||||
close(cmd.stop)
|
||||
<-cmd.done
|
||||
return cmd.err
|
||||
}
|
||||
|
||||
// Wait blocks until the IDLE command has completed.
|
||||
func (cmd *IdleCommand) Wait() error {
|
||||
<-cmd.done
|
||||
if cmd.err != nil {
|
||||
return cmd.err
|
||||
}
|
||||
return cmd.lastChild.Wait()
|
||||
}
|
||||
|
||||
func (c *Client) idle() (*idleCommand, error) {
|
||||
cmd := &idleCommand{}
|
||||
contReq := c.registerContReq(cmd)
|
||||
cmd.enc = c.beginCommand("IDLE", cmd)
|
||||
cmd.enc.flush()
|
||||
|
||||
_, err := contReq.Wait()
|
||||
if err != nil {
|
||||
cmd.enc.end()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// idleCommand represents a singular IDLE command, without the restart logic.
|
||||
type idleCommand struct {
|
||||
commandBase
|
||||
enc *commandEncoder
|
||||
}
|
||||
|
||||
// Close stops the IDLE command.
|
||||
//
|
||||
// This method blocks until the command to stop IDLE is written, but doesn't
|
||||
// wait for the server to respond. Callers can use Wait for this purpose.
|
||||
func (cmd *idleCommand) Close() error {
|
||||
if cmd.err != nil {
|
||||
return cmd.err
|
||||
}
|
||||
if cmd.enc == nil {
|
||||
return fmt.Errorf("imapclient: IDLE command closed twice")
|
||||
}
|
||||
cmd.enc.client.setWriteTimeout(cmdWriteTimeout)
|
||||
_, err := cmd.enc.client.bw.WriteString("DONE\r\n")
|
||||
if err == nil {
|
||||
err = cmd.enc.client.bw.Flush()
|
||||
}
|
||||
cmd.enc.end()
|
||||
cmd.enc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait blocks until the IDLE command has completed.
|
||||
//
|
||||
// Wait can only be called after Close.
|
||||
func (cmd *idleCommand) Wait() error {
|
||||
if cmd.enc != nil {
|
||||
panic("imapclient: idleCommand.Close must be called before Wait")
|
||||
}
|
||||
return cmd.wait()
|
||||
}
|
||||
259
vendor/github.com/emersion/go-imap/v2/imapclient/list.go
generated
vendored
Normal file
259
vendor/github.com/emersion/go-imap/v2/imapclient/list.go
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
func getSelectOpts(options *imap.ListOptions) []string {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var l []string
|
||||
if options.SelectSubscribed {
|
||||
l = append(l, "SUBSCRIBED")
|
||||
}
|
||||
if options.SelectRemote {
|
||||
l = append(l, "REMOTE")
|
||||
}
|
||||
if options.SelectRecursiveMatch {
|
||||
l = append(l, "RECURSIVEMATCH")
|
||||
}
|
||||
if options.SelectSpecialUse {
|
||||
l = append(l, "SPECIAL-USE")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func getReturnOpts(options *imap.ListOptions) []string {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var l []string
|
||||
if options.ReturnSubscribed {
|
||||
l = append(l, "SUBSCRIBED")
|
||||
}
|
||||
if options.ReturnChildren {
|
||||
l = append(l, "CHILDREN")
|
||||
}
|
||||
if options.ReturnStatus != nil {
|
||||
l = append(l, "STATUS")
|
||||
}
|
||||
if options.ReturnSpecialUse {
|
||||
l = append(l, "SPECIAL-USE")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// List sends a LIST command.
|
||||
//
|
||||
// The caller must fully consume the ListCommand. A simple way to do so is to
|
||||
// defer a call to ListCommand.Close.
|
||||
//
|
||||
// A nil options pointer is equivalent to a zero options value.
|
||||
//
|
||||
// A non-zero options value requires support for IMAP4rev2 or the LIST-EXTENDED
|
||||
// extension.
|
||||
func (c *Client) List(ref, pattern string, options *imap.ListOptions) *ListCommand {
|
||||
cmd := &ListCommand{
|
||||
mailboxes: make(chan *imap.ListData, 64),
|
||||
returnStatus: options != nil && options.ReturnStatus != nil,
|
||||
}
|
||||
enc := c.beginCommand("LIST", cmd)
|
||||
if selectOpts := getSelectOpts(options); len(selectOpts) > 0 {
|
||||
enc.SP().List(len(selectOpts), func(i int) {
|
||||
enc.Atom(selectOpts[i])
|
||||
})
|
||||
}
|
||||
enc.SP().Mailbox(ref).SP().Mailbox(pattern)
|
||||
if returnOpts := getReturnOpts(options); len(returnOpts) > 0 {
|
||||
enc.SP().Atom("RETURN").SP().List(len(returnOpts), func(i int) {
|
||||
opt := returnOpts[i]
|
||||
enc.Atom(opt)
|
||||
if opt == "STATUS" {
|
||||
returnStatus := statusItems(options.ReturnStatus)
|
||||
enc.SP().List(len(returnStatus), func(j int) {
|
||||
enc.Atom(returnStatus[j])
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleList() error {
|
||||
data, err := readList(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in LIST: %v", err)
|
||||
}
|
||||
|
||||
cmd := c.findPendingCmdFunc(func(cmd command) bool {
|
||||
switch cmd := cmd.(type) {
|
||||
case *ListCommand:
|
||||
return true // TODO: match pattern, check if already handled
|
||||
case *SelectCommand:
|
||||
return cmd.mailbox == data.Mailbox && cmd.data.List == nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
switch cmd := cmd.(type) {
|
||||
case *ListCommand:
|
||||
if cmd.returnStatus {
|
||||
if cmd.pendingData != nil {
|
||||
cmd.mailboxes <- cmd.pendingData
|
||||
}
|
||||
cmd.pendingData = data
|
||||
} else {
|
||||
cmd.mailboxes <- data
|
||||
}
|
||||
case *SelectCommand:
|
||||
cmd.data.List = data
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCommand is a LIST command.
|
||||
type ListCommand struct {
|
||||
commandBase
|
||||
mailboxes chan *imap.ListData
|
||||
|
||||
returnStatus bool
|
||||
pendingData *imap.ListData
|
||||
}
|
||||
|
||||
// Next advances to the next mailbox.
|
||||
//
|
||||
// On success, the mailbox LIST data is returned. On error or if there are no
|
||||
// more mailboxes, nil is returned.
|
||||
func (cmd *ListCommand) Next() *imap.ListData {
|
||||
return <-cmd.mailboxes
|
||||
}
|
||||
|
||||
// Close releases the command.
|
||||
//
|
||||
// Calling Close unblocks the IMAP client decoder and lets it read the next
|
||||
// responses. Next will always return nil after Close.
|
||||
func (cmd *ListCommand) Close() error {
|
||||
for cmd.Next() != nil {
|
||||
// ignore
|
||||
}
|
||||
return cmd.wait()
|
||||
}
|
||||
|
||||
// Collect accumulates mailboxes into a list.
|
||||
//
|
||||
// This is equivalent to calling Next repeatedly and then Close.
|
||||
func (cmd *ListCommand) Collect() ([]*imap.ListData, error) {
|
||||
var l []*imap.ListData
|
||||
for {
|
||||
data := cmd.Next()
|
||||
if data == nil {
|
||||
break
|
||||
}
|
||||
l = append(l, data)
|
||||
}
|
||||
return l, cmd.Close()
|
||||
}
|
||||
|
||||
func readList(dec *imapwire.Decoder) (*imap.ListData, error) {
|
||||
var data imap.ListData
|
||||
|
||||
var err error
|
||||
data.Attrs, err = internal.ExpectMailboxAttrList(dec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in mbx-list-flags: %w", err)
|
||||
}
|
||||
|
||||
if !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
data.Delim, err = readDelim(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !dec.ExpectSP() || !dec.ExpectMailbox(&data.Mailbox) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
if dec.SP() {
|
||||
err := dec.ExpectList(func() error {
|
||||
var tag string
|
||||
if !dec.ExpectAString(&tag) || !dec.ExpectSP() {
|
||||
return dec.Err()
|
||||
}
|
||||
var err error
|
||||
switch strings.ToUpper(tag) {
|
||||
case "CHILDINFO":
|
||||
data.ChildInfo, err = readChildInfoExtendedItem(dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in childinfo-extended-item: %v", err)
|
||||
}
|
||||
case "OLDNAME":
|
||||
data.OldName, err = readOldNameExtendedItem(dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in oldname-extended-item: %v", err)
|
||||
}
|
||||
default:
|
||||
if !dec.DiscardValue() {
|
||||
return fmt.Errorf("in tagged-ext-val: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in mbox-list-extended: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func readChildInfoExtendedItem(dec *imapwire.Decoder) (*imap.ListDataChildInfo, error) {
|
||||
var childInfo imap.ListDataChildInfo
|
||||
err := dec.ExpectList(func() error {
|
||||
var opt string
|
||||
if !dec.ExpectAString(&opt) {
|
||||
return dec.Err()
|
||||
}
|
||||
if strings.ToUpper(opt) == "SUBSCRIBED" {
|
||||
childInfo.Subscribed = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &childInfo, err
|
||||
}
|
||||
|
||||
func readOldNameExtendedItem(dec *imapwire.Decoder) (string, error) {
|
||||
var name string
|
||||
if !dec.ExpectSpecial('(') || !dec.ExpectMailbox(&name) || !dec.ExpectSpecial(')') {
|
||||
return "", dec.Err()
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func readDelim(dec *imapwire.Decoder) (rune, error) {
|
||||
var delimStr string
|
||||
if dec.Quoted(&delimStr) {
|
||||
delim, size := utf8.DecodeRuneInString(delimStr)
|
||||
if delim == utf8.RuneError || size != len(delimStr) {
|
||||
return 0, fmt.Errorf("mailbox delimiter must be a single rune")
|
||||
}
|
||||
return delim, nil
|
||||
} else if !dec.ExpectNIL() {
|
||||
return 0, dec.Err()
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
205
vendor/github.com/emersion/go-imap/v2/imapclient/metadata.go
generated
vendored
Normal file
205
vendor/github.com/emersion/go-imap/v2/imapclient/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
type GetMetadataDepth int
|
||||
|
||||
const (
|
||||
GetMetadataDepthZero GetMetadataDepth = 0
|
||||
GetMetadataDepthOne GetMetadataDepth = 1
|
||||
GetMetadataDepthInfinity GetMetadataDepth = -1
|
||||
)
|
||||
|
||||
func (depth GetMetadataDepth) String() string {
|
||||
switch depth {
|
||||
case GetMetadataDepthZero:
|
||||
return "0"
|
||||
case GetMetadataDepthOne:
|
||||
return "1"
|
||||
case GetMetadataDepthInfinity:
|
||||
return "infinity"
|
||||
default:
|
||||
panic(fmt.Errorf("imapclient: unknown GETMETADATA depth %d", depth))
|
||||
}
|
||||
}
|
||||
|
||||
// GetMetadataOptions contains options for the GETMETADATA command.
|
||||
type GetMetadataOptions struct {
|
||||
MaxSize *uint32
|
||||
Depth GetMetadataDepth
|
||||
}
|
||||
|
||||
func (options *GetMetadataOptions) names() []string {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
var l []string
|
||||
if options.MaxSize != nil {
|
||||
l = append(l, "MAXSIZE")
|
||||
}
|
||||
if options.Depth != GetMetadataDepthZero {
|
||||
l = append(l, "DEPTH")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMetadata sends a GETMETADATA command.
|
||||
//
|
||||
// This command requires support for the METADATA or METADATA-SERVER extension.
|
||||
func (c *Client) GetMetadata(mailbox string, entries []string, options *GetMetadataOptions) *GetMetadataCommand {
|
||||
cmd := &GetMetadataCommand{mailbox: mailbox}
|
||||
enc := c.beginCommand("GETMETADATA", cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
if opts := options.names(); len(opts) > 0 {
|
||||
enc.SP().List(len(opts), func(i int) {
|
||||
opt := opts[i]
|
||||
enc.Atom(opt).SP()
|
||||
switch opt {
|
||||
case "MAXSIZE":
|
||||
enc.Number(*options.MaxSize)
|
||||
case "DEPTH":
|
||||
enc.Atom(options.Depth.String())
|
||||
default:
|
||||
panic(fmt.Errorf("imapclient: unknown GETMETADATA option %q", opt))
|
||||
}
|
||||
})
|
||||
}
|
||||
enc.SP().List(len(entries), func(i int) {
|
||||
enc.String(entries[i])
|
||||
})
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetMetadata sends a SETMETADATA command.
|
||||
//
|
||||
// To remove an entry, set it to nil.
|
||||
//
|
||||
// This command requires support for the METADATA or METADATA-SERVER extension.
|
||||
func (c *Client) SetMetadata(mailbox string, entries map[string]*[]byte) *Command {
|
||||
cmd := &Command{}
|
||||
enc := c.beginCommand("SETMETADATA", cmd)
|
||||
enc.SP().Mailbox(mailbox).SP().Special('(')
|
||||
i := 0
|
||||
for k, v := range entries {
|
||||
if i > 0 {
|
||||
enc.SP()
|
||||
}
|
||||
enc.String(k).SP()
|
||||
if v == nil {
|
||||
enc.NIL()
|
||||
} else {
|
||||
enc.String(string(*v)) // TODO: use literals if required
|
||||
}
|
||||
i++
|
||||
}
|
||||
enc.Special(')')
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleMetadata() error {
|
||||
data, err := readMetadataResp(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in metadata-resp: %v", err)
|
||||
}
|
||||
|
||||
cmd := c.findPendingCmdFunc(func(anyCmd command) bool {
|
||||
cmd, ok := anyCmd.(*GetMetadataCommand)
|
||||
return ok && cmd.mailbox == data.Mailbox
|
||||
})
|
||||
if cmd != nil && len(data.EntryValues) > 0 {
|
||||
cmd := cmd.(*GetMetadataCommand)
|
||||
cmd.data.Mailbox = data.Mailbox
|
||||
if cmd.data.Entries == nil {
|
||||
cmd.data.Entries = make(map[string]*[]byte)
|
||||
}
|
||||
// The server might send multiple METADATA responses for a single
|
||||
// METADATA command
|
||||
for k, v := range data.EntryValues {
|
||||
cmd.data.Entries[k] = v
|
||||
}
|
||||
} else if handler := c.options.unilateralDataHandler().Metadata; handler != nil && len(data.EntryList) > 0 {
|
||||
handler(data.Mailbox, data.EntryList)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMetadataCommand is a GETMETADATA command.
|
||||
type GetMetadataCommand struct {
|
||||
commandBase
|
||||
mailbox string
|
||||
data GetMetadataData
|
||||
}
|
||||
|
||||
func (cmd *GetMetadataCommand) Wait() (*GetMetadataData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
// GetMetadataData is the data returned by the GETMETADATA command.
|
||||
type GetMetadataData struct {
|
||||
Mailbox string
|
||||
Entries map[string]*[]byte
|
||||
}
|
||||
|
||||
type metadataResp struct {
|
||||
Mailbox string
|
||||
EntryList []string
|
||||
EntryValues map[string]*[]byte
|
||||
}
|
||||
|
||||
func readMetadataResp(dec *imapwire.Decoder) (*metadataResp, error) {
|
||||
var data metadataResp
|
||||
|
||||
if !dec.ExpectMailbox(&data.Mailbox) || !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
isList, err := dec.List(func() error {
|
||||
var name string
|
||||
if !dec.ExpectAString(&name) || !dec.ExpectSP() {
|
||||
return dec.Err()
|
||||
}
|
||||
|
||||
// TODO: decode as []byte
|
||||
var (
|
||||
value *[]byte
|
||||
s string
|
||||
)
|
||||
if dec.String(&s) || dec.Literal(&s) {
|
||||
b := []byte(s)
|
||||
value = &b
|
||||
} else if !dec.ExpectNIL() {
|
||||
return dec.Err()
|
||||
}
|
||||
|
||||
if data.EntryValues == nil {
|
||||
data.EntryValues = make(map[string]*[]byte)
|
||||
}
|
||||
data.EntryValues[name] = value
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !isList {
|
||||
var name string
|
||||
if !dec.ExpectAString(&name) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
data.EntryList = append(data.EntryList, name)
|
||||
|
||||
for dec.SP() {
|
||||
if !dec.ExpectAString(&name) {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
data.EntryList = append(data.EntryList, name)
|
||||
}
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
74
vendor/github.com/emersion/go-imap/v2/imapclient/move.go
generated
vendored
Normal file
74
vendor/github.com/emersion/go-imap/v2/imapclient/move.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// Move sends a MOVE command.
|
||||
//
|
||||
// If the server doesn't support IMAP4rev2 nor the MOVE extension, a fallback
|
||||
// with COPY + STORE + EXPUNGE commands is used.
|
||||
func (c *Client) Move(numSet imap.NumSet, mailbox string) *MoveCommand {
|
||||
// If the server doesn't support MOVE, fallback to [UID] COPY,
|
||||
// [UID] STORE +FLAGS.SILENT \Deleted and [UID] EXPUNGE
|
||||
cmdName := "MOVE"
|
||||
if !c.Caps().Has(imap.CapMove) {
|
||||
cmdName = "COPY"
|
||||
}
|
||||
|
||||
cmd := &MoveCommand{}
|
||||
enc := c.beginCommand(uidCmdName(cmdName, imapwire.NumSetKind(numSet)), cmd)
|
||||
enc.SP().NumSet(numSet).SP().Mailbox(mailbox)
|
||||
enc.end()
|
||||
|
||||
if cmdName == "COPY" {
|
||||
cmd.store = c.Store(numSet, &imap.StoreFlags{
|
||||
Op: imap.StoreFlagsAdd,
|
||||
Silent: true,
|
||||
Flags: []imap.Flag{imap.FlagDeleted},
|
||||
}, nil)
|
||||
if uidSet, ok := numSet.(imap.UIDSet); ok && c.Caps().Has(imap.CapUIDPlus) {
|
||||
cmd.expunge = c.UIDExpunge(uidSet)
|
||||
} else {
|
||||
cmd.expunge = c.Expunge()
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MoveCommand is a MOVE command.
|
||||
type MoveCommand struct {
|
||||
commandBase
|
||||
data MoveData
|
||||
|
||||
// Fallback
|
||||
store *FetchCommand
|
||||
expunge *ExpungeCommand
|
||||
}
|
||||
|
||||
func (cmd *MoveCommand) Wait() (*MoveData, error) {
|
||||
if err := cmd.wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cmd.store != nil {
|
||||
if err := cmd.store.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if cmd.expunge != nil {
|
||||
if err := cmd.expunge.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &cmd.data, nil
|
||||
}
|
||||
|
||||
// MoveData contains the data returned by a MOVE command.
|
||||
type MoveData struct {
|
||||
// requires UIDPLUS or IMAP4rev2
|
||||
UIDValidity uint32
|
||||
SourceUIDs imap.NumSet
|
||||
DestUIDs imap.NumSet
|
||||
}
|
||||
110
vendor/github.com/emersion/go-imap/v2/imapclient/namespace.go
generated
vendored
Normal file
110
vendor/github.com/emersion/go-imap/v2/imapclient/namespace.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// Namespace sends a NAMESPACE command.
|
||||
//
|
||||
// This command requires support for IMAP4rev2 or the NAMESPACE extension.
|
||||
func (c *Client) Namespace() *NamespaceCommand {
|
||||
cmd := &NamespaceCommand{}
|
||||
c.beginCommand("NAMESPACE", cmd).end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleNamespace() error {
|
||||
data, err := readNamespaceResponse(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in namespace-response: %v", err)
|
||||
}
|
||||
if cmd := findPendingCmdByType[*NamespaceCommand](c); cmd != nil {
|
||||
cmd.data = *data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NamespaceCommand is a NAMESPACE command.
|
||||
type NamespaceCommand struct {
|
||||
commandBase
|
||||
data imap.NamespaceData
|
||||
}
|
||||
|
||||
func (cmd *NamespaceCommand) Wait() (*imap.NamespaceData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
func readNamespaceResponse(dec *imapwire.Decoder) (*imap.NamespaceData, error) {
|
||||
var (
|
||||
data imap.NamespaceData
|
||||
err error
|
||||
)
|
||||
|
||||
data.Personal, err = readNamespace(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
data.Other, err = readNamespace(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
data.Shared, err = readNamespace(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func readNamespace(dec *imapwire.Decoder) ([]imap.NamespaceDescriptor, error) {
|
||||
var l []imap.NamespaceDescriptor
|
||||
err := dec.ExpectNList(func() error {
|
||||
descr, err := readNamespaceDescr(dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in namespace-descr: %v", err)
|
||||
}
|
||||
l = append(l, *descr)
|
||||
return nil
|
||||
})
|
||||
return l, err
|
||||
}
|
||||
|
||||
func readNamespaceDescr(dec *imapwire.Decoder) (*imap.NamespaceDescriptor, error) {
|
||||
var descr imap.NamespaceDescriptor
|
||||
|
||||
if !dec.ExpectSpecial('(') || !dec.ExpectString(&descr.Prefix) || !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
var err error
|
||||
descr.Delim, err = readDelim(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Skip namespace-response-extensions
|
||||
for dec.SP() {
|
||||
if !dec.DiscardValue() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if !dec.ExpectSpecial(')') {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
return &descr, nil
|
||||
}
|
||||
176
vendor/github.com/emersion/go-imap/v2/imapclient/quota.go
generated
vendored
Normal file
176
vendor/github.com/emersion/go-imap/v2/imapclient/quota.go
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// GetQuota sends a GETQUOTA command.
|
||||
//
|
||||
// This command requires support for the QUOTA extension.
|
||||
func (c *Client) GetQuota(root string) *GetQuotaCommand {
|
||||
cmd := &GetQuotaCommand{root: root}
|
||||
enc := c.beginCommand("GETQUOTA", cmd)
|
||||
enc.SP().String(root)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetQuotaRoot sends a GETQUOTAROOT command.
|
||||
//
|
||||
// This command requires support for the QUOTA extension.
|
||||
func (c *Client) GetQuotaRoot(mailbox string) *GetQuotaRootCommand {
|
||||
cmd := &GetQuotaRootCommand{mailbox: mailbox}
|
||||
enc := c.beginCommand("GETQUOTAROOT", cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetQuota sends a SETQUOTA command.
|
||||
//
|
||||
// This command requires support for the SETQUOTA extension.
|
||||
func (c *Client) SetQuota(root string, limits map[imap.QuotaResourceType]int64) *Command {
|
||||
// TODO: consider returning the QUOTA response data?
|
||||
cmd := &Command{}
|
||||
enc := c.beginCommand("SETQUOTA", cmd)
|
||||
enc.SP().String(root).SP().Special('(')
|
||||
i := 0
|
||||
for typ, limit := range limits {
|
||||
if i > 0 {
|
||||
enc.SP()
|
||||
}
|
||||
enc.Atom(string(typ)).SP().Number64(limit)
|
||||
i++
|
||||
}
|
||||
enc.Special(')')
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleQuota() error {
|
||||
data, err := readQuotaResponse(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in quota-response: %v", err)
|
||||
}
|
||||
|
||||
cmd := c.findPendingCmdFunc(func(cmd command) bool {
|
||||
switch cmd := cmd.(type) {
|
||||
case *GetQuotaCommand:
|
||||
return cmd.root == data.Root
|
||||
case *GetQuotaRootCommand:
|
||||
for _, root := range cmd.roots {
|
||||
if root == data.Root {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
switch cmd := cmd.(type) {
|
||||
case *GetQuotaCommand:
|
||||
cmd.data = data
|
||||
case *GetQuotaRootCommand:
|
||||
cmd.data = append(cmd.data, *data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleQuotaRoot() error {
|
||||
mailbox, roots, err := readQuotaRoot(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in quotaroot-response: %v", err)
|
||||
}
|
||||
|
||||
cmd := c.findPendingCmdFunc(func(anyCmd command) bool {
|
||||
cmd, ok := anyCmd.(*GetQuotaRootCommand)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cmd.mailbox == mailbox
|
||||
})
|
||||
if cmd != nil {
|
||||
cmd := cmd.(*GetQuotaRootCommand)
|
||||
cmd.roots = roots
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetQuotaCommand is a GETQUOTA command.
|
||||
type GetQuotaCommand struct {
|
||||
commandBase
|
||||
root string
|
||||
data *QuotaData
|
||||
}
|
||||
|
||||
func (cmd *GetQuotaCommand) Wait() (*QuotaData, error) {
|
||||
if err := cmd.wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.data, nil
|
||||
}
|
||||
|
||||
// GetQuotaRootCommand is a GETQUOTAROOT command.
|
||||
type GetQuotaRootCommand struct {
|
||||
commandBase
|
||||
mailbox string
|
||||
roots []string
|
||||
data []QuotaData
|
||||
}
|
||||
|
||||
func (cmd *GetQuotaRootCommand) Wait() ([]QuotaData, error) {
|
||||
if err := cmd.wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.data, nil
|
||||
}
|
||||
|
||||
// QuotaData is the data returned by a QUOTA response.
|
||||
type QuotaData struct {
|
||||
Root string
|
||||
Resources map[imap.QuotaResourceType]QuotaResourceData
|
||||
}
|
||||
|
||||
// QuotaResourceData contains the usage and limit for a quota resource.
|
||||
type QuotaResourceData struct {
|
||||
Usage int64
|
||||
Limit int64
|
||||
}
|
||||
|
||||
func readQuotaResponse(dec *imapwire.Decoder) (*QuotaData, error) {
|
||||
var data QuotaData
|
||||
if !dec.ExpectAString(&data.Root) || !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
data.Resources = make(map[imap.QuotaResourceType]QuotaResourceData)
|
||||
err := dec.ExpectList(func() error {
|
||||
var (
|
||||
name string
|
||||
resData QuotaResourceData
|
||||
)
|
||||
if !dec.ExpectAtom(&name) || !dec.ExpectSP() || !dec.ExpectNumber64(&resData.Usage) || !dec.ExpectSP() || !dec.ExpectNumber64(&resData.Limit) {
|
||||
return fmt.Errorf("in quota-resource: %v", dec.Err())
|
||||
}
|
||||
data.Resources[imap.QuotaResourceType(name)] = resData
|
||||
return nil
|
||||
})
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func readQuotaRoot(dec *imapwire.Decoder) (mailbox string, roots []string, err error) {
|
||||
if !dec.ExpectMailbox(&mailbox) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
for dec.SP() {
|
||||
var root string
|
||||
if !dec.ExpectAString(&root) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
roots = append(roots, root)
|
||||
}
|
||||
return mailbox, roots, nil
|
||||
}
|
||||
401
vendor/github.com/emersion/go-imap/v2/imapclient/search.go
generated
vendored
Normal file
401
vendor/github.com/emersion/go-imap/v2/imapclient/search.go
generated
vendored
Normal file
@@ -0,0 +1,401 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
func returnSearchOptions(options *imap.SearchOptions) []string {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := map[string]bool{
|
||||
"MIN": options.ReturnMin,
|
||||
"MAX": options.ReturnMax,
|
||||
"ALL": options.ReturnAll,
|
||||
"COUNT": options.ReturnCount,
|
||||
}
|
||||
|
||||
var l []string
|
||||
for k, ret := range m {
|
||||
if ret {
|
||||
l = append(l, k)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (c *Client) search(numKind imapwire.NumKind, criteria *imap.SearchCriteria, options *imap.SearchOptions) *SearchCommand {
|
||||
// The IMAP4rev2 SEARCH charset defaults to UTF-8. When UTF8=ACCEPT is
|
||||
// enabled, specifying any CHARSET is invalid. For IMAP4rev1 the default is
|
||||
// undefined and only US-ASCII support is required. What's more, some
|
||||
// servers completely reject the CHARSET keyword. So, let's check if we
|
||||
// actually have UTF-8 strings in the search criteria before using that.
|
||||
// TODO: there might be a benefit in specifying CHARSET UTF-8 for IMAP4rev1
|
||||
// servers even if we only send ASCII characters: the server then must
|
||||
// decode encoded headers and Content-Transfer-Encoding before matching the
|
||||
// criteria.
|
||||
var charset string
|
||||
if !c.Caps().Has(imap.CapIMAP4rev2) && !c.enabled.Has(imap.CapUTF8Accept) && !searchCriteriaIsASCII(criteria) {
|
||||
charset = "UTF-8"
|
||||
}
|
||||
|
||||
var all imap.NumSet
|
||||
switch numKind {
|
||||
case imapwire.NumKindSeq:
|
||||
all = imap.SeqSet(nil)
|
||||
case imapwire.NumKindUID:
|
||||
all = imap.UIDSet(nil)
|
||||
}
|
||||
|
||||
cmd := &SearchCommand{}
|
||||
cmd.data.All = all
|
||||
enc := c.beginCommand(uidCmdName("SEARCH", numKind), cmd)
|
||||
if returnOpts := returnSearchOptions(options); len(returnOpts) > 0 {
|
||||
enc.SP().Atom("RETURN").SP().List(len(returnOpts), func(i int) {
|
||||
enc.Atom(returnOpts[i])
|
||||
})
|
||||
}
|
||||
enc.SP()
|
||||
if charset != "" {
|
||||
enc.Atom("CHARSET").SP().Atom(charset).SP()
|
||||
}
|
||||
writeSearchKey(enc.Encoder, criteria)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Search sends a SEARCH command.
|
||||
func (c *Client) Search(criteria *imap.SearchCriteria, options *imap.SearchOptions) *SearchCommand {
|
||||
return c.search(imapwire.NumKindSeq, criteria, options)
|
||||
}
|
||||
|
||||
// UIDSearch sends a UID SEARCH command.
|
||||
func (c *Client) UIDSearch(criteria *imap.SearchCriteria, options *imap.SearchOptions) *SearchCommand {
|
||||
return c.search(imapwire.NumKindUID, criteria, options)
|
||||
}
|
||||
|
||||
func (c *Client) handleSearch() error {
|
||||
cmd := findPendingCmdByType[*SearchCommand](c)
|
||||
for c.dec.SP() {
|
||||
if c.dec.Special('(') {
|
||||
var name string
|
||||
if !c.dec.ExpectAtom(&name) || !c.dec.ExpectSP() {
|
||||
return c.dec.Err()
|
||||
} else if strings.ToUpper(name) != "MODSEQ" {
|
||||
return fmt.Errorf("in search-sort-mod-seq: expected %q, got %q", "MODSEQ", name)
|
||||
}
|
||||
var modSeq uint64
|
||||
if !c.dec.ExpectModSeq(&modSeq) || !c.dec.ExpectSpecial(')') {
|
||||
return c.dec.Err()
|
||||
}
|
||||
if cmd != nil {
|
||||
cmd.data.ModSeq = modSeq
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
var num uint32
|
||||
if !c.dec.ExpectNumber(&num) {
|
||||
return c.dec.Err()
|
||||
}
|
||||
if cmd != nil {
|
||||
switch all := cmd.data.All.(type) {
|
||||
case imap.SeqSet:
|
||||
all.AddNum(num)
|
||||
cmd.data.All = all
|
||||
case imap.UIDSet:
|
||||
all.AddNum(imap.UID(num))
|
||||
cmd.data.All = all
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleESearch() error {
|
||||
if !c.dec.ExpectSP() {
|
||||
return c.dec.Err()
|
||||
}
|
||||
tag, data, err := readESearchResponse(c.dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := c.findPendingCmdFunc(func(anyCmd command) bool {
|
||||
cmd, ok := anyCmd.(*SearchCommand)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if tag != "" {
|
||||
return cmd.tag == tag
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if cmd != nil {
|
||||
cmd := cmd.(*SearchCommand)
|
||||
cmd.data = *data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchCommand is a SEARCH command.
|
||||
type SearchCommand struct {
|
||||
commandBase
|
||||
data imap.SearchData
|
||||
}
|
||||
|
||||
func (cmd *SearchCommand) Wait() (*imap.SearchData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
func writeSearchKey(enc *imapwire.Encoder, criteria *imap.SearchCriteria) {
|
||||
firstItem := true
|
||||
encodeItem := func() *imapwire.Encoder {
|
||||
if !firstItem {
|
||||
enc.SP()
|
||||
}
|
||||
firstItem = false
|
||||
return enc
|
||||
}
|
||||
|
||||
for _, seqSet := range criteria.SeqNum {
|
||||
encodeItem().NumSet(seqSet)
|
||||
}
|
||||
for _, uidSet := range criteria.UID {
|
||||
encodeItem().Atom("UID").SP().NumSet(uidSet)
|
||||
}
|
||||
|
||||
if !criteria.Since.IsZero() && !criteria.Before.IsZero() && criteria.Before.Sub(criteria.Since) == 24*time.Hour {
|
||||
encodeItem().Atom("ON").SP().String(criteria.Since.Format(internal.DateLayout))
|
||||
} else {
|
||||
if !criteria.Since.IsZero() {
|
||||
encodeItem().Atom("SINCE").SP().String(criteria.Since.Format(internal.DateLayout))
|
||||
}
|
||||
if !criteria.Before.IsZero() {
|
||||
encodeItem().Atom("BEFORE").SP().String(criteria.Before.Format(internal.DateLayout))
|
||||
}
|
||||
}
|
||||
if !criteria.SentSince.IsZero() && !criteria.SentBefore.IsZero() && criteria.SentBefore.Sub(criteria.SentSince) == 24*time.Hour {
|
||||
encodeItem().Atom("SENTON").SP().String(criteria.SentSince.Format(internal.DateLayout))
|
||||
} else {
|
||||
if !criteria.SentSince.IsZero() {
|
||||
encodeItem().Atom("SENTSINCE").SP().String(criteria.SentSince.Format(internal.DateLayout))
|
||||
}
|
||||
if !criteria.SentBefore.IsZero() {
|
||||
encodeItem().Atom("SENTBEFORE").SP().String(criteria.SentBefore.Format(internal.DateLayout))
|
||||
}
|
||||
}
|
||||
|
||||
for _, kv := range criteria.Header {
|
||||
switch k := strings.ToUpper(kv.Key); k {
|
||||
case "BCC", "CC", "FROM", "SUBJECT", "TO":
|
||||
encodeItem().Atom(k)
|
||||
default:
|
||||
encodeItem().Atom("HEADER").SP().String(kv.Key)
|
||||
}
|
||||
enc.SP().String(kv.Value)
|
||||
}
|
||||
|
||||
for _, s := range criteria.Body {
|
||||
encodeItem().Atom("BODY").SP().String(s)
|
||||
}
|
||||
for _, s := range criteria.Text {
|
||||
encodeItem().Atom("TEXT").SP().String(s)
|
||||
}
|
||||
|
||||
for _, flag := range criteria.Flag {
|
||||
if k := flagSearchKey(flag); k != "" {
|
||||
encodeItem().Atom(k)
|
||||
} else {
|
||||
encodeItem().Atom("KEYWORD").SP().Flag(flag)
|
||||
}
|
||||
}
|
||||
for _, flag := range criteria.NotFlag {
|
||||
if k := flagSearchKey(flag); k != "" {
|
||||
encodeItem().Atom("UN" + k)
|
||||
} else {
|
||||
encodeItem().Atom("UNKEYWORD").SP().Flag(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if criteria.Larger > 0 {
|
||||
encodeItem().Atom("LARGER").SP().Number64(criteria.Larger)
|
||||
}
|
||||
if criteria.Smaller > 0 {
|
||||
encodeItem().Atom("SMALLER").SP().Number64(criteria.Smaller)
|
||||
}
|
||||
|
||||
if modSeq := criteria.ModSeq; modSeq != nil {
|
||||
encodeItem().Atom("MODSEQ")
|
||||
if modSeq.MetadataName != "" && modSeq.MetadataType != "" {
|
||||
enc.SP().Quoted(modSeq.MetadataName).SP().Atom(string(modSeq.MetadataType))
|
||||
}
|
||||
enc.SP()
|
||||
if modSeq.ModSeq != 0 {
|
||||
enc.ModSeq(modSeq.ModSeq)
|
||||
} else {
|
||||
enc.Atom("0")
|
||||
}
|
||||
}
|
||||
|
||||
for _, not := range criteria.Not {
|
||||
encodeItem().Atom("NOT").SP()
|
||||
enc.Special('(')
|
||||
writeSearchKey(enc, ¬)
|
||||
enc.Special(')')
|
||||
}
|
||||
for _, or := range criteria.Or {
|
||||
encodeItem().Atom("OR").SP()
|
||||
enc.Special('(')
|
||||
writeSearchKey(enc, &or[0])
|
||||
enc.Special(')')
|
||||
enc.SP()
|
||||
enc.Special('(')
|
||||
writeSearchKey(enc, &or[1])
|
||||
enc.Special(')')
|
||||
}
|
||||
|
||||
if firstItem {
|
||||
enc.Atom("ALL")
|
||||
}
|
||||
}
|
||||
|
||||
func flagSearchKey(flag imap.Flag) string {
|
||||
switch flag {
|
||||
case imap.FlagAnswered, imap.FlagDeleted, imap.FlagDraft, imap.FlagFlagged, imap.FlagSeen:
|
||||
return strings.ToUpper(strings.TrimPrefix(string(flag), "\\"))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func readESearchResponse(dec *imapwire.Decoder) (tag string, data *imap.SearchData, err error) {
|
||||
data = &imap.SearchData{}
|
||||
if dec.Special('(') { // search-correlator
|
||||
var correlator string
|
||||
if !dec.ExpectAtom(&correlator) || !dec.ExpectSP() || !dec.ExpectAString(&tag) || !dec.ExpectSpecial(')') {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
if correlator != "TAG" {
|
||||
return "", nil, fmt.Errorf("in search-correlator: name must be TAG, but got %q", correlator)
|
||||
}
|
||||
}
|
||||
|
||||
var name string
|
||||
if !dec.SP() {
|
||||
return tag, data, nil
|
||||
} else if !dec.ExpectAtom(&name) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
isUID := name == "UID"
|
||||
|
||||
if isUID {
|
||||
if !dec.SP() {
|
||||
return tag, data, nil
|
||||
} else if !dec.ExpectAtom(&name) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if !dec.ExpectSP() {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
|
||||
switch strings.ToUpper(name) {
|
||||
case "MIN":
|
||||
var num uint32
|
||||
if !dec.ExpectNumber(&num) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
data.Min = num
|
||||
case "MAX":
|
||||
var num uint32
|
||||
if !dec.ExpectNumber(&num) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
data.Max = num
|
||||
case "ALL":
|
||||
numKind := imapwire.NumKindSeq
|
||||
if isUID {
|
||||
numKind = imapwire.NumKindUID
|
||||
}
|
||||
if !dec.ExpectNumSet(numKind, &data.All) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
if data.All.Dynamic() {
|
||||
return "", nil, fmt.Errorf("imapclient: server returned a dynamic ALL number set in SEARCH response")
|
||||
}
|
||||
case "COUNT":
|
||||
var num uint32
|
||||
if !dec.ExpectNumber(&num) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
data.Count = num
|
||||
case "MODSEQ":
|
||||
var modSeq uint64
|
||||
if !dec.ExpectModSeq(&modSeq) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
data.ModSeq = modSeq
|
||||
default:
|
||||
if !dec.DiscardValue() {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if !dec.SP() {
|
||||
break
|
||||
} else if !dec.ExpectAtom(&name) {
|
||||
return "", nil, dec.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return tag, data, nil
|
||||
}
|
||||
|
||||
func searchCriteriaIsASCII(criteria *imap.SearchCriteria) bool {
|
||||
for _, kv := range criteria.Header {
|
||||
if !isASCII(kv.Key) || !isASCII(kv.Value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, s := range criteria.Body {
|
||||
if !isASCII(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, s := range criteria.Text {
|
||||
if !isASCII(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, not := range criteria.Not {
|
||||
if !searchCriteriaIsASCII(¬) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, or := range criteria.Or {
|
||||
if !searchCriteriaIsASCII(&or[0]) || !searchCriteriaIsASCII(&or[1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
100
vendor/github.com/emersion/go-imap/v2/imapclient/select.go
generated
vendored
Normal file
100
vendor/github.com/emersion/go-imap/v2/imapclient/select.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal"
|
||||
)
|
||||
|
||||
// Select sends a SELECT or EXAMINE command.
|
||||
//
|
||||
// A nil options pointer is equivalent to a zero options value.
|
||||
func (c *Client) Select(mailbox string, options *imap.SelectOptions) *SelectCommand {
|
||||
cmdName := "SELECT"
|
||||
if options != nil && options.ReadOnly {
|
||||
cmdName = "EXAMINE"
|
||||
}
|
||||
|
||||
cmd := &SelectCommand{mailbox: mailbox}
|
||||
enc := c.beginCommand(cmdName, cmd)
|
||||
enc.SP().Mailbox(mailbox)
|
||||
if options != nil && options.CondStore {
|
||||
enc.SP().Special('(').Atom("CONDSTORE").Special(')')
|
||||
}
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Unselect sends an UNSELECT command.
|
||||
//
|
||||
// This command requires support for IMAP4rev2 or the UNSELECT extension.
|
||||
func (c *Client) Unselect() *Command {
|
||||
cmd := &unselectCommand{}
|
||||
c.beginCommand("UNSELECT", cmd).end()
|
||||
return &cmd.Command
|
||||
}
|
||||
|
||||
// UnselectAndExpunge sends a CLOSE command.
|
||||
//
|
||||
// CLOSE implicitly performs a silent EXPUNGE command.
|
||||
func (c *Client) UnselectAndExpunge() *Command {
|
||||
cmd := &unselectCommand{}
|
||||
c.beginCommand("CLOSE", cmd).end()
|
||||
return &cmd.Command
|
||||
}
|
||||
|
||||
func (c *Client) handleFlags() error {
|
||||
flags, err := internal.ExpectFlagList(c.dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
if c.state == imap.ConnStateSelected {
|
||||
c.mailbox = c.mailbox.copy()
|
||||
c.mailbox.PermanentFlags = flags
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
cmd := findPendingCmdByType[*SelectCommand](c)
|
||||
if cmd != nil {
|
||||
cmd.data.Flags = flags
|
||||
} else if handler := c.options.unilateralDataHandler().Mailbox; handler != nil {
|
||||
handler(&UnilateralDataMailbox{Flags: flags})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleExists(num uint32) error {
|
||||
cmd := findPendingCmdByType[*SelectCommand](c)
|
||||
if cmd != nil {
|
||||
cmd.data.NumMessages = num
|
||||
} else {
|
||||
c.mutex.Lock()
|
||||
if c.state == imap.ConnStateSelected {
|
||||
c.mailbox = c.mailbox.copy()
|
||||
c.mailbox.NumMessages = num
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
if handler := c.options.unilateralDataHandler().Mailbox; handler != nil {
|
||||
handler(&UnilateralDataMailbox{NumMessages: &num})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectCommand is a SELECT command.
|
||||
type SelectCommand struct {
|
||||
commandBase
|
||||
mailbox string
|
||||
data imap.SelectData
|
||||
}
|
||||
|
||||
func (cmd *SelectCommand) Wait() (*imap.SelectData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
type unselectCommand struct {
|
||||
Command
|
||||
}
|
||||
84
vendor/github.com/emersion/go-imap/v2/imapclient/sort.go
generated
vendored
Normal file
84
vendor/github.com/emersion/go-imap/v2/imapclient/sort.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
type SortKey string
|
||||
|
||||
const (
|
||||
SortKeyArrival SortKey = "ARRIVAL"
|
||||
SortKeyCc SortKey = "CC"
|
||||
SortKeyDate SortKey = "DATE"
|
||||
SortKeyFrom SortKey = "FROM"
|
||||
SortKeySize SortKey = "SIZE"
|
||||
SortKeySubject SortKey = "SUBJECT"
|
||||
SortKeyTo SortKey = "TO"
|
||||
)
|
||||
|
||||
type SortCriterion struct {
|
||||
Key SortKey
|
||||
Reverse bool
|
||||
}
|
||||
|
||||
// SortOptions contains options for the SORT command.
|
||||
type SortOptions struct {
|
||||
SearchCriteria *imap.SearchCriteria
|
||||
SortCriteria []SortCriterion
|
||||
}
|
||||
|
||||
func (c *Client) sort(numKind imapwire.NumKind, options *SortOptions) *SortCommand {
|
||||
cmd := &SortCommand{}
|
||||
enc := c.beginCommand(uidCmdName("SORT", numKind), cmd)
|
||||
enc.SP().List(len(options.SortCriteria), func(i int) {
|
||||
criterion := options.SortCriteria[i]
|
||||
if criterion.Reverse {
|
||||
enc.Atom("REVERSE").SP()
|
||||
}
|
||||
enc.Atom(string(criterion.Key))
|
||||
})
|
||||
enc.SP().Atom("UTF-8").SP()
|
||||
writeSearchKey(enc.Encoder, options.SearchCriteria)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleSort() error {
|
||||
cmd := findPendingCmdByType[*SortCommand](c)
|
||||
for c.dec.SP() {
|
||||
var num uint32
|
||||
if !c.dec.ExpectNumber(&num) {
|
||||
return c.dec.Err()
|
||||
}
|
||||
if cmd != nil {
|
||||
cmd.nums = append(cmd.nums, num)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort sends a SORT command.
|
||||
//
|
||||
// This command requires support for the SORT extension.
|
||||
func (c *Client) Sort(options *SortOptions) *SortCommand {
|
||||
return c.sort(imapwire.NumKindSeq, options)
|
||||
}
|
||||
|
||||
// UIDSort sends a UID SORT command.
|
||||
//
|
||||
// See Sort.
|
||||
func (c *Client) UIDSort(options *SortOptions) *SortCommand {
|
||||
return c.sort(imapwire.NumKindUID, options)
|
||||
}
|
||||
|
||||
// SortCommand is a SORT command.
|
||||
type SortCommand struct {
|
||||
commandBase
|
||||
nums []uint32
|
||||
}
|
||||
|
||||
func (cmd *SortCommand) Wait() ([]uint32, error) {
|
||||
err := cmd.wait()
|
||||
return cmd.nums, err
|
||||
}
|
||||
83
vendor/github.com/emersion/go-imap/v2/imapclient/starttls.go
generated
vendored
Normal file
83
vendor/github.com/emersion/go-imap/v2/imapclient/starttls.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// startTLS sends a STARTTLS command.
|
||||
//
|
||||
// Unlike other commands, this method blocks until the command completes.
|
||||
func (c *Client) startTLS(config *tls.Config) error {
|
||||
upgradeDone := make(chan struct{})
|
||||
cmd := &startTLSCommand{
|
||||
tlsConfig: config,
|
||||
upgradeDone: upgradeDone,
|
||||
}
|
||||
enc := c.beginCommand("STARTTLS", cmd)
|
||||
enc.flush()
|
||||
defer enc.end()
|
||||
|
||||
// Once a client issues a STARTTLS command, it MUST NOT issue further
|
||||
// commands until a server response is seen and the TLS negotiation is
|
||||
// complete
|
||||
|
||||
if err := cmd.wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The decoder goroutine will invoke Client.upgradeStartTLS
|
||||
<-upgradeDone
|
||||
|
||||
return cmd.tlsConn.Handshake()
|
||||
}
|
||||
|
||||
// upgradeStartTLS finishes the STARTTLS upgrade after the server has sent an
|
||||
// OK response. It runs in the decoder goroutine.
|
||||
func (c *Client) upgradeStartTLS(startTLS *startTLSCommand) {
|
||||
defer close(startTLS.upgradeDone)
|
||||
|
||||
// Drain buffered data from our bufio.Reader
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.CopyN(&buf, c.br, int64(c.br.Buffered())); err != nil {
|
||||
panic(err) // unreachable
|
||||
}
|
||||
|
||||
var cleartextConn net.Conn
|
||||
if buf.Len() > 0 {
|
||||
r := io.MultiReader(&buf, c.conn)
|
||||
cleartextConn = startTLSConn{c.conn, r}
|
||||
} else {
|
||||
cleartextConn = c.conn
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(cleartextConn, startTLS.tlsConfig)
|
||||
rw := c.options.wrapReadWriter(tlsConn)
|
||||
|
||||
c.br.Reset(rw)
|
||||
// Unfortunately we can't re-use the bufio.Writer here, it races with
|
||||
// Client.StartTLS
|
||||
c.bw = bufio.NewWriter(rw)
|
||||
|
||||
startTLS.tlsConn = tlsConn
|
||||
}
|
||||
|
||||
type startTLSCommand struct {
|
||||
commandBase
|
||||
tlsConfig *tls.Config
|
||||
|
||||
upgradeDone chan<- struct{}
|
||||
tlsConn *tls.Conn
|
||||
}
|
||||
|
||||
type startTLSConn struct {
|
||||
net.Conn
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (conn startTLSConn) Read(b []byte) (int, error) {
|
||||
return conn.r.Read(b)
|
||||
}
|
||||
164
vendor/github.com/emersion/go-imap/v2/imapclient/status.go
generated
vendored
Normal file
164
vendor/github.com/emersion/go-imap/v2/imapclient/status.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
func statusItems(options *imap.StatusOptions) []string {
|
||||
m := map[string]bool{
|
||||
"MESSAGES": options.NumMessages,
|
||||
"UIDNEXT": options.UIDNext,
|
||||
"UIDVALIDITY": options.UIDValidity,
|
||||
"UNSEEN": options.NumUnseen,
|
||||
"DELETED": options.NumDeleted,
|
||||
"SIZE": options.Size,
|
||||
"APPENDLIMIT": options.AppendLimit,
|
||||
"DELETED-STORAGE": options.DeletedStorage,
|
||||
"HIGHESTMODSEQ": options.HighestModSeq,
|
||||
}
|
||||
|
||||
var l []string
|
||||
for k, req := range m {
|
||||
if req {
|
||||
l = append(l, k)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Status sends a STATUS command.
|
||||
//
|
||||
// A nil options pointer is equivalent to a zero options value.
|
||||
func (c *Client) Status(mailbox string, options *imap.StatusOptions) *StatusCommand {
|
||||
if options == nil {
|
||||
options = new(imap.StatusOptions)
|
||||
}
|
||||
if options.NumRecent {
|
||||
panic("StatusOptions.NumRecent is not supported in imapclient")
|
||||
}
|
||||
|
||||
cmd := &StatusCommand{mailbox: mailbox}
|
||||
enc := c.beginCommand("STATUS", cmd)
|
||||
enc.SP().Mailbox(mailbox).SP()
|
||||
items := statusItems(options)
|
||||
enc.List(len(items), func(i int) {
|
||||
enc.Atom(items[i])
|
||||
})
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *Client) handleStatus() error {
|
||||
data, err := readStatus(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in status: %v", err)
|
||||
}
|
||||
|
||||
cmd := c.findPendingCmdFunc(func(cmd command) bool {
|
||||
switch cmd := cmd.(type) {
|
||||
case *StatusCommand:
|
||||
return cmd.mailbox == data.Mailbox
|
||||
case *ListCommand:
|
||||
return cmd.returnStatus && cmd.pendingData != nil && cmd.pendingData.Mailbox == data.Mailbox
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
switch cmd := cmd.(type) {
|
||||
case *StatusCommand:
|
||||
cmd.data = *data
|
||||
case *ListCommand:
|
||||
cmd.pendingData.Status = data
|
||||
cmd.mailboxes <- cmd.pendingData
|
||||
cmd.pendingData = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatusCommand is a STATUS command.
|
||||
type StatusCommand struct {
|
||||
commandBase
|
||||
mailbox string
|
||||
data imap.StatusData
|
||||
}
|
||||
|
||||
func (cmd *StatusCommand) Wait() (*imap.StatusData, error) {
|
||||
return &cmd.data, cmd.wait()
|
||||
}
|
||||
|
||||
func readStatus(dec *imapwire.Decoder) (*imap.StatusData, error) {
|
||||
var data imap.StatusData
|
||||
|
||||
if !dec.ExpectMailbox(&data.Mailbox) || !dec.ExpectSP() {
|
||||
return nil, dec.Err()
|
||||
}
|
||||
|
||||
err := dec.ExpectList(func() error {
|
||||
if err := readStatusAttVal(dec, &data); err != nil {
|
||||
return fmt.Errorf("in status-att-val: %v", dec.Err())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func readStatusAttVal(dec *imapwire.Decoder, data *imap.StatusData) error {
|
||||
var name string
|
||||
if !dec.ExpectAtom(&name) || !dec.ExpectSP() {
|
||||
return dec.Err()
|
||||
}
|
||||
|
||||
var ok bool
|
||||
switch strings.ToUpper(name) {
|
||||
case "MESSAGES":
|
||||
var num uint32
|
||||
ok = dec.ExpectNumber(&num)
|
||||
data.NumMessages = &num
|
||||
case "UIDNEXT":
|
||||
var uidNext imap.UID
|
||||
ok = dec.ExpectUID(&uidNext)
|
||||
data.UIDNext = uidNext
|
||||
case "UIDVALIDITY":
|
||||
ok = dec.ExpectNumber(&data.UIDValidity)
|
||||
case "UNSEEN":
|
||||
var num uint32
|
||||
ok = dec.ExpectNumber(&num)
|
||||
data.NumUnseen = &num
|
||||
case "DELETED":
|
||||
var num uint32
|
||||
ok = dec.ExpectNumber(&num)
|
||||
data.NumDeleted = &num
|
||||
case "SIZE":
|
||||
var size int64
|
||||
ok = dec.ExpectNumber64(&size)
|
||||
data.Size = &size
|
||||
case "APPENDLIMIT":
|
||||
var num uint32
|
||||
if dec.Number(&num) {
|
||||
ok = true
|
||||
} else {
|
||||
ok = dec.ExpectNIL()
|
||||
num = ^uint32(0)
|
||||
}
|
||||
data.AppendLimit = &num
|
||||
case "DELETED-STORAGE":
|
||||
var storage int64
|
||||
ok = dec.ExpectNumber64(&storage)
|
||||
data.DeletedStorage = &storage
|
||||
case "HIGHESTMODSEQ":
|
||||
ok = dec.ExpectModSeq(&data.HighestModSeq)
|
||||
default:
|
||||
if !dec.DiscardValue() {
|
||||
return dec.Err()
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/emersion/go-imap/v2/imapclient/store.go
generated
vendored
Normal file
44
vendor/github.com/emersion/go-imap/v2/imapclient/store.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// Store sends a STORE command.
|
||||
//
|
||||
// Unless StoreFlags.Silent is set, the server will return the updated values.
|
||||
//
|
||||
// A nil options pointer is equivalent to a zero options value.
|
||||
func (c *Client) Store(numSet imap.NumSet, store *imap.StoreFlags, options *imap.StoreOptions) *FetchCommand {
|
||||
cmd := &FetchCommand{
|
||||
numSet: numSet,
|
||||
msgs: make(chan *FetchMessageData, 128),
|
||||
}
|
||||
enc := c.beginCommand(uidCmdName("STORE", imapwire.NumSetKind(numSet)), cmd)
|
||||
enc.SP().NumSet(numSet).SP()
|
||||
if options != nil && options.UnchangedSince != 0 {
|
||||
enc.Special('(').Atom("UNCHANGEDSINCE").SP().ModSeq(options.UnchangedSince).Special(')').SP()
|
||||
}
|
||||
switch store.Op {
|
||||
case imap.StoreFlagsSet:
|
||||
// nothing to do
|
||||
case imap.StoreFlagsAdd:
|
||||
enc.Special('+')
|
||||
case imap.StoreFlagsDel:
|
||||
enc.Special('-')
|
||||
default:
|
||||
panic(fmt.Errorf("imapclient: unknown store flags op: %v", store.Op))
|
||||
}
|
||||
enc.Atom("FLAGS")
|
||||
if store.Silent {
|
||||
enc.Atom(".SILENT")
|
||||
}
|
||||
enc.SP().List(len(store.Flags), func(i int) {
|
||||
enc.Flag(store.Flags[i])
|
||||
})
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
85
vendor/github.com/emersion/go-imap/v2/imapclient/thread.go
generated
vendored
Normal file
85
vendor/github.com/emersion/go-imap/v2/imapclient/thread.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package imapclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
// ThreadOptions contains options for the THREAD command.
|
||||
type ThreadOptions struct {
|
||||
Algorithm imap.ThreadAlgorithm
|
||||
SearchCriteria *imap.SearchCriteria
|
||||
}
|
||||
|
||||
func (c *Client) thread(numKind imapwire.NumKind, options *ThreadOptions) *ThreadCommand {
|
||||
cmd := &ThreadCommand{}
|
||||
enc := c.beginCommand(uidCmdName("THREAD", numKind), cmd)
|
||||
enc.SP().Atom(string(options.Algorithm)).SP().Atom("UTF-8").SP()
|
||||
writeSearchKey(enc.Encoder, options.SearchCriteria)
|
||||
enc.end()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Thread sends a THREAD command.
|
||||
//
|
||||
// This command requires support for the THREAD extension.
|
||||
func (c *Client) Thread(options *ThreadOptions) *ThreadCommand {
|
||||
return c.thread(imapwire.NumKindSeq, options)
|
||||
}
|
||||
|
||||
// UIDThread sends a UID THREAD command.
|
||||
//
|
||||
// See Thread.
|
||||
func (c *Client) UIDThread(options *ThreadOptions) *ThreadCommand {
|
||||
return c.thread(imapwire.NumKindUID, options)
|
||||
}
|
||||
|
||||
func (c *Client) handleThread() error {
|
||||
cmd := findPendingCmdByType[*ThreadCommand](c)
|
||||
for c.dec.SP() {
|
||||
data, err := readThreadList(c.dec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in thread-list: %v", err)
|
||||
}
|
||||
if cmd != nil {
|
||||
cmd.data = append(cmd.data, *data)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ThreadCommand is a THREAD command.
|
||||
type ThreadCommand struct {
|
||||
commandBase
|
||||
data []ThreadData
|
||||
}
|
||||
|
||||
func (cmd *ThreadCommand) Wait() ([]ThreadData, error) {
|
||||
err := cmd.wait()
|
||||
return cmd.data, err
|
||||
}
|
||||
|
||||
type ThreadData struct {
|
||||
Chain []uint32
|
||||
SubThreads []ThreadData
|
||||
}
|
||||
|
||||
func readThreadList(dec *imapwire.Decoder) (*ThreadData, error) {
|
||||
var data ThreadData
|
||||
err := dec.ExpectList(func() error {
|
||||
var num uint32
|
||||
if len(data.SubThreads) == 0 && dec.Number(&num) {
|
||||
data.Chain = append(data.Chain, num)
|
||||
} else {
|
||||
sub, err := readThreadList(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.SubThreads = append(data.SubThreads, *sub)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &data, err
|
||||
}
|
||||
13
vendor/github.com/emersion/go-imap/v2/internal/acl.go
generated
vendored
Normal file
13
vendor/github.com/emersion/go-imap/v2/internal/acl.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
func FormatRights(rm imap.RightModification, rs imap.RightSet) string {
|
||||
s := ""
|
||||
if rm != imap.RightModificationReplace {
|
||||
s = string(rm)
|
||||
}
|
||||
return s + string(rs)
|
||||
}
|
||||
306
vendor/github.com/emersion/go-imap/v2/internal/imapnum/numset.go
generated
vendored
Normal file
306
vendor/github.com/emersion/go-imap/v2/internal/imapnum/numset.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
package imapnum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Range represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
|
||||
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
|
||||
// represented by setting Start = Stop. Zero is used to represent "*", which is
|
||||
// safe because seq-number uses nz-number rule. The order of values is always
|
||||
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
|
||||
type Range struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// Contains returns true if the seq-number q is contained in range value s.
|
||||
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
|
||||
// contains "*" and all numbers >= n.
|
||||
func (s Range) Contains(q uint32) bool {
|
||||
if q == 0 {
|
||||
return s.Stop == 0 // "*" is contained only in "*" and "n:*"
|
||||
}
|
||||
return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
|
||||
}
|
||||
|
||||
// Less returns true if s precedes and does not contain seq-number q.
|
||||
func (s Range) Less(q uint32) bool {
|
||||
return (s.Stop < q || q == 0) && s.Stop != 0
|
||||
}
|
||||
|
||||
// Merge combines range values s and t into a single union if the two
|
||||
// intersect or one is a superset of the other. The order of s and t does not
|
||||
// matter. If the values cannot be merged, s is returned unmodified and ok is
|
||||
// set to false.
|
||||
func (s Range) Merge(t Range) (union Range, ok bool) {
|
||||
union = s
|
||||
if s == t {
|
||||
return s, true
|
||||
}
|
||||
if s.Start != 0 && t.Start != 0 {
|
||||
// s and t are any combination of "n", "n:m", or "n:*"
|
||||
if s.Start > t.Start {
|
||||
s, t = t, s
|
||||
}
|
||||
// s starts at or before t, check where it ends
|
||||
if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
|
||||
return s, true // s is a superset of t
|
||||
}
|
||||
// s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
|
||||
if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
|
||||
return Range{s.Start, t.Stop}, true // s intersects or touches t
|
||||
}
|
||||
return union, false
|
||||
}
|
||||
// exactly one of s and t is "*"
|
||||
if s.Start == 0 {
|
||||
if t.Stop == 0 {
|
||||
return t, true // s is "*", t is "n:*"
|
||||
}
|
||||
} else if s.Stop == 0 {
|
||||
return s, true // s is "n:*", t is "*"
|
||||
}
|
||||
return union, false
|
||||
}
|
||||
|
||||
// String returns range value s as a seq-number or seq-range string.
|
||||
func (s Range) String() string {
|
||||
if s.Start == s.Stop {
|
||||
if s.Start == 0 {
|
||||
return "*"
|
||||
}
|
||||
return strconv.FormatUint(uint64(s.Start), 10)
|
||||
}
|
||||
b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
|
||||
if s.Stop == 0 {
|
||||
return string(append(b, ':', '*'))
|
||||
}
|
||||
return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
|
||||
}
|
||||
|
||||
func (s Range) append(nums []uint32) (out []uint32, ok bool) {
|
||||
if s.Start == 0 || s.Stop == 0 {
|
||||
return nil, false
|
||||
}
|
||||
for n := s.Start; n <= s.Stop; n++ {
|
||||
nums = append(nums, n)
|
||||
}
|
||||
return nums, true
|
||||
}
|
||||
|
||||
// Set is used to represent a set of message sequence numbers or UIDs (see
|
||||
// sequence-set ABNF rule). The zero value is an empty set.
|
||||
type Set []Range
|
||||
|
||||
// AddNum inserts new numbers into the set. The value 0 represents "*".
|
||||
func (s *Set) AddNum(q ...uint32) {
|
||||
for _, v := range q {
|
||||
s.insert(Range{v, v})
|
||||
}
|
||||
}
|
||||
|
||||
// AddRange inserts a new range into the set.
|
||||
func (s *Set) AddRange(start, stop uint32) {
|
||||
if (stop < start && stop != 0) || start == 0 {
|
||||
s.insert(Range{stop, start})
|
||||
} else {
|
||||
s.insert(Range{start, stop})
|
||||
}
|
||||
}
|
||||
|
||||
// AddSet inserts all values from t into s.
|
||||
func (s *Set) AddSet(t Set) {
|
||||
for _, v := range t {
|
||||
s.insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic returns true if the set contains "*" or "n:*" values.
|
||||
func (s Set) Dynamic() bool {
|
||||
return len(s) > 0 && s[len(s)-1].Stop == 0
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero sequence number or UID q is contained
|
||||
// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
|
||||
// responsibility to handle the special case where q is the maximum UID in the
|
||||
// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
|
||||
// it doesn't know what the maximum value is).
|
||||
func (s Set) Contains(q uint32) bool {
|
||||
if _, ok := s.search(q); ok {
|
||||
return q != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Nums returns a slice of all numbers contained in the set.
|
||||
func (s Set) Nums() (nums []uint32, ok bool) {
|
||||
for _, v := range s {
|
||||
nums, ok = v.append(nums)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nums, true
|
||||
}
|
||||
|
||||
// String returns a sorted representation of all contained number values.
|
||||
func (s Set) String() string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, 0, 64)
|
||||
for _, v := range s {
|
||||
b = append(b, ',')
|
||||
if v.Start == 0 {
|
||||
b = append(b, '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(b, uint64(v.Start), 10)
|
||||
if v.Start != v.Stop {
|
||||
if v.Stop == 0 {
|
||||
b = append(b, ':', '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
|
||||
}
|
||||
}
|
||||
return string(b[1:])
|
||||
}
|
||||
|
||||
// insert adds range value v to the set.
|
||||
func (ptr *Set) insert(v Range) {
|
||||
s := *ptr
|
||||
defer func() {
|
||||
*ptr = s
|
||||
}()
|
||||
|
||||
i, _ := s.search(v.Start)
|
||||
merged := false
|
||||
if i > 0 {
|
||||
// try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
|
||||
s[i-1], merged = s[i-1].Merge(v)
|
||||
}
|
||||
if i == len(s) {
|
||||
// v was either merged with the last entry or needs to be appended
|
||||
if !merged {
|
||||
s.insertAt(i, v)
|
||||
}
|
||||
return
|
||||
} else if merged {
|
||||
i--
|
||||
} else if s[i], merged = s[i].Merge(v); !merged {
|
||||
s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
|
||||
return
|
||||
}
|
||||
// v was merged with s[i], continue trying to merge until the end
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if s[i], merged = s[i].Merge(s[j]); !merged {
|
||||
if j > i+1 {
|
||||
// cut out all entries between i and j that were merged
|
||||
s = append(s[:i+1], s[j:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything after s[i] was merged
|
||||
s = s[:i+1]
|
||||
}
|
||||
|
||||
// insertAt inserts a new range value v at index i, resizing s.Set as needed.
|
||||
func (ptr *Set) insertAt(i int, v Range) {
|
||||
s := *ptr
|
||||
defer func() {
|
||||
*ptr = s
|
||||
}()
|
||||
|
||||
if n := len(s); i == n {
|
||||
// insert at the end
|
||||
s = append(s, v)
|
||||
return
|
||||
} else if n < cap(s) {
|
||||
// enough space, shift everything at and after i to the right
|
||||
s = s[:n+1]
|
||||
copy(s[i+1:], s[i:])
|
||||
} else {
|
||||
// allocate new slice and copy everything, n is at least 1
|
||||
set := make([]Range, n+1, n*2)
|
||||
copy(set, s[:i])
|
||||
copy(set[i+1:], s[i:])
|
||||
s = set
|
||||
}
|
||||
s[i] = v
|
||||
}
|
||||
|
||||
// search attempts to find the index of the range set value that contains q.
|
||||
// If no values contain q, the returned index is the position where q should be
|
||||
// inserted and ok is set to false.
|
||||
func (s Set) search(q uint32) (i int, ok bool) {
|
||||
min, max := 0, len(s)-1
|
||||
for min < max {
|
||||
if mid := (min + max) >> 1; s[mid].Less(q) {
|
||||
min = mid + 1
|
||||
} else {
|
||||
max = mid
|
||||
}
|
||||
}
|
||||
if max < 0 || s[min].Less(q) {
|
||||
return len(s), false // q is the new largest value
|
||||
}
|
||||
return min, s[min].Contains(q)
|
||||
}
|
||||
|
||||
// errBadNumSet is used to report problems with the format of a number set
|
||||
// value.
|
||||
type errBadNumSet string
|
||||
|
||||
func (err errBadNumSet) Error() string {
|
||||
return fmt.Sprintf("imap: bad number set value %q", string(err))
|
||||
}
|
||||
|
||||
// parseNum parses a single seq-number value (non-zero uint32 or "*").
|
||||
func parseNum(v string) (uint32, error) {
|
||||
if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
|
||||
return uint32(n), nil
|
||||
} else if v == "*" {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, errBadNumSet(v)
|
||||
}
|
||||
|
||||
// parseNumRange creates a new seq instance by parsing strings in the format
|
||||
// "n" or "n:m", where n and/or m may be "*". An error is returned for invalid
|
||||
// values.
|
||||
func parseNumRange(v string) (Range, error) {
|
||||
var (
|
||||
r Range
|
||||
err error
|
||||
)
|
||||
if sep := strings.IndexRune(v, ':'); sep < 0 {
|
||||
r.Start, err = parseNum(v)
|
||||
r.Stop = r.Start
|
||||
return r, err
|
||||
} else if r.Start, err = parseNum(v[:sep]); err == nil {
|
||||
if r.Stop, err = parseNum(v[sep+1:]); err == nil {
|
||||
if (r.Stop < r.Start && r.Stop != 0) || r.Start == 0 {
|
||||
r.Start, r.Stop = r.Stop, r.Start
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return r, errBadNumSet(v)
|
||||
}
|
||||
|
||||
// ParseSet returns a new Set after parsing the set string.
|
||||
func ParseSet(set string) (Set, error) {
|
||||
var s Set
|
||||
for _, sv := range strings.Split(set, ",") {
|
||||
r, err := parseNumRange(sv)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.AddRange(r.Start, r.Stop)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
654
vendor/github.com/emersion/go-imap/v2/internal/imapwire/decoder.go
generated
vendored
Normal file
654
vendor/github.com/emersion/go-imap/v2/internal/imapwire/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,654 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapnum"
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
// This limits the max list nesting depth to prevent stack overflow.
|
||||
const maxListDepth = 1000
|
||||
|
||||
// IsAtomChar returns true if ch is an ATOM-CHAR.
|
||||
func IsAtomChar(ch byte) bool {
|
||||
switch ch {
|
||||
case '(', ')', '{', ' ', '%', '*', '"', '\\', ']':
|
||||
return false
|
||||
default:
|
||||
return !unicode.IsControl(rune(ch))
|
||||
}
|
||||
}
|
||||
|
||||
// Is non-empty char
|
||||
func isAStringChar(ch byte) bool {
|
||||
return IsAtomChar(ch) || ch == ']'
|
||||
}
|
||||
|
||||
// DecoderExpectError is an error due to the Decoder.Expect family of methods.
|
||||
type DecoderExpectError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err *DecoderExpectError) Error() string {
|
||||
return fmt.Sprintf("imapwire: %v", err.Message)
|
||||
}
|
||||
|
||||
// A Decoder reads IMAP data.
|
||||
//
|
||||
// There are multiple families of methods:
|
||||
//
|
||||
// - Methods directly named after IMAP grammar elements attempt to decode
|
||||
// said element, and return false if it's another element.
|
||||
// - "Expect" methods do the same, but set the decoder error (see Err) on
|
||||
// failure.
|
||||
type Decoder struct {
|
||||
// CheckBufferedLiteralFunc is called when a literal is about to be decoded
|
||||
// and needs to be fully buffered in memory.
|
||||
CheckBufferedLiteralFunc func(size int64, nonSync bool) error
|
||||
// MaxSize defines a maximum number of bytes to be read from the input.
|
||||
// Literals are ignored.
|
||||
MaxSize int64
|
||||
|
||||
r *bufio.Reader
|
||||
side ConnSide
|
||||
err error
|
||||
literal bool
|
||||
crlf bool
|
||||
listDepth int
|
||||
readBytes int64
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder.
|
||||
func NewDecoder(r *bufio.Reader, side ConnSide) *Decoder {
|
||||
return &Decoder{r: r, side: side}
|
||||
}
|
||||
|
||||
func (dec *Decoder) mustUnreadByte() {
|
||||
if err := dec.r.UnreadByte(); err != nil {
|
||||
panic(fmt.Errorf("imapwire: failed to unread byte: %v", err))
|
||||
}
|
||||
dec.readBytes--
|
||||
}
|
||||
|
||||
// Err returns the decoder error, if any.
|
||||
func (dec *Decoder) Err() error {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
func (dec *Decoder) returnErr(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if dec.err == nil {
|
||||
dec.err = err
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) readByte() (byte, bool) {
|
||||
if dec.MaxSize > 0 && dec.readBytes > dec.MaxSize {
|
||||
return 0, dec.returnErr(fmt.Errorf("imapwire: max size exceeded"))
|
||||
}
|
||||
dec.crlf = false
|
||||
if dec.literal {
|
||||
return 0, dec.returnErr(fmt.Errorf("imapwire: cannot decode while a literal is open"))
|
||||
}
|
||||
b, err := dec.r.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return b, dec.returnErr(err)
|
||||
}
|
||||
dec.readBytes++
|
||||
return b, true
|
||||
}
|
||||
|
||||
func (dec *Decoder) acceptByte(want byte) bool {
|
||||
got, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
} else if got != want {
|
||||
dec.mustUnreadByte()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// EOF returns true if end-of-file is reached.
|
||||
func (dec *Decoder) EOF() bool {
|
||||
_, err := dec.r.ReadByte()
|
||||
if err == io.EOF {
|
||||
return true
|
||||
} else if err != nil {
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return false
|
||||
}
|
||||
|
||||
// Expect sets the decoder error if ok is false.
|
||||
func (dec *Decoder) Expect(ok bool, name string) bool {
|
||||
if !ok {
|
||||
msg := fmt.Sprintf("expected %v", name)
|
||||
if dec.r.Buffered() > 0 {
|
||||
b, _ := dec.r.Peek(1)
|
||||
msg += fmt.Sprintf(", got %q", b)
|
||||
}
|
||||
return dec.returnErr(&DecoderExpectError{Message: msg})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) SP() bool {
|
||||
if dec.acceptByte(' ') {
|
||||
// https://github.com/emersion/go-imap/issues/571
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return b != '\r' && b != '\n'
|
||||
}
|
||||
|
||||
// Special case: SP is optional if the next field is a parenthesized list
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return b == '('
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectSP() bool {
|
||||
return dec.Expect(dec.SP(), "SP")
|
||||
}
|
||||
|
||||
func (dec *Decoder) CRLF() bool {
|
||||
dec.acceptByte(' ') // https://github.com/emersion/go-imap/issues/540
|
||||
dec.acceptByte('\r') // be liberal in what we receive and accept lone LF
|
||||
if !dec.acceptByte('\n') {
|
||||
return false
|
||||
}
|
||||
dec.crlf = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectCRLF() bool {
|
||||
return dec.Expect(dec.CRLF(), "CRLF")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Func(ptr *string, valid func(ch byte) bool) bool {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !valid(b) {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
|
||||
sb.WriteByte(b)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) Atom(ptr *string) bool {
|
||||
return dec.Func(ptr, IsAtomChar)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectAtom(ptr *string) bool {
|
||||
return dec.Expect(dec.Atom(ptr), "atom")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNIL() bool {
|
||||
var s string
|
||||
return dec.ExpectAtom(&s) && dec.Expect(s == "NIL", "NIL")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Special(b byte) bool {
|
||||
return dec.acceptByte(b)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectSpecial(b byte) bool {
|
||||
return dec.Expect(dec.Special(b), fmt.Sprintf("'%v'", string(b)))
|
||||
}
|
||||
|
||||
func (dec *Decoder) Text(ptr *string) bool {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
} else if b == '\r' || b == '\n' {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
sb.WriteByte(b)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectText(ptr *string) bool {
|
||||
return dec.Expect(dec.Text(ptr), "text")
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardUntilByte(untilCh byte) {
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return
|
||||
} else if ch == untilCh {
|
||||
dec.mustUnreadByte()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardLine() {
|
||||
if dec.crlf {
|
||||
return
|
||||
}
|
||||
var text string
|
||||
dec.Text(&text)
|
||||
dec.CRLF()
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardValue() bool {
|
||||
var s string
|
||||
if dec.String(&s) {
|
||||
return true
|
||||
}
|
||||
|
||||
isList, err := dec.List(func() error {
|
||||
if !dec.DiscardValue() {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
} else if isList {
|
||||
return true
|
||||
}
|
||||
|
||||
if dec.Atom(&s) {
|
||||
return true
|
||||
}
|
||||
|
||||
dec.Expect(false, "value")
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) numberStr() (s string, ok bool) {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return "", false
|
||||
} else if ch < '0' || ch > '9' {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return "", false
|
||||
}
|
||||
return sb.String(), true
|
||||
}
|
||||
|
||||
func (dec *Decoder) Number(ptr *uint32) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v64, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = uint32(v64)
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumber(ptr *uint32) bool {
|
||||
return dec.Expect(dec.Number(ptr), "number")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectBodyFldOctets(ptr *uint32) bool {
|
||||
// Workaround: some servers incorrectly return "-1" for the body structure
|
||||
// size. See:
|
||||
// https://github.com/emersion/go-imap/issues/534
|
||||
if dec.acceptByte('-') {
|
||||
*ptr = 0
|
||||
return dec.Expect(dec.acceptByte('1'), "-1 (body-fld-octets workaround)")
|
||||
}
|
||||
return dec.ExpectNumber(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) Number64(ptr *int64) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = v
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumber64(ptr *int64) bool {
|
||||
return dec.Expect(dec.Number64(ptr), "number64")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ModSeq(ptr *uint64) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = v
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectModSeq(ptr *uint64) bool {
|
||||
return dec.Expect(dec.ModSeq(ptr), "mod-sequence-value")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Quoted(ptr *string) bool {
|
||||
if !dec.Special('"') {
|
||||
return false
|
||||
}
|
||||
var sb strings.Builder
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ch == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
ch, ok = dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectAString(ptr *string) bool {
|
||||
if dec.Quoted(ptr) {
|
||||
return true
|
||||
}
|
||||
if dec.Literal(ptr) {
|
||||
return true
|
||||
}
|
||||
// We cannot do dec.Atom(ptr) here because sometimes mailbox names are unquoted,
|
||||
// and they can contain special characters like `]`.
|
||||
return dec.Expect(dec.Func(ptr, isAStringChar), "ASTRING-CHAR")
|
||||
}
|
||||
|
||||
func (dec *Decoder) String(ptr *string) bool {
|
||||
return dec.Quoted(ptr) || dec.Literal(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectString(ptr *string) bool {
|
||||
return dec.Expect(dec.String(ptr), "string")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNString(ptr *string) bool {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "nstring") {
|
||||
return false
|
||||
}
|
||||
*ptr = ""
|
||||
return true
|
||||
}
|
||||
return dec.ExpectString(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNStringReader() (lit *LiteralReader, nonSync, ok bool) {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "nstring") {
|
||||
return nil, false, false
|
||||
}
|
||||
return nil, true, true
|
||||
}
|
||||
// TODO: read quoted string as a string instead of buffering
|
||||
if dec.Quoted(&s) {
|
||||
return newLiteralReaderFromString(s), true, true
|
||||
}
|
||||
if lit, nonSync, ok = dec.LiteralReader(); ok {
|
||||
return lit, nonSync, true
|
||||
} else {
|
||||
return nil, false, dec.Expect(false, "nstring")
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) List(f func() error) (isList bool, err error) {
|
||||
if !dec.Special('(') {
|
||||
return false, nil
|
||||
}
|
||||
if dec.Special(')') {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
dec.listDepth++
|
||||
defer func() {
|
||||
dec.listDepth--
|
||||
}()
|
||||
|
||||
if dec.listDepth >= maxListDepth {
|
||||
return false, fmt.Errorf("imapwire: exceeded max depth")
|
||||
}
|
||||
|
||||
for {
|
||||
if err := f(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if dec.Special(')') {
|
||||
return true, nil
|
||||
} else if !dec.ExpectSP() {
|
||||
return true, dec.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectList(f func() error) error {
|
||||
isList, err := dec.List(f)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !dec.Expect(isList, "(") {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNList(f func() error) error {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "NIL") {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return dec.ExpectList(f)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectMailbox(ptr *string) bool {
|
||||
var name string
|
||||
if !dec.ExpectAString(&name) {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
*ptr = "INBOX"
|
||||
return true
|
||||
}
|
||||
name, err := utf7.Decode(name)
|
||||
if err == nil {
|
||||
*ptr = name
|
||||
}
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectUID(ptr *imap.UID) bool {
|
||||
var num uint32
|
||||
if !dec.ExpectNumber(&num) {
|
||||
return false
|
||||
}
|
||||
*ptr = imap.UID(num)
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumSet(kind NumKind, ptr *imap.NumSet) bool {
|
||||
if dec.Special('$') {
|
||||
*ptr = imap.SearchRes()
|
||||
return true
|
||||
}
|
||||
|
||||
var s string
|
||||
if !dec.Expect(dec.Func(&s, isNumSetChar), "sequence-set") {
|
||||
return false
|
||||
}
|
||||
numSet, err := imapnum.ParseSet(s)
|
||||
if err != nil {
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case NumKindSeq:
|
||||
*ptr = seqSetFromNumSet(numSet)
|
||||
case NumKindUID:
|
||||
*ptr = uidSetFromNumSet(numSet)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectUIDSet(ptr *imap.UIDSet) bool {
|
||||
var numSet imap.NumSet
|
||||
ok := dec.ExpectNumSet(NumKindUID, &numSet)
|
||||
if ok {
|
||||
*ptr = numSet.(imap.UIDSet)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func isNumSetChar(ch byte) bool {
|
||||
return ch == '*' || IsAtomChar(ch)
|
||||
}
|
||||
|
||||
func (dec *Decoder) Literal(ptr *string) bool {
|
||||
lit, nonSync, ok := dec.LiteralReader()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if dec.CheckBufferedLiteralFunc != nil {
|
||||
if err := dec.CheckBufferedLiteralFunc(lit.Size(), nonSync); err != nil {
|
||||
lit.cancel()
|
||||
return false
|
||||
}
|
||||
}
|
||||
var sb strings.Builder
|
||||
_, err := io.Copy(&sb, lit)
|
||||
if err == nil {
|
||||
*ptr = sb.String()
|
||||
}
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
func (dec *Decoder) LiteralReader() (lit *LiteralReader, nonSync, ok bool) {
|
||||
if !dec.Special('{') {
|
||||
return nil, false, false
|
||||
}
|
||||
var size int64
|
||||
if !dec.ExpectNumber64(&size) {
|
||||
return nil, false, false
|
||||
}
|
||||
if dec.side == ConnSideServer {
|
||||
nonSync = dec.acceptByte('+')
|
||||
}
|
||||
if !dec.ExpectSpecial('}') || !dec.ExpectCRLF() {
|
||||
return nil, false, false
|
||||
}
|
||||
dec.literal = true
|
||||
lit = &LiteralReader{
|
||||
dec: dec,
|
||||
size: size,
|
||||
r: io.LimitReader(dec.r, size),
|
||||
}
|
||||
return lit, nonSync, true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectLiteralReader() (lit *LiteralReader, nonSync bool, err error) {
|
||||
lit, nonSync, ok := dec.LiteralReader()
|
||||
if !dec.Expect(ok, "literal") {
|
||||
return nil, false, dec.Err()
|
||||
}
|
||||
return lit, nonSync, nil
|
||||
}
|
||||
|
||||
type LiteralReader struct {
|
||||
dec *Decoder
|
||||
size int64
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func newLiteralReaderFromString(s string) *LiteralReader {
|
||||
return &LiteralReader{
|
||||
size: int64(len(s)),
|
||||
r: strings.NewReader(s),
|
||||
}
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) Size() int64 {
|
||||
return lit.size
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) Read(b []byte) (int, error) {
|
||||
n, err := lit.r.Read(b)
|
||||
if err == io.EOF {
|
||||
lit.cancel()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) cancel() {
|
||||
if lit.dec == nil {
|
||||
return
|
||||
}
|
||||
lit.dec.literal = false
|
||||
lit.dec = nil
|
||||
}
|
||||
341
vendor/github.com/emersion/go-imap/v2/internal/imapwire/encoder.go
generated
vendored
Normal file
341
vendor/github.com/emersion/go-imap/v2/internal/imapwire/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
// An Encoder writes IMAP data.
|
||||
//
|
||||
// Most methods don't return an error, instead they defer error handling until
|
||||
// CRLF is called. These methods return the Encoder so that calls can be
|
||||
// chained.
|
||||
type Encoder struct {
|
||||
// QuotedUTF8 allows raw UTF-8 in quoted strings. This requires IMAP4rev2
|
||||
// to be available, or UTF8=ACCEPT to be enabled.
|
||||
QuotedUTF8 bool
|
||||
// LiteralMinus enables non-synchronizing literals for short payloads.
|
||||
// This requires IMAP4rev2 or LITERAL-. This is only meaningful for
|
||||
// clients.
|
||||
LiteralMinus bool
|
||||
// LiteralPlus enables non-synchronizing literals for all payloads. This
|
||||
// requires LITERAL+. This is only meaningful for clients.
|
||||
LiteralPlus bool
|
||||
// NewContinuationRequest creates a new continuation request. This is only
|
||||
// meaningful for clients.
|
||||
NewContinuationRequest func() *ContinuationRequest
|
||||
|
||||
w *bufio.Writer
|
||||
side ConnSide
|
||||
err error
|
||||
literal bool
|
||||
}
|
||||
|
||||
// NewEncoder creates a new encoder.
|
||||
func NewEncoder(w *bufio.Writer, side ConnSide) *Encoder {
|
||||
return &Encoder{w: w, side: side}
|
||||
}
|
||||
|
||||
func (enc *Encoder) setErr(err error) {
|
||||
if enc.err == nil {
|
||||
enc.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeString(s string) *Encoder {
|
||||
if enc.err != nil {
|
||||
return enc
|
||||
}
|
||||
if enc.literal {
|
||||
enc.err = fmt.Errorf("imapwire: cannot encode while a literal is open")
|
||||
return enc
|
||||
}
|
||||
if _, err := enc.w.WriteString(s); err != nil {
|
||||
enc.err = err
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// CRLF writes a "\r\n" sequence and flushes the buffered writer.
|
||||
func (enc *Encoder) CRLF() error {
|
||||
enc.writeString("\r\n")
|
||||
if enc.err != nil {
|
||||
return enc.err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) Atom(s string) *Encoder {
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) SP() *Encoder {
|
||||
return enc.writeString(" ")
|
||||
}
|
||||
|
||||
func (enc *Encoder) Special(ch byte) *Encoder {
|
||||
return enc.writeString(string(ch))
|
||||
}
|
||||
|
||||
func (enc *Encoder) Quoted(s string) *Encoder {
|
||||
var sb strings.Builder
|
||||
sb.Grow(2 + len(s))
|
||||
sb.WriteByte('"')
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch == '"' || ch == '\\' {
|
||||
sb.WriteByte('\\')
|
||||
}
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
sb.WriteByte('"')
|
||||
return enc.writeString(sb.String())
|
||||
}
|
||||
|
||||
func (enc *Encoder) String(s string) *Encoder {
|
||||
if !enc.validQuoted(s) {
|
||||
enc.stringLiteral(s)
|
||||
return enc
|
||||
}
|
||||
return enc.Quoted(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) validQuoted(s string) bool {
|
||||
if len(s) > 4096 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
|
||||
// NUL, CR and LF are never valid
|
||||
switch ch {
|
||||
case 0, '\r', '\n':
|
||||
return false
|
||||
}
|
||||
|
||||
if !enc.QuotedUTF8 && ch > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (enc *Encoder) stringLiteral(s string) {
|
||||
var sync *ContinuationRequest
|
||||
if enc.side == ConnSideClient && (!enc.LiteralMinus || len(s) > 4096) && !enc.LiteralPlus {
|
||||
if enc.NewContinuationRequest != nil {
|
||||
sync = enc.NewContinuationRequest()
|
||||
}
|
||||
if sync == nil {
|
||||
enc.setErr(fmt.Errorf("imapwire: cannot send synchronizing literal"))
|
||||
return
|
||||
}
|
||||
}
|
||||
wc := enc.Literal(int64(len(s)), sync)
|
||||
_, writeErr := io.WriteString(wc, s)
|
||||
closeErr := wc.Close()
|
||||
if writeErr != nil {
|
||||
enc.setErr(writeErr)
|
||||
} else if closeErr != nil {
|
||||
enc.setErr(closeErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) Mailbox(name string) *Encoder {
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
return enc.Atom("INBOX")
|
||||
} else {
|
||||
if enc.QuotedUTF8 {
|
||||
name = utf7.Escape(name)
|
||||
} else {
|
||||
name = utf7.Encode(name)
|
||||
}
|
||||
return enc.String(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) NumSet(numSet imap.NumSet) *Encoder {
|
||||
s := numSet.String()
|
||||
if s == "" {
|
||||
enc.setErr(fmt.Errorf("imapwire: cannot encode empty sequence set"))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) Flag(flag imap.Flag) *Encoder {
|
||||
if flag != "\\*" && !isValidFlag(string(flag)) {
|
||||
enc.setErr(fmt.Errorf("imapwire: invalid flag %q", flag))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(string(flag))
|
||||
}
|
||||
|
||||
func (enc *Encoder) MailboxAttr(attr imap.MailboxAttr) *Encoder {
|
||||
if !strings.HasPrefix(string(attr), "\\") || !isValidFlag(string(attr)) {
|
||||
enc.setErr(fmt.Errorf("imapwire: invalid mailbox attribute %q", attr))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(string(attr))
|
||||
}
|
||||
|
||||
// isValidFlag checks whether the provided string satisfies
|
||||
// flag-keyword / flag-extension.
|
||||
func isValidFlag(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch == '\\' {
|
||||
if i != 0 {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !IsAtomChar(ch) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
||||
|
||||
func (enc *Encoder) Number(v uint32) *Encoder {
|
||||
return enc.writeString(strconv.FormatUint(uint64(v), 10))
|
||||
}
|
||||
|
||||
func (enc *Encoder) Number64(v int64) *Encoder {
|
||||
// TODO: disallow negative values
|
||||
return enc.writeString(strconv.FormatInt(v, 10))
|
||||
}
|
||||
|
||||
func (enc *Encoder) ModSeq(v uint64) *Encoder {
|
||||
// TODO: disallow zero values
|
||||
return enc.writeString(strconv.FormatUint(v, 10))
|
||||
}
|
||||
|
||||
// List writes a parenthesized list.
|
||||
func (enc *Encoder) List(n int, f func(i int)) *Encoder {
|
||||
enc.Special('(')
|
||||
for i := 0; i < n; i++ {
|
||||
if i > 0 {
|
||||
enc.SP()
|
||||
}
|
||||
f(i)
|
||||
}
|
||||
enc.Special(')')
|
||||
return enc
|
||||
}
|
||||
|
||||
func (enc *Encoder) BeginList() *ListEncoder {
|
||||
enc.Special('(')
|
||||
return &ListEncoder{enc: enc}
|
||||
}
|
||||
|
||||
func (enc *Encoder) NIL() *Encoder {
|
||||
return enc.Atom("NIL")
|
||||
}
|
||||
|
||||
func (enc *Encoder) Text(s string) *Encoder {
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) UID(uid imap.UID) *Encoder {
|
||||
return enc.Number(uint32(uid))
|
||||
}
|
||||
|
||||
// Literal writes a literal.
|
||||
//
|
||||
// The caller must write exactly size bytes to the returned writer.
|
||||
//
|
||||
// If sync is non-nil, the literal is synchronizing: the encoder will wait for
|
||||
// nil to be sent to the channel before writing the literal data. If an error
|
||||
// is sent to the channel, the literal will be cancelled.
|
||||
func (enc *Encoder) Literal(size int64, sync *ContinuationRequest) io.WriteCloser {
|
||||
if sync != nil && enc.side == ConnSideServer {
|
||||
panic("imapwire: sync must be nil on a server-side Encoder.Literal")
|
||||
}
|
||||
|
||||
// TODO: literal8
|
||||
enc.writeString("{")
|
||||
enc.Number64(size)
|
||||
if sync == nil && enc.side == ConnSideClient {
|
||||
enc.writeString("+")
|
||||
}
|
||||
enc.writeString("}")
|
||||
|
||||
if sync == nil {
|
||||
enc.writeString("\r\n")
|
||||
} else {
|
||||
if err := enc.CRLF(); err != nil {
|
||||
return errorWriter{err}
|
||||
}
|
||||
if _, err := sync.Wait(); err != nil {
|
||||
enc.setErr(err)
|
||||
return errorWriter{err}
|
||||
}
|
||||
}
|
||||
|
||||
enc.literal = true
|
||||
return &literalWriter{
|
||||
enc: enc,
|
||||
n: size,
|
||||
}
|
||||
}
|
||||
|
||||
type errorWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (ew errorWriter) Write(b []byte) (int, error) {
|
||||
return 0, ew.err
|
||||
}
|
||||
|
||||
func (ew errorWriter) Close() error {
|
||||
return ew.err
|
||||
}
|
||||
|
||||
type literalWriter struct {
|
||||
enc *Encoder
|
||||
n int64
|
||||
}
|
||||
|
||||
func (lw *literalWriter) Write(b []byte) (int, error) {
|
||||
if lw.n-int64(len(b)) < 0 {
|
||||
return 0, fmt.Errorf("wrote too many bytes in literal")
|
||||
}
|
||||
n, err := lw.enc.w.Write(b)
|
||||
lw.n -= int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lw *literalWriter) Close() error {
|
||||
lw.enc.literal = false
|
||||
if lw.n != 0 {
|
||||
return fmt.Errorf("wrote too few bytes in literal (%v remaining)", lw.n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListEncoder struct {
|
||||
enc *Encoder
|
||||
n int
|
||||
}
|
||||
|
||||
func (le *ListEncoder) Item() *Encoder {
|
||||
if le.n > 0 {
|
||||
le.enc.SP()
|
||||
}
|
||||
le.n++
|
||||
return le.enc
|
||||
}
|
||||
|
||||
func (le *ListEncoder) End() {
|
||||
le.enc.Special(')')
|
||||
le.enc = nil
|
||||
}
|
||||
47
vendor/github.com/emersion/go-imap/v2/internal/imapwire/imapwire.go
generated
vendored
Normal file
47
vendor/github.com/emersion/go-imap/v2/internal/imapwire/imapwire.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Package imapwire implements the IMAP wire protocol.
|
||||
//
|
||||
// The IMAP wire protocol is defined in RFC 9051 section 4.
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ConnSide describes the side of a connection: client or server.
|
||||
type ConnSide int
|
||||
|
||||
const (
|
||||
ConnSideClient ConnSide = 1 + iota
|
||||
ConnSideServer
|
||||
)
|
||||
|
||||
// ContinuationRequest is a continuation request.
|
||||
//
|
||||
// The sender must call either Done or Cancel. The receiver must call Wait.
|
||||
type ContinuationRequest struct {
|
||||
done chan struct{}
|
||||
err error
|
||||
text string
|
||||
}
|
||||
|
||||
func NewContinuationRequest() *ContinuationRequest {
|
||||
return &ContinuationRequest{done: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Cancel(err error) {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("imapwire: continuation request cancelled")
|
||||
}
|
||||
cont.err = err
|
||||
close(cont.done)
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Done(text string) {
|
||||
cont.text = text
|
||||
close(cont.done)
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Wait() (string, error) {
|
||||
<-cont.done
|
||||
return cont.text, cont.err
|
||||
}
|
||||
39
vendor/github.com/emersion/go-imap/v2/internal/imapwire/num.go
generated
vendored
Normal file
39
vendor/github.com/emersion/go-imap/v2/internal/imapwire/num.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapnum"
|
||||
)
|
||||
|
||||
type NumKind int
|
||||
|
||||
const (
|
||||
NumKindSeq NumKind = iota + 1
|
||||
NumKindUID
|
||||
)
|
||||
|
||||
func seqSetFromNumSet(s imapnum.Set) imap.SeqSet {
|
||||
return *(*imap.SeqSet)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
func uidSetFromNumSet(s imapnum.Set) imap.UIDSet {
|
||||
return *(*imap.UIDSet)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
func NumSetKind(numSet imap.NumSet) NumKind {
|
||||
switch numSet.(type) {
|
||||
case imap.SeqSet:
|
||||
return NumKindSeq
|
||||
case imap.UIDSet:
|
||||
return NumKindUID
|
||||
default:
|
||||
panic("imap: invalid NumSet type")
|
||||
}
|
||||
}
|
||||
|
||||
func ParseSeqSet(s string) (imap.SeqSet, error) {
|
||||
numSet, err := imapnum.ParseSet(s)
|
||||
return seqSetFromNumSet(numSet), err
|
||||
}
|
||||
188
vendor/github.com/emersion/go-imap/v2/internal/internal.go
generated
vendored
Normal file
188
vendor/github.com/emersion/go-imap/v2/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
const (
|
||||
DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
|
||||
DateLayout = "2-Jan-2006"
|
||||
)
|
||||
|
||||
const FlagRecent imap.Flag = "\\Recent" // removed in IMAP4rev2
|
||||
|
||||
func DecodeDateTime(dec *imapwire.Decoder) (time.Time, error) {
|
||||
var s string
|
||||
if !dec.Quoted(&s) {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
t, err := time.Parse(DateTimeLayout, s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("in date-time: %v", err) // TODO: use imapwire.DecodeExpectError?
|
||||
}
|
||||
return t, err
|
||||
}
|
||||
|
||||
func ExpectDateTime(dec *imapwire.Decoder) (time.Time, error) {
|
||||
t, err := DecodeDateTime(dec)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if !dec.Expect(!t.IsZero(), "date-time") {
|
||||
return t, dec.Err()
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ExpectDate(dec *imapwire.Decoder) (time.Time, error) {
|
||||
var s string
|
||||
if !dec.ExpectAString(&s) {
|
||||
return time.Time{}, dec.Err()
|
||||
}
|
||||
t, err := time.Parse(DateLayout, s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("in date: %v", err) // use imapwire.DecodeExpectError?
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ExpectFlagList(dec *imapwire.Decoder) ([]imap.Flag, error) {
|
||||
var flags []imap.Flag
|
||||
err := dec.ExpectList(func() error {
|
||||
// Some servers start the list with a space, so we need to skip it
|
||||
// https://github.com/emersion/go-imap/pull/633
|
||||
dec.SP()
|
||||
|
||||
flag, err := ExpectFlag(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags = append(flags, flag)
|
||||
return nil
|
||||
})
|
||||
return flags, err
|
||||
}
|
||||
|
||||
func ExpectCap(dec *imapwire.Decoder) (imap.Cap, error) {
|
||||
var name string
|
||||
if !dec.ExpectAtom(&name) {
|
||||
return "", dec.Err()
|
||||
}
|
||||
return canonicalCap(name), nil
|
||||
}
|
||||
|
||||
func ExpectFlag(dec *imapwire.Decoder) (imap.Flag, error) {
|
||||
isSystem := dec.Special('\\')
|
||||
if isSystem && dec.Special('*') {
|
||||
return imap.FlagWildcard, nil // flag-perm
|
||||
}
|
||||
var name string
|
||||
if !dec.ExpectAtom(&name) {
|
||||
return "", fmt.Errorf("in flag: %w", dec.Err())
|
||||
}
|
||||
if isSystem {
|
||||
name = "\\" + name
|
||||
}
|
||||
return canonicalFlag(name), nil
|
||||
}
|
||||
|
||||
func ExpectMailboxAttrList(dec *imapwire.Decoder) ([]imap.MailboxAttr, error) {
|
||||
var attrs []imap.MailboxAttr
|
||||
err := dec.ExpectList(func() error {
|
||||
attr, err := ExpectMailboxAttr(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrs = append(attrs, attr)
|
||||
return nil
|
||||
})
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
func ExpectMailboxAttr(dec *imapwire.Decoder) (imap.MailboxAttr, error) {
|
||||
flag, err := ExpectFlag(dec)
|
||||
return canonicalMailboxAttr(string(flag)), err
|
||||
}
|
||||
|
||||
var (
|
||||
canonOnce sync.Once
|
||||
canonFlag map[string]imap.Flag
|
||||
canonMailboxAttr map[string]imap.MailboxAttr
|
||||
)
|
||||
|
||||
func canonInit() {
|
||||
flags := []imap.Flag{
|
||||
imap.FlagSeen,
|
||||
imap.FlagAnswered,
|
||||
imap.FlagFlagged,
|
||||
imap.FlagDeleted,
|
||||
imap.FlagDraft,
|
||||
imap.FlagForwarded,
|
||||
imap.FlagMDNSent,
|
||||
imap.FlagJunk,
|
||||
imap.FlagNotJunk,
|
||||
imap.FlagPhishing,
|
||||
imap.FlagImportant,
|
||||
}
|
||||
mailboxAttrs := []imap.MailboxAttr{
|
||||
imap.MailboxAttrNonExistent,
|
||||
imap.MailboxAttrNoInferiors,
|
||||
imap.MailboxAttrNoSelect,
|
||||
imap.MailboxAttrHasChildren,
|
||||
imap.MailboxAttrHasNoChildren,
|
||||
imap.MailboxAttrMarked,
|
||||
imap.MailboxAttrUnmarked,
|
||||
imap.MailboxAttrSubscribed,
|
||||
imap.MailboxAttrRemote,
|
||||
imap.MailboxAttrAll,
|
||||
imap.MailboxAttrArchive,
|
||||
imap.MailboxAttrDrafts,
|
||||
imap.MailboxAttrFlagged,
|
||||
imap.MailboxAttrJunk,
|
||||
imap.MailboxAttrSent,
|
||||
imap.MailboxAttrTrash,
|
||||
imap.MailboxAttrImportant,
|
||||
}
|
||||
|
||||
canonFlag = make(map[string]imap.Flag)
|
||||
for _, flag := range flags {
|
||||
canonFlag[strings.ToLower(string(flag))] = flag
|
||||
}
|
||||
|
||||
canonMailboxAttr = make(map[string]imap.MailboxAttr)
|
||||
for _, attr := range mailboxAttrs {
|
||||
canonMailboxAttr[strings.ToLower(string(attr))] = attr
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalFlag(s string) imap.Flag {
|
||||
canonOnce.Do(canonInit)
|
||||
if flag, ok := canonFlag[strings.ToLower(s)]; ok {
|
||||
return flag
|
||||
}
|
||||
return imap.Flag(s)
|
||||
}
|
||||
|
||||
func canonicalMailboxAttr(s string) imap.MailboxAttr {
|
||||
canonOnce.Do(canonInit)
|
||||
if attr, ok := canonMailboxAttr[strings.ToLower(s)]; ok {
|
||||
return attr
|
||||
}
|
||||
return imap.MailboxAttr(s)
|
||||
}
|
||||
|
||||
func canonicalCap(s string) imap.Cap {
|
||||
// Only two caps are not fully uppercase
|
||||
for _, cap := range []imap.Cap{imap.CapIMAP4rev1, imap.CapIMAP4rev2} {
|
||||
if strings.EqualFold(s, string(cap)) {
|
||||
return cap
|
||||
}
|
||||
}
|
||||
return imap.Cap(strings.ToUpper(s))
|
||||
}
|
||||
23
vendor/github.com/emersion/go-imap/v2/internal/sasl.go
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/v2/internal/sasl.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func EncodeSASL(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return "="
|
||||
} else {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeSASL(s string) ([]byte, error) {
|
||||
if s == "=" {
|
||||
// go-sasl treats nil as no challenge/response, so return a non-nil
|
||||
// empty byte slice
|
||||
return []byte{}, nil
|
||||
} else {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
}
|
||||
118
vendor/github.com/emersion/go-imap/v2/internal/utf7/decoder.go
generated
vendored
Normal file
118
vendor/github.com/emersion/go-imap/v2/internal/utf7/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrInvalidUTF7 means that a decoder encountered invalid UTF-7.
|
||||
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
|
||||
|
||||
// Decode decodes a string encoded with modified UTF-7.
|
||||
//
|
||||
// Note, raw UTF-8 is accepted.
|
||||
func Decode(src string) (string, error) {
|
||||
if !utf8.ValidString(src) {
|
||||
return "", errors.New("invalid UTF-8")
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
ascii := true
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
|
||||
if ch < min || (ch > max && ch < utf8.RuneSelf) {
|
||||
// Illegal code point in ASCII mode. Note, UTF-8 codepoints are
|
||||
// always allowed.
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
if ch != '&' {
|
||||
sb.WriteByte(ch)
|
||||
ascii = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the end of the Base64 or "&-" segment
|
||||
start := i + 1
|
||||
for i++; i < len(src) && src[i] != '-'; i++ {
|
||||
if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(src) { // Implicit shift ("&...")
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
if i == start { // Escape sequence "&-"
|
||||
sb.WriteByte('&')
|
||||
ascii = true
|
||||
} else { // Control or non-ASCII code points in base64
|
||||
if !ascii { // Null shift ("&...-&...-")
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
b := decode([]byte(src[start:i]))
|
||||
if len(b) == 0 { // Bad encoding
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
sb.Write(b)
|
||||
|
||||
ascii = false
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8.
|
||||
// A nil slice is returned if the encoding is invalid.
|
||||
func decode(b64 []byte) []byte {
|
||||
var b []byte
|
||||
|
||||
// Allocate a single block of memory large enough to store the Base64 data
|
||||
// (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
|
||||
// Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
|
||||
// double the space allocation for UTF-8.
|
||||
if n := len(b64); b64[n-1] == '=' {
|
||||
return nil
|
||||
} else if n&3 == 0 {
|
||||
b = make([]byte, b64Enc.DecodedLen(n)*3)
|
||||
} else {
|
||||
n += 4 - n&3
|
||||
b = make([]byte, n+b64Enc.DecodedLen(n)*3)
|
||||
copy(b[copy(b, b64):n], []byte("=="))
|
||||
b64, b = b[:n], b[n:]
|
||||
}
|
||||
|
||||
// Decode Base64 into the first 1/3rd of b
|
||||
n, err := b64Enc.Decode(b, b64)
|
||||
if err != nil || n&1 == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
||||
b, s := b[:n], b[n:]
|
||||
j := 0
|
||||
for i := 0; i < n; i += 2 {
|
||||
r := rune(b[i])<<8 | rune(b[i+1])
|
||||
if utf16.IsSurrogate(r) {
|
||||
if i += 2; i == n {
|
||||
return nil
|
||||
}
|
||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
||||
if r = utf16.DecodeRune(r, r2); r == utf8.RuneError {
|
||||
return nil
|
||||
}
|
||||
} else if min <= r && r <= max {
|
||||
return nil
|
||||
}
|
||||
j += utf8.EncodeRune(s[j:], r)
|
||||
}
|
||||
return s[:j]
|
||||
}
|
||||
88
vendor/github.com/emersion/go-imap/v2/internal/utf7/encoder.go
generated
vendored
Normal file
88
vendor/github.com/emersion/go-imap/v2/internal/utf7/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Encode encodes a string with modified UTF-7.
|
||||
func Encode(src string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
for i := 0; i < len(src); {
|
||||
ch := src[i]
|
||||
|
||||
if min <= ch && ch <= max {
|
||||
sb.WriteByte(ch)
|
||||
if ch == '&' {
|
||||
sb.WriteByte('-')
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
start := i
|
||||
|
||||
// Find the next printable ASCII code point
|
||||
i++
|
||||
for i < len(src) && (src[i] < min || src[i] > max) {
|
||||
i++
|
||||
}
|
||||
|
||||
sb.Write(encode([]byte(src[start:i])))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
|
||||
// removes the padding, and adds UTF-7 shifts.
|
||||
func encode(s []byte) []byte {
|
||||
// len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
|
||||
// control code points (see table below).
|
||||
b := make([]byte, 0, len(s)+4)
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRune(s)
|
||||
if r > utf8.MaxRune {
|
||||
r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
|
||||
}
|
||||
s = s[size:]
|
||||
if r1, r2 := utf16.EncodeRune(r); r1 != utf8.RuneError {
|
||||
b = append(b, byte(r1>>8), byte(r1))
|
||||
r = r2
|
||||
}
|
||||
b = append(b, byte(r>>8), byte(r))
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
n := b64Enc.EncodedLen(len(b)) + 2
|
||||
b64 := make([]byte, n)
|
||||
b64Enc.Encode(b64[1:], b)
|
||||
|
||||
// Strip padding
|
||||
n -= 2 - (len(b)+2)%3
|
||||
b64 = b64[:n]
|
||||
|
||||
// Add UTF-7 shifts
|
||||
b64[0] = '&'
|
||||
b64[n-1] = '-'
|
||||
return b64
|
||||
}
|
||||
|
||||
// Escape passes through raw UTF-8 as-is and escapes the special UTF-7 marker
|
||||
// (the ampersand character).
|
||||
func Escape(src string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
for _, ch := range src {
|
||||
sb.WriteRune(ch)
|
||||
if ch == '&' {
|
||||
sb.WriteByte('-')
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
13
vendor/github.com/emersion/go-imap/v2/internal/utf7/utf7.go
generated
vendored
Normal file
13
vendor/github.com/emersion/go-imap/v2/internal/utf7/utf7.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
const (
|
||||
min = 0x20 // Minimum self-representing UTF-7 value
|
||||
max = 0x7E // Maximum self-representing UTF-7 value
|
||||
)
|
||||
|
||||
var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
||||
30
vendor/github.com/emersion/go-imap/v2/list.go
generated
vendored
Normal file
30
vendor/github.com/emersion/go-imap/v2/list.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package imap
|
||||
|
||||
// ListOptions contains options for the LIST command.
|
||||
type ListOptions struct {
|
||||
SelectSubscribed bool
|
||||
SelectRemote bool
|
||||
SelectRecursiveMatch bool // requires SelectSubscribed to be set
|
||||
SelectSpecialUse bool // requires SPECIAL-USE
|
||||
|
||||
ReturnSubscribed bool
|
||||
ReturnChildren bool
|
||||
ReturnStatus *StatusOptions // requires IMAP4rev2 or LIST-STATUS
|
||||
ReturnSpecialUse bool // requires SPECIAL-USE
|
||||
}
|
||||
|
||||
// ListData is the mailbox data returned by a LIST command.
|
||||
type ListData struct {
|
||||
Attrs []MailboxAttr
|
||||
Delim rune
|
||||
Mailbox string
|
||||
|
||||
// Extended data
|
||||
ChildInfo *ListDataChildInfo
|
||||
OldName string
|
||||
Status *StatusData
|
||||
}
|
||||
|
||||
type ListDataChildInfo struct {
|
||||
Subscribed bool
|
||||
}
|
||||
14
vendor/github.com/emersion/go-imap/v2/namespace.go
generated
vendored
Normal file
14
vendor/github.com/emersion/go-imap/v2/namespace.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package imap
|
||||
|
||||
// NamespaceData is the data returned by the NAMESPACE command.
|
||||
type NamespaceData struct {
|
||||
Personal []NamespaceDescriptor
|
||||
Other []NamespaceDescriptor
|
||||
Shared []NamespaceDescriptor
|
||||
}
|
||||
|
||||
// NamespaceDescriptor describes a namespace.
|
||||
type NamespaceDescriptor struct {
|
||||
Prefix string
|
||||
Delim rune
|
||||
}
|
||||
149
vendor/github.com/emersion/go-imap/v2/numset.go
generated
vendored
Normal file
149
vendor/github.com/emersion/go-imap/v2/numset.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/emersion/go-imap/v2/internal/imapnum"
|
||||
)
|
||||
|
||||
// NumSet is a set of numbers identifying messages. NumSet is either a SeqSet
|
||||
// or a UIDSet.
|
||||
type NumSet interface {
|
||||
// String returns the IMAP representation of the message number set.
|
||||
String() string
|
||||
// Dynamic returns true if the set contains "*" or "n:*" ranges or if the
|
||||
// set represents the special SEARCHRES marker.
|
||||
Dynamic() bool
|
||||
|
||||
numSet() imapnum.Set
|
||||
}
|
||||
|
||||
var (
|
||||
_ NumSet = SeqSet(nil)
|
||||
_ NumSet = UIDSet(nil)
|
||||
)
|
||||
|
||||
// SeqSet is a set of message sequence numbers.
|
||||
type SeqSet []SeqRange
|
||||
|
||||
// SeqSetNum returns a new SeqSet containing the specified sequence numbers.
|
||||
func SeqSetNum(nums ...uint32) SeqSet {
|
||||
var s SeqSet
|
||||
s.AddNum(nums...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SeqSet) numSetPtr() *imapnum.Set {
|
||||
return (*imapnum.Set)(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func (s SeqSet) numSet() imapnum.Set {
|
||||
return *s.numSetPtr()
|
||||
}
|
||||
|
||||
func (s SeqSet) String() string {
|
||||
return s.numSet().String()
|
||||
}
|
||||
|
||||
func (s SeqSet) Dynamic() bool {
|
||||
return s.numSet().Dynamic()
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero sequence number num is contained in
|
||||
// the set.
|
||||
func (s *SeqSet) Contains(num uint32) bool {
|
||||
return s.numSet().Contains(num)
|
||||
}
|
||||
|
||||
// Nums returns a slice of all sequence numbers contained in the set.
|
||||
func (s *SeqSet) Nums() ([]uint32, bool) {
|
||||
return s.numSet().Nums()
|
||||
}
|
||||
|
||||
// AddNum inserts new sequence numbers into the set. The value 0 represents "*".
|
||||
func (s *SeqSet) AddNum(nums ...uint32) {
|
||||
s.numSetPtr().AddNum(nums...)
|
||||
}
|
||||
|
||||
// AddRange inserts a new range into the set.
|
||||
func (s *SeqSet) AddRange(start, stop uint32) {
|
||||
s.numSetPtr().AddRange(start, stop)
|
||||
}
|
||||
|
||||
// AddSet inserts all sequence numbers from other into s.
|
||||
func (s *SeqSet) AddSet(other SeqSet) {
|
||||
s.numSetPtr().AddSet(other.numSet())
|
||||
}
|
||||
|
||||
// SeqRange is a range of message sequence numbers.
|
||||
type SeqRange struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// UIDSet is a set of message UIDs.
|
||||
type UIDSet []UIDRange
|
||||
|
||||
// UIDSetNum returns a new UIDSet containing the specified UIDs.
|
||||
func UIDSetNum(uids ...UID) UIDSet {
|
||||
var s UIDSet
|
||||
s.AddNum(uids...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *UIDSet) numSetPtr() *imapnum.Set {
|
||||
return (*imapnum.Set)(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func (s UIDSet) numSet() imapnum.Set {
|
||||
return *s.numSetPtr()
|
||||
}
|
||||
|
||||
func (s UIDSet) String() string {
|
||||
if IsSearchRes(s) {
|
||||
return "$"
|
||||
}
|
||||
return s.numSet().String()
|
||||
}
|
||||
|
||||
func (s UIDSet) Dynamic() bool {
|
||||
return s.numSet().Dynamic() || IsSearchRes(s)
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero UID uid is contained in the set.
|
||||
func (s UIDSet) Contains(uid UID) bool {
|
||||
return s.numSet().Contains(uint32(uid))
|
||||
}
|
||||
|
||||
// Nums returns a slice of all UIDs contained in the set.
|
||||
func (s UIDSet) Nums() ([]UID, bool) {
|
||||
nums, ok := s.numSet().Nums()
|
||||
return uidListFromNumList(nums), ok
|
||||
}
|
||||
|
||||
// AddNum inserts new UIDs into the set. The value 0 represents "*".
|
||||
func (s *UIDSet) AddNum(uids ...UID) {
|
||||
s.numSetPtr().AddNum(numListFromUIDList(uids)...)
|
||||
}
|
||||
|
||||
// AddRange inserts a new range into the set.
|
||||
func (s *UIDSet) AddRange(start, stop UID) {
|
||||
s.numSetPtr().AddRange(uint32(start), uint32(stop))
|
||||
}
|
||||
|
||||
// AddSet inserts all UIDs from other into s.
|
||||
func (s *UIDSet) AddSet(other UIDSet) {
|
||||
s.numSetPtr().AddSet(other.numSet())
|
||||
}
|
||||
|
||||
// UIDRange is a range of message UIDs.
|
||||
type UIDRange struct {
|
||||
Start, Stop UID
|
||||
}
|
||||
|
||||
func numListFromUIDList(uids []UID) []uint32 {
|
||||
return *(*[]uint32)(unsafe.Pointer(&uids))
|
||||
}
|
||||
|
||||
func uidListFromNumList(nums []uint32) []UID {
|
||||
return *(*[]UID)(unsafe.Pointer(&nums))
|
||||
}
|
||||
13
vendor/github.com/emersion/go-imap/v2/quota.go
generated
vendored
Normal file
13
vendor/github.com/emersion/go-imap/v2/quota.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package imap
|
||||
|
||||
// QuotaResourceType is a QUOTA resource type.
|
||||
//
|
||||
// See RFC 9208 section 5.
|
||||
type QuotaResourceType string
|
||||
|
||||
const (
|
||||
QuotaResourceStorage QuotaResourceType = "STORAGE"
|
||||
QuotaResourceMessage QuotaResourceType = "MESSAGE"
|
||||
QuotaResourceMailbox QuotaResourceType = "MAILBOX"
|
||||
QuotaResourceAnnotationStorage QuotaResourceType = "ANNOTATION-STORAGE"
|
||||
)
|
||||
4
vendor/github.com/emersion/go-imap/v2/rename.go
generated
vendored
Normal file
4
vendor/github.com/emersion/go-imap/v2/rename.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
package imap
|
||||
|
||||
// RenameOptions contains options for the RENAME command.
|
||||
type RenameOptions struct{}
|
||||
81
vendor/github.com/emersion/go-imap/v2/response.go
generated
vendored
Normal file
81
vendor/github.com/emersion/go-imap/v2/response.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StatusResponseType is a generic status response type.
|
||||
type StatusResponseType string
|
||||
|
||||
const (
|
||||
StatusResponseTypeOK StatusResponseType = "OK"
|
||||
StatusResponseTypeNo StatusResponseType = "NO"
|
||||
StatusResponseTypeBad StatusResponseType = "BAD"
|
||||
StatusResponseTypePreAuth StatusResponseType = "PREAUTH"
|
||||
StatusResponseTypeBye StatusResponseType = "BYE"
|
||||
)
|
||||
|
||||
// ResponseCode is a response code.
|
||||
type ResponseCode string
|
||||
|
||||
const (
|
||||
ResponseCodeAlert ResponseCode = "ALERT"
|
||||
ResponseCodeAlreadyExists ResponseCode = "ALREADYEXISTS"
|
||||
ResponseCodeAuthenticationFailed ResponseCode = "AUTHENTICATIONFAILED"
|
||||
ResponseCodeAuthorizationFailed ResponseCode = "AUTHORIZATIONFAILED"
|
||||
ResponseCodeBadCharset ResponseCode = "BADCHARSET"
|
||||
ResponseCodeCannot ResponseCode = "CANNOT"
|
||||
ResponseCodeClientBug ResponseCode = "CLIENTBUG"
|
||||
ResponseCodeContactAdmin ResponseCode = "CONTACTADMIN"
|
||||
ResponseCodeCorruption ResponseCode = "CORRUPTION"
|
||||
ResponseCodeExpired ResponseCode = "EXPIRED"
|
||||
ResponseCodeHasChildren ResponseCode = "HASCHILDREN"
|
||||
ResponseCodeInUse ResponseCode = "INUSE"
|
||||
ResponseCodeLimit ResponseCode = "LIMIT"
|
||||
ResponseCodeNonExistent ResponseCode = "NONEXISTENT"
|
||||
ResponseCodeNoPerm ResponseCode = "NOPERM"
|
||||
ResponseCodeOverQuota ResponseCode = "OVERQUOTA"
|
||||
ResponseCodeParse ResponseCode = "PARSE"
|
||||
ResponseCodePrivacyRequired ResponseCode = "PRIVACYREQUIRED"
|
||||
ResponseCodeServerBug ResponseCode = "SERVERBUG"
|
||||
ResponseCodeTryCreate ResponseCode = "TRYCREATE"
|
||||
ResponseCodeUnavailable ResponseCode = "UNAVAILABLE"
|
||||
ResponseCodeUnknownCTE ResponseCode = "UNKNOWN-CTE"
|
||||
|
||||
// METADATA
|
||||
ResponseCodeTooMany ResponseCode = "TOOMANY"
|
||||
ResponseCodeNoPrivate ResponseCode = "NOPRIVATE"
|
||||
|
||||
// APPENDLIMIT
|
||||
ResponseCodeTooBig ResponseCode = "TOOBIG"
|
||||
)
|
||||
|
||||
// StatusResponse is a generic status response.
|
||||
//
|
||||
// See RFC 9051 section 7.1.
|
||||
type StatusResponse struct {
|
||||
Type StatusResponseType
|
||||
Code ResponseCode
|
||||
Text string
|
||||
}
|
||||
|
||||
// Error is an IMAP error caused by a status response.
|
||||
type Error StatusResponse
|
||||
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
// Error implements the error interface.
|
||||
func (err *Error) Error() string {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "imap: %v", err.Type)
|
||||
if err.Code != "" {
|
||||
fmt.Fprintf(&sb, " [%v]", err.Code)
|
||||
}
|
||||
text := err.Text
|
||||
if text == "" {
|
||||
text = "<unknown>"
|
||||
}
|
||||
fmt.Fprintf(&sb, " %v", text)
|
||||
return sb.String()
|
||||
}
|
||||
201
vendor/github.com/emersion/go-imap/v2/search.go
generated
vendored
Normal file
201
vendor/github.com/emersion/go-imap/v2/search.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SearchOptions contains options for the SEARCH command.
|
||||
type SearchOptions struct {
|
||||
// Requires IMAP4rev2 or ESEARCH
|
||||
ReturnMin bool
|
||||
ReturnMax bool
|
||||
ReturnAll bool
|
||||
ReturnCount bool
|
||||
// Requires IMAP4rev2 or SEARCHRES
|
||||
ReturnSave bool
|
||||
}
|
||||
|
||||
// SearchCriteria is a criteria for the SEARCH command.
|
||||
//
|
||||
// When multiple fields are populated, the result is the intersection ("and"
|
||||
// function) of all messages that match the fields.
|
||||
//
|
||||
// And, Not and Or can be used to combine multiple criteria together. For
|
||||
// instance, the following criteria matches messages not containing "hello":
|
||||
//
|
||||
// SearchCriteria{Not: []SearchCriteria{{
|
||||
// Body: []string{"hello"},
|
||||
// }}}
|
||||
//
|
||||
// The following criteria matches messages containing either "hello" or
|
||||
// "world":
|
||||
//
|
||||
// SearchCriteria{Or: [][2]SearchCriteria{{
|
||||
// {Body: []string{"hello"}},
|
||||
// {Body: []string{"world"}},
|
||||
// }}}
|
||||
type SearchCriteria struct {
|
||||
SeqNum []SeqSet
|
||||
UID []UIDSet
|
||||
|
||||
// Only the date is used, the time and timezone are ignored
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
SentSince time.Time
|
||||
SentBefore time.Time
|
||||
|
||||
Header []SearchCriteriaHeaderField
|
||||
Body []string
|
||||
Text []string
|
||||
|
||||
Flag []Flag
|
||||
NotFlag []Flag
|
||||
|
||||
Larger int64
|
||||
Smaller int64
|
||||
|
||||
Not []SearchCriteria
|
||||
Or [][2]SearchCriteria
|
||||
|
||||
ModSeq *SearchCriteriaModSeq // requires CONDSTORE
|
||||
}
|
||||
|
||||
// And intersects two search criteria.
|
||||
func (criteria *SearchCriteria) And(other *SearchCriteria) {
|
||||
criteria.SeqNum = append(criteria.SeqNum, other.SeqNum...)
|
||||
criteria.UID = append(criteria.UID, other.UID...)
|
||||
|
||||
criteria.Since = intersectSince(criteria.Since, other.Since)
|
||||
criteria.Before = intersectBefore(criteria.Before, other.Before)
|
||||
criteria.SentSince = intersectSince(criteria.SentSince, other.SentSince)
|
||||
criteria.SentBefore = intersectBefore(criteria.SentBefore, other.SentBefore)
|
||||
|
||||
criteria.Header = append(criteria.Header, other.Header...)
|
||||
criteria.Body = append(criteria.Body, other.Body...)
|
||||
criteria.Text = append(criteria.Text, other.Text...)
|
||||
|
||||
criteria.Flag = append(criteria.Flag, other.Flag...)
|
||||
criteria.NotFlag = append(criteria.NotFlag, other.NotFlag...)
|
||||
|
||||
if criteria.Larger == 0 || other.Larger > criteria.Larger {
|
||||
criteria.Larger = other.Larger
|
||||
}
|
||||
if criteria.Smaller == 0 || other.Smaller < criteria.Smaller {
|
||||
criteria.Smaller = other.Smaller
|
||||
}
|
||||
|
||||
criteria.Not = append(criteria.Not, other.Not...)
|
||||
criteria.Or = append(criteria.Or, other.Or...)
|
||||
}
|
||||
|
||||
func intersectSince(t1, t2 time.Time) time.Time {
|
||||
switch {
|
||||
case t1.IsZero():
|
||||
return t2
|
||||
case t2.IsZero():
|
||||
return t1
|
||||
case t1.After(t2):
|
||||
return t1
|
||||
default:
|
||||
return t2
|
||||
}
|
||||
}
|
||||
|
||||
func intersectBefore(t1, t2 time.Time) time.Time {
|
||||
switch {
|
||||
case t1.IsZero():
|
||||
return t2
|
||||
case t2.IsZero():
|
||||
return t1
|
||||
case t1.Before(t2):
|
||||
return t1
|
||||
default:
|
||||
return t2
|
||||
}
|
||||
}
|
||||
|
||||
type SearchCriteriaHeaderField struct {
|
||||
Key, Value string
|
||||
}
|
||||
|
||||
type SearchCriteriaModSeq struct {
|
||||
ModSeq uint64
|
||||
MetadataName string
|
||||
MetadataType SearchCriteriaMetadataType
|
||||
}
|
||||
|
||||
type SearchCriteriaMetadataType string
|
||||
|
||||
const (
|
||||
SearchCriteriaMetadataAll SearchCriteriaMetadataType = "all"
|
||||
SearchCriteriaMetadataPrivate SearchCriteriaMetadataType = "priv"
|
||||
SearchCriteriaMetadataShared SearchCriteriaMetadataType = "shared"
|
||||
)
|
||||
|
||||
// SearchData is the data returned by a SEARCH command.
|
||||
type SearchData struct {
|
||||
All NumSet
|
||||
|
||||
// requires IMAP4rev2 or ESEARCH
|
||||
Min uint32
|
||||
Max uint32
|
||||
Count uint32
|
||||
|
||||
// requires CONDSTORE
|
||||
ModSeq uint64
|
||||
}
|
||||
|
||||
// AllSeqNums returns All as a slice of sequence numbers.
|
||||
func (data *SearchData) AllSeqNums() []uint32 {
|
||||
seqSet, ok := data.All.(SeqSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: a dynamic sequence set would be a server bug
|
||||
nums, ok := seqSet.Nums()
|
||||
if !ok {
|
||||
panic("imap: SearchData.All is a dynamic number set")
|
||||
}
|
||||
return nums
|
||||
}
|
||||
|
||||
// AllUIDs returns All as a slice of UIDs.
|
||||
func (data *SearchData) AllUIDs() []UID {
|
||||
uidSet, ok := data.All.(UIDSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: a dynamic sequence set would be a server bug
|
||||
uids, ok := uidSet.Nums()
|
||||
if !ok {
|
||||
panic("imap: SearchData.All is a dynamic number set")
|
||||
}
|
||||
return uids
|
||||
}
|
||||
|
||||
// searchRes is a special empty UIDSet which can be used as a marker. It has
|
||||
// a non-zero cap so that its data pointer is non-nil and can be compared.
|
||||
//
|
||||
// It's a UIDSet rather than a SeqSet so that it can be passed to the
|
||||
// UID EXPUNGE command.
|
||||
var (
|
||||
searchRes = make(UIDSet, 0, 1)
|
||||
searchResAddr = reflect.ValueOf(searchRes).Pointer()
|
||||
)
|
||||
|
||||
// SearchRes returns a special marker which can be used instead of a UIDSet to
|
||||
// reference the last SEARCH result. On the wire, it's encoded as '$'.
|
||||
//
|
||||
// It requires IMAP4rev2 or the SEARCHRES extension.
|
||||
func SearchRes() UIDSet {
|
||||
return searchRes
|
||||
}
|
||||
|
||||
// IsSearchRes checks whether a sequence set is a reference to the last SEARCH
|
||||
// result. See SearchRes.
|
||||
func IsSearchRes(numSet NumSet) bool {
|
||||
return reflect.ValueOf(numSet).Pointer() == searchResAddr
|
||||
}
|
||||
31
vendor/github.com/emersion/go-imap/v2/select.go
generated
vendored
Normal file
31
vendor/github.com/emersion/go-imap/v2/select.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package imap
|
||||
|
||||
// SelectOptions contains options for the SELECT or EXAMINE command.
|
||||
type SelectOptions struct {
|
||||
ReadOnly bool
|
||||
CondStore bool // requires CONDSTORE
|
||||
}
|
||||
|
||||
// SelectData is the data returned by a SELECT command.
|
||||
//
|
||||
// In the old RFC 2060, PermanentFlags, UIDNext and UIDValidity are optional.
|
||||
type SelectData struct {
|
||||
// Flags defined for this mailbox
|
||||
Flags []Flag
|
||||
// Flags that the client can change permanently
|
||||
PermanentFlags []Flag
|
||||
// Number of messages in this mailbox (aka. "EXISTS")
|
||||
NumMessages uint32
|
||||
// Sequence number of the first unseen message. Obsolete, IMAP4rev1 only.
|
||||
// Server-only, not supported in imapclient.
|
||||
FirstUnseenSeqNum uint32
|
||||
// Number of recent messages in this mailbox. Obsolete, IMAP4rev1 only.
|
||||
// Server-only, not supported in imapclient.
|
||||
NumRecent uint32
|
||||
UIDNext UID
|
||||
UIDValidity uint32
|
||||
|
||||
List *ListData // requires IMAP4rev2
|
||||
|
||||
HighestModSeq uint64 // requires CONDSTORE
|
||||
}
|
||||
35
vendor/github.com/emersion/go-imap/v2/status.go
generated
vendored
Normal file
35
vendor/github.com/emersion/go-imap/v2/status.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package imap
|
||||
|
||||
// StatusOptions contains options for the STATUS command.
|
||||
type StatusOptions struct {
|
||||
NumMessages bool
|
||||
NumRecent bool // Obsolete, IMAP4rev1 only. Server-only, not supported in imapclient.
|
||||
UIDNext bool
|
||||
UIDValidity bool
|
||||
NumUnseen bool
|
||||
NumDeleted bool // requires IMAP4rev2 or QUOTA
|
||||
Size bool // requires IMAP4rev2 or STATUS=SIZE
|
||||
|
||||
AppendLimit bool // requires APPENDLIMIT
|
||||
DeletedStorage bool // requires QUOTA=RES-STORAGE
|
||||
HighestModSeq bool // requires CONDSTORE
|
||||
}
|
||||
|
||||
// StatusData is the data returned by a STATUS command.
|
||||
//
|
||||
// The mailbox name is always populated. The remaining fields are optional.
|
||||
type StatusData struct {
|
||||
Mailbox string
|
||||
|
||||
NumMessages *uint32
|
||||
NumRecent *uint32 // Obsolete, IMAP4rev1 only. Server-only, not supported in imapclient.
|
||||
UIDNext UID
|
||||
UIDValidity uint32
|
||||
NumUnseen *uint32
|
||||
NumDeleted *uint32
|
||||
Size *int64
|
||||
|
||||
AppendLimit *uint32
|
||||
DeletedStorage *int64
|
||||
HighestModSeq uint64
|
||||
}
|
||||
22
vendor/github.com/emersion/go-imap/v2/store.go
generated
vendored
Normal file
22
vendor/github.com/emersion/go-imap/v2/store.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package imap
|
||||
|
||||
// StoreOptions contains options for the STORE command.
|
||||
type StoreOptions struct {
|
||||
UnchangedSince uint64 // requires CONDSTORE
|
||||
}
|
||||
|
||||
// StoreFlagsOp is a flag operation: set, add or delete.
|
||||
type StoreFlagsOp int
|
||||
|
||||
const (
|
||||
StoreFlagsSet StoreFlagsOp = iota
|
||||
StoreFlagsAdd
|
||||
StoreFlagsDel
|
||||
)
|
||||
|
||||
// StoreFlags alters message flags.
|
||||
type StoreFlags struct {
|
||||
Op StoreFlagsOp
|
||||
Silent bool
|
||||
Flags []Flag
|
||||
}
|
||||
9
vendor/github.com/emersion/go-imap/v2/thread.go
generated
vendored
Normal file
9
vendor/github.com/emersion/go-imap/v2/thread.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package imap
|
||||
|
||||
// ThreadAlgorithm is a threading algorithm.
|
||||
type ThreadAlgorithm string
|
||||
|
||||
const (
|
||||
ThreadOrderedSubject ThreadAlgorithm = "ORDEREDSUBJECT"
|
||||
ThreadReferences ThreadAlgorithm = "REFERENCES"
|
||||
)
|
||||
Reference in New Issue
Block a user