Update vendor/

This commit is contained in:
2018-06-28 01:09:56 +01:00
parent 3e5ab5bb0a
commit 21c6e571d8
108 changed files with 121110 additions and 1144 deletions

View File

@@ -0,0 +1,2 @@
// Package backendutil provides utility functions to implement IMAP backends.
package backendutil

View File

@@ -0,0 +1,68 @@
package backendutil
import (
"bytes"
"errors"
"io"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
)
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-- {
n := section.Path[i]
mr := e.MultipartReader()
if mr == nil {
return nil, errNoSuchPart
}
for j := 1; j <= n; j++ {
p, err := mr.NextPart()
if err == io.EOF {
return nil, errNoSuchPart
} else if err != nil {
return nil, err
}
if j == n {
e = p
break
}
}
}
// Then, write the requested data to a buffer
b := new(bytes.Buffer)
// Write the header
mw, err := message.CreateWriter(b, e.Header)
if err != nil {
return nil, err
}
defer mw.Close()
// If the header hasn't been requested, discard it
if section.Specifier == imap.TextSpecifier {
b.Reset()
}
// Write the body, if requested
switch section.Specifier {
case imap.EntireSpecifier, imap.TextSpecifier:
if _, err := io.Copy(mw, e.Body); err != nil {
return nil, err
}
}
var l imap.Literal = b
if section.Partial != nil {
l = bytes.NewReader(section.ExtractPartial(b.Bytes()))
}
return l, nil
}

View File

@@ -0,0 +1,60 @@
package backendutil
import (
"io"
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
)
// FetchBodyStructure computes a message's body structure from its content.
func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure, error) {
bs := new(imap.BodyStructure)
mediaType, mediaParams, _ := e.Header.ContentType()
typeParts := strings.SplitN(mediaType, "/", 2)
bs.MIMEType = typeParts[0]
if len(typeParts) == 2 {
bs.MIMESubType = typeParts[1]
}
bs.Params = mediaParams
bs.Id = e.Header.Get("Content-Id")
bs.Description = e.Header.Get("Content-Description")
bs.Encoding = e.Header.Get("Content-Encoding")
// TODO: bs.Size
if mr := e.MultipartReader(); mr != nil {
var parts []*imap.BodyStructure
for {
p, err := mr.NextPart()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
pbs, err := FetchBodyStructure(p, extended)
if err != nil {
return nil, err
}
parts = append(parts, pbs)
}
bs.Parts = parts
}
// TODO: bs.Envelope, bs.BodyStructure
// TODO: bs.Lines
if extended {
bs.Extended = true
bs.Disposition, bs.DispositionParams, _ = e.Header.ContentDisposition()
// TODO: bs.Language, bs.Location
// TODO: bs.MD5
}
return bs, nil
}

View File

@@ -0,0 +1,50 @@
package backendutil
import (
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
)
func headerAddressList(h mail.Header, key string) ([]*imap.Address, error) {
addrs, err := h.AddressList(key)
list := make([]*imap.Address, len(addrs))
for i, a := range addrs {
parts := strings.SplitN(a.Address, "@", 2)
mailbox := parts[0]
var hostname string
if len(parts) == 2 {
hostname = parts[1]
}
list[i] = &imap.Address{
PersonalName: a.Name,
MailboxName: mailbox,
HostName: hostname,
}
}
return list, err
}
// FetchEnvelope returns a message's envelope from its header.
func FetchEnvelope(h message.Header) (*imap.Envelope, error) {
mh := mail.Header{h}
env := new(imap.Envelope)
env.Date, _ = mh.Date()
env.Subject, _ = mh.Subject()
env.From, _ = headerAddressList(mh, "From")
env.Sender, _ = headerAddressList(mh, "Sender")
env.ReplyTo, _ = headerAddressList(mh, "Reply-To")
env.To, _ = headerAddressList(mh, "To")
env.Cc, _ = headerAddressList(mh, "Cc")
env.Bcc, _ = headerAddressList(mh, "Bcc")
env.InReplyTo = mh.Get("In-Reply-To")
env.MessageId = mh.Get("Message-Id")
return env, nil
}

View File

@@ -0,0 +1,40 @@
package backendutil
import (
"github.com/emersion/go-imap"
)
// UpdateFlags executes a flag operation on the flag set current.
func UpdateFlags(current []string, op imap.FlagsOp, flags []string) []string {
switch op {
case imap.SetFlags:
// TODO: keep \Recent if it is present
return flags
case imap.AddFlags:
// Check for duplicates
for _, flag := range current {
for i, addFlag := range flags {
if addFlag == flag {
flags = append(flags[:i], flags[i+1:]...)
break
}
}
}
return append(current, flags...)
case imap.RemoveFlags:
// Iterate through flags from the last one to the first one, to be able to
// delete some of them.
for i := len(current) - 1; i >= 0; i-- {
flag := current[i]
for _, removeFlag := range flags {
if removeFlag == flag {
current = append(current[:i], current[i+1:]...)
break
}
}
}
return current
}
return current
}

