go mod
This commit is contained in:
4
go.mod
4
go.mod
@@ -5,9 +5,13 @@ go 1.17
|
|||||||
require (
|
require (
|
||||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e
|
||||||
github.com/emersion/go-imap v1.2.0
|
github.com/emersion/go-imap v1.2.0
|
||||||
|
local/args v0.0.0-00010101000000-000000000000
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace local/args => ../../args
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -10,3 +10,7 @@ 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
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=
|
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
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
Normal file
1
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.swp
|
||||||
4
vendor/github.com/taknb2nch/go-pop3/LICENSE → vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
Executable file → Normal file
4
vendor/github.com/taknb2nch/go-pop3/LICENSE → vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
Executable file → Normal file
@@ -1,6 +1,4 @@
|
|||||||
The MIT License (MIT)
|
Copyright (c) 2012 Scott Lawrence <bytbox@gmail.com>
|
||||||
|
|
||||||
Copyright (c) 2014 Takanobu Hagino
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
Normal file
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// 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
Normal file
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
Normal file
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
/client.go
|
||||||
|
/server.go
|
||||||
|
coverage.txt
|
||||||
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 The Go-IMAP Authors
|
||||||
|
Copyright (c) 2016 emersion
|
||||||
|
Copyright (c) 2016 Proton Technologies AG
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# 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
Normal file
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,689 @@
|
|||||||
|
// 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
Normal file
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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
Normal file
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
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
Normal file
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
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
Normal file
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
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
Normal file
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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
Normal file
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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
Normal file
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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
Normal file
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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
Normal file
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Capability is a CAPABILITY command, as defined in RFC 3501 section 6.1.1.
|
||||||
|
type Capability struct{}
|
||||||
|
|
||||||
|
func (c *Capability) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "CAPABILITY",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Capability) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check is a CHECK command, as defined in RFC 3501 section 6.4.1.
|
||||||
|
type Check struct{}
|
||||||
|
|
||||||
|
func (cmd *Check) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "CHECK",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Check) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close is a CLOSE command, as defined in RFC 3501 section 6.4.2.
|
||||||
|
type Close struct{}
|
||||||
|
|
||||||
|
func (cmd *Close) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "CLOSE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Close) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package commands implements IMAP commands defined in RFC 3501.
|
||||||
|
package commands
|
||||||
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/utf7"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy is a COPY command, as defined in RFC 3501 section 6.4.7.
|
||||||
|
type Copy struct {
|
||||||
|
SeqSet *imap.SeqSet
|
||||||
|
Mailbox string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Copy) Command() *imap.Command {
|
||||||
|
mailbox, _ := utf7.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
Normal file
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/utf7"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create is a CREATE command, as defined in RFC 3501 section 6.3.3.
|
||||||
|
type Create struct {
|
||||||
|
Mailbox string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Create) Command() *imap.Command {
|
||||||
|
mailbox, _ := utf7.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
Normal file
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/utf7"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete is a DELETE command, as defined in RFC 3501 section 6.3.3.
|
||||||
|
type Delete struct {
|
||||||
|
Mailbox string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Delete) Command() *imap.Command {
|
||||||
|
mailbox, _ := utf7.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
Normal file
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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
Normal file
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expunge is an EXPUNGE command, as defined in RFC 3501 section 6.4.3.
|
||||||
|
type Expunge struct{}
|
||||||
|
|
||||||
|
func (cmd *Expunge) Command() *imap.Command {
|
||||||
|
return &imap.Command{Name: "EXPUNGE"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Expunge) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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
Normal file
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
Normal file
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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
Normal file
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login is a LOGIN command, as defined in RFC 3501 section 6.2.2.
|
||||||
|
type Login struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Login) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "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
Normal file
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logout is a LOGOUT command, as defined in RFC 3501 section 6.1.3.
|
||||||
|
type Logout struct{}
|
||||||
|
|
||||||
|
func (c *Logout) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "LOGOUT",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Logout) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
Normal file
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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
Normal file
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Noop is a NOOP command, as defined in RFC 3501 section 6.1.2.
|
||||||
|
type Noop struct{}
|
||||||
|
|
||||||
|
func (c *Noop) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "NOOP",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Noop) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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
Normal file
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search is a SEARCH command, as defined in RFC 3501 section 6.4.4.
|
||||||
|
type Search struct {
|
||||||
|
Charset string
|
||||||
|
Criteria *imap.SearchCriteria
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Search) Command() *imap.Command {
|
||||||
|
var args []interface{}
|
||||||
|
if cmd.Charset != "" {
|
||||||
|
args = append(args, 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
Normal file
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/utf7"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Select is a SELECT command, as defined in RFC 3501 section 6.3.1. If ReadOnly
|
||||||
|
// is set to true, the EXAMINE command will be used instead.
|
||||||
|
type Select struct {
|
||||||
|
Mailbox string
|
||||||
|
ReadOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Select) Command() *imap.Command {
|
||||||
|
name := "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
Normal file
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartTLS is a STARTTLS command, as defined in RFC 3501 section 6.2.1.
|
||||||
|
type StartTLS struct{}
|
||||||
|
|
||||||
|
func (cmd *StartTLS) Command() *imap.Command {
|
||||||
|
return &imap.Command{
|
||||||
|
Name: "STARTTLS",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *StartTLS) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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
Normal file
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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
Normal file
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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
Normal file
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uid is a UID command, as defined in RFC 3501 section 6.4.8. It wraps another
|
||||||
|
// command (e.g. wrapping a Fetch command will result in a UID FETCH).
|
||||||
|
type Uid struct {
|
||||||
|
Cmd imap.Commander
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Uid) Command() *imap.Command {
|
||||||
|
inner := cmd.Cmd.Command()
|
||||||
|
|
||||||
|
args := []interface{}{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
Normal file
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
Normal file
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
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
Normal file
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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
Normal file
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// 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
Normal file
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package imap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A literal, as defined in RFC 3501 section 4.3.
|
||||||
|
type Literal interface {
|
||||||
|
io.Reader
|
||||||
|
|
||||||
|
// Len returns the number of bytes of the literal.
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package imap
|
||||||
|
|
||||||
|
// Logger is the behaviour used by server/client to
|
||||||
|
// report errors for accepting connections and unexpected behavior from handlers.
|
||||||
|
type Logger interface {
|
||||||
|
Printf(format string, v ...interface{})
|
||||||
|
Println(v ...interface{})
|
||||||
|
}
|
||||||
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
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
Normal file
1187
vendor/github.com/emersion/go-imap/message.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
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
Normal file
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
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
Normal file
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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
Normal file
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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
Normal file
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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
Normal file
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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
Normal file
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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
Normal file
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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
Normal file
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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
Normal file
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// 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
Normal file
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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
Normal file
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
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
Normal file
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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
Normal file
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
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
Normal file
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
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
Normal file
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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
Normal file
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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
Normal file
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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
Normal file
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// 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
Normal file
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
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
Normal file
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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
Normal file
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 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
Normal file
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
Normal file
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 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
Normal file
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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
Normal file
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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
Normal file
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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
Normal file
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
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
Normal file
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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
Normal file
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
115
vendor/github.com/taknb2nch/go-pop3/README.md
generated
vendored
115
vendor/github.com/taknb2nch/go-pop3/README.md
generated
vendored
@@ -1,115 +0,0 @@
|
|||||||
go-pop3
|
|
||||||
==========
|
|
||||||
|
|
||||||
This is a simple POP3 client package in Go language.
|
|
||||||
|
|
||||||
##Usage
|
|
||||||
```go
|
|
||||||
if err := pop3.ReceiveMail(address, user, pass,
|
|
||||||
func(number int, uid, data string, err error) (bool, error) {
|
|
||||||
log.Printf("%d, %s\n", number, uid)
|
|
||||||
|
|
||||||
// implement your own logic here
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Fatalf("%v\n", err)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
or use the method that implements the command
|
|
||||||
```go
|
|
||||||
client, err := pop3.Dial(address)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
client.Quit()
|
|
||||||
client.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = client.User(user); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.Pass(pass); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int
|
|
||||||
var size uint64
|
|
||||||
|
|
||||||
if count, size, err = client.Stat(); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Count: %d, Size: %d\n", count, size)
|
|
||||||
|
|
||||||
if count, size, err = client.List(6); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Number: %d, Size: %d\n", count, size)
|
|
||||||
|
|
||||||
var mis []pop3.MessageInfo
|
|
||||||
|
|
||||||
if mis, err = client.ListAll(); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mi := range mis {
|
|
||||||
log.Printf("Number: %d, Size: %d\n", mi.Number, mi.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
var number int
|
|
||||||
var uid string
|
|
||||||
|
|
||||||
if number, uid, err = client.Uidl(6); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Number: %d, Uid: %s\n", number, uid)
|
|
||||||
|
|
||||||
if mis, err = client.UidlAll(); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mi := range mis {
|
|
||||||
log.Printf("Number: %d, Uid: %s\n", mi.Number, mi.Uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
var content string
|
|
||||||
|
|
||||||
if content, err = client.Retr(8); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Content:\n%s\n", content)
|
|
||||||
|
|
||||||
if err = client.Dele(6); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.Noop(); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.Rset(); err != nil {
|
|
||||||
log.Printf("Error: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##License
|
|
||||||
[MIT License](https://github.com/taknb2nch/go-pop3/blob/master/LICENSE)
|
|
||||||
400
vendor/github.com/taknb2nch/go-pop3/pop3.go
generated
vendored
400
vendor/github.com/taknb2nch/go-pop3/pop3.go
generated
vendored
@@ -1,400 +0,0 @@
|
|||||||
// Package pop3 provides simple POP3 client.
|
|
||||||
package pop3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
EOF = errors.New("skip the all mail remaining")
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageInfo has Number, Size, and Uid fields,
|
|
||||||
// and used as a return value of ListAll and UidlAll.
|
|
||||||
// When used as the return value of the method ListAll,
|
|
||||||
// MessageInfo contain only the Number and Size values.
|
|
||||||
// When used as the return value of the method UidlAll,
|
|
||||||
// MessageInfo contain only the Number and Uid values.
|
|
||||||
type MessageInfo struct {
|
|
||||||
Number int
|
|
||||||
Size uint64
|
|
||||||
Uid string
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Client represents a client connection to an POP server.
|
|
||||||
type Client struct {
|
|
||||||
// Text is the pop3.Conn used by the Client.
|
|
||||||
Text *Conn
|
|
||||||
// keep a reference to the connection so it can be used to create a TLS
|
|
||||||
// connection later
|
|
||||||
conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial returns a new Client connected to an POP server at addr.
|
|
||||||
// The addr must include a port number.
|
|
||||||
func Dial(addr string) (*Client, error) {
|
|
||||||
conn, err := net.Dial("tcp", addr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewClient(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a new Client using an existing connection.
|
|
||||||
func NewClient(conn net.Conn) (*Client, error) {
|
|
||||||
text := NewConn(conn)
|
|
||||||
|
|
||||||
_, err := text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{Text: text, conn: conn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// User issues a USER command to the server using the provided user name.
|
|
||||||
func (c *Client) User(user string) error {
|
|
||||||
return c.cmdSimple("USER %s", user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass issues a PASS command to the server using the provided password.
|
|
||||||
func (c *Client) Pass(pass string) error {
|
|
||||||
return c.cmdSimple("PASS %s", pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat issues a STAT command to the server
|
|
||||||
// and returns mail count and total size.
|
|
||||||
func (c *Client) Stat() (int, uint64, error) {
|
|
||||||
return c.cmdStatOrList("STAT", "STAT")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retr issues a RETR command to the server using the provided mail number
|
|
||||||
// and returns mail data.
|
|
||||||
func (c *Client) Retr(number int) (string, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = c.Text.WriteLine("RETR %d", number)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.Text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Text.ReadToPeriod()
|
|
||||||
}
|
|
||||||
|
|
||||||
// List issues a LIST command to the server using the provided mail number
|
|
||||||
// and returns mail number and size.
|
|
||||||
func (c *Client) List(number int) (int, uint64, error) {
|
|
||||||
return c.cmdStatOrList("LIST", "LIST %d", number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List issues a LIST command to the server
|
|
||||||
// and returns array of MessageInfo.
|
|
||||||
func (c *Client) ListAll() ([]MessageInfo, error) {
|
|
||||||
list := make([]MessageInfo, 0)
|
|
||||||
|
|
||||||
err := c.cmdReadLines("LIST", func(line string) error {
|
|
||||||
number, size, err := c.convertNumberAndSize(line)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
list = append(list, MessageInfo{Number: number, Size: size})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uidl issues a UIDL command to the server using the provided mail number
|
|
||||||
// and returns mail number and unique id.
|
|
||||||
func (c *Client) Uidl(number int) (int, string, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = c.Text.WriteLine("UIDL %d", number)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg string
|
|
||||||
|
|
||||||
msg, err = c.Text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var val int
|
|
||||||
var uid string
|
|
||||||
|
|
||||||
val, uid, err = c.convertNumberAndUid(msg)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, uid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uidl issues a UIDL command to the server
|
|
||||||
// and returns array of MessageInfo.
|
|
||||||
func (c *Client) UidlAll() ([]MessageInfo, error) {
|
|
||||||
list := make([]MessageInfo, 0)
|
|
||||||
|
|
||||||
err := c.cmdReadLines("UIDL", func(line string) error {
|
|
||||||
number, uid, err := c.convertNumberAndUid(line)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
list = append(list, MessageInfo{Number: number, Uid: uid})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dele issues a DELE command to the server using the provided mail number.
|
|
||||||
func (c *Client) Dele(number int) error {
|
|
||||||
return c.cmdSimple("DELE %d", number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noop issues a NOOP command to the server.
|
|
||||||
func (c *Client) Noop() error {
|
|
||||||
return c.cmdSimple("NOOP")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rset issues a RSET command to the server.
|
|
||||||
func (c *Client) Rset() error {
|
|
||||||
return c.cmdSimple("RSET")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quit issues a QUIT command to the server.
|
|
||||||
func (c *Client) Quit() error {
|
|
||||||
return c.cmdSimple("QUIT")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveMail connects to the server at addr,
|
|
||||||
// and authenticates with user and pass,
|
|
||||||
// and calling receiveFn for each mail.
|
|
||||||
func ReceiveMail(addr, user, pass string, receiveFn ReceiveMailFunc) error {
|
|
||||||
c, err := Dial(addr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil && err != EOF {
|
|
||||||
c.Rset()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
c.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = c.User(user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.Pass(pass); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var mis []MessageInfo
|
|
||||||
|
|
||||||
if mis, err = c.UidlAll(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mi := range mis {
|
|
||||||
var data string
|
|
||||||
|
|
||||||
data, err = c.Retr(mi.Number)
|
|
||||||
|
|
||||||
del, err := receiveFn(mi.Number, mi.Uid, data, err)
|
|
||||||
|
|
||||||
if err != nil && err != EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if del {
|
|
||||||
if err = c.Dele(mi.Number); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveMailFunc is the type of the function called for each mail.
|
|
||||||
// Its arguments are mail's number, uid, data, and mail receiving error.
|
|
||||||
// if this function returns false value, the mail will be deleted,
|
|
||||||
// if its returns EOF, skip the all mail of remaining.
|
|
||||||
// (after deleting mail, if necessary)
|
|
||||||
type ReceiveMailFunc func(number int, uid, data string, err error) (bool, error)
|
|
||||||
|
|
||||||
func (c *Client) cmdSimple(format string, args ...interface{}) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = c.Text.WriteLine(format, args...)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.Text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) cmdStatOrList(name, format string, args ...interface{}) (int, uint64, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = c.Text.WriteLine(format, args...)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg string
|
|
||||||
|
|
||||||
msg, err = c.Text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.Split(msg, " ")
|
|
||||||
|
|
||||||
if len(s) < 2 {
|
|
||||||
return 0, 0, ResponseError(fmt.Sprintf("invalid response format: %s", msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
var val int
|
|
||||||
var size uint64
|
|
||||||
|
|
||||||
val, size, err = c.convertNumberAndSize(msg)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) cmdReadLines(cmnd string, lineFn lineFunc) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = c.Text.WriteLine(cmnd)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.Text.ReadResponse()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines []string
|
|
||||||
|
|
||||||
lines, err = c.Text.ReadLines()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
err = lineFn(line)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type lineFunc func(line string) error
|
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
return c.Text.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) convertNumberAndSize(line string) (int, uint64, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
s := strings.Split(line, " ")
|
|
||||||
|
|
||||||
if len(s) < 2 {
|
|
||||||
return 0, 0, errors.New(fmt.Sprintf("the length of the array is less than 2: %s", line))
|
|
||||||
}
|
|
||||||
|
|
||||||
var val int
|
|
||||||
var size uint64
|
|
||||||
|
|
||||||
if val, err = strconv.Atoi(s[0]); err != nil {
|
|
||||||
return 0, 0, errors.New(fmt.Sprintf("can not convert element[0] to int type: %s", line))
|
|
||||||
}
|
|
||||||
|
|
||||||
if size, err = strconv.ParseUint(s[1], 10, 64); err != nil {
|
|
||||||
return 0, 0, errors.New(fmt.Sprintf("can not convert element[1] to uint64 type: %s", line))
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) convertNumberAndUid(line string) (int, string, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
s := strings.Split(line, " ")
|
|
||||||
|
|
||||||
if len(s) < 2 {
|
|
||||||
return 0, "", errors.New(fmt.Sprintf("the length of the array is less than 2: %s", line))
|
|
||||||
}
|
|
||||||
|
|
||||||
var val int
|
|
||||||
|
|
||||||
if val, err = strconv.Atoi(s[0]); err != nil {
|
|
||||||
return 0, "", errors.New(fmt.Sprintf("can not convert element[0] to int type: %s", line))
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, s[1], nil
|
|
||||||
}
|
|
||||||
175
vendor/github.com/taknb2nch/go-pop3/pop3proto.go
generated
vendored
175
vendor/github.com/taknb2nch/go-pop3/pop3proto.go
generated
vendored
@@ -1,175 +0,0 @@
|
|||||||
package pop3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
_ "log"
|
|
||||||
"net/textproto"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A ResponseError describes a protocol violation such as an invalid response or a hung-up connection.
|
|
||||||
type ResponseError string
|
|
||||||
|
|
||||||
func (r ResponseError) Error() string {
|
|
||||||
return string(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Conn represents a textual network protocol connection for POP3.
|
|
||||||
type Conn struct {
|
|
||||||
Reader
|
|
||||||
Writer
|
|
||||||
conn io.ReadWriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn returns a new Conn using conn for I/O.
|
|
||||||
func NewConn(conn io.ReadWriteCloser) *Conn {
|
|
||||||
return &Conn{
|
|
||||||
Reader: Reader{R: textproto.NewReader(bufio.NewReader(conn))},
|
|
||||||
Writer: Writer{W: bufio.NewWriter(conn)},
|
|
||||||
conn: conn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Reader implements convenience methods
|
|
||||||
// for reading requests or responses from a text protocol network connection.
|
|
||||||
type Reader struct {
|
|
||||||
R *textproto.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a new Reader reading from r.
|
|
||||||
func NewReader(r *bufio.Reader) *Reader {
|
|
||||||
return &Reader{R: textproto.NewReader(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadLine reads a single line from r,
|
|
||||||
// eliding the final \n or \r\n from the returned string.
|
|
||||||
// This calls textproto.Reader.ReadLine simply.
|
|
||||||
func (r *Reader) ReadLine() (string, error) {
|
|
||||||
return r.R.ReadLine()
|
|
||||||
// for debug
|
|
||||||
// l, err := r.R.ReadLine()
|
|
||||||
// log.Printf("> %s\n", l)
|
|
||||||
// return l, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadLines reads a multiline until the last line of the only period,
|
|
||||||
// and returns a each line at slice.
|
|
||||||
// it does not contain last period.
|
|
||||||
func (r *Reader) ReadLines() ([]string, error) {
|
|
||||||
var lines []string
|
|
||||||
var line string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, err = r.R.ReadLine()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if line == "." {
|
|
||||||
return lines, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadToPeriod reads a multiline until the last line of the only period,
|
|
||||||
// and returns as a string.
|
|
||||||
// it does not contain last period.
|
|
||||||
func (r *Reader) ReadToPeriod() (string, error) {
|
|
||||||
lines, err := r.ReadLines()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(lines, "\r\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResponse reads a single line from r,
|
|
||||||
// and parses reponse.
|
|
||||||
// if the response is -ERR or has some other errors,
|
|
||||||
// it returns error.
|
|
||||||
func (r *Reader) ReadResponse() (string, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.parseResponse(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) parseResponse(line string) (string, error) {
|
|
||||||
s := strings.ToUpper(line)
|
|
||||||
|
|
||||||
if s == "+OK" {
|
|
||||||
return "", nil
|
|
||||||
} else if strings.HasPrefix(s, "+OK ") {
|
|
||||||
return line[4:], nil
|
|
||||||
} else if s == "-ERR" {
|
|
||||||
return "", ResponseError("")
|
|
||||||
} else if strings.HasPrefix(s, "-ERR ") {
|
|
||||||
return "", ResponseError(line[5:])
|
|
||||||
} else {
|
|
||||||
return "", ResponseError(fmt.Sprintf("unknown response: %s", line))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var crnl = []byte{'\r', '\n'}
|
|
||||||
|
|
||||||
// A Writer implements convenience methods
|
|
||||||
// for writing requests or responses to a text protocol network connection.
|
|
||||||
type Writer struct {
|
|
||||||
W *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter returns a new Writer writing to w.
|
|
||||||
func NewWriter(w *bufio.Writer) *Writer {
|
|
||||||
return &Writer{W: w}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteLine writes the formatted output followed by \r\n.
|
|
||||||
func (w *Writer) WriteLine(format string, args ...interface{}) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(w.W, format, args...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = w.W.Write(crnl); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.W.Flush()
|
|
||||||
|
|
||||||
// for debug
|
|
||||||
// var err error
|
|
||||||
|
|
||||||
// l := fmt.Sprintf(format, args...)
|
|
||||||
|
|
||||||
// if _, err = fmt.Fprint(w.W, l); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if _, err = w.W.Write(crnl); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err = w.W.Flush(); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// log.Printf("< %s\n", l)
|
|
||||||
|
|
||||||
// return nil
|
|
||||||
}
|
|
||||||
526
vendor/golang.org/x/crypto/cast5/cast5.go
generated
vendored
526
vendor/golang.org/x/crypto/cast5/cast5.go
generated
vendored
@@ -1,526 +0,0 @@
|
|||||||
// Copyright 2010 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 cast5 implements CAST5, as defined in RFC 2144. CAST5 is a common
|
|
||||||
// OpenPGP cipher.
|
|
||||||
package cast5 // import "golang.org/x/crypto/cast5"
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
const BlockSize = 8
|
|
||||||
const KeySize = 16
|
|
||||||
|
|
||||||
type Cipher struct {
|
|
||||||
masking [16]uint32
|
|
||||||
rotate [16]uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCipher(key []byte) (c *Cipher, err error) {
|
|
||||||
if len(key) != KeySize {
|
|
||||||
return nil, errors.New("CAST5: keys must be 16 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
c = new(Cipher)
|
|
||||||
c.keySchedule(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cipher) BlockSize() int {
|
|
||||||
return BlockSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cipher) Encrypt(dst, src []byte) {
|
|
||||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
|
|
||||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
|
|
||||||
|
|
||||||
l, r = r, l^f1(r, c.masking[0], c.rotate[0])
|
|
||||||
l, r = r, l^f2(r, c.masking[1], c.rotate[1])
|
|
||||||
l, r = r, l^f3(r, c.masking[2], c.rotate[2])
|
|
||||||
l, r = r, l^f1(r, c.masking[3], c.rotate[3])
|
|
||||||
|
|
||||||
l, r = r, l^f2(r, c.masking[4], c.rotate[4])
|
|
||||||
l, r = r, l^f3(r, c.masking[5], c.rotate[5])
|
|
||||||
l, r = r, l^f1(r, c.masking[6], c.rotate[6])
|
|
||||||
l, r = r, l^f2(r, c.masking[7], c.rotate[7])
|
|
||||||
|
|
||||||
l, r = r, l^f3(r, c.masking[8], c.rotate[8])
|
|
||||||
l, r = r, l^f1(r, c.masking[9], c.rotate[9])
|
|
||||||
l, r = r, l^f2(r, c.masking[10], c.rotate[10])
|
|
||||||
l, r = r, l^f3(r, c.masking[11], c.rotate[11])
|
|
||||||
|
|
||||||
l, r = r, l^f1(r, c.masking[12], c.rotate[12])
|
|
||||||
l, r = r, l^f2(r, c.masking[13], c.rotate[13])
|
|
||||||
l, r = r, l^f3(r, c.masking[14], c.rotate[14])
|
|
||||||
l, r = r, l^f1(r, c.masking[15], c.rotate[15])
|
|
||||||
|
|
||||||
dst[0] = uint8(r >> 24)
|
|
||||||
dst[1] = uint8(r >> 16)
|
|
||||||
dst[2] = uint8(r >> 8)
|
|
||||||
dst[3] = uint8(r)
|
|
||||||
dst[4] = uint8(l >> 24)
|
|
||||||
dst[5] = uint8(l >> 16)
|
|
||||||
dst[6] = uint8(l >> 8)
|
|
||||||
dst[7] = uint8(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cipher) Decrypt(dst, src []byte) {
|
|
||||||
l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
|
|
||||||
r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
|
|
||||||
|
|
||||||
l, r = r, l^f1(r, c.masking[15], c.rotate[15])
|
|
||||||
l, r = r, l^f3(r, c.masking[14], c.rotate[14])
|
|
||||||
l, r = r, l^f2(r, c.masking[13], c.rotate[13])
|
|
||||||
l, r = r, l^f1(r, c.masking[12], c.rotate[12])
|
|
||||||
|
|
||||||
l, r = r, l^f3(r, c.masking[11], c.rotate[11])
|
|
||||||
l, r = r, l^f2(r, c.masking[10], c.rotate[10])
|
|
||||||
l, r = r, l^f1(r, c.masking[9], c.rotate[9])
|
|
||||||
l, r = r, l^f3(r, c.masking[8], c.rotate[8])
|
|
||||||
|
|
||||||
l, r = r, l^f2(r, c.masking[7], c.rotate[7])
|
|
||||||
l, r = r, l^f1(r, c.masking[6], c.rotate[6])
|
|
||||||
l, r = r, l^f3(r, c.masking[5], c.rotate[5])
|
|
||||||
l, r = r, l^f2(r, c.masking[4], c.rotate[4])
|
|
||||||
|
|
||||||
l, r = r, l^f1(r, c.masking[3], c.rotate[3])
|
|
||||||
l, r = r, l^f3(r, c.masking[2], c.rotate[2])
|
|
||||||
l, r = r, l^f2(r, c.masking[1], c.rotate[1])
|
|
||||||
l, r = r, l^f1(r, c.masking[0], c.rotate[0])
|
|
||||||
|
|
||||||
dst[0] = uint8(r >> 24)
|
|
||||||
dst[1] = uint8(r >> 16)
|
|
||||||
dst[2] = uint8(r >> 8)
|
|
||||||
dst[3] = uint8(r)
|
|
||||||
dst[4] = uint8(l >> 24)
|
|
||||||
dst[5] = uint8(l >> 16)
|
|
||||||
dst[6] = uint8(l >> 8)
|
|
||||||
dst[7] = uint8(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyScheduleA [4][7]uint8
|
|
||||||
type keyScheduleB [4][5]uint8
|
|
||||||
|
|
||||||
// keyScheduleRound contains the magic values for a round of the key schedule.
|
|
||||||
// The keyScheduleA deals with the lines like:
|
|
||||||
// z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8]
|
|
||||||
// Conceptually, both x and z are in the same array, x first. The first
|
|
||||||
// element describes which word of this array gets written to and the
|
|
||||||
// second, which word gets read. So, for the line above, it's "4, 0", because
|
|
||||||
// it's writing to the first word of z, which, being after x, is word 4, and
|
|
||||||
// reading from the first word of x: word 0.
|
|
||||||
//
|
|
||||||
// Next are the indexes into the S-boxes. Now the array is treated as bytes. So
|
|
||||||
// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear
|
|
||||||
// that it's z that we're indexing.
|
|
||||||
//
|
|
||||||
// keyScheduleB deals with lines like:
|
|
||||||
// K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2]
|
|
||||||
// "K1" is ignored because key words are always written in order. So the five
|
|
||||||
// elements are the S-box indexes. They use the same form as in keyScheduleA,
|
|
||||||
// above.
|
|
||||||
|
|
||||||
type keyScheduleRound struct{}
|
|
||||||
type keySchedule []keyScheduleRound
|
|
||||||
|
|
||||||
var schedule = []struct {
|
|
||||||
a keyScheduleA
|
|
||||||
b keyScheduleB
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
keyScheduleA{
|
|
||||||
{4, 0, 0xd, 0xf, 0xc, 0xe, 0x8},
|
|
||||||
{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
|
|
||||||
{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
|
|
||||||
{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
|
|
||||||
},
|
|
||||||
keyScheduleB{
|
|
||||||
{16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2},
|
|
||||||
{16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6},
|
|
||||||
{16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9},
|
|
||||||
{16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
keyScheduleA{
|
|
||||||
{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
|
|
||||||
{1, 4, 0, 2, 1, 3, 16 + 2},
|
|
||||||
{2, 5, 7, 6, 5, 4, 16 + 1},
|
|
||||||
{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
|
|
||||||
},
|
|
||||||
keyScheduleB{
|
|
||||||
{3, 2, 0xc, 0xd, 8},
|
|
||||||
{1, 0, 0xe, 0xf, 0xd},
|
|
||||||
{7, 6, 8, 9, 3},
|
|
||||||
{5, 4, 0xa, 0xb, 7},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
keyScheduleA{
|
|
||||||
{4, 0, 0xd, 0xf, 0xc, 0xe, 8},
|
|
||||||
{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
|
|
||||||
{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
|
|
||||||
{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
|
|
||||||
},
|
|
||||||
keyScheduleB{
|
|
||||||
{16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9},
|
|
||||||
{16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc},
|
|
||||||
{16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2},
|
|
||||||
{16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
keyScheduleA{
|
|
||||||
{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
|
|
||||||
{1, 4, 0, 2, 1, 3, 16 + 2},
|
|
||||||
{2, 5, 7, 6, 5, 4, 16 + 1},
|
|
||||||
{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
|
|
||||||
},
|
|
||||||
keyScheduleB{
|
|
||||||
{8, 9, 7, 6, 3},
|
|
||||||
{0xa, 0xb, 5, 4, 7},
|
|
||||||
{0xc, 0xd, 3, 2, 8},
|
|
||||||
{0xe, 0xf, 1, 0, 0xd},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cipher) keySchedule(in []byte) {
|
|
||||||
var t [8]uint32
|
|
||||||
var k [32]uint32
|
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
j := i * 4
|
|
||||||
t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3])
|
|
||||||
}
|
|
||||||
|
|
||||||
x := []byte{6, 7, 4, 5}
|
|
||||||
ki := 0
|
|
||||||
|
|
||||||
for half := 0; half < 2; half++ {
|
|
||||||
for _, round := range schedule {
|
|
||||||
for j := 0; j < 4; j++ {
|
|
||||||
var a [7]uint8
|
|
||||||
copy(a[:], round.a[j][:])
|
|
||||||
w := t[a[1]]
|
|
||||||
w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff]
|
|
||||||
w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff]
|
|
||||||
w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff]
|
|
||||||
w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff]
|
|
||||||
w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff]
|
|
||||||
t[a[0]] = w
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := 0; j < 4; j++ {
|
|
||||||
var b [5]uint8
|
|
||||||
copy(b[:], round.b[j][:])
|
|
||||||
w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff]
|
|
||||||
w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff]
|
|
||||||
w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff]
|
|
||||||
w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff]
|
|
||||||
w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff]
|
|
||||||
k[ki] = w
|
|
||||||
ki++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 16; i++ {
|
|
||||||
c.masking[i] = k[i]
|
|
||||||
c.rotate[i] = uint8(k[16+i] & 0x1f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the three 'f' functions. See RFC 2144, section 2.2.
|
|
||||||
func f1(d, m uint32, r uint8) uint32 {
|
|
||||||
t := m + d
|
|
||||||
I := (t << r) | (t >> (32 - r))
|
|
||||||
return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
|
|
||||||
}
|
|
||||||
|
|
||||||
func f2(d, m uint32, r uint8) uint32 {
|
|
||||||
t := m ^ d
|
|
||||||
I := (t << r) | (t >> (32 - r))
|
|
||||||
return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
|
|
||||||
}
|
|
||||||
|
|
||||||
func f3(d, m uint32, r uint8) uint32 {
|
|
||||||
t := m - d
|
|
||||||
I := (t << r) | (t >> (32 - r))
|
|
||||||
return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
|
|
||||||
}
|
|
||||||
|
|
||||||
var sBox = [8][256]uint32{
|
|
||||||
{
|
|
||||||
0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949,
|
|
||||||
0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e,
|
|
||||||
0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d,
|
|
||||||
0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0,
|
|
||||||
0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7,
|
|
||||||
0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935,
|
|
||||||
0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d,
|
|
||||||
0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50,
|
|
||||||
0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe,
|
|
||||||
0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3,
|
|
||||||
0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167,
|
|
||||||
0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291,
|
|
||||||
0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779,
|
|
||||||
0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2,
|
|
||||||
0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511,
|
|
||||||
0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d,
|
|
||||||
0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5,
|
|
||||||
0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324,
|
|
||||||
0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c,
|
|
||||||
0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc,
|
|
||||||
0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d,
|
|
||||||
0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96,
|
|
||||||
0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a,
|
|
||||||
0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d,
|
|
||||||
0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd,
|
|
||||||
0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6,
|
|
||||||
0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9,
|
|
||||||
0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872,
|
|
||||||
0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c,
|
|
||||||
0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e,
|
|
||||||
0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9,
|
|
||||||
0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651,
|
|
||||||
0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3,
|
|
||||||
0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb,
|
|
||||||
0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806,
|
|
||||||
0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b,
|
|
||||||
0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359,
|
|
||||||
0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b,
|
|
||||||
0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c,
|
|
||||||
0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34,
|
|
||||||
0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb,
|
|
||||||
0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd,
|
|
||||||
0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860,
|
|
||||||
0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b,
|
|
||||||
0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304,
|
|
||||||
0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b,
|
|
||||||
0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf,
|
|
||||||
0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c,
|
|
||||||
0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13,
|
|
||||||
0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f,
|
|
||||||
0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6,
|
|
||||||
0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6,
|
|
||||||
0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58,
|
|
||||||
0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906,
|
|
||||||
0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d,
|
|
||||||
0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6,
|
|
||||||
0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4,
|
|
||||||
0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6,
|
|
||||||
0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f,
|
|
||||||
0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249,
|
|
||||||
0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa,
|
|
||||||
0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9,
|
|
||||||
0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90,
|
|
||||||
0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5,
|
|
||||||
0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e,
|
|
||||||
0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240,
|
|
||||||
0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5,
|
|
||||||
0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b,
|
|
||||||
0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71,
|
|
||||||
0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04,
|
|
||||||
0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82,
|
|
||||||
0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15,
|
|
||||||
0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2,
|
|
||||||
0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176,
|
|
||||||
0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148,
|
|
||||||
0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc,
|
|
||||||
0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341,
|
|
||||||
0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e,
|
|
||||||
0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51,
|
|
||||||
0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f,
|
|
||||||
0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a,
|
|
||||||
0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b,
|
|
||||||
0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b,
|
|
||||||
0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5,
|
|
||||||
0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45,
|
|
||||||
0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536,
|
|
||||||
0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc,
|
|
||||||
0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0,
|
|
||||||
0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69,
|
|
||||||
0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2,
|
|
||||||
0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49,
|
|
||||||
0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d,
|
|
||||||
0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a,
|
|
||||||
0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1,
|
|
||||||
0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf,
|
|
||||||
0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15,
|
|
||||||
0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121,
|
|
||||||
0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25,
|
|
||||||
0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5,
|
|
||||||
0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb,
|
|
||||||
0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5,
|
|
||||||
0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d,
|
|
||||||
0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6,
|
|
||||||
0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23,
|
|
||||||
0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003,
|
|
||||||
0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6,
|
|
||||||
0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119,
|
|
||||||
0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24,
|
|
||||||
0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a,
|
|
||||||
0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79,
|
|
||||||
0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df,
|
|
||||||
0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26,
|
|
||||||
0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab,
|
|
||||||
0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7,
|
|
||||||
0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417,
|
|
||||||
0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2,
|
|
||||||
0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2,
|
|
||||||
0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a,
|
|
||||||
0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919,
|
|
||||||
0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef,
|
|
||||||
0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876,
|
|
||||||
0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab,
|
|
||||||
0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04,
|
|
||||||
0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282,
|
|
||||||
0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f,
|
|
||||||
0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a,
|
|
||||||
0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff,
|
|
||||||
0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02,
|
|
||||||
0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a,
|
|
||||||
0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7,
|
|
||||||
0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9,
|
|
||||||
0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981,
|
|
||||||
0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774,
|
|
||||||
0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655,
|
|
||||||
0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2,
|
|
||||||
0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910,
|
|
||||||
0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1,
|
|
||||||
0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da,
|
|
||||||
0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049,
|
|
||||||
0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f,
|
|
||||||
0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba,
|
|
||||||
0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be,
|
|
||||||
0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3,
|
|
||||||
0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840,
|
|
||||||
0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4,
|
|
||||||
0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2,
|
|
||||||
0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7,
|
|
||||||
0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5,
|
|
||||||
0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e,
|
|
||||||
0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e,
|
|
||||||
0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801,
|
|
||||||
0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad,
|
|
||||||
0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0,
|
|
||||||
0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20,
|
|
||||||
0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8,
|
|
||||||
0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac,
|
|
||||||
0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138,
|
|
||||||
0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367,
|
|
||||||
0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98,
|
|
||||||
0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072,
|
|
||||||
0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3,
|
|
||||||
0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd,
|
|
||||||
0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8,
|
|
||||||
0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9,
|
|
||||||
0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54,
|
|
||||||
0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387,
|
|
||||||
0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc,
|
|
||||||
0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf,
|
|
||||||
0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf,
|
|
||||||
0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f,
|
|
||||||
0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289,
|
|
||||||
0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950,
|
|
||||||
0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f,
|
|
||||||
0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b,
|
|
||||||
0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be,
|
|
||||||
0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13,
|
|
||||||
0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976,
|
|
||||||
0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0,
|
|
||||||
0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891,
|
|
||||||
0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da,
|
|
||||||
0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc,
|
|
||||||
0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084,
|
|
||||||
0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25,
|
|
||||||
0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121,
|
|
||||||
0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5,
|
|
||||||
0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd,
|
|
||||||
0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f,
|
|
||||||
0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de,
|
|
||||||
0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43,
|
|
||||||
0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19,
|
|
||||||
0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2,
|
|
||||||
0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516,
|
|
||||||
0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88,
|
|
||||||
0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816,
|
|
||||||
0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756,
|
|
||||||
0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a,
|
|
||||||
0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264,
|
|
||||||
0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688,
|
|
||||||
0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28,
|
|
||||||
0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3,
|
|
||||||
0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7,
|
|
||||||
0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06,
|
|
||||||
0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033,
|
|
||||||
0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a,
|
|
||||||
0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566,
|
|
||||||
0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509,
|
|
||||||
0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962,
|
|
||||||
0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e,
|
|
||||||
0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c,
|
|
||||||
0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c,
|
|
||||||
0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285,
|
|
||||||
0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301,
|
|
||||||
0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be,
|
|
||||||
0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767,
|
|
||||||
0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647,
|
|
||||||
0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914,
|
|
||||||
0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c,
|
|
||||||
0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5,
|
|
||||||
0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc,
|
|
||||||
0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd,
|
|
||||||
0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d,
|
|
||||||
0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2,
|
|
||||||
0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862,
|
|
||||||
0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc,
|
|
||||||
0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c,
|
|
||||||
0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e,
|
|
||||||
0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039,
|
|
||||||
0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8,
|
|
||||||
0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42,
|
|
||||||
0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5,
|
|
||||||
0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472,
|
|
||||||
0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225,
|
|
||||||
0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c,
|
|
||||||
0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb,
|
|
||||||
0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054,
|
|
||||||
0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70,
|
|
||||||
0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc,
|
|
||||||
0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c,
|
|
||||||
0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3,
|
|
||||||
0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4,
|
|
||||||
0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101,
|
|
||||||
0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f,
|
|
||||||
0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e,
|
|
||||||
0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a,
|
|
||||||
0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c,
|
|
||||||
0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384,
|
|
||||||
0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c,
|
|
||||||
0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82,
|
|
||||||
0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
219
vendor/golang.org/x/crypto/openpgp/armor/armor.go
generated
vendored
219
vendor/golang.org/x/crypto/openpgp/armor/armor.go
generated
vendored
@@ -1,219 +0,0 @@
|
|||||||
// Copyright 2010 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 armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
|
|
||||||
// very similar to PEM except that it has an additional CRC checksum.
|
|
||||||
package armor // import "golang.org/x/crypto/openpgp/armor"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Block represents an OpenPGP armored structure.
|
|
||||||
//
|
|
||||||
// The encoded form is:
|
|
||||||
// -----BEGIN Type-----
|
|
||||||
// Headers
|
|
||||||
//
|
|
||||||
// base64-encoded Bytes
|
|
||||||
// '=' base64 encoded checksum
|
|
||||||
// -----END Type-----
|
|
||||||
// where Headers is a possibly empty sequence of Key: Value lines.
|
|
||||||
//
|
|
||||||
// Since the armored data can be very large, this package presents a streaming
|
|
||||||
// interface.
|
|
||||||
type Block struct {
|
|
||||||
Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
|
|
||||||
Header map[string]string // Optional headers.
|
|
||||||
Body io.Reader // A Reader from which the contents can be read
|
|
||||||
lReader lineReader
|
|
||||||
oReader openpgpReader
|
|
||||||
}
|
|
||||||
|
|
||||||
var ArmorCorrupt error = errors.StructuralError("armor invalid")
|
|
||||||
|
|
||||||
const crc24Init = 0xb704ce
|
|
||||||
const crc24Poly = 0x1864cfb
|
|
||||||
const crc24Mask = 0xffffff
|
|
||||||
|
|
||||||
// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
|
|
||||||
func crc24(crc uint32, d []byte) uint32 {
|
|
||||||
for _, b := range d {
|
|
||||||
crc ^= uint32(b) << 16
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
crc <<= 1
|
|
||||||
if crc&0x1000000 != 0 {
|
|
||||||
crc ^= crc24Poly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc
|
|
||||||
}
|
|
||||||
|
|
||||||
var armorStart = []byte("-----BEGIN ")
|
|
||||||
var armorEnd = []byte("-----END ")
|
|
||||||
var armorEndOfLine = []byte("-----")
|
|
||||||
|
|
||||||
// lineReader wraps a line based reader. It watches for the end of an armor
|
|
||||||
// block and records the expected CRC value.
|
|
||||||
type lineReader struct {
|
|
||||||
in *bufio.Reader
|
|
||||||
buf []byte
|
|
||||||
eof bool
|
|
||||||
crc uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineReader) Read(p []byte) (n int, err error) {
|
|
||||||
if l.eof {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(l.buf) > 0 {
|
|
||||||
n = copy(p, l.buf)
|
|
||||||
l.buf = l.buf[n:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line, isPrefix, err := l.in.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) == 5 && line[0] == '=' {
|
|
||||||
// This is the checksum line
|
|
||||||
var expectedBytes [3]byte
|
|
||||||
var m int
|
|
||||||
m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
|
|
||||||
if m != 3 || err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.crc = uint32(expectedBytes[0])<<16 |
|
|
||||||
uint32(expectedBytes[1])<<8 |
|
|
||||||
uint32(expectedBytes[2])
|
|
||||||
|
|
||||||
line, _, err = l.in.ReadLine()
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !bytes.HasPrefix(line, armorEnd) {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
l.eof = true
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) > 96 {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(p, line)
|
|
||||||
bytesToSave := len(line) - n
|
|
||||||
if bytesToSave > 0 {
|
|
||||||
if cap(l.buf) < bytesToSave {
|
|
||||||
l.buf = make([]byte, 0, bytesToSave)
|
|
||||||
}
|
|
||||||
l.buf = l.buf[0:bytesToSave]
|
|
||||||
copy(l.buf, line[n:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
|
|
||||||
// a running CRC of the resulting data and checks the CRC against the value
|
|
||||||
// found by the lineReader at EOF.
|
|
||||||
type openpgpReader struct {
|
|
||||||
lReader *lineReader
|
|
||||||
b64Reader io.Reader
|
|
||||||
currentCRC uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *openpgpReader) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = r.b64Reader.Read(p)
|
|
||||||
r.currentCRC = crc24(r.currentCRC, p[:n])
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
if r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads a PGP armored block from the given Reader. It will ignore
|
|
||||||
// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
|
|
||||||
// given Reader is not usable after calling this function: an arbitrary amount
|
|
||||||
// of data may have been read past the end of the block.
|
|
||||||
func Decode(in io.Reader) (p *Block, err error) {
|
|
||||||
r := bufio.NewReaderSize(in, 100)
|
|
||||||
var line []byte
|
|
||||||
ignoreNext := false
|
|
||||||
|
|
||||||
TryNextBlock:
|
|
||||||
p = nil
|
|
||||||
|
|
||||||
// Skip leading garbage
|
|
||||||
for {
|
|
||||||
ignoreThis := ignoreNext
|
|
||||||
line, ignoreNext, err = r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ignoreNext || ignoreThis {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p = new(Block)
|
|
||||||
p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
|
|
||||||
p.Header = make(map[string]string)
|
|
||||||
nextIsContinuation := false
|
|
||||||
var lastKey string
|
|
||||||
|
|
||||||
// Read headers
|
|
||||||
for {
|
|
||||||
isContinuation := nextIsContinuation
|
|
||||||
line, nextIsContinuation, err = r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
p = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isContinuation {
|
|
||||||
p.Header[lastKey] += string(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.Index(line, []byte(": "))
|
|
||||||
if i == -1 {
|
|
||||||
goto TryNextBlock
|
|
||||||
}
|
|
||||||
lastKey = string(line[:i])
|
|
||||||
p.Header[lastKey] = string(line[i+2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lReader.in = r
|
|
||||||
p.oReader.currentCRC = crc24Init
|
|
||||||
p.oReader.lReader = &p.lReader
|
|
||||||
p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
|
|
||||||
p.Body = &p.oReader
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
160
vendor/golang.org/x/crypto/openpgp/armor/encode.go
generated
vendored
160
vendor/golang.org/x/crypto/openpgp/armor/encode.go
generated
vendored
@@ -1,160 +0,0 @@
|
|||||||
// Copyright 2010 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 armor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var armorHeaderSep = []byte(": ")
|
|
||||||
var blockEnd = []byte("\n=")
|
|
||||||
var newline = []byte("\n")
|
|
||||||
var armorEndOfLineOut = []byte("-----\n")
|
|
||||||
|
|
||||||
// writeSlices writes its arguments to the given Writer.
|
|
||||||
func writeSlices(out io.Writer, slices ...[]byte) (err error) {
|
|
||||||
for _, s := range slices {
|
|
||||||
_, err = out.Write(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineBreaker breaks data across several lines, all of the same byte length
|
|
||||||
// (except possibly the last). Lines are broken with a single '\n'.
|
|
||||||
type lineBreaker struct {
|
|
||||||
lineLength int
|
|
||||||
line []byte
|
|
||||||
used int
|
|
||||||
out io.Writer
|
|
||||||
haveWritten bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
|
|
||||||
return &lineBreaker{
|
|
||||||
lineLength: lineLength,
|
|
||||||
line: make([]byte, lineLength),
|
|
||||||
used: 0,
|
|
||||||
out: out,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineBreaker) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b)
|
|
||||||
|
|
||||||
if n == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.used == 0 && l.haveWritten {
|
|
||||||
_, err = l.out.Write([]byte{'\n'})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.used+len(b) < l.lineLength {
|
|
||||||
l.used += copy(l.line[l.used:], b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.haveWritten = true
|
|
||||||
_, err = l.out.Write(l.line[0:l.used])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
excess := l.lineLength - l.used
|
|
||||||
l.used = 0
|
|
||||||
|
|
||||||
_, err = l.out.Write(b[0:excess])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = l.Write(b[excess:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineBreaker) Close() (err error) {
|
|
||||||
if l.used > 0 {
|
|
||||||
_, err = l.out.Write(l.line[0:l.used])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// encoding keeps track of a running CRC24 over the data which has been written
|
|
||||||
// to it and outputs a OpenPGP checksum when closed, followed by an armor
|
|
||||||
// trailer.
|
|
||||||
//
|
|
||||||
// It's built into a stack of io.Writers:
|
|
||||||
// encoding -> base64 encoder -> lineBreaker -> out
|
|
||||||
type encoding struct {
|
|
||||||
out io.Writer
|
|
||||||
breaker *lineBreaker
|
|
||||||
b64 io.WriteCloser
|
|
||||||
crc uint32
|
|
||||||
blockType []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoding) Write(data []byte) (n int, err error) {
|
|
||||||
e.crc = crc24(e.crc, data)
|
|
||||||
return e.b64.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoding) Close() (err error) {
|
|
||||||
err = e.b64.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.breaker.Close()
|
|
||||||
|
|
||||||
var checksumBytes [3]byte
|
|
||||||
checksumBytes[0] = byte(e.crc >> 16)
|
|
||||||
checksumBytes[1] = byte(e.crc >> 8)
|
|
||||||
checksumBytes[2] = byte(e.crc)
|
|
||||||
|
|
||||||
var b64ChecksumBytes [4]byte
|
|
||||||
base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
|
|
||||||
|
|
||||||
return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns a WriteCloser which will encode the data written to it in
|
|
||||||
// OpenPGP armor.
|
|
||||||
func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
|
|
||||||
bType := []byte(blockType)
|
|
||||||
err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range headers {
|
|
||||||
err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = out.Write(newline)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e := &encoding{
|
|
||||||
out: out,
|
|
||||||
breaker: newLineBreaker(out, 64),
|
|
||||||
crc: crc24Init,
|
|
||||||
blockType: bType,
|
|
||||||
}
|
|
||||||
e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
59
vendor/golang.org/x/crypto/openpgp/canonical_text.go
generated
vendored
59
vendor/golang.org/x/crypto/openpgp/canonical_text.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
// Copyright 2011 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 openpgp
|
|
||||||
|
|
||||||
import "hash"
|
|
||||||
|
|
||||||
// NewCanonicalTextHash reformats text written to it into the canonical
|
|
||||||
// form and then applies the hash h. See RFC 4880, section 5.2.1.
|
|
||||||
func NewCanonicalTextHash(h hash.Hash) hash.Hash {
|
|
||||||
return &canonicalTextHash{h, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
type canonicalTextHash struct {
|
|
||||||
h hash.Hash
|
|
||||||
s int
|
|
||||||
}
|
|
||||||
|
|
||||||
var newline = []byte{'\r', '\n'}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
|
|
||||||
start := 0
|
|
||||||
|
|
||||||
for i, c := range buf {
|
|
||||||
switch cth.s {
|
|
||||||
case 0:
|
|
||||||
if c == '\r' {
|
|
||||||
cth.s = 1
|
|
||||||
} else if c == '\n' {
|
|
||||||
cth.h.Write(buf[start:i])
|
|
||||||
cth.h.Write(newline)
|
|
||||||
start = i + 1
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
cth.s = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cth.h.Write(buf[start:])
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Sum(in []byte) []byte {
|
|
||||||
return cth.h.Sum(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Reset() {
|
|
||||||
cth.h.Reset()
|
|
||||||
cth.s = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Size() int {
|
|
||||||
return cth.h.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) BlockSize() int {
|
|
||||||
return cth.h.BlockSize()
|
|
||||||
}
|
|
||||||
122
vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go
generated
vendored
122
vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go
generated
vendored
@@ -1,122 +0,0 @@
|
|||||||
// Copyright 2011 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 elgamal implements ElGamal encryption, suitable for OpenPGP,
|
|
||||||
// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
|
|
||||||
// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
|
|
||||||
// n. 4, 1985, pp. 469-472.
|
|
||||||
//
|
|
||||||
// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
|
|
||||||
// unsuitable for other protocols. RSA should be used in preference in any
|
|
||||||
// case.
|
|
||||||
package elgamal // import "golang.org/x/crypto/openpgp/elgamal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey represents an ElGamal public key.
|
|
||||||
type PublicKey struct {
|
|
||||||
G, P, Y *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateKey represents an ElGamal private key.
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
X *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt encrypts the given message to the given public key. The result is a
|
|
||||||
// pair of integers. Errors can result from reading random, or because msg is
|
|
||||||
// too large to be encrypted to the public key.
|
|
||||||
func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
|
|
||||||
pLen := (pub.P.BitLen() + 7) / 8
|
|
||||||
if len(msg) > pLen-11 {
|
|
||||||
err = errors.New("elgamal: message too long")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EM = 0x02 || PS || 0x00 || M
|
|
||||||
em := make([]byte, pLen-1)
|
|
||||||
em[0] = 2
|
|
||||||
ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
|
|
||||||
err = nonZeroRandomBytes(ps, random)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
em[len(em)-len(msg)-1] = 0
|
|
||||||
copy(mm, msg)
|
|
||||||
|
|
||||||
m := new(big.Int).SetBytes(em)
|
|
||||||
|
|
||||||
k, err := rand.Int(random, pub.P)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 = new(big.Int).Exp(pub.G, k, pub.P)
|
|
||||||
s := new(big.Int).Exp(pub.Y, k, pub.P)
|
|
||||||
c2 = s.Mul(s, m)
|
|
||||||
c2.Mod(c2, pub.P)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt takes two integers, resulting from an ElGamal encryption, and
|
|
||||||
// returns the plaintext of the message. An error can result only if the
|
|
||||||
// ciphertext is invalid. Users should keep in mind that this is a padding
|
|
||||||
// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
|
|
||||||
// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks
|
|
||||||
// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel
|
|
||||||
// Bleichenbacher, Advances in Cryptology (Crypto '98),
|
|
||||||
func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
|
|
||||||
s := new(big.Int).Exp(c1, priv.X, priv.P)
|
|
||||||
s.ModInverse(s, priv.P)
|
|
||||||
s.Mul(s, c2)
|
|
||||||
s.Mod(s, priv.P)
|
|
||||||
em := s.Bytes()
|
|
||||||
|
|
||||||
firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
|
|
||||||
|
|
||||||
// The remainder of the plaintext must be a string of non-zero random
|
|
||||||
// octets, followed by a 0, followed by the message.
|
|
||||||
// lookingForIndex: 1 iff we are still looking for the zero.
|
|
||||||
// index: the offset of the first zero byte.
|
|
||||||
var lookingForIndex, index int
|
|
||||||
lookingForIndex = 1
|
|
||||||
|
|
||||||
for i := 1; i < len(em); i++ {
|
|
||||||
equals0 := subtle.ConstantTimeByteEq(em[i], 0)
|
|
||||||
index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
|
|
||||||
lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
|
|
||||||
return nil, errors.New("elgamal: decryption error")
|
|
||||||
}
|
|
||||||
return em[index+1:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nonZeroRandomBytes fills the given slice with non-zero random octets.
|
|
||||||
func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
|
|
||||||
_, err = io.ReadFull(rand, s)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
for s[i] == 0 {
|
|
||||||
_, err = io.ReadFull(rand, s[i:i+1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
72
vendor/golang.org/x/crypto/openpgp/errors/errors.go
generated
vendored
72
vendor/golang.org/x/crypto/openpgp/errors/errors.go
generated
vendored
@@ -1,72 +0,0 @@
|
|||||||
// Copyright 2010 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 errors contains common error types for the OpenPGP packages.
|
|
||||||
package errors // import "golang.org/x/crypto/openpgp/errors"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A StructuralError is returned when OpenPGP data is found to be syntactically
|
|
||||||
// invalid.
|
|
||||||
type StructuralError string
|
|
||||||
|
|
||||||
func (s StructuralError) Error() string {
|
|
||||||
return "openpgp: invalid data: " + string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsupportedError indicates that, although the OpenPGP data is valid, it
|
|
||||||
// makes use of currently unimplemented features.
|
|
||||||
type UnsupportedError string
|
|
||||||
|
|
||||||
func (s UnsupportedError) Error() string {
|
|
||||||
return "openpgp: unsupported feature: " + string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidArgumentError indicates that the caller is in error and passed an
|
|
||||||
// incorrect value.
|
|
||||||
type InvalidArgumentError string
|
|
||||||
|
|
||||||
func (i InvalidArgumentError) Error() string {
|
|
||||||
return "openpgp: invalid argument: " + string(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignatureError indicates that a syntactically valid signature failed to
|
|
||||||
// validate.
|
|
||||||
type SignatureError string
|
|
||||||
|
|
||||||
func (b SignatureError) Error() string {
|
|
||||||
return "openpgp: invalid signature: " + string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyIncorrectError int
|
|
||||||
|
|
||||||
func (ki keyIncorrectError) Error() string {
|
|
||||||
return "openpgp: incorrect key"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyIncorrect error = keyIncorrectError(0)
|
|
||||||
|
|
||||||
type unknownIssuerError int
|
|
||||||
|
|
||||||
func (unknownIssuerError) Error() string {
|
|
||||||
return "openpgp: signature made by unknown entity"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrUnknownIssuer error = unknownIssuerError(0)
|
|
||||||
|
|
||||||
type keyRevokedError int
|
|
||||||
|
|
||||||
func (keyRevokedError) Error() string {
|
|
||||||
return "openpgp: signature made by revoked key"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyRevoked error = keyRevokedError(0)
|
|
||||||
|
|
||||||
type UnknownPacketTypeError uint8
|
|
||||||
|
|
||||||
func (upte UnknownPacketTypeError) Error() string {
|
|
||||||
return "openpgp: unknown packet type: " + strconv.Itoa(int(upte))
|
|
||||||
}
|
|
||||||
640
vendor/golang.org/x/crypto/openpgp/keys.go
generated
vendored
640
vendor/golang.org/x/crypto/openpgp/keys.go
generated
vendored
@@ -1,640 +0,0 @@
|
|||||||
// Copyright 2011 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 openpgp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/armor"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKeyType is the armor type for a PGP public key.
|
|
||||||
var PublicKeyType = "PGP PUBLIC KEY BLOCK"
|
|
||||||
|
|
||||||
// PrivateKeyType is the armor type for a PGP private key.
|
|
||||||
var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
|
|
||||||
|
|
||||||
// An Entity represents the components of an OpenPGP key: a primary public key
|
|
||||||
// (which must be a signing key), one or more identities claimed by that key,
|
|
||||||
// and zero or more subkeys, which may be encryption keys.
|
|
||||||
type Entity struct {
|
|
||||||
PrimaryKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
Identities map[string]*Identity // indexed by Identity.Name
|
|
||||||
Revocations []*packet.Signature
|
|
||||||
Subkeys []Subkey
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Identity represents an identity claimed by an Entity and zero or more
|
|
||||||
// assertions by other entities about that claim.
|
|
||||||
type Identity struct {
|
|
||||||
Name string // by convention, has the form "Full Name (comment) <email@example.com>"
|
|
||||||
UserId *packet.UserId
|
|
||||||
SelfSignature *packet.Signature
|
|
||||||
Signatures []*packet.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Subkey is an additional public key in an Entity. Subkeys can be used for
|
|
||||||
// encryption.
|
|
||||||
type Subkey struct {
|
|
||||||
PublicKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
Sig *packet.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Key identifies a specific public key in an Entity. This is either the
|
|
||||||
// Entity's primary key or a subkey.
|
|
||||||
type Key struct {
|
|
||||||
Entity *Entity
|
|
||||||
PublicKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
SelfSignature *packet.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// A KeyRing provides access to public and private keys.
|
|
||||||
type KeyRing interface {
|
|
||||||
// KeysById returns the set of keys that have the given key id.
|
|
||||||
KeysById(id uint64) []Key
|
|
||||||
// KeysByIdAndUsage returns the set of keys with the given id
|
|
||||||
// that also meet the key usage given by requiredUsage.
|
|
||||||
// The requiredUsage is expressed as the bitwise-OR of
|
|
||||||
// packet.KeyFlag* values.
|
|
||||||
KeysByIdUsage(id uint64, requiredUsage byte) []Key
|
|
||||||
// DecryptionKeys returns all private keys that are valid for
|
|
||||||
// decryption.
|
|
||||||
DecryptionKeys() []Key
|
|
||||||
}
|
|
||||||
|
|
||||||
// primaryIdentity returns the Identity marked as primary or the first identity
|
|
||||||
// if none are so marked.
|
|
||||||
func (e *Entity) primaryIdentity() *Identity {
|
|
||||||
var firstIdentity *Identity
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
if firstIdentity == nil {
|
|
||||||
firstIdentity = ident
|
|
||||||
}
|
|
||||||
if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
|
||||||
return ident
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return firstIdentity
|
|
||||||
}
|
|
||||||
|
|
||||||
// encryptionKey returns the best candidate Key for encrypting a message to the
|
|
||||||
// given Entity.
|
|
||||||
func (e *Entity) encryptionKey(now time.Time) (Key, bool) {
|
|
||||||
candidateSubkey := -1
|
|
||||||
|
|
||||||
// Iterate the keys to find the newest key
|
|
||||||
var maxTime time.Time
|
|
||||||
for i, subkey := range e.Subkeys {
|
|
||||||
if subkey.Sig.FlagsValid &&
|
|
||||||
subkey.Sig.FlagEncryptCommunications &&
|
|
||||||
subkey.PublicKey.PubKeyAlgo.CanEncrypt() &&
|
|
||||||
!subkey.Sig.KeyExpired(now) &&
|
|
||||||
(maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) {
|
|
||||||
candidateSubkey = i
|
|
||||||
maxTime = subkey.Sig.CreationTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if candidateSubkey != -1 {
|
|
||||||
subkey := e.Subkeys[candidateSubkey]
|
|
||||||
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have any candidate subkeys for encryption and
|
|
||||||
// the primary key doesn't have any usage metadata then we
|
|
||||||
// assume that the primary key is ok. Or, if the primary key is
|
|
||||||
// marked as ok to encrypt to, then we can obviously use it.
|
|
||||||
i := e.primaryIdentity()
|
|
||||||
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
|
|
||||||
e.PrimaryKey.PubKeyAlgo.CanEncrypt() &&
|
|
||||||
!i.SelfSignature.KeyExpired(now) {
|
|
||||||
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// This Entity appears to be signing only.
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// signingKey return the best candidate Key for signing a message with this
|
|
||||||
// Entity.
|
|
||||||
func (e *Entity) signingKey(now time.Time) (Key, bool) {
|
|
||||||
candidateSubkey := -1
|
|
||||||
|
|
||||||
for i, subkey := range e.Subkeys {
|
|
||||||
if subkey.Sig.FlagsValid &&
|
|
||||||
subkey.Sig.FlagSign &&
|
|
||||||
subkey.PublicKey.PubKeyAlgo.CanSign() &&
|
|
||||||
!subkey.Sig.KeyExpired(now) {
|
|
||||||
candidateSubkey = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if candidateSubkey != -1 {
|
|
||||||
subkey := e.Subkeys[candidateSubkey]
|
|
||||||
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have no candidate subkey then we assume that it's ok to sign
|
|
||||||
// with the primary key.
|
|
||||||
i := e.primaryIdentity()
|
|
||||||
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagSign &&
|
|
||||||
!i.SelfSignature.KeyExpired(now) {
|
|
||||||
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// An EntityList contains one or more Entities.
|
|
||||||
type EntityList []*Entity
|
|
||||||
|
|
||||||
// KeysById returns the set of keys that have the given key id.
|
|
||||||
func (el EntityList) KeysById(id uint64) (keys []Key) {
|
|
||||||
for _, e := range el {
|
|
||||||
if e.PrimaryKey.KeyId == id {
|
|
||||||
var selfSig *packet.Signature
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
if selfSig == nil {
|
|
||||||
selfSig = ident.SelfSignature
|
|
||||||
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
|
||||||
selfSig = ident.SelfSignature
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subKey := range e.Subkeys {
|
|
||||||
if subKey.PublicKey.KeyId == id {
|
|
||||||
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeysByIdAndUsage returns the set of keys with the given id that also meet
|
|
||||||
// the key usage given by requiredUsage. The requiredUsage is expressed as
|
|
||||||
// the bitwise-OR of packet.KeyFlag* values.
|
|
||||||
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
|
|
||||||
for _, key := range el.KeysById(id) {
|
|
||||||
if len(key.Entity.Revocations) > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.SelfSignature.RevocationReason != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.SelfSignature.FlagsValid && requiredUsage != 0 {
|
|
||||||
var usage byte
|
|
||||||
if key.SelfSignature.FlagCertify {
|
|
||||||
usage |= packet.KeyFlagCertify
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagSign {
|
|
||||||
usage |= packet.KeyFlagSign
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagEncryptCommunications {
|
|
||||||
usage |= packet.KeyFlagEncryptCommunications
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagEncryptStorage {
|
|
||||||
usage |= packet.KeyFlagEncryptStorage
|
|
||||||
}
|
|
||||||
if usage&requiredUsage != requiredUsage {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptionKeys returns all private keys that are valid for decryption.
|
|
||||||
func (el EntityList) DecryptionKeys() (keys []Key) {
|
|
||||||
for _, e := range el {
|
|
||||||
for _, subKey := range e.Subkeys {
|
|
||||||
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
|
|
||||||
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
|
|
||||||
func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
|
|
||||||
block, err := armor.Decode(r)
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, errors.InvalidArgumentError("no armored data found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if block.Type != PublicKeyType && block.Type != PrivateKeyType {
|
|
||||||
return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReadKeyRing(block.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadKeyRing reads one or more public/private keys. Unsupported keys are
|
|
||||||
// ignored as long as at least a single valid key is found.
|
|
||||||
func ReadKeyRing(r io.Reader) (el EntityList, err error) {
|
|
||||||
packets := packet.NewReader(r)
|
|
||||||
var lastUnsupportedError error
|
|
||||||
|
|
||||||
for {
|
|
||||||
var e *Entity
|
|
||||||
e, err = ReadEntity(packets)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: warn about skipped unsupported/unreadable keys
|
|
||||||
if _, ok := err.(errors.UnsupportedError); ok {
|
|
||||||
lastUnsupportedError = err
|
|
||||||
err = readToNextPublicKey(packets)
|
|
||||||
} else if _, ok := err.(errors.StructuralError); ok {
|
|
||||||
// Skip unreadable, badly-formatted keys
|
|
||||||
lastUnsupportedError = err
|
|
||||||
err = readToNextPublicKey(packets)
|
|
||||||
}
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
el = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
el = append(el, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(el) == 0 && err == nil {
|
|
||||||
err = lastUnsupportedError
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readToNextPublicKey reads packets until the start of the entity and leaves
|
|
||||||
// the first packet of the new entity in the Reader.
|
|
||||||
func readToNextPublicKey(packets *packet.Reader) (err error) {
|
|
||||||
var p packet.Packet
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
if _, ok := err.(errors.UnsupportedError); ok {
|
|
||||||
err = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
|
|
||||||
packets.Unread(p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntity reads an entity (public key, identities, subkeys etc) from the
|
|
||||||
// given Reader.
|
|
||||||
func ReadEntity(packets *packet.Reader) (*Entity, error) {
|
|
||||||
e := new(Entity)
|
|
||||||
e.Identities = make(map[string]*Identity)
|
|
||||||
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
|
|
||||||
if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
|
|
||||||
packets.Unread(p)
|
|
||||||
return nil, errors.StructuralError("first packet was not a public/private key")
|
|
||||||
}
|
|
||||||
e.PrimaryKey = &e.PrivateKey.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if !e.PrimaryKey.PubKeyAlgo.CanSign() {
|
|
||||||
return nil, errors.StructuralError("primary key cannot be used for signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
var current *Identity
|
|
||||||
var revocations []*packet.Signature
|
|
||||||
EachPacket:
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pkt := p.(type) {
|
|
||||||
case *packet.UserId:
|
|
||||||
current = new(Identity)
|
|
||||||
current.Name = pkt.Id
|
|
||||||
current.UserId = pkt
|
|
||||||
e.Identities[pkt.Id] = current
|
|
||||||
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, io.ErrUnexpectedEOF
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, ok := p.(*packet.Signature)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.StructuralError("user ID packet not followed by self-signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
|
|
||||||
if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil {
|
|
||||||
return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
current.SelfSignature = sig
|
|
||||||
break
|
|
||||||
}
|
|
||||||
current.Signatures = append(current.Signatures, sig)
|
|
||||||
}
|
|
||||||
case *packet.Signature:
|
|
||||||
if pkt.SigType == packet.SigTypeKeyRevocation {
|
|
||||||
revocations = append(revocations, pkt)
|
|
||||||
} else if pkt.SigType == packet.SigTypeDirectSignature {
|
|
||||||
// TODO: RFC4880 5.2.1 permits signatures
|
|
||||||
// directly on keys (eg. to bind additional
|
|
||||||
// revocation keys).
|
|
||||||
} else if current == nil {
|
|
||||||
return nil, errors.StructuralError("signature packet found before user id packet")
|
|
||||||
} else {
|
|
||||||
current.Signatures = append(current.Signatures, pkt)
|
|
||||||
}
|
|
||||||
case *packet.PrivateKey:
|
|
||||||
if pkt.IsSubkey == false {
|
|
||||||
packets.Unread(p)
|
|
||||||
break EachPacket
|
|
||||||
}
|
|
||||||
err = addSubkey(e, packets, &pkt.PublicKey, pkt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *packet.PublicKey:
|
|
||||||
if pkt.IsSubkey == false {
|
|
||||||
packets.Unread(p)
|
|
||||||
break EachPacket
|
|
||||||
}
|
|
||||||
err = addSubkey(e, packets, pkt, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// we ignore unknown packets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(e.Identities) == 0 {
|
|
||||||
return nil, errors.StructuralError("entity without any identities")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, revocation := range revocations {
|
|
||||||
err = e.PrimaryKey.VerifyRevocationSignature(revocation)
|
|
||||||
if err == nil {
|
|
||||||
e.Revocations = append(e.Revocations, revocation)
|
|
||||||
} else {
|
|
||||||
// TODO: RFC 4880 5.2.3.15 defines revocation keys.
|
|
||||||
return nil, errors.StructuralError("revocation signature signed by alternate key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
|
|
||||||
var subKey Subkey
|
|
||||||
subKey.PublicKey = pub
|
|
||||||
subKey.PrivateKey = priv
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
var ok bool
|
|
||||||
subKey.Sig, ok = p.(*packet.Signature)
|
|
||||||
if !ok {
|
|
||||||
return errors.StructuralError("subkey packet not followed by signature")
|
|
||||||
}
|
|
||||||
if subKey.Sig.SigType != packet.SigTypeSubkeyBinding && subKey.Sig.SigType != packet.SigTypeSubkeyRevocation {
|
|
||||||
return errors.StructuralError("subkey signature with wrong type")
|
|
||||||
}
|
|
||||||
err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig)
|
|
||||||
if err != nil {
|
|
||||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
e.Subkeys = append(e.Subkeys, subKey)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultRSAKeyBits = 2048
|
|
||||||
|
|
||||||
// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
|
|
||||||
// single identity composed of the given full name, comment and email, any of
|
|
||||||
// which may be empty but must not contain any of "()<>\x00".
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) {
|
|
||||||
currentTime := config.Now()
|
|
||||||
|
|
||||||
bits := defaultRSAKeyBits
|
|
||||||
if config != nil && config.RSABits != 0 {
|
|
||||||
bits = config.RSABits
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := packet.NewUserId(name, comment, email)
|
|
||||||
if uid == nil {
|
|
||||||
return nil, errors.InvalidArgumentError("user id field contained invalid characters")
|
|
||||||
}
|
|
||||||
signingPriv, err := rsa.GenerateKey(config.Random(), bits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encryptingPriv, err := rsa.GenerateKey(config.Random(), bits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e := &Entity{
|
|
||||||
PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey),
|
|
||||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv),
|
|
||||||
Identities: make(map[string]*Identity),
|
|
||||||
}
|
|
||||||
isPrimaryId := true
|
|
||||||
e.Identities[uid.Id] = &Identity{
|
|
||||||
Name: uid.Id,
|
|
||||||
UserId: uid,
|
|
||||||
SelfSignature: &packet.Signature{
|
|
||||||
CreationTime: currentTime,
|
|
||||||
SigType: packet.SigTypePositiveCert,
|
|
||||||
PubKeyAlgo: packet.PubKeyAlgoRSA,
|
|
||||||
Hash: config.Hash(),
|
|
||||||
IsPrimaryId: &isPrimaryId,
|
|
||||||
FlagsValid: true,
|
|
||||||
FlagSign: true,
|
|
||||||
FlagCertify: true,
|
|
||||||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = e.Identities[uid.Id].SelfSignature.SignUserId(uid.Id, e.PrimaryKey, e.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user passes in a DefaultHash via packet.Config,
|
|
||||||
// set the PreferredHash for the SelfSignature.
|
|
||||||
if config != nil && config.DefaultHash != 0 {
|
|
||||||
e.Identities[uid.Id].SelfSignature.PreferredHash = []uint8{hashToHashId(config.DefaultHash)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Likewise for DefaultCipher.
|
|
||||||
if config != nil && config.DefaultCipher != 0 {
|
|
||||||
e.Identities[uid.Id].SelfSignature.PreferredSymmetric = []uint8{uint8(config.DefaultCipher)}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Subkeys = make([]Subkey, 1)
|
|
||||||
e.Subkeys[0] = Subkey{
|
|
||||||
PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey),
|
|
||||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv),
|
|
||||||
Sig: &packet.Signature{
|
|
||||||
CreationTime: currentTime,
|
|
||||||
SigType: packet.SigTypeSubkeyBinding,
|
|
||||||
PubKeyAlgo: packet.PubKeyAlgoRSA,
|
|
||||||
Hash: config.Hash(),
|
|
||||||
FlagsValid: true,
|
|
||||||
FlagEncryptStorage: true,
|
|
||||||
FlagEncryptCommunications: true,
|
|
||||||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
e.Subkeys[0].PublicKey.IsSubkey = true
|
|
||||||
e.Subkeys[0].PrivateKey.IsSubkey = true
|
|
||||||
err = e.Subkeys[0].Sig.SignKey(e.Subkeys[0].PublicKey, e.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializePrivate serializes an Entity, including private key material, to
|
|
||||||
// the given Writer. For now, it must only be used on an Entity returned from
|
|
||||||
// NewEntity.
|
|
||||||
// config is ignored
|
|
||||||
func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) {
|
|
||||||
err = e.PrivateKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
err = ident.UserId.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = ident.SelfSignature.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, subkey := range e.Subkeys {
|
|
||||||
err = subkey.PrivateKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = subkey.Sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize writes the public part of the given Entity to w. (No private
|
|
||||||
// key material will be output).
|
|
||||||
func (e *Entity) Serialize(w io.Writer) error {
|
|
||||||
err := e.PrimaryKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
err = ident.UserId.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ident.SelfSignature.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, sig := range ident.Signatures {
|
|
||||||
err = sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, subkey := range e.Subkeys {
|
|
||||||
err = subkey.PublicKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = subkey.Sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignIdentity adds a signature to e, from signer, attesting that identity is
|
|
||||||
// associated with e. The provided identity must already be an element of
|
|
||||||
// e.Identities and the private key of signer must have been decrypted if
|
|
||||||
// necessary.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error {
|
|
||||||
if signer.PrivateKey == nil {
|
|
||||||
return errors.InvalidArgumentError("signing Entity must have a private key")
|
|
||||||
}
|
|
||||||
if signer.PrivateKey.Encrypted {
|
|
||||||
return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
|
|
||||||
}
|
|
||||||
ident, ok := e.Identities[identity]
|
|
||||||
if !ok {
|
|
||||||
return errors.InvalidArgumentError("given identity string not found in Entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := &packet.Signature{
|
|
||||||
SigType: packet.SigTypeGenericCert,
|
|
||||||
PubKeyAlgo: signer.PrivateKey.PubKeyAlgo,
|
|
||||||
Hash: config.Hash(),
|
|
||||||
CreationTime: config.Now(),
|
|
||||||
IssuerKeyId: &signer.PrivateKey.KeyId,
|
|
||||||
}
|
|
||||||
if err := sig.SignUserId(identity, e.PrimaryKey, signer.PrivateKey, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ident.Signatures = append(ident.Signatures, sig)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
123
vendor/golang.org/x/crypto/openpgp/packet/compressed.go
generated
vendored
123
vendor/golang.org/x/crypto/openpgp/packet/compressed.go
generated
vendored
@@ -1,123 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/bzip2"
|
|
||||||
"compress/flate"
|
|
||||||
"compress/zlib"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compressed represents a compressed OpenPGP packet. The decompressed contents
|
|
||||||
// will contain more OpenPGP packets. See RFC 4880, section 5.6.
|
|
||||||
type Compressed struct {
|
|
||||||
Body io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NoCompression = flate.NoCompression
|
|
||||||
BestSpeed = flate.BestSpeed
|
|
||||||
BestCompression = flate.BestCompression
|
|
||||||
DefaultCompression = flate.DefaultCompression
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompressionConfig contains compressor configuration settings.
|
|
||||||
type CompressionConfig struct {
|
|
||||||
// Level is the compression level to use. It must be set to
|
|
||||||
// between -1 and 9, with -1 causing the compressor to use the
|
|
||||||
// default compression level, 0 causing the compressor to use
|
|
||||||
// no compression and 1 to 9 representing increasing (better,
|
|
||||||
// slower) compression levels. If Level is less than -1 or
|
|
||||||
// more then 9, a non-nil error will be returned during
|
|
||||||
// encryption. See the constants above for convenient common
|
|
||||||
// settings for Level.
|
|
||||||
Level int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compressed) parse(r io.Reader) error {
|
|
||||||
var buf [1]byte
|
|
||||||
_, err := readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch buf[0] {
|
|
||||||
case 1:
|
|
||||||
c.Body = flate.NewReader(r)
|
|
||||||
case 2:
|
|
||||||
c.Body, err = zlib.NewReader(r)
|
|
||||||
case 3:
|
|
||||||
c.Body = bzip2.NewReader(r)
|
|
||||||
default:
|
|
||||||
err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// compressedWriterCloser represents the serialized compression stream
|
|
||||||
// header and the compressor. Its Close() method ensures that both the
|
|
||||||
// compressor and serialized stream header are closed. Its Write()
|
|
||||||
// method writes to the compressor.
|
|
||||||
type compressedWriteCloser struct {
|
|
||||||
sh io.Closer // Stream Header
|
|
||||||
c io.WriteCloser // Compressor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cwc compressedWriteCloser) Write(p []byte) (int, error) {
|
|
||||||
return cwc.c.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cwc compressedWriteCloser) Close() (err error) {
|
|
||||||
err = cwc.c.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cwc.sh.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeCompressed serializes a compressed data packet to w and
|
|
||||||
// returns a WriteCloser to which the literal data packets themselves
|
|
||||||
// can be written and which MUST be closed on completion. If cc is
|
|
||||||
// nil, sensible defaults will be used to configure the compression
|
|
||||||
// algorithm.
|
|
||||||
func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) {
|
|
||||||
compressed, err := serializeStreamHeader(w, packetTypeCompressed)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = compressed.Write([]byte{uint8(algo)})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
level := DefaultCompression
|
|
||||||
if cc != nil {
|
|
||||||
level = cc.Level
|
|
||||||
}
|
|
||||||
|
|
||||||
var compressor io.WriteCloser
|
|
||||||
switch algo {
|
|
||||||
case CompressionZIP:
|
|
||||||
compressor, err = flate.NewWriter(compressed, level)
|
|
||||||
case CompressionZLIB:
|
|
||||||
compressor, err = zlib.NewWriterLevel(compressed, level)
|
|
||||||
default:
|
|
||||||
s := strconv.Itoa(int(algo))
|
|
||||||
err = errors.UnsupportedError("Unsupported compression algorithm: " + s)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
literaldata = compressedWriteCloser{compressed, compressor}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
91
vendor/golang.org/x/crypto/openpgp/packet/config.go
generated
vendored
91
vendor/golang.org/x/crypto/openpgp/packet/config.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
// Copyright 2012 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config collects a number of parameters along with sensible defaults.
|
|
||||||
// A nil *Config is valid and results in all default values.
|
|
||||||
type Config struct {
|
|
||||||
// Rand provides the source of entropy.
|
|
||||||
// If nil, the crypto/rand Reader is used.
|
|
||||||
Rand io.Reader
|
|
||||||
// DefaultHash is the default hash function to be used.
|
|
||||||
// If zero, SHA-256 is used.
|
|
||||||
DefaultHash crypto.Hash
|
|
||||||
// DefaultCipher is the cipher to be used.
|
|
||||||
// If zero, AES-128 is used.
|
|
||||||
DefaultCipher CipherFunction
|
|
||||||
// Time returns the current time as the number of seconds since the
|
|
||||||
// epoch. If Time is nil, time.Now is used.
|
|
||||||
Time func() time.Time
|
|
||||||
// DefaultCompressionAlgo is the compression algorithm to be
|
|
||||||
// applied to the plaintext before encryption. If zero, no
|
|
||||||
// compression is done.
|
|
||||||
DefaultCompressionAlgo CompressionAlgo
|
|
||||||
// CompressionConfig configures the compression settings.
|
|
||||||
CompressionConfig *CompressionConfig
|
|
||||||
// S2KCount is only used for symmetric encryption. It
|
|
||||||
// determines the strength of the passphrase stretching when
|
|
||||||
// the said passphrase is hashed to produce a key. S2KCount
|
|
||||||
// should be between 1024 and 65011712, inclusive. If Config
|
|
||||||
// is nil or S2KCount is 0, the value 65536 used. Not all
|
|
||||||
// values in the above range can be represented. S2KCount will
|
|
||||||
// be rounded up to the next representable value if it cannot
|
|
||||||
// be encoded exactly. When set, it is strongly encrouraged to
|
|
||||||
// use a value that is at least 65536. See RFC 4880 Section
|
|
||||||
// 3.7.1.3.
|
|
||||||
S2KCount int
|
|
||||||
// RSABits is the number of bits in new RSA keys made with NewEntity.
|
|
||||||
// If zero, then 2048 bit keys are created.
|
|
||||||
RSABits int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Random() io.Reader {
|
|
||||||
if c == nil || c.Rand == nil {
|
|
||||||
return rand.Reader
|
|
||||||
}
|
|
||||||
return c.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Hash() crypto.Hash {
|
|
||||||
if c == nil || uint(c.DefaultHash) == 0 {
|
|
||||||
return crypto.SHA256
|
|
||||||
}
|
|
||||||
return c.DefaultHash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Cipher() CipherFunction {
|
|
||||||
if c == nil || uint8(c.DefaultCipher) == 0 {
|
|
||||||
return CipherAES128
|
|
||||||
}
|
|
||||||
return c.DefaultCipher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Now() time.Time {
|
|
||||||
if c == nil || c.Time == nil {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
return c.Time()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Compression() CompressionAlgo {
|
|
||||||
if c == nil {
|
|
||||||
return CompressionNone
|
|
||||||
}
|
|
||||||
return c.DefaultCompressionAlgo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) PasswordHashIterations() int {
|
|
||||||
if c == nil || c.S2KCount == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return c.S2KCount
|
|
||||||
}
|
|
||||||
206
vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go
generated
vendored
206
vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go
generated
vendored
@@ -1,206 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/elgamal"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const encryptedKeyVersion = 3
|
|
||||||
|
|
||||||
// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
|
|
||||||
// section 5.1.
|
|
||||||
type EncryptedKey struct {
|
|
||||||
KeyId uint64
|
|
||||||
Algo PublicKeyAlgorithm
|
|
||||||
CipherFunc CipherFunction // only valid after a successful Decrypt
|
|
||||||
Key []byte // only valid after a successful Decrypt
|
|
||||||
|
|
||||||
encryptedMPI1, encryptedMPI2 parsedMPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EncryptedKey) parse(r io.Reader) (err error) {
|
|
||||||
var buf [10]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != encryptedKeyVersion {
|
|
||||||
return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
e.KeyId = binary.BigEndian.Uint64(buf[1:9])
|
|
||||||
e.Algo = PublicKeyAlgorithm(buf[9])
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.encryptedMPI2.bytes, e.encryptedMPI2.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = consumeAll(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func checksumKeyMaterial(key []byte) uint16 {
|
|
||||||
var checksum uint16
|
|
||||||
for _, v := range key {
|
|
||||||
checksum += uint16(v)
|
|
||||||
}
|
|
||||||
return checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts an encrypted session key with the given private key. The
|
|
||||||
// private key must have been decrypted first.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
|
|
||||||
var err error
|
|
||||||
var b []byte
|
|
||||||
|
|
||||||
// TODO(agl): use session key decryption routines here to avoid
|
|
||||||
// padding oracle attacks.
|
|
||||||
switch priv.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
k := priv.PrivateKey.(*rsa.PrivateKey)
|
|
||||||
b, err = rsa.DecryptPKCS1v15(config.Random(), k, padToKeySize(&k.PublicKey, e.encryptedMPI1.bytes))
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
c1 := new(big.Int).SetBytes(e.encryptedMPI1.bytes)
|
|
||||||
c2 := new(big.Int).SetBytes(e.encryptedMPI2.bytes)
|
|
||||||
b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.CipherFunc = CipherFunction(b[0])
|
|
||||||
e.Key = b[1 : len(b)-2]
|
|
||||||
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
|
|
||||||
checksum := checksumKeyMaterial(e.Key)
|
|
||||||
if checksum != expectedChecksum {
|
|
||||||
return errors.StructuralError("EncryptedKey checksum incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize writes the encrypted key packet, e, to w.
|
|
||||||
func (e *EncryptedKey) Serialize(w io.Writer) error {
|
|
||||||
var mpiLen int
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
mpiLen = 2 + len(e.encryptedMPI1.bytes)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
mpiLen = 2 + len(e.encryptedMPI1.bytes) + 2 + len(e.encryptedMPI2.bytes)
|
|
||||||
default:
|
|
||||||
return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen)
|
|
||||||
|
|
||||||
w.Write([]byte{encryptedKeyVersion})
|
|
||||||
binary.Write(w, binary.BigEndian, e.KeyId)
|
|
||||||
w.Write([]byte{byte(e.Algo)})
|
|
||||||
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
writeMPIs(w, e.encryptedMPI1)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
writeMPIs(w, e.encryptedMPI1, e.encryptedMPI2)
|
|
||||||
default:
|
|
||||||
panic("internal error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeEncryptedKey serializes an encrypted key packet to w that contains
|
|
||||||
// key, encrypted to pub.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error {
|
|
||||||
var buf [10]byte
|
|
||||||
buf[0] = encryptedKeyVersion
|
|
||||||
binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
|
|
||||||
buf[9] = byte(pub.PubKeyAlgo)
|
|
||||||
|
|
||||||
keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */)
|
|
||||||
keyBlock[0] = byte(cipherFunc)
|
|
||||||
copy(keyBlock[1:], key)
|
|
||||||
checksum := checksumKeyMaterial(key)
|
|
||||||
keyBlock[1+len(key)] = byte(checksum >> 8)
|
|
||||||
keyBlock[1+len(key)+1] = byte(checksum)
|
|
||||||
|
|
||||||
switch pub.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
|
|
||||||
case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
|
|
||||||
return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
|
|
||||||
cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
|
|
||||||
if err != nil {
|
|
||||||
return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
|
|
||||||
|
|
||||||
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(header[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeMPI(w, 8*uint16(len(cipherText)), cipherText)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
|
|
||||||
c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
|
|
||||||
if err != nil {
|
|
||||||
return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
packetLen := 10 /* header length */
|
|
||||||
packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
|
|
||||||
packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
|
|
||||||
|
|
||||||
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(header[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writeBig(w, c1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeBig(w, c2)
|
|
||||||
}
|
|
||||||
89
vendor/golang.org/x/crypto/openpgp/packet/literal.go
generated
vendored
89
vendor/golang.org/x/crypto/openpgp/packet/literal.go
generated
vendored
@@ -1,89 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
|
|
||||||
type LiteralData struct {
|
|
||||||
IsBinary bool
|
|
||||||
FileName string
|
|
||||||
Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
|
|
||||||
Body io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForEyesOnly returns whether the contents of the LiteralData have been marked
|
|
||||||
// as especially sensitive.
|
|
||||||
func (l *LiteralData) ForEyesOnly() bool {
|
|
||||||
return l.FileName == "_CONSOLE"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LiteralData) parse(r io.Reader) (err error) {
|
|
||||||
var buf [256]byte
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.IsBinary = buf[0] == 'b'
|
|
||||||
fileNameLen := int(buf[1])
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:fileNameLen])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.FileName = string(buf[:fileNameLen])
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Time = binary.BigEndian.Uint32(buf[:4])
|
|
||||||
l.Body = r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeLiteral serializes a literal data packet to w and returns a
|
|
||||||
// WriteCloser to which the data itself can be written and which MUST be closed
|
|
||||||
// on completion. The fileName is truncated to 255 bytes.
|
|
||||||
func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
buf[0] = 't'
|
|
||||||
if isBinary {
|
|
||||||
buf[0] = 'b'
|
|
||||||
}
|
|
||||||
if len(fileName) > 255 {
|
|
||||||
fileName = fileName[:255]
|
|
||||||
}
|
|
||||||
buf[1] = byte(len(fileName))
|
|
||||||
|
|
||||||
inner, err := serializeStreamHeader(w, packetTypeLiteralData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = inner.Write(buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = inner.Write([]byte(fileName))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(buf[:], time)
|
|
||||||
_, err = inner.Write(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext = inner
|
|
||||||
return
|
|
||||||
}
|
|
||||||
143
vendor/golang.org/x/crypto/openpgp/packet/ocfb.go
generated
vendored
143
vendor/golang.org/x/crypto/openpgp/packet/ocfb.go
generated
vendored
@@ -1,143 +0,0 @@
|
|||||||
// Copyright 2010 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.
|
|
||||||
|
|
||||||
// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ocfbEncrypter struct {
|
|
||||||
b cipher.Block
|
|
||||||
fre []byte
|
|
||||||
outUsed int
|
|
||||||
}
|
|
||||||
|
|
||||||
// An OCFBResyncOption determines if the "resynchronization step" of OCFB is
|
|
||||||
// performed.
|
|
||||||
type OCFBResyncOption bool
|
|
||||||
|
|
||||||
const (
|
|
||||||
OCFBResync OCFBResyncOption = true
|
|
||||||
OCFBNoResync OCFBResyncOption = false
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's
|
|
||||||
// cipher feedback mode using the given cipher.Block, and an initial amount of
|
|
||||||
// ciphertext. randData must be random bytes and be the same length as the
|
|
||||||
// cipher.Block's block size. Resync determines if the "resynchronization step"
|
|
||||||
// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on
|
|
||||||
// this point.
|
|
||||||
func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) {
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
if len(randData) != blockSize {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
x := &ocfbEncrypter{
|
|
||||||
b: block,
|
|
||||||
fre: make([]byte, blockSize),
|
|
||||||
outUsed: 0,
|
|
||||||
}
|
|
||||||
prefix := make([]byte, blockSize+2)
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, x.fre)
|
|
||||||
for i := 0; i < blockSize; i++ {
|
|
||||||
prefix[i] = randData[i] ^ x.fre[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, prefix[:blockSize])
|
|
||||||
prefix[blockSize] = x.fre[0] ^ randData[blockSize-2]
|
|
||||||
prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1]
|
|
||||||
|
|
||||||
if resync {
|
|
||||||
block.Encrypt(x.fre, prefix[2:])
|
|
||||||
} else {
|
|
||||||
x.fre[0] = prefix[blockSize]
|
|
||||||
x.fre[1] = prefix[blockSize+1]
|
|
||||||
x.outUsed = 2
|
|
||||||
}
|
|
||||||
return x, prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) {
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if x.outUsed == len(x.fre) {
|
|
||||||
x.b.Encrypt(x.fre, x.fre)
|
|
||||||
x.outUsed = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
x.fre[x.outUsed] ^= src[i]
|
|
||||||
dst[i] = x.fre[x.outUsed]
|
|
||||||
x.outUsed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ocfbDecrypter struct {
|
|
||||||
b cipher.Block
|
|
||||||
fre []byte
|
|
||||||
outUsed int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's
|
|
||||||
// cipher feedback mode using the given cipher.Block. Prefix must be the first
|
|
||||||
// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's
|
|
||||||
// block size. If an incorrect key is detected then nil is returned. On
|
|
||||||
// successful exit, blockSize+2 bytes of decrypted data are written into
|
|
||||||
// prefix. Resync determines if the "resynchronization step" from RFC 4880,
|
|
||||||
// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point.
|
|
||||||
func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream {
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
if len(prefix) != blockSize+2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
x := &ocfbDecrypter{
|
|
||||||
b: block,
|
|
||||||
fre: make([]byte, blockSize),
|
|
||||||
outUsed: 0,
|
|
||||||
}
|
|
||||||
prefixCopy := make([]byte, len(prefix))
|
|
||||||
copy(prefixCopy, prefix)
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, x.fre)
|
|
||||||
for i := 0; i < blockSize; i++ {
|
|
||||||
prefixCopy[i] ^= x.fre[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, prefix[:blockSize])
|
|
||||||
prefixCopy[blockSize] ^= x.fre[0]
|
|
||||||
prefixCopy[blockSize+1] ^= x.fre[1]
|
|
||||||
|
|
||||||
if prefixCopy[blockSize-2] != prefixCopy[blockSize] ||
|
|
||||||
prefixCopy[blockSize-1] != prefixCopy[blockSize+1] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if resync {
|
|
||||||
block.Encrypt(x.fre, prefix[2:])
|
|
||||||
} else {
|
|
||||||
x.fre[0] = prefix[blockSize]
|
|
||||||
x.fre[1] = prefix[blockSize+1]
|
|
||||||
x.outUsed = 2
|
|
||||||
}
|
|
||||||
copy(prefix, prefixCopy)
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) {
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if x.outUsed == len(x.fre) {
|
|
||||||
x.b.Encrypt(x.fre, x.fre)
|
|
||||||
x.outUsed = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
c := src[i]
|
|
||||||
dst[i] = x.fre[x.outUsed] ^ src[i]
|
|
||||||
x.fre[x.outUsed] = c
|
|
||||||
x.outUsed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
vendor/golang.org/x/crypto/openpgp/packet/one_pass_signature.go
generated
vendored
73
vendor/golang.org/x/crypto/openpgp/packet/one_pass_signature.go
generated
vendored
@@ -1,73 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"encoding/binary"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
"golang.org/x/crypto/openpgp/s2k"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OnePassSignature represents a one-pass signature packet. See RFC 4880,
|
|
||||||
// section 5.4.
|
|
||||||
type OnePassSignature struct {
|
|
||||||
SigType SignatureType
|
|
||||||
Hash crypto.Hash
|
|
||||||
PubKeyAlgo PublicKeyAlgorithm
|
|
||||||
KeyId uint64
|
|
||||||
IsLast bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const onePassSignatureVersion = 3
|
|
||||||
|
|
||||||
func (ops *OnePassSignature) parse(r io.Reader) (err error) {
|
|
||||||
var buf [13]byte
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != onePassSignatureVersion {
|
|
||||||
err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
ops.Hash, ok = s2k.HashIdToHash(buf[2])
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
ops.SigType = SignatureType(buf[1])
|
|
||||||
ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
|
|
||||||
ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
|
|
||||||
ops.IsLast = buf[12] != 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals the given OnePassSignature to w.
|
|
||||||
func (ops *OnePassSignature) Serialize(w io.Writer) error {
|
|
||||||
var buf [13]byte
|
|
||||||
buf[0] = onePassSignatureVersion
|
|
||||||
buf[1] = uint8(ops.SigType)
|
|
||||||
var ok bool
|
|
||||||
buf[2], ok = s2k.HashToHashId(ops.Hash)
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
|
||||||
}
|
|
||||||
buf[3] = uint8(ops.PubKeyAlgo)
|
|
||||||
binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
|
|
||||||
if ops.IsLast {
|
|
||||||
buf[12] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := w.Write(buf[:])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
162
vendor/golang.org/x/crypto/openpgp/packet/opaque.go
generated
vendored
162
vendor/golang.org/x/crypto/openpgp/packet/opaque.go
generated
vendored
@@ -1,162 +0,0 @@
|
|||||||
// Copyright 2012 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is
|
|
||||||
// useful for splitting and storing the original packet contents separately,
|
|
||||||
// handling unsupported packet types or accessing parts of the packet not yet
|
|
||||||
// implemented by this package.
|
|
||||||
type OpaquePacket struct {
|
|
||||||
// Packet type
|
|
||||||
Tag uint8
|
|
||||||
// Reason why the packet was parsed opaquely
|
|
||||||
Reason error
|
|
||||||
// Binary contents of the packet data
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *OpaquePacket) parse(r io.Reader) (err error) {
|
|
||||||
op.Contents, err = ioutil.ReadAll(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals the packet to a writer in its original form, including
|
|
||||||
// the packet header.
|
|
||||||
func (op *OpaquePacket) Serialize(w io.Writer) (err error) {
|
|
||||||
err = serializeHeader(w, packetType(op.Tag), len(op.Contents))
|
|
||||||
if err == nil {
|
|
||||||
_, err = w.Write(op.Contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse attempts to parse the opaque contents into a structure supported by
|
|
||||||
// this package. If the packet is not known then the result will be another
|
|
||||||
// OpaquePacket.
|
|
||||||
func (op *OpaquePacket) Parse() (p Packet, err error) {
|
|
||||||
hdr := bytes.NewBuffer(nil)
|
|
||||||
err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents))
|
|
||||||
if err != nil {
|
|
||||||
op.Reason = err
|
|
||||||
return op, err
|
|
||||||
}
|
|
||||||
p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents)))
|
|
||||||
if err != nil {
|
|
||||||
op.Reason = err
|
|
||||||
p = op
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueReader reads OpaquePackets from an io.Reader.
|
|
||||||
type OpaqueReader struct {
|
|
||||||
r io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpaqueReader(r io.Reader) *OpaqueReader {
|
|
||||||
return &OpaqueReader{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next OpaquePacket.
|
|
||||||
func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
|
|
||||||
tag, _, contents, err := readHeader(or.r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
op = &OpaquePacket{Tag: uint8(tag), Reason: err}
|
|
||||||
err = op.parse(contents)
|
|
||||||
if err != nil {
|
|
||||||
consumeAll(contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueSubpacket represents an unparsed OpenPGP subpacket,
|
|
||||||
// as found in signature and user attribute packets.
|
|
||||||
type OpaqueSubpacket struct {
|
|
||||||
SubType uint8
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
|
|
||||||
// their byte representation.
|
|
||||||
func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
|
|
||||||
var (
|
|
||||||
subHeaderLen int
|
|
||||||
subPacket *OpaqueSubpacket
|
|
||||||
)
|
|
||||||
for len(contents) > 0 {
|
|
||||||
subHeaderLen, subPacket, err = nextSubpacket(contents)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result = append(result, subPacket)
|
|
||||||
contents = contents[subHeaderLen+len(subPacket.Contents):]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
|
|
||||||
// RFC 4880, section 5.2.3.1
|
|
||||||
var subLen uint32
|
|
||||||
if len(contents) < 1 {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subPacket = &OpaqueSubpacket{}
|
|
||||||
switch {
|
|
||||||
case contents[0] < 192:
|
|
||||||
subHeaderLen = 2 // 1 length byte, 1 subtype byte
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subLen = uint32(contents[0])
|
|
||||||
contents = contents[1:]
|
|
||||||
case contents[0] < 255:
|
|
||||||
subHeaderLen = 3 // 2 length bytes, 1 subtype
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
|
|
||||||
contents = contents[2:]
|
|
||||||
default:
|
|
||||||
subHeaderLen = 6 // 5 length bytes, 1 subtype
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subLen = uint32(contents[1])<<24 |
|
|
||||||
uint32(contents[2])<<16 |
|
|
||||||
uint32(contents[3])<<8 |
|
|
||||||
uint32(contents[4])
|
|
||||||
contents = contents[5:]
|
|
||||||
}
|
|
||||||
if subLen > uint32(len(contents)) || subLen == 0 {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subPacket.SubType = contents[0]
|
|
||||||
subPacket.Contents = contents[1:subLen]
|
|
||||||
return
|
|
||||||
Truncated:
|
|
||||||
err = errors.StructuralError("subpacket truncated")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
|
|
||||||
buf := make([]byte, 6)
|
|
||||||
n := serializeSubpacketLength(buf, len(osp.Contents)+1)
|
|
||||||
buf[n] = osp.SubType
|
|
||||||
if _, err = w.Write(buf[:n+1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(osp.Contents)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
549
vendor/golang.org/x/crypto/openpgp/packet/packet.go
generated
vendored
549
vendor/golang.org/x/crypto/openpgp/packet/packet.go
generated
vendored
@@ -1,549 +0,0 @@
|
|||||||
// Copyright 2011 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 packet implements parsing and serialization of OpenPGP packets, as
|
|
||||||
// specified in RFC 4880.
|
|
||||||
package packet // import "golang.org/x/crypto/openpgp/packet"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"crypto/rsa"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cast5"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// readFull is the same as io.ReadFull except that reading zero bytes returns
|
|
||||||
// ErrUnexpectedEOF rather than EOF.
|
|
||||||
func readFull(r io.Reader, buf []byte) (n int, err error) {
|
|
||||||
n, err = io.ReadFull(r, buf)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
|
|
||||||
func readLength(r io.Reader) (length int64, isPartial bool, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
_, err = readFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case buf[0] < 192:
|
|
||||||
length = int64(buf[0])
|
|
||||||
case buf[0] < 224:
|
|
||||||
length = int64(buf[0]-192) << 8
|
|
||||||
_, err = readFull(r, buf[0:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length += int64(buf[0]) + 192
|
|
||||||
case buf[0] < 255:
|
|
||||||
length = int64(1) << (buf[0] & 0x1f)
|
|
||||||
isPartial = true
|
|
||||||
default:
|
|
||||||
_, err = readFull(r, buf[0:4])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length = int64(buf[0])<<24 |
|
|
||||||
int64(buf[1])<<16 |
|
|
||||||
int64(buf[2])<<8 |
|
|
||||||
int64(buf[3])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
|
|
||||||
// The continuation lengths are parsed and removed from the stream and EOF is
|
|
||||||
// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
|
|
||||||
type partialLengthReader struct {
|
|
||||||
r io.Reader
|
|
||||||
remaining int64
|
|
||||||
isPartial bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *partialLengthReader) Read(p []byte) (n int, err error) {
|
|
||||||
for r.remaining == 0 {
|
|
||||||
if !r.isPartial {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
r.remaining, r.isPartial, err = readLength(r.r)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toRead := int64(len(p))
|
|
||||||
if toRead > r.remaining {
|
|
||||||
toRead = r.remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = r.r.Read(p[:int(toRead)])
|
|
||||||
r.remaining -= int64(n)
|
|
||||||
if n < int(toRead) && err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
|
|
||||||
// See RFC 4880, section 4.2.2.4.
|
|
||||||
type partialLengthWriter struct {
|
|
||||||
w io.WriteCloser
|
|
||||||
lengthByte [1]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
|
|
||||||
for len(p) > 0 {
|
|
||||||
for power := uint(14); power < 32; power-- {
|
|
||||||
l := 1 << power
|
|
||||||
if len(p) >= l {
|
|
||||||
w.lengthByte[0] = 224 + uint8(power)
|
|
||||||
_, err = w.w.Write(w.lengthByte[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var m int
|
|
||||||
m, err = w.w.Write(p[:l])
|
|
||||||
n += m
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p = p[l:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *partialLengthWriter) Close() error {
|
|
||||||
w.lengthByte[0] = 0
|
|
||||||
_, err := w.w.Write(w.lengthByte[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
|
|
||||||
// underlying Reader returns EOF before the limit has been reached.
|
|
||||||
type spanReader struct {
|
|
||||||
r io.Reader
|
|
||||||
n int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *spanReader) Read(p []byte) (n int, err error) {
|
|
||||||
if l.n <= 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
if int64(len(p)) > l.n {
|
|
||||||
p = p[0:l.n]
|
|
||||||
}
|
|
||||||
n, err = l.r.Read(p)
|
|
||||||
l.n -= int64(n)
|
|
||||||
if l.n > 0 && err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readHeader parses a packet header and returns an io.Reader which will return
|
|
||||||
// the contents of the packet. See RFC 4880, section 4.2.
|
|
||||||
func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
_, err = io.ReadFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0]&0x80 == 0 {
|
|
||||||
err = errors.StructuralError("tag byte does not have MSB set")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0]&0x40 == 0 {
|
|
||||||
// Old format packet
|
|
||||||
tag = packetType((buf[0] & 0x3f) >> 2)
|
|
||||||
lengthType := buf[0] & 3
|
|
||||||
if lengthType == 3 {
|
|
||||||
length = -1
|
|
||||||
contents = r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lengthBytes := 1 << lengthType
|
|
||||||
_, err = readFull(r, buf[0:lengthBytes])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < lengthBytes; i++ {
|
|
||||||
length <<= 8
|
|
||||||
length |= int64(buf[i])
|
|
||||||
}
|
|
||||||
contents = &spanReader{r, length}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// New format packet
|
|
||||||
tag = packetType(buf[0] & 0x3f)
|
|
||||||
length, isPartial, err := readLength(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isPartial {
|
|
||||||
contents = &partialLengthReader{
|
|
||||||
remaining: length,
|
|
||||||
isPartial: true,
|
|
||||||
r: r,
|
|
||||||
}
|
|
||||||
length = -1
|
|
||||||
} else {
|
|
||||||
contents = &spanReader{r, length}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
|
|
||||||
// 4.2.
|
|
||||||
func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
|
|
||||||
var buf [6]byte
|
|
||||||
var n int
|
|
||||||
|
|
||||||
buf[0] = 0x80 | 0x40 | byte(ptype)
|
|
||||||
if length < 192 {
|
|
||||||
buf[1] = byte(length)
|
|
||||||
n = 2
|
|
||||||
} else if length < 8384 {
|
|
||||||
length -= 192
|
|
||||||
buf[1] = 192 + byte(length>>8)
|
|
||||||
buf[2] = byte(length)
|
|
||||||
n = 3
|
|
||||||
} else {
|
|
||||||
buf[1] = 255
|
|
||||||
buf[2] = byte(length >> 24)
|
|
||||||
buf[3] = byte(length >> 16)
|
|
||||||
buf[4] = byte(length >> 8)
|
|
||||||
buf[5] = byte(length)
|
|
||||||
n = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = w.Write(buf[:n])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeStreamHeader writes an OpenPGP packet header to w where the
|
|
||||||
// length of the packet is unknown. It returns a io.WriteCloser which can be
|
|
||||||
// used to write the contents of the packet. See RFC 4880, section 4.2.
|
|
||||||
func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
|
|
||||||
var buf [1]byte
|
|
||||||
buf[0] = 0x80 | 0x40 | byte(ptype)
|
|
||||||
_, err = w.Write(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = &partialLengthWriter{w: w}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet represents an OpenPGP packet. Users are expected to try casting
|
|
||||||
// instances of this interface to specific packet types.
|
|
||||||
type Packet interface {
|
|
||||||
parse(io.Reader) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// consumeAll reads from the given Reader until error, returning the number of
|
|
||||||
// bytes read.
|
|
||||||
func consumeAll(r io.Reader) (n int64, err error) {
|
|
||||||
var m int
|
|
||||||
var buf [1024]byte
|
|
||||||
|
|
||||||
for {
|
|
||||||
m, err = r.Read(buf[:])
|
|
||||||
n += int64(m)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packetType represents the numeric ids of the different OpenPGP packet types. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
|
|
||||||
type packetType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
packetTypeEncryptedKey packetType = 1
|
|
||||||
packetTypeSignature packetType = 2
|
|
||||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
|
||||||
packetTypeOnePassSignature packetType = 4
|
|
||||||
packetTypePrivateKey packetType = 5
|
|
||||||
packetTypePublicKey packetType = 6
|
|
||||||
packetTypePrivateSubkey packetType = 7
|
|
||||||
packetTypeCompressed packetType = 8
|
|
||||||
packetTypeSymmetricallyEncrypted packetType = 9
|
|
||||||
packetTypeLiteralData packetType = 11
|
|
||||||
packetTypeUserId packetType = 13
|
|
||||||
packetTypePublicSubkey packetType = 14
|
|
||||||
packetTypeUserAttribute packetType = 17
|
|
||||||
packetTypeSymmetricallyEncryptedMDC packetType = 18
|
|
||||||
)
|
|
||||||
|
|
||||||
// peekVersion detects the version of a public key packet about to
|
|
||||||
// be read. A bufio.Reader at the original position of the io.Reader
|
|
||||||
// is returned.
|
|
||||||
func peekVersion(r io.Reader) (bufr *bufio.Reader, ver byte, err error) {
|
|
||||||
bufr = bufio.NewReader(r)
|
|
||||||
var verBuf []byte
|
|
||||||
if verBuf, err = bufr.Peek(1); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ver = verBuf[0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads a single OpenPGP packet from the given io.Reader. If there is an
|
|
||||||
// error parsing a packet, the whole packet is consumed from the input.
|
|
||||||
func Read(r io.Reader) (p Packet, err error) {
|
|
||||||
tag, _, contents, err := readHeader(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case packetTypeEncryptedKey:
|
|
||||||
p = new(EncryptedKey)
|
|
||||||
case packetTypeSignature:
|
|
||||||
var version byte
|
|
||||||
// Detect signature version
|
|
||||||
if contents, version, err = peekVersion(contents); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if version < 4 {
|
|
||||||
p = new(SignatureV3)
|
|
||||||
} else {
|
|
||||||
p = new(Signature)
|
|
||||||
}
|
|
||||||
case packetTypeSymmetricKeyEncrypted:
|
|
||||||
p = new(SymmetricKeyEncrypted)
|
|
||||||
case packetTypeOnePassSignature:
|
|
||||||
p = new(OnePassSignature)
|
|
||||||
case packetTypePrivateKey, packetTypePrivateSubkey:
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
if tag == packetTypePrivateSubkey {
|
|
||||||
pk.IsSubkey = true
|
|
||||||
}
|
|
||||||
p = pk
|
|
||||||
case packetTypePublicKey, packetTypePublicSubkey:
|
|
||||||
var version byte
|
|
||||||
if contents, version, err = peekVersion(contents); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isSubkey := tag == packetTypePublicSubkey
|
|
||||||
if version < 4 {
|
|
||||||
p = &PublicKeyV3{IsSubkey: isSubkey}
|
|
||||||
} else {
|
|
||||||
p = &PublicKey{IsSubkey: isSubkey}
|
|
||||||
}
|
|
||||||
case packetTypeCompressed:
|
|
||||||
p = new(Compressed)
|
|
||||||
case packetTypeSymmetricallyEncrypted:
|
|
||||||
p = new(SymmetricallyEncrypted)
|
|
||||||
case packetTypeLiteralData:
|
|
||||||
p = new(LiteralData)
|
|
||||||
case packetTypeUserId:
|
|
||||||
p = new(UserId)
|
|
||||||
case packetTypeUserAttribute:
|
|
||||||
p = new(UserAttribute)
|
|
||||||
case packetTypeSymmetricallyEncryptedMDC:
|
|
||||||
se := new(SymmetricallyEncrypted)
|
|
||||||
se.MDC = true
|
|
||||||
p = se
|
|
||||||
default:
|
|
||||||
err = errors.UnknownPacketTypeError(tag)
|
|
||||||
}
|
|
||||||
if p != nil {
|
|
||||||
err = p.parse(contents)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
consumeAll(contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignatureType represents the different semantic meanings of an OpenPGP
|
|
||||||
// signature. See RFC 4880, section 5.2.1.
|
|
||||||
type SignatureType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
SigTypeBinary SignatureType = 0
|
|
||||||
SigTypeText = 1
|
|
||||||
SigTypeGenericCert = 0x10
|
|
||||||
SigTypePersonaCert = 0x11
|
|
||||||
SigTypeCasualCert = 0x12
|
|
||||||
SigTypePositiveCert = 0x13
|
|
||||||
SigTypeSubkeyBinding = 0x18
|
|
||||||
SigTypePrimaryKeyBinding = 0x19
|
|
||||||
SigTypeDirectSignature = 0x1F
|
|
||||||
SigTypeKeyRevocation = 0x20
|
|
||||||
SigTypeSubkeyRevocation = 0x28
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKeyAlgorithm represents the different public key system specified for
|
|
||||||
// OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
|
|
||||||
type PublicKeyAlgorithm uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
PubKeyAlgoRSA PublicKeyAlgorithm = 1
|
|
||||||
PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
|
|
||||||
PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3
|
|
||||||
PubKeyAlgoElGamal PublicKeyAlgorithm = 16
|
|
||||||
PubKeyAlgoDSA PublicKeyAlgorithm = 17
|
|
||||||
// RFC 6637, Section 5.
|
|
||||||
PubKeyAlgoECDH PublicKeyAlgorithm = 18
|
|
||||||
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
|
|
||||||
)
|
|
||||||
|
|
||||||
// CanEncrypt returns true if it's possible to encrypt a message to a public
|
|
||||||
// key of the given type.
|
|
||||||
func (pka PublicKeyAlgorithm) CanEncrypt() bool {
|
|
||||||
switch pka {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSign returns true if it's possible for a public key of the given type to
|
|
||||||
// sign a message.
|
|
||||||
func (pka PublicKeyAlgorithm) CanSign() bool {
|
|
||||||
switch pka {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CipherFunction represents the different block ciphers specified for OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
|
|
||||||
type CipherFunction uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
Cipher3DES CipherFunction = 2
|
|
||||||
CipherCAST5 CipherFunction = 3
|
|
||||||
CipherAES128 CipherFunction = 7
|
|
||||||
CipherAES192 CipherFunction = 8
|
|
||||||
CipherAES256 CipherFunction = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeySize returns the key size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) KeySize() int {
|
|
||||||
switch cipher {
|
|
||||||
case Cipher3DES:
|
|
||||||
return 24
|
|
||||||
case CipherCAST5:
|
|
||||||
return cast5.KeySize
|
|
||||||
case CipherAES128:
|
|
||||||
return 16
|
|
||||||
case CipherAES192:
|
|
||||||
return 24
|
|
||||||
case CipherAES256:
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockSize returns the block size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) blockSize() int {
|
|
||||||
switch cipher {
|
|
||||||
case Cipher3DES:
|
|
||||||
return des.BlockSize
|
|
||||||
case CipherCAST5:
|
|
||||||
return 8
|
|
||||||
case CipherAES128, CipherAES192, CipherAES256:
|
|
||||||
return 16
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// new returns a fresh instance of the given cipher.
|
|
||||||
func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
|
|
||||||
switch cipher {
|
|
||||||
case Cipher3DES:
|
|
||||||
block, _ = des.NewTripleDESCipher(key)
|
|
||||||
case CipherCAST5:
|
|
||||||
block, _ = cast5.NewCipher(key)
|
|
||||||
case CipherAES128, CipherAES192, CipherAES256:
|
|
||||||
block, _ = aes.NewCipher(key)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMPI reads a big integer from r. The bit length returned is the bit
|
|
||||||
// length that was specified in r. This is preserved so that the integer can be
|
|
||||||
// reserialized exactly.
|
|
||||||
func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) {
|
|
||||||
var buf [2]byte
|
|
||||||
_, err = readFull(r, buf[0:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bitLength = uint16(buf[0])<<8 | uint16(buf[1])
|
|
||||||
numBytes := (int(bitLength) + 7) / 8
|
|
||||||
mpi = make([]byte, numBytes)
|
|
||||||
_, err = readFull(r, mpi)
|
|
||||||
// According to RFC 4880 3.2. we should check that the MPI has no leading
|
|
||||||
// zeroes (at least when not an encrypted MPI?), but this implementation
|
|
||||||
// does generate leading zeroes, so we keep accepting them.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeMPI serializes a big integer to w.
|
|
||||||
func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) {
|
|
||||||
// Note that we can produce leading zeroes, in violation of RFC 4880 3.2.
|
|
||||||
// Implementations seem to be tolerant of them, and stripping them would
|
|
||||||
// make it complex to guarantee matching re-serialization.
|
|
||||||
_, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)})
|
|
||||||
if err == nil {
|
|
||||||
_, err = w.Write(mpiBytes)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeBig serializes a *big.Int to w.
|
|
||||||
func writeBig(w io.Writer, i *big.Int) error {
|
|
||||||
return writeMPI(w, uint16(i.BitLen()), i.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// padToKeySize left-pads a MPI with zeroes to match the length of the
|
|
||||||
// specified RSA public.
|
|
||||||
func padToKeySize(pub *rsa.PublicKey, b []byte) []byte {
|
|
||||||
k := (pub.N.BitLen() + 7) / 8
|
|
||||||
if len(b) >= k {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
bb := make([]byte, k)
|
|
||||||
copy(bb[len(bb)-len(b):], b)
|
|
||||||
return bb
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompressionAlgo Represents the different compression algorithms
|
|
||||||
// supported by OpenPGP (except for BZIP2, which is not currently
|
|
||||||
// supported). See Section 9.3 of RFC 4880.
|
|
||||||
type CompressionAlgo uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
CompressionNone CompressionAlgo = 0
|
|
||||||
CompressionZIP CompressionAlgo = 1
|
|
||||||
CompressionZLIB CompressionAlgo = 2
|
|
||||||
)
|
|
||||||
380
vendor/golang.org/x/crypto/openpgp/packet/private_key.go
generated
vendored
380
vendor/golang.org/x/crypto/openpgp/packet/private_key.go
generated
vendored
@@ -1,380 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/elgamal"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
"golang.org/x/crypto/openpgp/s2k"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivateKey represents a possibly encrypted private key. See RFC 4880,
|
|
||||||
// section 5.5.3.
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
Encrypted bool // if true then the private key is unavailable until Decrypt has been called.
|
|
||||||
encryptedData []byte
|
|
||||||
cipher CipherFunction
|
|
||||||
s2k func(out, in []byte)
|
|
||||||
PrivateKey interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or a crypto.Signer.
|
|
||||||
sha1Checksum bool
|
|
||||||
iv []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewElGamalPrivateKey(currentTime time.Time, priv *elgamal.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewElGamalPublicKey(currentTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDSAPrivateKey(currentTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewECDSAPublicKey(currentTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerPrivateKey creates a sign-only PrivateKey from a crypto.Signer that
|
|
||||||
// implements RSA or ECDSA.
|
|
||||||
func NewSignerPrivateKey(currentTime time.Time, signer crypto.Signer) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
switch pubkey := signer.Public().(type) {
|
|
||||||
case rsa.PublicKey:
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(currentTime, &pubkey)
|
|
||||||
pk.PubKeyAlgo = PubKeyAlgoRSASignOnly
|
|
||||||
case ecdsa.PublicKey:
|
|
||||||
pk.PublicKey = *NewECDSAPublicKey(currentTime, &pubkey)
|
|
||||||
default:
|
|
||||||
panic("openpgp: unknown crypto.Signer type in NewSignerPrivateKey")
|
|
||||||
}
|
|
||||||
pk.PrivateKey = signer
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
|
||||||
err = (&pk.PublicKey).parse(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var buf [1]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s2kType := buf[0]
|
|
||||||
|
|
||||||
switch s2kType {
|
|
||||||
case 0:
|
|
||||||
pk.s2k = nil
|
|
||||||
pk.Encrypted = false
|
|
||||||
case 254, 255:
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.cipher = CipherFunction(buf[0])
|
|
||||||
pk.Encrypted = true
|
|
||||||
pk.s2k, err = s2k.Parse(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s2kType == 254 {
|
|
||||||
pk.sha1Checksum = true
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.UnsupportedError("deprecated s2k function in private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.Encrypted {
|
|
||||||
blockSize := pk.cipher.blockSize()
|
|
||||||
if blockSize == 0 {
|
|
||||||
return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
|
|
||||||
}
|
|
||||||
pk.iv = make([]byte, blockSize)
|
|
||||||
_, err = readFull(r, pk.iv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.encryptedData, err = ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pk.Encrypted {
|
|
||||||
return pk.parsePrivateKey(pk.encryptedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func mod64kHash(d []byte) uint16 {
|
|
||||||
var h uint16
|
|
||||||
for _, b := range d {
|
|
||||||
h += uint16(b)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
|
|
||||||
// TODO(agl): support encrypted private keys
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
err = pk.PublicKey.serializeWithoutHeaders(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf.WriteByte(0 /* no encryption */)
|
|
||||||
|
|
||||||
privateKeyBuf := bytes.NewBuffer(nil)
|
|
||||||
|
|
||||||
switch priv := pk.PrivateKey.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
err = serializeRSAPrivateKey(privateKeyBuf, priv)
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
err = serializeDSAPrivateKey(privateKeyBuf, priv)
|
|
||||||
case *elgamal.PrivateKey:
|
|
||||||
err = serializeElGamalPrivateKey(privateKeyBuf, priv)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
err = serializeECDSAPrivateKey(privateKeyBuf, priv)
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("unknown private key type")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ptype := packetTypePrivateKey
|
|
||||||
contents := buf.Bytes()
|
|
||||||
privateKeyBytes := privateKeyBuf.Bytes()
|
|
||||||
if pk.IsSubkey {
|
|
||||||
ptype = packetTypePrivateSubkey
|
|
||||||
}
|
|
||||||
err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(contents)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(privateKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
checksum := mod64kHash(privateKeyBytes)
|
|
||||||
var checksumBytes [2]byte
|
|
||||||
checksumBytes[0] = byte(checksum >> 8)
|
|
||||||
checksumBytes[1] = byte(checksum)
|
|
||||||
_, err = w.Write(checksumBytes[:])
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
|
|
||||||
err := writeBig(w, priv.D)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writeBig(w, priv.Primes[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writeBig(w, priv.Primes[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeBig(w, priv.Precomputed.Qinv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
|
|
||||||
return writeBig(w, priv.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
|
|
||||||
return writeBig(w, priv.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
|
|
||||||
return writeBig(w, priv.D)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
|
||||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|
||||||
if !pk.Encrypted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
key := make([]byte, pk.cipher.KeySize())
|
|
||||||
pk.s2k(key, passphrase)
|
|
||||||
block := pk.cipher.new(key)
|
|
||||||
cfb := cipher.NewCFBDecrypter(block, pk.iv)
|
|
||||||
|
|
||||||
data := make([]byte, len(pk.encryptedData))
|
|
||||||
cfb.XORKeyStream(data, pk.encryptedData)
|
|
||||||
|
|
||||||
if pk.sha1Checksum {
|
|
||||||
if len(data) < sha1.Size {
|
|
||||||
return errors.StructuralError("truncated private key data")
|
|
||||||
}
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(data[:len(data)-sha1.Size])
|
|
||||||
sum := h.Sum(nil)
|
|
||||||
if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
|
|
||||||
return errors.StructuralError("private key checksum failure")
|
|
||||||
}
|
|
||||||
data = data[:len(data)-sha1.Size]
|
|
||||||
} else {
|
|
||||||
if len(data) < 2 {
|
|
||||||
return errors.StructuralError("truncated private key data")
|
|
||||||
}
|
|
||||||
var sum uint16
|
|
||||||
for i := 0; i < len(data)-2; i++ {
|
|
||||||
sum += uint16(data[i])
|
|
||||||
}
|
|
||||||
if data[len(data)-2] != uint8(sum>>8) ||
|
|
||||||
data[len(data)-1] != uint8(sum) {
|
|
||||||
return errors.StructuralError("private key checksum failure")
|
|
||||||
}
|
|
||||||
data = data[:len(data)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
return pk.parsePrivateKey(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
|
|
||||||
switch pk.PublicKey.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
return pk.parseRSAPrivateKey(data)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
return pk.parseDSAPrivateKey(data)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
return pk.parseElGamalPrivateKey(data)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
return pk.parseECDSAPrivateKey(data)
|
|
||||||
}
|
|
||||||
panic("impossible")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
|
|
||||||
rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
|
|
||||||
rsaPriv := new(rsa.PrivateKey)
|
|
||||||
rsaPriv.PublicKey = *rsaPub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
q, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rsaPriv.D = new(big.Int).SetBytes(d)
|
|
||||||
rsaPriv.Primes = make([]*big.Int, 2)
|
|
||||||
rsaPriv.Primes[0] = new(big.Int).SetBytes(p)
|
|
||||||
rsaPriv.Primes[1] = new(big.Int).SetBytes(q)
|
|
||||||
if err := rsaPriv.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rsaPriv.Precompute()
|
|
||||||
pk.PrivateKey = rsaPriv
|
|
||||||
pk.Encrypted = false
|
|
||||||
pk.encryptedData = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
|
|
||||||
dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
|
|
||||||
dsaPriv := new(dsa.PrivateKey)
|
|
||||||
dsaPriv.PublicKey = *dsaPub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
x, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dsaPriv.X = new(big.Int).SetBytes(x)
|
|
||||||
pk.PrivateKey = dsaPriv
|
|
||||||
pk.Encrypted = false
|
|
||||||
pk.encryptedData = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
|
|
||||||
pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
|
|
||||||
priv := new(elgamal.PrivateKey)
|
|
||||||
priv.PublicKey = *pub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
x, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
priv.X = new(big.Int).SetBytes(x)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
pk.Encrypted = false
|
|
||||||
pk.encryptedData = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
|
|
||||||
ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d, _, err := readMPI(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.PrivateKey = &ecdsa.PrivateKey{
|
|
||||||
PublicKey: *ecdsaPub,
|
|
||||||
D: new(big.Int).SetBytes(d),
|
|
||||||
}
|
|
||||||
pk.Encrypted = false
|
|
||||||
pk.encryptedData = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
753
vendor/golang.org/x/crypto/openpgp/packet/public_key.go
generated
vendored
753
vendor/golang.org/x/crypto/openpgp/packet/public_key.go
generated
vendored
@@ -1,753 +0,0 @@
|
|||||||
// Copyright 2011 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/elgamal"
|
|
||||||
"golang.org/x/crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NIST curve P-256
|
|
||||||
oidCurveP256 []byte = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}
|
|
||||||
// NIST curve P-384
|
|
||||||
oidCurveP384 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x22}
|
|
||||||
// NIST curve P-521
|
|
||||||
oidCurveP521 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x23}
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxOIDLength = 8
|
|
||||||
|
|
||||||
// ecdsaKey stores the algorithm-specific fields for ECDSA keys.
|
|
||||||
// as defined in RFC 6637, Section 9.
|
|
||||||
type ecdsaKey struct {
|
|
||||||
// oid contains the OID byte sequence identifying the elliptic curve used
|
|
||||||
oid []byte
|
|
||||||
// p contains the elliptic curve point that represents the public key
|
|
||||||
p parsedMPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseOID reads the OID for the curve as defined in RFC 6637, Section 9.
|
|
||||||
func parseOID(r io.Reader) (oid []byte, err error) {
|
|
||||||
buf := make([]byte, maxOIDLength)
|
|
||||||
if _, err = readFull(r, buf[:1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
oidLen := buf[0]
|
|
||||||
if int(oidLen) > len(buf) {
|
|
||||||
err = errors.UnsupportedError("invalid oid length: " + strconv.Itoa(int(oidLen)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
oid = buf[:oidLen]
|
|
||||||
_, err = readFull(r, oid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdsaKey) parse(r io.Reader) (err error) {
|
|
||||||
if f.oid, err = parseOID(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.p.bytes, f.p.bitLength, err = readMPI(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdsaKey) serialize(w io.Writer) (err error) {
|
|
||||||
buf := make([]byte, maxOIDLength+1)
|
|
||||||
buf[0] = byte(len(f.oid))
|
|
||||||
copy(buf[1:], f.oid)
|
|
||||||
if _, err = w.Write(buf[:len(f.oid)+1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return writeMPIs(w, f.p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdsaKey) newECDSA() (*ecdsa.PublicKey, error) {
|
|
||||||
var c elliptic.Curve
|
|
||||||
if bytes.Equal(f.oid, oidCurveP256) {
|
|
||||||
c = elliptic.P256()
|
|
||||||
} else if bytes.Equal(f.oid, oidCurveP384) {
|
|
||||||
c = elliptic.P384()
|
|
||||||
} else if bytes.Equal(f.oid, oidCurveP521) {
|
|
||||||
c = elliptic.P521()
|
|
||||||
} else {
|
|
||||||
return nil, errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", f.oid))
|
|
||||||
}
|
|
||||||
x, y := elliptic.Unmarshal(c, f.p.bytes)
|
|
||||||
if x == nil {
|
|
||||||
return nil, errors.UnsupportedError("failed to parse EC point")
|
|
||||||
}
|
|
||||||
return &ecdsa.PublicKey{Curve: c, X: x, Y: y}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdsaKey) byteLen() int {
|
|
||||||
return 1 + len(f.oid) + 2 + len(f.p.bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type kdfHashFunction byte
|
|
||||||
type kdfAlgorithm byte
|
|
||||||
|
|
||||||
// ecdhKdf stores key derivation function parameters
|
|
||||||
// used for ECDH encryption. See RFC 6637, Section 9.
|
|
||||||
type ecdhKdf struct {
|
|
||||||
KdfHash kdfHashFunction
|
|
||||||
KdfAlgo kdfAlgorithm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdhKdf) parse(r io.Reader) (err error) {
|
|
||||||
buf := make([]byte, 1)
|
|
||||||
if _, err = readFull(r, buf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kdfLen := int(buf[0])
|
|
||||||
if kdfLen < 3 {
|
|
||||||
return errors.UnsupportedError("Unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
|
|
||||||
}
|
|
||||||
buf = make([]byte, kdfLen)
|
|
||||||
if _, err = readFull(r, buf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reserved := int(buf[0])
|
|
||||||
f.KdfHash = kdfHashFunction(buf[1])
|
|
||||||
f.KdfAlgo = kdfAlgorithm(buf[2])
|
|
||||||
if reserved != 0x01 {
|
|
||||||
return errors.UnsupportedError("Unsupported KDF reserved field: " + strconv.Itoa(reserved))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdhKdf) serialize(w io.Writer) (err error) {
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
// See RFC 6637, Section 9, Algorithm-Specific Fields for ECDH keys.
|
|
||||||
buf[0] = byte(0x03) // Length of the following fields
|
|
||||||
buf[1] = byte(0x01) // Reserved for future extensions, must be 1 for now
|
|
||||||
buf[2] = byte(f.KdfHash)
|
|
||||||
buf[3] = byte(f.KdfAlgo)
|
|
||||||
_, err = w.Write(buf[:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ecdhKdf) byteLen() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
|
|
||||||
type PublicKey struct {
|
|
||||||
CreationTime time.Time
|
|
||||||
PubKeyAlgo PublicKeyAlgorithm
|
|
||||||
PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
Fingerprint [20]byte
|
|
||||||
KeyId uint64
|
|
||||||
IsSubkey bool
|
|
||||||
|
|
||||||
n, e, p, q, g, y parsedMPI
|
|
||||||
|
|
||||||
// RFC 6637 fields
|
|
||||||
ec *ecdsaKey
|
|
||||||
ecdh *ecdhKdf
|
|
||||||
}
|
|
||||||
|
|
||||||
// signingKey provides a convenient abstraction over signature verification
|
|
||||||
// for v3 and v4 public keys.
|
|
||||||
type signingKey interface {
|
|
||||||
SerializeSignaturePrefix(io.Writer)
|
|
||||||
serializeWithoutHeaders(io.Writer) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromBig(n *big.Int) parsedMPI {
|
|
||||||
return parsedMPI{
|
|
||||||
bytes: n.Bytes(),
|
|
||||||
bitLength: uint16(n.BitLen()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
|
|
||||||
func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoRSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
n: fromBig(pub.N),
|
|
||||||
e: fromBig(big.NewInt(int64(pub.E))),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerPrintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDSAPublicKey returns a PublicKey that wraps the given dsa.PublicKey.
|
|
||||||
func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoDSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: fromBig(pub.P),
|
|
||||||
q: fromBig(pub.Q),
|
|
||||||
g: fromBig(pub.G),
|
|
||||||
y: fromBig(pub.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerPrintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewElGamalPublicKey returns a PublicKey that wraps the given elgamal.PublicKey.
|
|
||||||
func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoElGamal,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: fromBig(pub.P),
|
|
||||||
g: fromBig(pub.G),
|
|
||||||
y: fromBig(pub.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerPrintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoECDSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
ec: new(ecdsaKey),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pub.Curve {
|
|
||||||
case elliptic.P256():
|
|
||||||
pk.ec.oid = oidCurveP256
|
|
||||||
case elliptic.P384():
|
|
||||||
pk.ec.oid = oidCurveP384
|
|
||||||
case elliptic.P521():
|
|
||||||
pk.ec.oid = oidCurveP521
|
|
||||||
default:
|
|
||||||
panic("unknown elliptic curve")
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.ec.p.bytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
|
||||||
|
|
||||||
// The bit length is 3 (for the 0x04 specifying an uncompressed key)
|
|
||||||
// plus two field elements (for x and y), which are rounded up to the
|
|
||||||
// nearest byte. See https://tools.ietf.org/html/rfc6637#section-6
|
|
||||||
fieldBytes := (pub.Curve.Params().BitSize + 7) & ^7
|
|
||||||
pk.ec.p.bitLength = uint16(3 + fieldBytes + fieldBytes)
|
|
||||||
|
|
||||||
pk.setFingerPrintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) parse(r io.Reader) (err error) {
|
|
||||||
// RFC 4880, section 5.5.2
|
|
||||||
var buf [6]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != 4 {
|
|
||||||
return errors.UnsupportedError("public key version")
|
|
||||||
}
|
|
||||||
pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
|
|
||||||
pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
err = pk.parseRSA(r)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
err = pk.parseDSA(r)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
err = pk.parseElGamal(r)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
pk.ec = new(ecdsaKey)
|
|
||||||
if err = pk.ec.parse(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pk.PublicKey, err = pk.ec.newECDSA()
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
pk.ec = new(ecdsaKey)
|
|
||||||
if err = pk.ec.parse(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.ecdh = new(ecdhKdf)
|
|
||||||
if err = pk.ecdh.parse(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// The ECDH key is stored in an ecdsa.PublicKey for convenience.
|
|
||||||
pk.PublicKey, err = pk.ec.newECDSA()
|
|
||||||
default:
|
|
||||||
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerPrintAndKeyId()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) setFingerPrintAndKeyId() {
|
|
||||||
// RFC 4880, section 12.2
|
|
||||||
fingerPrint := sha1.New()
|
|
||||||
pk.SerializeSignaturePrefix(fingerPrint)
|
|
||||||
pk.serializeWithoutHeaders(fingerPrint)
|
|
||||||
copy(pk.Fingerprint[:], fingerPrint.Sum(nil))
|
|
||||||
pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
|
|
||||||
// section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
|
|
||||||
pk.n.bytes, pk.n.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.e.bytes, pk.e.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pk.e.bytes) > 3 {
|
|
||||||
err = errors.UnsupportedError("large public exponent")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rsa := &rsa.PublicKey{
|
|
||||||
N: new(big.Int).SetBytes(pk.n.bytes),
|
|
||||||
E: 0,
|
|
||||||
}
|
|
||||||
for i := 0; i < len(pk.e.bytes); i++ {
|
|
||||||
rsa.E <<= 8
|
|
||||||
rsa.E |= int(pk.e.bytes[i])
|
|
||||||
}
|
|
||||||
pk.PublicKey = rsa
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
|
|
||||||
// section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
|
|
||||||
pk.p.bytes, pk.p.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.q.bytes, pk.q.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.g.bytes, pk.g.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.y.bytes, pk.y.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dsa := new(dsa.PublicKey)
|
|
||||||
dsa.P = new(big.Int).SetBytes(pk.p.bytes)
|
|
||||||
dsa.Q = new(big.Int).SetBytes(pk.q.bytes)
|
|
||||||
dsa.G = new(big.Int).SetBytes(pk.g.bytes)
|
|
||||||
dsa.Y = new(big.Int).SetBytes(pk.y.bytes)
|
|
||||||
pk.PublicKey = dsa
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseElGamal parses ElGamal public key material from the given Reader. See
|
|
||||||
// RFC 4880, section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
|
|
||||||
pk.p.bytes, pk.p.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.g.bytes, pk.g.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.y.bytes, pk.y.bitLength, err = readMPI(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
elgamal := new(elgamal.PublicKey)
|
|
||||||
elgamal.P = new(big.Int).SetBytes(pk.p.bytes)
|
|
||||||
elgamal.G = new(big.Int).SetBytes(pk.g.bytes)
|
|
||||||
elgamal.Y = new(big.Int).SetBytes(pk.y.bytes)
|
|
||||||
pk.PublicKey = elgamal
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
|
|
||||||
// The prefix is used when calculating a signature over this public key. See
|
|
||||||
// RFC 4880, section 5.2.4.
|
|
||||||
func (pk *PublicKey) SerializeSignaturePrefix(h io.Writer) {
|
|
||||||
var pLength uint16
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
pLength += 2 + uint16(len(pk.n.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.e.bytes))
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
pLength += 2 + uint16(len(pk.p.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.q.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.g.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.y.bytes))
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
pLength += 2 + uint16(len(pk.p.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.g.bytes))
|
|
||||||
pLength += 2 + uint16(len(pk.y.bytes))
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
pLength += uint16(pk.ec.byteLen())
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
pLength += uint16(pk.ec.byteLen())
|
|
||||||
pLength += uint16(pk.ecdh.byteLen())
|
|
||||||
default:
|
|
||||||
panic("unknown public key algorithm")
|
|
||||||
}
|
|
||||||
pLength += 6
|
|
||||||
h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) Serialize(w io.Writer) (err error) {
|
|
||||||
length := 6 // 6 byte header
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
length += 2 + len(pk.n.bytes)
|
|
||||||
length += 2 + len(pk.e.bytes)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
length += 2 + len(pk.p.bytes)
|
|
||||||
length += 2 + len(pk.q.bytes)
|
|
||||||
length += 2 + len(pk.g.bytes)
|
|
||||||
length += 2 + len(pk.y.bytes)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
length += 2 + len(pk.p.bytes)
|
|
||||||
length += 2 + len(pk.g.bytes)
|
|
||||||
length += 2 + len(pk.y.bytes)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
length += pk.ec.byteLen()
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
length += pk.ec.byteLen()
|
|
||||||
length += pk.ecdh.byteLen()
|
|
||||||
default:
|
|
||||||
panic("unknown public key algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
packetType := packetTypePublicKey
|
|
||||||
if pk.IsSubkey {
|
|
||||||
packetType = packetTypePublicSubkey
|
|
||||||
}
|
|
||||||
err = serializeHeader(w, packetType, length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return pk.serializeWithoutHeaders(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeWithoutHeaders marshals the PublicKey to w in the form of an
|
|
||||||
// OpenPGP public key packet, not including the packet header.
|
|
||||||
func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
|
|
||||||
var buf [6]byte
|
|
||||||
buf[0] = 4
|
|
||||||
t := uint32(pk.CreationTime.Unix())
|
|
||||||
buf[1] = byte(t >> 24)
|
|
||||||
buf[2] = byte(t >> 16)
|
|
||||||
buf[3] = byte(t >> 8)
|
|
||||||
buf[4] = byte(t)
|
|
||||||
buf[5] = byte(pk.PubKeyAlgo)
|
|
||||||
|
|
||||||
_, err = w.Write(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
return writeMPIs(w, pk.n, pk.e)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
return writeMPIs(w, pk.p, pk.g, pk.y)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
return pk.ec.serialize(w)
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
if err = pk.ec.serialize(w); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return pk.ecdh.serialize(w)
|
|
||||||
}
|
|
||||||
return errors.InvalidArgumentError("bad public-key algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSign returns true iff this public key can generate signatures
|
|
||||||
func (pk *PublicKey) CanSign() bool {
|
|
||||||
return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, of the data hashed into signed. signed is mutated by this call.
|
|
||||||
func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
|
|
||||||
if !pk.CanSign() {
|
|
||||||
return errors.InvalidArgumentError("public key cannot generate signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
signed.Write(sig.HashSuffix)
|
|
||||||
hashBytes := signed.Sum(nil)
|
|
||||||
|
|
||||||
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
|
|
||||||
return errors.SignatureError("hash tag doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.PubKeyAlgo != sig.PubKeyAlgo {
|
|
||||||
return errors.InvalidArgumentError("public key and signature use different algorithms")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
||||||
rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
|
|
||||||
err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.SignatureError("RSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
|
|
||||||
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
|
||||||
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
|
|
||||||
if len(hashBytes) > subgroupSize {
|
|
||||||
hashBytes = hashBytes[:subgroupSize]
|
|
||||||
}
|
|
||||||
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
|
|
||||||
return errors.SignatureError("DSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey)
|
|
||||||
if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.bytes), new(big.Int).SetBytes(sig.ECDSASigS.bytes)) {
|
|
||||||
return errors.SignatureError("ECDSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return errors.SignatureError("Unsupported public key algorithm used in signature")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, of the data hashed into signed. signed is mutated by this call.
|
|
||||||
func (pk *PublicKey) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
|
|
||||||
if !pk.CanSign() {
|
|
||||||
return errors.InvalidArgumentError("public key cannot generate signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix := make([]byte, 5)
|
|
||||||
suffix[0] = byte(sig.SigType)
|
|
||||||
binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
|
|
||||||
signed.Write(suffix)
|
|
||||||
hashBytes := signed.Sum(nil)
|
|
||||||
|
|
||||||
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
|
|
||||||
return errors.SignatureError("hash tag doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.PubKeyAlgo != sig.PubKeyAlgo {
|
|
||||||
return errors.InvalidArgumentError("public key and signature use different algorithms")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
||||||
rsaPublicKey := pk.PublicKey.(*rsa.PublicKey)
|
|
||||||
if err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes)); err != nil {
|
|
||||||
return errors.SignatureError("RSA verification failure")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
dsaPublicKey := pk.PublicKey.(*dsa.PublicKey)
|
|
||||||
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
|
||||||
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
|
|
||||||
if len(hashBytes) > subgroupSize {
|
|
||||||
hashBytes = hashBytes[:subgroupSize]
|
|
||||||
}
|
|
||||||
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
|
|
||||||
return errors.SignatureError("DSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
panic("shouldn't happen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keySignatureHash returns a Hash of the message that needs to be signed for
|
|
||||||
// pk to assert a subkey relationship to signed.
|
|
||||||
func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
pk.SerializeSignaturePrefix(h)
|
|
||||||
pk.serializeWithoutHeaders(h)
|
|
||||||
signed.SerializeSignaturePrefix(h)
|
|
||||||
signed.serializeWithoutHeaders(h)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyKeySignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, of signed.
|
|
||||||
func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error {
|
|
||||||
h, err := keySignatureHash(pk, signed, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = pk.VerifySignature(h, sig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig.FlagSign {
|
|
||||||
// Signing subkeys must be cross-signed. See
|
|
||||||
// https://www.gnupg.org/faq/subkey-cross-certify.html.
|
|
||||||
if sig.EmbeddedSignature == nil {
|
|
||||||
return errors.StructuralError("signing subkey is missing cross-signature")
|
|
||||||
}
|
|
||||||
// Verify the cross-signature. This is calculated over the same
|
|
||||||
// data as the main signature, so we cannot just recursively
|
|
||||||
// call signed.VerifyKeySignature(...)
|
|
||||||
if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil {
|
|
||||||
return errors.StructuralError("error while hashing for cross-signature: " + err.Error())
|
|
||||||
}
|
|
||||||
if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil {
|
|
||||||
return errors.StructuralError("error while verifying cross-signature: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
pk.SerializeSignaturePrefix(h)
|
|
||||||
pk.serializeWithoutHeaders(h)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key.
|
|
||||||
func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) {
|
|
||||||
h, err := keyRevocationHash(pk, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignature(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userIdSignatureHash returns a Hash of the message that needs to be signed
|
|
||||||
// to assert that pk is a valid key for id.
|
|
||||||
func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
pk.SerializeSignaturePrefix(h)
|
|
||||||
pk.serializeWithoutHeaders(h)
|
|
||||||
|
|
||||||
var buf [5]byte
|
|
||||||
buf[0] = 0xb4
|
|
||||||
buf[1] = byte(len(id) >> 24)
|
|
||||||
buf[2] = byte(len(id) >> 16)
|
|
||||||
buf[3] = byte(len(id) >> 8)
|
|
||||||
buf[4] = byte(len(id))
|
|
||||||
h.Write(buf[:])
|
|
||||||
h.Write([]byte(id))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, that id is the identity of pub.
|
|
||||||
func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) {
|
|
||||||
h, err := userIdSignatureHash(id, pub, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignature(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, that id is the identity of pub.
|
|
||||||
func (pk *PublicKey) VerifyUserIdSignatureV3(id string, pub *PublicKey, sig *SignatureV3) (err error) {
|
|
||||||
h, err := userIdSignatureV3Hash(id, pub, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignatureV3(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyIdString returns the public key's fingerprint in capital hex
|
|
||||||
// (e.g. "6C7EE1B8621CC013").
|
|
||||||
func (pk *PublicKey) KeyIdString() string {
|
|
||||||
return fmt.Sprintf("%X", pk.Fingerprint[12:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyIdShortString returns the short form of public key's fingerprint
|
|
||||||
// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
|
|
||||||
func (pk *PublicKey) KeyIdShortString() string {
|
|
||||||
return fmt.Sprintf("%X", pk.Fingerprint[16:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
// A parsedMPI is used to store the contents of a big integer, along with the
|
|
||||||
// bit length that was specified in the original input. This allows the MPI to
|
|
||||||
// be reserialized exactly.
|
|
||||||
type parsedMPI struct {
|
|
||||||
bytes []byte
|
|
||||||
bitLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeMPIs is a utility function for serializing several big integers to the
|
|
||||||
// given Writer.
|
|
||||||
func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) {
|
|
||||||
for _, mpi := range mpis {
|
|
||||||
err = writeMPI(w, mpi.bitLength, mpi.bytes)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// BitLength returns the bit length for the given public key.
|
|
||||||
func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
bitLength = pk.n.bitLength
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
bitLength = pk.p.bitLength
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
bitLength = pk.p.bitLength
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("bad public-key algorithm")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user