Initial commit
This commit is contained in:
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
Normal file
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
/client.go
|
||||
/server.go
|
||||
coverage.txt
|
5
vendor/github.com/emersion/go-imap/.travis.yml
generated
vendored
Normal file
5
vendor/github.com/emersion/go-imap/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh)
|
||||
after_script: bash <(curl -s https://codecov.io/bash)
|
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 The Go-IMAP Authors
|
||||
Copyright (c) 2016 emersion
|
||||
Copyright (c) 2016 Proton Technologies AG
|
||||
|
||||
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.
|
183
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
183
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
# go-imap
|
||||
|
||||
[](https://godoc.org/github.com/emersion/go-imap)
|
||||
[](https://travis-ci.org/emersion/go-imap)
|
||||
[](https://codecov.io/gh/emersion/go-imap)
|
||||
[](https://goreportcard.com/report/github.com/emersion/go-imap)
|
||||
[](https://github.com/emersion/stability-badges#unstable)
|
||||
[](https://gitter.im/goimap/Lobby)
|
||||
|
||||
An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
|
||||
can be used to build a client and/or a server.
|
||||
|
||||
```bash
|
||||
go get github.com/emersion/go-imap/...
|
||||
```
|
||||
|
||||
## Why?
|
||||
|
||||
Other IMAP implementations in Go:
|
||||
* Require to make [many type assertions or conversions](https://github.com/emersion/neutron/blob/ca635850e2223d6cfe818664ef901fa6e3c1d859/backend/imap/util.go#L110)
|
||||
* Are not idiomatic or are [ugly](https://github.com/jordwest/imap-server/blob/master/conn/commands.go#L53)
|
||||
* Are [not pleasant to use](https://github.com/emersion/neutron/blob/ca635850e2223d6cfe818664ef901fa6e3c1d859/backend/imap/messages.go#L228)
|
||||
* Implement a server _xor_ a client, not both
|
||||
* Don't implement unilateral updates (i.e. the server can't notify clients for
|
||||
new messages)
|
||||
* Do not have a good test coverage
|
||||
* Don't handle encoding and charset automatically
|
||||
|
||||
## Usage
|
||||
|
||||
### Client [](https://godoc.org/github.com/emersion/go-imap/client)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Connecting to server...")
|
||||
|
||||
// Connect to server
|
||||
c, err := client.DialTLS("mail.example.org:993", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Connected")
|
||||
|
||||
// Don't forget to logout
|
||||
defer c.Logout()
|
||||
|
||||
// Login
|
||||
if err := c.Login("username", "password"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Logged in")
|
||||
|
||||
// List mailboxes
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
go func () {
|
||||
done <- c.List("", "*", mailboxes)
|
||||
}()
|
||||
|
||||
log.Println("Mailboxes:")
|
||||
for m := range mailboxes {
|
||||
log.Println("* " + m.Name)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Select INBOX
|
||||
mbox, err := c.Select("INBOX", false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Flags for INBOX:", mbox.Flags)
|
||||
|
||||
// Get the last 4 messages
|
||||
from := uint32(1)
|
||||
to := mbox.Messages
|
||||
if mbox.Messages > 3 {
|
||||
// We're using unsigned integers here, only substract if the result is > 0
|
||||
from = mbox.Messages - 3
|
||||
}
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(from, to)
|
||||
|
||||
messages := make(chan *imap.Message, 10)
|
||||
done = make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.Fetch(seqset, []string{imap.EnvelopeMsgAttr}, messages)
|
||||
}()
|
||||
|
||||
log.Println("Last 4 messages:")
|
||||
for msg := range messages {
|
||||
log.Println("* " + msg.Envelope.Subject)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Done!")
|
||||
}
|
||||
```
|
||||
|
||||
### Server [](https://godoc.org/github.com/emersion/go-imap/server)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/server"
|
||||
"github.com/emersion/go-imap/backend/memory"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a memory backend
|
||||
be := memory.New()
|
||||
|
||||
// Create a new server
|
||||
s := server.New(be)
|
||||
s.Addr = ":1143"
|
||||
// Since we will use this server for testing only, we can allow plain text
|
||||
// authentication over unencrypted connections
|
||||
s.AllowInsecureAuth = true
|
||||
|
||||
log.Println("Starting IMAP server at localhost:1143")
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can now use `telnet localhost 1143` to manually connect to the server.
|
||||
|
||||
## Extending go-imap
|
||||
|
||||
### Extensions
|
||||
|
||||
Commands defined in IMAP extensions are available in other packages. See [the
|
||||
wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
|
||||
to learn how to use them.
|
||||
|
||||
* [APPENDLIMIT](https://github.com/emersion/go-imap-appendlimit)
|
||||
* [COMPRESS](https://github.com/emersion/go-imap-compress)
|
||||
* [ENABLE](https://github.com/emersion/go-imap-enable)
|
||||
* [ID](https://github.com/ProtonMail/go-imap-id)
|
||||
* [IDLE](https://github.com/emersion/go-imap-idle)
|
||||
* [MOVE](https://github.com/emersion/go-imap-move)
|
||||
* [QUOTA](https://github.com/emersion/go-imap-quota)
|
||||
* [SPECIAL-USE](https://github.com/emersion/go-imap-specialuse)
|
||||
* [UNSELECT](https://github.com/emersion/go-imap-unselect)
|
||||
* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)
|
||||
|
||||
### Server backends
|
||||
|
||||
* [Memory](https://github.com/emersion/go-imap/tree/master/backend/memory) (for testing)
|
||||
* [Multi](https://github.com/emersion/go-imap-multi)
|
||||
* [PGP](https://github.com/emersion/go-imap-pgp)
|
||||
* [Proxy](https://github.com/emersion/go-imap-proxy)
|
||||
|
||||
### Related projects
|
||||
|
||||
* [go-message](https://github.com/emersion/go-message) - parsing and formatting MIME and mail messages
|
||||
* [go-pgpmail](https://github.com/emersion/go-pgpmail) - decrypting and encrypting mails with OpenPGP
|
||||
* [go-sasl](https://github.com/emersion/go-sasl) - sending and receiving SASL authentications
|
||||
* [go-smtp](https://github.com/emersion/go-smtp) - building SMTP clients and servers
|
||||
* [go-dkim](https://github.com/emersion/go-dkim) - creating and verifying DKIM signatures
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
16
vendor/github.com/emersion/go-imap/backend/backend.go
generated
vendored
Normal file
16
vendor/github.com/emersion/go-imap/backend/backend.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Package backend defines an IMAP server backend interface.
|
||||
package backend
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrInvalidCredentials is returned by Backend.Login when a username or a
|
||||
// password is incorrect.
|
||||
var ErrInvalidCredentials = errors.New("Invalid credentials")
|
||||
|
||||
// Backend is an IMAP server backend. A backend operation always deals with
|
||||
// users.
|
||||
type Backend interface {
|
||||
// Login authenticates a user. If the username or the password is incorrect,
|
||||
// it returns ErrInvalidCredentials.
|
||||
Login(username, password string) (User, error)
|
||||
}
|
78
vendor/github.com/emersion/go-imap/backend/mailbox.go
generated
vendored
Normal file
78
vendor/github.com/emersion/go-imap/backend/mailbox.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Mailbox represents a mailbox belonging to a user in the mail storage system.
|
||||
// A mailbox operation always deals with messages.
|
||||
type Mailbox interface {
|
||||
// Name returns this mailbox name.
|
||||
Name() string
|
||||
|
||||
// Info returns this mailbox info.
|
||||
Info() (*imap.MailboxInfo, error)
|
||||
|
||||
// Status returns this mailbox status. The fields Name, Flags and
|
||||
// PermanentFlags in the returned MailboxStatus must be always populated. This
|
||||
// function does not affect the state of any messages in the mailbox. See RFC
|
||||
// 3501 section 6.3.10 for a list of items that can be requested.
|
||||
Status(items []string) (*imap.MailboxStatus, error)
|
||||
|
||||
// SetSubscribed adds or removes the mailbox to the server's set of "active"
|
||||
// or "subscribed" mailboxes.
|
||||
SetSubscribed(subscribed bool) error
|
||||
|
||||
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
|
||||
// refers to any implementation-dependent housekeeping associated with the
|
||||
// mailbox (e.g., resolving the server's in-memory state of the mailbox with
|
||||
// the state on its disk). A checkpoint MAY take a non-instantaneous amount of
|
||||
// real time to complete. If a server implementation has no such housekeeping
|
||||
// considerations, CHECK is equivalent to NOOP.
|
||||
Check() error
|
||||
|
||||
// ListMessages returns a list of messages. seqset must be interpreted as UIDs
|
||||
// if uid is set to true and as message sequence numbers otherwise. See RFC
|
||||
// 3501 section 6.4.5 for a list of items that can be requested.
|
||||
//
|
||||
// Messages must be sent to ch. When the function returns, ch must be closed.
|
||||
ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) error
|
||||
|
||||
// SearchMessages searches messages. The returned list must contain UIDs if
|
||||
// uid is set to true, or sequence numbers otherwise.
|
||||
SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)
|
||||
|
||||
// CreateMessage appends a new message to this mailbox. The \Recent flag will
|
||||
// be added no matter flags is empty or not. If date is nil, the current time
|
||||
// will be used.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a mailbox update.
|
||||
CreateMessage(flags []string, date time.Time, body imap.Literal) error
|
||||
|
||||
// UpdateMessagesFlags alters flags for the specified message(s).
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a message update.
|
||||
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error
|
||||
|
||||
// CopyMessages copies the specified message(s) to the end of the specified
|
||||
// destination mailbox. The flags and internal date of the message(s) SHOULD
|
||||
// be preserved, and the Recent flag SHOULD be set, in the copy.
|
||||
//
|
||||
// If the destination mailbox does not exist, a server SHOULD return an error.
|
||||
// It SHOULD NOT automatically create the mailbox.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a mailbox update.
|
||||
CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error
|
||||
|
||||
// Expunge permanently removes all messages that have the \Deleted flag set
|
||||
// from the currently selected mailbox.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via an expunge update.
|
||||
Expunge() error
|
||||
}
|
109
vendor/github.com/emersion/go-imap/backend/updates.go
generated
vendored
Normal file
109
vendor/github.com/emersion/go-imap/backend/updates.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Update contains user and mailbox information about an unilateral backend
|
||||
// update.
|
||||
type Update struct {
|
||||
// The user targeted by this update. If empty, all connected users will
|
||||
// be notified.
|
||||
Username string
|
||||
// The mailbox targeted by this update. If empty, the update targets all
|
||||
// mailboxes.
|
||||
Mailbox string
|
||||
|
||||
// A channel that will be closed once the update has been processed.
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when the update has been broadcast to
|
||||
// all clients.
|
||||
func (u *Update) Done() <-chan struct{} {
|
||||
if u.done == nil {
|
||||
u.done = make(chan struct{})
|
||||
}
|
||||
return u.done
|
||||
}
|
||||
|
||||
// DoneUpdate marks an update as done.
|
||||
// TODO: remove this function
|
||||
func DoneUpdate(u *Update) {
|
||||
if u.done != nil {
|
||||
close(u.done)
|
||||
}
|
||||
}
|
||||
|
||||
// StatusUpdate is a status update. See RFC 3501 section 7.1 for a list of
|
||||
// status responses.
|
||||
type StatusUpdate struct {
|
||||
Update
|
||||
*imap.StatusResp
|
||||
}
|
||||
|
||||
// MailboxUpdate is a mailbox update.
|
||||
type MailboxUpdate struct {
|
||||
Update
|
||||
*imap.MailboxStatus
|
||||
}
|
||||
|
||||
// MessageUpdate is a message update.
|
||||
type MessageUpdate struct {
|
||||
Update
|
||||
*imap.Message
|
||||
}
|
||||
|
||||
// ExpungeUpdate is an expunge update.
|
||||
type ExpungeUpdate struct {
|
||||
Update
|
||||
SeqNum uint32
|
||||
}
|
||||
|
||||
// Updater is a Backend that implements Updater is able to send unilateral
|
||||
// backend updates. Backends not implementing this interface don't correctly
|
||||
// send unilateral updates, for instance if a user logs in from two connections
|
||||
// and deletes a message from one of them, the over is not aware that such a
|
||||
// mesage has been deleted. More importantly, backends implementing Updater can
|
||||
// notify the user for external updates such as new message notifications.
|
||||
type Updater interface {
|
||||
// Updates returns a set of channels where updates are sent to.
|
||||
Updates() <-chan interface{}
|
||||
}
|
||||
|
||||
// UpdaterMailbox is a Mailbox that implements UpdaterMailbox is able to poll
|
||||
// updates for new messages or message status updates during a period of
|
||||
// inactivity.
|
||||
type UpdaterMailbox interface {
|
||||
// Poll requests mailbox updates.
|
||||
Poll() error
|
||||
}
|
||||
|
||||
// WaitUpdates returns a channel that's closed when all provided updates have
|
||||
// been dispatched to all clients. It panics if one of the provided value is
|
||||
// not an update.
|
||||
func WaitUpdates(updates ...interface{}) <-chan struct{} {
|
||||
done := make(chan struct{})
|
||||
|
||||
var chs []<-chan struct{}
|
||||
for _, u := range updates {
|
||||
uu, ok := u.(interface {
|
||||
Done() <-chan struct{}
|
||||
})
|
||||
if !ok {
|
||||
panic("imap: cannot wait for update: provided value is not a valid update")
|
||||
}
|
||||
|
||||
chs = append(chs, uu.Done())
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Wait for all updates to be sent
|
||||
for _, ch := range chs {
|
||||
<-ch
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
return done
|
||||
}
|
92
vendor/github.com/emersion/go-imap/backend/user.go
generated
vendored
Normal file
92
vendor/github.com/emersion/go-imap/backend/user.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package backend
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNoSuchMailbox is returned by User.GetMailbox, User.DeleteMailbox and
|
||||
// User.RenameMailbox when retrieving, deleting or renaming a mailbox that
|
||||
// doesn't exist.
|
||||
ErrNoSuchMailbox = errors.New("No such mailbox")
|
||||
// ErrMailboxAlreadyExists is returned by User.CreateMailbox and
|
||||
// User.RenameMailbox when creating or renaming mailbox that already exists.
|
||||
ErrMailboxAlreadyExists = errors.New("Mailbox already exists")
|
||||
)
|
||||
|
||||
// User represents a user in the mail storage system. A user operation always
|
||||
// deals with mailboxes.
|
||||
type User interface {
|
||||
// Username returns this user's username.
|
||||
Username() string
|
||||
|
||||
// ListMailboxes returns a list of mailboxes belonging to this user. If
|
||||
// subscribed is set to true, only returns subscribed mailboxes.
|
||||
ListMailboxes(subscribed bool) ([]Mailbox, error)
|
||||
|
||||
// GetMailbox returns a mailbox. If it doesn't exist, it returns
|
||||
// ErrNoSuchMailbox.
|
||||
GetMailbox(name string) (Mailbox, error)
|
||||
|
||||
// CreateMailbox creates a new mailbox.
|
||||
//
|
||||
// If the mailbox already exists, an error must be returned. If the mailbox
|
||||
// name is suffixed with the server's hierarchy separator character, this is a
|
||||
// declaration that the client intends to create mailbox names under this name
|
||||
// in the hierarchy.
|
||||
//
|
||||
// If the server's hierarchy separator character appears elsewhere in the
|
||||
// name, the server SHOULD create any superior hierarchical names that are
|
||||
// needed for the CREATE command to be successfully completed. In other
|
||||
// words, an attempt to create "foo/bar/zap" on a server in which "/" is the
|
||||
// hierarchy separator character SHOULD create foo/ and foo/bar/ if they do
|
||||
// not already exist.
|
||||
//
|
||||
// If a new mailbox is created with the same name as a mailbox which was
|
||||
// deleted, its unique identifiers MUST be greater than any unique identifiers
|
||||
// used in the previous incarnation of the mailbox UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
CreateMailbox(name string) error
|
||||
|
||||
// DeleteMailbox permanently remove the mailbox with the given name. It is an
|
||||
// error to // attempt to delete INBOX or a mailbox name that does not exist.
|
||||
//
|
||||
// The DELETE command MUST NOT remove inferior hierarchical names. For
|
||||
// example, if a mailbox "foo" has an inferior "foo.bar" (assuming "." is the
|
||||
// hierarchy delimiter character), removing "foo" MUST NOT remove "foo.bar".
|
||||
//
|
||||
// The value of the highest-used unique identifier of the deleted mailbox MUST
|
||||
// be preserved so that a new mailbox created with the same name will not
|
||||
// reuse the identifiers of the former incarnation, UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
DeleteMailbox(name string) error
|
||||
|
||||
// RenameMailbox changes the name of a mailbox. It is an error to attempt to
|
||||
// rename from a mailbox name that does not exist or to a mailbox name that
|
||||
// already exists.
|
||||
//
|
||||
// If the name has inferior hierarchical names, then the inferior hierarchical
|
||||
// names MUST also be renamed. For example, a rename of "foo" to "zap" will
|
||||
// rename "foo/bar" (assuming "/" is the hierarchy delimiter character) to
|
||||
// "zap/bar".
|
||||
//
|
||||
// If the server's hierarchy separator character appears in the name, the
|
||||
// server SHOULD create any superior hierarchical names that are needed for
|
||||
// the RENAME command to complete successfully. In other words, an attempt to
|
||||
// rename "foo/bar/zap" to baz/rag/zowie on a server in which "/" is the
|
||||
// hierarchy separator character SHOULD create baz/ and baz/rag/ if they do
|
||||
// not already exist.
|
||||
//
|
||||
// The value of the highest-used unique identifier of the old mailbox name
|
||||
// MUST be preserved so that a new mailbox created with the same name will not
|
||||
// reuse the identifiers of the former incarnation, UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
//
|
||||
// Renaming INBOX is permitted, and has special behavior. It moves all
|
||||
// messages in INBOX to a new mailbox with the given name, leaving INBOX
|
||||
// empty. If the server implementation supports inferior hierarchical names
|
||||
// of INBOX, these are unaffected by a rename of INBOX.
|
||||
RenameMailbox(existingName, newName string) error
|
||||
|
||||
// Logout is called when this User will no longer be used, likely because the
|
||||
// client closed the connection.
|
||||
Logout() error
|
||||
}
|
89
vendor/github.com/emersion/go-imap/command.go
generated
vendored
Normal file
89
vendor/github.com/emersion/go-imap/command.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IMAP4rev1 commands.
|
||||
const (
|
||||
Capability string = "CAPABILITY"
|
||||
Noop = "NOOP"
|
||||
Logout = "LOGOUT"
|
||||
StartTLS = "STARTTLS"
|
||||
|
||||
Authenticate = "AUTHENTICATE"
|
||||
Login = "LOGIN"
|
||||
|
||||
Select = "SELECT"
|
||||
Examine = "EXAMINE"
|
||||
Create = "CREATE"
|
||||
Delete = "DELETE"
|
||||
Rename = "RENAME"
|
||||
Subscribe = "SUBSCRIBE"
|
||||
Unsubscribe = "UNSUBSCRIBE"
|
||||
List = "LIST"
|
||||
Lsub = "LSUB"
|
||||
Status = "STATUS"
|
||||
Append = "APPEND"
|
||||
|
||||
Check = "CHECK"
|
||||
Close = "CLOSE"
|
||||
Expunge = "EXPUNGE"
|
||||
Search = "SEARCH"
|
||||
Fetch = "FETCH"
|
||||
Store = "STORE"
|
||||
Copy = "COPY"
|
||||
Uid = "UID"
|
||||
)
|
||||
|
||||
// A value that can be converted to a command.
|
||||
type Commander interface {
|
||||
Command() *Command
|
||||
}
|
||||
|
||||
// A command.
|
||||
type Command struct {
|
||||
// The command tag. It acts as a unique identifier for this command. If empty,
|
||||
// the command is untagged.
|
||||
Tag string
|
||||
// The command name.
|
||||
Name string
|
||||
// The command arguments.
|
||||
Arguments []interface{}
|
||||
}
|
||||
|
||||
// Implements the Commander interface.
|
||||
func (cmd *Command) Command() *Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (cmd *Command) WriteTo(w *Writer) error {
|
||||
tag := cmd.Tag
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
fields := []interface{}{tag, cmd.Name}
|
||||
fields = append(fields, cmd.Arguments...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// Parse a command from fields.
|
||||
func (cmd *Command) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("imap: cannot parse command: no enough fields")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Tag, ok = fields[0].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid tag")
|
||||
}
|
||||
if cmd.Name, ok = fields[1].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid name")
|
||||
}
|
||||
cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
|
||||
|
||||
cmd.Arguments = fields[2:]
|
||||
return nil
|
||||
}
|
95
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
Normal file
95
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Append is an APPEND command, as defined in RFC 3501 section 6.3.11.
|
||||
type Append struct {
|
||||
Mailbox string
|
||||
Flags []string
|
||||
Date time.Time
|
||||
Message imap.Literal
|
||||
}
|
||||
|
||||
func (cmd *Append) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
args = append(args, mailbox)
|
||||
|
||||
if cmd.Flags != nil {
|
||||
flags := make([]interface{}, len(cmd.Flags))
|
||||
for i, flag := range cmd.Flags {
|
||||
flags[i] = flag
|
||||
}
|
||||
args = append(args, flags)
|
||||
}
|
||||
|
||||
if !cmd.Date.IsZero() {
|
||||
args = append(args, cmd.Date)
|
||||
}
|
||||
|
||||
args = append(args, cmd.Message)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Append,
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Append) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
// Parse mailbox name
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err = utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
// Parse message literal
|
||||
litIndex := len(fields) - 1
|
||||
var ok bool
|
||||
if cmd.Message, ok = fields[litIndex].(imap.Literal); !ok {
|
||||
return errors.New("Message must be a literal")
|
||||
}
|
||||
|
||||
// Remaining fields a optional
|
||||
fields = fields[1:litIndex]
|
||||
if len(fields) > 0 {
|
||||
// Parse flags list
|
||||
if flags, ok := fields[0].([]interface{}); ok {
|
||||
if cmd.Flags, err = imap.ParseStringList(flags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, flag := range cmd.Flags {
|
||||
cmd.Flags[i] = imap.CanonicalFlag(flag)
|
||||
}
|
||||
|
||||
fields = fields[1:]
|
||||
}
|
||||
|
||||
// Parse date
|
||||
if len(fields) > 0 {
|
||||
date, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Date must be a string")
|
||||
}
|
||||
if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
83
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
Normal file
83
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// AuthenticateConn is a connection that supports IMAP authentication.
|
||||
type AuthenticateConn interface {
|
||||
io.Reader
|
||||
|
||||
// WriteResp writes an IMAP response to this connection.
|
||||
WriteResp(res imap.WriterTo) error
|
||||
}
|
||||
|
||||
// Authenticate is an AUTHENTICATE command, as defined in RFC 3501 section
|
||||
// 6.2.2.
|
||||
type Authenticate struct {
|
||||
Mechanism string
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Authenticate,
|
||||
Arguments: []interface{}{cmd.Mechanism},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Mechanism, ok = fields[0].(string); !ok {
|
||||
return errors.New("Mechanism must be a string")
|
||||
}
|
||||
|
||||
cmd.Mechanism = strings.ToUpper(cmd.Mechanism)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn AuthenticateConn) error {
|
||||
sasl, ok := mechanisms[cmd.Mechanism]
|
||||
if !ok {
|
||||
return errors.New("Unsupported mechanism")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
var response []byte
|
||||
for {
|
||||
challenge, done, err := sasl.Next(response)
|
||||
if err != nil || done {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(challenge)
|
||||
cont := &imap.ContinuationResp{Info: encoded}
|
||||
if err := conn.WriteResp(cont); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner.Scan()
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded = scanner.Text()
|
||||
if encoded != "" {
|
||||
response, err = base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Capability is a CAPABILITY command, as defined in RFC 3501 section 6.1.1.
|
||||
type Capability struct{}
|
||||
|
||||
func (c *Capability) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Capability,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Capability) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Check is a CHECK command, as defined in RFC 3501 section 6.4.1.
|
||||
type Check struct{}
|
||||
|
||||
func (cmd *Check) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Check,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Check) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Close is a CLOSE command, as defined in RFC 3501 section 6.4.2.
|
||||
type Close struct{}
|
||||
|
||||
func (cmd *Close) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Close,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Close) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package commands implements IMAP commands defined in RFC 3501.
|
||||
package commands
|
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Copy is a COPY command, as defined in RFC 3501 section 6.4.7.
|
||||
type Copy struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Copy) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Copy,
|
||||
Arguments: []interface{}{cmd.SeqSet, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Copy) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if seqSet, ok := fields[0].(string); !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
} else if seqSet, err := imap.NewSeqSet(seqSet); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.SeqSet = seqSet
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[1].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
Normal file
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Create is a CREATE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Create struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Create) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Create,
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Create) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
Normal file
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Delete is a DELETE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Delete struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Delete) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Delete,
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Delete) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
Normal file
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Expunge is an EXPUNGE command, as defined in RFC 3501 section 6.4.3.
|
||||
type Expunge struct{}
|
||||
|
||||
func (cmd *Expunge) Command() *imap.Command {
|
||||
return &imap.Command{Name: imap.Expunge}
|
||||
}
|
||||
|
||||
func (cmd *Expunge) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
70
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
70
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5.
|
||||
type Fetch struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Items []string
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Command() *imap.Command {
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, item := range cmd.Items {
|
||||
if section, err := imap.NewBodySectionName(item); err == nil {
|
||||
items[i] = section
|
||||
} else {
|
||||
items[i] = item
|
||||
}
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Fetch,
|
||||
Arguments: []interface{}{cmd.SeqSet, items},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Sequence set must be a string")
|
||||
}
|
||||
|
||||
var err error
|
||||
if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch items := fields[1].(type) {
|
||||
case string: // A macro or a single item
|
||||
switch strings.ToUpper(items) {
|
||||
case "ALL":
|
||||
cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE"}
|
||||
case "FAST":
|
||||
cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE"}
|
||||
case "FULL":
|
||||
cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY"}
|
||||
default:
|
||||
cmd.Items = []string{strings.ToUpper(items)}
|
||||
}
|
||||
case []interface{}: // A list of items
|
||||
cmd.Items = make([]string, len(items))
|
||||
for i, v := range items {
|
||||
item, _ := v.(string)
|
||||
cmd.Items[i] = strings.ToUpper(item)
|
||||
}
|
||||
default:
|
||||
return errors.New("Items must be either a string or a list")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
57
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
Normal file
57
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// List is a LIST command, as defined in RFC 3501 section 6.3.8. If Subscribed
|
||||
// is set to true, LSUB will be used instead.
|
||||
type List struct {
|
||||
Reference string
|
||||
Mailbox string
|
||||
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (cmd *List) Command() *imap.Command {
|
||||
name := imap.List
|
||||
if cmd.Subscribed {
|
||||
name = imap.Lsub
|
||||
}
|
||||
|
||||
ref, _ := utf7.Encoder.String(cmd.Reference)
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{ref, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *List) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Reference name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
// TODO: canonical mailbox path
|
||||
cmd.Reference = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[1].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
Normal file
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Login is a LOGIN command, as defined in RFC 3501 section 6.2.2.
|
||||
type Login struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (cmd *Login) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Login,
|
||||
Arguments: []interface{}{cmd.Username, cmd.Password},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Login) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Username, ok = fields[0].(string); !ok {
|
||||
return errors.New("Username is not a string")
|
||||
}
|
||||
if cmd.Password, ok = fields[1].(string); !ok {
|
||||
return errors.New("Password is not a string")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Logout is a LOGOUT command, as defined in RFC 3501 section 6.1.3.
|
||||
type Logout struct{}
|
||||
|
||||
func (c *Logout) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Logout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Logout) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Noop is a NOOP command, as defined in RFC 3501 section 6.1.2.
|
||||
type Noop struct{}
|
||||
|
||||
func (c *Noop) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Noop,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Noop) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
48
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
48
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Rename is a RENAME command, as defined in RFC 3501 section 6.3.5.
|
||||
type Rename struct {
|
||||
Existing string
|
||||
New string
|
||||
}
|
||||
|
||||
func (cmd *Rename) Command() *imap.Command {
|
||||
existingName, _ := utf7.Encoder.String(cmd.Existing)
|
||||
newName, _ := utf7.Encoder.String(cmd.New)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Rename,
|
||||
Arguments: []interface{}{existingName, newName},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Rename) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if existingName, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if existingName, err := utf7.Decoder.String(existingName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Existing = imap.CanonicalMailboxName(existingName)
|
||||
}
|
||||
|
||||
if newName, ok := fields[1].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if newName, err := utf7.Decoder.String(newName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.New = imap.CanonicalMailboxName(newName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
Normal file
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Search is a SEARCH command, as defined in RFC 3501 section 6.4.4.
|
||||
type Search struct {
|
||||
Charset string
|
||||
Criteria *imap.SearchCriteria
|
||||
}
|
||||
|
||||
func (cmd *Search) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
if cmd.Charset != "" {
|
||||
args = append(args, "CHARSET", cmd.Charset)
|
||||
}
|
||||
args = append(args, cmd.Criteria.Format()...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Search,
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Search) Parse(fields []interface{}) error {
|
||||
if len(fields) == 0 {
|
||||
return errors.New("Missing search criteria")
|
||||
}
|
||||
|
||||
// Parse charset
|
||||
if f, ok := fields[0].(string); ok && strings.EqualFold(f, "CHARSET") {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Missing CHARSET value")
|
||||
}
|
||||
if cmd.Charset, ok = fields[1].(string); !ok {
|
||||
return errors.New("Charset must be a string")
|
||||
}
|
||||
fields = fields[2:]
|
||||
}
|
||||
|
||||
var charsetReader func(io.Reader) io.Reader
|
||||
charset := strings.ToLower(cmd.Charset)
|
||||
if charset != "utf-8" && charset != "us-ascii" && charset != "" {
|
||||
charsetReader = func(r io.Reader) io.Reader {
|
||||
r, _ = imap.CharsetReader(charset, r)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Criteria = new(imap.SearchCriteria)
|
||||
return cmd.Criteria.ParseWithCharset(fields, charsetReader)
|
||||
}
|
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
Normal file
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Select is a SELECT command, as defined in RFC 3501 section 6.3.1. If ReadOnly
|
||||
// is set to true, the EXAMINE command will be used instead.
|
||||
type Select struct {
|
||||
Mailbox string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
func (cmd *Select) Command() *imap.Command {
|
||||
name := imap.Select
|
||||
if cmd.ReadOnly {
|
||||
name = imap.Examine
|
||||
}
|
||||
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Select) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// StartTLS is a STARTTLS command, as defined in RFC 3501 section 6.2.1.
|
||||
type StartTLS struct{}
|
||||
|
||||
func (cmd *StartTLS) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.StartTLS,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
55
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
55
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Status is a STATUS command, as defined in RFC 3501 section 6.3.10.
|
||||
type Status struct {
|
||||
Mailbox string
|
||||
Items []string
|
||||
}
|
||||
|
||||
func (cmd *Status) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, f := range cmd.Items {
|
||||
items[i] = f
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Status,
|
||||
Arguments: []interface{}{mailbox, items},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Status) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, ok := fields[0].(string); !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
if items, ok := fields[1].([]interface{}); !ok {
|
||||
return errors.New("Items must be a list")
|
||||
} else {
|
||||
cmd.Items = make([]string, len(items))
|
||||
for i, v := range items {
|
||||
item, _ := v.(string)
|
||||
cmd.Items[i] = strings.ToUpper(item)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
45
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
Normal file
45
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Store is a STORE command, as defined in RFC 3501 section 6.4.6.
|
||||
type Store struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Item string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (cmd *Store) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: imap.Store,
|
||||
Arguments: []interface{}{cmd.SeqSet, cmd.Item, cmd.Value},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Store) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
}
|
||||
if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.Item, ok = fields[1].(string); !ok {
|
||||
return errors.New("Item name must be a string")
|
||||
}
|
||||
cmd.Item = strings.ToUpper(cmd.Item)
|
||||
|
||||
cmd.Value = fields[2]
|
||||
|
||||
return
|
||||
}
|
71
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
Normal file
71
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Subscribe is a SUBSCRIBE command, as defined in RFC 3501 section 6.3.6.
|
||||
type Subscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Subscribe,
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enogh arguments")
|
||||
}
|
||||
|
||||
mailbox, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
}
|
||||
|
||||
if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// An UNSUBSCRIBE command.
|
||||
// See RFC 3501 section 6.3.7
|
||||
type Unsubscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoder.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Unsubscribe,
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enogh arguments")
|
||||
}
|
||||
|
||||
mailbox, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
}
|
||||
|
||||
if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
Normal file
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Uid is a UID command, as defined in RFC 3501 section 6.4.8. It wraps another
|
||||
// command (e.g. wrapping a Fetch command will result in a UID FETCH).
|
||||
type Uid struct {
|
||||
Cmd imap.Commander
|
||||
}
|
||||
|
||||
func (cmd *Uid) Command() *imap.Command {
|
||||
inner := cmd.Cmd.Command()
|
||||
|
||||
args := []interface{}{inner.Name}
|
||||
args = append(args, inner.Arguments...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: imap.Uid,
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Uid) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No command name specified")
|
||||
}
|
||||
|
||||
name, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Command name must be a string")
|
||||
}
|
||||
|
||||
cmd.Cmd = &imap.Command{
|
||||
Name: strings.ToUpper(name), // Command names are case-insensitive
|
||||
Arguments: fields[1:],
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
197
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
Normal file
197
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// A connection state.
|
||||
// See RFC 3501 section 3.
|
||||
type ConnState int
|
||||
|
||||
const (
|
||||
// In the connecting state, the server has not yet sent a greeting and no
|
||||
// command can be issued.
|
||||
ConnectingState = 0
|
||||
|
||||
// In the not authenticated state, the client MUST supply
|
||||
// authentication credentials before most commands will be
|
||||
// permitted. This state is entered when a connection starts
|
||||
// unless the connection has been pre-authenticated.
|
||||
NotAuthenticatedState ConnState = 1 << 0
|
||||
|
||||
// In the authenticated state, the client is authenticated and MUST
|
||||
// select a mailbox to access before commands that affect messages
|
||||
// will be permitted. This state is entered when a
|
||||
// pre-authenticated connection starts, when acceptable
|
||||
// authentication credentials have been provided, after an error in
|
||||
// selecting a mailbox, or after a successful CLOSE command.
|
||||
AuthenticatedState = 1 << 1
|
||||
|
||||
// In a selected state, a mailbox has been selected to access.
|
||||
// This state is entered when a mailbox has been successfully
|
||||
// selected.
|
||||
SelectedState = AuthenticatedState + 1<<2
|
||||
|
||||
// In the logout state, the connection is being terminated. This
|
||||
// state can be entered as a result of a client request (via the
|
||||
// LOGOUT command) or by unilateral action on the part of either
|
||||
// the client or server.
|
||||
LogoutState = 1 << 3
|
||||
|
||||
// ConnectedState is either NotAuthenticatedState, AuthenticatedState or
|
||||
// SelectedState.
|
||||
ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
|
||||
)
|
||||
|
||||
// A function that upgrades a connection.
|
||||
//
|
||||
// This should only be used by libraries implementing an IMAP extension (e.g.
|
||||
// COMPRESS).
|
||||
type ConnUpgrader func(conn net.Conn) (net.Conn, error)
|
||||
|
||||
type debugWriter struct {
|
||||
io.Writer
|
||||
|
||||
local io.Writer
|
||||
remote io.Writer
|
||||
}
|
||||
|
||||
// NewDebugWriter creates a new io.Writer that will write local network activity
|
||||
// to local and remote network activity to remote.
|
||||
func NewDebugWriter(local, remote io.Writer) io.Writer {
|
||||
return &debugWriter{Writer: local, local: local, remote: remote}
|
||||
}
|
||||
|
||||
type multiFlusher struct {
|
||||
flushers []flusher
|
||||
}
|
||||
|
||||
func (mf *multiFlusher) Flush() error {
|
||||
for _, f := range mf.flushers {
|
||||
if err := f.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMultiFlusher(flushers ...flusher) flusher {
|
||||
return &multiFlusher{flushers}
|
||||
}
|
||||
|
||||
// An IMAP connection.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
*Reader
|
||||
*Writer
|
||||
|
||||
br *bufio.Reader
|
||||
bw *bufio.Writer
|
||||
|
||||
waits chan struct{}
|
||||
|
||||
// Print all commands and responses to this io.Writer.
|
||||
debug io.Writer
|
||||
}
|
||||
|
||||
// NewConn creates a new IMAP connection.
|
||||
func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
|
||||
c := &Conn{Conn: conn, Reader: r, Writer: w}
|
||||
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Conn) init() {
|
||||
r := io.Reader(c.Conn)
|
||||
w := io.Writer(c.Conn)
|
||||
|
||||
if c.debug != nil {
|
||||
localDebug, remoteDebug := c.debug, c.debug
|
||||
if debug, ok := c.debug.(*debugWriter); ok {
|
||||
localDebug, remoteDebug = debug.local, debug.remote
|
||||
}
|
||||
|
||||
if localDebug != nil {
|
||||
w = io.MultiWriter(c.Conn, localDebug)
|
||||
}
|
||||
if remoteDebug != nil {
|
||||
r = io.TeeReader(c.Conn, remoteDebug)
|
||||
}
|
||||
}
|
||||
|
||||
if c.br == nil {
|
||||
c.br = bufio.NewReader(r)
|
||||
c.Reader.reader = c.br
|
||||
} else {
|
||||
c.br.Reset(r)
|
||||
}
|
||||
|
||||
if c.bw == nil {
|
||||
c.bw = bufio.NewWriter(w)
|
||||
c.Writer.Writer = c.bw
|
||||
} else {
|
||||
c.bw.Reset(w)
|
||||
}
|
||||
|
||||
if f, ok := c.Conn.(flusher); ok {
|
||||
c.Writer.Writer = struct {
|
||||
io.Writer
|
||||
flusher
|
||||
}{
|
||||
c.bw,
|
||||
newMultiFlusher(c.bw, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.Writer.Write(b)
|
||||
}
|
||||
|
||||
// Flush writes any buffered data to the underlying connection.
|
||||
func (c *Conn) Flush() error {
|
||||
if err := c.Writer.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
||||
// tunnel.
|
||||
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
||||
// Flush all buffered data
|
||||
if err := c.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Block reads and writes during the upgrading process
|
||||
c.waits = make(chan struct{})
|
||||
defer close(c.waits)
|
||||
|
||||
upgraded, err := upgrader(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Conn = upgraded
|
||||
c.init()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the connection to be ready for reads and writes.
|
||||
func (c *Conn) Wait() {
|
||||
if c.waits != nil {
|
||||
<-c.waits
|
||||
}
|
||||
}
|
||||
|
||||
// SetDebug defines an io.Writer to which all network activity will be logged.
|
||||
// If nil is provided, network activity will not be logged.
|
||||
func (c *Conn) SetDebug(w io.Writer) {
|
||||
c.debug = w
|
||||
c.init()
|
||||
}
|
72
vendor/github.com/emersion/go-imap/date.go
generated
vendored
Normal file
72
vendor/github.com/emersion/go-imap/date.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Date and time layouts.
|
||||
// Dovecot adds a leading zero to dates:
|
||||
// https://github.com/dovecot/core/blob/4fbd5c5e113078e72f29465ccc96d44955ceadc2/src/lib-imap/imap-date.c#L166
|
||||
// Cyrus adds a leading space to dates:
|
||||
// https://github.com/cyrusimap/cyrus-imapd/blob/1cb805a3bffbdf829df0964f3b802cdc917e76db/lib/times.c#L543
|
||||
// GMail doesn't support leading spaces in dates used in SEARCH commands.
|
||||
const (
|
||||
// Defined in RFC 3501 as date-text on page 83.
|
||||
DateLayout = "_2-Jan-2006"
|
||||
// Defined in RFC 3501 as date-time on page 83.
|
||||
DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
|
||||
// Defined in RFC 5322 section 3.3, mentioned as env-date in RFC 3501 page 84.
|
||||
envelopeDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
// Use as an example in RFC 3501 page 54.
|
||||
searchDateLayout = "2-Jan-2006"
|
||||
)
|
||||
|
||||
// time.Time with a specific layout.
|
||||
type (
|
||||
Date time.Time
|
||||
DateTime time.Time
|
||||
envelopeDateTime time.Time
|
||||
searchDate time.Time
|
||||
)
|
||||
|
||||
// Permutations of the layouts defined in RFC 5322, section 3.3.
|
||||
var envelopeDateTimeLayouts = [...]string{
|
||||
envelopeDateTimeLayout, // popular, try it first
|
||||
"_2 Jan 2006 15:04:05 -0700",
|
||||
"_2 Jan 2006 15:04:05 MST",
|
||||
"_2 Jan 2006 15:04:05 -0700 (MST)",
|
||||
"_2 Jan 2006 15:04 -0700",
|
||||
"_2 Jan 2006 15:04 MST",
|
||||
"_2 Jan 2006 15:04 -0700 (MST)",
|
||||
"_2 Jan 06 15:04:05 -0700",
|
||||
"_2 Jan 06 15:04:05 MST",
|
||||
"_2 Jan 06 15:04:05 -0700 (MST)",
|
||||
"_2 Jan 06 15:04 -0700",
|
||||
"_2 Jan 06 15:04 MST",
|
||||
"_2 Jan 06 15:04 -0700 (MST)",
|
||||
"Mon, _2 Jan 2006 15:04:05 -0700",
|
||||
"Mon, _2 Jan 2006 15:04:05 MST",
|
||||
"Mon, _2 Jan 2006 15:04:05 -0700 (MST)",
|
||||
"Mon, _2 Jan 2006 15:04 -0700",
|
||||
"Mon, _2 Jan 2006 15:04 MST",
|
||||
"Mon, _2 Jan 2006 15:04 -0700 (MST)",
|
||||
"Mon, _2 Jan 06 15:04:05 -0700",
|
||||
"Mon, _2 Jan 06 15:04:05 MST",
|
||||
"Mon, _2 Jan 06 15:04:05 -0700 (MST)",
|
||||
"Mon, _2 Jan 06 15:04 -0700",
|
||||
"Mon, _2 Jan 06 15:04 MST",
|
||||
"Mon, _2 Jan 06 15:04 -0700 (MST)",
|
||||
}
|
||||
|
||||
// Try parsing the date based on the layouts defined in RFC 5322, section 3.3.
|
||||
// Inspired by https://github.com/golang/go/blob/master/src/net/mail/message.go
|
||||
func parseMessageDateTime(maybeDate string) (time.Time, error) {
|
||||
for _, layout := range envelopeDateTimeLayouts {
|
||||
parsed, err := time.Parse(layout, maybeDate)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("date %s could not be parsed", maybeDate)
|
||||
}
|
121
vendor/github.com/emersion/go-imap/handle.go
generated
vendored
Normal file
121
vendor/github.com/emersion/go-imap/handle.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A response that can be either accepted or rejected by a handler.
|
||||
type RespHandle struct {
|
||||
Resp interface{}
|
||||
Accepts chan bool
|
||||
}
|
||||
|
||||
// Accept this response. This means that the handler will process it.
|
||||
func (h *RespHandle) Accept() {
|
||||
h.Accepts <- true
|
||||
}
|
||||
|
||||
// Reject this response. The handler cannot process it.
|
||||
func (h *RespHandle) Reject() {
|
||||
h.Accepts <- false
|
||||
}
|
||||
|
||||
// Accept this response if it has the specified name. If not, reject it.
|
||||
func (h *RespHandle) AcceptNamedResp(name string) (fields []interface{}, accepted bool) {
|
||||
res, ok := h.Resp.(*Resp)
|
||||
if !ok || len(res.Fields) == 0 {
|
||||
h.Reject()
|
||||
return
|
||||
}
|
||||
|
||||
n, ok := res.Fields[0].(string)
|
||||
if !ok || n != name {
|
||||
h.Reject()
|
||||
return
|
||||
}
|
||||
|
||||
h.Accept()
|
||||
|
||||
fields = res.Fields[1:]
|
||||
accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
// Delivers responses to handlers.
|
||||
type RespHandler chan *RespHandle
|
||||
|
||||
// Handles responses from a handler.
|
||||
type RespHandlerFrom interface {
|
||||
HandleFrom(hdlr RespHandler) error
|
||||
}
|
||||
|
||||
// A RespHandlerFrom that forwards responses to multiple RespHandler.
|
||||
type MultiRespHandler struct {
|
||||
handlers []RespHandler
|
||||
locker sync.Locker
|
||||
}
|
||||
|
||||
func NewMultiRespHandler() *MultiRespHandler {
|
||||
return &MultiRespHandler{
|
||||
locker: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (mh *MultiRespHandler) HandleFrom(ch RespHandler) error {
|
||||
for rh := range ch {
|
||||
mh.locker.Lock()
|
||||
|
||||
accepted := false
|
||||
for i := len(mh.handlers) - 1; i >= 0; i-- {
|
||||
hdlr := mh.handlers[i]
|
||||
|
||||
rh := &RespHandle{
|
||||
Resp: rh.Resp,
|
||||
Accepts: make(chan bool),
|
||||
}
|
||||
|
||||
hdlr <- rh
|
||||
if accepted = <-rh.Accepts; accepted {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
mh.locker.Unlock()
|
||||
|
||||
if accepted {
|
||||
rh.Accept()
|
||||
} else {
|
||||
rh.Reject()
|
||||
}
|
||||
}
|
||||
|
||||
mh.locker.Lock()
|
||||
for _, hdlr := range mh.handlers {
|
||||
close(hdlr)
|
||||
}
|
||||
mh.handlers = nil
|
||||
mh.locker.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MultiRespHandler) Add(hdlr RespHandler) {
|
||||
if hdlr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mh.locker.Lock()
|
||||
mh.handlers = append(mh.handlers, hdlr)
|
||||
mh.locker.Unlock()
|
||||
}
|
||||
|
||||
func (mh *MultiRespHandler) Del(hdlr RespHandler) {
|
||||
mh.locker.Lock()
|
||||
for i, h := range mh.handlers {
|
||||
if h == hdlr {
|
||||
close(hdlr)
|
||||
mh.handlers = append(mh.handlers[:i], mh.handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
mh.locker.Unlock()
|
||||
}
|
28
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
Normal file
28
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Package imap implements IMAP4rev1 (RFC 3501).
|
||||
package imap
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// FlagsOp is an operation that will be applied on message flags.
|
||||
type FlagsOp string
|
||||
|
||||
const (
|
||||
// SetFlags replaces existing flags by new ones.
|
||||
SetFlags FlagsOp = "FLAGS"
|
||||
// AddFlags adds new flags.
|
||||
AddFlags = "+FLAGS"
|
||||
// RemoveFlags removes existing flags.
|
||||
RemoveFlags = "-FLAGS"
|
||||
)
|
||||
|
||||
// SilentOp can be appended to a FlagsOp to prevent the operation from
|
||||
// triggering unilateral message updates.
|
||||
const SilentOp = ".SILENT"
|
||||
|
||||
// CharsetReader, if non-nil, defines a function to generate charset-conversion
|
||||
// readers, converting from the provided charset into UTF-8. Charsets are always
|
||||
// lower-case. utf-8 and us-ascii charsets are handled by default. One of the
|
||||
// the CharsetReader's result values must be non-nil.
|
||||
var CharsetReader func(charset string, r io.Reader) (io.Reader, error)
|
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
Normal file
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A literal, as defined in RFC 3501 section 4.3.
|
||||
type Literal interface {
|
||||
io.Reader
|
||||
|
||||
// Len returns the number of bytes of the literal.
|
||||
Len() int
|
||||
}
|
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package imap
|
||||
|
||||
// Logger is the behaviour used by server/client to
|
||||
// report errors for accepting connections and unexpected behavior from handlers.
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
Println(v ...interface{})
|
||||
}
|
246
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
246
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// The primary mailbox, as defined in RFC 3501 section 5.1.
|
||||
const InboxName = "INBOX"
|
||||
|
||||
// Returns the canonical form of a mailbox name. Mailbox names can be
|
||||
// case-sensitive or case-insensitive depending on the backend implementation.
|
||||
// The special INBOX mailbox is case-insensitive.
|
||||
func CanonicalMailboxName(name string) string {
|
||||
if strings.ToUpper(name) == InboxName {
|
||||
return InboxName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Mailbox attributes definied in RFC 3501 section 7.2.2.
|
||||
const (
|
||||
// It is not possible for any child levels of hierarchy to exist under this\
|
||||
// name; no child levels exist now and none can be created in the future.
|
||||
NoInferiorsAttr = "\\Noinferiors"
|
||||
// It is not possible to use this name as a selectable mailbox.
|
||||
NoSelectAttr = "\\Noselect"
|
||||
// The mailbox has been marked "interesting" by the server; the mailbox
|
||||
// probably contains messages that have been added since the last time the
|
||||
// mailbox was selected.
|
||||
MarkedAttr = "\\Marked"
|
||||
// The mailbox does not contain any additional messages since the last time
|
||||
// the mailbox was selected.
|
||||
UnmarkedAttr = "\\Unmarked"
|
||||
)
|
||||
|
||||
// Basic mailbox info.
|
||||
type MailboxInfo struct {
|
||||
// The mailbox attributes.
|
||||
Attributes []string
|
||||
// The server's path separator.
|
||||
Delimiter string
|
||||
// The mailbox name.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Parse mailbox info from fields.
|
||||
func (info *MailboxInfo) Parse(fields []interface{}) error {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("Mailbox info needs at least 3 fields")
|
||||
}
|
||||
|
||||
info.Attributes, _ = ParseStringList(fields[0])
|
||||
|
||||
info.Delimiter, _ = fields[1].(string)
|
||||
|
||||
name, _ := fields[2].(string)
|
||||
info.Name, _ = utf7.Decoder.String(name)
|
||||
info.Name = CanonicalMailboxName(info.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format mailbox info to fields.
|
||||
func (info *MailboxInfo) Format() []interface{} {
|
||||
name, _ := utf7.Encoder.String(info.Name)
|
||||
// Thunderbird doesn't understand delimiters if not quoted
|
||||
return []interface{}{FormatStringList(info.Attributes), Quoted(info.Delimiter), name}
|
||||
}
|
||||
|
||||
// TODO: optimize this
|
||||
func (info *MailboxInfo) match(name, pattern string) bool {
|
||||
i := strings.IndexAny(pattern, "*%")
|
||||
if i == -1 {
|
||||
// No more wildcards
|
||||
return name == pattern
|
||||
}
|
||||
|
||||
// Get parts before and after wildcard
|
||||
chunk, wildcard, rest := pattern[0:i], pattern[i], pattern[i+1:]
|
||||
|
||||
// Check that name begins with chunk
|
||||
if len(chunk) > 0 && !strings.HasPrefix(name, chunk) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, chunk)
|
||||
|
||||
// Expand wildcard
|
||||
var j int
|
||||
for j = 0; j < len(name); j++ {
|
||||
if wildcard == '%' && string(name[j]) == info.Delimiter {
|
||||
break // Stop on delimiter if wildcard is %
|
||||
}
|
||||
// Try to match the rest from here
|
||||
if info.match(name[j:], rest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return info.match(name[j:], rest)
|
||||
}
|
||||
|
||||
// Match checks if a reference and a pattern matches this mailbox name, as
|
||||
// defined in RFC 3501 section 6.3.8.
|
||||
func (info *MailboxInfo) Match(reference, pattern string) bool {
|
||||
name := info.Name
|
||||
|
||||
if strings.HasPrefix(pattern, info.Delimiter) {
|
||||
reference = ""
|
||||
pattern = strings.TrimPrefix(pattern, info.Delimiter)
|
||||
}
|
||||
if reference != "" {
|
||||
if !strings.HasSuffix(reference, info.Delimiter) {
|
||||
reference += info.Delimiter
|
||||
}
|
||||
if !strings.HasPrefix(name, reference) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, reference)
|
||||
}
|
||||
|
||||
return info.match(name, pattern)
|
||||
}
|
||||
|
||||
// Mailbox status items.
|
||||
const (
|
||||
MailboxFlags = "FLAGS"
|
||||
MailboxPermanentFlags = "PERMANENTFLAGS"
|
||||
|
||||
// Defined in RFC 3501 section 6.3.10.
|
||||
MailboxMessages = "MESSAGES"
|
||||
MailboxRecent = "RECENT"
|
||||
MailboxUnseen = "UNSEEN"
|
||||
MailboxUidNext = "UIDNEXT"
|
||||
MailboxUidValidity = "UIDVALIDITY"
|
||||
)
|
||||
|
||||
// A mailbox status.
|
||||
type MailboxStatus struct {
|
||||
// The mailbox name.
|
||||
Name string
|
||||
// True if the mailbox is open in read-only mode.
|
||||
ReadOnly bool
|
||||
// The mailbox items that are currently filled in. This map's values
|
||||
// should not be used directly, they must only be used by libraries
|
||||
// implementing extensions of the IMAP protocol.
|
||||
Items map[string]interface{}
|
||||
|
||||
// The Items map may be accessed in different goroutines. Protect
|
||||
// concurrent writes.
|
||||
ItemsLocker sync.Mutex
|
||||
|
||||
// The mailbox flags.
|
||||
Flags []string
|
||||
// The mailbox permanent flags.
|
||||
PermanentFlags []string
|
||||
|
||||
// The number of messages in this mailbox.
|
||||
Messages uint32
|
||||
// The number of messages not seen since the last time the mailbox was opened.
|
||||
Recent uint32
|
||||
// The number of unread messages.
|
||||
Unseen uint32
|
||||
// The next UID.
|
||||
UidNext uint32
|
||||
// Together with a UID, it is a unique identifier for a message.
|
||||
// Must be greater than or equal to 1.
|
||||
UidValidity uint32
|
||||
}
|
||||
|
||||
// Create a new mailbox status that will contain the specified items.
|
||||
func NewMailboxStatus(name string, items []string) *MailboxStatus {
|
||||
status := &MailboxStatus{
|
||||
Name: name,
|
||||
Items: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for _, k := range items {
|
||||
status.Items[k] = nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Parse(fields []interface{}) error {
|
||||
status.Items = make(map[string]interface{})
|
||||
|
||||
var k string
|
||||
for i, f := range fields {
|
||||
if i%2 == 0 {
|
||||
var ok bool
|
||||
if k, ok = f.(string); !ok {
|
||||
return errors.New("Key is not a string")
|
||||
}
|
||||
k = strings.ToUpper(k)
|
||||
} else {
|
||||
status.Items[k] = nil
|
||||
|
||||
var err error
|
||||
switch k {
|
||||
case MailboxMessages:
|
||||
status.Messages, err = ParseNumber(f)
|
||||
case MailboxRecent:
|
||||
status.Recent, err = ParseNumber(f)
|
||||
case MailboxUnseen:
|
||||
status.Unseen, err = ParseNumber(f)
|
||||
case MailboxUidNext:
|
||||
status.UidNext, err = ParseNumber(f)
|
||||
case MailboxUidValidity:
|
||||
status.UidValidity, err = ParseNumber(f)
|
||||
default:
|
||||
status.Items[k] = f
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
for k, v := range status.Items {
|
||||
switch k {
|
||||
case MailboxMessages:
|
||||
v = status.Messages
|
||||
case MailboxRecent:
|
||||
v = status.Recent
|
||||
case MailboxUnseen:
|
||||
v = status.Unseen
|
||||
case MailboxUidNext:
|
||||
v = status.UidNext
|
||||
case MailboxUidValidity:
|
||||
v = status.UidValidity
|
||||
}
|
||||
|
||||
fields = append(fields, k, v)
|
||||
}
|
||||
return fields
|
||||
}
|
1048
vendor/github.com/emersion/go-imap/message.go
generated
vendored
Normal file
1048
vendor/github.com/emersion/go-imap/message.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
430
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
430
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
sp = ' '
|
||||
cr = '\r'
|
||||
lf = '\n'
|
||||
dquote = '"'
|
||||
literalStart = '{'
|
||||
literalEnd = '}'
|
||||
listStart = '('
|
||||
listEnd = ')'
|
||||
respCodeStart = '['
|
||||
respCodeEnd = ']'
|
||||
)
|
||||
|
||||
const (
|
||||
crlf = "\r\n"
|
||||
nilAtom = "NIL"
|
||||
)
|
||||
|
||||
// TODO: add CTL to atomSpecials
|
||||
var (
|
||||
quotedSpecials = string([]rune{dquote, '\\'})
|
||||
respSpecials = string([]rune{respCodeEnd})
|
||||
atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
|
||||
)
|
||||
|
||||
type parseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func newParseError(text string) error {
|
||||
return &parseError{errors.New(text)}
|
||||
}
|
||||
|
||||
// IsParseError returns true if the provided error is a parse error produced by
|
||||
// Reader.
|
||||
func IsParseError(err error) bool {
|
||||
_, ok := err.(*parseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// A string reader.
|
||||
type StringReader interface {
|
||||
// ReadString reads until the first occurrence of delim in the input,
|
||||
// returning a string containing the data up to and including the delimiter.
|
||||
// See https://golang.org/pkg/bufio/#Reader.ReadString
|
||||
ReadString(delim byte) (line string, err error)
|
||||
}
|
||||
|
||||
type reader interface {
|
||||
io.Reader
|
||||
io.RuneScanner
|
||||
StringReader
|
||||
}
|
||||
|
||||
// Convert a field to a number.
|
||||
func ParseNumber(f interface{}) (uint32, error) {
|
||||
// Useful for tests
|
||||
if n, ok := f.(uint32); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
s, ok := f.(string)
|
||||
if !ok {
|
||||
return 0, newParseError("number is not a string")
|
||||
}
|
||||
|
||||
nbr, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0, &parseError{err}
|
||||
}
|
||||
|
||||
return uint32(nbr), nil
|
||||
}
|
||||
|
||||
// Convert a field list to a string list.
|
||||
func ParseStringList(f interface{}) ([]string, error) {
|
||||
fields, ok := f.([]interface{})
|
||||
if !ok {
|
||||
return nil, newParseError("string list is not a list")
|
||||
}
|
||||
|
||||
list := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
var ok bool
|
||||
if list[i], ok = f.(string); !ok {
|
||||
return nil, newParseError("string list contains a non-string")
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func trimSuffix(str string, suffix rune) string {
|
||||
return str[:len(str)-1]
|
||||
}
|
||||
|
||||
// An IMAP reader.
|
||||
type Reader struct {
|
||||
MaxLiteralSize uint32 // The maximum literal size.
|
||||
|
||||
reader
|
||||
|
||||
continues chan<- bool
|
||||
|
||||
brackets int
|
||||
inRespCode bool
|
||||
}
|
||||
|
||||
func (r *Reader) ReadSp() error {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if char != sp {
|
||||
return newParseError("not a space")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadCrlf() (err error) {
|
||||
var char rune
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != cr {
|
||||
err = newParseError("line doesn't end with a CR")
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != lf {
|
||||
err = newParseError("line doesn't end with a LF")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadAtom() (interface{}, error) {
|
||||
r.brackets = 0
|
||||
|
||||
var atom string
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: list-wildcards and \
|
||||
if r.brackets == 0 && (char == listStart || char == literalStart || char == dquote) {
|
||||
return nil, newParseError("atom contains forbidden char: " + string(char))
|
||||
}
|
||||
if char == cr {
|
||||
break
|
||||
}
|
||||
if r.brackets == 0 && (char == sp || char == listEnd) {
|
||||
break
|
||||
}
|
||||
if char == respCodeEnd {
|
||||
if r.brackets == 0 {
|
||||
if r.inRespCode {
|
||||
break
|
||||
} else {
|
||||
return nil, newParseError("atom contains bad brackets nesting")
|
||||
}
|
||||
}
|
||||
r.brackets--
|
||||
}
|
||||
if char == respCodeStart {
|
||||
r.brackets++
|
||||
}
|
||||
|
||||
atom += string(char)
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
|
||||
if atom == "NIL" {
|
||||
return nil, nil
|
||||
}
|
||||
return atom, nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLiteral() (Literal, error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if char != literalStart {
|
||||
return nil, newParseError("literal string doesn't start with an open brace")
|
||||
}
|
||||
|
||||
lstr, err := r.ReadString(byte(literalEnd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lstr = trimSuffix(lstr, literalEnd)
|
||||
n, err := strconv.ParseUint(lstr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newParseError("cannot parse literal length: " + err.Error())
|
||||
}
|
||||
if r.MaxLiteralSize > 0 && uint32(n) > r.MaxLiteralSize {
|
||||
return nil, newParseError("literal exceeding maximum size")
|
||||
}
|
||||
|
||||
if err := r.ReadCrlf(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send continuation request if necessary
|
||||
if r.continues != nil {
|
||||
r.continues <- true
|
||||
}
|
||||
|
||||
// Read literal
|
||||
b := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(b), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadQuotedString() (string, error) {
|
||||
if char, _, err := r.ReadRune(); err != nil {
|
||||
return "", err
|
||||
} else if char != dquote {
|
||||
return "", newParseError("quoted string doesn't start with a double quote")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var escaped bool
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if char == '\\' && !escaped {
|
||||
escaped = true
|
||||
} else {
|
||||
if char == cr || char == lf {
|
||||
r.UnreadRune()
|
||||
return "", newParseError("CR or LF not allowed in quoted string")
|
||||
}
|
||||
if char == dquote && !escaped {
|
||||
break
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(quotedSpecials, char) && escaped {
|
||||
return "", newParseError("quoted string cannot contain backslash followed by a non-quoted-specials char")
|
||||
}
|
||||
|
||||
buf.WriteRune(char)
|
||||
escaped = false
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadFields() (fields []interface{}, err error) {
|
||||
var char rune
|
||||
for {
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = r.UnreadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var field interface{}
|
||||
ok := true
|
||||
switch char {
|
||||
case literalStart:
|
||||
field, err = r.ReadLiteral()
|
||||
case dquote:
|
||||
field, err = r.ReadQuotedString()
|
||||
case listStart:
|
||||
field, err = r.ReadList()
|
||||
case listEnd:
|
||||
ok = false
|
||||
case cr:
|
||||
return
|
||||
default:
|
||||
field, err = r.ReadAtom()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char == cr || char == listEnd || char == respCodeEnd {
|
||||
return
|
||||
}
|
||||
if char == listStart {
|
||||
r.UnreadRune()
|
||||
continue
|
||||
}
|
||||
if char != sp {
|
||||
err = newParseError("fields are not separated by a space")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) ReadList() (fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != listStart {
|
||||
err = newParseError("list doesn't start with an open parenthesis")
|
||||
return
|
||||
}
|
||||
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != listEnd {
|
||||
err = newParseError("list doesn't end with a close parenthesis")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLine() (fields []interface{}, err error) {
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
err = r.ReadCrlf()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadRespCode() (code string, fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeStart {
|
||||
err = newParseError("response code doesn't start with an open bracket")
|
||||
return
|
||||
}
|
||||
|
||||
r.inRespCode = true
|
||||
fields, err = r.ReadFields()
|
||||
r.inRespCode = false
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
err = newParseError("response code doesn't contain any field")
|
||||
return
|
||||
}
|
||||
|
||||
code, ok := fields[0].(string)
|
||||
if !ok {
|
||||
err = newParseError("response code doesn't start with a string atom")
|
||||
return
|
||||
}
|
||||
if code == "" {
|
||||
err = newParseError("response code is empty")
|
||||
return
|
||||
}
|
||||
|
||||
fields = fields[1:]
|
||||
|
||||
r.UnreadRune()
|
||||
char, _, err = r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeEnd {
|
||||
err = newParseError("response code doesn't end with a close bracket")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadInfo() (info string, err error) {
|
||||
info, err = r.ReadString(byte(cr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info = strings.TrimSuffix(info, string(cr))
|
||||
info = strings.TrimLeft(info, " ")
|
||||
|
||||
var char rune
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != lf {
|
||||
err = newParseError("line doesn't end with a LF")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewReader(r reader) *Reader {
|
||||
return &Reader{reader: r}
|
||||
}
|
||||
|
||||
func NewServerReader(r reader, continues chan<- bool) *Reader {
|
||||
return &Reader{reader: r, continues: continues}
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Parse(fields []interface{}) error
|
||||
}
|
156
vendor/github.com/emersion/go-imap/response.go
generated
vendored
Normal file
156
vendor/github.com/emersion/go-imap/response.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A value that can be converted to a Resp.
|
||||
type Responser interface {
|
||||
Response() *Resp
|
||||
}
|
||||
|
||||
// A response.
|
||||
// See RFC 3501 section 2.2.2
|
||||
type Resp struct {
|
||||
// The response tag. Can be either "" for untagged responses, "+" for continuation
|
||||
// requests or a previous command's tag.
|
||||
Tag string
|
||||
// The parsed response fields.
|
||||
Fields []interface{}
|
||||
}
|
||||
|
||||
func (r *Resp) WriteTo(w *Writer) error {
|
||||
tag := r.Tag
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
fields := []interface{}{tag}
|
||||
fields = append(fields, r.Fields...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// Create a new untagged response.
|
||||
func NewUntaggedResp(fields []interface{}) *Resp {
|
||||
return &Resp{
|
||||
Tag: "*",
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
// A continuation request.
|
||||
type ContinuationResp struct {
|
||||
// The info message sent with the continuation request.
|
||||
Info string
|
||||
}
|
||||
|
||||
func (r *ContinuationResp) WriteTo(w *Writer) error {
|
||||
if err := w.writeString("+"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Info != "" {
|
||||
if err := w.writeString(string(sp) + r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
// Read a single response from a Reader. Returns either a continuation request,
|
||||
// a status response or a raw response.
|
||||
func ReadResp(r *Reader) (out interface{}, err error) {
|
||||
atom, err := r.ReadAtom()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tag, ok := atom.(string)
|
||||
if !ok {
|
||||
err = errors.New("Response tag is not an atom")
|
||||
return
|
||||
}
|
||||
|
||||
if tag == "+" {
|
||||
if err := r.ReadSp(); err != nil {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
res := &ContinuationResp{}
|
||||
res.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = res
|
||||
return
|
||||
}
|
||||
|
||||
if err = r.ReadSp(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Can be either data or status
|
||||
// Try to parse a status
|
||||
isStatus := false
|
||||
var fields []interface{}
|
||||
|
||||
if atom, err = r.ReadAtom(); err == nil {
|
||||
fields = append(fields, atom)
|
||||
|
||||
if err = r.ReadSp(); err == nil {
|
||||
if name, ok := atom.(string); ok {
|
||||
status := StatusRespType(name)
|
||||
if status == StatusOk || status == StatusNo || status == StatusBad || status == StatusPreauth || status == StatusBye {
|
||||
isStatus = true
|
||||
|
||||
res := &StatusResp{
|
||||
Tag: tag,
|
||||
Type: status,
|
||||
}
|
||||
|
||||
var char rune
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
r.UnreadRune()
|
||||
|
||||
if char == '[' {
|
||||
// Contains code & arguments
|
||||
res.Code, res.Arguments, err = r.ReadRespCode()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = res
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
if !isStatus {
|
||||
// Not a status so it's data
|
||||
res := &Resp{Tag: tag}
|
||||
|
||||
var remaining []interface{}
|
||||
remaining, err = r.ReadLine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res.Fields = append(fields, remaining...)
|
||||
out = res
|
||||
}
|
||||
|
||||
return
|
||||
}
|
72
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
Normal file
72
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// An AUTHENTICATE response.
|
||||
type Authenticate struct {
|
||||
Mechanism sasl.Client
|
||||
InitialResponse []byte
|
||||
Writer *imap.Writer
|
||||
}
|
||||
|
||||
func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
|
||||
w := r.Writer
|
||||
|
||||
// Cancel auth if an error occurs
|
||||
defer (func() {
|
||||
if err != nil {
|
||||
w.Write([]byte("*\r\n"))
|
||||
w.Flush()
|
||||
}
|
||||
})()
|
||||
|
||||
for h := range hdlr {
|
||||
cont, ok := h.Resp.(*imap.ContinuationResp)
|
||||
if !ok {
|
||||
h.Reject()
|
||||
continue
|
||||
}
|
||||
h.Accept()
|
||||
|
||||
// Empty challenge, send initial response as stated in RFC 2222 section 5.1
|
||||
if cont.Info == "" && r.InitialResponse != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
|
||||
if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.InitialResponse = nil
|
||||
continue
|
||||
}
|
||||
|
||||
var challenge []byte
|
||||
challenge, err = base64.StdEncoding.DecodeString(cont.Info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res []byte
|
||||
res, err = r.Mechanism.Next(challenge)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(res)
|
||||
if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
39
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
Normal file
39
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A CAPABILITY response.
|
||||
// See RFC 3501 section 7.2.1
|
||||
type Capability struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Capability) HandleFrom(hdlr imap.RespHandler) (err error) {
|
||||
for h := range hdlr {
|
||||
caps, ok := h.AcceptNamedResp(imap.Capability)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
r.Caps = make([]string, len(caps))
|
||||
for i, c := range caps {
|
||||
r.Caps[i], _ = c.(string)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Capability) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.Capability}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, cap)
|
||||
}
|
||||
|
||||
res := &imap.Resp{Fields: fields}
|
||||
return res.WriteTo(w)
|
||||
}
|
45
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
Normal file
45
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An EXPUNGE response.
|
||||
// See RFC 3501 section 7.4.1
|
||||
type Expunge struct {
|
||||
SeqNums chan uint32
|
||||
}
|
||||
|
||||
func (r *Expunge) HandleFrom(hdlr imap.RespHandler) error {
|
||||
defer close(r.SeqNums)
|
||||
|
||||
for h := range hdlr {
|
||||
res, ok := h.Resp.(*imap.Resp)
|
||||
if !ok || len(res.Fields) < 3 {
|
||||
h.Reject()
|
||||
continue
|
||||
}
|
||||
if name, ok := res.Fields[1].(string); !ok || name != imap.Expunge {
|
||||
h.Reject()
|
||||
continue
|
||||
}
|
||||
h.Accept()
|
||||
|
||||
seqNum, _ := imap.ParseNumber(res.Fields[0])
|
||||
r.SeqNums <- seqNum
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Expunge) WriteTo(w *imap.Writer) error {
|
||||
for seqNum := range r.SeqNums {
|
||||
res := imap.NewUntaggedResp([]interface{}{seqNum, imap.Expunge})
|
||||
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
55
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
Normal file
55
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A FETCH response.
|
||||
// See RFC 3501 section 7.4.2
|
||||
type Fetch struct {
|
||||
Messages chan *imap.Message
|
||||
}
|
||||
|
||||
func (r *Fetch) HandleFrom(hdlr imap.RespHandler) error {
|
||||
defer close(r.Messages)
|
||||
|
||||
for h := range hdlr {
|
||||
res, ok := h.Resp.(*imap.Resp)
|
||||
if !ok || len(res.Fields) < 3 {
|
||||
h.Reject()
|
||||
continue
|
||||
}
|
||||
if name, ok := res.Fields[1].(string); !ok || name != imap.Fetch {
|
||||
h.Reject()
|
||||
continue
|
||||
}
|
||||
h.Accept()
|
||||
|
||||
seqNum, _ := imap.ParseNumber(res.Fields[0])
|
||||
fields, _ := res.Fields[2].([]interface{})
|
||||
|
||||
msg := &imap.Message{
|
||||
SeqNum: seqNum,
|
||||
}
|
||||
|
||||
if err := msg.Parse(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Messages <- msg
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Fetch) WriteTo(w *imap.Writer) error {
|
||||
for msg := range r.Messages {
|
||||
res := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.Fetch, msg.Format()})
|
||||
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
59
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
Normal file
59
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A LIST response.
|
||||
// If Subscribed is set to true, LSUB will be used instead.
|
||||
// See RFC 3501 section 7.2.2
|
||||
type List struct {
|
||||
Mailboxes chan *imap.MailboxInfo
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (r *List) Name() string {
|
||||
if r.Subscribed {
|
||||
return imap.Lsub
|
||||
} else {
|
||||
return imap.List
|
||||
}
|
||||
}
|
||||
|
||||
func (r *List) HandleFrom(hdlr imap.RespHandler) error {
|
||||
defer close(r.Mailboxes)
|
||||
|
||||
name := r.Name()
|
||||
|
||||
for h := range hdlr {
|
||||
fields, ok := h.AcceptNamedResp(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxInfo{}
|
||||
if err := mbox.Parse(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Mailboxes <- mbox
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *List) WriteTo(w *imap.Writer) error {
|
||||
name := r.Name()
|
||||
|
||||
for mbox := range r.Mailboxes {
|
||||
fields := []interface{}{name}
|
||||
fields = append(fields, mbox.Format()...)
|
||||
|
||||
res := imap.NewUntaggedResp(fields)
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
2
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
Normal file
2
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// IMAP responses defined in RFC 3501.
|
||||
package responses
|
37
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
Normal file
37
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A SEARCH response.
|
||||
// See RFC 3501 section 7.2.5
|
||||
type Search struct {
|
||||
Ids []uint32
|
||||
}
|
||||
|
||||
func (r *Search) HandleFrom(hdlr imap.RespHandler) (err error) {
|
||||
for h := range hdlr {
|
||||
fields, ok := h.AcceptNamedResp(imap.Search)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
id, _ := imap.ParseNumber(f)
|
||||
r.Ids = append(r.Ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Search) WriteTo(w *imap.Writer) (err error) {
|
||||
fields := []interface{}{imap.Search}
|
||||
for _, id := range r.Ids {
|
||||
fields = append(fields, id)
|
||||
}
|
||||
|
||||
res := imap.NewUntaggedResp(fields)
|
||||
return res.WriteTo(w)
|
||||
}
|
145
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
Normal file
145
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A SELECT response.
|
||||
type Select struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Select) HandleFrom(hdlr imap.RespHandler) (err error) {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
mbox.Items = make(map[string]interface{})
|
||||
for h := range hdlr {
|
||||
switch res := h.Resp.(type) {
|
||||
case *imap.Resp:
|
||||
fields, ok := h.AcceptNamedResp(imap.MailboxFlags)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
flags, _ := fields[0].([]interface{})
|
||||
mbox.Flags, _ = imap.ParseStringList(flags)
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[imap.MailboxFlags] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
case *imap.StatusResp:
|
||||
if len(res.Arguments) < 1 {
|
||||
h.Accepts <- false
|
||||
break
|
||||
}
|
||||
|
||||
accepted := true
|
||||
switch res.Code {
|
||||
case imap.MailboxUnseen:
|
||||
mbox.Unseen, _ = imap.ParseNumber(res.Arguments[0])
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[imap.MailboxUnseen] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
case imap.MailboxPermanentFlags:
|
||||
flags, _ := res.Arguments[0].([]interface{})
|
||||
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[imap.MailboxPermanentFlags] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
case imap.MailboxUidNext:
|
||||
mbox.UidNext, _ = imap.ParseNumber(res.Arguments[0])
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[imap.MailboxUidNext] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
case imap.MailboxUidValidity:
|
||||
mbox.UidValidity, _ = imap.ParseNumber(res.Arguments[0])
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[imap.MailboxUidValidity] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
default:
|
||||
accepted = false
|
||||
}
|
||||
h.Accepts <- accepted
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Select) WriteTo(w *imap.Writer) (err error) {
|
||||
status := r.Mailbox
|
||||
|
||||
for k := range r.Mailbox.Items {
|
||||
switch k {
|
||||
case imap.MailboxFlags:
|
||||
flags := make([]interface{}, len(status.Flags))
|
||||
for i, f := range status.Flags {
|
||||
flags[i] = f
|
||||
}
|
||||
res := imap.NewUntaggedResp([]interface{}{"FLAGS", flags})
|
||||
if err = res.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxPermanentFlags:
|
||||
flags := make([]interface{}, len(status.PermanentFlags))
|
||||
for i, f := range status.PermanentFlags {
|
||||
flags[i] = f
|
||||
}
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodePermanentFlags,
|
||||
Arguments: []interface{}{flags},
|
||||
Info: "Flags permitted.",
|
||||
}
|
||||
if err = statusRes.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxMessages:
|
||||
res := imap.NewUntaggedResp([]interface{}{status.Messages, "EXISTS"})
|
||||
if err = res.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxRecent:
|
||||
res := imap.NewUntaggedResp([]interface{}{status.Recent, "RECENT"})
|
||||
if err = res.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxUnseen:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeUnseen,
|
||||
Arguments: []interface{}{status.Unseen},
|
||||
Info: fmt.Sprintf("Message %d is first unseen", status.Unseen),
|
||||
}
|
||||
if err = statusRes.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxUidNext:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeUidNext,
|
||||
Arguments: []interface{}{status.UidNext},
|
||||
Info: "Predicted next UID",
|
||||
}
|
||||
if err = statusRes.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
case imap.MailboxUidValidity:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeUidValidity,
|
||||
Arguments: []interface{}{status.UidValidity},
|
||||
Info: "UIDs valid",
|
||||
}
|
||||
if err = statusRes.WriteTo(w); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
63
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
Normal file
63
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// A STATUS response.
|
||||
// See RFC 3501 section 7.2.4
|
||||
type Status struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Status) HandleFrom(hdlr imap.RespHandler) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
mbox.Items = nil
|
||||
|
||||
for h := range hdlr {
|
||||
fields, ok := h.AcceptNamedResp(imap.Status)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(fields) < 2 {
|
||||
return errors.New("STATUS response expects two fields")
|
||||
}
|
||||
|
||||
name, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("STATUS response expects a string as first argument")
|
||||
}
|
||||
mbox.Name, _ = utf7.Decoder.String(name)
|
||||
mbox.Name = imap.CanonicalMailboxName(mbox.Name)
|
||||
|
||||
var items []interface{}
|
||||
if items, ok = fields[1].([]interface{}); !ok {
|
||||
return errors.New("STATUS response expects a list as second argument")
|
||||
}
|
||||
|
||||
if err := mbox.Parse(items); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Status) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
|
||||
fields := []interface{}{imap.Status, mbox.Name, mbox.Format()}
|
||||
|
||||
res := imap.NewUntaggedResp(fields)
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
367
vendor/github.com/emersion/go-imap/search.go
generated
vendored
Normal file
367
vendor/github.com/emersion/go-imap/search.go
generated
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func maybeString(mystery interface{}) string {
|
||||
if s, ok := mystery.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convertField(f interface{}, charsetReader func(io.Reader) io.Reader) string {
|
||||
// An IMAP string contains only 7-bit data, no need to decode it
|
||||
if s, ok := f.(string); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
// If no charset is provided, getting directly the string is faster
|
||||
if charsetReader == nil {
|
||||
if stringer, ok := f.(fmt.Stringer); ok {
|
||||
return stringer.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Not a string, it must be a literal
|
||||
l, ok := f.(Literal)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var r io.Reader = l
|
||||
if charsetReader != nil {
|
||||
if dec := charsetReader(r); dec != nil {
|
||||
r = dec
|
||||
}
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func popSearchField(fields []interface{}) (interface{}, []interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil, errors.New("imap: no enough fields for search key")
|
||||
}
|
||||
return fields[0], fields[1:], nil
|
||||
}
|
||||
|
||||
// SearchCriteria is a search criteria. A message matches the criteria if and
|
||||
// only if it matches each one of its fields.
|
||||
type SearchCriteria struct {
|
||||
SeqNum *SeqSet // Sequence number is in sequence set
|
||||
Uid *SeqSet // UID is in sequence set
|
||||
|
||||
// Time and timezone are ignored
|
||||
Since time.Time // Internal date is since this date
|
||||
Before time.Time // Internal date is before this date
|
||||
SentSince time.Time // Date header field is since this date
|
||||
SentBefore time.Time // Date header field is before this date
|
||||
|
||||
Header textproto.MIMEHeader // Each header field value is present
|
||||
Body []string // Each string is in the body
|
||||
Text []string // Each string is in the text (header + body)
|
||||
|
||||
WithFlags []string // Each flag is present
|
||||
WithoutFlags []string // Each flag is not present
|
||||
|
||||
Larger uint32 // Size is larger than this number
|
||||
Smaller uint32 // Size is smaller than this number
|
||||
|
||||
Not []*SearchCriteria // Each criteria doesn't match
|
||||
Or [][2]*SearchCriteria // Each criteria pair has at least one match of two
|
||||
}
|
||||
|
||||
// NewSearchCriteria creates a new search criteria.
|
||||
func NewSearchCriteria() *SearchCriteria {
|
||||
return &SearchCriteria{Header: make(textproto.MIMEHeader)}
|
||||
}
|
||||
|
||||
func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.Reader) io.Reader) ([]interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
f := fields[0]
|
||||
fields = fields[1:]
|
||||
|
||||
if subfields, ok := f.([]interface{}); ok {
|
||||
return fields, c.ParseWithCharset(subfields, charsetReader)
|
||||
}
|
||||
|
||||
key, ok := f.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("imap: invalid search criteria field type: %T", f)
|
||||
}
|
||||
key = strings.ToUpper(key)
|
||||
|
||||
var err error
|
||||
switch key {
|
||||
case "ALL":
|
||||
// Nothing to do
|
||||
case "ANSWERED", "DELETED", "DRAFT", "FLAGGED", "RECENT", "SEEN":
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag("\\"+key))
|
||||
case "BCC", "CC", "FROM", "SUBJECT", "TO":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(key, convertField(f, charsetReader))
|
||||
case "BEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Before.IsZero() || t.Before(c.Before) {
|
||||
c.Before = t
|
||||
}
|
||||
case "BODY":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Body = append(c.Body, convertField(f, charsetReader))
|
||||
}
|
||||
case "HEADER":
|
||||
var f1, f2 interface{}
|
||||
if f1, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if f2, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(maybeString(f1), convertField(f2, charsetReader))
|
||||
}
|
||||
case "KEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
case "LARGER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Larger == 0 || n > c.Larger {
|
||||
c.Larger = n
|
||||
}
|
||||
case "NEW":
|
||||
c.WithFlags = append(c.WithFlags, RecentFlag)
|
||||
c.WithoutFlags = append(c.WithoutFlags, SeenFlag)
|
||||
case "NOT":
|
||||
not := new(SearchCriteria)
|
||||
if fields, err = not.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Not = append(c.Not, not)
|
||||
case "OLD":
|
||||
c.WithoutFlags = append(c.WithoutFlags, RecentFlag)
|
||||
case "ON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Since = t
|
||||
c.Before = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "OR":
|
||||
c1, c2 := new(SearchCriteria), new(SearchCriteria)
|
||||
if fields, err = c1.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
} else if fields, err = c2.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Or = append(c.Or, [2]*SearchCriteria{c1, c2})
|
||||
case "SENTBEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentBefore.IsZero() || t.Before(c.SentBefore) {
|
||||
c.SentBefore = t
|
||||
}
|
||||
case "SENTON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.SentSince = t
|
||||
c.SentBefore = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "SENTSINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentSince.IsZero() || t.After(c.SentSince) {
|
||||
c.SentSince = t
|
||||
}
|
||||
case "SINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Since.IsZero() || t.After(c.Since) {
|
||||
c.Since = t
|
||||
}
|
||||
case "SMALLER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Smaller == 0 || n < c.Smaller {
|
||||
c.Smaller = n
|
||||
}
|
||||
case "TEXT":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Text = append(c.Text, convertField(f, charsetReader))
|
||||
}
|
||||
case "UID":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if c.Uid, err = NewSeqSet(maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN":
|
||||
unflag := strings.TrimPrefix(key, "UN")
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag("\\"+unflag))
|
||||
case "UNKEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
default: // Try to parse a sequence set
|
||||
if c.SeqNum, err = NewSeqSet(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// ParseWithCharset parses a search criteria from the provided fields.
|
||||
// charsetReader is an optional function that converts from the fields charset
|
||||
// to UTF-8.
|
||||
func (c *SearchCriteria) ParseWithCharset(fields []interface{}, charsetReader func(io.Reader) io.Reader) error {
|
||||
for len(fields) > 0 {
|
||||
var err error
|
||||
if fields, err = c.parseField(fields, charsetReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format formats search criteria to fields. UTF-8 is used.
|
||||
func (c *SearchCriteria) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
|
||||
if c.SeqNum != nil {
|
||||
fields = append(fields, c.SeqNum)
|
||||
}
|
||||
if c.Uid != nil {
|
||||
fields = append(fields, "UID", c.Uid)
|
||||
}
|
||||
|
||||
if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour {
|
||||
fields = append(fields, "ON", searchDate(c.Since))
|
||||
} else {
|
||||
if !c.Since.IsZero() {
|
||||
fields = append(fields, "SINCE", searchDate(c.Since))
|
||||
}
|
||||
if !c.Before.IsZero() {
|
||||
fields = append(fields, "BEFORE", searchDate(c.Before))
|
||||
}
|
||||
}
|
||||
if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour {
|
||||
fields = append(fields, "SENTON", searchDate(c.SentSince))
|
||||
} else {
|
||||
if !c.SentSince.IsZero() {
|
||||
fields = append(fields, "SENTSINCE", searchDate(c.SentSince))
|
||||
}
|
||||
if !c.SentBefore.IsZero() {
|
||||
fields = append(fields, "SENTBEFORE", searchDate(c.SentBefore))
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range c.Header {
|
||||
var prefields []interface{}
|
||||
switch key {
|
||||
case "Bcc", "Cc", "From", "Subject", "To":
|
||||
prefields = []interface{}{strings.ToUpper(key)}
|
||||
default:
|
||||
prefields = []interface{}{"HEADER", key}
|
||||
}
|
||||
for _, value := range values {
|
||||
fields = append(fields, prefields...)
|
||||
fields = append(fields, value)
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range c.Body {
|
||||
fields = append(fields, "BODY", value)
|
||||
}
|
||||
for _, value := range c.Text {
|
||||
fields = append(fields, "TEXT", value)
|
||||
}
|
||||
|
||||
for _, flag := range c.WithFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag:
|
||||
subfields = []interface{}{strings.ToUpper(strings.TrimPrefix(flag, "\\"))}
|
||||
default:
|
||||
subfields = []interface{}{"KEYWORD", flag}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
for _, flag := range c.WithoutFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag:
|
||||
subfields = []interface{}{"UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\"))}
|
||||
case RecentFlag:
|
||||
subfields = []interface{}{"OLD"}
|
||||
default:
|
||||
subfields = []interface{}{"UNKEYWORD", flag}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
|
||||
if c.Larger > 0 {
|
||||
fields = append(fields, "LARGER", c.Larger)
|
||||
}
|
||||
if c.Smaller > 0 {
|
||||
fields = append(fields, "SMALLER", c.Smaller)
|
||||
}
|
||||
|
||||
for _, not := range c.Not {
|
||||
fields = append(fields, "NOT", not.Format())
|
||||
}
|
||||
|
||||
for _, or := range c.Or {
|
||||
fields = append(fields, "OR", or[0].Format(), or[1].Format())
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
290
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
Normal file
290
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrBadSeqSet is used to report problems with the format of a sequence set
|
||||
// value.
|
||||
type ErrBadSeqSet string
|
||||
|
||||
func (err ErrBadSeqSet) Error() string {
|
||||
return fmt.Sprintf("imap: bad sequence set value %q", string(err))
|
||||
}
|
||||
|
||||
// Seq 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 Seq struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// parseSeqNumber parses a single seq-number value (non-zero uint32 or "*").
|
||||
func parseSeqNumber(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, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// parseSeq 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 parseSeq(v string) (s Seq, err error) {
|
||||
if sep := strings.IndexRune(v, ':'); sep < 0 {
|
||||
s.Start, err = parseSeqNumber(v)
|
||||
s.Stop = s.Start
|
||||
return
|
||||
} else if s.Start, err = parseSeqNumber(v[:sep]); err == nil {
|
||||
if s.Stop, err = parseSeqNumber(v[sep+1:]); err == nil {
|
||||
if (s.Stop < s.Start && s.Stop != 0) || s.Start == 0 {
|
||||
s.Start, s.Stop = s.Stop, s.Start
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return s, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// Contains returns true if the seq-number q is contained in sequence value s.
|
||||
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
|
||||
// contains "*" and all numbers >= n.
|
||||
func (s Seq) 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 Seq) Less(q uint32) bool {
|
||||
return (s.Stop < q || q == 0) && s.Stop != 0
|
||||
}
|
||||
|
||||
// Merge combines sequence 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 Seq) Merge(t Seq) (union Seq, ok bool) {
|
||||
if union = s; s == t {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
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 Seq{s.Start, t.Stop}, true // s intersects or touches t
|
||||
}
|
||||
return
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// String returns sequence value s as a seq-number or seq-range string.
|
||||
func (s Seq) 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))
|
||||
}
|
||||
|
||||
// SeqSet 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 SeqSet struct {
|
||||
Set []Seq
|
||||
}
|
||||
|
||||
// NewSeqSet returns a new SeqSet instance after parsing the set string.
|
||||
func NewSeqSet(set string) (s *SeqSet, err error) {
|
||||
s = new(SeqSet)
|
||||
return s, s.Add(set)
|
||||
}
|
||||
|
||||
// Add inserts new sequence values into the set. The string format is described
|
||||
// by RFC 3501 sequence-set ABNF rule. If an error is encountered, all values
|
||||
// inserted successfully prior to the error remain in the set.
|
||||
func (s *SeqSet) Add(set string) error {
|
||||
for _, sv := range strings.Split(set, ",") {
|
||||
v, err := parseSeq(sv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.insert(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNum inserts new sequence numbers into the set. The value 0 represents "*".
|
||||
func (s *SeqSet) AddNum(q ...uint32) {
|
||||
for _, v := range q {
|
||||
s.insert(Seq{v, v})
|
||||
}
|
||||
}
|
||||
|
||||
// AddRange inserts a new sequence range into the set.
|
||||
func (s *SeqSet) AddRange(Start, Stop uint32) {
|
||||
if (Stop < Start && Stop != 0) || Start == 0 {
|
||||
s.insert(Seq{Stop, Start})
|
||||
} else {
|
||||
s.insert(Seq{Start, Stop})
|
||||
}
|
||||
}
|
||||
|
||||
// AddSet inserts all values from t into s.
|
||||
func (s *SeqSet) AddSet(t *SeqSet) {
|
||||
for _, v := range t.Set {
|
||||
s.insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all values from the set.
|
||||
func (s *SeqSet) Clear() {
|
||||
s.Set = s.Set[:0]
|
||||
}
|
||||
|
||||
// Empty returns true if the sequence set does not contain any values.
|
||||
func (s SeqSet) Empty() bool {
|
||||
return len(s.Set) == 0
|
||||
}
|
||||
|
||||
// Dynamic returns true if the set contains "*" or "n:*" values.
|
||||
func (s SeqSet) Dynamic() bool {
|
||||
return len(s.Set) > 0 && s.Set[len(s.Set)-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 SeqSet) Contains(q uint32) bool {
|
||||
if _, ok := s.search(q); ok {
|
||||
return q != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns a sorted representation of all contained sequence values.
|
||||
func (s SeqSet) String() string {
|
||||
if len(s.Set) == 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, 0, 64)
|
||||
for _, v := range s.Set {
|
||||
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 sequence value v to the set.
|
||||
func (s *SeqSet) insert(v Seq) {
|
||||
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.Set[i-1], merged = s.Set[i-1].Merge(v)
|
||||
}
|
||||
if i == len(s.Set) {
|
||||
// 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.Set[i], merged = s.Set[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.Set[i], continue trying to merge until the end
|
||||
for j := i + 1; j < len(s.Set); j++ {
|
||||
if s.Set[i], merged = s.Set[i].Merge(s.Set[j]); !merged {
|
||||
if j > i+1 {
|
||||
// cut out all entries between i and j that were merged
|
||||
s.Set = append(s.Set[:i+1], s.Set[j:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything after s.Set[i] was merged
|
||||
s.Set = s.Set[:i+1]
|
||||
}
|
||||
|
||||
// insertAt inserts a new sequence value v at index i, resizing s.Set as needed.
|
||||
func (s *SeqSet) insertAt(i int, v Seq) {
|
||||
if n := len(s.Set); i == n {
|
||||
// insert at the end
|
||||
s.Set = append(s.Set, v)
|
||||
return
|
||||
} else if n < cap(s.Set) {
|
||||
// enough space, shift everything at and after i to the right
|
||||
s.Set = s.Set[:n+1]
|
||||
copy(s.Set[i+1:], s.Set[i:])
|
||||
} else {
|
||||
// allocate new slice and copy everything, n is at least 1
|
||||
set := make([]Seq, n+1, n*2)
|
||||
copy(set, s.Set[:i])
|
||||
copy(set[i+1:], s.Set[i:])
|
||||
s.Set = set
|
||||
}
|
||||
s.Set[i] = v
|
||||
return
|
||||
}
|
||||
|
||||
// search attempts to find the index of the sequence 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 SeqSet) search(q uint32) (i int, ok bool) {
|
||||
min, max := 0, len(s.Set)-1
|
||||
for min < max {
|
||||
if mid := (min + max) >> 1; s.Set[mid].Less(q) {
|
||||
min = mid + 1
|
||||
} else {
|
||||
max = mid
|
||||
}
|
||||
}
|
||||
if max < 0 || s.Set[min].Less(q) {
|
||||
return len(s.Set), false // q is the new largest value
|
||||
}
|
||||
return min, s.Set[min].Contains(q)
|
||||
}
|
52
vendor/github.com/emersion/go-imap/server/cmd_any.go
generated
vendored
Normal file
52
vendor/github.com/emersion/go-imap/server/cmd_any.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
type Capability struct {
|
||||
commands.Capability
|
||||
}
|
||||
|
||||
func (cmd *Capability) Handle(conn Conn) error {
|
||||
res := &responses.Capability{Caps: conn.Capabilities()}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
type Noop struct {
|
||||
commands.Noop
|
||||
}
|
||||
|
||||
func (cmd *Noop) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox != nil {
|
||||
// If a mailbox is selected, NOOP can be used to poll for server updates
|
||||
if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok {
|
||||
return mbox.Poll()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Logout struct {
|
||||
commands.Logout
|
||||
}
|
||||
|
||||
func (cmd *Logout) Handle(conn Conn) error {
|
||||
res := &imap.StatusResp{
|
||||
Type: imap.StatusBye,
|
||||
Info: "Closing connection",
|
||||
}
|
||||
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Request to close the connection
|
||||
conn.Context().State = imap.LogoutState
|
||||
return nil
|
||||
}
|
261
vendor/github.com/emersion/go-imap/server/cmd_auth.go
generated
vendored
Normal file
261
vendor/github.com/emersion/go-imap/server/cmd_auth.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// imap errors in Authenticated state.
|
||||
var (
|
||||
ErrNotAuthenticated = errors.New("Not authenticated")
|
||||
)
|
||||
|
||||
type Select struct {
|
||||
commands.Select
|
||||
}
|
||||
|
||||
func (cmd *Select) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := []string{
|
||||
imap.MailboxFlags, imap.MailboxPermanentFlags,
|
||||
imap.MailboxMessages, imap.MailboxRecent, imap.MailboxUnseen,
|
||||
imap.MailboxUidNext, imap.MailboxUidValidity,
|
||||
}
|
||||
|
||||
status, err := mbox.Status(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Mailbox = mbox
|
||||
ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
|
||||
|
||||
res := &responses.Select{Mailbox: status}
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code := imap.CodeReadWrite
|
||||
if ctx.MailboxReadOnly {
|
||||
code = imap.CodeReadOnly
|
||||
}
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: code,
|
||||
})
|
||||
}
|
||||
|
||||
type Create struct {
|
||||
commands.Create
|
||||
}
|
||||
|
||||
func (cmd *Create) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.CreateMailbox(cmd.Mailbox)
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
commands.Delete
|
||||
}
|
||||
|
||||
func (cmd *Delete) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.DeleteMailbox(cmd.Mailbox)
|
||||
}
|
||||
|
||||
type Rename struct {
|
||||
commands.Rename
|
||||
}
|
||||
|
||||
func (cmd *Rename) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.RenameMailbox(cmd.Existing, cmd.New)
|
||||
}
|
||||
|
||||
type Subscribe struct {
|
||||
commands.Subscribe
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mbox.SetSubscribed(true)
|
||||
}
|
||||
|
||||
type Unsubscribe struct {
|
||||
commands.Unsubscribe
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mbox.SetSubscribed(false)
|
||||
}
|
||||
|
||||
type List struct {
|
||||
commands.List
|
||||
}
|
||||
|
||||
func (cmd *List) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
ch := make(chan *imap.MailboxInfo)
|
||||
res := &responses.List{Mailboxes: ch, Subscribed: cmd.Subscribed}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
close(done)
|
||||
})()
|
||||
|
||||
mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed)
|
||||
if err != nil {
|
||||
close(ch)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mbox := range mailboxes {
|
||||
info, err := mbox.Info()
|
||||
if err != nil {
|
||||
close(ch)
|
||||
return err
|
||||
}
|
||||
|
||||
// An empty ("" string) mailbox name argument is a special request to return
|
||||
// the hierarchy delimiter and the root name of the name given in the
|
||||
// reference.
|
||||
if cmd.Mailbox == "" {
|
||||
ch <- &imap.MailboxInfo{
|
||||
Attributes: []string{imap.NoSelectAttr},
|
||||
Delimiter: info.Delimiter,
|
||||
Name: info.Delimiter,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if info.Match(cmd.Reference, cmd.Mailbox) {
|
||||
ch <- info
|
||||
}
|
||||
}
|
||||
|
||||
close(ch)
|
||||
|
||||
return <-done
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
commands.Status
|
||||
}
|
||||
|
||||
func (cmd *Status) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := mbox.Status(cmd.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only keep items thqat have been requested
|
||||
items := make(map[string]interface{})
|
||||
for _, k := range cmd.Items {
|
||||
items[k] = status.Items[k]
|
||||
}
|
||||
status.Items = items
|
||||
|
||||
res := &responses.Status{Mailbox: status}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
type Append struct {
|
||||
commands.Append
|
||||
}
|
||||
|
||||
func (cmd *Append) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err == backend.ErrNoSuchMailbox {
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusNo,
|
||||
Code: imap.CodeTryCreate,
|
||||
Info: err.Error(),
|
||||
})
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mbox.CreateMessage(cmd.Flags, cmd.Date, cmd.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If APPEND targets the currently selected mailbox, send an untagged EXISTS
|
||||
// Do this only if the backend doesn't send updates itself
|
||||
if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
|
||||
status, err := mbox.Status([]string{imap.MailboxMessages})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := &responses.Select{Mailbox: status}
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
127
vendor/github.com/emersion/go-imap/server/cmd_noauth.go
generated
vendored
Normal file
127
vendor/github.com/emersion/go-imap/server/cmd_noauth.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// IMAP errors in Not Authenticated state.
|
||||
var (
|
||||
ErrAlreadyAuthenticated = errors.New("Already authenticated")
|
||||
ErrAuthDisabled = errors.New("Authentication disabled")
|
||||
)
|
||||
|
||||
type StartTLS struct {
|
||||
commands.StartTLS
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if conn.IsTLS() {
|
||||
return errors.New("TLS is already enabled")
|
||||
}
|
||||
if conn.Server().TLSConfig == nil {
|
||||
return errors.New("TLS support not enabled")
|
||||
}
|
||||
|
||||
// Send an OK status response to let the client know that the TLS handshake
|
||||
// can begin
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Info: "Begin TLS negotiation now",
|
||||
})
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Upgrade(conn Conn) error {
|
||||
tlsConfig := conn.Server().TLSConfig
|
||||
|
||||
var tlsConn *tls.Conn
|
||||
err := conn.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
tlsConn = tls.Server(conn, tlsConfig)
|
||||
err := tlsConn.Handshake()
|
||||
return tlsConn, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.setTLSConn(tlsConn)
|
||||
|
||||
res := &responses.Capability{Caps: conn.Capabilities()}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
func afterAuthStatus(conn Conn) error {
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeCapability,
|
||||
Arguments: imap.FormatStringList(conn.Capabilities()),
|
||||
})
|
||||
}
|
||||
|
||||
func canAuth(conn Conn) bool {
|
||||
for _, cap := range conn.Capabilities() {
|
||||
if cap == "AUTH=PLAIN" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
commands.Login
|
||||
}
|
||||
|
||||
func (cmd *Login) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if !canAuth(conn) {
|
||||
return ErrAuthDisabled
|
||||
}
|
||||
|
||||
user, err := conn.Server().Backend.Login(cmd.Username, cmd.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.State = imap.AuthenticatedState
|
||||
ctx.User = user
|
||||
return afterAuthStatus(conn)
|
||||
}
|
||||
|
||||
type Authenticate struct {
|
||||
commands.Authenticate
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if !canAuth(conn) {
|
||||
return ErrAuthDisabled
|
||||
}
|
||||
|
||||
mechanisms := map[string]sasl.Server{}
|
||||
for name, newSasl := range conn.Server().auths {
|
||||
mechanisms[name] = newSasl(conn)
|
||||
}
|
||||
|
||||
err := cmd.Authenticate.Handle(mechanisms, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return afterAuthStatus(conn)
|
||||
}
|
313
vendor/github.com/emersion/go-imap/server/cmd_selected.go
generated
vendored
Normal file
313
vendor/github.com/emersion/go-imap/server/cmd_selected.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// imap errors in Selected state.
|
||||
var (
|
||||
ErrNoMailboxSelected = errors.New("No mailbox selected")
|
||||
ErrMailboxReadOnly = errors.New("Mailbox opened in read-only mode")
|
||||
)
|
||||
|
||||
// A command handler that supports UIDs.
|
||||
type UidHandler interface {
|
||||
Handler
|
||||
|
||||
// Handle this command using UIDs for a given connection.
|
||||
UidHandle(conn Conn) error
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
commands.Check
|
||||
}
|
||||
|
||||
func (cmd *Check) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
return ctx.Mailbox.Check()
|
||||
}
|
||||
|
||||
type Close struct {
|
||||
commands.Close
|
||||
}
|
||||
|
||||
func (cmd *Close) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
mailbox := ctx.Mailbox
|
||||
ctx.Mailbox = nil
|
||||
ctx.MailboxReadOnly = false
|
||||
|
||||
if err := mailbox.Expunge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No need to send expunge updates here, since the mailbox is already unselected
|
||||
return nil
|
||||
}
|
||||
|
||||
type Expunge struct {
|
||||
commands.Expunge
|
||||
}
|
||||
|
||||
func (cmd *Expunge) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
// Get a list of messages that will be deleted
|
||||
// That will allow us to send expunge updates if the backend doesn't support it
|
||||
var seqnums []uint32
|
||||
if conn.Server().Updates == nil {
|
||||
criteria := &imap.SearchCriteria{
|
||||
WithFlags: []string{imap.DeletedFlag},
|
||||
}
|
||||
|
||||
var err error
|
||||
seqnums, err = ctx.Mailbox.SearchMessages(false, criteria)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Mailbox.Expunge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the backend doesn't support expunge updates, let's do it ourselves
|
||||
if conn.Server().Updates == nil {
|
||||
done := make(chan error)
|
||||
defer close(done)
|
||||
|
||||
ch := make(chan uint32)
|
||||
res := &responses.Expunge{SeqNums: ch}
|
||||
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
})()
|
||||
|
||||
// Iterate sequence numbers from the last one to the first one, as deleting
|
||||
// messages changes their respective numbers
|
||||
for i := len(seqnums) - 1; i >= 0; i-- {
|
||||
ch <- seqnums[i]
|
||||
}
|
||||
close(ch)
|
||||
|
||||
if err := <-done; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Search struct {
|
||||
commands.Search
|
||||
}
|
||||
|
||||
func (cmd *Search) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
ids, err := ctx.Mailbox.SearchMessages(uid, cmd.Criteria)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := &responses.Search{Ids: ids}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
func (cmd *Search) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Search) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Fetch struct {
|
||||
commands.Fetch
|
||||
}
|
||||
|
||||
func (cmd *Fetch) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
ch := make(chan *imap.Message)
|
||||
res := &responses.Fetch{Messages: ch}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
})()
|
||||
|
||||
err := ctx.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return <-done
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Fetch) UidHandle(conn Conn) error {
|
||||
// Append UID to the list of requested items if it isn't already present
|
||||
hasUid := false
|
||||
for _, item := range cmd.Items {
|
||||
if item == "UID" {
|
||||
hasUid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUid {
|
||||
cmd.Items = append(cmd.Items, "UID")
|
||||
}
|
||||
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
commands.Store
|
||||
}
|
||||
|
||||
func (cmd *Store) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
itemStr := cmd.Item
|
||||
silent := strings.HasSuffix(itemStr, imap.SilentOp)
|
||||
if silent {
|
||||
itemStr = strings.TrimSuffix(itemStr, imap.SilentOp)
|
||||
}
|
||||
item := imap.FlagsOp(itemStr)
|
||||
|
||||
if item != imap.SetFlags && item != imap.AddFlags && item != imap.RemoveFlags {
|
||||
return errors.New("Unsupported STORE operation")
|
||||
}
|
||||
|
||||
flagsList, ok := cmd.Value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("Flags must be a list")
|
||||
}
|
||||
flags, err := imap.ParseStringList(flagsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, flag := range flags {
|
||||
flags[i] = imap.CanonicalFlag(flag)
|
||||
}
|
||||
|
||||
// If the backend supports message updates, this will prevent this connection
|
||||
// from receiving them
|
||||
// TODO: find a better way to do this, without conn.silent
|
||||
*conn.silent() = silent
|
||||
err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
|
||||
*conn.silent() = false
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Not silent: send FETCH updates if the backend doesn't support message
|
||||
// updates
|
||||
if conn.Server().Updates == nil && !silent {
|
||||
inner := &Fetch{}
|
||||
inner.SeqSet = cmd.SeqSet
|
||||
inner.Items = []string{"FLAGS"}
|
||||
if uid {
|
||||
inner.Items = append(inner.Items, "UID")
|
||||
}
|
||||
|
||||
if err := inner.handle(uid, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Store) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Store) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Copy struct {
|
||||
commands.Copy
|
||||
}
|
||||
|
||||
func (cmd *Copy) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
return ctx.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
|
||||
}
|
||||
|
||||
func (cmd *Copy) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Copy) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Uid struct {
|
||||
commands.Uid
|
||||
}
|
||||
|
||||
func (cmd *Uid) Handle(conn Conn) error {
|
||||
inner := cmd.Cmd.Command()
|
||||
hdlr, err := conn.commandHandler(inner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uidHdlr, ok := hdlr.(UidHandler)
|
||||
if !ok {
|
||||
return errors.New("Command unsupported with UID")
|
||||
}
|
||||
|
||||
if err := uidHdlr.UidHandle(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Info: imap.Uid + " " + inner.Name + " completed",
|
||||
})
|
||||
}
|
383
vendor/github.com/emersion/go-imap/server/conn.go
generated
vendored
Normal file
383
vendor/github.com/emersion/go-imap/server/conn.go
generated
vendored
Normal file
@@ -0,0 +1,383 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
)
|
||||
|
||||
// Conn is a connection to a client.
|
||||
type Conn interface {
|
||||
io.Reader
|
||||
|
||||
// Server returns this connection's server.
|
||||
Server() *Server
|
||||
// Context returns this connection's context.
|
||||
Context() *Context
|
||||
// Capabilities returns a list of capabilities enabled for this connection.
|
||||
Capabilities() []string
|
||||
// WriteResp writes a response to this connection.
|
||||
WriteResp(res imap.WriterTo) error
|
||||
// IsTLS returns true if TLS is enabled.
|
||||
IsTLS() bool
|
||||
// TLSState returns the TLS connection state if TLS is enabled, nil otherwise.
|
||||
TLSState() *tls.ConnectionState
|
||||
// Upgrade upgrades a connection, e.g. wrap an unencrypted connection with an
|
||||
// encrypted tunnel.
|
||||
Upgrade(upgrader imap.ConnUpgrader) error
|
||||
// Close closes this connection.
|
||||
Close() error
|
||||
|
||||
setTLSConn(*tls.Conn)
|
||||
silent() *bool // TODO: remove this
|
||||
serve() error
|
||||
commandHandler(cmd *imap.Command) (hdlr Handler, err error)
|
||||
}
|
||||
|
||||
// Context stores a connection's metadata.
|
||||
type Context struct {
|
||||
// This connection's current state.
|
||||
State imap.ConnState
|
||||
// If the client is logged in, the user.
|
||||
User backend.User
|
||||
// If the client has selected a mailbox, the mailbox.
|
||||
Mailbox backend.Mailbox
|
||||
// True if the currently selected mailbox has been opened in read-only mode.
|
||||
MailboxReadOnly bool
|
||||
// Responses to send to the client.
|
||||
Responses chan<- imap.WriterTo
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
*imap.Conn
|
||||
|
||||
s *Server
|
||||
ctx *Context
|
||||
l sync.Locker
|
||||
tlsConn *tls.Conn
|
||||
continues chan bool
|
||||
responses chan imap.WriterTo
|
||||
silentVal bool
|
||||
}
|
||||
|
||||
func newConn(s *Server, c net.Conn) *conn {
|
||||
// Create an imap.Reader and an imap.Writer
|
||||
continues := make(chan bool)
|
||||
r := imap.NewServerReader(nil, continues)
|
||||
w := imap.NewWriter(nil)
|
||||
|
||||
responses := make(chan imap.WriterTo)
|
||||
|
||||
tlsConn, _ := c.(*tls.Conn)
|
||||
|
||||
conn := &conn{
|
||||
Conn: imap.NewConn(c, r, w),
|
||||
|
||||
s: s,
|
||||
l: &sync.Mutex{},
|
||||
ctx: &Context{
|
||||
State: imap.ConnectingState,
|
||||
Responses: responses,
|
||||
},
|
||||
tlsConn: tlsConn,
|
||||
continues: continues,
|
||||
responses: responses,
|
||||
}
|
||||
|
||||
if s.Debug != nil {
|
||||
conn.Conn.SetDebug(s.Debug)
|
||||
}
|
||||
if s.MaxLiteralSize > 0 {
|
||||
conn.Conn.MaxLiteralSize = s.MaxLiteralSize
|
||||
}
|
||||
|
||||
conn.l.Lock()
|
||||
go conn.send()
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *conn) Server() *Server {
|
||||
return c.s
|
||||
}
|
||||
|
||||
func (c *conn) Context() *Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type response struct {
|
||||
response imap.WriterTo
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (r *response) WriteTo(w *imap.Writer) error {
|
||||
err := r.response.WriteTo(w)
|
||||
close(r.done)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) setDeadline() {
|
||||
if c.s.AutoLogout == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dur := c.s.AutoLogout
|
||||
if dur < MinAutoLogout {
|
||||
dur = MinAutoLogout
|
||||
}
|
||||
t := time.Now().Add(dur)
|
||||
|
||||
c.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *conn) WriteResp(r imap.WriterTo) error {
|
||||
done := make(chan struct{})
|
||||
c.responses <- &response{r, done}
|
||||
<-done
|
||||
c.setDeadline()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
if c.ctx.User != nil {
|
||||
c.ctx.User.Logout()
|
||||
}
|
||||
|
||||
if err := c.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
close(c.continues)
|
||||
|
||||
c.ctx.State = imap.LogoutState
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Capabilities() []string {
|
||||
caps := []string{"IMAP4rev1"}
|
||||
|
||||
if c.ctx.State == imap.NotAuthenticatedState {
|
||||
if !c.IsTLS() && c.s.TLSConfig != nil {
|
||||
caps = append(caps, "STARTTLS")
|
||||
}
|
||||
|
||||
if !c.canAuth() {
|
||||
caps = append(caps, "LOGINDISABLED")
|
||||
} else {
|
||||
for name := range c.s.auths {
|
||||
caps = append(caps, "AUTH="+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range c.s.extensions {
|
||||
caps = append(caps, ext.Capabilities(c)...)
|
||||
}
|
||||
|
||||
return caps
|
||||
}
|
||||
|
||||
func (c *conn) send() {
|
||||
// Send continuation requests
|
||||
go func() {
|
||||
for range c.continues {
|
||||
res := &imap.ContinuationResp{Info: "send literal"}
|
||||
if err := res.WriteTo(c.Writer); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot send continuation request: ", err)
|
||||
} else if err := c.Writer.Flush(); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot flush connection: ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Send responses
|
||||
for {
|
||||
// Get a response that needs to be sent
|
||||
res := <-c.responses
|
||||
|
||||
// Request to send the response
|
||||
c.l.Lock()
|
||||
|
||||
// Send the response
|
||||
if err := res.WriteTo(c.Writer); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot send response: ", err)
|
||||
} else if err := c.Writer.Flush(); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot flush connection: ", err)
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) greet() error {
|
||||
c.ctx.State = imap.NotAuthenticatedState
|
||||
|
||||
caps := c.Capabilities()
|
||||
args := make([]interface{}, len(caps))
|
||||
for i, cap := range caps {
|
||||
args[i] = cap
|
||||
}
|
||||
|
||||
greeting := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeCapability,
|
||||
Arguments: args,
|
||||
Info: "IMAP4rev1 Service Ready",
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
defer c.l.Lock()
|
||||
|
||||
return c.WriteResp(greeting)
|
||||
}
|
||||
|
||||
func (c *conn) setTLSConn(tlsConn *tls.Conn) {
|
||||
c.tlsConn = tlsConn
|
||||
}
|
||||
|
||||
func (c *conn) IsTLS() bool {
|
||||
return c.tlsConn != nil
|
||||
}
|
||||
|
||||
func (c *conn) TLSState() *tls.ConnectionState {
|
||||
if c.tlsConn != nil {
|
||||
state := c.tlsConn.ConnectionState()
|
||||
return &state
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// canAuth checks if the client can use plain text authentication.
|
||||
func (c *conn) canAuth() bool {
|
||||
return c.IsTLS() || c.s.AllowInsecureAuth
|
||||
}
|
||||
|
||||
func (c *conn) silent() *bool {
|
||||
return &c.silentVal
|
||||
}
|
||||
|
||||
func (c *conn) serve() error {
|
||||
// Send greeting
|
||||
if err := c.greet(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
if c.ctx.State == imap.LogoutState {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res *imap.StatusResp
|
||||
var up Upgrader
|
||||
|
||||
c.Wait()
|
||||
fields, err := c.ReadLine()
|
||||
if err == io.EOF || c.ctx.State == imap.LogoutState {
|
||||
return nil
|
||||
}
|
||||
c.setDeadline()
|
||||
|
||||
if err != nil {
|
||||
if imap.IsParseError(err) {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.s.ErrorLog.Println("cannot read command:", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
cmd := &imap.Command{}
|
||||
if err := cmd.Parse(fields); err != nil {
|
||||
res = &imap.StatusResp{
|
||||
Tag: cmd.Tag,
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
res, up, err = c.handleCommand(cmd)
|
||||
if err != nil {
|
||||
res = &imap.StatusResp{
|
||||
Tag: cmd.Tag,
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
c.l.Unlock()
|
||||
|
||||
if err := c.WriteResp(res); err != nil {
|
||||
c.s.ErrorLog.Println("cannot write response:", err)
|
||||
c.l.Lock()
|
||||
continue
|
||||
}
|
||||
|
||||
if up != nil && res.Type == imap.StatusOk {
|
||||
if err := up.Upgrade(c); err != nil {
|
||||
c.s.ErrorLog.Println("cannot upgrade connection:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.l.Lock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) commandHandler(cmd *imap.Command) (hdlr Handler, err error) {
|
||||
newHandler := c.s.Command(cmd.Name)
|
||||
if newHandler == nil {
|
||||
err = errors.New("Unknown command")
|
||||
return
|
||||
}
|
||||
|
||||
hdlr = newHandler()
|
||||
err = hdlr.Parse(cmd.Arguments)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) handleCommand(cmd *imap.Command) (res *imap.StatusResp, up Upgrader, err error) {
|
||||
hdlr, err := c.commandHandler(cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
defer c.l.Lock()
|
||||
|
||||
hdlrErr := hdlr.Handle(c)
|
||||
if statusErr, ok := hdlrErr.(*errStatusResp); ok {
|
||||
res = statusErr.resp
|
||||
} else if hdlrErr != nil {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusNo,
|
||||
Info: hdlrErr.Error(),
|
||||
}
|
||||
} else {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
res.Tag = cmd.Tag
|
||||
|
||||
if res.Type == imap.StatusOk && res.Info == "" {
|
||||
res.Info = cmd.Name + " completed"
|
||||
}
|
||||
}
|
||||
|
||||
up, _ = hdlr.(Upgrader)
|
||||
return
|
||||
}
|
426
vendor/github.com/emersion/go-imap/server/server.go
generated
vendored
Normal file
426
vendor/github.com/emersion/go-imap/server/server.go
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
// Package server provides an IMAP server.
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// The minimum autologout duration defined in RFC 3501 section 5.4.
|
||||
const MinAutoLogout = 30 * time.Minute
|
||||
|
||||
// A command handler.
|
||||
type Handler interface {
|
||||
imap.Parser
|
||||
|
||||
// Handle this command for a given connection.
|
||||
//
|
||||
// By default, after this function has returned a status response is sent. To
|
||||
// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
|
||||
Handle(conn Conn) error
|
||||
}
|
||||
|
||||
// A connection upgrader. If a Handler is also an Upgrader, the connection will
|
||||
// be upgraded after the Handler succeeds.
|
||||
//
|
||||
// This should only be used by libraries implementing an IMAP extension (e.g.
|
||||
// COMPRESS).
|
||||
type Upgrader interface {
|
||||
// Upgrade the connection. This method should call conn.Upgrade().
|
||||
Upgrade(conn Conn) error
|
||||
}
|
||||
|
||||
// A function that creates handlers.
|
||||
type HandlerFactory func() Handler
|
||||
|
||||
// A function that creates SASL servers.
|
||||
type SaslServerFactory func(conn Conn) sasl.Server
|
||||
|
||||
// An IMAP extension.
|
||||
type Extension interface {
|
||||
// Get capabilities provided by this extension for a given connection.
|
||||
Capabilities(c Conn) []string
|
||||
// Get the command handler factory for the provided command name.
|
||||
Command(name string) HandlerFactory
|
||||
}
|
||||
|
||||
// An extension that provides additional features to each connection.
|
||||
type ConnExtension interface {
|
||||
Extension
|
||||
|
||||
// This function will be called when a client connects to the server. It can
|
||||
// be used to add new features to the default Conn interface by implementing
|
||||
// new methods.
|
||||
NewConn(c Conn) Conn
|
||||
}
|
||||
|
||||
type errStatusResp struct {
|
||||
resp *imap.StatusResp
|
||||
}
|
||||
|
||||
func (err *errStatusResp) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ErrStatusResp can be returned by a Handler to replace the default status
|
||||
// response. The response tag must be empty.
|
||||
//
|
||||
// To disable the default status response, use ErrNoStatusResp instead.
|
||||
func ErrStatusResp(res *imap.StatusResp) error {
|
||||
return &errStatusResp{res}
|
||||
}
|
||||
|
||||
// ErrNoStatusResp can be returned by a Handler to prevent the default status
|
||||
// response from being sent.
|
||||
func ErrNoStatusResp() error {
|
||||
return &errStatusResp{nil}
|
||||
}
|
||||
|
||||
// An IMAP server.
|
||||
type Server struct {
|
||||
locker sync.Mutex
|
||||
listeners map[net.Listener]struct{}
|
||||
conns map[Conn]struct{}
|
||||
|
||||
commands map[string]HandlerFactory
|
||||
auths map[string]SaslServerFactory
|
||||
extensions []Extension
|
||||
|
||||
// TCP address to listen on.
|
||||
Addr string
|
||||
// This server's TLS configuration.
|
||||
TLSConfig *tls.Config
|
||||
// This server's backend.
|
||||
Backend backend.Backend
|
||||
// Backend updates that will be sent to connected clients.
|
||||
Updates <-chan interface{}
|
||||
// Automatically logout clients after a duration. To do not logout users
|
||||
// automatically, set this to zero. The duration MUST be at least
|
||||
// MinAutoLogout (as stated in RFC 3501 section 5.4).
|
||||
AutoLogout time.Duration
|
||||
// Allow authentication over unencrypted connections.
|
||||
AllowInsecureAuth bool
|
||||
// An io.Writer to which all network activity will be mirrored.
|
||||
Debug io.Writer
|
||||
// ErrorLog specifies an optional logger for errors accepting
|
||||
// connections and unexpected behavior from handlers.
|
||||
// If nil, logging goes to os.Stderr via the log package's
|
||||
// standard logger.
|
||||
ErrorLog imap.Logger
|
||||
// The maximum literal size, in bytes. Literals exceeding this size will be
|
||||
// rejected. A value of zero disables the limit (this is the default).
|
||||
MaxLiteralSize uint32
|
||||
}
|
||||
|
||||
// Create a new IMAP server from an existing listener.
|
||||
func New(bkd backend.Backend) *Server {
|
||||
s := &Server{
|
||||
listeners: make(map[net.Listener]struct{}),
|
||||
conns: make(map[Conn]struct{}),
|
||||
Backend: bkd,
|
||||
ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags),
|
||||
}
|
||||
|
||||
s.auths = map[string]SaslServerFactory{
|
||||
sasl.Plain: func(conn Conn) sasl.Server {
|
||||
return sasl.NewPlainServer(func(identity, username, password string) error {
|
||||
if identity != "" && identity != username {
|
||||
return errors.New("Identities not supported")
|
||||
}
|
||||
|
||||
user, err := bkd.Login(username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := conn.Context()
|
||||
ctx.State = imap.AuthenticatedState
|
||||
ctx.User = user
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
s.commands = map[string]HandlerFactory{
|
||||
imap.Noop: func() Handler { return &Noop{} },
|
||||
imap.Capability: func() Handler { return &Capability{} },
|
||||
imap.Logout: func() Handler { return &Logout{} },
|
||||
|
||||
imap.StartTLS: func() Handler { return &StartTLS{} },
|
||||
imap.Login: func() Handler { return &Login{} },
|
||||
imap.Authenticate: func() Handler { return &Authenticate{} },
|
||||
|
||||
imap.Select: func() Handler { return &Select{} },
|
||||
imap.Examine: func() Handler {
|
||||
hdlr := &Select{}
|
||||
hdlr.ReadOnly = true
|
||||
return hdlr
|
||||
},
|
||||
imap.Create: func() Handler { return &Create{} },
|
||||
imap.Delete: func() Handler { return &Delete{} },
|
||||
imap.Rename: func() Handler { return &Rename{} },
|
||||
imap.Subscribe: func() Handler { return &Subscribe{} },
|
||||
imap.Unsubscribe: func() Handler { return &Unsubscribe{} },
|
||||
imap.List: func() Handler { return &List{} },
|
||||
imap.Lsub: func() Handler {
|
||||
hdlr := &List{}
|
||||
hdlr.Subscribed = true
|
||||
return hdlr
|
||||
},
|
||||
imap.Status: func() Handler { return &Status{} },
|
||||
imap.Append: func() Handler { return &Append{} },
|
||||
|
||||
imap.Check: func() Handler { return &Check{} },
|
||||
imap.Close: func() Handler { return &Close{} },
|
||||
imap.Expunge: func() Handler { return &Expunge{} },
|
||||
imap.Search: func() Handler { return &Search{} },
|
||||
imap.Fetch: func() Handler { return &Fetch{} },
|
||||
imap.Store: func() Handler { return &Store{} },
|
||||
imap.Copy: func() Handler { return &Copy{} },
|
||||
imap.Uid: func() Handler { return &Uid{} },
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l.
|
||||
func (s *Server) Serve(l net.Listener) error {
|
||||
s.locker.Lock()
|
||||
s.listeners[l] = struct{}{}
|
||||
s.locker.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
l.Close()
|
||||
delete(s.listeners, l)
|
||||
}()
|
||||
|
||||
go s.listenUpdates()
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var conn Conn = newConn(s, c)
|
||||
for _, ext := range s.extensions {
|
||||
if ext, ok := ext.(ConnExtension); ok {
|
||||
conn = ext.NewConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
go s.serveConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address s.Addr and then calls Serve
|
||||
// to handle requests on incoming connections.
|
||||
//
|
||||
// If s.Addr is blank, ":imap" is used.
|
||||
func (s *Server) ListenAndServe() error {
|
||||
addr := s.Addr
|
||||
if addr == "" {
|
||||
addr = ":imap"
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
|
||||
// Serve to handle requests on incoming TLS connections.
|
||||
//
|
||||
// If s.Addr is blank, ":imaps" is used.
|
||||
func (s *Server) ListenAndServeTLS() error {
|
||||
addr := s.Addr
|
||||
if addr == "" {
|
||||
addr = ":imaps"
|
||||
}
|
||||
|
||||
l, err := tls.Listen("tcp", addr, s.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
func (s *Server) serveConn(conn Conn) error {
|
||||
s.locker.Lock()
|
||||
s.conns[conn] = struct{}{}
|
||||
s.locker.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
conn.Close()
|
||||
delete(s.conns, conn)
|
||||
}()
|
||||
|
||||
return conn.serve()
|
||||
}
|
||||
|
||||
// Get a command handler factory for the provided command name.
|
||||
func (s *Server) Command(name string) HandlerFactory {
|
||||
// Extensions can override builtin commands
|
||||
for _, ext := range s.extensions {
|
||||
if h := ext.Command(name); h != nil {
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
return s.commands[name]
|
||||
}
|
||||
|
||||
func (s *Server) listenUpdates() (err error) {
|
||||
updater, ok := s.Backend.(backend.Updater)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.Updates = updater.Updates()
|
||||
|
||||
for {
|
||||
item := <-s.Updates
|
||||
|
||||
var (
|
||||
update *backend.Update
|
||||
res imap.WriterTo
|
||||
)
|
||||
|
||||
switch item := item.(type) {
|
||||
case *backend.StatusUpdate:
|
||||
update = &item.Update
|
||||
res = item.StatusResp
|
||||
case *backend.MailboxUpdate:
|
||||
update = &item.Update
|
||||
res = &responses.Select{Mailbox: item.MailboxStatus}
|
||||
case *backend.MessageUpdate:
|
||||
update = &item.Update
|
||||
|
||||
ch := make(chan *imap.Message, 1)
|
||||
ch <- item.Message
|
||||
close(ch)
|
||||
|
||||
res = &responses.Fetch{Messages: ch}
|
||||
case *backend.ExpungeUpdate:
|
||||
update = &item.Update
|
||||
|
||||
ch := make(chan uint32, 1)
|
||||
ch <- item.SeqNum
|
||||
close(ch)
|
||||
|
||||
res = &responses.Expunge{SeqNums: ch}
|
||||
default:
|
||||
s.ErrorLog.Printf("unhandled update: %T\n", item)
|
||||
}
|
||||
|
||||
if update == nil || res == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sends := make(chan struct{})
|
||||
wait := 0
|
||||
s.locker.Lock()
|
||||
for conn := range s.conns {
|
||||
ctx := conn.Context()
|
||||
|
||||
if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) {
|
||||
continue
|
||||
}
|
||||
if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) {
|
||||
continue
|
||||
}
|
||||
if *conn.silent() {
|
||||
// If silent is set, do not send message updates
|
||||
if _, ok := res.(*responses.Fetch); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
conn := conn // Copy conn to a local variable
|
||||
go func() {
|
||||
done := make(chan struct{})
|
||||
conn.Context().Responses <- &response{
|
||||
response: res,
|
||||
done: done,
|
||||
}
|
||||
<-done
|
||||
sends <- struct{}{}
|
||||
}()
|
||||
|
||||
wait++
|
||||
}
|
||||
s.locker.Unlock()
|
||||
|
||||
if wait > 0 {
|
||||
go func() {
|
||||
for done := 0; done < wait; done++ {
|
||||
<-sends
|
||||
}
|
||||
close(sends)
|
||||
|
||||
backend.DoneUpdate(update)
|
||||
}()
|
||||
} else {
|
||||
backend.DoneUpdate(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachConn iterates through all opened connections.
|
||||
func (s *Server) ForEachConn(f func(Conn)) {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
for conn := range s.conns {
|
||||
f(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Stops listening and closes all current connections.
|
||||
func (s *Server) Close() error {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
|
||||
for l := range s.listeners {
|
||||
l.Close()
|
||||
}
|
||||
|
||||
for conn := range s.conns {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enable some IMAP extensions on this server.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (s *Server) Enable(extensions ...Extension) {
|
||||
s.extensions = append(s.extensions, extensions...)
|
||||
}
|
||||
|
||||
// Enable an authentication mechanism on this server.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (s *Server) EnableAuth(name string, f SaslServerFactory) {
|
||||
s.auths[name] = f
|
||||
}
|
120
vendor/github.com/emersion/go-imap/status.go
generated
vendored
Normal file
120
vendor/github.com/emersion/go-imap/status.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A status response type.
|
||||
type StatusRespType string
|
||||
|
||||
// Status response types defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
// The OK response indicates an information message from the server. When
|
||||
// tagged, it indicates successful completion of the associated command.
|
||||
// The untagged form indicates an information-only message.
|
||||
StatusOk StatusRespType = "OK"
|
||||
|
||||
// The NO response indicates an operational error message from the
|
||||
// server. When tagged, it indicates unsuccessful completion of the
|
||||
// associated command. The untagged form indicates a warning; the
|
||||
// command can still complete successfully.
|
||||
StatusNo = "NO"
|
||||
|
||||
// The BAD response indicates an error message from the server. When
|
||||
// tagged, it reports a protocol-level error in the client's command;
|
||||
// the tag indicates the command that caused the error. The untagged
|
||||
// form indicates a protocol-level error for which the associated
|
||||
// command can not be determined; it can also indicate an internal
|
||||
// server failure.
|
||||
StatusBad = "BAD"
|
||||
|
||||
// The PREAUTH response is always untagged, and is one of three
|
||||
// possible greetings at connection startup. It indicates that the
|
||||
// connection has already been authenticated by external means; thus
|
||||
// no LOGIN command is needed.
|
||||
StatusPreauth = "PREAUTH"
|
||||
|
||||
// The BYE response is always untagged, and indicates that the server
|
||||
// is about to close the connection.
|
||||
StatusBye = "BYE"
|
||||
)
|
||||
|
||||
// Status response codes defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
CodeAlert = "ALERT"
|
||||
CodeBadCharset = "BADCHARSET"
|
||||
CodeCapability = "CAPABILITY"
|
||||
CodeParse = "PARSE"
|
||||
CodePermanentFlags = "PERMANENTFLAGS"
|
||||
CodeReadOnly = "READ-ONLY"
|
||||
CodeReadWrite = "READ-WRITE"
|
||||
CodeTryCreate = "TRYCREATE"
|
||||
CodeUidNext = "UIDNEXT"
|
||||
CodeUidValidity = "UIDVALIDITY"
|
||||
CodeUnseen = "UNSEEN"
|
||||
)
|
||||
|
||||
// A status response.
|
||||
// See RFC 3501 section 7.1
|
||||
type StatusResp struct {
|
||||
// The response tag. If empty, it defaults to *.
|
||||
Tag string
|
||||
|
||||
// The status type.
|
||||
Type StatusRespType
|
||||
|
||||
// The status code.
|
||||
// See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml
|
||||
Code string
|
||||
|
||||
// Arguments provided with the status code.
|
||||
Arguments []interface{}
|
||||
|
||||
// The status info.
|
||||
Info string
|
||||
}
|
||||
|
||||
// If this status is NO or BAD, returns an error with the status info.
|
||||
// Otherwise, returns nil.
|
||||
func (r *StatusResp) Err() error {
|
||||
if r == nil {
|
||||
// No status response, connection closed before we get one
|
||||
return errors.New("imap: connection closed during command execution")
|
||||
}
|
||||
|
||||
if r.Type == StatusNo || r.Type == StatusBad {
|
||||
return errors.New(r.Info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StatusResp) WriteTo(w *Writer) error {
|
||||
tag := r.Tag
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
if err := w.writeFields([]interface{}{tag, string(r.Type)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Code != "" {
|
||||
if err := w.writeRespCode(r.Code, r.Arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeString(r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
152
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
Normal file
152
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var ErrBadUtf7 = errors.New("bad utf-7 encoding")
|
||||
|
||||
var Decoder = &encoding.Decoder{
|
||||
Transformer: &decoder{true},
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
ascii bool
|
||||
}
|
||||
|
||||
func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
|
||||
if ch < min || ch > max { // Illegal code point in ASCII mode
|
||||
err = ErrBadUtf7
|
||||
return
|
||||
}
|
||||
|
||||
if ch != '&' {
|
||||
if nDst+1 > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc++
|
||||
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
|
||||
d.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
|
||||
err = ErrBadUtf7
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(src) { // Implicit shift ("&...")
|
||||
if atEOF {
|
||||
err = ErrBadUtf7
|
||||
} else {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if i == start { // Escape sequence "&-"
|
||||
b = []byte{'&'}
|
||||
d.ascii = true
|
||||
} else { // Control or non-ASCII code points in base64
|
||||
if !d.ascii { // Null shift ("&...-&...-")
|
||||
err = ErrBadUtf7
|
||||
return
|
||||
}
|
||||
|
||||
b = decode(src[start:i])
|
||||
d.ascii = false
|
||||
}
|
||||
|
||||
if len(b) == 0 { // Bad encoding
|
||||
err = ErrBadUtf7
|
||||
return
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i + 1
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
if atEOF {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) Reset() {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
// 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, enc.DecodedLen(n)*3)
|
||||
} else {
|
||||
n += 4 - n&3
|
||||
b = make([]byte, n+enc.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 := enc.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 == repl {
|
||||
return nil
|
||||
}
|
||||
} else if min <= r && r <= max {
|
||||
return nil
|
||||
}
|
||||
j += utf8.EncodeRune(s[j:], r)
|
||||
}
|
||||
return s[:j]
|
||||
}
|
96
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
Normal file
96
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var Encoder = &encoding.Encoder{
|
||||
Transformer: &encoder{},
|
||||
}
|
||||
|
||||
type encoder struct{}
|
||||
|
||||
func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); {
|
||||
ch := src[i]
|
||||
|
||||
var b []byte
|
||||
if min <= ch && ch <= max {
|
||||
b = []byte{ch}
|
||||
if ch == '&' {
|
||||
b = append(b, '-')
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
start := i
|
||||
|
||||
// Find the next printable ASCII code point
|
||||
i++
|
||||
for i < len(src) && (src[i] < min || src[i] > max) {
|
||||
i++
|
||||
}
|
||||
|
||||
if !atEOF && i == len(src) {
|
||||
err = transform.ErrShortSrc
|
||||
return
|
||||
}
|
||||
|
||||
b = encode(src[start:i])
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *encoder) Reset() {}
|
||||
|
||||
// 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 != repl {
|
||||
b = append(b, byte(r1>>8), byte(r1))
|
||||
r = r2
|
||||
}
|
||||
b = append(b, byte(r>>8), byte(r))
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
n := enc.EncodedLen(len(b)) + 2
|
||||
b64 := make([]byte, n)
|
||||
enc.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
|
||||
}
|
15
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
Normal file
15
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// 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
|
||||
|
||||
repl = '\uFFFD' // Unicode replacement code point
|
||||
)
|
||||
|
||||
var enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
229
vendor/github.com/emersion/go-imap/write.go
generated
vendored
Normal file
229
vendor/github.com/emersion/go-imap/write.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// A string that will be quoted.
|
||||
type Quoted string
|
||||
|
||||
type WriterTo interface {
|
||||
WriteTo(w *Writer) error
|
||||
}
|
||||
|
||||
func formatNumber(num uint32) string {
|
||||
return strconv.FormatUint(uint64(num), 10)
|
||||
}
|
||||
|
||||
// Convert a string list to a field list.
|
||||
func FormatStringList(list []string) (fields []interface{}) {
|
||||
fields = make([]interface{}, len(list))
|
||||
for i, v := range list {
|
||||
fields[i] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a string is 8-bit clean.
|
||||
func isAscii(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > unicode.MaxASCII || unicode.IsControl(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// An IMAP writer.
|
||||
type Writer struct {
|
||||
io.Writer
|
||||
|
||||
continues <-chan bool
|
||||
}
|
||||
|
||||
// Helper function to write a string to w.
|
||||
func (w *Writer) writeString(s string) error {
|
||||
_, err := io.WriteString(w.Writer, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Writer) writeCrlf() error {
|
||||
if err := w.writeString(crlf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (w *Writer) writeNumber(num uint32) error {
|
||||
return w.writeString(formatNumber(num))
|
||||
}
|
||||
|
||||
func (w *Writer) writeQuoted(s string) error {
|
||||
return w.writeString(strconv.Quote(s))
|
||||
}
|
||||
|
||||
func (w *Writer) writeAtom(s string) error {
|
||||
return w.writeString(s)
|
||||
}
|
||||
|
||||
func (w *Writer) writeAstring(s string) error {
|
||||
if !isAscii(s) {
|
||||
// IMAP doesn't allow 8-bit data outside literals
|
||||
return w.writeLiteral(bytes.NewBufferString(s))
|
||||
}
|
||||
|
||||
specials := string([]rune{dquote, listStart, listEnd, literalStart, sp})
|
||||
if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, specials) {
|
||||
return w.writeQuoted(s)
|
||||
}
|
||||
|
||||
return w.writeAtom(s)
|
||||
}
|
||||
|
||||
func (w *Writer) writeDateTime(t time.Time, layout string) error {
|
||||
if t.IsZero() {
|
||||
return w.writeAtom(nilAtom)
|
||||
}
|
||||
return w.writeQuoted(t.Format(layout))
|
||||
}
|
||||
|
||||
func (w *Writer) writeFields(fields []interface{}) error {
|
||||
for i, field := range fields {
|
||||
if i > 0 { // Write separator
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeField(field); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeList(fields []interface{}) error {
|
||||
if err := w.writeString(string(listStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(listEnd))
|
||||
}
|
||||
|
||||
func (w *Writer) writeLiteral(l Literal) error {
|
||||
if l == nil {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
|
||||
header := string(literalStart) + strconv.Itoa(l.Len()) + string(literalEnd) + crlf
|
||||
if err := w.writeString(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If a channel is available, wait for a continuation request before sending data
|
||||
if w.continues != nil {
|
||||
// Make sure to flush the writer, otherwise we may never receive a continuation request
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !<-w.continues {
|
||||
return fmt.Errorf("imap: cannot send literal: no continuation request received")
|
||||
}
|
||||
}
|
||||
|
||||
_, err := io.Copy(w, l)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Writer) writeField(field interface{}) error {
|
||||
if field == nil {
|
||||
return w.writeAtom(nilAtom)
|
||||
}
|
||||
|
||||
switch field := field.(type) {
|
||||
case string:
|
||||
return w.writeAstring(field)
|
||||
case Quoted:
|
||||
return w.writeQuoted(string(field))
|
||||
case int:
|
||||
return w.writeNumber(uint32(field))
|
||||
case uint32:
|
||||
return w.writeNumber(field)
|
||||
case Literal:
|
||||
return w.writeLiteral(field)
|
||||
case []interface{}:
|
||||
return w.writeList(field)
|
||||
case envelopeDateTime:
|
||||
return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
|
||||
case searchDate:
|
||||
return w.writeDateTime(time.Time(field), searchDateLayout)
|
||||
case Date:
|
||||
return w.writeDateTime(time.Time(field), DateLayout)
|
||||
case DateTime:
|
||||
return w.writeDateTime(time.Time(field), DateTimeLayout)
|
||||
case time.Time:
|
||||
return w.writeDateTime(field, DateTimeLayout)
|
||||
case *SeqSet:
|
||||
return w.writeString(field.String())
|
||||
case *BodySectionName:
|
||||
// Can contain spaces - that's why we don't just pass it as a string
|
||||
return w.writeString(field.String())
|
||||
}
|
||||
|
||||
return fmt.Errorf("imap: cannot format field: %v", field)
|
||||
}
|
||||
|
||||
func (w *Writer) writeRespCode(code string, args []interface{}) error {
|
||||
if err := w.writeString(string(respCodeStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []interface{}{code}
|
||||
fields = append(fields, args...)
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(respCodeEnd))
|
||||
}
|
||||
|
||||
func (w *Writer) writeLine(fields ...interface{}) error {
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
func (w *Writer) Flush() error {
|
||||
if f, ok := w.Writer.(flusher); ok {
|
||||
return f.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{Writer: w}
|
||||
}
|
||||
|
||||
func NewClientWriter(w io.Writer, continues <-chan bool) *Writer {
|
||||
return &Writer{Writer: w, continues: continues}
|
||||
}
|
Reference in New Issue
Block a user