View File

@@ -0,0 +1,225 @@
package backendutil
import (
"bytes"
"fmt"
"io"
"strings"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
)
func matchString(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
func bufferBody(e *message.Entity) (*bytes.Buffer, error) {
b := new(bytes.Buffer)
if _, err := io.Copy(b, e.Body); err != nil {
return nil, err
}
e.Body = b
return b, nil
}
func matchBody(e *message.Entity, substr string) (bool, error) {
if s, ok := e.Body.(fmt.Stringer); ok {
return matchString(s.String(), substr), nil
}
b, err := bufferBody(e)
if err != nil {
return false, err
}
return matchString(b.String(), substr), nil
}
type lengther interface {
Len() int
}
func bodyLen(e *message.Entity) (int, error) {
if l, ok := e.Body.(lengther); ok {
return l.Len(), nil
}
b, err := bufferBody(e)
if err != nil {
return 0, err
}
return b.Len(), nil
}
// Match returns true if a message matches the provided criteria. Sequence
// number, UID, flag and internal date contrainsts are not checked.
func Match(e *message.Entity, c *imap.SearchCriteria) (bool, error) {
// TODO: support encoded header fields for Bcc, Cc, From, To
// TODO: add header size for Larger and Smaller
h := mail.Header{e.Header}
if !c.SentBefore.IsZero() || !c.SentSince.IsZero() {
t, err := h.Date()
if err != nil {
return false, err
}
t = t.Round(24 * time.Hour)
if !c.SentBefore.IsZero() && !t.Before(c.SentBefore) {
return false, nil
}
if !c.SentSince.IsZero() && !t.After(c.SentSince) {
return false, nil
}
}
for key, wantValues := range c.Header {
values, ok := e.Header[key]
for _, wantValue := range wantValues {
if wantValue == "" && !ok {
return false, nil
}
if wantValue != "" {
ok := false
for _, v := range values {
if matchString(v, wantValue) {
ok = true
break
}
}
if !ok {
return false, nil
}
}
}
}
for _, body := range c.Body {
if ok, err := matchBody(e, body); err != nil || !ok {
return false, err
}
}
for _, text := range c.Text {
// TODO: also match header fields
if ok, err := matchBody(e, text); err != nil || !ok {
return false, err
}
}
if c.Larger > 0 || c.Smaller > 0 {
n, err := bodyLen(e)
if err != nil {
return false, err
}
if c.Larger > 0 && uint32(n) < c.Larger {
return false, nil
}
if c.Smaller > 0 && uint32(n) > c.Smaller {
return false, nil
}
}
for _, not := range c.Not {
ok, err := Match(e, not)
if err != nil || ok {
return false, err
}
}
for _, or := range c.Or {
ok1, err := Match(e, or[0])
if err != nil {
return ok1, err
}
ok2, err := Match(e, or[1])
if err != nil || (!ok1 && !ok2) {
return false, err
}
}
return true, nil
}
func matchFlags(flags map[string]bool, c *imap.SearchCriteria) bool {
for _, f := range c.WithFlags {
if !flags[f] {
return false
}
}
for _, f := range c.WithoutFlags {
if flags[f] {
return false
}
}
for _, not := range c.Not {
if matchFlags(flags, not) {
return false
}
}
for _, or := range c.Or {
if !matchFlags(flags, or[0]) && !matchFlags(flags, or[1]) {
return false
}
}
return true
}
// MatchFlags returns true if a flag list matches the provided criteria.
func MatchFlags(flags []string, c *imap.SearchCriteria) bool {
flagsMap := make(map[string]bool)
for _, f := range flags {
flagsMap[f] = true
}
return matchFlags(flagsMap, c)
}
// MatchSeqNumAndUid returns true if a sequence number and a UID matches the
// provided criteria.
func MatchSeqNumAndUid(seqNum uint32, uid uint32, c *imap.SearchCriteria) bool {
if c.SeqNum != nil && !c.SeqNum.Contains(seqNum) {
return false
}
if c.Uid != nil && !c.Uid.Contains(uid) {
return false
}
for _, not := range c.Not {
if MatchSeqNumAndUid(seqNum, uid, not) {
return false
}
}
for _, or := range c.Or {
if !MatchSeqNumAndUid(seqNum, uid, or[0]) && !MatchSeqNumAndUid(seqNum, uid, or[1]) {
return false
}
}
return true
}
// MatchDate returns true if a date matches the provided criteria.
func MatchDate(date time.Time, c *imap.SearchCriteria) bool {
date = date.Round(24 * time.Hour)
if !c.Since.IsZero() && !date.After(c.Since) {
return false
}
if !c.Before.IsZero() && !date.Before(c.Before) {
return false
}
for _, not := range c.Not {
if MatchDate(date, not) {
return false
}
}
for _, or := range c.Or {
if !MatchDate(date, or[0]) && !MatchDate(date, or[1]) {
return false
}
}
return true
}