Compare commits
14 Commits
v0.0.1
...
13d2acdf16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13d2acdf16 | ||
|
|
cae30cc5d1 | ||
|
|
bb4b7b4da8 | ||
|
|
deb6c2d196 | ||
|
|
5191add42b | ||
|
|
e3d5a7e221 | ||
|
|
7d372adb78 | ||
|
|
6b04a324f3 | ||
|
|
48cb9bf32b | ||
|
|
2313cc3ac5 | ||
|
|
4b1b194000 | ||
|
|
f850c567ed | ||
|
|
9d91f3a69e | ||
|
|
766e4de5fa |
@@ -3,7 +3,7 @@ package contact
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/system/sysconf"
|
"gogs.inhome.blapointe.com/local/system/sysconf"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package contact
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"local/encryptor"
|
"gogs.inhome.blapointe.com/local/encryptor"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|||||||
18
cmd/recv/go.mod
Normal file
18
cmd/recv/go.mod
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module gogs.inhome.blapointe.com/contact/cmd/recv
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
replace gogs.inhome.blapointe.com/local-sandbox/contact => ../../
|
||||||
|
|
||||||
|
require (
|
||||||
|
gogs.inhome.blapointe.com/local-sandbox/contact v0.0.0-00010101000000-000000000000
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
|
||||||
|
github.com/emersion/go-imap v1.2.0 // indirect
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
3
cmd/recv/go.sh
Executable file
3
cmd/recv/go.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
GOPRIVATE= GONOSUMDB=\* GONOPROXY=\* GOPROXY= go "$@"
|
||||||
18
cmd/recv/go.sum
Normal file
18
cmd/recv/go.sum
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
|
||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
||||||
|
github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA=
|
||||||
|
github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||||
|
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34 h1:0tuX5dfOksiOQD1vbJjVNVTVxTTIng7UrUdSLF5T+Ao=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34/go.mod h1:YG9n3Clg7683ohkVnJK2hdX8bBS9EojIsd1qPZumX0Y=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
81
cmd/recv/main.go
Executable file
81
cmd/recv/main.go
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/mail"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gogs.inhome.blapointe.com/local-sandbox/contact"
|
||||||
|
"gogs.inhome.blapointe.com/local/args"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
emailer := contact.NewEmailer()
|
||||||
|
as := args.NewArgSet()
|
||||||
|
as.Append(args.STRING, "imap", "imap server:port", "imap.gmail.com:993")
|
||||||
|
as.Append(args.STRING, "pop3", "pop3 server:port", "")
|
||||||
|
as.Append(args.INT, "n", "limit (<1 for inf)", 10)
|
||||||
|
as.Append(args.STRING, "u", "username", emailer.From)
|
||||||
|
as.Append(args.STRING, "p", "password", emailer.Password)
|
||||||
|
as.Append(args.INT, "b", "dont read more than this many characters", 4096)
|
||||||
|
as.Append(args.BOOL, "json", "output as json", false)
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailer.IMAP = as.Get("imap").GetString()
|
||||||
|
emailer.POP3 = as.Get("pop3").GetString()
|
||||||
|
emailer.Limit = as.Get("n").GetInt()
|
||||||
|
emailer.From = as.GetString("u")
|
||||||
|
emailer.Password = as.GetString("p")
|
||||||
|
|
||||||
|
var msgs <-chan *mail.Message
|
||||||
|
var err error
|
||||||
|
if emailer.POP3 != "" {
|
||||||
|
msgs, err = emailer.ReadPOP3()
|
||||||
|
} else {
|
||||||
|
msgs, err = emailer.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
emails := make([]map[string]any, 0, emailer.Limit)
|
||||||
|
for msg := range msgs {
|
||||||
|
b, _ := ioutil.ReadAll(io.LimitReader(msg.Body, int64(as.GetInt("b"))))
|
||||||
|
s := strings.ReplaceAll(string(b), "\r\n", "\n")
|
||||||
|
s = strings.ReplaceAll(string(s), "\n", "\n")
|
||||||
|
s = strings.ReplaceAll(string(s), "\r", "\n")
|
||||||
|
if !strings.Contains(s, " ") {
|
||||||
|
s = "..."
|
||||||
|
}
|
||||||
|
d, _ := msg.Header.Date()
|
||||||
|
d = d.In(time.Local)
|
||||||
|
if as.GetBool("json") {
|
||||||
|
emails = append(emails, map[string]any{
|
||||||
|
"t": d.Format("06-01-02T15:04Z07"),
|
||||||
|
"from": msg.Header.Get("From"),
|
||||||
|
"subject": msg.Header.Get("Subject"),
|
||||||
|
"body": s,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fmt.Printf(
|
||||||
|
"@%+v @%+v: \n\t%+v: \n\t%s\n",
|
||||||
|
d.Format("06-01-02T15:04Z07"),
|
||||||
|
msg.Header.Get("From"),
|
||||||
|
msg.Header.Get("Subject"),
|
||||||
|
s,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if as.GetBool("json") {
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
enc.Encode(emails)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
cmd/send/go.mod
Normal file
18
cmd/send/go.mod
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module gogs.inhome.blapointe.com/contact/cmd/send
|
||||||
|
|
||||||
|
go 1.21.0
|
||||||
|
|
||||||
|
replace gogs.inhome.blapointe.com/local-sandbox/contact => ../../
|
||||||
|
|
||||||
|
require (
|
||||||
|
gogs.inhome.blapointe.com/local-sandbox/contact v0.0.1
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
|
||||||
|
github.com/emersion/go-imap v1.2.0 // indirect
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
18
cmd/send/go.sum
Normal file
18
cmd/send/go.sum
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
|
||||||
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
||||||
|
github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA=
|
||||||
|
github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||||
|
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34 h1:0tuX5dfOksiOQD1vbJjVNVTVxTTIng7UrUdSLF5T+Ao=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34/go.mod h1:YG9n3Clg7683ohkVnJK2hdX8bBS9EojIsd1qPZumX0Y=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"local/args"
|
"gogs.inhome.blapointe.com/local-sandbox/contact"
|
||||||
"local/sandbox/contact/contact"
|
"gogs.inhome.blapointe.com/local/args"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -86,23 +86,35 @@ func (e *Emailer) ReadIMAP() (chan *mail.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Emailer) ReadPOP3() (chan *mail.Message, error) {
|
func (e *Emailer) ReadPOP3() (chan *mail.Message, error) {
|
||||||
emails := []*mail.Message{}
|
limit := e.Limit
|
||||||
|
if limit < 1 {
|
||||||
|
limit = 1000
|
||||||
|
}
|
||||||
|
emails := make(chan *mail.Message, limit)
|
||||||
|
defer close(emails)
|
||||||
|
|
||||||
|
log.Printf("pop3.DialTLS(%s)", e.POP3)
|
||||||
c, err := pop3.DialTLS(e.POP3)
|
c, err := pop3.DialTLS(e.POP3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer c.Rset()
|
defer c.Rset()
|
||||||
|
|
||||||
|
log.Printf("c.Auth(%s, xyz)", e.From)
|
||||||
if err := c.Auth(e.From, e.Password); err != nil {
|
if err := c.Auth(e.From, e.Password); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("c.ListAll()")
|
||||||
ids, _, err := c.ListAll()
|
ids, _, err := c.ListAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for j, id := range ids {
|
log.Printf("/c.ListAll() = %v", ids)
|
||||||
if e.Limit > 0 && len(ids)-1-j >= e.Limit {
|
|
||||||
break
|
for i := len(ids) - 1; i >= 0; i-- {
|
||||||
}
|
id := ids[i]
|
||||||
|
log.Printf("c.Retr(%v)", id)
|
||||||
raw, err := c.Retr(id)
|
raw, err := c.Retr(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -111,10 +123,13 @@ func (e *Emailer) ReadPOP3() (chan *mail.Message, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
emails = append(emails, msg)
|
select {
|
||||||
|
case emails <- msg:
|
||||||
|
default:
|
||||||
|
return emails, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
}
|
||||||
//return emails, nil //c.Quit()
|
return emails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Emailer) Send(to, subj, msg string) error {
|
func (e *Emailer) Send(to, subj, msg string) error {
|
||||||
@@ -12,7 +12,7 @@ func TestEmailer(t *testing.T) {
|
|||||||
SMTP: "smtp.gmail.com:587",
|
SMTP: "smtp.gmail.com:587",
|
||||||
POP3: "pop.gmail.com:995",
|
POP3: "pop.gmail.com:995",
|
||||||
IMAP: "imap.gmail.com:993",
|
IMAP: "imap.gmail.com:993",
|
||||||
Password: "ML3WQRFSqe9rQ8qNkm",
|
Password: "lhnjijrvqaesiufp",
|
||||||
}
|
}
|
||||||
|
|
||||||
emails, err := e.Read()
|
emails, err := e.Read()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module local/sandbox/contact/contact
|
module gogs.inhome.blapointe.com/local-sandbox/contact
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
1
last.msg
1
last.msg
@@ -1 +0,0 @@
|
|||||||
wcBMA1Z2i1z86bGuAQgAZ1hb8CH2dcUuKWfstpILJqaonxEsinE2KgrzqHjEtogawL8pqNCWXPYgFxPh2sj4z9zI4/7+jcAcd3wz/mXj/FdaBUpFyMaC1cV9OmD8jSTvamBpG5KI2HHxuHRv0Y19rgjcXUjtZSJcu1ZYj6yMn2+KdDTHScRBXsiapOHc5S9nKrXJ69cFF7jvQRIgnn8KNVWmcau5BXKYrXa9XnwqZtNTFYBaPgyOWb8TMnBK93T4NegMTRj/XeGQ4NAT/5sfHNRvC84mYxZQFojuUTnT3zVgdET7o6AKrtsbHstL8mzaPjM9nMPZfTotSCzsXBE07Dt1zLmPXeo4eT0VyOUtpdLgAeMsLYZM1RdSXeECzuBk4EThv3zgP+IDH09o4OzkfKlNi862TzMNh6rMpCvBGuA14x4vOzvLsDLZ4MDiagQM2eCA4LzgQeQO24U6HvCz7cdAy22h7BqG4pOs1Hfhj8oA
|
|
||||||
47
recv.go
47
recv.go
@@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"local/args"
|
|
||||||
"local/sandbox/contact/contact"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
emailer := contact.NewEmailer()
|
|
||||||
as := args.NewArgSet()
|
|
||||||
as.Append(args.STRING, "imap", "imap server:port", "imap.gmail.com:993")
|
|
||||||
as.Append(args.INT, "n", "limit (<1 for inf)", 10)
|
|
||||||
if err := as.Parse(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
emailer.IMAP = as.Get("imap").GetString()
|
|
||||||
emailer.Limit = as.Get("n").GetInt()
|
|
||||||
|
|
||||||
msgs, err := emailer.Read()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
for msg := range msgs {
|
|
||||||
b, _ := ioutil.ReadAll(io.LimitReader(msg.Body, 1024))
|
|
||||||
s := strings.ReplaceAll(string(b), "\r\n", " ")
|
|
||||||
s = strings.ReplaceAll(string(s), "\n", " ")
|
|
||||||
s = strings.ReplaceAll(string(s), "\r", " ")
|
|
||||||
if !strings.Contains(s, " ") {
|
|
||||||
s = "..."
|
|
||||||
}
|
|
||||||
d, _ := msg.Header.Date()
|
|
||||||
d = d.In(time.Local)
|
|
||||||
fmt.Printf(
|
|
||||||
"@%+v @%+v: %+v: %s\n",
|
|
||||||
d.Format("06-01-02T15:04Z07"),
|
|
||||||
msg.Header.Get("From"),
|
|
||||||
msg.Header.Get("Subject"),
|
|
||||||
s,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
1
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
|||||||
*.swp
|
|
||||||
19
vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
19
vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2012 Scott Lawrence <bytbox@gmail.com>
|
|
||||||
|
|
||||||
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.
|
|
||||||
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
@@ -1,223 +0,0 @@
|
|||||||
// Package pop3 provides an implementation of the Post Office Protocol, Version
|
|
||||||
// 3 as defined in RFC 1939. Commands specified as optional are not
|
|
||||||
// implemented; however, this implementation may be trivially extended to
|
|
||||||
// support them.
|
|
||||||
|
|
||||||
package pop3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The POP3 client.
|
|
||||||
type Client struct {
|
|
||||||
conn net.Conn
|
|
||||||
bin *bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial creates an unsecured connection to the POP3 server at the given address
|
|
||||||
// and returns the corresponding Client.
|
|
||||||
func Dial(addr string) (*Client, error) {
|
|
||||||
conn, err := net.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewClient(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTLS creates a TLS-secured connection to the POP3 server at the given
|
|
||||||
// address and returns the corresponding Client.
|
|
||||||
func DialTLS(addr string) (*Client, error) {
|
|
||||||
conn, err := tls.Dial("tcp", addr, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewClient(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a new Client object using an existing connection.
|
|
||||||
func NewClient(conn net.Conn) (*Client, error) {
|
|
||||||
client := &Client{
|
|
||||||
bin: bufio.NewReader(conn),
|
|
||||||
conn: conn,
|
|
||||||
}
|
|
||||||
// send dud command, to read a line
|
|
||||||
_, err := client.Cmd("")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function to synchronously run an arbitrary command and wait for
|
|
||||||
// output. The terminating CRLF must be included in the format string.
|
|
||||||
//
|
|
||||||
// Output sent after the first line must be retrieved via readLines.
|
|
||||||
func (c *Client) Cmd(format string, args ...interface{}) (string, error) {
|
|
||||||
fmt.Fprintf(c.conn, format, args...)
|
|
||||||
line, _, err := c.bin.ReadLine()
|
|
||||||
if err != nil { return "", err }
|
|
||||||
l := string(line)
|
|
||||||
if l[0:3] != "+OK" {
|
|
||||||
err = errors.New(l[5:])
|
|
||||||
}
|
|
||||||
if len(l) >= 4 {
|
|
||||||
return l[4:], err
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ReadLines() (lines []string, err error) {
|
|
||||||
lines = make([]string, 0)
|
|
||||||
l, _, err := c.bin.ReadLine()
|
|
||||||
line := string(l)
|
|
||||||
for err == nil && line != "." {
|
|
||||||
if len(line) > 0 && line[0] == '.' {
|
|
||||||
line = line[1:]
|
|
||||||
}
|
|
||||||
lines = append(lines, line)
|
|
||||||
l, _, err = c.bin.ReadLine()
|
|
||||||
line = string(l)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// User sends the given username to the server. Generally, there is no reason
|
|
||||||
// not to use the Auth convenience method.
|
|
||||||
func (c *Client) User(username string) (err error) {
|
|
||||||
_, err = c.Cmd("USER %s\r\n", username)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass sends the given password to the server. The password is sent
|
|
||||||
// unencrypted unless the connection is already secured by TLS (via DialTLS or
|
|
||||||
// some other mechanism). Generally, there is no reason not to use the Auth
|
|
||||||
// convenience method.
|
|
||||||
func (c *Client) Pass(password string) (err error) {
|
|
||||||
_, err = c.Cmd("PASS %s\r\n", password)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth sends the given username and password to the server, calling the User
|
|
||||||
// and Pass methods as appropriate.
|
|
||||||
func (c *Client) Auth(username, password string) (err error) {
|
|
||||||
err = c.User(username)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.Pass(password)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat retrieves a drop listing for the current maildrop, consisting of the
|
|
||||||
// number of messages and the total size (in octets) of the maildrop.
|
|
||||||
// Information provided besides the number of messages and the size of the
|
|
||||||
// maildrop is ignored. In the event of an error, all returned numeric values
|
|
||||||
// will be 0.
|
|
||||||
func (c *Client) Stat() (count, size int, err error) {
|
|
||||||
l, err := c.Cmd("STAT\r\n")
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
parts := strings.Fields(l)
|
|
||||||
count, err = strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, errors.New("Invalid server response")
|
|
||||||
}
|
|
||||||
size, err = strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, errors.New("Invalid server response")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the size of the given message, if it exists. If the message
|
|
||||||
// does not exist, or another error is encountered, the returned size will be
|
|
||||||
// 0.
|
|
||||||
func (c *Client) List(msg int) (size int, err error) {
|
|
||||||
l, err := c.Cmd("LIST %d\r\n", msg)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
size, err = strconv.Atoi(strings.Fields(l)[1])
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.New("Invalid server response")
|
|
||||||
}
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAll returns a list of all messages and their sizes.
|
|
||||||
func (c *Client) ListAll() (msgs []int, sizes []int, err error) {
|
|
||||||
_, err = c.Cmd("LIST\r\n")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lines, err := c.ReadLines()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msgs = make([]int, len(lines), len(lines))
|
|
||||||
sizes = make([]int, len(lines), len(lines))
|
|
||||||
for i, l := range lines {
|
|
||||||
var m, s int
|
|
||||||
fs := strings.Fields(l)
|
|
||||||
m, err = strconv.Atoi(fs[0])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, err = strconv.Atoi(fs[1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msgs[i] = m
|
|
||||||
sizes[i] = s
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retr downloads and returns the given message. The lines are separated by LF,
|
|
||||||
// whatever the server sent.
|
|
||||||
func (c *Client) Retr(msg int) (text string, err error) {
|
|
||||||
_, err = c.Cmd("RETR %d\r\n", msg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
lines, err := c.ReadLines()
|
|
||||||
text = strings.Join(lines, "\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dele marks the given message as deleted.
|
|
||||||
func (c *Client) Dele(msg int) (err error) {
|
|
||||||
_, err = c.Cmd("DELE %d\r\n", msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noop does nothing, but will prolong the end of the connection if the server
|
|
||||||
// has a timeout set.
|
|
||||||
func (c *Client) Noop() (err error) {
|
|
||||||
_, err = c.Cmd("NOOP\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rset unmarks any messages marked for deletion previously in this session.
|
|
||||||
func (c *Client) Rset() (err error) {
|
|
||||||
_, err = c.Cmd("RSET\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quit sends the QUIT message to the POP3 server and closes the connection.
|
|
||||||
func (c *Client) Quit() error {
|
|
||||||
_, err := c.Cmd("QUIT\r\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.conn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
image: alpine/edge
|
|
||||||
packages:
|
|
||||||
- go
|
|
||||||
sources:
|
|
||||||
- https://github.com/emersion/go-imap
|
|
||||||
artifacts:
|
|
||||||
- coverage.html
|
|
||||||
tasks:
|
|
||||||
- build: |
|
|
||||||
cd go-imap
|
|
||||||
go build -race -v ./...
|
|
||||||
- test: |
|
|
||||||
cd go-imap
|
|
||||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
|
||||||
- coverage: |
|
|
||||||
cd go-imap
|
|
||||||
go tool cover -html=coverage.txt -o ~/coverage.html
|
|
||||||
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
@@ -1,28 +0,0 @@
|
|||||||
# 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
|
|
||||||
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
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.
|
|
||||||
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
@@ -1,177 +0,0 @@
|
|||||||
# go-imap
|
|
||||||
|
|
||||||
[](https://godocs.io/github.com/emersion/go-imap)
|
|
||||||
[](https://builds.sr.ht/~emersion/go-imap/commits?)
|
|
||||||
|
|
||||||
An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
|
|
||||||
can be used to build a client and/or a server.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Client [](https://godocs.io/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 subtract 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, []imap.FetchItem{imap.FetchEnvelope}, 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://godocs.io/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.
|
|
||||||
|
|
||||||
## Extensions
|
|
||||||
|
|
||||||
Support for several IMAP extensions is included in go-imap itself. This
|
|
||||||
includes:
|
|
||||||
|
|
||||||
* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
|
|
||||||
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
|
|
||||||
* [ENABLE](https://tools.ietf.org/html/rfc5161)
|
|
||||||
* [IDLE](https://tools.ietf.org/html/rfc2177)
|
|
||||||
* [IMPORTANT](https://tools.ietf.org/html/rfc8457)
|
|
||||||
* [LITERAL+](https://tools.ietf.org/html/rfc7888)
|
|
||||||
* [MOVE](https://tools.ietf.org/html/rfc6851)
|
|
||||||
* [SASL-IR](https://tools.ietf.org/html/rfc4959)
|
|
||||||
* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
|
|
||||||
* [UNSELECT](https://tools.ietf.org/html/rfc3691)
|
|
||||||
|
|
||||||
Support for other extensions is provided via separate packages. See below.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
* [COMPRESS](https://github.com/emersion/go-imap-compress)
|
|
||||||
* [ID](https://github.com/ProtonMail/go-imap-id)
|
|
||||||
* [METADATA](https://github.com/emersion/go-imap-metadata)
|
|
||||||
* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
|
|
||||||
* [QUOTA](https://github.com/emersion/go-imap-quota)
|
|
||||||
* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
|
|
||||||
* [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-msgauth](https://github.com/emersion/go-msgauth) - handle DKIM, DMARC and Authentication-Results
|
|
||||||
* [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
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
@@ -1,689 +0,0 @@
|
|||||||
// Package client provides an IMAP client.
|
|
||||||
//
|
|
||||||
// It is not safe to use the same Client from multiple goroutines. In general,
|
|
||||||
// the IMAP protocol doesn't make it possible to send multiple independent
|
|
||||||
// IMAP commands on the same connection.
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/commands"
|
|
||||||
"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
|
|
||||||
serverName string
|
|
||||||
|
|
||||||
loggedOut chan struct{}
|
|
||||||
continues chan<- bool
|
|
||||||
upgrading bool
|
|
||||||
|
|
||||||
handlers []responses.Handler
|
|
||||||
handlersLocker sync.Mutex
|
|
||||||
|
|
||||||
// The current connection state.
|
|
||||||
state imap.ConnState
|
|
||||||
// The selected mailbox, if there is one.
|
|
||||||
mailbox *imap.MailboxStatus
|
|
||||||
// The cached server capabilities.
|
|
||||||
caps map[string]bool
|
|
||||||
// state, mailbox and caps may be accessed in different goroutines. Protect
|
|
||||||
// access.
|
|
||||||
locker sync.Mutex
|
|
||||||
|
|
||||||
// A channel to which unilateral updates from the server will be sent. An
|
|
||||||
// update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
|
|
||||||
// *ExpungeUpdate. Note that blocking this channel blocks the whole client,
|
|
||||||
// so it's recommended to use a separate goroutine and a buffered channel to
|
|
||||||
// prevent deadlocks.
|
|
||||||
Updates chan<- Update
|
|
||||||
|
|
||||||
// ErrorLog specifies an optional logger for errors accepting connections and
|
|
||||||
// unexpected behavior from handlers. By default, logging goes to os.Stderr
|
|
||||||
// via the log package's standard logger. The logger must be safe to use
|
|
||||||
// simultaneously from multiple goroutines.
|
|
||||||
ErrorLog imap.Logger
|
|
||||||
|
|
||||||
// Timeout specifies a maximum amount of time to wait on a command.
|
|
||||||
//
|
|
||||||
// A Timeout of zero means no timeout. This is the default.
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) registerHandler(h responses.Handler) {
|
|
||||||
if h == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.handlersLocker.Lock()
|
|
||||||
c.handlers = append(c.handlers, h)
|
|
||||||
c.handlersLocker.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handle(resp imap.Resp) error {
|
|
||||||
c.handlersLocker.Lock()
|
|
||||||
for i := len(c.handlers) - 1; i >= 0; i-- {
|
|
||||||
if err := c.handlers[i].Handle(resp); err != responses.ErrUnhandled {
|
|
||||||
if err == errUnregisterHandler {
|
|
||||||
c.handlers = append(c.handlers[:i], c.handlers[i+1:]...)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
c.handlersLocker.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.handlersLocker.Unlock()
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) reader() {
|
|
||||||
defer close(c.loggedOut)
|
|
||||||
// Loop while connected.
|
|
||||||
for {
|
|
||||||
connected, err := c.readOnce()
|
|
||||||
if err != nil {
|
|
||||||
c.ErrorLog.Println("error reading response:", err)
|
|
||||||
}
|
|
||||||
if !connected {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) readOnce() (bool, error) {
|
|
||||||
if c.State() == imap.LogoutState {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := imap.ReadResp(c.conn.Reader)
|
|
||||||
if err == io.EOF || c.State() == imap.LogoutState {
|
|
||||||
return false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
if imap.IsParseError(err) {
|
|
||||||
return true, err
|
|
||||||
} else {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.handle(resp); err != nil && err != responses.ErrUnhandled {
|
|
||||||
c.ErrorLog.Println("cannot handle response ", resp, err)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) writeReply(reply []byte) error {
|
|
||||||
if _, err := c.conn.Writer.Write(reply); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Flush reply
|
|
||||||
return c.conn.Writer.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
type handleResult struct {
|
|
||||||
status *imap.StatusResp
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
|
||||||
cmd := cmdr.Command()
|
|
||||||
cmd.Tag = generateTag()
|
|
||||||
|
|
||||||
var replies <-chan []byte
|
|
||||||
if replier, ok := h.(responses.Replier); ok {
|
|
||||||
replies = replier.Replies()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Timeout > 0 {
|
|
||||||
err := c.conn.SetDeadline(time.Now().Add(c.Timeout))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// It's possible the client had a timeout set from a previous command, but no
|
|
||||||
// longer does. Ensure we respect that. The zero time means no deadline.
|
|
||||||
if err := c.conn.SetDeadline(time.Time{}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are upgrading.
|
|
||||||
upgrading := c.upgrading
|
|
||||||
|
|
||||||
// Add handler before sending command, to be sure to get the response in time
|
|
||||||
// (in tests, the response is sent right after our command is received, so
|
|
||||||
// sometimes the response was received before the setup of this handler)
|
|
||||||
doneHandle := make(chan handleResult, 1)
|
|
||||||
unregister := make(chan struct{})
|
|
||||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
|
||||||
select {
|
|
||||||
case <-unregister:
|
|
||||||
// If an error occured while sending the command, abort
|
|
||||||
return errUnregisterHandler
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := resp.(*imap.StatusResp); ok && s.Tag == cmd.Tag {
|
|
||||||
// This is the command's status response, we're done
|
|
||||||
doneHandle <- handleResult{s, nil}
|
|
||||||
// Special handling of connection upgrading.
|
|
||||||
if upgrading {
|
|
||||||
c.upgrading = false
|
|
||||||
// Wait for upgrade to finish.
|
|
||||||
c.conn.Wait()
|
|
||||||
}
|
|
||||||
// Cancel any pending literal write
|
|
||||||
select {
|
|
||||||
case c.continues <- false:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return errUnregisterHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
if h != nil {
|
|
||||||
// Pass the response to the response handler
|
|
||||||
if err := h.Handle(resp); err != nil && err != responses.ErrUnhandled {
|
|
||||||
// If the response handler returns an error, abort
|
|
||||||
doneHandle <- handleResult{nil, err}
|
|
||||||
return errUnregisterHandler
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Send the command to the server
|
|
||||||
if err := cmd.WriteTo(c.conn.Writer); err != nil {
|
|
||||||
// Error while sending the command
|
|
||||||
close(unregister)
|
|
||||||
|
|
||||||
if err, ok := err.(imap.LiteralLengthErr); ok {
|
|
||||||
// Expected > Actual
|
|
||||||
// The server is waiting for us to write
|
|
||||||
// more bytes, we don't have them. Run.
|
|
||||||
// Expected < Actual
|
|
||||||
// We are about to send a potentially truncated message, we don't
|
|
||||||
// want this (ths terminating CRLF is not sent at this point).
|
|
||||||
c.conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Flush writer if we are upgrading
|
|
||||||
if upgrading {
|
|
||||||
if err := c.conn.Writer.Flush(); err != nil {
|
|
||||||
// Error while sending the command
|
|
||||||
close(unregister)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case reply := <-replies:
|
|
||||||
// Response handler needs to send a reply (Used for AUTHENTICATE)
|
|
||||||
if err := c.writeReply(reply); err != nil {
|
|
||||||
close(unregister)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case <-c.loggedOut:
|
|
||||||
// If the connection is closed (such as from an I/O error), ensure we
|
|
||||||
// realize this and don't block waiting on a response that will never
|
|
||||||
// come. loggedOut is a channel that closes when the reader goroutine
|
|
||||||
// ends.
|
|
||||||
close(unregister)
|
|
||||||
return nil, errClosed
|
|
||||||
case result := <-doneHandle:
|
|
||||||
return result.status, result.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State returns the current connection state.
|
|
||||||
func (c *Client) State() imap.ConnState {
|
|
||||||
c.locker.Lock()
|
|
||||||
state := c.state
|
|
||||||
c.locker.Unlock()
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mailbox returns the selected mailbox. It returns nil if there isn't one.
|
|
||||||
func (c *Client) Mailbox() *imap.MailboxStatus {
|
|
||||||
// c.Mailbox fields are not supposed to change, so we can return the pointer.
|
|
||||||
c.locker.Lock()
|
|
||||||
mbox := c.mailbox
|
|
||||||
c.locker.Unlock()
|
|
||||||
return mbox
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetState sets this connection's internal state.
|
|
||||||
//
|
|
||||||
// This function should not be called directly, it must only be used by
|
|
||||||
// libraries implementing extensions of the IMAP protocol.
|
|
||||||
func (c *Client) SetState(state imap.ConnState, mailbox *imap.MailboxStatus) {
|
|
||||||
c.locker.Lock()
|
|
||||||
c.state = state
|
|
||||||
c.mailbox = mailbox
|
|
||||||
c.locker.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute executes a generic command. cmdr is a value that can be converted to
|
|
||||||
// a raw command and h is a response handler. The function returns when the
|
|
||||||
// command has completed or failed, in this case err is nil. A non-nil err value
|
|
||||||
// indicates a network error.
|
|
||||||
//
|
|
||||||
// This function should not be called directly, it must only be used by
|
|
||||||
// libraries implementing extensions of the IMAP protocol.
|
|
||||||
func (c *Client) Execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
|
||||||
return c.execute(cmdr, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleContinuationReqs() {
|
|
||||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
|
||||||
if _, ok := resp.(*imap.ContinuationReq); ok {
|
|
||||||
go func() {
|
|
||||||
c.continues <- true
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) gotStatusCaps(args []interface{}) {
|
|
||||||
c.locker.Lock()
|
|
||||||
|
|
||||||
c.caps = make(map[string]bool)
|
|
||||||
for _, cap := range args {
|
|
||||||
if cap, ok := cap.(string); ok {
|
|
||||||
c.caps[cap] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The server can send unilateral data. This function handles it.
|
|
||||||
func (c *Client) handleUnilateral() {
|
|
||||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
|
||||||
switch resp := resp.(type) {
|
|
||||||
case *imap.StatusResp:
|
|
||||||
if resp.Tag != "*" {
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
switch resp.Type {
|
|
||||||
case imap.StatusRespOk, imap.StatusRespNo, imap.StatusRespBad:
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &StatusUpdate{resp}
|
|
||||||
}
|
|
||||||
case imap.StatusRespBye:
|
|
||||||
c.locker.Lock()
|
|
||||||
c.state = imap.LogoutState
|
|
||||||
c.mailbox = nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
c.conn.Close()
|
|
||||||
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &StatusUpdate{resp}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
case *imap.DataResp:
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok {
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
switch name {
|
|
||||||
case "CAPABILITY":
|
|
||||||
c.gotStatusCaps(fields)
|
|
||||||
case "EXISTS":
|
|
||||||
if c.Mailbox() == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if messages, err := imap.ParseNumber(fields[0]); err == nil {
|
|
||||||
c.locker.Lock()
|
|
||||||
c.mailbox.Messages = messages
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
c.mailbox.ItemsLocker.Lock()
|
|
||||||
c.mailbox.Items[imap.StatusMessages] = nil
|
|
||||||
c.mailbox.ItemsLocker.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
|
||||||
}
|
|
||||||
case "RECENT":
|
|
||||||
if c.Mailbox() == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if recent, err := imap.ParseNumber(fields[0]); err == nil {
|
|
||||||
c.locker.Lock()
|
|
||||||
c.mailbox.Recent = recent
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
c.mailbox.ItemsLocker.Lock()
|
|
||||||
c.mailbox.Items[imap.StatusRecent] = nil
|
|
||||||
c.mailbox.ItemsLocker.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
|
||||||
}
|
|
||||||
case "EXPUNGE":
|
|
||||||
seqNum, _ := imap.ParseNumber(fields[0])
|
|
||||||
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &ExpungeUpdate{seqNum}
|
|
||||||
}
|
|
||||||
case "FETCH":
|
|
||||||
seqNum, _ := imap.ParseNumber(fields[0])
|
|
||||||
fields, _ := fields[1].([]interface{})
|
|
||||||
|
|
||||||
msg := &imap.Message{SeqNum: seqNum}
|
|
||||||
if err := msg.Parse(fields); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Updates != nil {
|
|
||||||
c.Updates <- &MessageUpdate{msg}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return responses.ErrUnhandled
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleGreetAndStartReading() error {
|
|
||||||
var greetErr error
|
|
||||||
gotGreet := false
|
|
||||||
|
|
||||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
|
||||||
status, ok := resp.(*imap.StatusResp)
|
|
||||||
if !ok {
|
|
||||||
greetErr = fmt.Errorf("invalid greeting received from server: not a status response")
|
|
||||||
return errUnregisterHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
switch status.Type {
|
|
||||||
case imap.StatusRespPreauth:
|
|
||||||
c.state = imap.AuthenticatedState
|
|
||||||
case imap.StatusRespBye:
|
|
||||||
c.state = imap.LogoutState
|
|
||||||
case imap.StatusRespOk:
|
|
||||||
c.state = imap.NotAuthenticatedState
|
|
||||||
default:
|
|
||||||
c.state = imap.LogoutState
|
|
||||||
c.locker.Unlock()
|
|
||||||
greetErr = fmt.Errorf("invalid greeting received from server: %v", status.Type)
|
|
||||||
return errUnregisterHandler
|
|
||||||
}
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
if status.Code == imap.CodeCapability {
|
|
||||||
c.gotStatusCaps(status.Arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
gotGreet = true
|
|
||||||
return errUnregisterHandler
|
|
||||||
}))
|
|
||||||
|
|
||||||
// call `readOnce` until we get the greeting or an error
|
|
||||||
for !gotGreet {
|
|
||||||
connected, err := c.readOnce()
|
|
||||||
// Check for read errors
|
|
||||||
if err != nil {
|
|
||||||
// return read errors
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Check for invalid greet
|
|
||||||
if greetErr != nil {
|
|
||||||
// return read errors
|
|
||||||
return greetErr
|
|
||||||
}
|
|
||||||
// Check if connection was closed.
|
|
||||||
if !connected {
|
|
||||||
// connection closed.
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We got the greeting, now start the reader goroutine.
|
|
||||||
go c.reader()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
|
||||||
// tunnel.
|
|
||||||
//
|
|
||||||
// This function should not be called directly, it must only be used by
|
|
||||||
// libraries implementing extensions of the IMAP protocol.
|
|
||||||
func (c *Client) Upgrade(upgrader imap.ConnUpgrader) error {
|
|
||||||
return c.conn.Upgrade(upgrader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer returns the imap.Writer for this client's connection.
|
|
||||||
//
|
|
||||||
// This function should not be called directly, it must only be used by
|
|
||||||
// libraries implementing extensions of the IMAP protocol.
|
|
||||||
func (c *Client) Writer() *imap.Writer {
|
|
||||||
return c.conn.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTLS checks if this client's connection has TLS enabled.
|
|
||||||
func (c *Client) IsTLS() bool {
|
|
||||||
return c.isTLS
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggedOut returns a channel which is closed when the connection to the server
|
|
||||||
// is closed.
|
|
||||||
func (c *Client) LoggedOut() <-chan struct{} {
|
|
||||||
return c.loggedOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *Client) SetDebug(w io.Writer) {
|
|
||||||
// Need to send a command to unblock the reader goroutine.
|
|
||||||
cmd := new(commands.Noop)
|
|
||||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
|
||||||
// Flag connection as in upgrading
|
|
||||||
c.upgrading = true
|
|
||||||
if status, err := c.execute(cmd, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := status.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for reader to block.
|
|
||||||
c.conn.WaitReady()
|
|
||||||
|
|
||||||
c.conn.SetDebug(w)
|
|
||||||
return conn, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Println("SetDebug:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new client from an existing connection.
|
|
||||||
func New(conn net.Conn) (*Client, error) {
|
|
||||||
continues := make(chan bool)
|
|
||||||
w := imap.NewClientWriter(nil, continues)
|
|
||||||
r := imap.NewReader(nil)
|
|
||||||
|
|
||||||
c := &Client{
|
|
||||||
conn: imap.NewConn(conn, r, w),
|
|
||||||
loggedOut: make(chan struct{}),
|
|
||||||
continues: continues,
|
|
||||||
state: imap.ConnectingState,
|
|
||||||
ErrorLog: log.New(os.Stderr, "imap/client: ", log.LstdFlags),
|
|
||||||
}
|
|
||||||
|
|
||||||
c.handleContinuationReqs()
|
|
||||||
c.handleUnilateral()
|
|
||||||
if err := c.handleGreetAndStartReading(); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
plusOk, _ := c.Support("LITERAL+")
|
|
||||||
minusOk, _ := c.Support("LITERAL-")
|
|
||||||
// We don't use non-sync literal if it is bigger than 4096 bytes, so
|
|
||||||
// LITERAL- is fine too.
|
|
||||||
c.conn.AllowAsyncLiterals = plusOk || minusOk
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to an IMAP server using an unencrypted connection.
|
|
||||||
func Dial(addr string) (*Client, error) {
|
|
||||||
return DialWithDialer(new(net.Dialer), addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dialer interface {
|
|
||||||
// Dial connects to the given address.
|
|
||||||
Dial(network, addr string) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialWithDialer connects to an IMAP server using an unencrypted connection
|
|
||||||
// using dialer.Dial.
|
|
||||||
//
|
|
||||||
// Among other uses, this allows to apply a dial timeout.
|
|
||||||
func DialWithDialer(dialer Dialer, addr string) (*Client, error) {
|
|
||||||
conn, err := dialer.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't return to the caller until we try to receive a greeting. As such,
|
|
||||||
// there is no way to set the client's Timeout for that action. As a
|
|
||||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
|
||||||
// deadline.
|
|
||||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
|
||||||
err := conn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := New(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.serverName, _, _ = net.SplitHostPort(addr)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTLS connects to an IMAP server using an encrypted connection.
|
|
||||||
func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
|
|
||||||
return DialWithDialerTLS(new(net.Dialer), addr, tlsConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialWithDialerTLS connects to an IMAP server using an encrypted connection
|
|
||||||
// using dialer.Dial.
|
|
||||||
//
|
|
||||||
// Among other uses, this allows to apply a dial timeout.
|
|
||||||
func DialWithDialerTLS(dialer Dialer, addr string, tlsConfig *tls.Config) (*Client, error) {
|
|
||||||
conn, err := dialer.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName, _, _ := net.SplitHostPort(addr)
|
|
||||||
if tlsConfig == nil {
|
|
||||||
tlsConfig = &tls.Config{}
|
|
||||||
}
|
|
||||||
if tlsConfig.ServerName == "" {
|
|
||||||
tlsConfig = tlsConfig.Clone()
|
|
||||||
tlsConfig.ServerName = serverName
|
|
||||||
}
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
|
||||||
|
|
||||||
// We don't return to the caller until we try to receive a greeting. As such,
|
|
||||||
// there is no way to set the client's Timeout for that action. As a
|
|
||||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
|
||||||
// deadline.
|
|
||||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
|
||||||
err := tlsConn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := New(tlsConn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.isTLS = true
|
|
||||||
c.serverName = serverName
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
@@ -1,88 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/commands"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrAlreadyLoggedOut is returned if Logout is called when the client is
|
|
||||||
// already logged out.
|
|
||||||
var ErrAlreadyLoggedOut = errors.New("Already logged out")
|
|
||||||
|
|
||||||
// Capability requests a listing of capabilities that the server supports.
|
|
||||||
// Capabilities are often returned by the server with the greeting or with the
|
|
||||||
// STARTTLS and LOGIN responses, so usually explicitly requesting capabilities
|
|
||||||
// isn't needed.
|
|
||||||
//
|
|
||||||
// Most of the time, Support should be used instead.
|
|
||||||
func (c *Client) Capability() (map[string]bool, error) {
|
|
||||||
cmd := &commands.Capability{}
|
|
||||||
|
|
||||||
if status, err := c.execute(cmd, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := status.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
caps := c.caps
|
|
||||||
c.locker.Unlock()
|
|
||||||
return caps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support checks if cap is a capability supported by the server. If the server
|
|
||||||
// hasn't sent its capabilities yet, Support requests them.
|
|
||||||
func (c *Client) Support(cap string) (bool, error) {
|
|
||||||
c.locker.Lock()
|
|
||||||
ok := c.caps != nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
// If capabilities are not cached, request them
|
|
||||||
if !ok {
|
|
||||||
if _, err := c.Capability(); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
supported := c.caps[cap]
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
return supported, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noop always succeeds and does nothing.
|
|
||||||
//
|
|
||||||
// It can be used as a periodic poll for new messages or message status updates
|
|
||||||
// during a period of inactivity. It can also be used to reset any inactivity
|
|
||||||
// autologout timer on the server.
|
|
||||||
func (c *Client) Noop() error {
|
|
||||||
cmd := new(commands.Noop)
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout gracefully closes the connection.
|
|
||||||
func (c *Client) Logout() error {
|
|
||||||
if c.State() == imap.LogoutState {
|
|
||||||
return ErrAlreadyLoggedOut
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := new(commands.Logout)
|
|
||||||
|
|
||||||
if status, err := c.execute(cmd, nil); err == errClosed {
|
|
||||||
// Server closed connection, that's what we want anyway
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
} else if status != nil {
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
@@ -1,380 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/commands"
|
|
||||||
"github.com/emersion/go-imap/responses"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNotLoggedIn is returned if a function that requires the client to be
|
|
||||||
// logged in is called then the client isn't.
|
|
||||||
var ErrNotLoggedIn = errors.New("Not logged in")
|
|
||||||
|
|
||||||
func (c *Client) ensureAuthenticated() error {
|
|
||||||
state := c.State()
|
|
||||||
if state != imap.AuthenticatedState && state != imap.SelectedState {
|
|
||||||
return ErrNotLoggedIn
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select selects a mailbox so that messages in the mailbox can be accessed. Any
|
|
||||||
// currently selected mailbox is deselected before attempting the new selection.
|
|
||||||
// Even if the readOnly parameter is set to false, the server can decide to open
|
|
||||||
// the mailbox in read-only mode.
|
|
||||||
func (c *Client) Select(name string, readOnly bool) (*imap.MailboxStatus, error) {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Select{
|
|
||||||
Mailbox: name,
|
|
||||||
ReadOnly: readOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
mbox := &imap.MailboxStatus{Name: name, Items: make(map[imap.StatusItem]interface{})}
|
|
||||||
res := &responses.Select{
|
|
||||||
Mailbox: mbox,
|
|
||||||
}
|
|
||||||
c.locker.Lock()
|
|
||||||
c.mailbox = mbox
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
c.locker.Lock()
|
|
||||||
c.mailbox = nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := status.Err(); err != nil {
|
|
||||||
c.locker.Lock()
|
|
||||||
c.mailbox = nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
mbox.ReadOnly = (status.Code == imap.CodeReadOnly)
|
|
||||||
c.state = imap.SelectedState
|
|
||||||
c.locker.Unlock()
|
|
||||||
return mbox, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a mailbox with the given name.
|
|
||||||
func (c *Client) Create(name string) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Create{
|
|
||||||
Mailbox: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete permanently removes the mailbox with the given name.
|
|
||||||
func (c *Client) Delete(name string) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Delete{
|
|
||||||
Mailbox: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename changes the name of a mailbox.
|
|
||||||
func (c *Client) Rename(existingName, newName string) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Rename{
|
|
||||||
Existing: existingName,
|
|
||||||
New: newName,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe adds the specified mailbox name to the server's set of "active" or
|
|
||||||
// "subscribed" mailboxes.
|
|
||||||
func (c *Client) Subscribe(name string) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Subscribe{
|
|
||||||
Mailbox: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe removes the specified mailbox name from the server's set of
|
|
||||||
// "active" or "subscribed" mailboxes.
|
|
||||||
func (c *Client) Unsubscribe(name string) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Unsubscribe{
|
|
||||||
Mailbox: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a subset of names from the complete set of all names available
|
|
||||||
// to the client.
|
|
||||||
//
|
|
||||||
// An empty name argument is a special request to return the hierarchy delimiter
|
|
||||||
// and the root name of the name given in the reference. The character "*" is a
|
|
||||||
// wildcard, and matches zero or more characters at this position. The
|
|
||||||
// character "%" is similar to "*", but it does not match a hierarchy delimiter.
|
|
||||||
func (c *Client) List(ref, name string, ch chan *imap.MailboxInfo) error {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.List{
|
|
||||||
Reference: ref,
|
|
||||||
Mailbox: name,
|
|
||||||
}
|
|
||||||
res := &responses.List{Mailboxes: ch}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lsub returns a subset of names from the set of names that the user has
|
|
||||||
// declared as being "active" or "subscribed".
|
|
||||||
func (c *Client) Lsub(ref, name string, ch chan *imap.MailboxInfo) error {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.List{
|
|
||||||
Reference: ref,
|
|
||||||
Mailbox: name,
|
|
||||||
Subscribed: true,
|
|
||||||
}
|
|
||||||
res := &responses.List{
|
|
||||||
Mailboxes: ch,
|
|
||||||
Subscribed: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status requests the status of the indicated mailbox. It does not change the
|
|
||||||
// currently selected mailbox, nor does it affect the state of any messages in
|
|
||||||
// the queried mailbox.
|
|
||||||
//
|
|
||||||
// See RFC 3501 section 6.3.10 for a list of items that can be requested.
|
|
||||||
func (c *Client) Status(name string, items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Status{
|
|
||||||
Mailbox: name,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
res := &responses.Status{
|
|
||||||
Mailbox: new(imap.MailboxStatus),
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res.Mailbox, status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append appends the literal argument as a new message to the end of the
|
|
||||||
// specified destination mailbox. This argument SHOULD be in the format of an
|
|
||||||
// RFC 2822 message. flags and date are optional arguments and can be set to
|
|
||||||
// nil and the empty struct.
|
|
||||||
func (c *Client) Append(mbox string, flags []string, date time.Time, msg imap.Literal) error {
|
|
||||||
if err := c.ensureAuthenticated(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Append{
|
|
||||||
Mailbox: mbox,
|
|
||||||
Flags: flags,
|
|
||||||
Date: date,
|
|
||||||
Message: msg,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable requests the server to enable the named extensions. The extensions
|
|
||||||
// which were successfully enabled are returned.
|
|
||||||
//
|
|
||||||
// See RFC 5161 section 3.1.
|
|
||||||
func (c *Client) Enable(caps []string) ([]string, error) {
|
|
||||||
if ok, err := c.Support("ENABLE"); !ok || err != nil {
|
|
||||||
return nil, ErrExtensionUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
// ENABLE is invalid if a mailbox has been selected.
|
|
||||||
if c.State() != imap.AuthenticatedState {
|
|
||||||
return nil, ErrNotLoggedIn
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Enable{Caps: caps}
|
|
||||||
res := &responses.Enabled{}
|
|
||||||
|
|
||||||
if status, err := c.Execute(cmd, res); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return res.Caps, status.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) idle(stop <-chan struct{}) error {
|
|
||||||
cmd := &commands.Idle{}
|
|
||||||
|
|
||||||
res := &responses.Idle{
|
|
||||||
Stop: stop,
|
|
||||||
RepliesCh: make(chan []byte, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
if status, err := c.Execute(cmd, res); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdleOptions holds options for Client.Idle.
|
|
||||||
type IdleOptions struct {
|
|
||||||
// LogoutTimeout is used to avoid being logged out by the server when
|
|
||||||
// idling. Each LogoutTimeout, the IDLE command is restarted. If set to
|
|
||||||
// zero, a default is used. If negative, this behavior is disabled.
|
|
||||||
LogoutTimeout time.Duration
|
|
||||||
// Poll interval when the server doesn't support IDLE. If zero, a default
|
|
||||||
// is used. If negative, polling is always disabled.
|
|
||||||
PollInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Idle indicates to the server that the client is ready to receive unsolicited
|
|
||||||
// mailbox update messages. When the client wants to send commands again, it
|
|
||||||
// must first close stop.
|
|
||||||
//
|
|
||||||
// If the server doesn't support IDLE, go-imap falls back to polling.
|
|
||||||
func (c *Client) Idle(stop <-chan struct{}, opts *IdleOptions) error {
|
|
||||||
if ok, err := c.Support("IDLE"); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !ok {
|
|
||||||
return c.idleFallback(stop, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
logoutTimeout := 25 * time.Minute
|
|
||||||
if opts != nil {
|
|
||||||
if opts.LogoutTimeout > 0 {
|
|
||||||
logoutTimeout = opts.LogoutTimeout
|
|
||||||
} else if opts.LogoutTimeout < 0 {
|
|
||||||
return c.idle(stop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.NewTicker(logoutTimeout)
|
|
||||||
defer t.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
stopOrRestart := make(chan struct{})
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- c.idle(stopOrRestart)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
close(stopOrRestart)
|
|
||||||
if err := <-done; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-stop:
|
|
||||||
close(stopOrRestart)
|
|
||||||
return <-done
|
|
||||||
case err := <-done:
|
|
||||||
close(stopOrRestart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) idleFallback(stop <-chan struct{}, opts *IdleOptions) error {
|
|
||||||
pollInterval := time.Minute
|
|
||||||
if opts != nil {
|
|
||||||
if opts.PollInterval > 0 {
|
|
||||||
pollInterval = opts.PollInterval
|
|
||||||
} else if opts.PollInterval < 0 {
|
|
||||||
return ErrExtensionUnsupported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.NewTicker(pollInterval)
|
|
||||||
defer t.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
if err := c.Noop(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-stop:
|
|
||||||
return nil
|
|
||||||
case <-c.LoggedOut():
|
|
||||||
return errors.New("disconnected while idling")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
@@ -1,174 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrAlreadyLoggedIn is returned if Login or Authenticate is called when the
|
|
||||||
// client is already logged in.
|
|
||||||
ErrAlreadyLoggedIn = errors.New("Already logged in")
|
|
||||||
// ErrTLSAlreadyEnabled is returned if StartTLS is called when TLS is already
|
|
||||||
// enabled.
|
|
||||||
ErrTLSAlreadyEnabled = errors.New("TLS is already enabled")
|
|
||||||
// ErrLoginDisabled is returned if Login or Authenticate is called when the
|
|
||||||
// server has disabled authentication. Most of the time, calling enabling TLS
|
|
||||||
// solves the problem.
|
|
||||||
ErrLoginDisabled = errors.New("Login is disabled in current state")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SupportStartTLS checks if the server supports STARTTLS.
|
|
||||||
func (c *Client) SupportStartTLS() (bool, error) {
|
|
||||||
return c.Support("STARTTLS")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartTLS starts TLS negotiation.
|
|
||||||
func (c *Client) StartTLS(tlsConfig *tls.Config) error {
|
|
||||||
if c.isTLS {
|
|
||||||
return ErrTLSAlreadyEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig == nil {
|
|
||||||
tlsConfig = new(tls.Config)
|
|
||||||
}
|
|
||||||
if tlsConfig.ServerName == "" {
|
|
||||||
tlsConfig = tlsConfig.Clone()
|
|
||||||
tlsConfig.ServerName = c.serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := new(commands.StartTLS)
|
|
||||||
|
|
||||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
|
||||||
// Flag connection as in upgrading
|
|
||||||
c.upgrading = true
|
|
||||||
if status, err := c.execute(cmd, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := status.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for reader to block.
|
|
||||||
c.conn.WaitReady()
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capabilities change when TLS is enabled
|
|
||||||
c.locker.Lock()
|
|
||||||
c.caps = nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
return tlsConn, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.isTLS = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportAuth checks if the server supports a given authentication mechanism.
|
|
||||||
func (c *Client) SupportAuth(mech string) (bool, error) {
|
|
||||||
return c.Support("AUTH=" + mech)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate indicates a SASL authentication mechanism to the server. If the
|
|
||||||
// server supports the requested authentication mechanism, it performs an
|
|
||||||
// authentication protocol exchange to authenticate and identify the client.
|
|
||||||
func (c *Client) Authenticate(auth sasl.Client) error {
|
|
||||||
if c.State() != imap.NotAuthenticatedState {
|
|
||||||
return ErrAlreadyLoggedIn
|
|
||||||
}
|
|
||||||
|
|
||||||
mech, ir, err := auth.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Authenticate{
|
|
||||||
Mechanism: mech,
|
|
||||||
}
|
|
||||||
|
|
||||||
irOk, err := c.Support("SASL-IR")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if irOk {
|
|
||||||
cmd.InitialResponse = ir
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &responses.Authenticate{
|
|
||||||
Mechanism: auth,
|
|
||||||
InitialResponse: ir,
|
|
||||||
RepliesCh: make(chan []byte, 10),
|
|
||||||
}
|
|
||||||
if irOk {
|
|
||||||
res.InitialResponse = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = status.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
c.state = imap.AuthenticatedState
|
|
||||||
c.caps = nil // Capabilities change when user is logged in
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
if status.Code == "CAPABILITY" {
|
|
||||||
c.gotStatusCaps(status.Arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login identifies the client to the server and carries the plaintext password
|
|
||||||
// authenticating this user.
|
|
||||||
func (c *Client) Login(username, password string) error {
|
|
||||||
if state := c.State(); state == imap.AuthenticatedState || state == imap.SelectedState {
|
|
||||||
return ErrAlreadyLoggedIn
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
loginDisabled := c.caps != nil && c.caps["LOGINDISABLED"]
|
|
||||||
c.locker.Unlock()
|
|
||||||
if loginDisabled {
|
|
||||||
return ErrLoginDisabled
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Login{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = status.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
c.state = imap.AuthenticatedState
|
|
||||||
c.caps = nil // Capabilities change when user is logged in
|
|
||||||
c.locker.Unlock()
|
|
||||||
|
|
||||||
if status.Code == "CAPABILITY" {
|
|
||||||
c.gotStatusCaps(status.Arguments)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
@@ -1,367 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/commands"
|
|
||||||
"github.com/emersion/go-imap/responses"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNoMailboxSelected is returned if a command that requires a mailbox to be
|
|
||||||
// selected is called when there isn't.
|
|
||||||
ErrNoMailboxSelected = errors.New("No mailbox selected")
|
|
||||||
|
|
||||||
// ErrExtensionUnsupported is returned if a command uses a extension that
|
|
||||||
// is not supported by the server.
|
|
||||||
ErrExtensionUnsupported = errors.New("The required extension is not supported by the server")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
|
|
||||||
// refers to any implementation-dependent housekeeping associated with the
|
|
||||||
// mailbox that is not normally executed as part of each command.
|
|
||||||
func (c *Client) Check() error {
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := new(commands.Check)
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close permanently removes all messages that have the \Deleted flag set from
|
|
||||||
// the currently selected mailbox, and returns to the authenticated state from
|
|
||||||
// the selected state.
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := new(commands.Close)
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := status.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.locker.Lock()
|
|
||||||
c.state = imap.AuthenticatedState
|
|
||||||
c.mailbox = nil
|
|
||||||
c.locker.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate closes the tcp connection
|
|
||||||
func (c *Client) Terminate() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expunge permanently removes all messages that have the \Deleted flag set from
|
|
||||||
// the currently selected mailbox. If ch is not nil, sends sequence IDs of each
|
|
||||||
// deleted message to this channel.
|
|
||||||
func (c *Client) Expunge(ch chan uint32) error {
|
|
||||||
if ch != nil {
|
|
||||||
defer close(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := new(commands.Expunge)
|
|
||||||
|
|
||||||
var h responses.Handler
|
|
||||||
if ch != nil {
|
|
||||||
h = &responses.Expunge{SeqNums: ch}
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, h)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) executeSearch(uid bool, criteria *imap.SearchCriteria, charset string) (ids []uint32, status *imap.StatusResp, err error) {
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
err = ErrNoMailboxSelected
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd imap.Commander = &commands.Search{
|
|
||||||
Charset: charset,
|
|
||||||
Criteria: criteria,
|
|
||||||
}
|
|
||||||
if uid {
|
|
||||||
cmd = &commands.Uid{Cmd: cmd}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := new(responses.Search)
|
|
||||||
|
|
||||||
status, err = c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err, ids = status.Err(), res.Ids
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
|
|
||||||
ids, status, err := c.executeSearch(uid, criteria, "UTF-8")
|
|
||||||
if status != nil && status.Code == imap.CodeBadCharset {
|
|
||||||
// Some servers don't support UTF-8
|
|
||||||
ids, _, err = c.executeSearch(uid, criteria, "US-ASCII")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search searches the mailbox for messages that match the given searching
|
|
||||||
// criteria. Searching criteria consist of one or more search keys. The response
|
|
||||||
// contains a list of message sequence IDs corresponding to those messages that
|
|
||||||
// match the searching criteria. When multiple keys are specified, the result is
|
|
||||||
// the intersection (AND function) of all the messages that match those keys.
|
|
||||||
// Criteria must be UTF-8 encoded. See RFC 3501 section 6.4.4 for a list of
|
|
||||||
// searching criteria. When no criteria has been set, all messages in the mailbox
|
|
||||||
// will be searched using ALL criteria.
|
|
||||||
func (c *Client) Search(criteria *imap.SearchCriteria) (seqNums []uint32, err error) {
|
|
||||||
return c.search(false, criteria)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UidSearch is identical to Search, but UIDs are returned instead of message
|
|
||||||
// sequence numbers.
|
|
||||||
func (c *Client) UidSearch(criteria *imap.SearchCriteria) (uids []uint32, err error) {
|
|
||||||
return c.search(true, criteria)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd imap.Commander = &commands.Fetch{
|
|
||||||
SeqSet: seqset,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
if uid {
|
|
||||||
cmd = &commands.Uid{Cmd: cmd}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch retrieves data associated with a message in the mailbox. See RFC 3501
|
|
||||||
// section 6.4.5 for a list of items that can be requested.
|
|
||||||
func (c *Client) Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
|
||||||
return c.fetch(false, seqset, items, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UidFetch is identical to Fetch, but seqset is interpreted as containing
|
|
||||||
// unique identifiers instead of message sequence numbers.
|
|
||||||
func (c *Client) UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
|
||||||
return c.fetch(true, seqset, items, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) store(uid bool, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
|
||||||
if ch != nil {
|
|
||||||
defer close(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this could break extensions (this only works when item is FLAGS)
|
|
||||||
if fields, ok := value.([]interface{}); ok {
|
|
||||||
for i, field := range fields {
|
|
||||||
if s, ok := field.(string); ok {
|
|
||||||
fields[i] = imap.RawString(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ch is nil, the updated values are data which will be lost, so don't
|
|
||||||
// retrieve it.
|
|
||||||
if ch == nil {
|
|
||||||
op, _, err := imap.ParseFlagsOp(item)
|
|
||||||
if err == nil {
|
|
||||||
item = imap.FormatFlagsOp(op, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd imap.Commander = &commands.Store{
|
|
||||||
SeqSet: seqset,
|
|
||||||
Item: item,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
if uid {
|
|
||||||
cmd = &commands.Uid{Cmd: cmd}
|
|
||||||
}
|
|
||||||
|
|
||||||
var h responses.Handler
|
|
||||||
if ch != nil {
|
|
||||||
h = &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, h)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store alters data associated with a message in the mailbox. If ch is not nil,
|
|
||||||
// the updated value of the data will be sent to this channel. See RFC 3501
|
|
||||||
// section 6.4.6 for a list of items that can be updated.
|
|
||||||
func (c *Client) Store(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
|
||||||
return c.store(false, seqset, item, value, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UidStore is identical to Store, but seqset is interpreted as containing
|
|
||||||
// unique identifiers instead of message sequence numbers.
|
|
||||||
func (c *Client) UidStore(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
|
||||||
return c.store(true, seqset, item, value, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) copy(uid bool, seqset *imap.SeqSet, dest string) error {
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd imap.Commander = &commands.Copy{
|
|
||||||
SeqSet: seqset,
|
|
||||||
Mailbox: dest,
|
|
||||||
}
|
|
||||||
if uid {
|
|
||||||
cmd = &commands.Uid{Cmd: cmd}
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := c.execute(cmd, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copies the specified message(s) to the end of the specified destination
|
|
||||||
// mailbox.
|
|
||||||
func (c *Client) Copy(seqset *imap.SeqSet, dest string) error {
|
|
||||||
return c.copy(false, seqset, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UidCopy is identical to Copy, but seqset is interpreted as containing unique
|
|
||||||
// identifiers instead of message sequence numbers.
|
|
||||||
func (c *Client) UidCopy(seqset *imap.SeqSet, dest string) error {
|
|
||||||
return c.copy(true, seqset, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) move(uid bool, seqset *imap.SeqSet, dest string) error {
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := c.Support("MOVE"); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !ok {
|
|
||||||
return c.moveFallback(uid, seqset, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd imap.Commander = &commands.Move{
|
|
||||||
SeqSet: seqset,
|
|
||||||
Mailbox: dest,
|
|
||||||
}
|
|
||||||
if uid {
|
|
||||||
cmd = &commands.Uid{Cmd: cmd}
|
|
||||||
}
|
|
||||||
|
|
||||||
if status, err := c.Execute(cmd, nil); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return status.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// moveFallback uses COPY, STORE and EXPUNGE for servers which don't support
|
|
||||||
// MOVE.
|
|
||||||
func (c *Client) moveFallback(uid bool, seqset *imap.SeqSet, dest string) error {
|
|
||||||
item := imap.FormatFlagsOp(imap.AddFlags, true)
|
|
||||||
flags := []interface{}{imap.DeletedFlag}
|
|
||||||
if uid {
|
|
||||||
if err := c.UidCopy(seqset, dest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.UidStore(seqset, item, flags, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := c.Copy(seqset, dest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Store(seqset, item, flags, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Expunge(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move moves the specified message(s) to the end of the specified destination
|
|
||||||
// mailbox.
|
|
||||||
//
|
|
||||||
// If the server doesn't support the MOVE extension defined in RFC 6851,
|
|
||||||
// go-imap will fallback to copy, store and expunge.
|
|
||||||
func (c *Client) Move(seqset *imap.SeqSet, dest string) error {
|
|
||||||
return c.move(false, seqset, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UidMove is identical to Move, but seqset is interpreted as containing unique
|
|
||||||
// identifiers instead of message sequence numbers.
|
|
||||||
func (c *Client) UidMove(seqset *imap.SeqSet, dest string) error {
|
|
||||||
return c.move(true, seqset, dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unselect frees server's resources associated with the selected mailbox and
|
|
||||||
// returns the server to the authenticated state. This command performs the same
|
|
||||||
// actions as Close, except that no messages are permanently removed from the
|
|
||||||
// currently selected mailbox.
|
|
||||||
//
|
|
||||||
// If client does not support the UNSELECT extension, ErrExtensionUnsupported
|
|
||||||
// is returned.
|
|
||||||
func (c *Client) Unselect() error {
|
|
||||||
if ok, err := c.Support("UNSELECT"); !ok || err != nil {
|
|
||||||
return ErrExtensionUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.State() != imap.SelectedState {
|
|
||||||
return ErrNoMailboxSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &commands.Unselect{}
|
|
||||||
if status, err := c.Execute(cmd, nil); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := status.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetState(imap.AuthenticatedState, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
@@ -1,24 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
)
|
|
||||||
|
|
||||||
func randomString(n int) (string, error) {
|
|
||||||
b := make([]byte, n)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTag() string {
|
|
||||||
tag, err := randomString(4)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
@@ -1,57 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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{}{RawString(tag), RawString(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
|
|
||||||
}
|
|
||||||
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
@@ -1,93 +0,0 @@
|
|||||||
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.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
args = append(args, imap.FormatMailboxName(mailbox))
|
|
||||||
|
|
||||||
if cmd.Flags != nil {
|
|
||||||
flags := make([]interface{}, len(cmd.Flags))
|
|
||||||
for i, flag := range cmd.Flags {
|
|
||||||
flags[i] = imap.RawString(flag)
|
|
||||||
}
|
|
||||||
args = append(args, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cmd.Date.IsZero() {
|
|
||||||
args = append(args, cmd.Date)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, cmd.Message)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "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, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err = utf7.Encoding.NewDecoder().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 {
|
|
||||||
if date, ok := fields[0].(string); !ok {
|
|
||||||
return errors.New("Date must be a string")
|
|
||||||
} else if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
@@ -1,124 +0,0 @@
|
|||||||
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
|
|
||||||
InitialResponse []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Authenticate) Command() *imap.Command {
|
|
||||||
args := []interface{}{imap.RawString(cmd.Mechanism)}
|
|
||||||
if cmd.InitialResponse != nil {
|
|
||||||
var encodedResponse string
|
|
||||||
if len(cmd.InitialResponse) == 0 {
|
|
||||||
// Empty initial response should be encoded as "=", not empty
|
|
||||||
// string.
|
|
||||||
encodedResponse = "="
|
|
||||||
} else {
|
|
||||||
encodedResponse = base64.StdEncoding.EncodeToString(cmd.InitialResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, imap.RawString(encodedResponse))
|
|
||||||
}
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "AUTHENTICATE",
|
|
||||||
Arguments: args,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if len(fields) != 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedResponse, ok := fields[1].(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Initial response must be a string")
|
|
||||||
}
|
|
||||||
if encodedResponse == "=" {
|
|
||||||
cmd.InitialResponse = []byte{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
cmd.InitialResponse, err = base64.StdEncoding.DecodeString(encodedResponse)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
response := cmd.InitialResponse
|
|
||||||
for {
|
|
||||||
challenge, done, err := sasl.Next(response)
|
|
||||||
if err != nil || done {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(challenge)
|
|
||||||
cont := &imap.ContinuationReq{Info: encoded}
|
|
||||||
if err := conn.WriteResp(cont); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !scanner.Scan() {
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return errors.New("unexpected EOF")
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded = scanner.Text()
|
|
||||||
if encoded != "" {
|
|
||||||
if encoded == "*" {
|
|
||||||
return &imap.ErrStatusResp{Resp: &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespBad,
|
|
||||||
Info: "negotiation cancelled",
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
response, err = base64.StdEncoding.DecodeString(encoded)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "CAPABILITY",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Capability) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "CHECK",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Check) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "CLOSE",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Close) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
@@ -1,2 +0,0 @@
|
|||||||
// Package commands implements IMAP commands defined in RFC 3501.
|
|
||||||
package commands
|
|
||||||
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
@@ -1,47 +0,0 @@
|
|||||||
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.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "COPY",
|
|
||||||
Arguments: []interface{}{cmd.SeqSet, imap.FormatMailboxName(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.ParseSeqSet(seqSet); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
cmd.SeqSet = seqSet
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := utf7.Encoding.NewDecoder().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
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
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.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "CREATE",
|
|
||||||
Arguments: []interface{}{mailbox},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Create) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := utf7.Encoding.NewDecoder().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
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
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.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "DELETE",
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Delete) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An ENABLE command, defined in RFC 5161 section 3.1.
|
|
||||||
type Enable struct {
|
|
||||||
Caps []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Enable) Command() *imap.Command {
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "ENABLE",
|
|
||||||
Arguments: imap.FormatStringList(cmd.Caps),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Enable) Parse(fields []interface{}) error {
|
|
||||||
var err error
|
|
||||||
cmd.Caps, err = imap.ParseStringList(fields)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
@@ -1,16 +0,0 @@
|
|||||||
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: "EXPUNGE"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Expunge) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
@@ -1,55 +0,0 @@
|
|||||||
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 []imap.FetchItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Fetch) Command() *imap.Command {
|
|
||||||
items := make([]interface{}, len(cmd.Items))
|
|
||||||
for i, item := range cmd.Items {
|
|
||||||
items[i] = imap.RawString(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "FETCH",
|
|
||||||
Arguments: []interface{}{cmd.SeqSet, items},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Fetch) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if seqset, ok := fields[0].(string); !ok {
|
|
||||||
return errors.New("Sequence set must be an atom")
|
|
||||||
} else if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch items := fields[1].(type) {
|
|
||||||
case string: // A macro or a single item
|
|
||||||
cmd.Items = imap.FetchItem(strings.ToUpper(items)).Expand()
|
|
||||||
case []interface{}: // A list of items
|
|
||||||
cmd.Items = make([]imap.FetchItem, 0, len(items))
|
|
||||||
for _, v := range items {
|
|
||||||
itemStr, _ := v.(string)
|
|
||||||
item := imap.FetchItem(strings.ToUpper(itemStr))
|
|
||||||
cmd.Items = append(cmd.Items, item.Expand()...)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("Items must be either a string or a list")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An IDLE command.
|
|
||||||
// Se RFC 2177 section 3.
|
|
||||||
type Idle struct{}
|
|
||||||
|
|
||||||
func (cmd *Idle) Command() *imap.Command {
|
|
||||||
return &imap.Command{Name: "IDLE"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Idle) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
@@ -1,60 +0,0 @@
|
|||||||
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 := "LIST"
|
|
||||||
if cmd.Subscribed {
|
|
||||||
name = "LSUB"
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := utf7.Encoding.NewEncoder()
|
|
||||||
ref, _ := enc.String(cmd.Reference)
|
|
||||||
mailbox, _ := enc.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")
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := utf7.Encoding.NewDecoder()
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := dec.String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
// TODO: canonical mailbox path
|
|
||||||
cmd.Reference = imap.CanonicalMailboxName(mailbox)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := dec.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
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
@@ -1,36 +0,0 @@
|
|||||||
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: "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 err error
|
|
||||||
if cmd.Username, err = imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cmd.Password, err = imap.ParseString(fields[1]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "LOGOUT",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Logout) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
@@ -1,48 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/utf7"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A MOVE command.
|
|
||||||
// See RFC 6851 section 3.1.
|
|
||||||
type Move struct {
|
|
||||||
SeqSet *imap.SeqSet
|
|
||||||
Mailbox string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Move) Command() *imap.Command {
|
|
||||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "MOVE",
|
|
||||||
Arguments: []interface{}{cmd.SeqSet, mailbox},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Move) Parse(fields []interface{}) (err error) {
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
seqset, ok := fields[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Invalid sequence set")
|
|
||||||
}
|
|
||||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mailbox, ok := fields[1].(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Mailbox name must be a string")
|
|
||||||
}
|
|
||||||
if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "NOOP",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Noop) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
@@ -1,51 +0,0 @@
|
|||||||
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 {
|
|
||||||
enc := utf7.Encoding.NewEncoder()
|
|
||||||
existingName, _ := enc.String(cmd.Existing)
|
|
||||||
newName, _ := enc.String(cmd.New)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "RENAME",
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(existingName), imap.FormatMailboxName(newName)},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Rename) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := utf7.Encoding.NewDecoder()
|
|
||||||
|
|
||||||
if existingName, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if existingName, err := dec.String(existingName); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
cmd.Existing = imap.CanonicalMailboxName(existingName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newName, err := imap.ParseString(fields[1]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if newName, err := dec.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
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
@@ -1,57 +0,0 @@
|
|||||||
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, imap.RawString("CHARSET"), imap.RawString(cmd.Charset))
|
|
||||||
}
|
|
||||||
args = append(args, cmd.Criteria.Format()...)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "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
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
@@ -1,45 +0,0 @@
|
|||||||
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 := "SELECT"
|
|
||||||
if cmd.ReadOnly {
|
|
||||||
name = "EXAMINE"
|
|
||||||
}
|
|
||||||
|
|
||||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: name,
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Select) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := utf7.Encoding.NewDecoder().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
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
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: "STARTTLS",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *StartTLS) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
@@ -1,58 +0,0 @@
|
|||||||
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 []imap.StatusItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Status) Command() *imap.Command {
|
|
||||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
items := make([]interface{}, len(cmd.Items))
|
|
||||||
for i, item := range cmd.Items {
|
|
||||||
items[i] = imap.RawString(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "STATUS",
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox), items},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Status) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
|
||||||
}
|
|
||||||
|
|
||||||
items, ok := fields[1].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return errors.New("STATUS command parameter is not a list")
|
|
||||||
}
|
|
||||||
cmd.Items = make([]imap.StatusItem, len(items))
|
|
||||||
for i, f := range items {
|
|
||||||
if s, ok := f.(string); !ok {
|
|
||||||
return errors.New("Got a non-string field in a STATUS command parameter")
|
|
||||||
} else {
|
|
||||||
cmd.Items[i] = imap.StatusItem(strings.ToUpper(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
@@ -1,50 +0,0 @@
|
|||||||
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 imap.StoreItem
|
|
||||||
Value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Store) Command() *imap.Command {
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "STORE",
|
|
||||||
Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Item), cmd.Value},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Store) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 3 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
seqset, ok := fields[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("Invalid sequence set")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if item, ok := fields[1].(string); !ok {
|
|
||||||
return errors.New("Item name must be a string")
|
|
||||||
} else {
|
|
||||||
cmd.Item = imap.StoreItem(strings.ToUpper(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields[2:]) == 1 {
|
|
||||||
cmd.Value = fields[2]
|
|
||||||
} else {
|
|
||||||
cmd.Value = fields[2:]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
@@ -1,63 +0,0 @@
|
|||||||
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.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "SUBSCRIBE",
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Subscribe) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 0 {
|
|
||||||
return errors.New("No enough arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An UNSUBSCRIBE command.
|
|
||||||
// See RFC 3501 section 6.3.7
|
|
||||||
type Unsubscribe struct {
|
|
||||||
Mailbox string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Unsubscribe) Command() *imap.Command {
|
|
||||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "UNSUBSCRIBE",
|
|
||||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Unsubscribe) Parse(fields []interface{}) error {
|
|
||||||
if len(fields) < 0 {
|
|
||||||
return errors.New("No enogh arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
@@ -1,44 +0,0 @@
|
|||||||
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{}{imap.RawString(inner.Name)}
|
|
||||||
args = append(args, inner.Arguments...)
|
|
||||||
|
|
||||||
return &imap.Command{
|
|
||||||
Name: "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
|
|
||||||
}
|
|
||||||
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An UNSELECT command.
|
|
||||||
// See RFC 3691 section 2.
|
|
||||||
type Unselect struct{}
|
|
||||||
|
|
||||||
func (cmd *Unselect) Command() *imap.Command {
|
|
||||||
return &imap.Command{Name: "UNSELECT"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Unselect) Parse(fields []interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
@@ -1,284 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 Waiter struct {
|
|
||||||
start sync.WaitGroup
|
|
||||||
end sync.WaitGroup
|
|
||||||
finished bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWaiter() *Waiter {
|
|
||||||
w := &Waiter{finished: false}
|
|
||||||
w.start.Add(1)
|
|
||||||
w.end.Add(1)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waiter) Wait() {
|
|
||||||
if !w.finished {
|
|
||||||
// Signal that we are ready for upgrade to continue.
|
|
||||||
w.start.Done()
|
|
||||||
// Wait for upgrade to finish.
|
|
||||||
w.end.Wait()
|
|
||||||
w.finished = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waiter) WaitReady() {
|
|
||||||
if !w.finished {
|
|
||||||
// Wait for reader/writer goroutine to be ready for upgrade.
|
|
||||||
w.start.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waiter) Close() {
|
|
||||||
if !w.finished {
|
|
||||||
// Upgrade is finished, close chanel to release reader/writer
|
|
||||||
w.end.Done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type LockedWriter struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLockedWriter - goroutine safe writer.
|
|
||||||
func NewLockedWriter(w io.Writer) io.Writer {
|
|
||||||
return &LockedWriter{writer: w}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *LockedWriter) Write(b []byte) (int, error) {
|
|
||||||
w.lock.Lock()
|
|
||||||
defer w.lock.Unlock()
|
|
||||||
return w.writer.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Underlying connection state information.
|
|
||||||
type ConnInfo struct {
|
|
||||||
RemoteAddr net.Addr
|
|
||||||
LocalAddr net.Addr
|
|
||||||
|
|
||||||
// nil if connection is not using TLS.
|
|
||||||
TLS *tls.ConnectionState
|
|
||||||
}
|
|
||||||
|
|
||||||
// An IMAP connection.
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
*Reader
|
|
||||||
*Writer
|
|
||||||
|
|
||||||
br *bufio.Reader
|
|
||||||
bw *bufio.Writer
|
|
||||||
|
|
||||||
waiter *Waiter
|
|
||||||
|
|
||||||
// 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) createWaiter() *Waiter {
|
|
||||||
// create new waiter each time.
|
|
||||||
w := NewWaiter()
|
|
||||||
c.waiter = w
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
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 local and remote are the same, then we need a LockedWriter.
|
|
||||||
if localDebug == remoteDebug {
|
|
||||||
localDebug = NewLockedWriter(localDebug)
|
|
||||||
remoteDebug = localDebug
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Info() *ConnInfo {
|
|
||||||
info := &ConnInfo{
|
|
||||||
RemoteAddr: c.RemoteAddr(),
|
|
||||||
LocalAddr: c.LocalAddr(),
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn, ok := c.Conn.(*tls.Conn)
|
|
||||||
if ok {
|
|
||||||
state := tlsConn.ConnectionState()
|
|
||||||
info.TLS = &state
|
|
||||||
}
|
|
||||||
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return c.Writer.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
|
||||||
// tunnel.
|
|
||||||
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
|
||||||
// Block reads and writes during the upgrading process
|
|
||||||
w := c.createWaiter()
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
upgraded, err := upgrader(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Conn = upgraded
|
|
||||||
c.init()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by reader/writer goroutines to wait for Upgrade to finish
|
|
||||||
func (c *Conn) Wait() {
|
|
||||||
c.waiter.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by Upgrader to wait for reader/writer goroutines to be ready for
|
|
||||||
// upgrade.
|
|
||||||
func (c *Conn) WaitReady() {
|
|
||||||
c.waiter.WaitReady()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
@@ -1,71 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"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 -0700",
|
|
||||||
"_2 Jan 2006 15:04 MST",
|
|
||||||
"_2 Jan 06 15:04:05 -0700",
|
|
||||||
"_2 Jan 06 15:04:05 MST",
|
|
||||||
"_2 Jan 06 15:04 -0700",
|
|
||||||
"_2 Jan 06 15:04 MST",
|
|
||||||
"Mon, _2 Jan 2006 15:04:05 -0700",
|
|
||||||
"Mon, _2 Jan 2006 15:04:05 MST",
|
|
||||||
"Mon, _2 Jan 2006 15:04 -0700",
|
|
||||||
"Mon, _2 Jan 2006 15:04 MST",
|
|
||||||
"Mon, _2 Jan 06 15:04:05 -0700",
|
|
||||||
"Mon, _2 Jan 06 15:04:05 MST",
|
|
||||||
"Mon, _2 Jan 06 15:04 -0700",
|
|
||||||
"Mon, _2 Jan 06 15:04 MST",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this is a blunt way to strip any trailing CFWS (comment). A sharper
|
|
||||||
// one would strip multiple CFWS, and only if really valid according to
|
|
||||||
// RFC5322.
|
|
||||||
var commentRE = regexp.MustCompile(`[ \t]+\(.*\)$`)
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
maybeDate = commentRE.ReplaceAllString(maybeDate, "")
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
@@ -1,108 +0,0 @@
|
|||||||
// Package imap implements IMAP4rev1 (RFC 3501).
|
|
||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A StatusItem is a mailbox status data item that can be retrieved with a
|
|
||||||
// STATUS command. See RFC 3501 section 6.3.10.
|
|
||||||
type StatusItem string
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusMessages StatusItem = "MESSAGES"
|
|
||||||
StatusRecent StatusItem = "RECENT"
|
|
||||||
StatusUidNext StatusItem = "UIDNEXT"
|
|
||||||
StatusUidValidity StatusItem = "UIDVALIDITY"
|
|
||||||
StatusUnseen StatusItem = "UNSEEN"
|
|
||||||
|
|
||||||
StatusAppendLimit StatusItem = "APPENDLIMIT"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A FetchItem is a message data item that can be fetched.
|
|
||||||
type FetchItem string
|
|
||||||
|
|
||||||
// List of items that can be fetched.
|
|
||||||
const (
|
|
||||||
// Macros
|
|
||||||
FetchAll FetchItem = "ALL"
|
|
||||||
FetchFast FetchItem = "FAST"
|
|
||||||
FetchFull FetchItem = "FULL"
|
|
||||||
|
|
||||||
// Items
|
|
||||||
FetchBody FetchItem = "BODY"
|
|
||||||
FetchBodyStructure FetchItem = "BODYSTRUCTURE"
|
|
||||||
FetchEnvelope FetchItem = "ENVELOPE"
|
|
||||||
FetchFlags FetchItem = "FLAGS"
|
|
||||||
FetchInternalDate FetchItem = "INTERNALDATE"
|
|
||||||
FetchRFC822 FetchItem = "RFC822"
|
|
||||||
FetchRFC822Header FetchItem = "RFC822.HEADER"
|
|
||||||
FetchRFC822Size FetchItem = "RFC822.SIZE"
|
|
||||||
FetchRFC822Text FetchItem = "RFC822.TEXT"
|
|
||||||
FetchUid FetchItem = "UID"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Expand expands the item if it's a macro.
|
|
||||||
func (item FetchItem) Expand() []FetchItem {
|
|
||||||
switch item {
|
|
||||||
case FetchAll:
|
|
||||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope}
|
|
||||||
case FetchFast:
|
|
||||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size}
|
|
||||||
case FetchFull:
|
|
||||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope, FetchBody}
|
|
||||||
default:
|
|
||||||
return []FetchItem{item}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
// A StoreItem is a message data item that can be updated.
|
|
||||||
type StoreItem string
|
|
||||||
|
|
||||||
// FormatFlagsOp returns the StoreItem that executes the flags operation op.
|
|
||||||
func FormatFlagsOp(op FlagsOp, silent bool) StoreItem {
|
|
||||||
s := string(op)
|
|
||||||
if silent {
|
|
||||||
s += silentOp
|
|
||||||
}
|
|
||||||
return StoreItem(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFlagsOp parses a flags operation from StoreItem.
|
|
||||||
func ParseFlagsOp(item StoreItem) (op FlagsOp, silent bool, err error) {
|
|
||||||
itemStr := string(item)
|
|
||||||
silent = strings.HasSuffix(itemStr, silentOp)
|
|
||||||
if silent {
|
|
||||||
itemStr = strings.TrimSuffix(itemStr, silentOp)
|
|
||||||
}
|
|
||||||
op = FlagsOp(itemStr)
|
|
||||||
|
|
||||||
if op != SetFlags && op != AddFlags && op != RemoveFlags {
|
|
||||||
err = errors.New("Unsupported STORE operation")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
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
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
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{})
|
|
||||||
}
|
|
||||||
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
@@ -1,314 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap/utf7"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The primary mailbox, as defined in RFC 3501 section 5.1.
|
|
||||||
const InboxName = "INBOX"
|
|
||||||
|
|
||||||
// CanonicalMailboxName 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mailbox attributes defined in RFC 6154 section 2 (SPECIAL-USE extension).
|
|
||||||
const (
|
|
||||||
// This mailbox presents all messages in the user's message store.
|
|
||||||
AllAttr = "\\All"
|
|
||||||
// This mailbox is used to archive messages.
|
|
||||||
ArchiveAttr = "\\Archive"
|
|
||||||
// This mailbox is used to hold draft messages -- typically, messages that are
|
|
||||||
// being composed but have not yet been sent.
|
|
||||||
DraftsAttr = "\\Drafts"
|
|
||||||
// This mailbox presents all messages marked in some way as "important".
|
|
||||||
FlaggedAttr = "\\Flagged"
|
|
||||||
// This mailbox is where messages deemed to be junk mail are held.
|
|
||||||
JunkAttr = "\\Junk"
|
|
||||||
// This mailbox is used to hold copies of messages that have been sent.
|
|
||||||
SentAttr = "\\Sent"
|
|
||||||
// This mailbox is used to hold messages that have been deleted or marked for
|
|
||||||
// deletion.
|
|
||||||
TrashAttr = "\\Trash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mailbox attributes defined in RFC 3348 (CHILDREN extension)
|
|
||||||
const (
|
|
||||||
// The presence of this attribute indicates that the mailbox has child
|
|
||||||
// mailboxes.
|
|
||||||
HasChildrenAttr = "\\HasChildren"
|
|
||||||
// The presence of this attribute indicates that the mailbox has no child
|
|
||||||
// mailboxes.
|
|
||||||
HasNoChildrenAttr = "\\HasNoChildren"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This mailbox attribute is a signal that the mailbox contains messages that
|
|
||||||
// are likely important to the user. This attribute is defined in RFC 8457
|
|
||||||
// section 3.
|
|
||||||
const ImportantAttr = "\\Important"
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if info.Attributes, err = ParseStringList(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
if info.Delimiter, ok = fields[1].(string); !ok {
|
|
||||||
// The delimiter may be specified as NIL, which gets converted to a nil interface.
|
|
||||||
if fields[1] != nil {
|
|
||||||
return errors.New("Mailbox delimiter must be a string")
|
|
||||||
}
|
|
||||||
info.Delimiter = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if name, err := ParseString(fields[2]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
info.Name = CanonicalMailboxName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format mailbox info to fields.
|
|
||||||
func (info *MailboxInfo) Format() []interface{} {
|
|
||||||
name, _ := utf7.Encoding.NewEncoder().String(info.Name)
|
|
||||||
attrs := make([]interface{}, len(info.Attributes))
|
|
||||||
for i, attr := range info.Attributes {
|
|
||||||
attrs[i] = RawString(attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the delimiter is NIL, we need to treat it specially by inserting
|
|
||||||
// a nil field (so that it's later converted to an unquoted NIL atom).
|
|
||||||
var del interface{}
|
|
||||||
|
|
||||||
if info.Delimiter != "" {
|
|
||||||
del = info.Delimiter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thunderbird doesn't understand delimiters if not quoted
|
|
||||||
return []interface{}{attrs, del, FormatMailboxName(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 info.Delimiter != "" && strings.HasPrefix(pattern, info.Delimiter) {
|
|
||||||
reference = ""
|
|
||||||
pattern = strings.TrimPrefix(pattern, info.Delimiter)
|
|
||||||
}
|
|
||||||
if reference != "" {
|
|
||||||
if info.Delimiter != "" && !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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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[StatusItem]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 sequence number of the first unseen message in the mailbox.
|
|
||||||
UnseenSeqNum uint32
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Per-mailbox limit of message size. Set only if server supports the
|
|
||||||
// APPENDLIMIT extension.
|
|
||||||
AppendLimit uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new mailbox status that will contain the specified items.
|
|
||||||
func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
|
|
||||||
status := &MailboxStatus{
|
|
||||||
Name: name,
|
|
||||||
Items: make(map[StatusItem]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range items {
|
|
||||||
status.Items[k] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *MailboxStatus) Parse(fields []interface{}) error {
|
|
||||||
status.Items = make(map[StatusItem]interface{})
|
|
||||||
|
|
||||||
var k StatusItem
|
|
||||||
for i, f := range fields {
|
|
||||||
if i%2 == 0 {
|
|
||||||
if kstr, ok := f.(string); !ok {
|
|
||||||
return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
|
|
||||||
} else {
|
|
||||||
k = StatusItem(strings.ToUpper(kstr))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status.Items[k] = nil
|
|
||||||
|
|
||||||
var err error
|
|
||||||
switch k {
|
|
||||||
case StatusMessages:
|
|
||||||
status.Messages, err = ParseNumber(f)
|
|
||||||
case StatusRecent:
|
|
||||||
status.Recent, err = ParseNumber(f)
|
|
||||||
case StatusUnseen:
|
|
||||||
status.Unseen, err = ParseNumber(f)
|
|
||||||
case StatusUidNext:
|
|
||||||
status.UidNext, err = ParseNumber(f)
|
|
||||||
case StatusUidValidity:
|
|
||||||
status.UidValidity, err = ParseNumber(f)
|
|
||||||
case StatusAppendLimit:
|
|
||||||
status.AppendLimit, 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 StatusMessages:
|
|
||||||
v = status.Messages
|
|
||||||
case StatusRecent:
|
|
||||||
v = status.Recent
|
|
||||||
case StatusUnseen:
|
|
||||||
v = status.Unseen
|
|
||||||
case StatusUidNext:
|
|
||||||
v = status.UidNext
|
|
||||||
case StatusUidValidity:
|
|
||||||
v = status.UidValidity
|
|
||||||
case StatusAppendLimit:
|
|
||||||
v = status.AppendLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = append(fields, RawString(k), v)
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMailboxName(name string) interface{} {
|
|
||||||
// Some e-mails servers don't handle quoted INBOX names correctly so we special-case it.
|
|
||||||
if strings.EqualFold(name, "INBOX") {
|
|
||||||
return RawString(name)
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
1187
vendor/github.com/emersion/go-imap/message.go
generated
vendored
1187
vendor/github.com/emersion/go-imap/message.go
generated
vendored
File diff suppressed because it is too large
Load Diff
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
@@ -1,467 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseNumber parses a number.
|
|
||||||
func ParseNumber(f interface{}) (uint32, error) {
|
|
||||||
// Useful for tests
|
|
||||||
if n, ok := f.(uint32); ok {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var s string
|
|
||||||
switch f := f.(type) {
|
|
||||||
case RawString:
|
|
||||||
s = string(f)
|
|
||||||
case string:
|
|
||||||
s = f
|
|
||||||
default:
|
|
||||||
return 0, newParseError("expected a number, got a non-atom")
|
|
||||||
}
|
|
||||||
|
|
||||||
nbr, err := strconv.ParseUint(string(s), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, &parseError{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(nbr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseString parses a string, which is either a literal, a quoted string or an
|
|
||||||
// atom.
|
|
||||||
func ParseString(f interface{}) (string, error) {
|
|
||||||
if s, ok := f.(string); ok {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Useful for tests
|
|
||||||
if a, ok := f.(RawString); ok {
|
|
||||||
return string(a), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, ok := f.(Literal); ok {
|
|
||||||
b := make([]byte, l.Len())
|
|
||||||
if _, err := io.ReadFull(l, b); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", newParseError("expected a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a field list to a string list.
|
|
||||||
func ParseStringList(f interface{}) ([]string, error) {
|
|
||||||
fields, ok := f.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, newParseError("expected a string list, got a non-list")
|
|
||||||
}
|
|
||||||
|
|
||||||
list := make([]string, len(fields))
|
|
||||||
for i, f := range fields {
|
|
||||||
var err error
|
|
||||||
if list[i], err = ParseString(f); err != nil {
|
|
||||||
return nil, newParseError("cannot parse string in string list: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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("expected a space")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadCrlf() (err error) {
|
|
||||||
var char rune
|
|
||||||
|
|
||||||
if char, _, err = r.ReadRune(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if char == lf {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if char != cr {
|
|
||||||
err = newParseError("line doesn't end with a CR")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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 || char == lf {
|
|
||||||
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 == nilAtom {
|
|
||||||
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)
|
|
||||||
|
|
||||||
nonSync := strings.HasSuffix(lstr, "+")
|
|
||||||
if nonSync {
|
|
||||||
lstr = trimSuffix(lstr, '+')
|
|
||||||
}
|
|
||||||
|
|
||||||
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 && !nonSync {
|
|
||||||
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 == lf || char == listEnd || char == respCodeEnd {
|
|
||||||
if char == cr || char == lf {
|
|
||||||
r.UnreadRune()
|
|
||||||
}
|
|
||||||
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 StatusRespCode, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
codeStr, ok := fields[0].(string)
|
|
||||||
if !ok {
|
|
||||||
err = newParseError("response code doesn't start with a string atom")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if codeStr == "" {
|
|
||||||
err = newParseError("response code is empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
code = StatusRespCode(strings.ToUpper(codeStr))
|
|
||||||
|
|
||||||
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(lf))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info = strings.TrimSuffix(info, string(lf))
|
|
||||||
info = strings.TrimSuffix(info, string(cr))
|
|
||||||
info = strings.TrimLeft(info, " ")
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
@@ -1,181 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Resp is an IMAP response. It is either a *DataResp, a
|
|
||||||
// *ContinuationReq or a *StatusResp.
|
|
||||||
type Resp interface {
|
|
||||||
resp()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResp reads a single response from a Reader.
|
|
||||||
func ReadResp(r *Reader) (Resp, error) {
|
|
||||||
atom, err := r.ReadAtom()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tag, ok := atom.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, newParseError("response tag is not an atom")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag == "+" {
|
|
||||||
if err := r.ReadSp(); err != nil {
|
|
||||||
r.UnreadRune()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &ContinuationReq{}
|
|
||||||
resp.Info, err = r.ReadInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.ReadSp(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can be either data or status
|
|
||||||
// Try to parse a status
|
|
||||||
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)
|
|
||||||
switch status {
|
|
||||||
case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
|
|
||||||
resp := &StatusResp{
|
|
||||||
Tag: tag,
|
|
||||||
Type: status,
|
|
||||||
}
|
|
||||||
|
|
||||||
char, _, err := r.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.UnreadRune()
|
|
||||||
|
|
||||||
if char == '[' {
|
|
||||||
// Contains code & arguments
|
|
||||||
resp.Code, resp.Arguments, err = r.ReadRespCode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Info, err = r.ReadInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r.UnreadRune()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r.UnreadRune()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a status so it's data
|
|
||||||
resp := &DataResp{Tag: tag}
|
|
||||||
|
|
||||||
var remaining []interface{}
|
|
||||||
remaining, err = r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Fields = append(fields, remaining...)
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataResp is an IMAP response containing data.
|
|
||||||
type DataResp 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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUntaggedResp creates a new untagged response.
|
|
||||||
func NewUntaggedResp(fields []interface{}) *DataResp {
|
|
||||||
return &DataResp{
|
|
||||||
Tag: "*",
|
|
||||||
Fields: fields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DataResp) resp() {}
|
|
||||||
|
|
||||||
func (r *DataResp) WriteTo(w *Writer) error {
|
|
||||||
tag := RawString(r.Tag)
|
|
||||||
if tag == "" {
|
|
||||||
tag = RawString("*")
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := []interface{}{RawString(tag)}
|
|
||||||
fields = append(fields, r.Fields...)
|
|
||||||
return w.writeLine(fields...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContinuationReq is a continuation request response.
|
|
||||||
type ContinuationReq struct {
|
|
||||||
// The info message sent with the continuation request.
|
|
||||||
Info string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ContinuationReq) resp() {}
|
|
||||||
|
|
||||||
func (r *ContinuationReq) 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseNamedResp attempts to parse a named data response.
|
|
||||||
func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
|
|
||||||
data, ok := resp.(*DataResp)
|
|
||||||
if !ok || len(data.Fields) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some responses (namely EXISTS and RECENT) are formatted like so:
|
|
||||||
// [num] [name] [...]
|
|
||||||
// Which is fucking stupid. But we handle that here by checking if the
|
|
||||||
// response name is a number and then rearranging it.
|
|
||||||
if len(data.Fields) > 1 {
|
|
||||||
name, ok := data.Fields[1].(string)
|
|
||||||
if ok {
|
|
||||||
if _, err := ParseNumber(data.Fields[0]); err == nil {
|
|
||||||
fields := []interface{}{data.Fields[0]}
|
|
||||||
fields = append(fields, data.Fields[2:]...)
|
|
||||||
return strings.ToUpper(name), fields, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IMAP commands are formatted like this:
|
|
||||||
// [name] [...]
|
|
||||||
name, ok = data.Fields[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return strings.ToUpper(name), data.Fields[1:], true
|
|
||||||
}
|
|
||||||
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
@@ -1,61 +0,0 @@
|
|||||||
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
|
|
||||||
RepliesCh chan []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements
|
|
||||||
func (r *Authenticate) Replies() <-chan []byte {
|
|
||||||
return r.RepliesCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Authenticate) writeLine(l string) error {
|
|
||||||
r.RepliesCh <- []byte(l + "\r\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Authenticate) cancel() error {
|
|
||||||
return r.writeLine("*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Authenticate) Handle(resp imap.Resp) error {
|
|
||||||
cont, ok := resp.(*imap.ContinuationReq)
|
|
||||||
if !ok {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 := r.writeLine(encoded); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.InitialResponse = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
challenge, err := base64.StdEncoding.DecodeString(cont.Info)
|
|
||||||
if err != nil {
|
|
||||||
r.cancel()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := r.Mechanism.Next(challenge)
|
|
||||||
if err != nil {
|
|
||||||
r.cancel()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(reply)
|
|
||||||
return r.writeLine(encoded)
|
|
||||||
}
|
|
||||||
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
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) WriteTo(w *imap.Writer) error {
|
|
||||||
fields := []interface{}{imap.RawString("CAPABILITY")}
|
|
||||||
for _, cap := range r.Caps {
|
|
||||||
fields = append(fields, imap.RawString(cap))
|
|
||||||
}
|
|
||||||
|
|
||||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
|
||||||
}
|
|
||||||
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
@@ -1,33 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An ENABLED response, defined in RFC 5161 section 3.2.
|
|
||||||
type Enabled struct {
|
|
||||||
Caps []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Enabled) Handle(resp imap.Resp) error {
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != "ENABLED" {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
if caps, err := imap.ParseStringList(fields); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
r.Caps = append(r.Caps, caps...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Enabled) WriteTo(w *imap.Writer) error {
|
|
||||||
fields := []interface{}{imap.RawString("ENABLED")}
|
|
||||||
for _, cap := range r.Caps {
|
|
||||||
fields = append(fields, imap.RawString(cap))
|
|
||||||
}
|
|
||||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
|
||||||
}
|
|
||||||
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
@@ -1,43 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const expungeName = "EXPUNGE"
|
|
||||||
|
|
||||||
// An EXPUNGE response.
|
|
||||||
// See RFC 3501 section 7.4.1
|
|
||||||
type Expunge struct {
|
|
||||||
SeqNums chan uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Expunge) Handle(resp imap.Resp) error {
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != expungeName {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return errNotEnoughFields
|
|
||||||
}
|
|
||||||
|
|
||||||
seqNum, err := imap.ParseNumber(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SeqNums <- seqNum
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Expunge) WriteTo(w *imap.Writer) error {
|
|
||||||
for seqNum := range r.SeqNums {
|
|
||||||
resp := imap.NewUntaggedResp([]interface{}{seqNum, imap.RawString(expungeName)})
|
|
||||||
if err := resp.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
@@ -1,70 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const fetchName = "FETCH"
|
|
||||||
|
|
||||||
// A FETCH response.
|
|
||||||
// See RFC 3501 section 7.4.2
|
|
||||||
type Fetch struct {
|
|
||||||
Messages chan *imap.Message
|
|
||||||
SeqSet *imap.SeqSet
|
|
||||||
Uid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Fetch) Handle(resp imap.Resp) error {
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != fetchName {
|
|
||||||
return ErrUnhandled
|
|
||||||
} else if len(fields) < 1 {
|
|
||||||
return errNotEnoughFields
|
|
||||||
}
|
|
||||||
|
|
||||||
seqNum, err := imap.ParseNumber(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msgFields, _ := fields[1].([]interface{})
|
|
||||||
msg := &imap.Message{SeqNum: seqNum}
|
|
||||||
if err := msg.Parse(msgFields); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Uid && msg.Uid == 0 {
|
|
||||||
// we requested UIDs and got a message without one --> unilateral update --> ignore
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
var num uint32
|
|
||||||
if r.Uid {
|
|
||||||
num = msg.Uid
|
|
||||||
} else {
|
|
||||||
num = seqNum
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether we obtained a result we requested with our SeqSet
|
|
||||||
// If the result is not contained in our SeqSet we have to handle an additional special case:
|
|
||||||
// In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
|
|
||||||
// is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
|
|
||||||
// Thus, such a result is correct and has to be returned by us.
|
|
||||||
if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Messages <- msg
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Fetch) WriteTo(w *imap.Writer) error {
|
|
||||||
var err error
|
|
||||||
for msg := range r.Messages {
|
|
||||||
resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.RawString(fetchName), msg.Format()})
|
|
||||||
if err == nil {
|
|
||||||
err = resp.WriteTo(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An IDLE response.
|
|
||||||
type Idle struct {
|
|
||||||
RepliesCh chan []byte
|
|
||||||
Stop <-chan struct{}
|
|
||||||
|
|
||||||
gotContinuationReq bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Idle) Replies() <-chan []byte {
|
|
||||||
return r.RepliesCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Idle) stop() {
|
|
||||||
r.RepliesCh <- []byte("DONE\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Idle) Handle(resp imap.Resp) error {
|
|
||||||
// Wait for a continuation request
|
|
||||||
if _, ok := resp.(*imap.ContinuationReq); ok && !r.gotContinuationReq {
|
|
||||||
r.gotContinuationReq = true
|
|
||||||
|
|
||||||
// We got a continuation request, wait for r.Stop to be closed
|
|
||||||
go func() {
|
|
||||||
<-r.Stop
|
|
||||||
r.stop()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
@@ -1,57 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
listName = "LIST"
|
|
||||||
lsubName = "LSUB"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 lsubName
|
|
||||||
} else {
|
|
||||||
return listName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *List) Handle(resp imap.Resp) error {
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != r.Name() {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
respName := r.Name()
|
|
||||||
|
|
||||||
for mbox := range r.Mailboxes {
|
|
||||||
fields := []interface{}{imap.RawString(respName)}
|
|
||||||
fields = append(fields, mbox.Format()...)
|
|
||||||
|
|
||||||
resp := imap.NewUntaggedResp(fields)
|
|
||||||
if err := resp.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
@@ -1,35 +0,0 @@
|
|||||||
// IMAP responses defined in RFC 3501.
|
|
||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnhandled is used when a response hasn't been handled.
|
|
||||||
var ErrUnhandled = errors.New("imap: unhandled response")
|
|
||||||
|
|
||||||
var errNotEnoughFields = errors.New("imap: not enough fields in response")
|
|
||||||
|
|
||||||
// Handler handles responses.
|
|
||||||
type Handler interface {
|
|
||||||
// Handle processes a response. If the response cannot be processed,
|
|
||||||
// ErrUnhandledResp must be returned.
|
|
||||||
Handle(resp imap.Resp) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlerFunc is a function that handles responses.
|
|
||||||
type HandlerFunc func(resp imap.Resp) error
|
|
||||||
|
|
||||||
// Handle implements Handler.
|
|
||||||
func (f HandlerFunc) Handle(resp imap.Resp) error {
|
|
||||||
return f(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replier is a Handler that needs to send raw data (for instance
|
|
||||||
// AUTHENTICATE).
|
|
||||||
type Replier interface {
|
|
||||||
Handler
|
|
||||||
Replies() <-chan []byte
|
|
||||||
}
|
|
||||||
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
@@ -1,41 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const searchName = "SEARCH"
|
|
||||||
|
|
||||||
// A SEARCH response.
|
|
||||||
// See RFC 3501 section 7.2.5
|
|
||||||
type Search struct {
|
|
||||||
Ids []uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Search) Handle(resp imap.Resp) error {
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != searchName {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Ids = make([]uint32, len(fields))
|
|
||||||
for i, f := range fields {
|
|
||||||
if id, err := imap.ParseNumber(f); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
r.Ids[i] = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Search) WriteTo(w *imap.Writer) (err error) {
|
|
||||||
fields := []interface{}{imap.RawString(searchName)}
|
|
||||||
for _, id := range r.Ids {
|
|
||||||
fields = append(fields, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := imap.NewUntaggedResp(fields)
|
|
||||||
return resp.WriteTo(w)
|
|
||||||
}
|
|
||||||
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
@@ -1,142 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A SELECT response.
|
|
||||||
type Select struct {
|
|
||||||
Mailbox *imap.MailboxStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Select) Handle(resp imap.Resp) error {
|
|
||||||
if r.Mailbox == nil {
|
|
||||||
r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
|
|
||||||
}
|
|
||||||
mbox := r.Mailbox
|
|
||||||
|
|
||||||
switch resp := resp.(type) {
|
|
||||||
case *imap.DataResp:
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != "FLAGS" {
|
|
||||||
return ErrUnhandled
|
|
||||||
} else if len(fields) < 1 {
|
|
||||||
return errNotEnoughFields
|
|
||||||
}
|
|
||||||
|
|
||||||
flags, _ := fields[0].([]interface{})
|
|
||||||
mbox.Flags, _ = imap.ParseStringList(flags)
|
|
||||||
case *imap.StatusResp:
|
|
||||||
if len(resp.Arguments) < 1 {
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
var item imap.StatusItem
|
|
||||||
switch resp.Code {
|
|
||||||
case "UNSEEN":
|
|
||||||
mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
|
|
||||||
case "PERMANENTFLAGS":
|
|
||||||
flags, _ := resp.Arguments[0].([]interface{})
|
|
||||||
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
|
|
||||||
case "UIDNEXT":
|
|
||||||
mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
|
|
||||||
item = imap.StatusUidNext
|
|
||||||
case "UIDVALIDITY":
|
|
||||||
mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
|
|
||||||
item = imap.StatusUidValidity
|
|
||||||
default:
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
|
|
||||||
if item != "" {
|
|
||||||
mbox.ItemsLocker.Lock()
|
|
||||||
mbox.Items[item] = nil
|
|
||||||
mbox.ItemsLocker.Unlock()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return ErrUnhandled
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Select) WriteTo(w *imap.Writer) error {
|
|
||||||
mbox := r.Mailbox
|
|
||||||
|
|
||||||
if mbox.Flags != nil {
|
|
||||||
flags := make([]interface{}, len(mbox.Flags))
|
|
||||||
for i, f := range mbox.Flags {
|
|
||||||
flags[i] = imap.RawString(f)
|
|
||||||
}
|
|
||||||
res := imap.NewUntaggedResp([]interface{}{imap.RawString("FLAGS"), flags})
|
|
||||||
if err := res.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mbox.PermanentFlags != nil {
|
|
||||||
flags := make([]interface{}, len(mbox.PermanentFlags))
|
|
||||||
for i, f := range mbox.PermanentFlags {
|
|
||||||
flags[i] = imap.RawString(f)
|
|
||||||
}
|
|
||||||
statusRes := &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespOk,
|
|
||||||
Code: imap.CodePermanentFlags,
|
|
||||||
Arguments: []interface{}{flags},
|
|
||||||
Info: "Flags permitted.",
|
|
||||||
}
|
|
||||||
if err := statusRes.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mbox.UnseenSeqNum > 0 {
|
|
||||||
statusRes := &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespOk,
|
|
||||||
Code: imap.CodeUnseen,
|
|
||||||
Arguments: []interface{}{mbox.UnseenSeqNum},
|
|
||||||
Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
|
|
||||||
}
|
|
||||||
if err := statusRes.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range r.Mailbox.Items {
|
|
||||||
switch k {
|
|
||||||
case imap.StatusMessages:
|
|
||||||
res := imap.NewUntaggedResp([]interface{}{mbox.Messages, imap.RawString("EXISTS")})
|
|
||||||
if err := res.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case imap.StatusRecent:
|
|
||||||
res := imap.NewUntaggedResp([]interface{}{mbox.Recent, imap.RawString("RECENT")})
|
|
||||||
if err := res.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case imap.StatusUidNext:
|
|
||||||
statusRes := &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespOk,
|
|
||||||
Code: imap.CodeUidNext,
|
|
||||||
Arguments: []interface{}{mbox.UidNext},
|
|
||||||
Info: "Predicted next UID",
|
|
||||||
}
|
|
||||||
if err := statusRes.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case imap.StatusUidValidity:
|
|
||||||
statusRes := &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespOk,
|
|
||||||
Code: imap.CodeUidValidity,
|
|
||||||
Arguments: []interface{}{mbox.UidValidity},
|
|
||||||
Info: "UIDs valid",
|
|
||||||
}
|
|
||||||
if err := statusRes.WriteTo(w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
@@ -1,53 +0,0 @@
|
|||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
|
||||||
"github.com/emersion/go-imap/utf7"
|
|
||||||
)
|
|
||||||
|
|
||||||
const statusName = "STATUS"
|
|
||||||
|
|
||||||
// A STATUS response.
|
|
||||||
// See RFC 3501 section 7.2.4
|
|
||||||
type Status struct {
|
|
||||||
Mailbox *imap.MailboxStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Status) Handle(resp imap.Resp) error {
|
|
||||||
if r.Mailbox == nil {
|
|
||||||
r.Mailbox = &imap.MailboxStatus{}
|
|
||||||
}
|
|
||||||
mbox := r.Mailbox
|
|
||||||
|
|
||||||
name, fields, ok := imap.ParseNamedResp(resp)
|
|
||||||
if !ok || name != statusName {
|
|
||||||
return ErrUnhandled
|
|
||||||
} else if len(fields) < 2 {
|
|
||||||
return errNotEnoughFields
|
|
||||||
}
|
|
||||||
|
|
||||||
if name, err := imap.ParseString(fields[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
mbox.Name = imap.CanonicalMailboxName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []interface{}
|
|
||||||
if items, ok = fields[1].([]interface{}); !ok {
|
|
||||||
return errors.New("STATUS response expects a list as second argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
mbox.Items = nil
|
|
||||||
return mbox.Parse(items)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Status) WriteTo(w *imap.Writer) error {
|
|
||||||
mbox := r.Mailbox
|
|
||||||
name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
|
|
||||||
fields := []interface{}{imap.RawString(statusName), imap.FormatMailboxName(name), mbox.Format()}
|
|
||||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
|
||||||
}
|
|
||||||
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
@@ -1,371 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"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 := make([]byte, l.Len())
|
|
||||||
if _, err := io.ReadFull(r, b); 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 = ParseSeqSet(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 = ParseSeqSet(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, RawString("UID"), c.Uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour {
|
|
||||||
fields = append(fields, RawString("ON"), searchDate(c.Since))
|
|
||||||
} else {
|
|
||||||
if !c.Since.IsZero() {
|
|
||||||
fields = append(fields, RawString("SINCE"), searchDate(c.Since))
|
|
||||||
}
|
|
||||||
if !c.Before.IsZero() {
|
|
||||||
fields = append(fields, RawString("BEFORE"), searchDate(c.Before))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour {
|
|
||||||
fields = append(fields, RawString("SENTON"), searchDate(c.SentSince))
|
|
||||||
} else {
|
|
||||||
if !c.SentSince.IsZero() {
|
|
||||||
fields = append(fields, RawString("SENTSINCE"), searchDate(c.SentSince))
|
|
||||||
}
|
|
||||||
if !c.SentBefore.IsZero() {
|
|
||||||
fields = append(fields, RawString("SENTBEFORE"), searchDate(c.SentBefore))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, values := range c.Header {
|
|
||||||
var prefields []interface{}
|
|
||||||
switch key {
|
|
||||||
case "Bcc", "Cc", "From", "Subject", "To":
|
|
||||||
prefields = []interface{}{RawString(strings.ToUpper(key))}
|
|
||||||
default:
|
|
||||||
prefields = []interface{}{RawString("HEADER"), key}
|
|
||||||
}
|
|
||||||
for _, value := range values {
|
|
||||||
fields = append(fields, prefields...)
|
|
||||||
fields = append(fields, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range c.Body {
|
|
||||||
fields = append(fields, RawString("BODY"), value)
|
|
||||||
}
|
|
||||||
for _, value := range c.Text {
|
|
||||||
fields = append(fields, RawString("TEXT"), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, flag := range c.WithFlags {
|
|
||||||
var subfields []interface{}
|
|
||||||
switch flag {
|
|
||||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag:
|
|
||||||
subfields = []interface{}{RawString(strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
|
||||||
default:
|
|
||||||
subfields = []interface{}{RawString("KEYWORD"), RawString(flag)}
|
|
||||||
}
|
|
||||||
fields = append(fields, subfields...)
|
|
||||||
}
|
|
||||||
for _, flag := range c.WithoutFlags {
|
|
||||||
var subfields []interface{}
|
|
||||||
switch flag {
|
|
||||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag:
|
|
||||||
subfields = []interface{}{RawString("UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
|
||||||
case RecentFlag:
|
|
||||||
subfields = []interface{}{RawString("OLD")}
|
|
||||||
default:
|
|
||||||
subfields = []interface{}{RawString("UNKEYWORD"), RawString(flag)}
|
|
||||||
}
|
|
||||||
fields = append(fields, subfields...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Larger > 0 {
|
|
||||||
fields = append(fields, RawString("LARGER"), c.Larger)
|
|
||||||
}
|
|
||||||
if c.Smaller > 0 {
|
|
||||||
fields = append(fields, RawString("SMALLER"), c.Smaller)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, not := range c.Not {
|
|
||||||
fields = append(fields, RawString("NOT"), not.Format())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, or := range c.Or {
|
|
||||||
fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a single criteria given, add ALL criteria as fallback
|
|
||||||
if len(fields) == 0 {
|
|
||||||
fields = append(fields, RawString("ALL"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
@@ -1,289 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseSeqSet returns a new SeqSet instance after parsing the set string.
|
|
||||||
func ParseSeqSet(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
@@ -1,136 +0,0 @@
|
|||||||
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.
|
|
||||||
StatusRespOk 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.
|
|
||||||
StatusRespNo StatusRespType = "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.
|
|
||||||
StatusRespBad StatusRespType = "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.
|
|
||||||
StatusRespPreauth StatusRespType = "PREAUTH"
|
|
||||||
|
|
||||||
// The BYE response is always untagged, and indicates that the server
|
|
||||||
// is about to close the connection.
|
|
||||||
StatusRespBye StatusRespType = "BYE"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StatusRespCode string
|
|
||||||
|
|
||||||
// Status response codes defined in RFC 3501 section 7.1.
|
|
||||||
const (
|
|
||||||
CodeAlert StatusRespCode = "ALERT"
|
|
||||||
CodeBadCharset StatusRespCode = "BADCHARSET"
|
|
||||||
CodeCapability StatusRespCode = "CAPABILITY"
|
|
||||||
CodeParse StatusRespCode = "PARSE"
|
|
||||||
CodePermanentFlags StatusRespCode = "PERMANENTFLAGS"
|
|
||||||
CodeReadOnly StatusRespCode = "READ-ONLY"
|
|
||||||
CodeReadWrite StatusRespCode = "READ-WRITE"
|
|
||||||
CodeTryCreate StatusRespCode = "TRYCREATE"
|
|
||||||
CodeUidNext StatusRespCode = "UIDNEXT"
|
|
||||||
CodeUidValidity StatusRespCode = "UIDVALIDITY"
|
|
||||||
CodeUnseen StatusRespCode = "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 StatusRespCode
|
|
||||||
// Arguments provided with the status code.
|
|
||||||
Arguments []interface{}
|
|
||||||
// The status info.
|
|
||||||
Info string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *StatusResp) resp() {}
|
|
||||||
|
|
||||||
// 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 == StatusRespNo || r.Type == StatusRespBad {
|
|
||||||
return errors.New(r.Info)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *StatusResp) WriteTo(w *Writer) error {
|
|
||||||
tag := RawString(r.Tag)
|
|
||||||
if tag == "" {
|
|
||||||
tag = "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.writeFields([]interface{}{RawString(tag), RawString(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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrStatusResp can be returned by a server.Handler to replace the default status
|
|
||||||
// response. The response tag must be empty.
|
|
||||||
//
|
|
||||||
// To suppress default response, Resp should be set to nil.
|
|
||||||
type ErrStatusResp struct {
|
|
||||||
// Response to send instead of default.
|
|
||||||
Resp *StatusResp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *ErrStatusResp) Error() string {
|
|
||||||
if err.Resp == nil {
|
|
||||||
return "imap: suppressed response"
|
|
||||||
}
|
|
||||||
return err.Resp.Info
|
|
||||||
}
|
|
||||||
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
@@ -1,149 +0,0 @@
|
|||||||
package utf7
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7.
|
|
||||||
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
|
|
||||||
|
|
||||||
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 = ErrInvalidUTF7
|
|
||||||
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 = ErrInvalidUTF7
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == len(src) { // Implicit shift ("&...")
|
|
||||||
if atEOF {
|
|
||||||
err = ErrInvalidUTF7
|
|
||||||
} 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 = ErrInvalidUTF7
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b = decode(src[start:i])
|
|
||||||
d.ascii = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) == 0 { // Bad encoding
|
|
||||||
err = ErrInvalidUTF7
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if nDst+len(b) > len(dst) {
|
|
||||||
d.ascii = true
|
|
||||||
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, b64Enc.DecodedLen(n)*3)
|
|
||||||
} else {
|
|
||||||
n += 4 - n&3
|
|
||||||
b = make([]byte, n+b64Enc.DecodedLen(n)*3)
|
|
||||||
copy(b[copy(b, b64):n], []byte("=="))
|
|
||||||
b64, b = b[:n], b[n:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode Base64 into the first 1/3rd of b
|
|
||||||
n, err := b64Enc.Decode(b, b64)
|
|
||||||
if err != nil || n&1 == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
|
||||||
b, s := b[:n], b[n:]
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < n; i += 2 {
|
|
||||||
r := rune(b[i])<<8 | rune(b[i+1])
|
|
||||||
if utf16.IsSurrogate(r) {
|
|
||||||
if i += 2; i == n {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
|
||||||
if r = utf16.DecodeRune(r, r2); r == repl {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else if min <= r && r <= max {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
j += utf8.EncodeRune(s[j:], r)
|
|
||||||
}
|
|
||||||
return s[:j]
|
|
||||||
}
|
|
||||||
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
package utf7
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf16"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 := b64Enc.EncodedLen(len(b)) + 2
|
|
||||||
b64 := make([]byte, n)
|
|
||||||
b64Enc.Encode(b64[1:], b)
|
|
||||||
|
|
||||||
// Strip padding
|
|
||||||
n -= 2 - (len(b)+2)%3
|
|
||||||
b64 = b64[:n]
|
|
||||||
|
|
||||||
// Add UTF-7 shifts
|
|
||||||
b64[0] = '&'
|
|
||||||
b64[n-1] = '-'
|
|
||||||
return b64
|
|
||||||
}
|
|
||||||
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
@@ -1,34 +0,0 @@
|
|||||||
// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
|
|
||||||
package utf7
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
min = 0x20 // Minimum self-representing UTF-7 value
|
|
||||||
max = 0x7E // Maximum self-representing UTF-7 value
|
|
||||||
|
|
||||||
repl = '\uFFFD' // Unicode replacement code point
|
|
||||||
)
|
|
||||||
|
|
||||||
var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
|
||||||
|
|
||||||
type enc struct{}
|
|
||||||
|
|
||||||
func (e enc) NewDecoder() *encoding.Decoder {
|
|
||||||
return &encoding.Decoder{
|
|
||||||
Transformer: &decoder{true},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e enc) NewEncoder() *encoding.Encoder {
|
|
||||||
return &encoding.Encoder{
|
|
||||||
Transformer: &encoder{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding is the modified UTF-7 encoding.
|
|
||||||
var Encoding encoding.Encoding = enc{}
|
|
||||||
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
@@ -1,255 +0,0 @@
|
|||||||
package imap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type flusher interface {
|
|
||||||
Flush() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// A raw string.
|
|
||||||
RawString 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
|
|
||||||
|
|
||||||
AllowAsyncLiterals bool
|
|
||||||
|
|
||||||
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) writeQuotedOrLiteral(s string) error {
|
|
||||||
if !isAscii(s) {
|
|
||||||
// IMAP doesn't allow 8-bit data outside literals
|
|
||||||
return w.writeLiteral(bytes.NewBufferString(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.writeQuoted(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeDateTime(t time.Time, layout string) error {
|
|
||||||
if t.IsZero() {
|
|
||||||
return w.writeString(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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LiteralLengthErr is returned when the Len() of the Literal object does not
|
|
||||||
// match the actual length of the byte stream.
|
|
||||||
type LiteralLengthErr struct {
|
|
||||||
Actual int
|
|
||||||
Expected int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e LiteralLengthErr) Error() string {
|
|
||||||
return fmt.Sprintf("imap: size of Literal is not equal to Len() (%d != %d)", e.Expected, e.Actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeLiteral(l Literal) error {
|
|
||||||
if l == nil {
|
|
||||||
return w.writeString(nilAtom)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsyncLiteral := w.AllowAsyncLiterals && l.Len() <= 4096
|
|
||||||
|
|
||||||
header := string(literalStart) + strconv.Itoa(l.Len())
|
|
||||||
if unsyncLiteral {
|
|
||||||
header += string('+')
|
|
||||||
}
|
|
||||||
header += 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 !unsyncLiteral && 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case of bufio.Buffer, it will be 0 after io.Copy.
|
|
||||||
literalLen := int64(l.Len())
|
|
||||||
|
|
||||||
n, err := io.CopyN(w, l, literalLen)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && n != literalLen {
|
|
||||||
return LiteralLengthErr{int(n), l.Len()}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
extra, _ := io.Copy(ioutil.Discard, l)
|
|
||||||
if extra != 0 {
|
|
||||||
return LiteralLengthErr{int(n + extra), l.Len()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeField(field interface{}) error {
|
|
||||||
if field == nil {
|
|
||||||
return w.writeString(nilAtom)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch field := field.(type) {
|
|
||||||
case RawString:
|
|
||||||
return w.writeString(string(field))
|
|
||||||
case string:
|
|
||||||
return w.writeQuotedOrLiteral(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(string(field.FetchItem()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("imap: cannot format field: %v", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
|
|
||||||
if err := w.writeString(string(respCodeStart)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := []interface{}{RawString(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}
|
|
||||||
}
|
|
||||||
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
image: alpine/latest
|
|
||||||
packages:
|
|
||||||
- go
|
|
||||||
# Required by codecov
|
|
||||||
- bash
|
|
||||||
- findutils
|
|
||||||
sources:
|
|
||||||
- https://github.com/emersion/go-sasl
|
|
||||||
tasks:
|
|
||||||
- build: |
|
|
||||||
cd go-sasl
|
|
||||||
go build -v ./...
|
|
||||||
- test: |
|
|
||||||
cd go-sasl
|
|
||||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
|
||||||
- upload-coverage: |
|
|
||||||
cd go-sasl
|
|
||||||
export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
|
|
||||||
curl -s https://codecov.io/bash | bash
|
|
||||||
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
@@ -1,24 +0,0 @@
|
|||||||
# 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
|
|
||||||
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 emersion
|
|
||||||
|
|
||||||
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.
|
|
||||||
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
# go-sasl
|
|
||||||
|
|
||||||
[](https://godoc.org/github.com/emersion/go-sasl)
|
|
||||||
[](https://travis-ci.org/emersion/go-sasl)
|
|
||||||
|
|
||||||
A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go.
|
|
||||||
|
|
||||||
Implemented mechanisms:
|
|
||||||
* [ANONYMOUS](https://tools.ietf.org/html/rfc4505)
|
|
||||||
* [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A)
|
|
||||||
* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead)
|
|
||||||
* [PLAIN](https://tools.ietf.org/html/rfc4616)
|
|
||||||
* [OAUTHBEARER](https://tools.ietf.org/html/rfc7628)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
package sasl
|
|
||||||
|
|
||||||
// The ANONYMOUS mechanism name.
|
|
||||||
const Anonymous = "ANONYMOUS"
|
|
||||||
|
|
||||||
type anonymousClient struct {
|
|
||||||
Trace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *anonymousClient) Start() (mech string, ir []byte, err error) {
|
|
||||||
mech = Anonymous
|
|
||||||
ir = []byte(c.Trace)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) {
|
|
||||||
return nil, ErrUnexpectedServerChallenge
|
|
||||||
}
|
|
||||||
|
|
||||||
// A client implementation of the ANONYMOUS authentication mechanism, as
|
|
||||||
// described in RFC 4505.
|
|
||||||
func NewAnonymousClient(trace string) Client {
|
|
||||||
return &anonymousClient{trace}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get trace information from clients logging in anonymously.
|
|
||||||
type AnonymousAuthenticator func(trace string) error
|
|
||||||
|
|
||||||
type anonymousServer struct {
|
|
||||||
done bool
|
|
||||||
authenticate AnonymousAuthenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
|
||||||
if s.done {
|
|
||||||
err = ErrUnexpectedClientResponse
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No initial response, send an empty challenge
|
|
||||||
if response == nil {
|
|
||||||
return []byte{}, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.done = true
|
|
||||||
|
|
||||||
err = s.authenticate(string(response))
|
|
||||||
done = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A server implementation of the ANONYMOUS authentication mechanism, as
|
|
||||||
// described in RFC 4505.
|
|
||||||
func NewAnonymousServer(authenticator AnonymousAuthenticator) Server {
|
|
||||||
return &anonymousServer{authenticate: authenticator}
|
|
||||||
}
|
|
||||||
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
package sasl
|
|
||||||
|
|
||||||
// The EXTERNAL mechanism name.
|
|
||||||
const External = "EXTERNAL"
|
|
||||||
|
|
||||||
type externalClient struct {
|
|
||||||
Identity string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *externalClient) Start() (mech string, ir []byte, err error) {
|
|
||||||
mech = External
|
|
||||||
ir = []byte(a.Identity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *externalClient) Next(challenge []byte) (response []byte, err error) {
|
|
||||||
return nil, ErrUnexpectedServerChallenge
|
|
||||||
}
|
|
||||||
|
|
||||||
// An implementation of the EXTERNAL authentication mechanism, as described in
|
|
||||||
// RFC 4422. Authorization identity may be left blank to indicate that the
|
|
||||||
// client is requesting to act as the identity associated with the
|
|
||||||
// authentication credentials.
|
|
||||||
func NewExternalClient(identity string) Client {
|
|
||||||
return &externalClient{identity}
|
|
||||||
}
|
|
||||||
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
@@ -1,89 +0,0 @@
|
|||||||
package sasl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The LOGIN mechanism name.
|
|
||||||
const Login = "LOGIN"
|
|
||||||
|
|
||||||
var expectedChallenge = []byte("Password:")
|
|
||||||
|
|
||||||
type loginClient struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *loginClient) Start() (mech string, ir []byte, err error) {
|
|
||||||
mech = "LOGIN"
|
|
||||||
ir = []byte(a.Username)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
|
|
||||||
if bytes.Compare(challenge, expectedChallenge) != 0 {
|
|
||||||
return nil, ErrUnexpectedServerChallenge
|
|
||||||
} else {
|
|
||||||
return []byte(a.Password), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A client implementation of the LOGIN authentication mechanism for SMTP,
|
|
||||||
// as described in http://www.iana.org/go/draft-murchison-sasl-login
|
|
||||||
//
|
|
||||||
// It is considered obsolete, and should not be used when other mechanisms are
|
|
||||||
// available. For plaintext password authentication use PLAIN mechanism.
|
|
||||||
func NewLoginClient(username, password string) Client {
|
|
||||||
return &loginClient{username, password}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticates users with an username and a password.
|
|
||||||
type LoginAuthenticator func(username, password string) error
|
|
||||||
|
|
||||||
type loginState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
loginNotStarted loginState = iota
|
|
||||||
loginWaitingUsername
|
|
||||||
loginWaitingPassword
|
|
||||||
)
|
|
||||||
|
|
||||||
type loginServer struct {
|
|
||||||
state loginState
|
|
||||||
username, password string
|
|
||||||
authenticate LoginAuthenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
// A server implementation of the LOGIN authentication mechanism, as described
|
|
||||||
// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
|
|
||||||
//
|
|
||||||
// LOGIN is obsolete and should only be enabled for legacy clients that cannot
|
|
||||||
// be updated to use PLAIN.
|
|
||||||
func NewLoginServer(authenticator LoginAuthenticator) Server {
|
|
||||||
return &loginServer{authenticate: authenticator}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
|
||||||
switch a.state {
|
|
||||||
case loginNotStarted:
|
|
||||||
// Check for initial response field, as per RFC4422 section 3
|
|
||||||
if response == nil {
|
|
||||||
challenge = []byte("Username:")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
a.state++
|
|
||||||
fallthrough
|
|
||||||
case loginWaitingUsername:
|
|
||||||
a.username = string(response)
|
|
||||||
challenge = []byte("Password:")
|
|
||||||
case loginWaitingPassword:
|
|
||||||
a.password = string(response)
|
|
||||||
err = a.authenticate(a.username, a.password)
|
|
||||||
done = true
|
|
||||||
default:
|
|
||||||
err = ErrUnexpectedClientResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
a.state++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
@@ -1,191 +0,0 @@
|
|||||||
package sasl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The OAUTHBEARER mechanism name.
|
|
||||||
const OAuthBearer = "OAUTHBEARER"
|
|
||||||
|
|
||||||
type OAuthBearerError struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Schemes string `json:"schemes"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OAuthBearerOptions struct {
|
|
||||||
Username string
|
|
||||||
Token string
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements error
|
|
||||||
func (err *OAuthBearerError) Error() string {
|
|
||||||
return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
type oauthBearerClient struct {
|
|
||||||
OAuthBearerOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) {
|
|
||||||
mech = OAuthBearer
|
|
||||||
var str = "n,a=" + a.Username + ","
|
|
||||||
|
|
||||||
if a.Host != "" {
|
|
||||||
str += "\x01host=" + a.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Port != 0 {
|
|
||||||
str += "\x01port=" + strconv.Itoa(a.Port)
|
|
||||||
}
|
|
||||||
str += "\x01auth=Bearer " + a.Token + "\x01\x01"
|
|
||||||
ir = []byte(str)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) {
|
|
||||||
authBearerErr := &OAuthBearerError{}
|
|
||||||
if err := json.Unmarshal(challenge, authBearerErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return nil, authBearerErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An implementation of the OAUTHBEARER authentication mechanism, as
|
|
||||||
// described in RFC 7628.
|
|
||||||
func NewOAuthBearerClient(opt *OAuthBearerOptions) Client {
|
|
||||||
return &oauthBearerClient{*opt}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
|
|
||||||
|
|
||||||
type oauthBearerServer struct {
|
|
||||||
done bool
|
|
||||||
failErr error
|
|
||||||
authenticate OAuthBearerAuthenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) {
|
|
||||||
blob, err := json.Marshal(OAuthBearerError{
|
|
||||||
Status: "invalid_request",
|
|
||||||
Schemes: "bearer",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // wtf
|
|
||||||
}
|
|
||||||
a.failErr = errors.New(descr)
|
|
||||||
return blob, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
|
||||||
// Per RFC, we cannot just send an error, we need to return JSON-structured
|
|
||||||
// value as a challenge and then after getting dummy response from the
|
|
||||||
// client stop the exchange.
|
|
||||||
if a.failErr != nil {
|
|
||||||
// Server libraries (go-smtp, go-imap) will not call Next on
|
|
||||||
// protocol-specific SASL cancel response ('*'). However, GS2 (and
|
|
||||||
// indirectly OAUTHBEARER) defines a protocol-independent way to do so
|
|
||||||
// using 0x01.
|
|
||||||
if len(response) != 1 && response[0] != 0x01 {
|
|
||||||
return nil, true, errors.New("unexpected response")
|
|
||||||
}
|
|
||||||
return nil, true, a.failErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.done {
|
|
||||||
err = ErrUnexpectedClientResponse
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate empty challenge.
|
|
||||||
if response == nil {
|
|
||||||
return []byte{}, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a.done = true
|
|
||||||
|
|
||||||
// Cut n,a=username,\x01host=...\x01auth=...
|
|
||||||
// into
|
|
||||||
// n
|
|
||||||
// a=username
|
|
||||||
// \x01host=...\x01auth=...\x01\x01
|
|
||||||
parts := bytes.SplitN(response, []byte{','}, 3)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return a.fail("Invalid response")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(parts[0], []byte{'n'}) {
|
|
||||||
return a.fail("Invalid response, missing 'n'")
|
|
||||||
}
|
|
||||||
opts := OAuthBearerOptions{}
|
|
||||||
if !bytes.HasPrefix(parts[1], []byte("a=")) {
|
|
||||||
return a.fail("Invalid response, missing 'a'")
|
|
||||||
}
|
|
||||||
opts.Username = string(bytes.TrimPrefix(parts[1], []byte("a=")))
|
|
||||||
|
|
||||||
// Cut \x01host=...\x01auth=...\x01\x01
|
|
||||||
// into
|
|
||||||
// *empty*
|
|
||||||
// host=...
|
|
||||||
// auth=...
|
|
||||||
// *empty*
|
|
||||||
//
|
|
||||||
// Note that this code does not do a lot of checks to make sure the input
|
|
||||||
// follows the exact format specified by RFC.
|
|
||||||
params := bytes.Split(parts[2], []byte{0x01})
|
|
||||||
for _, p := range params {
|
|
||||||
// Skip empty fields (one at start and end).
|
|
||||||
if len(p) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pParts := bytes.SplitN(p, []byte{'='}, 2)
|
|
||||||
if len(pParts) != 2 {
|
|
||||||
return a.fail("Invalid response, missing '='")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(pParts[0]) {
|
|
||||||
case "host":
|
|
||||||
opts.Host = string(pParts[1])
|
|
||||||
case "port":
|
|
||||||
port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return a.fail("Invalid response, malformed 'port' value")
|
|
||||||
}
|
|
||||||
opts.Port = int(port)
|
|
||||||
case "auth":
|
|
||||||
const prefix = "bearer "
|
|
||||||
strValue := string(pParts[1])
|
|
||||||
// Token type is case-insensitive.
|
|
||||||
if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
|
|
||||||
return a.fail("Unsupported token type")
|
|
||||||
}
|
|
||||||
opts.Token = strValue[len(prefix):]
|
|
||||||
default:
|
|
||||||
return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authzErr := a.authenticate(opts)
|
|
||||||
if authzErr != nil {
|
|
||||||
blob, err := json.Marshal(authzErr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // wtf
|
|
||||||
}
|
|
||||||
a.failErr = authzErr
|
|
||||||
return blob, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server {
|
|
||||||
return &oauthBearerServer{authenticate: auth}
|
|
||||||
}
|
|
||||||
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
@@ -1,77 +0,0 @@
|
|||||||
package sasl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The PLAIN mechanism name.
|
|
||||||
const Plain = "PLAIN"
|
|
||||||
|
|
||||||
type plainClient struct {
|
|
||||||
Identity string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *plainClient) Start() (mech string, ir []byte, err error) {
|
|
||||||
mech = "PLAIN"
|
|
||||||
ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *plainClient) Next(challenge []byte) (response []byte, err error) {
|
|
||||||
return nil, ErrUnexpectedServerChallenge
|
|
||||||
}
|
|
||||||
|
|
||||||
// A client implementation of the PLAIN authentication mechanism, as described
|
|
||||||
// in RFC 4616. Authorization identity may be left blank to indicate that it is
|
|
||||||
// the same as the username.
|
|
||||||
func NewPlainClient(identity, username, password string) Client {
|
|
||||||
return &plainClient{identity, username, password}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticates users with an identity, a username and a password. If the
|
|
||||||
// identity is left blank, it indicates that it is the same as the username.
|
|
||||||
// If identity is not empty and the server doesn't support it, an error must be
|
|
||||||
// returned.
|
|
||||||
type PlainAuthenticator func(identity, username, password string) error
|
|
||||||
|
|
||||||
type plainServer struct {
|
|
||||||
done bool
|
|
||||||
authenticate PlainAuthenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
|
||||||
if a.done {
|
|
||||||
err = ErrUnexpectedClientResponse
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No initial response, send an empty challenge
|
|
||||||
if response == nil {
|
|
||||||
return []byte{}, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a.done = true
|
|
||||||
|
|
||||||
parts := bytes.Split(response, []byte("\x00"))
|
|
||||||
if len(parts) != 3 {
|
|
||||||
err = errors.New("Invalid response")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
identity := string(parts[0])
|
|
||||||
username := string(parts[1])
|
|
||||||
password := string(parts[2])
|
|
||||||
|
|
||||||
err = a.authenticate(identity, username, password)
|
|
||||||
done = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A server implementation of the PLAIN authentication mechanism, as described
|
|
||||||
// in RFC 4616.
|
|
||||||
func NewPlainServer(authenticator PlainAuthenticator) Server {
|
|
||||||
return &plainServer{authenticate: authenticator}
|
|
||||||
}
|
|
||||||
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
@@ -1,45 +0,0 @@
|
|||||||
// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422.
|
|
||||||
package sasl
|
|
||||||
|
|
||||||
// Note:
|
|
||||||
// Most of this code was copied, with some modifications, from net/smtp. It
|
|
||||||
// would be better if Go provided a standard package (e.g. crypto/sasl) that
|
|
||||||
// could be shared by SMTP, IMAP, and other packages.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Common SASL errors.
|
|
||||||
var (
|
|
||||||
ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response")
|
|
||||||
ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client interface to perform challenge-response authentication.
|
|
||||||
type Client interface {
|
|
||||||
// Begins SASL authentication with the server. It returns the
|
|
||||||
// authentication mechanism name and "initial response" data (if required by
|
|
||||||
// the selected mechanism). A non-nil error causes the client to abort the
|
|
||||||
// authentication attempt.
|
|
||||||
//
|
|
||||||
// A nil ir value is different from a zero-length value. The nil value
|
|
||||||
// indicates that the selected mechanism does not use an initial response,
|
|
||||||
// while a zero-length value indicates an empty initial response, which must
|
|
||||||
// be sent to the server.
|
|
||||||
Start() (mech string, ir []byte, err error)
|
|
||||||
|
|
||||||
// Continues challenge-response authentication. A non-nil error causes
|
|
||||||
// the client to abort the authentication attempt.
|
|
||||||
Next(challenge []byte) (response []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server interface to perform challenge-response authentication.
|
|
||||||
type Server interface {
|
|
||||||
// Begins or continues challenge-response authentication. If the client
|
|
||||||
// supplies an initial response, response is non-nil.
|
|
||||||
//
|
|
||||||
// If the authentication is finished, done is set to true. If the
|
|
||||||
// authentication has failed, an error is returned.
|
|
||||||
Next(response []byte) (challenge []byte, done bool, err error)
|
|
||||||
}
|
|
||||||
3
vendor/golang.org/x/text/AUTHORS
generated
vendored
3
vendor/golang.org/x/text/AUTHORS
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# This source code refers to The Go Authors for copyright purposes.
|
|
||||||
# The master list of authors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/AUTHORS.
|
|
||||||
3
vendor/golang.org/x/text/CONTRIBUTORS
generated
vendored
3
vendor/golang.org/x/text/CONTRIBUTORS
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# This source code was written by the Go contributors.
|
|
||||||
# The master list of contributors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
||||||
27
vendor/golang.org/x/text/LICENSE
generated
vendored
27
vendor/golang.org/x/text/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
22
vendor/golang.org/x/text/PATENTS
generated
vendored
22
vendor/golang.org/x/text/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
||||||
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
@@ -1,335 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package encoding defines an interface for character encodings, such as Shift
|
|
||||||
// JIS and Windows 1252, that can convert to and from UTF-8.
|
|
||||||
//
|
|
||||||
// Encoding implementations are provided in other packages, such as
|
|
||||||
// golang.org/x/text/encoding/charmap and
|
|
||||||
// golang.org/x/text/encoding/japanese.
|
|
||||||
package encoding // import "golang.org/x/text/encoding"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding/internal/identifier"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - There seems to be some inconsistency in when decoders return errors
|
|
||||||
// and when not. Also documentation seems to suggest they shouldn't return
|
|
||||||
// errors at all (except for UTF-16).
|
|
||||||
// - Encoders seem to rely on or at least benefit from the input being in NFC
|
|
||||||
// normal form. Perhaps add an example how users could prepare their output.
|
|
||||||
|
|
||||||
// Encoding is a character set encoding that can be transformed to and from
|
|
||||||
// UTF-8.
|
|
||||||
type Encoding interface {
|
|
||||||
// NewDecoder returns a Decoder.
|
|
||||||
NewDecoder() *Decoder
|
|
||||||
|
|
||||||
// NewEncoder returns an Encoder.
|
|
||||||
NewEncoder() *Encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Decoder converts bytes to UTF-8. It implements transform.Transformer.
|
|
||||||
//
|
|
||||||
// Transforming source bytes that are not of that encoding will not result in an
|
|
||||||
// error per se. Each byte that cannot be transcoded will be represented in the
|
|
||||||
// output by the UTF-8 encoding of '\uFFFD', the replacement rune.
|
|
||||||
type Decoder struct {
|
|
||||||
transform.Transformer
|
|
||||||
|
|
||||||
// This forces external creators of Decoders to use names in struct
|
|
||||||
// initializers, allowing for future extendibility without having to break
|
|
||||||
// code.
|
|
||||||
_ struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes converts the given encoded bytes to UTF-8. It returns the converted
|
|
||||||
// bytes or nil, err if any error occurred.
|
|
||||||
func (d *Decoder) Bytes(b []byte) ([]byte, error) {
|
|
||||||
b, _, err := transform.Bytes(d, b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String converts the given encoded string to UTF-8. It returns the converted
|
|
||||||
// string or "", err if any error occurred.
|
|
||||||
func (d *Decoder) String(s string) (string, error) {
|
|
||||||
s, _, err := transform.String(d, s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader wraps another Reader to decode its bytes.
|
|
||||||
//
|
|
||||||
// The Decoder may not be used for any other operation as long as the returned
|
|
||||||
// Reader is in use.
|
|
||||||
func (d *Decoder) Reader(r io.Reader) io.Reader {
|
|
||||||
return transform.NewReader(r, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Encoder converts bytes from UTF-8. It implements transform.Transformer.
|
|
||||||
//
|
|
||||||
// Each rune that cannot be transcoded will result in an error. In this case,
|
|
||||||
// the transform will consume all source byte up to, not including the offending
|
|
||||||
// rune. Transforming source bytes that are not valid UTF-8 will be replaced by
|
|
||||||
// `\uFFFD`. To return early with an error instead, use transform.Chain to
|
|
||||||
// preprocess the data with a UTF8Validator.
|
|
||||||
type Encoder struct {
|
|
||||||
transform.Transformer
|
|
||||||
|
|
||||||
// This forces external creators of Encoders to use names in struct
|
|
||||||
// initializers, allowing for future extendibility without having to break
|
|
||||||
// code.
|
|
||||||
_ struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes converts bytes from UTF-8. It returns the converted bytes or nil, err if
|
|
||||||
// any error occurred.
|
|
||||||
func (e *Encoder) Bytes(b []byte) ([]byte, error) {
|
|
||||||
b, _, err := transform.Bytes(e, b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String converts a string from UTF-8. It returns the converted string or
|
|
||||||
// "", err if any error occurred.
|
|
||||||
func (e *Encoder) String(s string) (string, error) {
|
|
||||||
s, _, err := transform.String(e, s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer wraps another Writer to encode its UTF-8 output.
|
|
||||||
//
|
|
||||||
// The Encoder may not be used for any other operation as long as the returned
|
|
||||||
// Writer is in use.
|
|
||||||
func (e *Encoder) Writer(w io.Writer) io.Writer {
|
|
||||||
return transform.NewWriter(w, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASCIISub is the ASCII substitute character, as recommended by
|
|
||||||
// https://unicode.org/reports/tr36/#Text_Comparison
|
|
||||||
const ASCIISub = '\x1a'
|
|
||||||
|
|
||||||
// Nop is the nop encoding. Its transformed bytes are the same as the source
|
|
||||||
// bytes; it does not replace invalid UTF-8 sequences.
|
|
||||||
var Nop Encoding = nop{}
|
|
||||||
|
|
||||||
type nop struct{}
|
|
||||||
|
|
||||||
func (nop) NewDecoder() *Decoder {
|
|
||||||
return &Decoder{Transformer: transform.Nop}
|
|
||||||
}
|
|
||||||
func (nop) NewEncoder() *Encoder {
|
|
||||||
return &Encoder{Transformer: transform.Nop}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replacement is the replacement encoding. Decoding from the replacement
|
|
||||||
// encoding yields a single '\uFFFD' replacement rune. Encoding from UTF-8 to
|
|
||||||
// the replacement encoding yields the same as the source bytes except that
|
|
||||||
// invalid UTF-8 is converted to '\uFFFD'.
|
|
||||||
//
|
|
||||||
// It is defined at http://encoding.spec.whatwg.org/#replacement
|
|
||||||
var Replacement Encoding = replacement{}
|
|
||||||
|
|
||||||
type replacement struct{}
|
|
||||||
|
|
||||||
func (replacement) NewDecoder() *Decoder {
|
|
||||||
return &Decoder{Transformer: replacementDecoder{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (replacement) NewEncoder() *Encoder {
|
|
||||||
return &Encoder{Transformer: replacementEncoder{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (replacement) ID() (mib identifier.MIB, other string) {
|
|
||||||
return identifier.Replacement, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type replacementDecoder struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (replacementDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
if len(dst) < 3 {
|
|
||||||
return 0, 0, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
if atEOF {
|
|
||||||
const fffd = "\ufffd"
|
|
||||||
dst[0] = fffd[0]
|
|
||||||
dst[1] = fffd[1]
|
|
||||||
dst[2] = fffd[2]
|
|
||||||
nDst = 3
|
|
||||||
}
|
|
||||||
return nDst, len(src), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type replacementEncoder struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (replacementEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
r, size := rune(0), 0
|
|
||||||
|
|
||||||
for ; nSrc < len(src); nSrc += size {
|
|
||||||
r = rune(src[nSrc])
|
|
||||||
|
|
||||||
// Decode a 1-byte rune.
|
|
||||||
if r < utf8.RuneSelf {
|
|
||||||
size = 1
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Decode a multi-byte rune.
|
|
||||||
r, size = utf8.DecodeRune(src[nSrc:])
|
|
||||||
if size == 1 {
|
|
||||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
|
||||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
|
||||||
// full character yet.
|
|
||||||
if !atEOF && !utf8.FullRune(src[nSrc:]) {
|
|
||||||
err = transform.ErrShortSrc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r = '\ufffd'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nDst+utf8.RuneLen(r) > len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nDst += utf8.EncodeRune(dst[nDst:], r)
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLEscapeUnsupported wraps encoders to replace source runes outside the
|
|
||||||
// repertoire of the destination encoding with HTML escape sequences.
|
|
||||||
//
|
|
||||||
// This wrapper exists to comply to URL and HTML forms requiring a
|
|
||||||
// non-terminating legacy encoder. The produced sequences may lead to data
|
|
||||||
// loss as they are indistinguishable from legitimate input. To avoid this
|
|
||||||
// issue, use UTF-8 encodings whenever possible.
|
|
||||||
func HTMLEscapeUnsupported(e *Encoder) *Encoder {
|
|
||||||
return &Encoder{Transformer: &errorHandler{e, errorToHTML}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceUnsupported wraps encoders to replace source runes outside the
|
|
||||||
// repertoire of the destination encoding with an encoding-specific
|
|
||||||
// replacement.
|
|
||||||
//
|
|
||||||
// This wrapper is only provided for backwards compatibility and legacy
|
|
||||||
// handling. Its use is strongly discouraged. Use UTF-8 whenever possible.
|
|
||||||
func ReplaceUnsupported(e *Encoder) *Encoder {
|
|
||||||
return &Encoder{Transformer: &errorHandler{e, errorToReplacement}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorHandler struct {
|
|
||||||
*Encoder
|
|
||||||
handler func(dst []byte, r rune, err repertoireError) (n int, ok bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: consider making this error public in some form.
|
|
||||||
type repertoireError interface {
|
|
||||||
Replacement() byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF)
|
|
||||||
for err != nil {
|
|
||||||
rerr, ok := err.(repertoireError)
|
|
||||||
if !ok {
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
r, sz := utf8.DecodeRune(src[nSrc:])
|
|
||||||
n, ok := h.handler(dst[nDst:], r, rerr)
|
|
||||||
if !ok {
|
|
||||||
return nDst, nSrc, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
nDst += n
|
|
||||||
if nSrc += sz; nSrc < len(src) {
|
|
||||||
var dn, sn int
|
|
||||||
dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF)
|
|
||||||
nDst += dn
|
|
||||||
nSrc += sn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nDst, nSrc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorToHTML(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
|
||||||
buf := [8]byte{}
|
|
||||||
b := strconv.AppendUint(buf[:0], uint64(r), 10)
|
|
||||||
if n = len(b) + len("&#;"); n >= len(dst) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
dst[0] = '&'
|
|
||||||
dst[1] = '#'
|
|
||||||
dst[copy(dst[2:], b)+2] = ';'
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorToReplacement(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
|
||||||
if len(dst) == 0 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
dst[0] = err.Replacement()
|
|
||||||
return 1, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrInvalidUTF8 means that a transformer encountered invalid UTF-8.
|
|
||||||
var ErrInvalidUTF8 = errors.New("encoding: invalid UTF-8")
|
|
||||||
|
|
||||||
// UTF8Validator is a transformer that returns ErrInvalidUTF8 on the first
|
|
||||||
// input byte that is not valid UTF-8.
|
|
||||||
var UTF8Validator transform.Transformer = utf8Validator{}
|
|
||||||
|
|
||||||
type utf8Validator struct{ transform.NopResetter }
|
|
||||||
|
|
||||||
func (utf8Validator) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
n := len(src)
|
|
||||||
if n > len(dst) {
|
|
||||||
n = len(dst)
|
|
||||||
}
|
|
||||||
for i := 0; i < n; {
|
|
||||||
if c := src[i]; c < utf8.RuneSelf {
|
|
||||||
dst[i] = c
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, size := utf8.DecodeRune(src[i:])
|
|
||||||
if size == 1 {
|
|
||||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
|
||||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
|
||||||
// full character yet.
|
|
||||||
err = ErrInvalidUTF8
|
|
||||||
if !atEOF && !utf8.FullRune(src[i:]) {
|
|
||||||
err = transform.ErrShortSrc
|
|
||||||
}
|
|
||||||
return i, i, err
|
|
||||||
}
|
|
||||||
if i+size > len(dst) {
|
|
||||||
return i, i, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
for ; size > 0; size-- {
|
|
||||||
dst[i] = src[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(src) > len(dst) {
|
|
||||||
err = transform.ErrShortDst
|
|
||||||
}
|
|
||||||
return n, n, err
|
|
||||||
}
|
|
||||||
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
@@ -1,81 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
// Package identifier defines the contract between implementations of Encoding
|
|
||||||
// and Index by defining identifiers that uniquely identify standardized coded
|
|
||||||
// character sets (CCS) and character encoding schemes (CES), which we will
|
|
||||||
// together refer to as encodings, for which Encoding implementations provide
|
|
||||||
// converters to and from UTF-8. This package is typically only of concern to
|
|
||||||
// implementers of Indexes and Encodings.
|
|
||||||
//
|
|
||||||
// One part of the identifier is the MIB code, which is defined by IANA and
|
|
||||||
// uniquely identifies a CCS or CES. Each code is associated with data that
|
|
||||||
// references authorities, official documentation as well as aliases and MIME
|
|
||||||
// names.
|
|
||||||
//
|
|
||||||
// Not all CESs are covered by the IANA registry. The "other" string that is
|
|
||||||
// returned by ID can be used to identify other character sets or versions of
|
|
||||||
// existing ones.
|
|
||||||
//
|
|
||||||
// It is recommended that each package that provides a set of Encodings provide
|
|
||||||
// the All and Common variables to reference all supported encodings and
|
|
||||||
// commonly used subset. This allows Index implementations to include all
|
|
||||||
// available encodings without explicitly referencing or knowing about them.
|
|
||||||
package identifier
|
|
||||||
|
|
||||||
// Note: this package is internal, but could be made public if there is a need
|
|
||||||
// for writing third-party Indexes and Encodings.
|
|
||||||
|
|
||||||
// References:
|
|
||||||
// - http://source.icu-project.org/repos/icu/icu/trunk/source/data/mappings/convrtrs.txt
|
|
||||||
// - http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
|
||||||
// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
|
|
||||||
// - http://www.ietf.org/rfc/rfc2978.txt
|
|
||||||
// - https://www.unicode.org/reports/tr22/
|
|
||||||
// - http://www.w3.org/TR/encoding/
|
|
||||||
// - https://encoding.spec.whatwg.org/
|
|
||||||
// - https://encoding.spec.whatwg.org/encodings.json
|
|
||||||
// - https://tools.ietf.org/html/rfc6657#section-5
|
|
||||||
|
|
||||||
// Interface can be implemented by Encodings to define the CCS or CES for which
|
|
||||||
// it implements conversions.
|
|
||||||
type Interface interface {
|
|
||||||
// ID returns an encoding identifier. Exactly one of the mib and other
|
|
||||||
// values should be non-zero.
|
|
||||||
//
|
|
||||||
// In the usual case it is only necessary to indicate the MIB code. The
|
|
||||||
// other string can be used to specify encodings for which there is no MIB,
|
|
||||||
// such as "x-mac-dingbat".
|
|
||||||
//
|
|
||||||
// The other string may only contain the characters a-z, A-Z, 0-9, - and _.
|
|
||||||
ID() (mib MIB, other string)
|
|
||||||
|
|
||||||
// NOTE: the restrictions on the encoding are to allow extending the syntax
|
|
||||||
// with additional information such as versions, vendors and other variants.
|
|
||||||
}
|
|
||||||
|
|
||||||
// A MIB identifies an encoding. It is derived from the IANA MIB codes and adds
|
|
||||||
// some identifiers for some encodings that are not covered by the IANA
|
|
||||||
// standard.
|
|
||||||
//
|
|
||||||
// See http://www.iana.org/assignments/ianacharset-mib.
|
|
||||||
type MIB uint16
|
|
||||||
|
|
||||||
// These additional MIB types are not defined in IANA. They are added because
|
|
||||||
// they are common and defined within the text repo.
|
|
||||||
const (
|
|
||||||
// Unofficial marks the start of encodings not registered by IANA.
|
|
||||||
Unofficial MIB = 10000 + iota
|
|
||||||
|
|
||||||
// Replacement is the WhatWG replacement encoding.
|
|
||||||
Replacement
|
|
||||||
|
|
||||||
// XUserDefined is the code for x-user-defined.
|
|
||||||
XUserDefined
|
|
||||||
|
|
||||||
// MacintoshCyrillic is the code for x-mac-cyrillic.
|
|
||||||
MacintoshCyrillic
|
|
||||||
)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user