Commit 711ef4f5 authored by ale's avatar ale

Update dependencies

parent 276f96fd
...@@ -19,11 +19,11 @@ type gpgFilter struct { ...@@ -19,11 +19,11 @@ type gpgFilter struct {
} }
func newGPGFilter(d mailboxDestination, keyFile string) (*gpgFilter, error) { func newGPGFilter(d mailboxDestination, keyFile string) (*gpgFilter, error) {
f, err := os.Open(keyFile) f, err := os.Open(keyFile) // #nosec
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close() // nolint
keys, err := openpgp.ReadArmoredKeyRing(f) keys, err := openpgp.ReadArmoredKeyRing(f)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -65,7 +65,7 @@ func (f *gpgFilter) WriteMessage(folder string, msg *mail.Message) error { ...@@ -65,7 +65,7 @@ func (f *gpgFilter) WriteMessage(folder string, msg *mail.Message) error {
// corrupted PGP message. // corrupted PGP message.
_, err := io.Copy(cleartext, msg.Body) _, err := io.Copy(cleartext, msg.Body)
if err != nil { if err != nil {
_ = cleartext.Close() cleartext.Close() // nolint
return err return err
} }
if err := cleartext.Close(); err != nil { if err := cleartext.Close(); err != nil {
......
...@@ -15,8 +15,25 @@ import ( ...@@ -15,8 +15,25 @@ import (
var ( var (
imapFetchBatchSize = 50 imapFetchBatchSize = 50
imapDeleteBatchSize = 1000 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 { type connectionParams struct {
ssl bool ssl bool
user string user string
...@@ -106,7 +123,7 @@ func connectIMAP(params *connectionParams) (*client.Client, error) { ...@@ -106,7 +123,7 @@ func connectIMAP(params *connectionParams) (*client.Client, error) {
c.ErrorLog = &nilLogger{} c.ErrorLog = &nilLogger{}
if err = c.Login(params.user, params.password); err != nil { if err = c.Login(params.user, params.password); err != nil {
// Ignore errors but try to close the connection. // Ignore errors but try to close the connection.
_ = c.Logout() c.Logout() // nolint
return nil, err return nil, err
} }
return c, nil return c, nil
...@@ -132,7 +149,7 @@ func (s *imapSource) folderName(folder string) string { ...@@ -132,7 +149,7 @@ func (s *imapSource) folderName(folder string) string {
} }
func (s *imapSource) Close() { func (s *imapSource) Close() {
_ = s.client.Logout() s.client.Logout() // nolint
} }
func (s *imapSource) ListFolders() ([]string, error) { func (s *imapSource) ListFolders() ([]string, error) {
...@@ -179,10 +196,10 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) { ...@@ -179,10 +196,10 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) {
// of messages). // of messages).
seqset := new(imap.SeqSet) seqset := new(imap.SeqSet)
seqset.AddRange(1, mbox.Messages) seqset.AddRange(1, mbox.Messages)
attrs := []string{ attrs := []imap.FetchItem{
imap.UidMsgAttr, imap.FetchUid,
imap.InternalDateMsgAttr, imap.FetchInternalDate,
imap.FlagsMsgAttr, imap.FetchFlags,
"BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)]", "BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)]",
} }
...@@ -197,7 +214,7 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) { ...@@ -197,7 +214,7 @@ func (s *imapSource) ListMessages(folder string) ([]message, error) {
if isDeleted(msg) { if isDeleted(msg) {
continue continue
} }
body := msg.GetBody("BODY[HEADER.FIELDS (MESSAGE-ID)]") body := msg.GetBody(bodyAndHeadersSectionName)
if body == nil { if body == nil {
continue continue
} }
...@@ -225,7 +242,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) { ...@@ -225,7 +242,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) {
for _, msg := range batch { for _, msg := range batch {
set.AddNum(msg.(*imapMessage).msg.Uid) set.AddNum(msg.(*imapMessage).msg.Uid)
} }
attrs := []string{"BODY.PEEK[]"} attrs := []imap.FetchItem{"BODY.PEEK[]"}
messages := make(chan *imap.Message, 100) messages := make(chan *imap.Message, 100)
done := make(chan error, 1) done := make(chan error, 1)
go func() { go func() {
...@@ -233,7 +250,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) { ...@@ -233,7 +250,7 @@ func (s *imapSource) fetchBatch(batch []message, ch chan *mail.Message) {
}() }()
for msg := range messages { for msg := range messages {
body := msg.GetBody("BODY[]") body := msg.GetBody(bodySectionName)
if body == nil { if body == nil {
continue continue
} }
...@@ -279,7 +296,7 @@ func (s *imapSource) deleteBatch(msgs []message) error { ...@@ -279,7 +296,7 @@ func (s *imapSource) deleteBatch(msgs []message) error {
for _, msg := range msgs { for _, msg := range msgs {
set.AddNum(msg.(*imapMessage).msg.Uid) 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 { func (s *imapSource) DeleteMessages(folder string, msgs []message) error {
......
...@@ -21,8 +21,8 @@ var ( ...@@ -21,8 +21,8 @@ var (
) )
type imapTestMessage struct { type imapTestMessage struct {
flags []string //flags []string
body []byte body []byte
} }
func (m imapTestMessage) Literal() imap.Literal { func (m imapTestMessage) Literal() imap.Literal {
......
...@@ -40,7 +40,7 @@ func (d Maildir) Unseen() ([]string, error) { ...@@ -40,7 +40,7 @@ func (d Maildir) Unseen() ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close() // nolint
names, err := f.Readdirnames(0) names, err := f.Readdirnames(0)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -63,7 +63,7 @@ func (d Maildir) Keys() ([]string, error) { ...@@ -63,7 +63,7 @@ func (d Maildir) Keys() ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close() // nolint
names, err := f.Readdirnames(0) names, err := f.Readdirnames(0)
return names, err return names, err
} }
...@@ -71,11 +71,11 @@ func (d Maildir) Keys() ([]string, error) { ...@@ -71,11 +71,11 @@ func (d Maildir) Keys() ([]string, error) {
// Header returns a parsed message header. // Header returns a parsed message header.
func (d Maildir) Header(key string) (mail.Header, error) { func (d Maildir) Header(key string) (mail.Header, error) {
filename := filepath.Join(string(d), "cur", key) filename := filepath.Join(string(d), "cur", key)
file, err := os.Open(filename) file, err := os.Open(filename) // #nosec
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close() defer file.Close() // nolint
tp := textproto.NewReader(bufio.NewReader(file)) tp := textproto.NewReader(bufio.NewReader(file))
hdr, err := tp.ReadMIMEHeader() hdr, err := tp.ReadMIMEHeader()
if err != nil { if err != nil {
...@@ -87,11 +87,11 @@ func (d Maildir) Header(key string) (mail.Header, error) { ...@@ -87,11 +87,11 @@ func (d Maildir) Header(key string) (mail.Header, error) {
// Message returns a Message by key. // Message returns a Message by key.
func (d Maildir) Message(key string) (*mail.Message, error) { func (d Maildir) Message(key string) (*mail.Message, error) {
filename := filepath.Join(string(d), "cur", key) filename := filepath.Join(string(d), "cur", key)
r, err := os.Open(filename) r, err := os.Open(filename) // #nosec
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer r.Close() defer r.Close() // nolint
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err = io.Copy(buf, r) _, err = io.Copy(buf, r)
if err != nil { if err != nil {
...@@ -121,8 +121,8 @@ func (d *Delivery) Close() error { ...@@ -121,8 +121,8 @@ func (d *Delivery) Close() error {
// Abort the delivery (remove temporary file). // Abort the delivery (remove temporary file).
func (d *Delivery) Abort() { func (d *Delivery) Abort() {
_ = d.f.Close() d.f.Close() // nolint
_ = os.Remove(d.curPath) os.Remove(d.curPath) // nolint
} }
// Write the message body. // Write the message body.
...@@ -187,7 +187,8 @@ type maildirMessage struct { ...@@ -187,7 +187,8 @@ type maildirMessage struct {
} }
func (m *maildirMessage) Date() time.Time { 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 return t
} }
...@@ -236,7 +237,7 @@ func (s *maildirSource) ListMessages(folder string) ([]message, error) { ...@@ -236,7 +237,7 @@ func (s *maildirSource) ListMessages(folder string) ([]message, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
unseen, _ := dir.Unseen() unseen, _ := dir.Unseen() // nolint
if len(unseen) > 0 { if len(unseen) > 0 {
keys = append(keys, unseen...) keys = append(keys, unseen...)
} }
......
...@@ -123,10 +123,10 @@ func messagesInFolder(src mailboxSource, folder string, selectionFilter messageF ...@@ -123,10 +123,10 @@ func messagesInFolder(src mailboxSource, folder string, selectionFilter messageF
func writeMessage(msg *mail.Message, w io.Writer) error { func writeMessage(msg *mail.Message, w io.Writer) error {
for hdr, values := range msg.Header { for hdr, values := range msg.Header {
for _, value := range values { 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) _, err := io.Copy(w, msg.Body)
return err return err
} }
...@@ -187,7 +187,7 @@ func syncFolder(src mailboxSource, dst mailboxDestination, folder string, select ...@@ -187,7 +187,7 @@ func syncFolder(src mailboxSource, dst mailboxDestination, folder string, select
go func() { go func() {
// Ignore errors on destination, it's ok if the folder // Ignore errors on destination, it's ok if the folder
// does not exist for example. // does not exist for example.
dstMsgs, _ = messagesInFolder(dst, folder, nil) dstMsgs, _ = messagesInFolder(dst, folder, nil) // nolint
wg.Done() wg.Done()
}() }()
wg.Wait() wg.Wait()
......
...@@ -6,29 +6,14 @@ ...@@ -6,29 +6,14 @@
[![Go Report [![Go Report
Card](https://goreportcard.com/badge/github.com/emersion/go-imap)](https://goreportcard.com/report/github.com/emersion/go-imap) 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) [![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 An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
can be used to build a client and/or a server. 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). ```shell
```bash
go get github.com/emersion/go-imap/... 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 ## Usage
### Client [![GoDoc](https://godoc.org/github.com/emersion/go-imap/client?status.svg)](https://godoc.org/github.com/emersion/go-imap/client) ### 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() { ...@@ -98,7 +83,7 @@ func main() {
messages := make(chan *imap.Message, 10) messages := make(chan *imap.Message, 10)
done = make(chan error, 1) done = make(chan error, 1)
go func() { go func() {
done <- c.Fetch(seqset, []string{imap.EnvelopeMsgAttr}, messages) done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
}() }()
log.Println("Last 4 messages:") log.Println("Last 4 messages:")
......
...@@ -14,7 +14,7 @@ var errNoSuchPart = errors.New("backendutil: no such message body part") ...@@ -14,7 +14,7 @@ var errNoSuchPart = errors.New("backendutil: no such message body part")
// FetchBodySection extracts a body section from a message. // FetchBodySection extracts a body section from a message.
func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Literal, error) { func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Literal, error) {
// First, find the requested part using the provided path // 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] n := section.Path[i]
mr := e.MultipartReader() mr := e.MultipartReader()
...@@ -47,9 +47,16 @@ func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Li ...@@ -47,9 +47,16 @@ func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Li
} }
defer mw.Close() defer mw.Close()
// If the header hasn't been requested, discard it switch section.Specifier {
if section.Specifier == imap.TextSpecifier { case imap.TextSpecifier:
// The header hasn't been requested. Discard it.
b.Reset() 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 // Write the body, if requested
......
...@@ -14,9 +14,9 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure, ...@@ -14,9 +14,9 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure,
mediaType, mediaParams, _ := e.Header.ContentType() mediaType, mediaParams, _ := e.Header.ContentType()
typeParts := strings.SplitN(mediaType, "/", 2) typeParts := strings.SplitN(mediaType, "/", 2)
bs.MimeType = typeParts[0] bs.MIMEType = typeParts[0]
if len(typeParts) == 2 { if len(typeParts) == 2 {
bs.MimeSubType = typeParts[1] bs.MIMESubType = typeParts[1]
} }
bs.Params = mediaParams bs.Params = mediaParams
...@@ -53,7 +53,7 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure, ...@@ -53,7 +53,7 @@ func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure,
bs.Disposition, bs.DispositionParams, _ = e.Header.ContentDisposition() bs.Disposition, bs.DispositionParams, _ = e.Header.ContentDisposition()
// TODO: bs.Language, bs.Location // TODO: bs.Language, bs.Location
// TODO: bs.Md5 // TODO: bs.MD5
} }
return bs, nil return bs, nil
......
...@@ -15,11 +15,11 @@ type Mailbox interface { ...@@ -15,11 +15,11 @@ type Mailbox interface {
// Info returns this mailbox info. // Info returns this mailbox info.
Info() (*imap.MailboxInfo, error) Info() (*imap.MailboxInfo, error)
// Status returns this mailbox status. The fields Name, Flags and // Status returns this mailbox status. The fields Name, Flags, PermanentFlags
// PermanentFlags in the returned MailboxStatus must be always populated. This // and UnseenSeqNum in the returned MailboxStatus must be always populated.
// function does not affect the state of any messages in the mailbox. See RFC // This function does not affect the state of any messages in the mailbox. See
// 3501 section 6.3.10 for a list of items that can be requested. // RFC 3501 section 6.3.10 for a list of items that can be requested.
Status(items []string) (*imap.MailboxStatus, error) Status(items []imap.StatusItem) (*imap.MailboxStatus, error)
// SetSubscribed adds or removes the mailbox to the server's set of "active" // SetSubscribed adds or removes the mailbox to the server's set of "active"
// or "subscribed" mailboxes. // or "subscribed" mailboxes.
...@@ -38,7 +38,7 @@ type Mailbox interface { ...@@ -38,7 +38,7 @@ type Mailbox interface {
// 3501 section 6.4.5 for a list of items that can be requested. // 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. // 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 // SearchMessages searches messages. The returned list must contain UIDs if
// uid is set to true, or sequence numbers otherwise. // uid is set to true, or sequence numbers otherwise.
......
...@@ -59,22 +59,42 @@ func (mbox *Mailbox) flags() []string { ...@@ -59,22 +59,42 @@ func (mbox *Mailbox) flags() []string {
return flags 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 := imap.NewMailboxStatus(mbox.name, items)
status.Flags = mbox.flags() status.Flags = mbox.flags()
status.PermanentFlags = []string{"\\*"} status.PermanentFlags = []string{"\\*"}
status.UnseenSeqNum = mbox.unseenSeqNum()
for _, name := range items { for _, name := range items {
switch name { switch name {
case imap.MailboxMessages: case imap.StatusMessages:
status.Messages = uint32(len(mbox.Messages)) status.Messages = uint32(len(mbox.Messages))
case imap.MailboxUidNext: case imap.StatusUidNext:
status.UidNext = mbox.uidNext() status.UidNext = mbox.uidNext()
case imap.MailboxUidValidity: case imap.StatusUidValidity:
status.UidValidity = 1 status.UidValidity = 1
case imap.MailboxRecent: case imap.StatusRecent:
status.Recent = 0 // TODO status.Recent = 0 // TODO
case imap.MailboxUnseen: case imap.StatusUnseen:
status.Unseen = 0 // TODO status.Unseen = 0 // TODO
} }
} }
...@@ -91,7 +111,7 @@ func (mbox *Mailbox) Check() error { ...@@ -91,7 +111,7 @@ func (mbox *Mailbox) Check() error {
return nil return nil
} }
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items