Commit 711ef4f5 authored by ale's avatar ale

Update dependencies

parent 276f96fd

Too many changes to show.

To preserve performance only 222 of 222+ files are displayed.

......@@ -19,11 +19,11 @@ type gpgFilter struct {
}
func newGPGFilter(d mailboxDestination, keyFile string) (*gpgFilter, error) {
f, err := os.Open(keyFile)
f, err := os.Open(keyFile) // #nosec
if err != nil {
return nil, err
}
defer f.Close()
defer f.Close() // nolint
keys, err := openpgp.ReadArmoredKeyRing(f)
if err != nil {
return nil, err
......@@ -65,7 +65,7 @@ func (f *gpgFilter) WriteMessage(folder string, msg *mail.Message) error {
// corrupted PGP message.
_, err := io.Copy(cleartext, msg.Body)
if err != nil {
_ = cleartext.Close()
cleartext.Close() // nolint
return err
}
if err := cleartext.Close(); err != nil {
......
......@@ -15,8 +15,25 @@ import (
var (
imapFetchBatchSize = 50
imapDeleteBatchSize = 1000
bodyAndHeadersSectionName *imap.BodySectionName
bodySectionName *imap.BodySectionName
)
func init() {
var err error
bodyAndHeadersSectionName, err = imap.ParseBodySectionName(
imap.FetchItem("BODY[HEADER.FIELDS (MESSAGE-ID)]"))
if err != nil {
log.Fatal(err)
}
bodySectionName, err = imap.ParseBodySectionName(
imap.FetchItem("BODY[]"))
if err != nil {
log.Fatal(err)
}
}
type connectionParams struct {
ssl bool
user string
......@@ -106,7 +123,7 @@ func connectIMAP(params *connectionParams) (*client.Client, error) {
c.ErrorLog = &nilLogger{}
if err = c.Login(params.user, params.password); err != nil {
// Ignore errors but try to close the connection.
_ = c.Logout()
c.Logout() // nolint
return nil, err
}
return c, nil
......@@ -132,7 +149,7 @@ func (s *imapSource) folderName(folder string) string {
}
func (s *imapSource) Close() {
_ = s.client.Logout()
s.client.Logout() // nolint
}
func (s *imapSource) ListFolders() ([]string, error) {
......@@ -179,10 +196,10 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) {
// of messages).
seqset := new(imap.SeqSet)
seqset.AddRange(1, mbox.Messages)
attrs := []string{
imap.UidMsgAttr,
imap.InternalDateMsgAttr,
imap.FlagsMsgAttr,
attrs := []imap.FetchItem{
imap.FetchUid,
imap.FetchInternalDate,
imap.FetchFlags,
"BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)]",
}
......@@ -197,7 +214,7 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) {
if isDeleted(msg) {
continue
}
body := msg.GetBody("BODY[HEADER.FIELDS (MESSAGE-ID)]")
body := msg.GetBody(bodyAndHeadersSectionName)
if body == nil {
continue
}
......@@ -225,7 +242,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) {
for _, msg := range batch {
set.AddNum(msg.(*imapMessage).msg.Uid)
}
attrs := []string{"BODY.PEEK[]"}
attrs := []imap.FetchItem{"BODY.PEEK[]"}
messages := make(chan *imap.Message, 100)
done := make(chan error, 1)
go func() {
......@@ -233,7 +250,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) {
}()
for msg := range messages {
body := msg.GetBody("BODY[]")
body := msg.GetBody(bodySectionName)
if body == nil {
continue
}
......@@ -279,7 +296,7 @@ func (s *imapSource) deleteBatch(msgs []message) error {
for _, msg := range msgs {
set.AddNum(msg.(*imapMessage).msg.Uid)
}
return s.client.UidStore(set, string(imap.SilentOp+imap.AddFlags), []string{imap.DeletedFlag}, nil)
return s.client.UidStore(set, imap.FormatFlagsOp(imap.AddFlags, true), []string{imap.DeletedFlag}, nil)
}
func (s *imapSource) DeleteMessages(folder string, msgs []message) error {
......
......@@ -21,8 +21,8 @@ var (
)
type imapTestMessage struct {
flags []string
body []byte
//flags []string
body []byte
}
func (m imapTestMessage) Literal() imap.Literal {
......
......@@ -40,7 +40,7 @@ func (d Maildir) Unseen() ([]string, error) {
if err != nil {
return nil, err
}
defer f.Close()
defer f.Close() // nolint
names, err := f.Readdirnames(0)
if err != nil {
return nil, err
......@@ -63,7 +63,7 @@ func (d Maildir) Keys() ([]string, error) {
if err != nil {
return nil, err
}
defer f.Close()
defer f.Close() // nolint
names, err := f.Readdirnames(0)
return names, err
}
......@@ -71,11 +71,11 @@ func (d Maildir) Keys() ([]string, error) {
// Header returns a parsed message header.
func (d Maildir) Header(key string) (mail.Header, error) {
filename := filepath.Join(string(d), "cur", key)
file, err := os.Open(filename)
file, err := os.Open(filename) // #nosec
if err != nil {
return nil, err
}
defer file.Close()
defer file.Close() // nolint
tp := textproto.NewReader(bufio.NewReader(file))
hdr, err := tp.ReadMIMEHeader()
if err != nil {
......@@ -87,11 +87,11 @@ func (d Maildir) Header(key string) (mail.Header, error) {
// Message returns a Message by key.
func (d Maildir) Message(key string) (*mail.Message, error) {
filename := filepath.Join(string(d), "cur", key)
r, err := os.Open(filename)
r, err := os.Open(filename) // #nosec
if err != nil {
return nil, err
}
defer r.Close()
defer r.Close() // nolint
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r)
if err != nil {
......@@ -121,8 +121,8 @@ func (d *Delivery) Close() error {
// Abort the delivery (remove temporary file).
func (d *Delivery) Abort() {
_ = d.f.Close()
_ = os.Remove(d.curPath)
d.f.Close() // nolint
os.Remove(d.curPath) // nolint
}
// Write the message body.
......@@ -187,7 +187,8 @@ type maildirMessage struct {
}
func (m *maildirMessage) Date() time.Time {
t, _ := mail.ParseDate(m.hdr.Get("Date"))
// Return the zero value on error.
t, _ := mail.ParseDate(m.hdr.Get("Date")) // nolint
return t
}
......@@ -236,7 +237,7 @@ func (s *maildirSource) ListMessages(folder string) ([]message, error) {
if err != nil {
return nil, err
}
unseen, _ := dir.Unseen()
unseen, _ := dir.Unseen() // nolint
if len(unseen) > 0 {
keys = append(keys, unseen...)
}
......
......@@ -123,10 +123,10 @@ func messagesInFolder(src mailboxSource, folder string, selectionFilter messageF
func writeMessage(msg *mail.Message, w io.Writer) error {
for hdr, values := range msg.Header {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\r\n", hdr, value)
fmt.Fprintf(w, "%s: %s\r\n", hdr, value) // nolint
}
}
fmt.Fprintf(w, "\r\n")
fmt.Fprintf(w, "\r\n") // nolint
_, err := io.Copy(w, msg.Body)
return err
}
......@@ -187,7 +187,7 @@ func syncFolder(src mailboxSource, dst mailboxDestination, folder string, select
go func() {
// Ignore errors on destination, it's ok if the folder
// does not exist for example.
dstMsgs, _ = messagesInFolder(dst, folder, nil)
dstMsgs, _ = messagesInFolder(dst, folder, nil) // nolint
wg.Done()
}()
wg.Wait()
......
......@@ -6,29 +6,14 @@
[![Go Report
Card](https://goreportcard.com/badge/github.com/emersion/go-imap)](https://goreportcard.com/report/github.com/emersion/go-imap)
[![Unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)
[![Gitter chat](https://badges.gitter.im/goimap/Lobby.svg)](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.
**Note**: new projects should use the [`v1` branch](https://github.com/emersion/go-imap/tree/v1).
```bash
```shell
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 [![GoDoc](https://godoc.org/github.com/emersion/go-imap/client?status.svg)](https://godoc.org/github.com/emersion/go-imap/client)
......@@ -98,7 +83,7 @@ func main() {
messages := make(chan *imap.Message, 10)
done = make(chan error, 1)
go func() {
done <- c.Fetch(seqset, []string{imap.EnvelopeMsgAttr}, messages)
done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
}()
log.Println("Last 4 messages:")
......
......@@ -14,7 +14,7 @@ var errNoSuchPart = errors.New("backendutil: no such message body part")
// FetchBodySection extracts a body section from a message.
func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Literal, error) {
// First, find the requested part using the provided path
for i := len(section.Path) - 1; i >= 0; i-- {
for i := 0; i < len(section.Path); i++ {
n := section.Path[i]
mr := e.MultipartReader()
......@@ -47,9 +47,16 @@ func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Li
}
defer mw.Close()
// If the header hasn't been requested, discard it
if section.Specifier == imap.TextSpecifier {
switch section.Specifier {
case imap.TextSpecifier:
// The header hasn't been requested. Discard it.
b.Reset()
case imap.EntireSpecifier:
if len(section.Path) > 0 {
// When selecting a specific part by index, IMAP servers
// return only the text, not the associated MIME header.
b.Reset()
}
}
// Write the body, if requested
......
......@@ -14,9 +14,9 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure,
mediaType, mediaParams, _ := e.Header.ContentType()
typeParts := strings.SplitN(mediaType, "/", 2)
bs.MimeType = typeParts[0]
bs.MIMEType = typeParts[0]
if len(typeParts) == 2 {
bs.MimeSubType = typeParts[1]
bs.MIMESubType = typeParts[1]
}
bs.Params = mediaParams
......@@ -53,7 +53,7 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure,
bs.Disposition, bs.DispositionParams, _ = e.Header.ContentDisposition()
// TODO: bs.Language, bs.Location
// TODO: bs.Md5
// TODO: bs.MD5
}
return bs, nil
......
......@@ -15,11 +15,11 @@ type Mailbox interface {
// 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)
// Status returns this mailbox status. The fields Name, Flags, PermanentFlags
// and UnseenSeqNum 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 []imap.StatusItem) (*imap.MailboxStatus, error)
// SetSubscribed adds or removes the mailbox to the server's set of "active"
// or "subscribed" mailboxes.
......@@ -38,7 +38,7 @@ type Mailbox interface {
// 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
ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error
// SearchMessages searches messages. The returned list must contain UIDs if
// uid is set to true, or sequence numbers otherwise.
......
......@@ -59,22 +59,42 @@ func (mbox *Mailbox) flags() []string {
return flags
}
func (mbox *Mailbox) Status(items []string) (*imap.MailboxStatus, error) {
func (mbox *Mailbox) unseenSeqNum() uint32 {
for i, msg := range mbox.Messages {
seqNum := uint32(i + 1)
seen := false
for _, flag := range msg.Flags {
if flag == imap.SeenFlag {
seen = true
break
}
}
if !seen {
return seqNum
}
}
return 0
}
func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
status := imap.NewMailboxStatus(mbox.name, items)
status.Flags = mbox.flags()
status.PermanentFlags = []string{"\\*"}
status.UnseenSeqNum = mbox.unseenSeqNum()
for _, name := range items {
switch name {
case imap.MailboxMessages:
case imap.StatusMessages:
status.Messages = uint32(len(mbox.Messages))
case imap.MailboxUidNext:
case imap.StatusUidNext:
status.UidNext = mbox.uidNext()
case imap.MailboxUidValidity:
case imap.StatusUidValidity:
status.UidValidity = 1
case imap.MailboxRecent:
case imap.StatusRecent:
status.Recent = 0 // TODO
case imap.MailboxUnseen:
case imap.StatusUnseen:
status.Unseen = 0 // TODO
}
}
......@@ -91,7 +111,7 @@ func (mbox *Mailbox) Check() error {
return nil
}
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []string, ch chan<- *imap.Message) error {
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
defer close(ch)
for i, msg := range mbox.Messages {
......
......@@ -21,23 +21,23 @@ func (m *Message) entity() (*message.Entity, error) {
return message.Read(bytes.NewReader(m.Body))
}
func (m *Message) Fetch(seqNum uint32, items []string) (*imap.Message, error) {
func (m *Message) Fetch(seqNum uint32, items []imap.FetchItem) (*imap.Message, error) {
fetched := imap.NewMessage(seqNum, items)
for _, item := range items {
switch item {
case imap.EnvelopeMsgAttr:
case imap.FetchEnvelope:
e, _ := m.entity()
fetched.Envelope, _ = backendutil.FetchEnvelope(e.Header)
case imap.BodyMsgAttr, imap.BodyStructureMsgAttr:
case imap.FetchBody, imap.FetchBodyStructure:
e, _ := m.entity()
fetched.BodyStructure, _ = backendutil.FetchBodyStructure(e, item == imap.BodyStructureMsgAttr)
case imap.FlagsMsgAttr:
fetched.BodyStructure, _ = backendutil.FetchBodyStructure(e, item == imap.FetchBodyStructure)
case imap.FetchFlags:
fetched.Flags = m.Flags
case imap.InternalDateMsgAttr:
case imap.FetchInternalDate:
fetched.InternalDate = m.Date
case imap.SizeMsgAttr:
case imap.FetchRFC822Size:
fetched.Size = m.Size
case imap.UidMsgAttr:
case imap.FetchUid:
fetched.Uid = m.Uid
default:
section, err := imap.ParseBodySectionName(item)
......
......@@ -6,35 +6,47 @@ import (
// Update contains user and mailbox information about an unilateral backend
// update.
type Update struct {
type Update interface {
// The user targeted by this update. If empty, all connected users will
// be notified.
Username string
Username() string
// The mailbox targeted by this update. If empty, the update targets all
// mailboxes.
Mailbox string
Mailbox() string
// Done returns a channel that is closed when the update has been broadcast to
// all clients.
Done() chan struct{}
}
// A channel that will be closed once the update has been processed.
// NewUpdate creates a new update.
func NewUpdate(username, mailbox string) Update {
return &update{
username: username,
mailbox: mailbox,
}
}
type update struct {
username string
mailbox string
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{} {
func (u *update) Username() string {
return u.username
}
func (u *update) Mailbox() string {
return u.mailbox
}
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 {
......@@ -60,50 +72,21 @@ type ExpungeUpdate struct {
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 {
// BackendUpdater 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 BackendUpdater interface {
// Updates returns a set of channels where updates are sent to.
Updates() <-chan interface{}
Updates() <-chan Update
}
// 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 {
// MailboxPoller is a Mailbox that is able to poll updates for new messages or
// message status updates during a period of inactivity.
type MailboxPoller 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
}
......@@ -12,51 +12,81 @@ import (
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/responses"
)
// errClosed is used when a connection is closed while waiting for a command
// response.
var errClosed = fmt.Errorf("imap: connection closed")
// errUnregisterHandler is returned by a response handler to unregister itself.
var errUnregisterHandler = fmt.Errorf("imap: unregister handler")
// Update is an unilateral server update.
type Update interface {
update()
}
// StatusUpdate is delivered when a status update is received.
type StatusUpdate struct {
Status *imap.StatusResp
}
func (u *StatusUpdate) update() {}
// MailboxUpdate is delivered when a mailbox status changes.
type MailboxUpdate struct {
Mailbox *imap.MailboxStatus
}
func (u *MailboxUpdate) update() {}
// ExpungeUpdate is delivered when a message is deleted.
type ExpungeUpdate struct {
SeqNum uint32
}
func (u *ExpungeUpdate) update() {}
// MessageUpdate is delivered when a message attribute changes.
type MessageUpdate struct {
Message *imap.Message
}
func (u *MessageUpdate) update() {}
// Client is an IMAP client.
type Client struct {
conn *imap.Conn
isTLS bool
handles imap.RespHandler
handler *imap.MultiRespHandler
greeted chan struct{}
loggedOut chan struct{}
// The cached server capabilities.
caps map[string]bool
// The caps map may be accessed in different goroutines. Protect access.
capsLocker sync.Mutex
handlers []responses.Handler
handlersLocker sync.Mutex
// The current connection state