novend
This commit is contained in:
15
go.mod
15
go.mod
@@ -1,3 +1,18 @@
|
||||
module gogs.inhome.blapointe.com/local-sandbox/contact
|
||||
|
||||
go 1.20
|
||||
|
||||
replace gogs.inhome.blapointe.com/local-sandbox/contact/contact => ./contact
|
||||
|
||||
require (
|
||||
gogs.inhome.blapointe.com/local-sandbox/contact/contact v0.0.0-00010101000000-000000000000
|
||||
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
|
||||
github.com/emersion/go-imap v1.2.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
18
go.sum
Normal file
18
go.sum
Normal file
@@ -0,0 +1,18 @@
|
||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
|
||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
||||
github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA=
|
||||
github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34 h1:0tuX5dfOksiOQD1vbJjVNVTVxTTIng7UrUdSLF5T+Ao=
|
||||
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34/go.mod h1:YG9n3Clg7683ohkVnJK2hdX8bBS9EojIsd1qPZumX0Y=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
1
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
1
vendor/github.com/bytbox/go-pop3/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
||||
*.swp
|
||||
19
vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
19
vendor/github.com/bytbox/go-pop3/LICENSE
generated
vendored
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2012 Scott Lawrence <bytbox@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
223
vendor/github.com/bytbox/go-pop3/pop3.go
generated
vendored
@@ -1,223 +0,0 @@
|
||||
// Package pop3 provides an implementation of the Post Office Protocol, Version
|
||||
// 3 as defined in RFC 1939. Commands specified as optional are not
|
||||
// implemented; however, this implementation may be trivially extended to
|
||||
// support them.
|
||||
|
||||
package pop3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The POP3 client.
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
bin *bufio.Reader
|
||||
}
|
||||
|
||||
// Dial creates an unsecured connection to the POP3 server at the given address
|
||||
// and returns the corresponding Client.
|
||||
func Dial(addr string) (*Client, error) {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(conn)
|
||||
}
|
||||
|
||||
// DialTLS creates a TLS-secured connection to the POP3 server at the given
|
||||
// address and returns the corresponding Client.
|
||||
func DialTLS(addr string) (*Client, error) {
|
||||
conn, err := tls.Dial("tcp", addr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(conn)
|
||||
}
|
||||
|
||||
// NewClient returns a new Client object using an existing connection.
|
||||
func NewClient(conn net.Conn) (*Client, error) {
|
||||
client := &Client{
|
||||
bin: bufio.NewReader(conn),
|
||||
conn: conn,
|
||||
}
|
||||
// send dud command, to read a line
|
||||
_, err := client.Cmd("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Convenience function to synchronously run an arbitrary command and wait for
|
||||
// output. The terminating CRLF must be included in the format string.
|
||||
//
|
||||
// Output sent after the first line must be retrieved via readLines.
|
||||
func (c *Client) Cmd(format string, args ...interface{}) (string, error) {
|
||||
fmt.Fprintf(c.conn, format, args...)
|
||||
line, _, err := c.bin.ReadLine()
|
||||
if err != nil { return "", err }
|
||||
l := string(line)
|
||||
if l[0:3] != "+OK" {
|
||||
err = errors.New(l[5:])
|
||||
}
|
||||
if len(l) >= 4 {
|
||||
return l[4:], err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (c *Client) ReadLines() (lines []string, err error) {
|
||||
lines = make([]string, 0)
|
||||
l, _, err := c.bin.ReadLine()
|
||||
line := string(l)
|
||||
for err == nil && line != "." {
|
||||
if len(line) > 0 && line[0] == '.' {
|
||||
line = line[1:]
|
||||
}
|
||||
lines = append(lines, line)
|
||||
l, _, err = c.bin.ReadLine()
|
||||
line = string(l)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// User sends the given username to the server. Generally, there is no reason
|
||||
// not to use the Auth convenience method.
|
||||
func (c *Client) User(username string) (err error) {
|
||||
_, err = c.Cmd("USER %s\r\n", username)
|
||||
return
|
||||
}
|
||||
|
||||
// Pass sends the given password to the server. The password is sent
|
||||
// unencrypted unless the connection is already secured by TLS (via DialTLS or
|
||||
// some other mechanism). Generally, there is no reason not to use the Auth
|
||||
// convenience method.
|
||||
func (c *Client) Pass(password string) (err error) {
|
||||
_, err = c.Cmd("PASS %s\r\n", password)
|
||||
return
|
||||
}
|
||||
|
||||
// Auth sends the given username and password to the server, calling the User
|
||||
// and Pass methods as appropriate.
|
||||
func (c *Client) Auth(username, password string) (err error) {
|
||||
err = c.User(username)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Pass(password)
|
||||
return
|
||||
}
|
||||
|
||||
// Stat retrieves a drop listing for the current maildrop, consisting of the
|
||||
// number of messages and the total size (in octets) of the maildrop.
|
||||
// Information provided besides the number of messages and the size of the
|
||||
// maildrop is ignored. In the event of an error, all returned numeric values
|
||||
// will be 0.
|
||||
func (c *Client) Stat() (count, size int, err error) {
|
||||
l, err := c.Cmd("STAT\r\n")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
parts := strings.Fields(l)
|
||||
count, err = strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, errors.New("Invalid server response")
|
||||
}
|
||||
size, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, errors.New("Invalid server response")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// List returns the size of the given message, if it exists. If the message
|
||||
// does not exist, or another error is encountered, the returned size will be
|
||||
// 0.
|
||||
func (c *Client) List(msg int) (size int, err error) {
|
||||
l, err := c.Cmd("LIST %d\r\n", msg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size, err = strconv.Atoi(strings.Fields(l)[1])
|
||||
if err != nil {
|
||||
return 0, errors.New("Invalid server response")
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// ListAll returns a list of all messages and their sizes.
|
||||
func (c *Client) ListAll() (msgs []int, sizes []int, err error) {
|
||||
_, err = c.Cmd("LIST\r\n")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines, err := c.ReadLines()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msgs = make([]int, len(lines), len(lines))
|
||||
sizes = make([]int, len(lines), len(lines))
|
||||
for i, l := range lines {
|
||||
var m, s int
|
||||
fs := strings.Fields(l)
|
||||
m, err = strconv.Atoi(fs[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s, err = strconv.Atoi(fs[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msgs[i] = m
|
||||
sizes[i] = s
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Retr downloads and returns the given message. The lines are separated by LF,
|
||||
// whatever the server sent.
|
||||
func (c *Client) Retr(msg int) (text string, err error) {
|
||||
_, err = c.Cmd("RETR %d\r\n", msg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lines, err := c.ReadLines()
|
||||
text = strings.Join(lines, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Dele marks the given message as deleted.
|
||||
func (c *Client) Dele(msg int) (err error) {
|
||||
_, err = c.Cmd("DELE %d\r\n", msg)
|
||||
return
|
||||
}
|
||||
|
||||
// Noop does nothing, but will prolong the end of the connection if the server
|
||||
// has a timeout set.
|
||||
func (c *Client) Noop() (err error) {
|
||||
_, err = c.Cmd("NOOP\r\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Rset unmarks any messages marked for deletion previously in this session.
|
||||
func (c *Client) Rset() (err error) {
|
||||
_, err = c.Cmd("RSET\r\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Quit sends the QUIT message to the POP3 server and closes the connection.
|
||||
func (c *Client) Quit() error {
|
||||
_, err := c.Cmd("QUIT\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn.Close()
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
@@ -1,17 +0,0 @@
|
||||
image: alpine/edge
|
||||
packages:
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/emersion/go-imap
|
||||
artifacts:
|
||||
- coverage.html
|
||||
tasks:
|
||||
- build: |
|
||||
cd go-imap
|
||||
go build -race -v ./...
|
||||
- test: |
|
||||
cd go-imap
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- coverage: |
|
||||
cd go-imap
|
||||
go tool cover -html=coverage.txt -o ~/coverage.html
|
||||
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
@@ -1,28 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
/client.go
|
||||
/server.go
|
||||
coverage.txt
|
||||
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 The Go-IMAP Authors
|
||||
Copyright (c) 2016 emersion
|
||||
Copyright (c) 2016 Proton Technologies AG
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
177
vendor/github.com/emersion/go-imap/README.md
generated
vendored
@@ -1,177 +0,0 @@
|
||||
# go-imap
|
||||
|
||||
[](https://godocs.io/github.com/emersion/go-imap)
|
||||
[](https://builds.sr.ht/~emersion/go-imap/commits?)
|
||||
|
||||
An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
|
||||
can be used to build a client and/or a server.
|
||||
|
||||
## Usage
|
||||
|
||||
### Client [](https://godocs.io/github.com/emersion/go-imap/client)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Connecting to server...")
|
||||
|
||||
// Connect to server
|
||||
c, err := client.DialTLS("mail.example.org:993", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Connected")
|
||||
|
||||
// Don't forget to logout
|
||||
defer c.Logout()
|
||||
|
||||
// Login
|
||||
if err := c.Login("username", "password"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Logged in")
|
||||
|
||||
// List mailboxes
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
go func () {
|
||||
done <- c.List("", "*", mailboxes)
|
||||
}()
|
||||
|
||||
log.Println("Mailboxes:")
|
||||
for m := range mailboxes {
|
||||
log.Println("* " + m.Name)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Select INBOX
|
||||
mbox, err := c.Select("INBOX", false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Flags for INBOX:", mbox.Flags)
|
||||
|
||||
// Get the last 4 messages
|
||||
from := uint32(1)
|
||||
to := mbox.Messages
|
||||
if mbox.Messages > 3 {
|
||||
// We're using unsigned integers here, only subtract if the result is > 0
|
||||
from = mbox.Messages - 3
|
||||
}
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(from, to)
|
||||
|
||||
messages := make(chan *imap.Message, 10)
|
||||
done = make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
|
||||
}()
|
||||
|
||||
log.Println("Last 4 messages:")
|
||||
for msg := range messages {
|
||||
log.Println("* " + msg.Envelope.Subject)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Done!")
|
||||
}
|
||||
```
|
||||
|
||||
### Server [](https://godocs.io/github.com/emersion/go-imap/server)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/server"
|
||||
"github.com/emersion/go-imap/backend/memory"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a memory backend
|
||||
be := memory.New()
|
||||
|
||||
// Create a new server
|
||||
s := server.New(be)
|
||||
s.Addr = ":1143"
|
||||
// Since we will use this server for testing only, we can allow plain text
|
||||
// authentication over unencrypted connections
|
||||
s.AllowInsecureAuth = true
|
||||
|
||||
log.Println("Starting IMAP server at localhost:1143")
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can now use `telnet localhost 1143` to manually connect to the server.
|
||||
|
||||
## Extensions
|
||||
|
||||
Support for several IMAP extensions is included in go-imap itself. This
|
||||
includes:
|
||||
|
||||
* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
|
||||
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
|
||||
* [ENABLE](https://tools.ietf.org/html/rfc5161)
|
||||
* [IDLE](https://tools.ietf.org/html/rfc2177)
|
||||
* [IMPORTANT](https://tools.ietf.org/html/rfc8457)
|
||||
* [LITERAL+](https://tools.ietf.org/html/rfc7888)
|
||||
* [MOVE](https://tools.ietf.org/html/rfc6851)
|
||||
* [SASL-IR](https://tools.ietf.org/html/rfc4959)
|
||||
* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
|
||||
* [UNSELECT](https://tools.ietf.org/html/rfc3691)
|
||||
|
||||
Support for other extensions is provided via separate packages. See below.
|
||||
|
||||
## Extending go-imap
|
||||
|
||||
### Extensions
|
||||
|
||||
Commands defined in IMAP extensions are available in other packages. See [the
|
||||
wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
|
||||
to learn how to use them.
|
||||
|
||||
* [COMPRESS](https://github.com/emersion/go-imap-compress)
|
||||
* [ID](https://github.com/ProtonMail/go-imap-id)
|
||||
* [METADATA](https://github.com/emersion/go-imap-metadata)
|
||||
* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
|
||||
* [QUOTA](https://github.com/emersion/go-imap-quota)
|
||||
* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
|
||||
* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)
|
||||
|
||||
### Server backends
|
||||
|
||||
* [Memory](https://github.com/emersion/go-imap/tree/master/backend/memory) (for testing)
|
||||
* [Multi](https://github.com/emersion/go-imap-multi)
|
||||
* [PGP](https://github.com/emersion/go-imap-pgp)
|
||||
* [Proxy](https://github.com/emersion/go-imap-proxy)
|
||||
|
||||
### Related projects
|
||||
|
||||
* [go-message](https://github.com/emersion/go-message) - parsing and formatting MIME and mail messages
|
||||
* [go-msgauth](https://github.com/emersion/go-msgauth) - handle DKIM, DMARC and Authentication-Results
|
||||
* [go-pgpmail](https://github.com/emersion/go-pgpmail) - decrypting and encrypting mails with OpenPGP
|
||||
* [go-sasl](https://github.com/emersion/go-sasl) - sending and receiving SASL authentications
|
||||
* [go-smtp](https://github.com/emersion/go-smtp) - building SMTP clients and servers
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
@@ -1,689 +0,0 @@
|
||||
// Package client provides an IMAP client.
|
||||
//
|
||||
// It is not safe to use the same Client from multiple goroutines. In general,
|
||||
// the IMAP protocol doesn't make it possible to send multiple independent
|
||||
// IMAP commands on the same connection.
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// errClosed is used when a connection is closed while waiting for a command
|
||||
// response.
|
||||
var errClosed = fmt.Errorf("imap: connection closed")
|
||||
|
||||
// errUnregisterHandler is returned by a response handler to unregister itself.
|
||||
var errUnregisterHandler = fmt.Errorf("imap: unregister handler")
|
||||
|
||||
// Update is an unilateral server update.
|
||||
type Update interface {
|
||||
update()
|
||||
}
|
||||
|
||||
// StatusUpdate is delivered when a status update is received.
|
||||
type StatusUpdate struct {
|
||||
Status *imap.StatusResp
|
||||
}
|
||||
|
||||
func (u *StatusUpdate) update() {}
|
||||
|
||||
// MailboxUpdate is delivered when a mailbox status changes.
|
||||
type MailboxUpdate struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (u *MailboxUpdate) update() {}
|
||||
|
||||
// ExpungeUpdate is delivered when a message is deleted.
|
||||
type ExpungeUpdate struct {
|
||||
SeqNum uint32
|
||||
}
|
||||
|
||||
func (u *ExpungeUpdate) update() {}
|
||||
|
||||
// MessageUpdate is delivered when a message attribute changes.
|
||||
type MessageUpdate struct {
|
||||
Message *imap.Message
|
||||
}
|
||||
|
||||
func (u *MessageUpdate) update() {}
|
||||
|
||||
// Client is an IMAP client.
|
||||
type Client struct {
|
||||
conn *imap.Conn
|
||||
isTLS bool
|
||||
serverName string
|
||||
|
||||
loggedOut chan struct{}
|
||||
continues chan<- bool
|
||||
upgrading bool
|
||||
|
||||
handlers []responses.Handler
|
||||
handlersLocker sync.Mutex
|
||||
|
||||
// The current connection state.
|
||||
state imap.ConnState
|
||||
// The selected mailbox, if there is one.
|
||||
mailbox *imap.MailboxStatus
|
||||
// The cached server capabilities.
|
||||
caps map[string]bool
|
||||
// state, mailbox and caps may be accessed in different goroutines. Protect
|
||||
// access.
|
||||
locker sync.Mutex
|
||||
|
||||
// A channel to which unilateral updates from the server will be sent. An
|
||||
// update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
|
||||
// *ExpungeUpdate. Note that blocking this channel blocks the whole client,
|
||||
// so it's recommended to use a separate goroutine and a buffered channel to
|
||||
// prevent deadlocks.
|
||||
Updates chan<- Update
|
||||
|
||||
// ErrorLog specifies an optional logger for errors accepting connections and
|
||||
// unexpected behavior from handlers. By default, logging goes to os.Stderr
|
||||
// via the log package's standard logger. The logger must be safe to use
|
||||
// simultaneously from multiple goroutines.
|
||||
ErrorLog imap.Logger
|
||||
|
||||
// Timeout specifies a maximum amount of time to wait on a command.
|
||||
//
|
||||
// A Timeout of zero means no timeout. This is the default.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (c *Client) registerHandler(h responses.Handler) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.handlersLocker.Lock()
|
||||
c.handlers = append(c.handlers, h)
|
||||
c.handlersLocker.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) handle(resp imap.Resp) error {
|
||||
c.handlersLocker.Lock()
|
||||
for i := len(c.handlers) - 1; i >= 0; i-- {
|
||||
if err := c.handlers[i].Handle(resp); err != responses.ErrUnhandled {
|
||||
if err == errUnregisterHandler {
|
||||
c.handlers = append(c.handlers[:i], c.handlers[i+1:]...)
|
||||
err = nil
|
||||
}
|
||||
c.handlersLocker.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.handlersLocker.Unlock()
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
func (c *Client) reader() {
|
||||
defer close(c.loggedOut)
|
||||
// Loop while connected.
|
||||
for {
|
||||
connected, err := c.readOnce()
|
||||
if err != nil {
|
||||
c.ErrorLog.Println("error reading response:", err)
|
||||
}
|
||||
if !connected {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readOnce() (bool, error) {
|
||||
if c.State() == imap.LogoutState {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
resp, err := imap.ReadResp(c.conn.Reader)
|
||||
if err == io.EOF || c.State() == imap.LogoutState {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
if imap.IsParseError(err) {
|
||||
return true, err
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.handle(resp); err != nil && err != responses.ErrUnhandled {
|
||||
c.ErrorLog.Println("cannot handle response ", resp, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Client) writeReply(reply []byte) error {
|
||||
if _, err := c.conn.Writer.Write(reply); err != nil {
|
||||
return err
|
||||
}
|
||||
// Flush reply
|
||||
return c.conn.Writer.Flush()
|
||||
}
|
||||
|
||||
type handleResult struct {
|
||||
status *imap.StatusResp
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *Client) execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
||||
cmd := cmdr.Command()
|
||||
cmd.Tag = generateTag()
|
||||
|
||||
var replies <-chan []byte
|
||||
if replier, ok := h.(responses.Replier); ok {
|
||||
replies = replier.Replies()
|
||||
}
|
||||
|
||||
if c.Timeout > 0 {
|
||||
err := c.conn.SetDeadline(time.Now().Add(c.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// It's possible the client had a timeout set from a previous command, but no
|
||||
// longer does. Ensure we respect that. The zero time means no deadline.
|
||||
if err := c.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are upgrading.
|
||||
upgrading := c.upgrading
|
||||
|
||||
// Add handler before sending command, to be sure to get the response in time
|
||||
// (in tests, the response is sent right after our command is received, so
|
||||
// sometimes the response was received before the setup of this handler)
|
||||
doneHandle := make(chan handleResult, 1)
|
||||
unregister := make(chan struct{})
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
select {
|
||||
case <-unregister:
|
||||
// If an error occured while sending the command, abort
|
||||
return errUnregisterHandler
|
||||
default:
|
||||
}
|
||||
|
||||
if s, ok := resp.(*imap.StatusResp); ok && s.Tag == cmd.Tag {
|
||||
// This is the command's status response, we're done
|
||||
doneHandle <- handleResult{s, nil}
|
||||
// Special handling of connection upgrading.
|
||||
if upgrading {
|
||||
c.upgrading = false
|
||||
// Wait for upgrade to finish.
|
||||
c.conn.Wait()
|
||||
}
|
||||
// Cancel any pending literal write
|
||||
select {
|
||||
case c.continues <- false:
|
||||
default:
|
||||
}
|
||||
return errUnregisterHandler
|
||||
}
|
||||
|
||||
if h != nil {
|
||||
// Pass the response to the response handler
|
||||
if err := h.Handle(resp); err != nil && err != responses.ErrUnhandled {
|
||||
// If the response handler returns an error, abort
|
||||
doneHandle <- handleResult{nil, err}
|
||||
return errUnregisterHandler
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return responses.ErrUnhandled
|
||||
}))
|
||||
|
||||
// Send the command to the server
|
||||
if err := cmd.WriteTo(c.conn.Writer); err != nil {
|
||||
// Error while sending the command
|
||||
close(unregister)
|
||||
|
||||
if err, ok := err.(imap.LiteralLengthErr); ok {
|
||||
// Expected > Actual
|
||||
// The server is waiting for us to write
|
||||
// more bytes, we don't have them. Run.
|
||||
// Expected < Actual
|
||||
// We are about to send a potentially truncated message, we don't
|
||||
// want this (ths terminating CRLF is not sent at this point).
|
||||
c.conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
// Flush writer if we are upgrading
|
||||
if upgrading {
|
||||
if err := c.conn.Writer.Flush(); err != nil {
|
||||
// Error while sending the command
|
||||
close(unregister)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case reply := <-replies:
|
||||
// Response handler needs to send a reply (Used for AUTHENTICATE)
|
||||
if err := c.writeReply(reply); err != nil {
|
||||
close(unregister)
|
||||
return nil, err
|
||||
}
|
||||
case <-c.loggedOut:
|
||||
// If the connection is closed (such as from an I/O error), ensure we
|
||||
// realize this and don't block waiting on a response that will never
|
||||
// come. loggedOut is a channel that closes when the reader goroutine
|
||||
// ends.
|
||||
close(unregister)
|
||||
return nil, errClosed
|
||||
case result := <-doneHandle:
|
||||
return result.status, result.err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State returns the current connection state.
|
||||
func (c *Client) State() imap.ConnState {
|
||||
c.locker.Lock()
|
||||
state := c.state
|
||||
c.locker.Unlock()
|
||||
return state
|
||||
}
|
||||
|
||||
// Mailbox returns the selected mailbox. It returns nil if there isn't one.
|
||||
func (c *Client) Mailbox() *imap.MailboxStatus {
|
||||
// c.Mailbox fields are not supposed to change, so we can return the pointer.
|
||||
c.locker.Lock()
|
||||
mbox := c.mailbox
|
||||
c.locker.Unlock()
|
||||
return mbox
|
||||
}
|
||||
|
||||
// SetState sets this connection's internal state.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) SetState(state imap.ConnState, mailbox *imap.MailboxStatus) {
|
||||
c.locker.Lock()
|
||||
c.state = state
|
||||
c.mailbox = mailbox
|
||||
c.locker.Unlock()
|
||||
}
|
||||
|
||||
// Execute executes a generic command. cmdr is a value that can be converted to
|
||||
// a raw command and h is a response handler. The function returns when the
|
||||
// command has completed or failed, in this case err is nil. A non-nil err value
|
||||
// indicates a network error.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
||||
return c.execute(cmdr, h)
|
||||
}
|
||||
|
||||
func (c *Client) handleContinuationReqs() {
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
if _, ok := resp.(*imap.ContinuationReq); ok {
|
||||
go func() {
|
||||
c.continues <- true
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
return responses.ErrUnhandled
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Client) gotStatusCaps(args []interface{}) {
|
||||
c.locker.Lock()
|
||||
|
||||
c.caps = make(map[string]bool)
|
||||
for _, cap := range args {
|
||||
if cap, ok := cap.(string); ok {
|
||||
c.caps[cap] = true
|
||||
}
|
||||
}
|
||||
|
||||
c.locker.Unlock()
|
||||
}
|
||||
|
||||
// The server can send unilateral data. This function handles it.
|
||||
func (c *Client) handleUnilateral() {
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
switch resp := resp.(type) {
|
||||
case *imap.StatusResp:
|
||||
if resp.Tag != "*" {
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
switch resp.Type {
|
||||
case imap.StatusRespOk, imap.StatusRespNo, imap.StatusRespBad:
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &StatusUpdate{resp}
|
||||
}
|
||||
case imap.StatusRespBye:
|
||||
c.locker.Lock()
|
||||
c.state = imap.LogoutState
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
|
||||
c.conn.Close()
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &StatusUpdate{resp}
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
case *imap.DataResp:
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok {
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "CAPABILITY":
|
||||
c.gotStatusCaps(fields)
|
||||
case "EXISTS":
|
||||
if c.Mailbox() == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if messages, err := imap.ParseNumber(fields[0]); err == nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox.Messages = messages
|
||||
c.locker.Unlock()
|
||||
|
||||
c.mailbox.ItemsLocker.Lock()
|
||||
c.mailbox.Items[imap.StatusMessages] = nil
|
||||
c.mailbox.ItemsLocker.Unlock()
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
||||
}
|
||||
case "RECENT":
|
||||
if c.Mailbox() == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if recent, err := imap.ParseNumber(fields[0]); err == nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox.Recent = recent
|
||||
c.locker.Unlock()
|
||||
|
||||
c.mailbox.ItemsLocker.Lock()
|
||||
c.mailbox.Items[imap.StatusRecent] = nil
|
||||
c.mailbox.ItemsLocker.Unlock()
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
||||
}
|
||||
case "EXPUNGE":
|
||||
seqNum, _ := imap.ParseNumber(fields[0])
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &ExpungeUpdate{seqNum}
|
||||
}
|
||||
case "FETCH":
|
||||
seqNum, _ := imap.ParseNumber(fields[0])
|
||||
fields, _ := fields[1].([]interface{})
|
||||
|
||||
msg := &imap.Message{SeqNum: seqNum}
|
||||
if err := msg.Parse(fields); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MessageUpdate{msg}
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Client) handleGreetAndStartReading() error {
|
||||
var greetErr error
|
||||
gotGreet := false
|
||||
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
status, ok := resp.(*imap.StatusResp)
|
||||
if !ok {
|
||||
greetErr = fmt.Errorf("invalid greeting received from server: not a status response")
|
||||
return errUnregisterHandler
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
switch status.Type {
|
||||
case imap.StatusRespPreauth:
|
||||
c.state = imap.AuthenticatedState
|
||||
case imap.StatusRespBye:
|
||||
c.state = imap.LogoutState
|
||||
case imap.StatusRespOk:
|
||||
c.state = imap.NotAuthenticatedState
|
||||
default:
|
||||
c.state = imap.LogoutState
|
||||
c.locker.Unlock()
|
||||
greetErr = fmt.Errorf("invalid greeting received from server: %v", status.Type)
|
||||
return errUnregisterHandler
|
||||
}
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == imap.CodeCapability {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
|
||||
gotGreet = true
|
||||
return errUnregisterHandler
|
||||
}))
|
||||
|
||||
// call `readOnce` until we get the greeting or an error
|
||||
for !gotGreet {
|
||||
connected, err := c.readOnce()
|
||||
// Check for read errors
|
||||
if err != nil {
|
||||
// return read errors
|
||||
return err
|
||||
}
|
||||
// Check for invalid greet
|
||||
if greetErr != nil {
|
||||
// return read errors
|
||||
return greetErr
|
||||
}
|
||||
// Check if connection was closed.
|
||||
if !connected {
|
||||
// connection closed.
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// We got the greeting, now start the reader goroutine.
|
||||
go c.reader()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
||||
// tunnel.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Upgrade(upgrader imap.ConnUpgrader) error {
|
||||
return c.conn.Upgrade(upgrader)
|
||||
}
|
||||
|
||||
// Writer returns the imap.Writer for this client's connection.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Writer() *imap.Writer {
|
||||
return c.conn.Writer
|
||||
}
|
||||
|
||||
// IsTLS checks if this client's connection has TLS enabled.
|
||||
func (c *Client) IsTLS() bool {
|
||||
return c.isTLS
|
||||
}
|
||||
|
||||
// LoggedOut returns a channel which is closed when the connection to the server
|
||||
// is closed.
|
||||
func (c *Client) LoggedOut() <-chan struct{} {
|
||||
return c.loggedOut
|
||||
}
|
||||
|
||||
// SetDebug defines an io.Writer to which all network activity will be logged.
|
||||
// If nil is provided, network activity will not be logged.
|
||||
func (c *Client) SetDebug(w io.Writer) {
|
||||
// Need to send a command to unblock the reader goroutine.
|
||||
cmd := new(commands.Noop)
|
||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
// Flag connection as in upgrading
|
||||
c.upgrading = true
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for reader to block.
|
||||
c.conn.WaitReady()
|
||||
|
||||
c.conn.SetDebug(w)
|
||||
return conn, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("SetDebug:", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// New creates a new client from an existing connection.
|
||||
func New(conn net.Conn) (*Client, error) {
|
||||
continues := make(chan bool)
|
||||
w := imap.NewClientWriter(nil, continues)
|
||||
r := imap.NewReader(nil)
|
||||
|
||||
c := &Client{
|
||||
conn: imap.NewConn(conn, r, w),
|
||||
loggedOut: make(chan struct{}),
|
||||
continues: continues,
|
||||
state: imap.ConnectingState,
|
||||
ErrorLog: log.New(os.Stderr, "imap/client: ", log.LstdFlags),
|
||||
}
|
||||
|
||||
c.handleContinuationReqs()
|
||||
c.handleUnilateral()
|
||||
if err := c.handleGreetAndStartReading(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
plusOk, _ := c.Support("LITERAL+")
|
||||
minusOk, _ := c.Support("LITERAL-")
|
||||
// We don't use non-sync literal if it is bigger than 4096 bytes, so
|
||||
// LITERAL- is fine too.
|
||||
c.conn.AllowAsyncLiterals = plusOk || minusOk
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Dial connects to an IMAP server using an unencrypted connection.
|
||||
func Dial(addr string) (*Client, error) {
|
||||
return DialWithDialer(new(net.Dialer), addr)
|
||||
}
|
||||
|
||||
type Dialer interface {
|
||||
// Dial connects to the given address.
|
||||
Dial(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// DialWithDialer connects to an IMAP server using an unencrypted connection
|
||||
// using dialer.Dial.
|
||||
//
|
||||
// Among other uses, this allows to apply a dial timeout.
|
||||
func DialWithDialer(dialer Dialer, addr string) (*Client, error) {
|
||||
conn, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We don't return to the caller until we try to receive a greeting. As such,
|
||||
// there is no way to set the client's Timeout for that action. As a
|
||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
||||
// deadline.
|
||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
||||
err := conn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := New(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.serverName, _, _ = net.SplitHostPort(addr)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DialTLS connects to an IMAP server using an encrypted connection.
|
||||
func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
|
||||
return DialWithDialerTLS(new(net.Dialer), addr, tlsConfig)
|
||||
}
|
||||
|
||||
// DialWithDialerTLS connects to an IMAP server using an encrypted connection
|
||||
// using dialer.Dial.
|
||||
//
|
||||
// Among other uses, this allows to apply a dial timeout.
|
||||
func DialWithDialerTLS(dialer Dialer, addr string, tlsConfig *tls.Config) (*Client, error) {
|
||||
conn, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverName, _, _ := net.SplitHostPort(addr)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = serverName
|
||||
}
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
||||
// We don't return to the caller until we try to receive a greeting. As such,
|
||||
// there is no way to set the client's Timeout for that action. As a
|
||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
||||
// deadline.
|
||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
||||
err := tlsConn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := New(tlsConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.isTLS = true
|
||||
c.serverName = serverName
|
||||
return c, nil
|
||||
}
|
||||
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
@@ -1,88 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
)
|
||||
|
||||
// ErrAlreadyLoggedOut is returned if Logout is called when the client is
|
||||
// already logged out.
|
||||
var ErrAlreadyLoggedOut = errors.New("Already logged out")
|
||||
|
||||
// Capability requests a listing of capabilities that the server supports.
|
||||
// Capabilities are often returned by the server with the greeting or with the
|
||||
// STARTTLS and LOGIN responses, so usually explicitly requesting capabilities
|
||||
// isn't needed.
|
||||
//
|
||||
// Most of the time, Support should be used instead.
|
||||
func (c *Client) Capability() (map[string]bool, error) {
|
||||
cmd := &commands.Capability{}
|
||||
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
caps := c.caps
|
||||
c.locker.Unlock()
|
||||
return caps, nil
|
||||
}
|
||||
|
||||
// Support checks if cap is a capability supported by the server. If the server
|
||||
// hasn't sent its capabilities yet, Support requests them.
|
||||
func (c *Client) Support(cap string) (bool, error) {
|
||||
c.locker.Lock()
|
||||
ok := c.caps != nil
|
||||
c.locker.Unlock()
|
||||
|
||||
// If capabilities are not cached, request them
|
||||
if !ok {
|
||||
if _, err := c.Capability(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
supported := c.caps[cap]
|
||||
c.locker.Unlock()
|
||||
|
||||
return supported, nil
|
||||
}
|
||||
|
||||
// Noop always succeeds and does nothing.
|
||||
//
|
||||
// It can be used as a periodic poll for new messages or message status updates
|
||||
// during a period of inactivity. It can also be used to reset any inactivity
|
||||
// autologout timer on the server.
|
||||
func (c *Client) Noop() error {
|
||||
cmd := new(commands.Noop)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Logout gracefully closes the connection.
|
||||
func (c *Client) Logout() error {
|
||||
if c.State() == imap.LogoutState {
|
||||
return ErrAlreadyLoggedOut
|
||||
}
|
||||
|
||||
cmd := new(commands.Logout)
|
||||
|
||||
if status, err := c.execute(cmd, nil); err == errClosed {
|
||||
// Server closed connection, that's what we want anyway
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if status != nil {
|
||||
return status.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
@@ -1,380 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// ErrNotLoggedIn is returned if a function that requires the client to be
|
||||
// logged in is called then the client isn't.
|
||||
var ErrNotLoggedIn = errors.New("Not logged in")
|
||||
|
||||
func (c *Client) ensureAuthenticated() error {
|
||||
state := c.State()
|
||||
if state != imap.AuthenticatedState && state != imap.SelectedState {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select selects a mailbox so that messages in the mailbox can be accessed. Any
|
||||
// currently selected mailbox is deselected before attempting the new selection.
|
||||
// Even if the readOnly parameter is set to false, the server can decide to open
|
||||
// the mailbox in read-only mode.
|
||||
func (c *Client) Select(name string, readOnly bool) (*imap.MailboxStatus, error) {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &commands.Select{
|
||||
Mailbox: name,
|
||||
ReadOnly: readOnly,
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxStatus{Name: name, Items: make(map[imap.StatusItem]interface{})}
|
||||
res := &responses.Select{
|
||||
Mailbox: mbox,
|
||||
}
|
||||
c.locker.Lock()
|
||||
c.mailbox = mbox
|
||||
c.locker.Unlock()
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
if err := status.Err(); err != nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
mbox.ReadOnly = (status.Code == imap.CodeReadOnly)
|
||||
c.state = imap.SelectedState
|
||||
c.locker.Unlock()
|
||||
return mbox, nil
|
||||
}
|
||||
|
||||
// Create creates a mailbox with the given name.
|
||||
func (c *Client) Create(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Create{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Delete permanently removes the mailbox with the given name.
|
||||
func (c *Client) Delete(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Delete{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Rename changes the name of a mailbox.
|
||||
func (c *Client) Rename(existingName, newName string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Rename{
|
||||
Existing: existingName,
|
||||
New: newName,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Subscribe adds the specified mailbox name to the server's set of "active" or
|
||||
// "subscribed" mailboxes.
|
||||
func (c *Client) Subscribe(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Subscribe{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Unsubscribe removes the specified mailbox name from the server's set of
|
||||
// "active" or "subscribed" mailboxes.
|
||||
func (c *Client) Unsubscribe(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Unsubscribe{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// List returns a subset of names from the complete set of all names available
|
||||
// to the client.
|
||||
//
|
||||
// An empty name argument is a special request to return the hierarchy delimiter
|
||||
// and the root name of the name given in the reference. The character "*" is a
|
||||
// wildcard, and matches zero or more characters at this position. The
|
||||
// character "%" is similar to "*", but it does not match a hierarchy delimiter.
|
||||
func (c *Client) List(ref, name string, ch chan *imap.MailboxInfo) error {
|
||||
defer close(ch)
|
||||
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.List{
|
||||
Reference: ref,
|
||||
Mailbox: name,
|
||||
}
|
||||
res := &responses.List{Mailboxes: ch}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Lsub returns a subset of names from the set of names that the user has
|
||||
// declared as being "active" or "subscribed".
|
||||
func (c *Client) Lsub(ref, name string, ch chan *imap.MailboxInfo) error {
|
||||
defer close(ch)
|
||||
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.List{
|
||||
Reference: ref,
|
||||
Mailbox: name,
|
||||
Subscribed: true,
|
||||
}
|
||||
res := &responses.List{
|
||||
Mailboxes: ch,
|
||||
Subscribed: true,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Status requests the status of the indicated mailbox. It does not change the
|
||||
// currently selected mailbox, nor does it affect the state of any messages in
|
||||
// the queried mailbox.
|
||||
//
|
||||
// See RFC 3501 section 6.3.10 for a list of items that can be requested.
|
||||
func (c *Client) Status(name string, items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &commands.Status{
|
||||
Mailbox: name,
|
||||
Items: items,
|
||||
}
|
||||
res := &responses.Status{
|
||||
Mailbox: new(imap.MailboxStatus),
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Mailbox, status.Err()
|
||||
}
|
||||
|
||||
// Append appends the literal argument as a new message to the end of the
|
||||
// specified destination mailbox. This argument SHOULD be in the format of an
|
||||
// RFC 2822 message. flags and date are optional arguments and can be set to
|
||||
// nil and the empty struct.
|
||||
func (c *Client) Append(mbox string, flags []string, date time.Time, msg imap.Literal) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Append{
|
||||
Mailbox: mbox,
|
||||
Flags: flags,
|
||||
Date: date,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Enable requests the server to enable the named extensions. The extensions
|
||||
// which were successfully enabled are returned.
|
||||
//
|
||||
// See RFC 5161 section 3.1.
|
||||
func (c *Client) Enable(caps []string) ([]string, error) {
|
||||
if ok, err := c.Support("ENABLE"); !ok || err != nil {
|
||||
return nil, ErrExtensionUnsupported
|
||||
}
|
||||
|
||||
// ENABLE is invalid if a mailbox has been selected.
|
||||
if c.State() != imap.AuthenticatedState {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
|
||||
cmd := &commands.Enable{Caps: caps}
|
||||
res := &responses.Enabled{}
|
||||
|
||||
if status, err := c.Execute(cmd, res); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return res.Caps, status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) idle(stop <-chan struct{}) error {
|
||||
cmd := &commands.Idle{}
|
||||
|
||||
res := &responses.Idle{
|
||||
Stop: stop,
|
||||
RepliesCh: make(chan []byte, 10),
|
||||
}
|
||||
|
||||
if status, err := c.Execute(cmd, res); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// IdleOptions holds options for Client.Idle.
|
||||
type IdleOptions struct {
|
||||
// LogoutTimeout is used to avoid being logged out by the server when
|
||||
// idling. Each LogoutTimeout, the IDLE command is restarted. If set to
|
||||
// zero, a default is used. If negative, this behavior is disabled.
|
||||
LogoutTimeout time.Duration
|
||||
// Poll interval when the server doesn't support IDLE. If zero, a default
|
||||
// is used. If negative, polling is always disabled.
|
||||
PollInterval time.Duration
|
||||
}
|
||||
|
||||
// Idle indicates to the server that the client is ready to receive unsolicited
|
||||
// mailbox update messages. When the client wants to send commands again, it
|
||||
// must first close stop.
|
||||
//
|
||||
// If the server doesn't support IDLE, go-imap falls back to polling.
|
||||
func (c *Client) Idle(stop <-chan struct{}, opts *IdleOptions) error {
|
||||
if ok, err := c.Support("IDLE"); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return c.idleFallback(stop, opts)
|
||||
}
|
||||
|
||||
logoutTimeout := 25 * time.Minute
|
||||
if opts != nil {
|
||||
if opts.LogoutTimeout > 0 {
|
||||
logoutTimeout = opts.LogoutTimeout
|
||||
} else if opts.LogoutTimeout < 0 {
|
||||
return c.idle(stop)
|
||||
}
|
||||
}
|
||||
|
||||
t := time.NewTicker(logoutTimeout)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
stopOrRestart := make(chan struct{})
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.idle(stopOrRestart)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
close(stopOrRestart)
|
||||
if err := <-done; err != nil {
|
||||
return err
|
||||
}
|
||||
case <-stop:
|
||||
close(stopOrRestart)
|
||||
return <-done
|
||||
case err := <-done:
|
||||
close(stopOrRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) idleFallback(stop <-chan struct{}, opts *IdleOptions) error {
|
||||
pollInterval := time.Minute
|
||||
if opts != nil {
|
||||
if opts.PollInterval > 0 {
|
||||
pollInterval = opts.PollInterval
|
||||
} else if opts.PollInterval < 0 {
|
||||
return ErrExtensionUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
t := time.NewTicker(pollInterval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
if err := c.Noop(); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-stop:
|
||||
return nil
|
||||
case <-c.LoggedOut():
|
||||
return errors.New("disconnected while idling")
|
||||
}
|
||||
}
|
||||
}
|
||||
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
@@ -1,174 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyLoggedIn is returned if Login or Authenticate is called when the
|
||||
// client is already logged in.
|
||||
ErrAlreadyLoggedIn = errors.New("Already logged in")
|
||||
// ErrTLSAlreadyEnabled is returned if StartTLS is called when TLS is already
|
||||
// enabled.
|
||||
ErrTLSAlreadyEnabled = errors.New("TLS is already enabled")
|
||||
// ErrLoginDisabled is returned if Login or Authenticate is called when the
|
||||
// server has disabled authentication. Most of the time, calling enabling TLS
|
||||
// solves the problem.
|
||||
ErrLoginDisabled = errors.New("Login is disabled in current state")
|
||||
)
|
||||
|
||||
// SupportStartTLS checks if the server supports STARTTLS.
|
||||
func (c *Client) SupportStartTLS() (bool, error) {
|
||||
return c.Support("STARTTLS")
|
||||
}
|
||||
|
||||
// StartTLS starts TLS negotiation.
|
||||
func (c *Client) StartTLS(tlsConfig *tls.Config) error {
|
||||
if c.isTLS {
|
||||
return ErrTLSAlreadyEnabled
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = new(tls.Config)
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = c.serverName
|
||||
}
|
||||
|
||||
cmd := new(commands.StartTLS)
|
||||
|
||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
// Flag connection as in upgrading
|
||||
c.upgrading = true
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for reader to block.
|
||||
c.conn.WaitReady()
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Capabilities change when TLS is enabled
|
||||
c.locker.Lock()
|
||||
c.caps = nil
|
||||
c.locker.Unlock()
|
||||
|
||||
return tlsConn, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isTLS = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportAuth checks if the server supports a given authentication mechanism.
|
||||
func (c *Client) SupportAuth(mech string) (bool, error) {
|
||||
return c.Support("AUTH=" + mech)
|
||||
}
|
||||
|
||||
// Authenticate indicates a SASL authentication mechanism to the server. If the
|
||||
// server supports the requested authentication mechanism, it performs an
|
||||
// authentication protocol exchange to authenticate and identify the client.
|
||||
func (c *Client) Authenticate(auth sasl.Client) error {
|
||||
if c.State() != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
mech, ir, err := auth.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Authenticate{
|
||||
Mechanism: mech,
|
||||
}
|
||||
|
||||
irOk, err := c.Support("SASL-IR")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if irOk {
|
||||
cmd.InitialResponse = ir
|
||||
}
|
||||
|
||||
res := &responses.Authenticate{
|
||||
Mechanism: auth,
|
||||
InitialResponse: ir,
|
||||
RepliesCh: make(chan []byte, 10),
|
||||
}
|
||||
if irOk {
|
||||
res.InitialResponse = nil
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.caps = nil // Capabilities change when user is logged in
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == "CAPABILITY" {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login identifies the client to the server and carries the plaintext password
|
||||
// authenticating this user.
|
||||
func (c *Client) Login(username, password string) error {
|
||||
if state := c.State(); state == imap.AuthenticatedState || state == imap.SelectedState {
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
loginDisabled := c.caps != nil && c.caps["LOGINDISABLED"]
|
||||
c.locker.Unlock()
|
||||
if loginDisabled {
|
||||
return ErrLoginDisabled
|
||||
}
|
||||
|
||||
cmd := &commands.Login{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.caps = nil // Capabilities change when user is logged in
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == "CAPABILITY" {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
@@ -1,367 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoMailboxSelected is returned if a command that requires a mailbox to be
|
||||
// selected is called when there isn't.
|
||||
ErrNoMailboxSelected = errors.New("No mailbox selected")
|
||||
|
||||
// ErrExtensionUnsupported is returned if a command uses a extension that
|
||||
// is not supported by the server.
|
||||
ErrExtensionUnsupported = errors.New("The required extension is not supported by the server")
|
||||
)
|
||||
|
||||
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
|
||||
// refers to any implementation-dependent housekeeping associated with the
|
||||
// mailbox that is not normally executed as part of each command.
|
||||
func (c *Client) Check() error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Check)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Close permanently removes all messages that have the \Deleted flag set from
|
||||
// the currently selected mailbox, and returns to the authenticated state from
|
||||
// the selected state.
|
||||
func (c *Client) Close() error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Close)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate closes the tcp connection
|
||||
func (c *Client) Terminate() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Expunge permanently removes all messages that have the \Deleted flag set from
|
||||
// the currently selected mailbox. If ch is not nil, sends sequence IDs of each
|
||||
// deleted message to this channel.
|
||||
func (c *Client) Expunge(ch chan uint32) error {
|
||||
if ch != nil {
|
||||
defer close(ch)
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Expunge)
|
||||
|
||||
var h responses.Handler
|
||||
if ch != nil {
|
||||
h = &responses.Expunge{SeqNums: ch}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
func (c *Client) executeSearch(uid bool, criteria *imap.SearchCriteria, charset string) (ids []uint32, status *imap.StatusResp, err error) {
|
||||
if c.State() != imap.SelectedState {
|
||||
err = ErrNoMailboxSelected
|
||||
return
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Search{
|
||||
Charset: charset,
|
||||
Criteria: criteria,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
res := new(responses.Search)
|
||||
|
||||
status, err = c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err, ids = status.Err(), res.Ids
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
|
||||
ids, status, err := c.executeSearch(uid, criteria, "UTF-8")
|
||||
if status != nil && status.Code == imap.CodeBadCharset {
|
||||
// Some servers don't support UTF-8
|
||||
ids, _, err = c.executeSearch(uid, criteria, "US-ASCII")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Search searches the mailbox for messages that match the given searching
|
||||
// criteria. Searching criteria consist of one or more search keys. The response
|
||||
// contains a list of message sequence IDs corresponding to those messages that
|
||||
// match the searching criteria. When multiple keys are specified, the result is
|
||||
// the intersection (AND function) of all the messages that match those keys.
|
||||
// Criteria must be UTF-8 encoded. See RFC 3501 section 6.4.4 for a list of
|
||||
// searching criteria. When no criteria has been set, all messages in the mailbox
|
||||
// will be searched using ALL criteria.
|
||||
func (c *Client) Search(criteria *imap.SearchCriteria) (seqNums []uint32, err error) {
|
||||
return c.search(false, criteria)
|
||||
}
|
||||
|
||||
// UidSearch is identical to Search, but UIDs are returned instead of message
|
||||
// sequence numbers.
|
||||
func (c *Client) UidSearch(criteria *imap.SearchCriteria) (uids []uint32, err error) {
|
||||
return c.search(true, criteria)
|
||||
}
|
||||
|
||||
func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
defer close(ch)
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Fetch{
|
||||
SeqSet: seqset,
|
||||
Items: items,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
res := &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Fetch retrieves data associated with a message in the mailbox. See RFC 3501
|
||||
// section 6.4.5 for a list of items that can be requested.
|
||||
func (c *Client) Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
return c.fetch(false, seqset, items, ch)
|
||||
}
|
||||
|
||||
// UidFetch is identical to Fetch, but seqset is interpreted as containing
|
||||
// unique identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
return c.fetch(true, seqset, items, ch)
|
||||
}
|
||||
|
||||
func (c *Client) store(uid bool, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
if ch != nil {
|
||||
defer close(ch)
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
// TODO: this could break extensions (this only works when item is FLAGS)
|
||||
if fields, ok := value.([]interface{}); ok {
|
||||
for i, field := range fields {
|
||||
if s, ok := field.(string); ok {
|
||||
fields[i] = imap.RawString(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If ch is nil, the updated values are data which will be lost, so don't
|
||||
// retrieve it.
|
||||
if ch == nil {
|
||||
op, _, err := imap.ParseFlagsOp(item)
|
||||
if err == nil {
|
||||
item = imap.FormatFlagsOp(op, true)
|
||||
}
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Store{
|
||||
SeqSet: seqset,
|
||||
Item: item,
|
||||
Value: value,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
var h responses.Handler
|
||||
if ch != nil {
|
||||
h = &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Store alters data associated with a message in the mailbox. If ch is not nil,
|
||||
// the updated value of the data will be sent to this channel. See RFC 3501
|
||||
// section 6.4.6 for a list of items that can be updated.
|
||||
func (c *Client) Store(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
return c.store(false, seqset, item, value, ch)
|
||||
}
|
||||
|
||||
// UidStore is identical to Store, but seqset is interpreted as containing
|
||||
// unique identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidStore(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
return c.store(true, seqset, item, value, ch)
|
||||
}
|
||||
|
||||
func (c *Client) copy(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Copy{
|
||||
SeqSet: seqset,
|
||||
Mailbox: dest,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Copy copies the specified message(s) to the end of the specified destination
|
||||
// mailbox.
|
||||
func (c *Client) Copy(seqset *imap.SeqSet, dest string) error {
|
||||
return c.copy(false, seqset, dest)
|
||||
}
|
||||
|
||||
// UidCopy is identical to Copy, but seqset is interpreted as containing unique
|
||||
// identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidCopy(seqset *imap.SeqSet, dest string) error {
|
||||
return c.copy(true, seqset, dest)
|
||||
}
|
||||
|
||||
func (c *Client) move(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
if ok, err := c.Support("MOVE"); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return c.moveFallback(uid, seqset, dest)
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Move{
|
||||
SeqSet: seqset,
|
||||
Mailbox: dest,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
if status, err := c.Execute(cmd, nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// moveFallback uses COPY, STORE and EXPUNGE for servers which don't support
|
||||
// MOVE.
|
||||
func (c *Client) moveFallback(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
item := imap.FormatFlagsOp(imap.AddFlags, true)
|
||||
flags := []interface{}{imap.DeletedFlag}
|
||||
if uid {
|
||||
if err := c.UidCopy(seqset, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.UidStore(seqset, item, flags, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.Copy(seqset, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Store(seqset, item, flags, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.Expunge(nil)
|
||||
}
|
||||
|
||||
// Move moves the specified message(s) to the end of the specified destination
|
||||
// mailbox.
|
||||
//
|
||||
// If the server doesn't support the MOVE extension defined in RFC 6851,
|
||||
// go-imap will fallback to copy, store and expunge.
|
||||
func (c *Client) Move(seqset *imap.SeqSet, dest string) error {
|
||||
return c.move(false, seqset, dest)
|
||||
}
|
||||
|
||||
// UidMove is identical to Move, but seqset is interpreted as containing unique
|
||||
// identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidMove(seqset *imap.SeqSet, dest string) error {
|
||||
return c.move(true, seqset, dest)
|
||||
}
|
||||
|
||||
// Unselect frees server's resources associated with the selected mailbox and
|
||||
// returns the server to the authenticated state. This command performs the same
|
||||
// actions as Close, except that no messages are permanently removed from the
|
||||
// currently selected mailbox.
|
||||
//
|
||||
// If client does not support the UNSELECT extension, ErrExtensionUnsupported
|
||||
// is returned.
|
||||
func (c *Client) Unselect() error {
|
||||
if ok, err := c.Support("UNSELECT"); !ok || err != nil {
|
||||
return ErrExtensionUnsupported
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := &commands.Unselect{}
|
||||
if status, err := c.Execute(cmd, nil); err != nil {
|
||||
return err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.SetState(imap.AuthenticatedState, nil)
|
||||
return nil
|
||||
}
|
||||
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
@@ -1,24 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func randomString(n int) (string, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func generateTag() string {
|
||||
tag, err := randomString(4)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
@@ -1,57 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A value that can be converted to a command.
|
||||
type Commander interface {
|
||||
Command() *Command
|
||||
}
|
||||
|
||||
// A command.
|
||||
type Command struct {
|
||||
// The command tag. It acts as a unique identifier for this command. If empty,
|
||||
// the command is untagged.
|
||||
Tag string
|
||||
// The command name.
|
||||
Name string
|
||||
// The command arguments.
|
||||
Arguments []interface{}
|
||||
}
|
||||
|
||||
// Implements the Commander interface.
|
||||
func (cmd *Command) Command() *Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (cmd *Command) WriteTo(w *Writer) error {
|
||||
tag := cmd.Tag
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(tag), RawString(cmd.Name)}
|
||||
fields = append(fields, cmd.Arguments...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// Parse a command from fields.
|
||||
func (cmd *Command) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("imap: cannot parse command: no enough fields")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Tag, ok = fields[0].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid tag")
|
||||
}
|
||||
if cmd.Name, ok = fields[1].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid name")
|
||||
}
|
||||
cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
|
||||
|
||||
cmd.Arguments = fields[2:]
|
||||
return nil
|
||||
}
|
||||
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
@@ -1,93 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Append is an APPEND command, as defined in RFC 3501 section 6.3.11.
|
||||
type Append struct {
|
||||
Mailbox string
|
||||
Flags []string
|
||||
Date time.Time
|
||||
Message imap.Literal
|
||||
}
|
||||
|
||||
func (cmd *Append) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
args = append(args, imap.FormatMailboxName(mailbox))
|
||||
|
||||
if cmd.Flags != nil {
|
||||
flags := make([]interface{}, len(cmd.Flags))
|
||||
for i, flag := range cmd.Flags {
|
||||
flags[i] = imap.RawString(flag)
|
||||
}
|
||||
args = append(args, flags)
|
||||
}
|
||||
|
||||
if !cmd.Date.IsZero() {
|
||||
args = append(args, cmd.Date)
|
||||
}
|
||||
|
||||
args = append(args, cmd.Message)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "APPEND",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Append) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
// Parse mailbox name
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
// Parse message literal
|
||||
litIndex := len(fields) - 1
|
||||
var ok bool
|
||||
if cmd.Message, ok = fields[litIndex].(imap.Literal); !ok {
|
||||
return errors.New("Message must be a literal")
|
||||
}
|
||||
|
||||
// Remaining fields a optional
|
||||
fields = fields[1:litIndex]
|
||||
if len(fields) > 0 {
|
||||
// Parse flags list
|
||||
if flags, ok := fields[0].([]interface{}); ok {
|
||||
if cmd.Flags, err = imap.ParseStringList(flags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, flag := range cmd.Flags {
|
||||
cmd.Flags[i] = imap.CanonicalFlag(flag)
|
||||
}
|
||||
|
||||
fields = fields[1:]
|
||||
}
|
||||
|
||||
// Parse date
|
||||
if len(fields) > 0 {
|
||||
if date, ok := fields[0].(string); !ok {
|
||||
return errors.New("Date must be a string")
|
||||
} else if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
@@ -1,124 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// AuthenticateConn is a connection that supports IMAP authentication.
|
||||
type AuthenticateConn interface {
|
||||
io.Reader
|
||||
|
||||
// WriteResp writes an IMAP response to this connection.
|
||||
WriteResp(res imap.WriterTo) error
|
||||
}
|
||||
|
||||
// Authenticate is an AUTHENTICATE command, as defined in RFC 3501 section
|
||||
// 6.2.2.
|
||||
type Authenticate struct {
|
||||
Mechanism string
|
||||
InitialResponse []byte
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Command() *imap.Command {
|
||||
args := []interface{}{imap.RawString(cmd.Mechanism)}
|
||||
if cmd.InitialResponse != nil {
|
||||
var encodedResponse string
|
||||
if len(cmd.InitialResponse) == 0 {
|
||||
// Empty initial response should be encoded as "=", not empty
|
||||
// string.
|
||||
encodedResponse = "="
|
||||
} else {
|
||||
encodedResponse = base64.StdEncoding.EncodeToString(cmd.InitialResponse)
|
||||
}
|
||||
|
||||
args = append(args, imap.RawString(encodedResponse))
|
||||
}
|
||||
return &imap.Command{
|
||||
Name: "AUTHENTICATE",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Mechanism, ok = fields[0].(string); !ok {
|
||||
return errors.New("Mechanism must be a string")
|
||||
}
|
||||
cmd.Mechanism = strings.ToUpper(cmd.Mechanism)
|
||||
|
||||
if len(fields) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
encodedResponse, ok := fields[1].(string)
|
||||
if !ok {
|
||||
return errors.New("Initial response must be a string")
|
||||
}
|
||||
if encodedResponse == "=" {
|
||||
cmd.InitialResponse = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
cmd.InitialResponse, err = base64.StdEncoding.DecodeString(encodedResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn AuthenticateConn) error {
|
||||
sasl, ok := mechanisms[cmd.Mechanism]
|
||||
if !ok {
|
||||
return errors.New("Unsupported mechanism")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
response := cmd.InitialResponse
|
||||
for {
|
||||
challenge, done, err := sasl.Next(response)
|
||||
if err != nil || done {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(challenge)
|
||||
cont := &imap.ContinuationReq{Info: encoded}
|
||||
if err := conn.WriteResp(cont); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !scanner.Scan() {
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("unexpected EOF")
|
||||
}
|
||||
|
||||
encoded = scanner.Text()
|
||||
if encoded != "" {
|
||||
if encoded == "*" {
|
||||
return &imap.ErrStatusResp{Resp: &imap.StatusResp{
|
||||
Type: imap.StatusRespBad,
|
||||
Info: "negotiation cancelled",
|
||||
}}
|
||||
}
|
||||
response, err = base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Capability is a CAPABILITY command, as defined in RFC 3501 section 6.1.1.
|
||||
type Capability struct{}
|
||||
|
||||
func (c *Capability) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CAPABILITY",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Capability) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Check is a CHECK command, as defined in RFC 3501 section 6.4.1.
|
||||
type Check struct{}
|
||||
|
||||
func (cmd *Check) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CHECK",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Check) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Close is a CLOSE command, as defined in RFC 3501 section 6.4.2.
|
||||
type Close struct{}
|
||||
|
||||
func (cmd *Close) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CLOSE",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Close) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
@@ -1,2 +0,0 @@
|
||||
// Package commands implements IMAP commands defined in RFC 3501.
|
||||
package commands
|
||||
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
@@ -1,47 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Copy is a COPY command, as defined in RFC 3501 section 6.4.7.
|
||||
type Copy struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Copy) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "COPY",
|
||||
Arguments: []interface{}{cmd.SeqSet, imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Copy) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if seqSet, ok := fields[0].(string); !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
} else if seqSet, err := imap.ParseSeqSet(seqSet); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.SeqSet = seqSet
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Create is a CREATE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Create struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Create) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "CREATE",
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Create) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Delete is a DELETE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Delete struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Delete) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "DELETE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Delete) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
@@ -1,23 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An ENABLE command, defined in RFC 5161 section 3.1.
|
||||
type Enable struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (cmd *Enable) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "ENABLE",
|
||||
Arguments: imap.FormatStringList(cmd.Caps),
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Enable) Parse(fields []interface{}) error {
|
||||
var err error
|
||||
cmd.Caps, err = imap.ParseStringList(fields)
|
||||
return err
|
||||
}
|
||||
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Expunge is an EXPUNGE command, as defined in RFC 3501 section 6.4.3.
|
||||
type Expunge struct{}
|
||||
|
||||
func (cmd *Expunge) Command() *imap.Command {
|
||||
return &imap.Command{Name: "EXPUNGE"}
|
||||
}
|
||||
|
||||
func (cmd *Expunge) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
55
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
@@ -1,55 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5.
|
||||
type Fetch struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Items []imap.FetchItem
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Command() *imap.Command {
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, item := range cmd.Items {
|
||||
items[i] = imap.RawString(item)
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: "FETCH",
|
||||
Arguments: []interface{}{cmd.SeqSet, items},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
var err error
|
||||
if seqset, ok := fields[0].(string); !ok {
|
||||
return errors.New("Sequence set must be an atom")
|
||||
} else if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch items := fields[1].(type) {
|
||||
case string: // A macro or a single item
|
||||
cmd.Items = imap.FetchItem(strings.ToUpper(items)).Expand()
|
||||
case []interface{}: // A list of items
|
||||
cmd.Items = make([]imap.FetchItem, 0, len(items))
|
||||
for _, v := range items {
|
||||
itemStr, _ := v.(string)
|
||||
item := imap.FetchItem(strings.ToUpper(itemStr))
|
||||
cmd.Items = append(cmd.Items, item.Expand()...)
|
||||
}
|
||||
default:
|
||||
return errors.New("Items must be either a string or a list")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An IDLE command.
|
||||
// Se RFC 2177 section 3.
|
||||
type Idle struct{}
|
||||
|
||||
func (cmd *Idle) Command() *imap.Command {
|
||||
return &imap.Command{Name: "IDLE"}
|
||||
}
|
||||
|
||||
func (cmd *Idle) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
@@ -1,60 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// List is a LIST command, as defined in RFC 3501 section 6.3.8. If Subscribed
|
||||
// is set to true, LSUB will be used instead.
|
||||
type List struct {
|
||||
Reference string
|
||||
Mailbox string
|
||||
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (cmd *List) Command() *imap.Command {
|
||||
name := "LIST"
|
||||
if cmd.Subscribed {
|
||||
name = "LSUB"
|
||||
}
|
||||
|
||||
enc := utf7.Encoding.NewEncoder()
|
||||
ref, _ := enc.String(cmd.Reference)
|
||||
mailbox, _ := enc.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{ref, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *List) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
dec := utf7.Encoding.NewDecoder()
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := dec.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
// TODO: canonical mailbox path
|
||||
cmd.Reference = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := dec.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Login is a LOGIN command, as defined in RFC 3501 section 6.2.2.
|
||||
type Login struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (cmd *Login) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "LOGIN",
|
||||
Arguments: []interface{}{cmd.Username, cmd.Password},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Login) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var err error
|
||||
if cmd.Username, err = imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Password, err = imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Logout is a LOGOUT command, as defined in RFC 3501 section 6.1.3.
|
||||
type Logout struct{}
|
||||
|
||||
func (c *Logout) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "LOGOUT",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Logout) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// A MOVE command.
|
||||
// See RFC 6851 section 3.1.
|
||||
type Move struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Move) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "MOVE",
|
||||
Arguments: []interface{}{cmd.SeqSet, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Move) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
}
|
||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mailbox, ok := fields[1].(string)
|
||||
if !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
}
|
||||
if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Noop is a NOOP command, as defined in RFC 3501 section 6.1.2.
|
||||
type Noop struct{}
|
||||
|
||||
func (c *Noop) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "NOOP",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Noop) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
@@ -1,51 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Rename is a RENAME command, as defined in RFC 3501 section 6.3.5.
|
||||
type Rename struct {
|
||||
Existing string
|
||||
New string
|
||||
}
|
||||
|
||||
func (cmd *Rename) Command() *imap.Command {
|
||||
enc := utf7.Encoding.NewEncoder()
|
||||
existingName, _ := enc.String(cmd.Existing)
|
||||
newName, _ := enc.String(cmd.New)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "RENAME",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(existingName), imap.FormatMailboxName(newName)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Rename) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
dec := utf7.Encoding.NewDecoder()
|
||||
|
||||
if existingName, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if existingName, err := dec.String(existingName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Existing = imap.CanonicalMailboxName(existingName)
|
||||
}
|
||||
|
||||
if newName, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if newName, err := dec.String(newName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.New = imap.CanonicalMailboxName(newName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
@@ -1,57 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Search is a SEARCH command, as defined in RFC 3501 section 6.4.4.
|
||||
type Search struct {
|
||||
Charset string
|
||||
Criteria *imap.SearchCriteria
|
||||
}
|
||||
|
||||
func (cmd *Search) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
if cmd.Charset != "" {
|
||||
args = append(args, imap.RawString("CHARSET"), imap.RawString(cmd.Charset))
|
||||
}
|
||||
args = append(args, cmd.Criteria.Format()...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "SEARCH",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Search) Parse(fields []interface{}) error {
|
||||
if len(fields) == 0 {
|
||||
return errors.New("Missing search criteria")
|
||||
}
|
||||
|
||||
// Parse charset
|
||||
if f, ok := fields[0].(string); ok && strings.EqualFold(f, "CHARSET") {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Missing CHARSET value")
|
||||
}
|
||||
if cmd.Charset, ok = fields[1].(string); !ok {
|
||||
return errors.New("Charset must be a string")
|
||||
}
|
||||
fields = fields[2:]
|
||||
}
|
||||
|
||||
var charsetReader func(io.Reader) io.Reader
|
||||
charset := strings.ToLower(cmd.Charset)
|
||||
if charset != "utf-8" && charset != "us-ascii" && charset != "" {
|
||||
charsetReader = func(r io.Reader) io.Reader {
|
||||
r, _ = imap.CharsetReader(charset, r)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Criteria = new(imap.SearchCriteria)
|
||||
return cmd.Criteria.ParseWithCharset(fields, charsetReader)
|
||||
}
|
||||
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
@@ -1,45 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Select is a SELECT command, as defined in RFC 3501 section 6.3.1. If ReadOnly
|
||||
// is set to true, the EXAMINE command will be used instead.
|
||||
type Select struct {
|
||||
Mailbox string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
func (cmd *Select) Command() *imap.Command {
|
||||
name := "SELECT"
|
||||
if cmd.ReadOnly {
|
||||
name = "EXAMINE"
|
||||
}
|
||||
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Select) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// StartTLS is a STARTTLS command, as defined in RFC 3501 section 6.2.1.
|
||||
type StartTLS struct{}
|
||||
|
||||
func (cmd *StartTLS) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "STARTTLS",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
@@ -1,58 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Status is a STATUS command, as defined in RFC 3501 section 6.3.10.
|
||||
type Status struct {
|
||||
Mailbox string
|
||||
Items []imap.StatusItem
|
||||
}
|
||||
|
||||
func (cmd *Status) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, item := range cmd.Items {
|
||||
items[i] = imap.RawString(item)
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: "STATUS",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox), items},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Status) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
items, ok := fields[1].([]interface{})
|
||||
if !ok {
|
||||
return errors.New("STATUS command parameter is not a list")
|
||||
}
|
||||
cmd.Items = make([]imap.StatusItem, len(items))
|
||||
for i, f := range items {
|
||||
if s, ok := f.(string); !ok {
|
||||
return errors.New("Got a non-string field in a STATUS command parameter")
|
||||
} else {
|
||||
cmd.Items[i] = imap.StatusItem(strings.ToUpper(s))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
@@ -1,50 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Store is a STORE command, as defined in RFC 3501 section 6.4.6.
|
||||
type Store struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Item imap.StoreItem
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (cmd *Store) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "STORE",
|
||||
Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Item), cmd.Value},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Store) Parse(fields []interface{}) error {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
}
|
||||
var err error
|
||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item, ok := fields[1].(string); !ok {
|
||||
return errors.New("Item name must be a string")
|
||||
} else {
|
||||
cmd.Item = imap.StoreItem(strings.ToUpper(item))
|
||||
}
|
||||
|
||||
if len(fields[2:]) == 1 {
|
||||
cmd.Value = fields[2]
|
||||
} else {
|
||||
cmd.Value = fields[2:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
@@ -1,63 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Subscribe is a SUBSCRIBE command, as defined in RFC 3501 section 6.3.6.
|
||||
type Subscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "SUBSCRIBE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// An UNSUBSCRIBE command.
|
||||
// See RFC 3501 section 6.3.7
|
||||
type Unsubscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "UNSUBSCRIBE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enogh arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
@@ -1,44 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Uid is a UID command, as defined in RFC 3501 section 6.4.8. It wraps another
|
||||
// command (e.g. wrapping a Fetch command will result in a UID FETCH).
|
||||
type Uid struct {
|
||||
Cmd imap.Commander
|
||||
}
|
||||
|
||||
func (cmd *Uid) Command() *imap.Command {
|
||||
inner := cmd.Cmd.Command()
|
||||
|
||||
args := []interface{}{imap.RawString(inner.Name)}
|
||||
args = append(args, inner.Arguments...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "UID",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Uid) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No command name specified")
|
||||
}
|
||||
|
||||
name, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Command name must be a string")
|
||||
}
|
||||
|
||||
cmd.Cmd = &imap.Command{
|
||||
Name: strings.ToUpper(name), // Command names are case-insensitive
|
||||
Arguments: fields[1:],
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An UNSELECT command.
|
||||
// See RFC 3691 section 2.
|
||||
type Unselect struct{}
|
||||
|
||||
func (cmd *Unselect) Command() *imap.Command {
|
||||
return &imap.Command{Name: "UNSELECT"}
|
||||
}
|
||||
|
||||
func (cmd *Unselect) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
||||
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
@@ -1,284 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A connection state.
|
||||
// See RFC 3501 section 3.
|
||||
type ConnState int
|
||||
|
||||
const (
|
||||
// In the connecting state, the server has not yet sent a greeting and no
|
||||
// command can be issued.
|
||||
ConnectingState = 0
|
||||
|
||||
// In the not authenticated state, the client MUST supply
|
||||
// authentication credentials before most commands will be
|
||||
// permitted. This state is entered when a connection starts
|
||||
// unless the connection has been pre-authenticated.
|
||||
NotAuthenticatedState ConnState = 1 << 0
|
||||
|
||||
// In the authenticated state, the client is authenticated and MUST
|
||||
// select a mailbox to access before commands that affect messages
|
||||
// will be permitted. This state is entered when a
|
||||
// pre-authenticated connection starts, when acceptable
|
||||
// authentication credentials have been provided, after an error in
|
||||
// selecting a mailbox, or after a successful CLOSE command.
|
||||
AuthenticatedState = 1 << 1
|
||||
|
||||
// In a selected state, a mailbox has been selected to access.
|
||||
// This state is entered when a mailbox has been successfully
|
||||
// selected.
|
||||
SelectedState = AuthenticatedState + 1<<2
|
||||
|
||||
// In the logout state, the connection is being terminated. This
|
||||
// state can be entered as a result of a client request (via the
|
||||
// LOGOUT command) or by unilateral action on the part of either
|
||||
// the client or server.
|
||||
LogoutState = 1 << 3
|
||||
|
||||
// ConnectedState is either NotAuthenticatedState, AuthenticatedState or
|
||||
// SelectedState.
|
||||
ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
|
||||
)
|
||||
|
||||
// A function that upgrades a connection.
|
||||
//
|
||||
// This should only be used by libraries implementing an IMAP extension (e.g.
|
||||
// COMPRESS).
|
||||
type ConnUpgrader func(conn net.Conn) (net.Conn, error)
|
||||
|
||||
type Waiter struct {
|
||||
start sync.WaitGroup
|
||||
end sync.WaitGroup
|
||||
finished bool
|
||||
}
|
||||
|
||||
func NewWaiter() *Waiter {
|
||||
w := &Waiter{finished: false}
|
||||
w.start.Add(1)
|
||||
w.end.Add(1)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *Waiter) Wait() {
|
||||
if !w.finished {
|
||||
// Signal that we are ready for upgrade to continue.
|
||||
w.start.Done()
|
||||
// Wait for upgrade to finish.
|
||||
w.end.Wait()
|
||||
w.finished = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Waiter) WaitReady() {
|
||||
if !w.finished {
|
||||
// Wait for reader/writer goroutine to be ready for upgrade.
|
||||
w.start.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Waiter) Close() {
|
||||
if !w.finished {
|
||||
// Upgrade is finished, close chanel to release reader/writer
|
||||
w.end.Done()
|
||||
}
|
||||
}
|
||||
|
||||
type LockedWriter struct {
|
||||
lock sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewLockedWriter - goroutine safe writer.
|
||||
func NewLockedWriter(w io.Writer) io.Writer {
|
||||
return &LockedWriter{writer: w}
|
||||
}
|
||||
|
||||
func (w *LockedWriter) Write(b []byte) (int, error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
return w.writer.Write(b)
|
||||
}
|
||||
|
||||
type debugWriter struct {
|
||||
io.Writer
|
||||
|
||||
local io.Writer
|
||||
remote io.Writer
|
||||
}
|
||||
|
||||
// NewDebugWriter creates a new io.Writer that will write local network activity
|
||||
// to local and remote network activity to remote.
|
||||
func NewDebugWriter(local, remote io.Writer) io.Writer {
|
||||
return &debugWriter{Writer: local, local: local, remote: remote}
|
||||
}
|
||||
|
||||
type multiFlusher struct {
|
||||
flushers []flusher
|
||||
}
|
||||
|
||||
func (mf *multiFlusher) Flush() error {
|
||||
for _, f := range mf.flushers {
|
||||
if err := f.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMultiFlusher(flushers ...flusher) flusher {
|
||||
return &multiFlusher{flushers}
|
||||
}
|
||||
|
||||
// Underlying connection state information.
|
||||
type ConnInfo struct {
|
||||
RemoteAddr net.Addr
|
||||
LocalAddr net.Addr
|
||||
|
||||
// nil if connection is not using TLS.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// An IMAP connection.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
*Reader
|
||||
*Writer
|
||||
|
||||
br *bufio.Reader
|
||||
bw *bufio.Writer
|
||||
|
||||
waiter *Waiter
|
||||
|
||||
// Print all commands and responses to this io.Writer.
|
||||
debug io.Writer
|
||||
}
|
||||
|
||||
// NewConn creates a new IMAP connection.
|
||||
func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
|
||||
c := &Conn{Conn: conn, Reader: r, Writer: w}
|
||||
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Conn) createWaiter() *Waiter {
|
||||
// create new waiter each time.
|
||||
w := NewWaiter()
|
||||
c.waiter = w
|
||||
return w
|
||||
}
|
||||
|
||||
func (c *Conn) init() {
|
||||
r := io.Reader(c.Conn)
|
||||
w := io.Writer(c.Conn)
|
||||
|
||||
if c.debug != nil {
|
||||
localDebug, remoteDebug := c.debug, c.debug
|
||||
if debug, ok := c.debug.(*debugWriter); ok {
|
||||
localDebug, remoteDebug = debug.local, debug.remote
|
||||
}
|
||||
// If local and remote are the same, then we need a LockedWriter.
|
||||
if localDebug == remoteDebug {
|
||||
localDebug = NewLockedWriter(localDebug)
|
||||
remoteDebug = localDebug
|
||||
}
|
||||
|
||||
if localDebug != nil {
|
||||
w = io.MultiWriter(c.Conn, localDebug)
|
||||
}
|
||||
if remoteDebug != nil {
|
||||
r = io.TeeReader(c.Conn, remoteDebug)
|
||||
}
|
||||
}
|
||||
|
||||
if c.br == nil {
|
||||
c.br = bufio.NewReader(r)
|
||||
c.Reader.reader = c.br
|
||||
} else {
|
||||
c.br.Reset(r)
|
||||
}
|
||||
|
||||
if c.bw == nil {
|
||||
c.bw = bufio.NewWriter(w)
|
||||
c.Writer.Writer = c.bw
|
||||
} else {
|
||||
c.bw.Reset(w)
|
||||
}
|
||||
|
||||
if f, ok := c.Conn.(flusher); ok {
|
||||
c.Writer.Writer = struct {
|
||||
io.Writer
|
||||
flusher
|
||||
}{
|
||||
c.bw,
|
||||
newMultiFlusher(c.bw, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Info() *ConnInfo {
|
||||
info := &ConnInfo{
|
||||
RemoteAddr: c.RemoteAddr(),
|
||||
LocalAddr: c.LocalAddr(),
|
||||
}
|
||||
|
||||
tlsConn, ok := c.Conn.(*tls.Conn)
|
||||
if ok {
|
||||
state := tlsConn.ConnectionState()
|
||||
info.TLS = &state
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.Writer.Write(b)
|
||||
}
|
||||
|
||||
// Flush writes any buffered data to the underlying connection.
|
||||
func (c *Conn) Flush() error {
|
||||
return c.Writer.Flush()
|
||||
}
|
||||
|
||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
||||
// tunnel.
|
||||
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
||||
// Block reads and writes during the upgrading process
|
||||
w := c.createWaiter()
|
||||
defer w.Close()
|
||||
|
||||
upgraded, err := upgrader(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Conn = upgraded
|
||||
c.init()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Called by reader/writer goroutines to wait for Upgrade to finish
|
||||
func (c *Conn) Wait() {
|
||||
c.waiter.Wait()
|
||||
}
|
||||
|
||||
// Called by Upgrader to wait for reader/writer goroutines to be ready for
|
||||
// upgrade.
|
||||
func (c *Conn) WaitReady() {
|
||||
c.waiter.WaitReady()
|
||||
}
|
||||
|
||||
// SetDebug defines an io.Writer to which all network activity will be logged.
|
||||
// If nil is provided, network activity will not be logged.
|
||||
func (c *Conn) SetDebug(w io.Writer) {
|
||||
c.debug = w
|
||||
c.init()
|
||||
}
|
||||
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
@@ -1,71 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Date and time layouts.
|
||||
// Dovecot adds a leading zero to dates:
|
||||
// https://github.com/dovecot/core/blob/4fbd5c5e113078e72f29465ccc96d44955ceadc2/src/lib-imap/imap-date.c#L166
|
||||
// Cyrus adds a leading space to dates:
|
||||
// https://github.com/cyrusimap/cyrus-imapd/blob/1cb805a3bffbdf829df0964f3b802cdc917e76db/lib/times.c#L543
|
||||
// GMail doesn't support leading spaces in dates used in SEARCH commands.
|
||||
const (
|
||||
// Defined in RFC 3501 as date-text on page 83.
|
||||
DateLayout = "_2-Jan-2006"
|
||||
// Defined in RFC 3501 as date-time on page 83.
|
||||
DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
|
||||
// Defined in RFC 5322 section 3.3, mentioned as env-date in RFC 3501 page 84.
|
||||
envelopeDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
// Use as an example in RFC 3501 page 54.
|
||||
searchDateLayout = "2-Jan-2006"
|
||||
)
|
||||
|
||||
// time.Time with a specific layout.
|
||||
type (
|
||||
Date time.Time
|
||||
DateTime time.Time
|
||||
envelopeDateTime time.Time
|
||||
searchDate time.Time
|
||||
)
|
||||
|
||||
// Permutations of the layouts defined in RFC 5322, section 3.3.
|
||||
var envelopeDateTimeLayouts = [...]string{
|
||||
envelopeDateTimeLayout, // popular, try it first
|
||||
"_2 Jan 2006 15:04:05 -0700",
|
||||
"_2 Jan 2006 15:04:05 MST",
|
||||
"_2 Jan 2006 15:04 -0700",
|
||||
"_2 Jan 2006 15:04 MST",
|
||||
"_2 Jan 06 15:04:05 -0700",
|
||||
"_2 Jan 06 15:04:05 MST",
|
||||
"_2 Jan 06 15:04 -0700",
|
||||
"_2 Jan 06 15:04 MST",
|
||||
"Mon, _2 Jan 2006 15:04:05 -0700",
|
||||
"Mon, _2 Jan 2006 15:04:05 MST",
|
||||
"Mon, _2 Jan 2006 15:04 -0700",
|
||||
"Mon, _2 Jan 2006 15:04 MST",
|
||||
"Mon, _2 Jan 06 15:04:05 -0700",
|
||||
"Mon, _2 Jan 06 15:04:05 MST",
|
||||
"Mon, _2 Jan 06 15:04 -0700",
|
||||
"Mon, _2 Jan 06 15:04 MST",
|
||||
}
|
||||
|
||||
// TODO: this is a blunt way to strip any trailing CFWS (comment). A sharper
|
||||
// one would strip multiple CFWS, and only if really valid according to
|
||||
// RFC5322.
|
||||
var commentRE = regexp.MustCompile(`[ \t]+\(.*\)$`)
|
||||
|
||||
// Try parsing the date based on the layouts defined in RFC 5322, section 3.3.
|
||||
// Inspired by https://github.com/golang/go/blob/master/src/net/mail/message.go
|
||||
func parseMessageDateTime(maybeDate string) (time.Time, error) {
|
||||
maybeDate = commentRE.ReplaceAllString(maybeDate, "")
|
||||
for _, layout := range envelopeDateTimeLayouts {
|
||||
parsed, err := time.Parse(layout, maybeDate)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("date %s could not be parsed", maybeDate)
|
||||
}
|
||||
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
@@ -1,108 +0,0 @@
|
||||
// Package imap implements IMAP4rev1 (RFC 3501).
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A StatusItem is a mailbox status data item that can be retrieved with a
|
||||
// STATUS command. See RFC 3501 section 6.3.10.
|
||||
type StatusItem string
|
||||
|
||||
const (
|
||||
StatusMessages StatusItem = "MESSAGES"
|
||||
StatusRecent StatusItem = "RECENT"
|
||||
StatusUidNext StatusItem = "UIDNEXT"
|
||||
StatusUidValidity StatusItem = "UIDVALIDITY"
|
||||
StatusUnseen StatusItem = "UNSEEN"
|
||||
|
||||
StatusAppendLimit StatusItem = "APPENDLIMIT"
|
||||
)
|
||||
|
||||
// A FetchItem is a message data item that can be fetched.
|
||||
type FetchItem string
|
||||
|
||||
// List of items that can be fetched.
|
||||
const (
|
||||
// Macros
|
||||
FetchAll FetchItem = "ALL"
|
||||
FetchFast FetchItem = "FAST"
|
||||
FetchFull FetchItem = "FULL"
|
||||
|
||||
// Items
|
||||
FetchBody FetchItem = "BODY"
|
||||
FetchBodyStructure FetchItem = "BODYSTRUCTURE"
|
||||
FetchEnvelope FetchItem = "ENVELOPE"
|
||||
FetchFlags FetchItem = "FLAGS"
|
||||
FetchInternalDate FetchItem = "INTERNALDATE"
|
||||
FetchRFC822 FetchItem = "RFC822"
|
||||
FetchRFC822Header FetchItem = "RFC822.HEADER"
|
||||
FetchRFC822Size FetchItem = "RFC822.SIZE"
|
||||
FetchRFC822Text FetchItem = "RFC822.TEXT"
|
||||
FetchUid FetchItem = "UID"
|
||||
)
|
||||
|
||||
// Expand expands the item if it's a macro.
|
||||
func (item FetchItem) Expand() []FetchItem {
|
||||
switch item {
|
||||
case FetchAll:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope}
|
||||
case FetchFast:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size}
|
||||
case FetchFull:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope, FetchBody}
|
||||
default:
|
||||
return []FetchItem{item}
|
||||
}
|
||||
}
|
||||
|
||||
// FlagsOp is an operation that will be applied on message flags.
|
||||
type FlagsOp string
|
||||
|
||||
const (
|
||||
// SetFlags replaces existing flags by new ones.
|
||||
SetFlags FlagsOp = "FLAGS"
|
||||
// AddFlags adds new flags.
|
||||
AddFlags = "+FLAGS"
|
||||
// RemoveFlags removes existing flags.
|
||||
RemoveFlags = "-FLAGS"
|
||||
)
|
||||
|
||||
// silentOp can be appended to a FlagsOp to prevent the operation from
|
||||
// triggering unilateral message updates.
|
||||
const silentOp = ".SILENT"
|
||||
|
||||
// A StoreItem is a message data item that can be updated.
|
||||
type StoreItem string
|
||||
|
||||
// FormatFlagsOp returns the StoreItem that executes the flags operation op.
|
||||
func FormatFlagsOp(op FlagsOp, silent bool) StoreItem {
|
||||
s := string(op)
|
||||
if silent {
|
||||
s += silentOp
|
||||
}
|
||||
return StoreItem(s)
|
||||
}
|
||||
|
||||
// ParseFlagsOp parses a flags operation from StoreItem.
|
||||
func ParseFlagsOp(item StoreItem) (op FlagsOp, silent bool, err error) {
|
||||
itemStr := string(item)
|
||||
silent = strings.HasSuffix(itemStr, silentOp)
|
||||
if silent {
|
||||
itemStr = strings.TrimSuffix(itemStr, silentOp)
|
||||
}
|
||||
op = FlagsOp(itemStr)
|
||||
|
||||
if op != SetFlags && op != AddFlags && op != RemoveFlags {
|
||||
err = errors.New("Unsupported STORE operation")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CharsetReader, if non-nil, defines a function to generate charset-conversion
|
||||
// readers, converting from the provided charset into UTF-8. Charsets are always
|
||||
// lower-case. utf-8 and us-ascii charsets are handled by default. One of the
|
||||
// the CharsetReader's result values must be non-nil.
|
||||
var CharsetReader func(charset string, r io.Reader) (io.Reader, error)
|
||||
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A literal, as defined in RFC 3501 section 4.3.
|
||||
type Literal interface {
|
||||
io.Reader
|
||||
|
||||
// Len returns the number of bytes of the literal.
|
||||
Len() int
|
||||
}
|
||||
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
package imap
|
||||
|
||||
// Logger is the behaviour used by server/client to
|
||||
// report errors for accepting connections and unexpected behavior from handlers.
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
Println(v ...interface{})
|
||||
}
|
||||
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
@@ -1,314 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// The primary mailbox, as defined in RFC 3501 section 5.1.
|
||||
const InboxName = "INBOX"
|
||||
|
||||
// CanonicalMailboxName returns the canonical form of a mailbox name. Mailbox names can be
|
||||
// case-sensitive or case-insensitive depending on the backend implementation.
|
||||
// The special INBOX mailbox is case-insensitive.
|
||||
func CanonicalMailboxName(name string) string {
|
||||
if strings.ToUpper(name) == InboxName {
|
||||
return InboxName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Mailbox attributes definied in RFC 3501 section 7.2.2.
|
||||
const (
|
||||
// It is not possible for any child levels of hierarchy to exist under this\
|
||||
// name; no child levels exist now and none can be created in the future.
|
||||
NoInferiorsAttr = "\\Noinferiors"
|
||||
// It is not possible to use this name as a selectable mailbox.
|
||||
NoSelectAttr = "\\Noselect"
|
||||
// The mailbox has been marked "interesting" by the server; the mailbox
|
||||
// probably contains messages that have been added since the last time the
|
||||
// mailbox was selected.
|
||||
MarkedAttr = "\\Marked"
|
||||
// The mailbox does not contain any additional messages since the last time
|
||||
// the mailbox was selected.
|
||||
UnmarkedAttr = "\\Unmarked"
|
||||
)
|
||||
|
||||
// Mailbox attributes defined in RFC 6154 section 2 (SPECIAL-USE extension).
|
||||
const (
|
||||
// This mailbox presents all messages in the user's message store.
|
||||
AllAttr = "\\All"
|
||||
// This mailbox is used to archive messages.
|
||||
ArchiveAttr = "\\Archive"
|
||||
// This mailbox is used to hold draft messages -- typically, messages that are
|
||||
// being composed but have not yet been sent.
|
||||
DraftsAttr = "\\Drafts"
|
||||
// This mailbox presents all messages marked in some way as "important".
|
||||
FlaggedAttr = "\\Flagged"
|
||||
// This mailbox is where messages deemed to be junk mail are held.
|
||||
JunkAttr = "\\Junk"
|
||||
// This mailbox is used to hold copies of messages that have been sent.
|
||||
SentAttr = "\\Sent"
|
||||
// This mailbox is used to hold messages that have been deleted or marked for
|
||||
// deletion.
|
||||
TrashAttr = "\\Trash"
|
||||
)
|
||||
|
||||
// Mailbox attributes defined in RFC 3348 (CHILDREN extension)
|
||||
const (
|
||||
// The presence of this attribute indicates that the mailbox has child
|
||||
// mailboxes.
|
||||
HasChildrenAttr = "\\HasChildren"
|
||||
// The presence of this attribute indicates that the mailbox has no child
|
||||
// mailboxes.
|
||||
HasNoChildrenAttr = "\\HasNoChildren"
|
||||
)
|
||||
|
||||
// This mailbox attribute is a signal that the mailbox contains messages that
|
||||
// are likely important to the user. This attribute is defined in RFC 8457
|
||||
// section 3.
|
||||
const ImportantAttr = "\\Important"
|
||||
|
||||
// Basic mailbox info.
|
||||
type MailboxInfo struct {
|
||||
// The mailbox attributes.
|
||||
Attributes []string
|
||||
// The server's path separator.
|
||||
Delimiter string
|
||||
// The mailbox name.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Parse mailbox info from fields.
|
||||
func (info *MailboxInfo) Parse(fields []interface{}) error {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("Mailbox info needs at least 3 fields")
|
||||
}
|
||||
|
||||
var err error
|
||||
if info.Attributes, err = ParseStringList(fields[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if info.Delimiter, ok = fields[1].(string); !ok {
|
||||
// The delimiter may be specified as NIL, which gets converted to a nil interface.
|
||||
if fields[1] != nil {
|
||||
return errors.New("Mailbox delimiter must be a string")
|
||||
}
|
||||
info.Delimiter = ""
|
||||
}
|
||||
|
||||
if name, err := ParseString(fields[2]); err != nil {
|
||||
return err
|
||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
info.Name = CanonicalMailboxName(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format mailbox info to fields.
|
||||
func (info *MailboxInfo) Format() []interface{} {
|
||||
name, _ := utf7.Encoding.NewEncoder().String(info.Name)
|
||||
attrs := make([]interface{}, len(info.Attributes))
|
||||
for i, attr := range info.Attributes {
|
||||
attrs[i] = RawString(attr)
|
||||
}
|
||||
|
||||
// If the delimiter is NIL, we need to treat it specially by inserting
|
||||
// a nil field (so that it's later converted to an unquoted NIL atom).
|
||||
var del interface{}
|
||||
|
||||
if info.Delimiter != "" {
|
||||
del = info.Delimiter
|
||||
}
|
||||
|
||||
// Thunderbird doesn't understand delimiters if not quoted
|
||||
return []interface{}{attrs, del, FormatMailboxName(name)}
|
||||
}
|
||||
|
||||
// TODO: optimize this
|
||||
func (info *MailboxInfo) match(name, pattern string) bool {
|
||||
i := strings.IndexAny(pattern, "*%")
|
||||
if i == -1 {
|
||||
// No more wildcards
|
||||
return name == pattern
|
||||
}
|
||||
|
||||
// Get parts before and after wildcard
|
||||
chunk, wildcard, rest := pattern[0:i], pattern[i], pattern[i+1:]
|
||||
|
||||
// Check that name begins with chunk
|
||||
if len(chunk) > 0 && !strings.HasPrefix(name, chunk) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, chunk)
|
||||
|
||||
// Expand wildcard
|
||||
var j int
|
||||
for j = 0; j < len(name); j++ {
|
||||
if wildcard == '%' && string(name[j]) == info.Delimiter {
|
||||
break // Stop on delimiter if wildcard is %
|
||||
}
|
||||
// Try to match the rest from here
|
||||
if info.match(name[j:], rest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return info.match(name[j:], rest)
|
||||
}
|
||||
|
||||
// Match checks if a reference and a pattern matches this mailbox name, as
|
||||
// defined in RFC 3501 section 6.3.8.
|
||||
func (info *MailboxInfo) Match(reference, pattern string) bool {
|
||||
name := info.Name
|
||||
|
||||
if info.Delimiter != "" && strings.HasPrefix(pattern, info.Delimiter) {
|
||||
reference = ""
|
||||
pattern = strings.TrimPrefix(pattern, info.Delimiter)
|
||||
}
|
||||
if reference != "" {
|
||||
if info.Delimiter != "" && !strings.HasSuffix(reference, info.Delimiter) {
|
||||
reference += info.Delimiter
|
||||
}
|
||||
if !strings.HasPrefix(name, reference) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, reference)
|
||||
}
|
||||
|
||||
return info.match(name, pattern)
|
||||
}
|
||||
|
||||
// A mailbox status.
|
||||
type MailboxStatus struct {
|
||||
// The mailbox name.
|
||||
Name string
|
||||
// True if the mailbox is open in read-only mode.
|
||||
ReadOnly bool
|
||||
// The mailbox items that are currently filled in. This map's values
|
||||
// should not be used directly, they must only be used by libraries
|
||||
// implementing extensions of the IMAP protocol.
|
||||
Items map[StatusItem]interface{}
|
||||
|
||||
// The Items map may be accessed in different goroutines. Protect
|
||||
// concurrent writes.
|
||||
ItemsLocker sync.Mutex
|
||||
|
||||
// The mailbox flags.
|
||||
Flags []string
|
||||
// The mailbox permanent flags.
|
||||
PermanentFlags []string
|
||||
// The sequence number of the first unseen message in the mailbox.
|
||||
UnseenSeqNum uint32
|
||||
|
||||
// The number of messages in this mailbox.
|
||||
Messages uint32
|
||||
// The number of messages not seen since the last time the mailbox was opened.
|
||||
Recent uint32
|
||||
// The number of unread messages.
|
||||
Unseen uint32
|
||||
// The next UID.
|
||||
UidNext uint32
|
||||
// Together with a UID, it is a unique identifier for a message.
|
||||
// Must be greater than or equal to 1.
|
||||
UidValidity uint32
|
||||
|
||||
// Per-mailbox limit of message size. Set only if server supports the
|
||||
// APPENDLIMIT extension.
|
||||
AppendLimit uint32
|
||||
}
|
||||
|
||||
// Create a new mailbox status that will contain the specified items.
|
||||
func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
|
||||
status := &MailboxStatus{
|
||||
Name: name,
|
||||
Items: make(map[StatusItem]interface{}),
|
||||
}
|
||||
|
||||
for _, k := range items {
|
||||
status.Items[k] = nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Parse(fields []interface{}) error {
|
||||
status.Items = make(map[StatusItem]interface{})
|
||||
|
||||
var k StatusItem
|
||||
for i, f := range fields {
|
||||
if i%2 == 0 {
|
||||
if kstr, ok := f.(string); !ok {
|
||||
return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
|
||||
} else {
|
||||
k = StatusItem(strings.ToUpper(kstr))
|
||||
}
|
||||
} else {
|
||||
status.Items[k] = nil
|
||||
|
||||
var err error
|
||||
switch k {
|
||||
case StatusMessages:
|
||||
status.Messages, err = ParseNumber(f)
|
||||
case StatusRecent:
|
||||
status.Recent, err = ParseNumber(f)
|
||||
case StatusUnseen:
|
||||
status.Unseen, err = ParseNumber(f)
|
||||
case StatusUidNext:
|
||||
status.UidNext, err = ParseNumber(f)
|
||||
case StatusUidValidity:
|
||||
status.UidValidity, err = ParseNumber(f)
|
||||
case StatusAppendLimit:
|
||||
status.AppendLimit, err = ParseNumber(f)
|
||||
default:
|
||||
status.Items[k] = f
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
for k, v := range status.Items {
|
||||
switch k {
|
||||
case StatusMessages:
|
||||
v = status.Messages
|
||||
case StatusRecent:
|
||||
v = status.Recent
|
||||
case StatusUnseen:
|
||||
v = status.Unseen
|
||||
case StatusUidNext:
|
||||
v = status.UidNext
|
||||
case StatusUidValidity:
|
||||
v = status.UidValidity
|
||||
case StatusAppendLimit:
|
||||
v = status.AppendLimit
|
||||
}
|
||||
|
||||
fields = append(fields, RawString(k), v)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func FormatMailboxName(name string) interface{} {
|
||||
// Some e-mails servers don't handle quoted INBOX names correctly so we special-case it.
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
return RawString(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
1187
vendor/github.com/emersion/go-imap/message.go
generated
vendored
1187
vendor/github.com/emersion/go-imap/message.go
generated
vendored
File diff suppressed because it is too large
Load Diff
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
@@ -1,467 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
sp = ' '
|
||||
cr = '\r'
|
||||
lf = '\n'
|
||||
dquote = '"'
|
||||
literalStart = '{'
|
||||
literalEnd = '}'
|
||||
listStart = '('
|
||||
listEnd = ')'
|
||||
respCodeStart = '['
|
||||
respCodeEnd = ']'
|
||||
)
|
||||
|
||||
const (
|
||||
crlf = "\r\n"
|
||||
nilAtom = "NIL"
|
||||
)
|
||||
|
||||
// TODO: add CTL to atomSpecials
|
||||
var (
|
||||
quotedSpecials = string([]rune{dquote, '\\'})
|
||||
respSpecials = string([]rune{respCodeEnd})
|
||||
atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
|
||||
)
|
||||
|
||||
type parseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func newParseError(text string) error {
|
||||
return &parseError{errors.New(text)}
|
||||
}
|
||||
|
||||
// IsParseError returns true if the provided error is a parse error produced by
|
||||
// Reader.
|
||||
func IsParseError(err error) bool {
|
||||
_, ok := err.(*parseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// A string reader.
|
||||
type StringReader interface {
|
||||
// ReadString reads until the first occurrence of delim in the input,
|
||||
// returning a string containing the data up to and including the delimiter.
|
||||
// See https://golang.org/pkg/bufio/#Reader.ReadString
|
||||
ReadString(delim byte) (line string, err error)
|
||||
}
|
||||
|
||||
type reader interface {
|
||||
io.Reader
|
||||
io.RuneScanner
|
||||
StringReader
|
||||
}
|
||||
|
||||
// ParseNumber parses a number.
|
||||
func ParseNumber(f interface{}) (uint32, error) {
|
||||
// Useful for tests
|
||||
if n, ok := f.(uint32); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var s string
|
||||
switch f := f.(type) {
|
||||
case RawString:
|
||||
s = string(f)
|
||||
case string:
|
||||
s = f
|
||||
default:
|
||||
return 0, newParseError("expected a number, got a non-atom")
|
||||
}
|
||||
|
||||
nbr, err := strconv.ParseUint(string(s), 10, 32)
|
||||
if err != nil {
|
||||
return 0, &parseError{err}
|
||||
}
|
||||
|
||||
return uint32(nbr), nil
|
||||
}
|
||||
|
||||
// ParseString parses a string, which is either a literal, a quoted string or an
|
||||
// atom.
|
||||
func ParseString(f interface{}) (string, error) {
|
||||
if s, ok := f.(string); ok {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Useful for tests
|
||||
if a, ok := f.(RawString); ok {
|
||||
return string(a), nil
|
||||
}
|
||||
|
||||
if l, ok := f.(Literal); ok {
|
||||
b := make([]byte, l.Len())
|
||||
if _, err := io.ReadFull(l, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "", newParseError("expected a string")
|
||||
}
|
||||
|
||||
// Convert a field list to a string list.
|
||||
func ParseStringList(f interface{}) ([]string, error) {
|
||||
fields, ok := f.([]interface{})
|
||||
if !ok {
|
||||
return nil, newParseError("expected a string list, got a non-list")
|
||||
}
|
||||
|
||||
list := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
var err error
|
||||
if list[i], err = ParseString(f); err != nil {
|
||||
return nil, newParseError("cannot parse string in string list: " + err.Error())
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func trimSuffix(str string, suffix rune) string {
|
||||
return str[:len(str)-1]
|
||||
}
|
||||
|
||||
// An IMAP reader.
|
||||
type Reader struct {
|
||||
MaxLiteralSize uint32 // The maximum literal size.
|
||||
|
||||
reader
|
||||
|
||||
continues chan<- bool
|
||||
|
||||
brackets int
|
||||
inRespCode bool
|
||||
}
|
||||
|
||||
func (r *Reader) ReadSp() error {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if char != sp {
|
||||
return newParseError("expected a space")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadCrlf() (err error) {
|
||||
var char rune
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char == lf {
|
||||
return
|
||||
}
|
||||
if char != cr {
|
||||
err = newParseError("line doesn't end with a CR")
|
||||
return
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != lf {
|
||||
err = newParseError("line doesn't end with a LF")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadAtom() (interface{}, error) {
|
||||
r.brackets = 0
|
||||
|
||||
var atom string
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: list-wildcards and \
|
||||
if r.brackets == 0 && (char == listStart || char == literalStart || char == dquote) {
|
||||
return nil, newParseError("atom contains forbidden char: " + string(char))
|
||||
}
|
||||
if char == cr || char == lf {
|
||||
break
|
||||
}
|
||||
if r.brackets == 0 && (char == sp || char == listEnd) {
|
||||
break
|
||||
}
|
||||
if char == respCodeEnd {
|
||||
if r.brackets == 0 {
|
||||
if r.inRespCode {
|
||||
break
|
||||
} else {
|
||||
return nil, newParseError("atom contains bad brackets nesting")
|
||||
}
|
||||
}
|
||||
r.brackets--
|
||||
}
|
||||
if char == respCodeStart {
|
||||
r.brackets++
|
||||
}
|
||||
|
||||
atom += string(char)
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
|
||||
if atom == nilAtom {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return atom, nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLiteral() (Literal, error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if char != literalStart {
|
||||
return nil, newParseError("literal string doesn't start with an open brace")
|
||||
}
|
||||
|
||||
lstr, err := r.ReadString(byte(literalEnd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lstr = trimSuffix(lstr, literalEnd)
|
||||
|
||||
nonSync := strings.HasSuffix(lstr, "+")
|
||||
if nonSync {
|
||||
lstr = trimSuffix(lstr, '+')
|
||||
}
|
||||
|
||||
n, err := strconv.ParseUint(lstr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newParseError("cannot parse literal length: " + err.Error())
|
||||
}
|
||||
if r.MaxLiteralSize > 0 && uint32(n) > r.MaxLiteralSize {
|
||||
return nil, newParseError("literal exceeding maximum size")
|
||||
}
|
||||
|
||||
if err := r.ReadCrlf(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send continuation request if necessary
|
||||
if r.continues != nil && !nonSync {
|
||||
r.continues <- true
|
||||
}
|
||||
|
||||
// Read literal
|
||||
b := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(b), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadQuotedString() (string, error) {
|
||||
if char, _, err := r.ReadRune(); err != nil {
|
||||
return "", err
|
||||
} else if char != dquote {
|
||||
return "", newParseError("quoted string doesn't start with a double quote")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var escaped bool
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if char == '\\' && !escaped {
|
||||
escaped = true
|
||||
} else {
|
||||
if char == cr || char == lf {
|
||||
r.UnreadRune()
|
||||
return "", newParseError("CR or LF not allowed in quoted string")
|
||||
}
|
||||
if char == dquote && !escaped {
|
||||
break
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(quotedSpecials, char) && escaped {
|
||||
return "", newParseError("quoted string cannot contain backslash followed by a non-quoted-specials char")
|
||||
}
|
||||
|
||||
buf.WriteRune(char)
|
||||
escaped = false
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadFields() (fields []interface{}, err error) {
|
||||
var char rune
|
||||
for {
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = r.UnreadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var field interface{}
|
||||
ok := true
|
||||
switch char {
|
||||
case literalStart:
|
||||
field, err = r.ReadLiteral()
|
||||
case dquote:
|
||||
field, err = r.ReadQuotedString()
|
||||
case listStart:
|
||||
field, err = r.ReadList()
|
||||
case listEnd:
|
||||
ok = false
|
||||
case cr:
|
||||
return
|
||||
default:
|
||||
field, err = r.ReadAtom()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char == cr || char == lf || char == listEnd || char == respCodeEnd {
|
||||
if char == cr || char == lf {
|
||||
r.UnreadRune()
|
||||
}
|
||||
return
|
||||
}
|
||||
if char == listStart {
|
||||
r.UnreadRune()
|
||||
continue
|
||||
}
|
||||
if char != sp {
|
||||
err = newParseError("fields are not separated by a space")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) ReadList() (fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != listStart {
|
||||
err = newParseError("list doesn't start with an open parenthesis")
|
||||
return
|
||||
}
|
||||
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != listEnd {
|
||||
err = newParseError("list doesn't end with a close parenthesis")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLine() (fields []interface{}, err error) {
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
err = r.ReadCrlf()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadRespCode() (code StatusRespCode, fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeStart {
|
||||
err = newParseError("response code doesn't start with an open bracket")
|
||||
return
|
||||
}
|
||||
|
||||
r.inRespCode = true
|
||||
fields, err = r.ReadFields()
|
||||
r.inRespCode = false
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
err = newParseError("response code doesn't contain any field")
|
||||
return
|
||||
}
|
||||
|
||||
codeStr, ok := fields[0].(string)
|
||||
if !ok {
|
||||
err = newParseError("response code doesn't start with a string atom")
|
||||
return
|
||||
}
|
||||
if codeStr == "" {
|
||||
err = newParseError("response code is empty")
|
||||
return
|
||||
}
|
||||
code = StatusRespCode(strings.ToUpper(codeStr))
|
||||
|
||||
fields = fields[1:]
|
||||
|
||||
r.UnreadRune()
|
||||
char, _, err = r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeEnd {
|
||||
err = newParseError("response code doesn't end with a close bracket")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadInfo() (info string, err error) {
|
||||
info, err = r.ReadString(byte(lf))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info = strings.TrimSuffix(info, string(lf))
|
||||
info = strings.TrimSuffix(info, string(cr))
|
||||
info = strings.TrimLeft(info, " ")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewReader(r reader) *Reader {
|
||||
return &Reader{reader: r}
|
||||
}
|
||||
|
||||
func NewServerReader(r reader, continues chan<- bool) *Reader {
|
||||
return &Reader{reader: r, continues: continues}
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Parse(fields []interface{}) error
|
||||
}
|
||||
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
@@ -1,181 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Resp is an IMAP response. It is either a *DataResp, a
|
||||
// *ContinuationReq or a *StatusResp.
|
||||
type Resp interface {
|
||||
resp()
|
||||
}
|
||||
|
||||
// ReadResp reads a single response from a Reader.
|
||||
func ReadResp(r *Reader) (Resp, error) {
|
||||
atom, err := r.ReadAtom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag, ok := atom.(string)
|
||||
if !ok {
|
||||
return nil, newParseError("response tag is not an atom")
|
||||
}
|
||||
|
||||
if tag == "+" {
|
||||
if err := r.ReadSp(); err != nil {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
resp := &ContinuationReq{}
|
||||
resp.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if err := r.ReadSp(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Can be either data or status
|
||||
// Try to parse a status
|
||||
var fields []interface{}
|
||||
if atom, err := r.ReadAtom(); err == nil {
|
||||
fields = append(fields, atom)
|
||||
|
||||
if err := r.ReadSp(); err == nil {
|
||||
if name, ok := atom.(string); ok {
|
||||
status := StatusRespType(name)
|
||||
switch status {
|
||||
case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
|
||||
resp := &StatusResp{
|
||||
Tag: tag,
|
||||
Type: status,
|
||||
}
|
||||
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.UnreadRune()
|
||||
|
||||
if char == '[' {
|
||||
// Contains code & arguments
|
||||
resp.Code, resp.Arguments, err = r.ReadRespCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
// Not a status so it's data
|
||||
resp := &DataResp{Tag: tag}
|
||||
|
||||
var remaining []interface{}
|
||||
remaining, err = r.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Fields = append(fields, remaining...)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DataResp is an IMAP response containing data.
|
||||
type DataResp struct {
|
||||
// The response tag. Can be either "" for untagged responses, "+" for continuation
|
||||
// requests or a previous command's tag.
|
||||
Tag string
|
||||
// The parsed response fields.
|
||||
Fields []interface{}
|
||||
}
|
||||
|
||||
// NewUntaggedResp creates a new untagged response.
|
||||
func NewUntaggedResp(fields []interface{}) *DataResp {
|
||||
return &DataResp{
|
||||
Tag: "*",
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DataResp) resp() {}
|
||||
|
||||
func (r *DataResp) WriteTo(w *Writer) error {
|
||||
tag := RawString(r.Tag)
|
||||
if tag == "" {
|
||||
tag = RawString("*")
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(tag)}
|
||||
fields = append(fields, r.Fields...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// ContinuationReq is a continuation request response.
|
||||
type ContinuationReq struct {
|
||||
// The info message sent with the continuation request.
|
||||
Info string
|
||||
}
|
||||
|
||||
func (r *ContinuationReq) resp() {}
|
||||
|
||||
func (r *ContinuationReq) WriteTo(w *Writer) error {
|
||||
if err := w.writeString("+"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Info != "" {
|
||||
if err := w.writeString(string(sp) + r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
// ParseNamedResp attempts to parse a named data response.
|
||||
func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
|
||||
data, ok := resp.(*DataResp)
|
||||
if !ok || len(data.Fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Some responses (namely EXISTS and RECENT) are formatted like so:
|
||||
// [num] [name] [...]
|
||||
// Which is fucking stupid. But we handle that here by checking if the
|
||||
// response name is a number and then rearranging it.
|
||||
if len(data.Fields) > 1 {
|
||||
name, ok := data.Fields[1].(string)
|
||||
if ok {
|
||||
if _, err := ParseNumber(data.Fields[0]); err == nil {
|
||||
fields := []interface{}{data.Fields[0]}
|
||||
fields = append(fields, data.Fields[2:]...)
|
||||
return strings.ToUpper(name), fields, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP commands are formatted like this:
|
||||
// [name] [...]
|
||||
name, ok = data.Fields[0].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
return strings.ToUpper(name), data.Fields[1:], true
|
||||
}
|
||||
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
@@ -1,61 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// An AUTHENTICATE response.
|
||||
type Authenticate struct {
|
||||
Mechanism sasl.Client
|
||||
InitialResponse []byte
|
||||
RepliesCh chan []byte
|
||||
}
|
||||
|
||||
// Implements
|
||||
func (r *Authenticate) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Authenticate) writeLine(l string) error {
|
||||
r.RepliesCh <- []byte(l + "\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Authenticate) cancel() error {
|
||||
return r.writeLine("*")
|
||||
}
|
||||
|
||||
func (r *Authenticate) Handle(resp imap.Resp) error {
|
||||
cont, ok := resp.(*imap.ContinuationReq)
|
||||
if !ok {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
// Empty challenge, send initial response as stated in RFC 2222 section 5.1
|
||||
if cont.Info == "" && r.InitialResponse != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
|
||||
if err := r.writeLine(encoded); err != nil {
|
||||
return err
|
||||
}
|
||||
r.InitialResponse = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
challenge, err := base64.StdEncoding.DecodeString(cont.Info)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := r.Mechanism.Next(challenge)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(reply)
|
||||
return r.writeLine(encoded)
|
||||
}
|
||||
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A CAPABILITY response.
|
||||
// See RFC 3501 section 7.2.1
|
||||
type Capability struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Capability) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("CAPABILITY")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An ENABLED response, defined in RFC 5161 section 3.2.
|
||||
type Enabled struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Enabled) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "ENABLED" {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if caps, err := imap.ParseStringList(fields); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Caps = append(r.Caps, caps...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Enabled) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("ENABLED")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
@@ -1,43 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const expungeName = "EXPUNGE"
|
||||
|
||||
// An EXPUNGE response.
|
||||
// See RFC 3501 section 7.4.1
|
||||
type Expunge struct {
|
||||
SeqNums chan uint32
|
||||
}
|
||||
|
||||
func (r *Expunge) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != expungeName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SeqNums <- seqNum
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Expunge) WriteTo(w *imap.Writer) error {
|
||||
for seqNum := range r.SeqNums {
|
||||
resp := imap.NewUntaggedResp([]interface{}{seqNum, imap.RawString(expungeName)})
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
@@ -1,70 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const fetchName = "FETCH"
|
||||
|
||||
// A FETCH response.
|
||||
// See RFC 3501 section 7.4.2
|
||||
type Fetch struct {
|
||||
Messages chan *imap.Message
|
||||
SeqSet *imap.SeqSet
|
||||
Uid bool
|
||||
}
|
||||
|
||||
func (r *Fetch) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != fetchName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgFields, _ := fields[1].([]interface{})
|
||||
msg := &imap.Message{SeqNum: seqNum}
|
||||
if err := msg.Parse(msgFields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Uid && msg.Uid == 0 {
|
||||
// we requested UIDs and got a message without one --> unilateral update --> ignore
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var num uint32
|
||||
if r.Uid {
|
||||
num = msg.Uid
|
||||
} else {
|
||||
num = seqNum
|
||||
}
|
||||
|
||||
// Check whether we obtained a result we requested with our SeqSet
|
||||
// If the result is not contained in our SeqSet we have to handle an additional special case:
|
||||
// In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
|
||||
// is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
|
||||
// Thus, such a result is correct and has to be returned by us.
|
||||
if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Messages <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Fetch) WriteTo(w *imap.Writer) error {
|
||||
var err error
|
||||
for msg := range r.Messages {
|
||||
resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.RawString(fetchName), msg.Format()})
|
||||
if err == nil {
|
||||
err = resp.WriteTo(w)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An IDLE response.
|
||||
type Idle struct {
|
||||
RepliesCh chan []byte
|
||||
Stop <-chan struct{}
|
||||
|
||||
gotContinuationReq bool
|
||||
}
|
||||
|
||||
func (r *Idle) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Idle) stop() {
|
||||
r.RepliesCh <- []byte("DONE\r\n")
|
||||
}
|
||||
|
||||
func (r *Idle) Handle(resp imap.Resp) error {
|
||||
// Wait for a continuation request
|
||||
if _, ok := resp.(*imap.ContinuationReq); ok && !r.gotContinuationReq {
|
||||
r.gotContinuationReq = true
|
||||
|
||||
// We got a continuation request, wait for r.Stop to be closed
|
||||
go func() {
|
||||
<-r.Stop
|
||||
r.stop()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrUnhandled
|
||||
}
|
||||
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
@@ -1,57 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const (
|
||||
listName = "LIST"
|
||||
lsubName = "LSUB"
|
||||
)
|
||||
|
||||
// A LIST response.
|
||||
// If Subscribed is set to true, LSUB will be used instead.
|
||||
// See RFC 3501 section 7.2.2
|
||||
type List struct {
|
||||
Mailboxes chan *imap.MailboxInfo
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (r *List) Name() string {
|
||||
if r.Subscribed {
|
||||
return lsubName
|
||||
} else {
|
||||
return listName
|
||||
}
|
||||
}
|
||||
|
||||
func (r *List) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != r.Name() {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxInfo{}
|
||||
if err := mbox.Parse(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Mailboxes <- mbox
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *List) WriteTo(w *imap.Writer) error {
|
||||
respName := r.Name()
|
||||
|
||||
for mbox := range r.Mailboxes {
|
||||
fields := []interface{}{imap.RawString(respName)}
|
||||
fields = append(fields, mbox.Format()...)
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
@@ -1,35 +0,0 @@
|
||||
// IMAP responses defined in RFC 3501.
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// ErrUnhandled is used when a response hasn't been handled.
|
||||
var ErrUnhandled = errors.New("imap: unhandled response")
|
||||
|
||||
var errNotEnoughFields = errors.New("imap: not enough fields in response")
|
||||
|
||||
// Handler handles responses.
|
||||
type Handler interface {
|
||||
// Handle processes a response. If the response cannot be processed,
|
||||
// ErrUnhandledResp must be returned.
|
||||
Handle(resp imap.Resp) error
|
||||
}
|
||||
|
||||
// HandlerFunc is a function that handles responses.
|
||||
type HandlerFunc func(resp imap.Resp) error
|
||||
|
||||
// Handle implements Handler.
|
||||
func (f HandlerFunc) Handle(resp imap.Resp) error {
|
||||
return f(resp)
|
||||
}
|
||||
|
||||
// Replier is a Handler that needs to send raw data (for instance
|
||||
// AUTHENTICATE).
|
||||
type Replier interface {
|
||||
Handler
|
||||
Replies() <-chan []byte
|
||||
}
|
||||
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
@@ -1,41 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const searchName = "SEARCH"
|
||||
|
||||
// A SEARCH response.
|
||||
// See RFC 3501 section 7.2.5
|
||||
type Search struct {
|
||||
Ids []uint32
|
||||
}
|
||||
|
||||
func (r *Search) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != searchName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Ids = make([]uint32, len(fields))
|
||||
for i, f := range fields {
|
||||
if id, err := imap.ParseNumber(f); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Ids[i] = id
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Search) WriteTo(w *imap.Writer) (err error) {
|
||||
fields := []interface{}{imap.RawString(searchName)}
|
||||
for _, id := range r.Ids {
|
||||
fields = append(fields, id)
|
||||
}
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
return resp.WriteTo(w)
|
||||
}
|
||||
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
@@ -1,142 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A SELECT response.
|
||||
type Select struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Select) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
switch resp := resp.(type) {
|
||||
case *imap.DataResp:
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "FLAGS" {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
flags, _ := fields[0].([]interface{})
|
||||
mbox.Flags, _ = imap.ParseStringList(flags)
|
||||
case *imap.StatusResp:
|
||||
if len(resp.Arguments) < 1 {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var item imap.StatusItem
|
||||
switch resp.Code {
|
||||
case "UNSEEN":
|
||||
mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
case "PERMANENTFLAGS":
|
||||
flags, _ := resp.Arguments[0].([]interface{})
|
||||
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
|
||||
case "UIDNEXT":
|
||||
mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidNext
|
||||
case "UIDVALIDITY":
|
||||
mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidValidity
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if item != "" {
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[item] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
}
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Select) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
|
||||
if mbox.Flags != nil {
|
||||
flags := make([]interface{}, len(mbox.Flags))
|
||||
for i, f := range mbox.Flags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
res := imap.NewUntaggedResp([]interface{}{imap.RawString("FLAGS"), flags})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.PermanentFlags != nil {
|
||||
flags := make([]interface{}, len(mbox.PermanentFlags))
|
||||
for i, f := range mbox.PermanentFlags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodePermanentFlags,
|
||||
Arguments: []interface{}{flags},
|
||||
Info: "Flags permitted.",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.UnseenSeqNum > 0 {
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUnseen,
|
||||
Arguments: []interface{}{mbox.UnseenSeqNum},
|
||||
Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for k := range r.Mailbox.Items {
|
||||
switch k {
|
||||
case imap.StatusMessages:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Messages, imap.RawString("EXISTS")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusRecent:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Recent, imap.RawString("RECENT")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidNext:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidNext,
|
||||
Arguments: []interface{}{mbox.UidNext},
|
||||
Info: "Predicted next UID",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidValidity:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidValidity,
|
||||
Arguments: []interface{}{mbox.UidValidity},
|
||||
Info: "UIDs valid",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
@@ -1,53 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
const statusName = "STATUS"
|
||||
|
||||
// A STATUS response.
|
||||
// See RFC 3501 section 7.2.4
|
||||
type Status struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Status) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != statusName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 2 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
if name, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
mbox.Name = imap.CanonicalMailboxName(name)
|
||||
}
|
||||
|
||||
var items []interface{}
|
||||
if items, ok = fields[1].([]interface{}); !ok {
|
||||
return errors.New("STATUS response expects a list as second argument")
|
||||
}
|
||||
|
||||
mbox.Items = nil
|
||||
return mbox.Parse(items)
|
||||
}
|
||||
|
||||
func (r *Status) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
|
||||
fields := []interface{}{imap.RawString(statusName), imap.FormatMailboxName(name), mbox.Format()}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
@@ -1,371 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func maybeString(mystery interface{}) string {
|
||||
if s, ok := mystery.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convertField(f interface{}, charsetReader func(io.Reader) io.Reader) string {
|
||||
// An IMAP string contains only 7-bit data, no need to decode it
|
||||
if s, ok := f.(string); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
// If no charset is provided, getting directly the string is faster
|
||||
if charsetReader == nil {
|
||||
if stringer, ok := f.(fmt.Stringer); ok {
|
||||
return stringer.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Not a string, it must be a literal
|
||||
l, ok := f.(Literal)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var r io.Reader = l
|
||||
if charsetReader != nil {
|
||||
if dec := charsetReader(r); dec != nil {
|
||||
r = dec
|
||||
}
|
||||
}
|
||||
|
||||
b := make([]byte, l.Len())
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func popSearchField(fields []interface{}) (interface{}, []interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil, errors.New("imap: no enough fields for search key")
|
||||
}
|
||||
return fields[0], fields[1:], nil
|
||||
}
|
||||
|
||||
// SearchCriteria is a search criteria. A message matches the criteria if and
|
||||
// only if it matches each one of its fields.
|
||||
type SearchCriteria struct {
|
||||
SeqNum *SeqSet // Sequence number is in sequence set
|
||||
Uid *SeqSet // UID is in sequence set
|
||||
|
||||
// Time and timezone are ignored
|
||||
Since time.Time // Internal date is since this date
|
||||
Before time.Time // Internal date is before this date
|
||||
SentSince time.Time // Date header field is since this date
|
||||
SentBefore time.Time // Date header field is before this date
|
||||
|
||||
Header textproto.MIMEHeader // Each header field value is present
|
||||
Body []string // Each string is in the body
|
||||
Text []string // Each string is in the text (header + body)
|
||||
|
||||
WithFlags []string // Each flag is present
|
||||
WithoutFlags []string // Each flag is not present
|
||||
|
||||
Larger uint32 // Size is larger than this number
|
||||
Smaller uint32 // Size is smaller than this number
|
||||
|
||||
Not []*SearchCriteria // Each criteria doesn't match
|
||||
Or [][2]*SearchCriteria // Each criteria pair has at least one match of two
|
||||
}
|
||||
|
||||
// NewSearchCriteria creates a new search criteria.
|
||||
func NewSearchCriteria() *SearchCriteria {
|
||||
return &SearchCriteria{Header: make(textproto.MIMEHeader)}
|
||||
}
|
||||
|
||||
func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.Reader) io.Reader) ([]interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
f := fields[0]
|
||||
fields = fields[1:]
|
||||
|
||||
if subfields, ok := f.([]interface{}); ok {
|
||||
return fields, c.ParseWithCharset(subfields, charsetReader)
|
||||
}
|
||||
|
||||
key, ok := f.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("imap: invalid search criteria field type: %T", f)
|
||||
}
|
||||
key = strings.ToUpper(key)
|
||||
|
||||
var err error
|
||||
switch key {
|
||||
case "ALL":
|
||||
// Nothing to do
|
||||
case "ANSWERED", "DELETED", "DRAFT", "FLAGGED", "RECENT", "SEEN":
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag("\\"+key))
|
||||
case "BCC", "CC", "FROM", "SUBJECT", "TO":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(key, convertField(f, charsetReader))
|
||||
case "BEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Before.IsZero() || t.Before(c.Before) {
|
||||
c.Before = t
|
||||
}
|
||||
case "BODY":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Body = append(c.Body, convertField(f, charsetReader))
|
||||
}
|
||||
case "HEADER":
|
||||
var f1, f2 interface{}
|
||||
if f1, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if f2, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(maybeString(f1), convertField(f2, charsetReader))
|
||||
}
|
||||
case "KEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
case "LARGER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Larger == 0 || n > c.Larger {
|
||||
c.Larger = n
|
||||
}
|
||||
case "NEW":
|
||||
c.WithFlags = append(c.WithFlags, RecentFlag)
|
||||
c.WithoutFlags = append(c.WithoutFlags, SeenFlag)
|
||||
case "NOT":
|
||||
not := new(SearchCriteria)
|
||||
if fields, err = not.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Not = append(c.Not, not)
|
||||
case "OLD":
|
||||
c.WithoutFlags = append(c.WithoutFlags, RecentFlag)
|
||||
case "ON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Since = t
|
||||
c.Before = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "OR":
|
||||
c1, c2 := new(SearchCriteria), new(SearchCriteria)
|
||||
if fields, err = c1.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
} else if fields, err = c2.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Or = append(c.Or, [2]*SearchCriteria{c1, c2})
|
||||
case "SENTBEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentBefore.IsZero() || t.Before(c.SentBefore) {
|
||||
c.SentBefore = t
|
||||
}
|
||||
case "SENTON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.SentSince = t
|
||||
c.SentBefore = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "SENTSINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentSince.IsZero() || t.After(c.SentSince) {
|
||||
c.SentSince = t
|
||||
}
|
||||
case "SINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Since.IsZero() || t.After(c.Since) {
|
||||
c.Since = t
|
||||
}
|
||||
case "SMALLER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Smaller == 0 || n < c.Smaller {
|
||||
c.Smaller = n
|
||||
}
|
||||
case "TEXT":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Text = append(c.Text, convertField(f, charsetReader))
|
||||
}
|
||||
case "UID":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if c.Uid, err = ParseSeqSet(maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN":
|
||||
unflag := strings.TrimPrefix(key, "UN")
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag("\\"+unflag))
|
||||
case "UNKEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
default: // Try to parse a sequence set
|
||||
if c.SeqNum, err = ParseSeqSet(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// ParseWithCharset parses a search criteria from the provided fields.
|
||||
// charsetReader is an optional function that converts from the fields charset
|
||||
// to UTF-8.
|
||||
func (c *SearchCriteria) ParseWithCharset(fields []interface{}, charsetReader func(io.Reader) io.Reader) error {
|
||||
for len(fields) > 0 {
|
||||
var err error
|
||||
if fields, err = c.parseField(fields, charsetReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format formats search criteria to fields. UTF-8 is used.
|
||||
func (c *SearchCriteria) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
|
||||
if c.SeqNum != nil {
|
||||
fields = append(fields, c.SeqNum)
|
||||
}
|
||||
if c.Uid != nil {
|
||||
fields = append(fields, RawString("UID"), c.Uid)
|
||||
}
|
||||
|
||||
if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour {
|
||||
fields = append(fields, RawString("ON"), searchDate(c.Since))
|
||||
} else {
|
||||
if !c.Since.IsZero() {
|
||||
fields = append(fields, RawString("SINCE"), searchDate(c.Since))
|
||||
}
|
||||
if !c.Before.IsZero() {
|
||||
fields = append(fields, RawString("BEFORE"), searchDate(c.Before))
|
||||
}
|
||||
}
|
||||
if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour {
|
||||
fields = append(fields, RawString("SENTON"), searchDate(c.SentSince))
|
||||
} else {
|
||||
if !c.SentSince.IsZero() {
|
||||
fields = append(fields, RawString("SENTSINCE"), searchDate(c.SentSince))
|
||||
}
|
||||
if !c.SentBefore.IsZero() {
|
||||
fields = append(fields, RawString("SENTBEFORE"), searchDate(c.SentBefore))
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range c.Header {
|
||||
var prefields []interface{}
|
||||
switch key {
|
||||
case "Bcc", "Cc", "From", "Subject", "To":
|
||||
prefields = []interface{}{RawString(strings.ToUpper(key))}
|
||||
default:
|
||||
prefields = []interface{}{RawString("HEADER"), key}
|
||||
}
|
||||
for _, value := range values {
|
||||
fields = append(fields, prefields...)
|
||||
fields = append(fields, value)
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range c.Body {
|
||||
fields = append(fields, RawString("BODY"), value)
|
||||
}
|
||||
for _, value := range c.Text {
|
||||
fields = append(fields, RawString("TEXT"), value)
|
||||
}
|
||||
|
||||
for _, flag := range c.WithFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag:
|
||||
subfields = []interface{}{RawString(strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
||||
default:
|
||||
subfields = []interface{}{RawString("KEYWORD"), RawString(flag)}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
for _, flag := range c.WithoutFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag:
|
||||
subfields = []interface{}{RawString("UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
||||
case RecentFlag:
|
||||
subfields = []interface{}{RawString("OLD")}
|
||||
default:
|
||||
subfields = []interface{}{RawString("UNKEYWORD"), RawString(flag)}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
|
||||
if c.Larger > 0 {
|
||||
fields = append(fields, RawString("LARGER"), c.Larger)
|
||||
}
|
||||
if c.Smaller > 0 {
|
||||
fields = append(fields, RawString("SMALLER"), c.Smaller)
|
||||
}
|
||||
|
||||
for _, not := range c.Not {
|
||||
fields = append(fields, RawString("NOT"), not.Format())
|
||||
}
|
||||
|
||||
for _, or := range c.Or {
|
||||
fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format())
|
||||
}
|
||||
|
||||
// Not a single criteria given, add ALL criteria as fallback
|
||||
if len(fields) == 0 {
|
||||
fields = append(fields, RawString("ALL"))
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
@@ -1,289 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrBadSeqSet is used to report problems with the format of a sequence set
|
||||
// value.
|
||||
type ErrBadSeqSet string
|
||||
|
||||
func (err ErrBadSeqSet) Error() string {
|
||||
return fmt.Sprintf("imap: bad sequence set value %q", string(err))
|
||||
}
|
||||
|
||||
// Seq represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
|
||||
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
|
||||
// represented by setting Start = Stop. Zero is used to represent "*", which is
|
||||
// safe because seq-number uses nz-number rule. The order of values is always
|
||||
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
|
||||
type Seq struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// parseSeqNumber parses a single seq-number value (non-zero uint32 or "*").
|
||||
func parseSeqNumber(v string) (uint32, error) {
|
||||
if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
|
||||
return uint32(n), nil
|
||||
} else if v == "*" {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// parseSeq creates a new seq instance by parsing strings in the format "n" or
|
||||
// "n:m", where n and/or m may be "*". An error is returned for invalid values.
|
||||
func parseSeq(v string) (s Seq, err error) {
|
||||
if sep := strings.IndexRune(v, ':'); sep < 0 {
|
||||
s.Start, err = parseSeqNumber(v)
|
||||
s.Stop = s.Start
|
||||
return
|
||||
} else if s.Start, err = parseSeqNumber(v[:sep]); err == nil {
|
||||
if s.Stop, err = parseSeqNumber(v[sep+1:]); err == nil {
|
||||
if (s.Stop < s.Start && s.Stop != 0) || s.Start == 0 {
|
||||
s.Start, s.Stop = s.Stop, s.Start
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return s, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// Contains returns true if the seq-number q is contained in sequence value s.
|
||||
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
|
||||
// contains "*" and all numbers >= n.
|
||||
func (s Seq) Contains(q uint32) bool {
|
||||
if q == 0 {
|
||||
return s.Stop == 0 // "*" is contained only in "*" and "n:*"
|
||||
}
|
||||
return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
|
||||
}
|
||||
|
||||
// Less returns true if s precedes and does not contain seq-number q.
|
||||
func (s Seq) Less(q uint32) bool {
|
||||
return (s.Stop < q || q == 0) && s.Stop != 0
|
||||
}
|
||||
|
||||
// Merge combines sequence values s and t into a single union if the two
|
||||
// intersect or one is a superset of the other. The order of s and t does not
|
||||
// matter. If the values cannot be merged, s is returned unmodified and ok is
|
||||
// set to false.
|
||||
func (s Seq) Merge(t Seq) (union Seq, ok bool) {
|
||||
if union = s; s == t {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
if s.Start != 0 && t.Start != 0 {
|
||||
// s and t are any combination of "n", "n:m", or "n:*"
|
||||
if s.Start > t.Start {
|
||||
s, t = t, s
|
||||
}
|
||||
// s starts at or before t, check where it ends
|
||||
if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
|
||||
return s, true // s is a superset of t
|
||||
}
|
||||
// s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
|
||||
if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
|
||||
return Seq{s.Start, t.Stop}, true // s intersects or touches t
|
||||
}
|
||||
return
|
||||
}
|
||||
// exactly one of s and t is "*"
|
||||
if s.Start == 0 {
|
||||
if t.Stop == 0 {
|
||||
return t, true // s is "*", t is "n:*"
|
||||
}
|
||||
} else if s.Stop == 0 {
|
||||
return s, true // s is "n:*", t is "*"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns sequence value s as a seq-number or seq-range string.
|
||||
func (s Seq) String() string {
|
||||
if s.Start == s.Stop {
|
||||
if s.Start == 0 {
|
||||
return "*"
|
||||
}
|
||||
return strconv.FormatUint(uint64(s.Start), 10)
|
||||
}
|
||||
b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
|
||||
if s.Stop == 0 {
|
||||
return string(append(b, ':', '*'))
|
||||
}
|
||||
return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
|
||||
}
|
||||
|
||||
// SeqSet is used to represent a set of message sequence numbers or UIDs (see
|
||||
// sequence-set ABNF rule). The zero value is an empty set.
|
||||
type SeqSet struct {
|
||||
Set []Seq
|
||||
}
|
||||
|
||||
// ParseSeqSet returns a new SeqSet instance after parsing the set string.
|
||||
func ParseSeqSet(set string) (s *SeqSet, err error) {
|
||||
s = new(SeqSet)
|
||||
return s, s.Add(set)
|
||||
}
|
||||
|
||||
// Add inserts new sequence values into the set. The string format is described
|
||||
// by RFC 3501 sequence-set ABNF rule. If an error is encountered, all values
|
||||
// inserted successfully prior to the error remain in the set.
|
||||
func (s *SeqSet) Add(set string) error {
|
||||
for _, sv := range strings.Split(set, ",") {
|
||||
v, err := parseSeq(sv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.insert(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNum inserts new sequence numbers into the set. The value 0 represents "*".
|
||||
func (s *SeqSet) AddNum(q ...uint32) {
|
||||
for _, v := range q {
|
||||
s.insert(Seq{v, v})
|
||||
}
|
||||
}
|
||||
|
||||
// AddRange inserts a new sequence range into the set.
|
||||
func (s *SeqSet) AddRange(Start, Stop uint32) {
|
||||
if (Stop < Start && Stop != 0) || Start == 0 {
|
||||
s.insert(Seq{Stop, Start})
|
||||
} else {
|
||||
s.insert(Seq{Start, Stop})
|
||||
}
|
||||
}
|
||||
|
||||
// AddSet inserts all values from t into s.
|
||||
func (s *SeqSet) AddSet(t *SeqSet) {
|
||||
for _, v := range t.Set {
|
||||
s.insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all values from the set.
|
||||
func (s *SeqSet) Clear() {
|
||||
s.Set = s.Set[:0]
|
||||
}
|
||||
|
||||
// Empty returns true if the sequence set does not contain any values.
|
||||
func (s SeqSet) Empty() bool {
|
||||
return len(s.Set) == 0
|
||||
}
|
||||
|
||||
// Dynamic returns true if the set contains "*" or "n:*" values.
|
||||
func (s SeqSet) Dynamic() bool {
|
||||
return len(s.Set) > 0 && s.Set[len(s.Set)-1].Stop == 0
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero sequence number or UID q is contained
|
||||
// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
|
||||
// responsibility to handle the special case where q is the maximum UID in the
|
||||
// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
|
||||
// it doesn't know what the maximum value is).
|
||||
func (s SeqSet) Contains(q uint32) bool {
|
||||
if _, ok := s.search(q); ok {
|
||||
return q != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns a sorted representation of all contained sequence values.
|
||||
func (s SeqSet) String() string {
|
||||
if len(s.Set) == 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, 0, 64)
|
||||
for _, v := range s.Set {
|
||||
b = append(b, ',')
|
||||
if v.Start == 0 {
|
||||
b = append(b, '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(b, uint64(v.Start), 10)
|
||||
if v.Start != v.Stop {
|
||||
if v.Stop == 0 {
|
||||
b = append(b, ':', '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
|
||||
}
|
||||
}
|
||||
return string(b[1:])
|
||||
}
|
||||
|
||||
// insert adds sequence value v to the set.
|
||||
func (s *SeqSet) insert(v Seq) {
|
||||
i, _ := s.search(v.Start)
|
||||
merged := false
|
||||
if i > 0 {
|
||||
// try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
|
||||
s.Set[i-1], merged = s.Set[i-1].Merge(v)
|
||||
}
|
||||
if i == len(s.Set) {
|
||||
// v was either merged with the last entry or needs to be appended
|
||||
if !merged {
|
||||
s.insertAt(i, v)
|
||||
}
|
||||
return
|
||||
} else if merged {
|
||||
i--
|
||||
} else if s.Set[i], merged = s.Set[i].Merge(v); !merged {
|
||||
s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
|
||||
return
|
||||
}
|
||||
// v was merged with s.Set[i], continue trying to merge until the end
|
||||
for j := i + 1; j < len(s.Set); j++ {
|
||||
if s.Set[i], merged = s.Set[i].Merge(s.Set[j]); !merged {
|
||||
if j > i+1 {
|
||||
// cut out all entries between i and j that were merged
|
||||
s.Set = append(s.Set[:i+1], s.Set[j:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything after s.Set[i] was merged
|
||||
s.Set = s.Set[:i+1]
|
||||
}
|
||||
|
||||
// insertAt inserts a new sequence value v at index i, resizing s.Set as needed.
|
||||
func (s *SeqSet) insertAt(i int, v Seq) {
|
||||
if n := len(s.Set); i == n {
|
||||
// insert at the end
|
||||
s.Set = append(s.Set, v)
|
||||
return
|
||||
} else if n < cap(s.Set) {
|
||||
// enough space, shift everything at and after i to the right
|
||||
s.Set = s.Set[:n+1]
|
||||
copy(s.Set[i+1:], s.Set[i:])
|
||||
} else {
|
||||
// allocate new slice and copy everything, n is at least 1
|
||||
set := make([]Seq, n+1, n*2)
|
||||
copy(set, s.Set[:i])
|
||||
copy(set[i+1:], s.Set[i:])
|
||||
s.Set = set
|
||||
}
|
||||
s.Set[i] = v
|
||||
}
|
||||
|
||||
// search attempts to find the index of the sequence set value that contains q.
|
||||
// If no values contain q, the returned index is the position where q should be
|
||||
// inserted and ok is set to false.
|
||||
func (s SeqSet) search(q uint32) (i int, ok bool) {
|
||||
min, max := 0, len(s.Set)-1
|
||||
for min < max {
|
||||
if mid := (min + max) >> 1; s.Set[mid].Less(q) {
|
||||
min = mid + 1
|
||||
} else {
|
||||
max = mid
|
||||
}
|
||||
}
|
||||
if max < 0 || s.Set[min].Less(q) {
|
||||
return len(s.Set), false // q is the new largest value
|
||||
}
|
||||
return min, s.Set[min].Contains(q)
|
||||
}
|
||||
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
@@ -1,136 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A status response type.
|
||||
type StatusRespType string
|
||||
|
||||
// Status response types defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
// The OK response indicates an information message from the server. When
|
||||
// tagged, it indicates successful completion of the associated command.
|
||||
// The untagged form indicates an information-only message.
|
||||
StatusRespOk StatusRespType = "OK"
|
||||
|
||||
// The NO response indicates an operational error message from the
|
||||
// server. When tagged, it indicates unsuccessful completion of the
|
||||
// associated command. The untagged form indicates a warning; the
|
||||
// command can still complete successfully.
|
||||
StatusRespNo StatusRespType = "NO"
|
||||
|
||||
// The BAD response indicates an error message from the server. When
|
||||
// tagged, it reports a protocol-level error in the client's command;
|
||||
// the tag indicates the command that caused the error. The untagged
|
||||
// form indicates a protocol-level error for which the associated
|
||||
// command can not be determined; it can also indicate an internal
|
||||
// server failure.
|
||||
StatusRespBad StatusRespType = "BAD"
|
||||
|
||||
// The PREAUTH response is always untagged, and is one of three
|
||||
// possible greetings at connection startup. It indicates that the
|
||||
// connection has already been authenticated by external means; thus
|
||||
// no LOGIN command is needed.
|
||||
StatusRespPreauth StatusRespType = "PREAUTH"
|
||||
|
||||
// The BYE response is always untagged, and indicates that the server
|
||||
// is about to close the connection.
|
||||
StatusRespBye StatusRespType = "BYE"
|
||||
)
|
||||
|
||||
type StatusRespCode string
|
||||
|
||||
// Status response codes defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
CodeAlert StatusRespCode = "ALERT"
|
||||
CodeBadCharset StatusRespCode = "BADCHARSET"
|
||||
CodeCapability StatusRespCode = "CAPABILITY"
|
||||
CodeParse StatusRespCode = "PARSE"
|
||||
CodePermanentFlags StatusRespCode = "PERMANENTFLAGS"
|
||||
CodeReadOnly StatusRespCode = "READ-ONLY"
|
||||
CodeReadWrite StatusRespCode = "READ-WRITE"
|
||||
CodeTryCreate StatusRespCode = "TRYCREATE"
|
||||
CodeUidNext StatusRespCode = "UIDNEXT"
|
||||
CodeUidValidity StatusRespCode = "UIDVALIDITY"
|
||||
CodeUnseen StatusRespCode = "UNSEEN"
|
||||
)
|
||||
|
||||
// A status response.
|
||||
// See RFC 3501 section 7.1
|
||||
type StatusResp struct {
|
||||
// The response tag. If empty, it defaults to *.
|
||||
Tag string
|
||||
// The status type.
|
||||
Type StatusRespType
|
||||
// The status code.
|
||||
// See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml
|
||||
Code StatusRespCode
|
||||
// Arguments provided with the status code.
|
||||
Arguments []interface{}
|
||||
// The status info.
|
||||
Info string
|
||||
}
|
||||
|
||||
func (r *StatusResp) resp() {}
|
||||
|
||||
// If this status is NO or BAD, returns an error with the status info.
|
||||
// Otherwise, returns nil.
|
||||
func (r *StatusResp) Err() error {
|
||||
if r == nil {
|
||||
// No status response, connection closed before we get one
|
||||
return errors.New("imap: connection closed during command execution")
|
||||
}
|
||||
|
||||
if r.Type == StatusRespNo || r.Type == StatusRespBad {
|
||||
return errors.New(r.Info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StatusResp) WriteTo(w *Writer) error {
|
||||
tag := RawString(r.Tag)
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
if err := w.writeFields([]interface{}{RawString(tag), RawString(r.Type)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Code != "" {
|
||||
if err := w.writeRespCode(r.Code, r.Arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeString(r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
// ErrStatusResp can be returned by a server.Handler to replace the default status
|
||||
// response. The response tag must be empty.
|
||||
//
|
||||
// To suppress default response, Resp should be set to nil.
|
||||
type ErrStatusResp struct {
|
||||
// Response to send instead of default.
|
||||
Resp *StatusResp
|
||||
}
|
||||
|
||||
func (err *ErrStatusResp) Error() string {
|
||||
if err.Resp == nil {
|
||||
return "imap: suppressed response"
|
||||
}
|
||||
return err.Resp.Info
|
||||
}
|
||||
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
@@ -1,149 +0,0 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7.
|
||||
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
|
||||
|
||||
type decoder struct {
|
||||
ascii bool
|
||||
}
|
||||
|
||||
func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
|
||||
if ch < min || ch > max { // Illegal code point in ASCII mode
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
if ch != '&' {
|
||||
if nDst+1 > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc++
|
||||
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
|
||||
d.ascii = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the end of the Base64 or "&-" segment
|
||||
start := i + 1
|
||||
for i++; i < len(src) && src[i] != '-'; i++ {
|
||||
if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(src) { // Implicit shift ("&...")
|
||||
if atEOF {
|
||||
err = ErrInvalidUTF7
|
||||
} else {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if i == start { // Escape sequence "&-"
|
||||
b = []byte{'&'}
|
||||
d.ascii = true
|
||||
} else { // Control or non-ASCII code points in base64
|
||||
if !d.ascii { // Null shift ("&...-&...-")
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
b = decode(src[start:i])
|
||||
d.ascii = false
|
||||
}
|
||||
|
||||
if len(b) == 0 { // Bad encoding
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
d.ascii = true
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i + 1
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
if atEOF {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) Reset() {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8.
|
||||
// A nil slice is returned if the encoding is invalid.
|
||||
func decode(b64 []byte) []byte {
|
||||
var b []byte
|
||||
|
||||
// Allocate a single block of memory large enough to store the Base64 data
|
||||
// (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
|
||||
// Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
|
||||
// double the space allocation for UTF-8.
|
||||
if n := len(b64); b64[n-1] == '=' {
|
||||
return nil
|
||||
} else if n&3 == 0 {
|
||||
b = make([]byte, b64Enc.DecodedLen(n)*3)
|
||||
} else {
|
||||
n += 4 - n&3
|
||||
b = make([]byte, n+b64Enc.DecodedLen(n)*3)
|
||||
copy(b[copy(b, b64):n], []byte("=="))
|
||||
b64, b = b[:n], b[n:]
|
||||
}
|
||||
|
||||
// Decode Base64 into the first 1/3rd of b
|
||||
n, err := b64Enc.Decode(b, b64)
|
||||
if err != nil || n&1 == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
||||
b, s := b[:n], b[n:]
|
||||
j := 0
|
||||
for i := 0; i < n; i += 2 {
|
||||
r := rune(b[i])<<8 | rune(b[i+1])
|
||||
if utf16.IsSurrogate(r) {
|
||||
if i += 2; i == n {
|
||||
return nil
|
||||
}
|
||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
||||
if r = utf16.DecodeRune(r, r2); r == repl {
|
||||
return nil
|
||||
}
|
||||
} else if min <= r && r <= max {
|
||||
return nil
|
||||
}
|
||||
j += utf8.EncodeRune(s[j:], r)
|
||||
}
|
||||
return s[:j]
|
||||
}
|
||||
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
@@ -1,91 +0,0 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type encoder struct{}
|
||||
|
||||
func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); {
|
||||
ch := src[i]
|
||||
|
||||
var b []byte
|
||||
if min <= ch && ch <= max {
|
||||
b = []byte{ch}
|
||||
if ch == '&' {
|
||||
b = append(b, '-')
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
start := i
|
||||
|
||||
// Find the next printable ASCII code point
|
||||
i++
|
||||
for i < len(src) && (src[i] < min || src[i] > max) {
|
||||
i++
|
||||
}
|
||||
|
||||
if !atEOF && i == len(src) {
|
||||
err = transform.ErrShortSrc
|
||||
return
|
||||
}
|
||||
|
||||
b = encode(src[start:i])
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *encoder) Reset() {}
|
||||
|
||||
// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
|
||||
// removes the padding, and adds UTF-7 shifts.
|
||||
func encode(s []byte) []byte {
|
||||
// len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
|
||||
// control code points (see table below).
|
||||
b := make([]byte, 0, len(s)+4)
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRune(s)
|
||||
if r > utf8.MaxRune {
|
||||
r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
|
||||
}
|
||||
s = s[size:]
|
||||
if r1, r2 := utf16.EncodeRune(r); r1 != repl {
|
||||
b = append(b, byte(r1>>8), byte(r1))
|
||||
r = r2
|
||||
}
|
||||
b = append(b, byte(r>>8), byte(r))
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
n := b64Enc.EncodedLen(len(b)) + 2
|
||||
b64 := make([]byte, n)
|
||||
b64Enc.Encode(b64[1:], b)
|
||||
|
||||
// Strip padding
|
||||
n -= 2 - (len(b)+2)%3
|
||||
b64 = b64[:n]
|
||||
|
||||
// Add UTF-7 shifts
|
||||
b64[0] = '&'
|
||||
b64[n-1] = '-'
|
||||
return b64
|
||||
}
|
||||
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
@@ -1,34 +0,0 @@
|
||||
// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
min = 0x20 // Minimum self-representing UTF-7 value
|
||||
max = 0x7E // Maximum self-representing UTF-7 value
|
||||
|
||||
repl = '\uFFFD' // Unicode replacement code point
|
||||
)
|
||||
|
||||
var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
||||
|
||||
type enc struct{}
|
||||
|
||||
func (e enc) NewDecoder() *encoding.Decoder {
|
||||
return &encoding.Decoder{
|
||||
Transformer: &decoder{true},
|
||||
}
|
||||
}
|
||||
|
||||
func (e enc) NewEncoder() *encoding.Encoder {
|
||||
return &encoding.Encoder{
|
||||
Transformer: &encoder{},
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding is the modified UTF-7 encoding.
|
||||
var Encoding encoding.Encoding = enc{}
|
||||
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type (
|
||||
// A raw string.
|
||||
RawString string
|
||||
)
|
||||
|
||||
type WriterTo interface {
|
||||
WriteTo(w *Writer) error
|
||||
}
|
||||
|
||||
func formatNumber(num uint32) string {
|
||||
return strconv.FormatUint(uint64(num), 10)
|
||||
}
|
||||
|
||||
// Convert a string list to a field list.
|
||||
func FormatStringList(list []string) (fields []interface{}) {
|
||||
fields = make([]interface{}, len(list))
|
||||
for i, v := range list {
|
||||
fields[i] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a string is 8-bit clean.
|
||||
func isAscii(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > unicode.MaxASCII || unicode.IsControl(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// An IMAP writer.
|
||||
type Writer struct {
|
||||
io.Writer
|
||||
|
||||
AllowAsyncLiterals bool
|
||||
|
||||
continues <-chan bool
|
||||
}
|
||||
|
||||
// Helper function to write a string to w.
|
||||
func (w *Writer) writeString(s string) error {
|
||||
_, err := io.WriteString(w.Writer, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Writer) writeCrlf() error {
|
||||
if err := w.writeString(crlf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (w *Writer) writeNumber(num uint32) error {
|
||||
return w.writeString(formatNumber(num))
|
||||
}
|
||||
|
||||
func (w *Writer) writeQuoted(s string) error {
|
||||
return w.writeString(strconv.Quote(s))
|
||||
}
|
||||
|
||||
func (w *Writer) writeQuotedOrLiteral(s string) error {
|
||||
if !isAscii(s) {
|
||||
// IMAP doesn't allow 8-bit data outside literals
|
||||
return w.writeLiteral(bytes.NewBufferString(s))
|
||||
}
|
||||
|
||||
return w.writeQuoted(s)
|
||||
}
|
||||
|
||||
func (w *Writer) writeDateTime(t time.Time, layout string) error {
|
||||
if t.IsZero() {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
return w.writeQuoted(t.Format(layout))
|
||||
}
|
||||
|
||||
func (w *Writer) writeFields(fields []interface{}) error {
|
||||
for i, field := range fields {
|
||||
if i > 0 { // Write separator
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeField(field); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeList(fields []interface{}) error {
|
||||
if err := w.writeString(string(listStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(listEnd))
|
||||
}
|
||||
|
||||
// LiteralLengthErr is returned when the Len() of the Literal object does not
|
||||
// match the actual length of the byte stream.
|
||||
type LiteralLengthErr struct {
|
||||
Actual int
|
||||
Expected int
|
||||
}
|
||||
|
||||
func (e LiteralLengthErr) Error() string {
|
||||
return fmt.Sprintf("imap: size of Literal is not equal to Len() (%d != %d)", e.Expected, e.Actual)
|
||||
}
|
||||
|
||||
func (w *Writer) writeLiteral(l Literal) error {
|
||||
if l == nil {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
|
||||
unsyncLiteral := w.AllowAsyncLiterals && l.Len() <= 4096
|
||||
|
||||
header := string(literalStart) + strconv.Itoa(l.Len())
|
||||
if unsyncLiteral {
|
||||
header += string('+')
|
||||
}
|
||||
header += string(literalEnd) + crlf
|
||||
if err := w.writeString(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If a channel is available, wait for a continuation request before sending data
|
||||
if !unsyncLiteral && w.continues != nil {
|
||||
// Make sure to flush the writer, otherwise we may never receive a continuation request
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !<-w.continues {
|
||||
return fmt.Errorf("imap: cannot send literal: no continuation request received")
|
||||
}
|
||||
}
|
||||
|
||||
// In case of bufio.Buffer, it will be 0 after io.Copy.
|
||||
literalLen := int64(l.Len())
|
||||
|
||||
n, err := io.CopyN(w, l, literalLen)
|
||||
if err != nil {
|
||||
if err == io.EOF && n != literalLen {
|
||||
return LiteralLengthErr{int(n), l.Len()}
|
||||
}
|
||||
return err
|
||||
}
|
||||
extra, _ := io.Copy(ioutil.Discard, l)
|
||||
if extra != 0 {
|
||||
return LiteralLengthErr{int(n + extra), l.Len()}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeField(field interface{}) error {
|
||||
if field == nil {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
|
||||
switch field := field.(type) {
|
||||
case RawString:
|
||||
return w.writeString(string(field))
|
||||
case string:
|
||||
return w.writeQuotedOrLiteral(field)
|
||||
case int:
|
||||
return w.writeNumber(uint32(field))
|
||||
case uint32:
|
||||
return w.writeNumber(field)
|
||||
case Literal:
|
||||
return w.writeLiteral(field)
|
||||
case []interface{}:
|
||||
return w.writeList(field)
|
||||
case envelopeDateTime:
|
||||
return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
|
||||
case searchDate:
|
||||
return w.writeDateTime(time.Time(field), searchDateLayout)
|
||||
case Date:
|
||||
return w.writeDateTime(time.Time(field), DateLayout)
|
||||
case DateTime:
|
||||
return w.writeDateTime(time.Time(field), DateTimeLayout)
|
||||
case time.Time:
|
||||
return w.writeDateTime(field, DateTimeLayout)
|
||||
case *SeqSet:
|
||||
return w.writeString(field.String())
|
||||
case *BodySectionName:
|
||||
// Can contain spaces - that's why we don't just pass it as a string
|
||||
return w.writeString(string(field.FetchItem()))
|
||||
}
|
||||
|
||||
return fmt.Errorf("imap: cannot format field: %v", field)
|
||||
}
|
||||
|
||||
func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
|
||||
if err := w.writeString(string(respCodeStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(code)}
|
||||
fields = append(fields, args...)
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(respCodeEnd))
|
||||
}
|
||||
|
||||
func (w *Writer) writeLine(fields ...interface{}) error {
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
func (w *Writer) Flush() error {
|
||||
if f, ok := w.Writer.(flusher); ok {
|
||||
return f.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{Writer: w}
|
||||
}
|
||||
|
||||
func NewClientWriter(w io.Writer, continues <-chan bool) *Writer {
|
||||
return &Writer{Writer: w, continues: continues}
|
||||
}
|
||||
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
@@ -1,19 +0,0 @@
|
||||
image: alpine/latest
|
||||
packages:
|
||||
- go
|
||||
# Required by codecov
|
||||
- bash
|
||||
- findutils
|
||||
sources:
|
||||
- https://github.com/emersion/go-sasl
|
||||
tasks:
|
||||
- build: |
|
||||
cd go-sasl
|
||||
go build -v ./...
|
||||
- test: |
|
||||
cd go-sasl
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- upload-coverage: |
|
||||
cd go-sasl
|
||||
export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
|
||||
curl -s https://codecov.io/bash | bash
|
||||
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
@@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 emersion
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
@@ -1,17 +0,0 @@
|
||||
# go-sasl
|
||||
|
||||
[](https://godoc.org/github.com/emersion/go-sasl)
|
||||
[](https://travis-ci.org/emersion/go-sasl)
|
||||
|
||||
A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go.
|
||||
|
||||
Implemented mechanisms:
|
||||
* [ANONYMOUS](https://tools.ietf.org/html/rfc4505)
|
||||
* [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A)
|
||||
* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead)
|
||||
* [PLAIN](https://tools.ietf.org/html/rfc4616)
|
||||
* [OAUTHBEARER](https://tools.ietf.org/html/rfc7628)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
@@ -1,56 +0,0 @@
|
||||
package sasl
|
||||
|
||||
// The ANONYMOUS mechanism name.
|
||||
const Anonymous = "ANONYMOUS"
|
||||
|
||||
type anonymousClient struct {
|
||||
Trace string
|
||||
}
|
||||
|
||||
func (c *anonymousClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = Anonymous
|
||||
ir = []byte(c.Trace)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// A client implementation of the ANONYMOUS authentication mechanism, as
|
||||
// described in RFC 4505.
|
||||
func NewAnonymousClient(trace string) Client {
|
||||
return &anonymousClient{trace}
|
||||
}
|
||||
|
||||
// Get trace information from clients logging in anonymously.
|
||||
type AnonymousAuthenticator func(trace string) error
|
||||
|
||||
type anonymousServer struct {
|
||||
done bool
|
||||
authenticate AnonymousAuthenticator
|
||||
}
|
||||
|
||||
func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
if s.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// No initial response, send an empty challenge
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
s.done = true
|
||||
|
||||
err = s.authenticate(string(response))
|
||||
done = true
|
||||
return
|
||||
}
|
||||
|
||||
// A server implementation of the ANONYMOUS authentication mechanism, as
|
||||
// described in RFC 4505.
|
||||
func NewAnonymousServer(authenticator AnonymousAuthenticator) Server {
|
||||
return &anonymousServer{authenticate: authenticator}
|
||||
}
|
||||
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
@@ -1,26 +0,0 @@
|
||||
package sasl
|
||||
|
||||
// The EXTERNAL mechanism name.
|
||||
const External = "EXTERNAL"
|
||||
|
||||
type externalClient struct {
|
||||
Identity string
|
||||
}
|
||||
|
||||
func (a *externalClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = External
|
||||
ir = []byte(a.Identity)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *externalClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// An implementation of the EXTERNAL authentication mechanism, as described in
|
||||
// RFC 4422. Authorization identity may be left blank to indicate that the
|
||||
// client is requesting to act as the identity associated with the
|
||||
// authentication credentials.
|
||||
func NewExternalClient(identity string) Client {
|
||||
return &externalClient{identity}
|
||||
}
|
||||
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
@@ -1,89 +0,0 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// The LOGIN mechanism name.
|
||||
const Login = "LOGIN"
|
||||
|
||||
var expectedChallenge = []byte("Password:")
|
||||
|
||||
type loginClient struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (a *loginClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = "LOGIN"
|
||||
ir = []byte(a.Username)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
|
||||
if bytes.Compare(challenge, expectedChallenge) != 0 {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
} else {
|
||||
return []byte(a.Password), nil
|
||||
}
|
||||
}
|
||||
|
||||
// A client implementation of the LOGIN authentication mechanism for SMTP,
|
||||
// as described in http://www.iana.org/go/draft-murchison-sasl-login
|
||||
//
|
||||
// It is considered obsolete, and should not be used when other mechanisms are
|
||||
// available. For plaintext password authentication use PLAIN mechanism.
|
||||
func NewLoginClient(username, password string) Client {
|
||||
return &loginClient{username, password}
|
||||
}
|
||||
|
||||
// Authenticates users with an username and a password.
|
||||
type LoginAuthenticator func(username, password string) error
|
||||
|
||||
type loginState int
|
||||
|
||||
const (
|
||||
loginNotStarted loginState = iota
|
||||
loginWaitingUsername
|
||||
loginWaitingPassword
|
||||
)
|
||||
|
||||
type loginServer struct {
|
||||
state loginState
|
||||
username, password string
|
||||
authenticate LoginAuthenticator
|
||||
}
|
||||
|
||||
// A server implementation of the LOGIN authentication mechanism, as described
|
||||
// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
|
||||
//
|
||||
// LOGIN is obsolete and should only be enabled for legacy clients that cannot
|
||||
// be updated to use PLAIN.
|
||||
func NewLoginServer(authenticator LoginAuthenticator) Server {
|
||||
return &loginServer{authenticate: authenticator}
|
||||
}
|
||||
|
||||
func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
switch a.state {
|
||||
case loginNotStarted:
|
||||
// Check for initial response field, as per RFC4422 section 3
|
||||
if response == nil {
|
||||
challenge = []byte("Username:")
|
||||
break
|
||||
}
|
||||
a.state++
|
||||
fallthrough
|
||||
case loginWaitingUsername:
|
||||
a.username = string(response)
|
||||
challenge = []byte("Password:")
|
||||
case loginWaitingPassword:
|
||||
a.password = string(response)
|
||||
err = a.authenticate(a.username, a.password)
|
||||
done = true
|
||||
default:
|
||||
err = ErrUnexpectedClientResponse
|
||||
}
|
||||
|
||||
a.state++
|
||||
return
|
||||
}
|
||||
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
@@ -1,191 +0,0 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The OAUTHBEARER mechanism name.
|
||||
const OAuthBearer = "OAUTHBEARER"
|
||||
|
||||
type OAuthBearerError struct {
|
||||
Status string `json:"status"`
|
||||
Schemes string `json:"schemes"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type OAuthBearerOptions struct {
|
||||
Username string
|
||||
Token string
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
// Implements error
|
||||
func (err *OAuthBearerError) Error() string {
|
||||
return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
|
||||
}
|
||||
|
||||
type oauthBearerClient struct {
|
||||
OAuthBearerOptions
|
||||
}
|
||||
|
||||
func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = OAuthBearer
|
||||
var str = "n,a=" + a.Username + ","
|
||||
|
||||
if a.Host != "" {
|
||||
str += "\x01host=" + a.Host
|
||||
}
|
||||
|
||||
if a.Port != 0 {
|
||||
str += "\x01port=" + strconv.Itoa(a.Port)
|
||||
}
|
||||
str += "\x01auth=Bearer " + a.Token + "\x01\x01"
|
||||
ir = []byte(str)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) {
|
||||
authBearerErr := &OAuthBearerError{}
|
||||
if err := json.Unmarshal(challenge, authBearerErr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return nil, authBearerErr
|
||||
}
|
||||
}
|
||||
|
||||
// An implementation of the OAUTHBEARER authentication mechanism, as
|
||||
// described in RFC 7628.
|
||||
func NewOAuthBearerClient(opt *OAuthBearerOptions) Client {
|
||||
return &oauthBearerClient{*opt}
|
||||
}
|
||||
|
||||
type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
|
||||
|
||||
type oauthBearerServer struct {
|
||||
done bool
|
||||
failErr error
|
||||
authenticate OAuthBearerAuthenticator
|
||||
}
|
||||
|
||||
func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) {
|
||||
blob, err := json.Marshal(OAuthBearerError{
|
||||
Status: "invalid_request",
|
||||
Schemes: "bearer",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // wtf
|
||||
}
|
||||
a.failErr = errors.New(descr)
|
||||
return blob, false, nil
|
||||
}
|
||||
|
||||
func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
// Per RFC, we cannot just send an error, we need to return JSON-structured
|
||||
// value as a challenge and then after getting dummy response from the
|
||||
// client stop the exchange.
|
||||
if a.failErr != nil {
|
||||
// Server libraries (go-smtp, go-imap) will not call Next on
|
||||
// protocol-specific SASL cancel response ('*'). However, GS2 (and
|
||||
// indirectly OAUTHBEARER) defines a protocol-independent way to do so
|
||||
// using 0x01.
|
||||
if len(response) != 1 && response[0] != 0x01 {
|
||||
return nil, true, errors.New("unexpected response")
|
||||
}
|
||||
return nil, true, a.failErr
|
||||
}
|
||||
|
||||
if a.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// Generate empty challenge.
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
a.done = true
|
||||
|
||||
// Cut n,a=username,\x01host=...\x01auth=...
|
||||
// into
|
||||
// n
|
||||
// a=username
|
||||
// \x01host=...\x01auth=...\x01\x01
|
||||
parts := bytes.SplitN(response, []byte{','}, 3)
|
||||
if len(parts) != 3 {
|
||||
return a.fail("Invalid response")
|
||||
}
|
||||
if !bytes.Equal(parts[0], []byte{'n'}) {
|
||||
return a.fail("Invalid response, missing 'n'")
|
||||
}
|
||||
opts := OAuthBearerOptions{}
|
||||
if !bytes.HasPrefix(parts[1], []byte("a=")) {
|
||||
return a.fail("Invalid response, missing 'a'")
|
||||
}
|
||||
opts.Username = string(bytes.TrimPrefix(parts[1], []byte("a=")))
|
||||
|
||||
// Cut \x01host=...\x01auth=...\x01\x01
|
||||
// into
|
||||
// *empty*
|
||||
// host=...
|
||||
// auth=...
|
||||
// *empty*
|
||||
//
|
||||
// Note that this code does not do a lot of checks to make sure the input
|
||||
// follows the exact format specified by RFC.
|
||||
params := bytes.Split(parts[2], []byte{0x01})
|
||||
for _, p := range params {
|
||||
// Skip empty fields (one at start and end).
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pParts := bytes.SplitN(p, []byte{'='}, 2)
|
||||
if len(pParts) != 2 {
|
||||
return a.fail("Invalid response, missing '='")
|
||||
}
|
||||
|
||||
switch string(pParts[0]) {
|
||||
case "host":
|
||||
opts.Host = string(pParts[1])
|
||||
case "port":
|
||||
port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
|
||||
if err != nil {
|
||||
return a.fail("Invalid response, malformed 'port' value")
|
||||
}
|
||||
opts.Port = int(port)
|
||||
case "auth":
|
||||
const prefix = "bearer "
|
||||
strValue := string(pParts[1])
|
||||
// Token type is case-insensitive.
|
||||
if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
|
||||
return a.fail("Unsupported token type")
|
||||
}
|
||||
opts.Token = strValue[len(prefix):]
|
||||
default:
|
||||
return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
|
||||
}
|
||||
}
|
||||
|
||||
authzErr := a.authenticate(opts)
|
||||
if authzErr != nil {
|
||||
blob, err := json.Marshal(authzErr)
|
||||
if err != nil {
|
||||
panic(err) // wtf
|
||||
}
|
||||
a.failErr = authzErr
|
||||
return blob, false, nil
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server {
|
||||
return &oauthBearerServer{authenticate: auth}
|
||||
}
|
||||
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
@@ -1,77 +0,0 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// The PLAIN mechanism name.
|
||||
const Plain = "PLAIN"
|
||||
|
||||
type plainClient struct {
|
||||
Identity string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (a *plainClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = "PLAIN"
|
||||
ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *plainClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// A client implementation of the PLAIN authentication mechanism, as described
|
||||
// in RFC 4616. Authorization identity may be left blank to indicate that it is
|
||||
// the same as the username.
|
||||
func NewPlainClient(identity, username, password string) Client {
|
||||
return &plainClient{identity, username, password}
|
||||
}
|
||||
|
||||
// Authenticates users with an identity, a username and a password. If the
|
||||
// identity is left blank, it indicates that it is the same as the username.
|
||||
// If identity is not empty and the server doesn't support it, an error must be
|
||||
// returned.
|
||||
type PlainAuthenticator func(identity, username, password string) error
|
||||
|
||||
type plainServer struct {
|
||||
done bool
|
||||
authenticate PlainAuthenticator
|
||||
}
|
||||
|
||||
func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
if a.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// No initial response, send an empty challenge
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
a.done = true
|
||||
|
||||
parts := bytes.Split(response, []byte("\x00"))
|
||||
if len(parts) != 3 {
|
||||
err = errors.New("Invalid response")
|
||||
return
|
||||
}
|
||||
|
||||
identity := string(parts[0])
|
||||
username := string(parts[1])
|
||||
password := string(parts[2])
|
||||
|
||||
err = a.authenticate(identity, username, password)
|
||||
done = true
|
||||
return
|
||||
}
|
||||
|
||||
// A server implementation of the PLAIN authentication mechanism, as described
|
||||
// in RFC 4616.
|
||||
func NewPlainServer(authenticator PlainAuthenticator) Server {
|
||||
return &plainServer{authenticate: authenticator}
|
||||
}
|
||||
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
@@ -1,45 +0,0 @@
|
||||
// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422.
|
||||
package sasl
|
||||
|
||||
// Note:
|
||||
// Most of this code was copied, with some modifications, from net/smtp. It
|
||||
// would be better if Go provided a standard package (e.g. crypto/sasl) that
|
||||
// could be shared by SMTP, IMAP, and other packages.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Common SASL errors.
|
||||
var (
|
||||
ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response")
|
||||
ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge")
|
||||
)
|
||||
|
||||
// Client interface to perform challenge-response authentication.
|
||||
type Client interface {
|
||||
// Begins SASL authentication with the server. It returns the
|
||||
// authentication mechanism name and "initial response" data (if required by
|
||||
// the selected mechanism). A non-nil error causes the client to abort the
|
||||
// authentication attempt.
|
||||
//
|
||||
// A nil ir value is different from a zero-length value. The nil value
|
||||
// indicates that the selected mechanism does not use an initial response,
|
||||
// while a zero-length value indicates an empty initial response, which must
|
||||
// be sent to the server.
|
||||
Start() (mech string, ir []byte, err error)
|
||||
|
||||
// Continues challenge-response authentication. A non-nil error causes
|
||||
// the client to abort the authentication attempt.
|
||||
Next(challenge []byte) (response []byte, err error)
|
||||
}
|
||||
|
||||
// Server interface to perform challenge-response authentication.
|
||||
type Server interface {
|
||||
// Begins or continues challenge-response authentication. If the client
|
||||
// supplies an initial response, response is non-nil.
|
||||
//
|
||||
// If the authentication is finished, done is set to true. If the
|
||||
// authentication has failed, an error is returned.
|
||||
Next(response []byte) (challenge []byte, done bool, err error)
|
||||
}
|
||||
3
vendor/golang.org/x/text/AUTHORS
generated
vendored
3
vendor/golang.org/x/text/AUTHORS
generated
vendored
@@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
3
vendor/golang.org/x/text/CONTRIBUTORS
generated
vendored
3
vendor/golang.org/x/text/CONTRIBUTORS
generated
vendored
@@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
27
vendor/golang.org/x/text/LICENSE
generated
vendored
27
vendor/golang.org/x/text/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
vendor/golang.org/x/text/PATENTS
generated
vendored
22
vendor/golang.org/x/text/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
335
vendor/golang.org/x/text/encoding/encoding.go
generated
vendored
@@ -1,335 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package encoding defines an interface for character encodings, such as Shift
|
||||
// JIS and Windows 1252, that can convert to and from UTF-8.
|
||||
//
|
||||
// Encoding implementations are provided in other packages, such as
|
||||
// golang.org/x/text/encoding/charmap and
|
||||
// golang.org/x/text/encoding/japanese.
|
||||
package encoding // import "golang.org/x/text/encoding"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/encoding/internal/identifier"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// TODO:
|
||||
// - There seems to be some inconsistency in when decoders return errors
|
||||
// and when not. Also documentation seems to suggest they shouldn't return
|
||||
// errors at all (except for UTF-16).
|
||||
// - Encoders seem to rely on or at least benefit from the input being in NFC
|
||||
// normal form. Perhaps add an example how users could prepare their output.
|
||||
|
||||
// Encoding is a character set encoding that can be transformed to and from
|
||||
// UTF-8.
|
||||
type Encoding interface {
|
||||
// NewDecoder returns a Decoder.
|
||||
NewDecoder() *Decoder
|
||||
|
||||
// NewEncoder returns an Encoder.
|
||||
NewEncoder() *Encoder
|
||||
}
|
||||
|
||||
// A Decoder converts bytes to UTF-8. It implements transform.Transformer.
|
||||
//
|
||||
// Transforming source bytes that are not of that encoding will not result in an
|
||||
// error per se. Each byte that cannot be transcoded will be represented in the
|
||||
// output by the UTF-8 encoding of '\uFFFD', the replacement rune.
|
||||
type Decoder struct {
|
||||
transform.Transformer
|
||||
|
||||
// This forces external creators of Decoders to use names in struct
|
||||
// initializers, allowing for future extendibility without having to break
|
||||
// code.
|
||||
_ struct{}
|
||||
}
|
||||
|
||||
// Bytes converts the given encoded bytes to UTF-8. It returns the converted
|
||||
// bytes or nil, err if any error occurred.
|
||||
func (d *Decoder) Bytes(b []byte) ([]byte, error) {
|
||||
b, _, err := transform.Bytes(d, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String converts the given encoded string to UTF-8. It returns the converted
|
||||
// string or "", err if any error occurred.
|
||||
func (d *Decoder) String(s string) (string, error) {
|
||||
s, _, err := transform.String(d, s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Reader wraps another Reader to decode its bytes.
|
||||
//
|
||||
// The Decoder may not be used for any other operation as long as the returned
|
||||
// Reader is in use.
|
||||
func (d *Decoder) Reader(r io.Reader) io.Reader {
|
||||
return transform.NewReader(r, d)
|
||||
}
|
||||
|
||||
// An Encoder converts bytes from UTF-8. It implements transform.Transformer.
|
||||
//
|
||||
// Each rune that cannot be transcoded will result in an error. In this case,
|
||||
// the transform will consume all source byte up to, not including the offending
|
||||
// rune. Transforming source bytes that are not valid UTF-8 will be replaced by
|
||||
// `\uFFFD`. To return early with an error instead, use transform.Chain to
|
||||
// preprocess the data with a UTF8Validator.
|
||||
type Encoder struct {
|
||||
transform.Transformer
|
||||
|
||||
// This forces external creators of Encoders to use names in struct
|
||||
// initializers, allowing for future extendibility without having to break
|
||||
// code.
|
||||
_ struct{}
|
||||
}
|
||||
|
||||
// Bytes converts bytes from UTF-8. It returns the converted bytes or nil, err if
|
||||
// any error occurred.
|
||||
func (e *Encoder) Bytes(b []byte) ([]byte, error) {
|
||||
b, _, err := transform.Bytes(e, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String converts a string from UTF-8. It returns the converted string or
|
||||
// "", err if any error occurred.
|
||||
func (e *Encoder) String(s string) (string, error) {
|
||||
s, _, err := transform.String(e, s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Writer wraps another Writer to encode its UTF-8 output.
|
||||
//
|
||||
// The Encoder may not be used for any other operation as long as the returned
|
||||
// Writer is in use.
|
||||
func (e *Encoder) Writer(w io.Writer) io.Writer {
|
||||
return transform.NewWriter(w, e)
|
||||
}
|
||||
|
||||
// ASCIISub is the ASCII substitute character, as recommended by
|
||||
// https://unicode.org/reports/tr36/#Text_Comparison
|
||||
const ASCIISub = '\x1a'
|
||||
|
||||
// Nop is the nop encoding. Its transformed bytes are the same as the source
|
||||
// bytes; it does not replace invalid UTF-8 sequences.
|
||||
var Nop Encoding = nop{}
|
||||
|
||||
type nop struct{}
|
||||
|
||||
func (nop) NewDecoder() *Decoder {
|
||||
return &Decoder{Transformer: transform.Nop}
|
||||
}
|
||||
func (nop) NewEncoder() *Encoder {
|
||||
return &Encoder{Transformer: transform.Nop}
|
||||
}
|
||||
|
||||
// Replacement is the replacement encoding. Decoding from the replacement
|
||||
// encoding yields a single '\uFFFD' replacement rune. Encoding from UTF-8 to
|
||||
// the replacement encoding yields the same as the source bytes except that
|
||||
// invalid UTF-8 is converted to '\uFFFD'.
|
||||
//
|
||||
// It is defined at http://encoding.spec.whatwg.org/#replacement
|
||||
var Replacement Encoding = replacement{}
|
||||
|
||||
type replacement struct{}
|
||||
|
||||
func (replacement) NewDecoder() *Decoder {
|
||||
return &Decoder{Transformer: replacementDecoder{}}
|
||||
}
|
||||
|
||||
func (replacement) NewEncoder() *Encoder {
|
||||
return &Encoder{Transformer: replacementEncoder{}}
|
||||
}
|
||||
|
||||
func (replacement) ID() (mib identifier.MIB, other string) {
|
||||
return identifier.Replacement, ""
|
||||
}
|
||||
|
||||
type replacementDecoder struct{ transform.NopResetter }
|
||||
|
||||
func (replacementDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
if len(dst) < 3 {
|
||||
return 0, 0, transform.ErrShortDst
|
||||
}
|
||||
if atEOF {
|
||||
const fffd = "\ufffd"
|
||||
dst[0] = fffd[0]
|
||||
dst[1] = fffd[1]
|
||||
dst[2] = fffd[2]
|
||||
nDst = 3
|
||||
}
|
||||
return nDst, len(src), nil
|
||||
}
|
||||
|
||||
type replacementEncoder struct{ transform.NopResetter }
|
||||
|
||||
func (replacementEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
r, size := rune(0), 0
|
||||
|
||||
for ; nSrc < len(src); nSrc += size {
|
||||
r = rune(src[nSrc])
|
||||
|
||||
// Decode a 1-byte rune.
|
||||
if r < utf8.RuneSelf {
|
||||
size = 1
|
||||
|
||||
} else {
|
||||
// Decode a multi-byte rune.
|
||||
r, size = utf8.DecodeRune(src[nSrc:])
|
||||
if size == 1 {
|
||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
||||
// full character yet.
|
||||
if !atEOF && !utf8.FullRune(src[nSrc:]) {
|
||||
err = transform.ErrShortSrc
|
||||
break
|
||||
}
|
||||
r = '\ufffd'
|
||||
}
|
||||
}
|
||||
|
||||
if nDst+utf8.RuneLen(r) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
break
|
||||
}
|
||||
nDst += utf8.EncodeRune(dst[nDst:], r)
|
||||
}
|
||||
return nDst, nSrc, err
|
||||
}
|
||||
|
||||
// HTMLEscapeUnsupported wraps encoders to replace source runes outside the
|
||||
// repertoire of the destination encoding with HTML escape sequences.
|
||||
//
|
||||
// This wrapper exists to comply to URL and HTML forms requiring a
|
||||
// non-terminating legacy encoder. The produced sequences may lead to data
|
||||
// loss as they are indistinguishable from legitimate input. To avoid this
|
||||
// issue, use UTF-8 encodings whenever possible.
|
||||
func HTMLEscapeUnsupported(e *Encoder) *Encoder {
|
||||
return &Encoder{Transformer: &errorHandler{e, errorToHTML}}
|
||||
}
|
||||
|
||||
// ReplaceUnsupported wraps encoders to replace source runes outside the
|
||||
// repertoire of the destination encoding with an encoding-specific
|
||||
// replacement.
|
||||
//
|
||||
// This wrapper is only provided for backwards compatibility and legacy
|
||||
// handling. Its use is strongly discouraged. Use UTF-8 whenever possible.
|
||||
func ReplaceUnsupported(e *Encoder) *Encoder {
|
||||
return &Encoder{Transformer: &errorHandler{e, errorToReplacement}}
|
||||
}
|
||||
|
||||
type errorHandler struct {
|
||||
*Encoder
|
||||
handler func(dst []byte, r rune, err repertoireError) (n int, ok bool)
|
||||
}
|
||||
|
||||
// TODO: consider making this error public in some form.
|
||||
type repertoireError interface {
|
||||
Replacement() byte
|
||||
}
|
||||
|
||||
func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF)
|
||||
for err != nil {
|
||||
rerr, ok := err.(repertoireError)
|
||||
if !ok {
|
||||
return nDst, nSrc, err
|
||||
}
|
||||
r, sz := utf8.DecodeRune(src[nSrc:])
|
||||
n, ok := h.handler(dst[nDst:], r, rerr)
|
||||
if !ok {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
err = nil
|
||||
nDst += n
|
||||
if nSrc += sz; nSrc < len(src) {
|
||||
var dn, sn int
|
||||
dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF)
|
||||
nDst += dn
|
||||
nSrc += sn
|
||||
}
|
||||
}
|
||||
return nDst, nSrc, err
|
||||
}
|
||||
|
||||
func errorToHTML(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
||||
buf := [8]byte{}
|
||||
b := strconv.AppendUint(buf[:0], uint64(r), 10)
|
||||
if n = len(b) + len("&#;"); n >= len(dst) {
|
||||
return 0, false
|
||||
}
|
||||
dst[0] = '&'
|
||||
dst[1] = '#'
|
||||
dst[copy(dst[2:], b)+2] = ';'
|
||||
return n, true
|
||||
}
|
||||
|
||||
func errorToReplacement(dst []byte, r rune, err repertoireError) (n int, ok bool) {
|
||||
if len(dst) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
dst[0] = err.Replacement()
|
||||
return 1, true
|
||||
}
|
||||
|
||||
// ErrInvalidUTF8 means that a transformer encountered invalid UTF-8.
|
||||
var ErrInvalidUTF8 = errors.New("encoding: invalid UTF-8")
|
||||
|
||||
// UTF8Validator is a transformer that returns ErrInvalidUTF8 on the first
|
||||
// input byte that is not valid UTF-8.
|
||||
var UTF8Validator transform.Transformer = utf8Validator{}
|
||||
|
||||
type utf8Validator struct{ transform.NopResetter }
|
||||
|
||||
func (utf8Validator) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
n := len(src)
|
||||
if n > len(dst) {
|
||||
n = len(dst)
|
||||
}
|
||||
for i := 0; i < n; {
|
||||
if c := src[i]; c < utf8.RuneSelf {
|
||||
dst[i] = c
|
||||
i++
|
||||
continue
|
||||
}
|
||||
_, size := utf8.DecodeRune(src[i:])
|
||||
if size == 1 {
|
||||
// All valid runes of size 1 (those below utf8.RuneSelf) were
|
||||
// handled above. We have invalid UTF-8 or we haven't seen the
|
||||
// full character yet.
|
||||
err = ErrInvalidUTF8
|
||||
if !atEOF && !utf8.FullRune(src[i:]) {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return i, i, err
|
||||
}
|
||||
if i+size > len(dst) {
|
||||
return i, i, transform.ErrShortDst
|
||||
}
|
||||
for ; size > 0; size-- {
|
||||
dst[i] = src[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
if len(src) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
}
|
||||
return n, n, err
|
||||
}
|
||||
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
81
vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
// Package identifier defines the contract between implementations of Encoding
|
||||
// and Index by defining identifiers that uniquely identify standardized coded
|
||||
// character sets (CCS) and character encoding schemes (CES), which we will
|
||||
// together refer to as encodings, for which Encoding implementations provide
|
||||
// converters to and from UTF-8. This package is typically only of concern to
|
||||
// implementers of Indexes and Encodings.
|
||||
//
|
||||
// One part of the identifier is the MIB code, which is defined by IANA and
|
||||
// uniquely identifies a CCS or CES. Each code is associated with data that
|
||||
// references authorities, official documentation as well as aliases and MIME
|
||||
// names.
|
||||
//
|
||||
// Not all CESs are covered by the IANA registry. The "other" string that is
|
||||
// returned by ID can be used to identify other character sets or versions of
|
||||
// existing ones.
|
||||
//
|
||||
// It is recommended that each package that provides a set of Encodings provide
|
||||
// the All and Common variables to reference all supported encodings and
|
||||
// commonly used subset. This allows Index implementations to include all
|
||||
// available encodings without explicitly referencing or knowing about them.
|
||||
package identifier
|
||||
|
||||
// Note: this package is internal, but could be made public if there is a need
|
||||
// for writing third-party Indexes and Encodings.
|
||||
|
||||
// References:
|
||||
// - http://source.icu-project.org/repos/icu/icu/trunk/source/data/mappings/convrtrs.txt
|
||||
// - http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
||||
// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
|
||||
// - http://www.ietf.org/rfc/rfc2978.txt
|
||||
// - https://www.unicode.org/reports/tr22/
|
||||
// - http://www.w3.org/TR/encoding/
|
||||
// - https://encoding.spec.whatwg.org/
|
||||
// - https://encoding.spec.whatwg.org/encodings.json
|
||||
// - https://tools.ietf.org/html/rfc6657#section-5
|
||||
|
||||
// Interface can be implemented by Encodings to define the CCS or CES for which
|
||||
// it implements conversions.
|
||||
type Interface interface {
|
||||
// ID returns an encoding identifier. Exactly one of the mib and other
|
||||
// values should be non-zero.
|
||||
//
|
||||
// In the usual case it is only necessary to indicate the MIB code. The
|
||||
// other string can be used to specify encodings for which there is no MIB,
|
||||
// such as "x-mac-dingbat".
|
||||
//
|
||||
// The other string may only contain the characters a-z, A-Z, 0-9, - and _.
|
||||
ID() (mib MIB, other string)
|
||||
|
||||
// NOTE: the restrictions on the encoding are to allow extending the syntax
|
||||
// with additional information such as versions, vendors and other variants.
|
||||
}
|
||||
|
||||
// A MIB identifies an encoding. It is derived from the IANA MIB codes and adds
|
||||
// some identifiers for some encodings that are not covered by the IANA
|
||||
// standard.
|
||||
//
|
||||
// See http://www.iana.org/assignments/ianacharset-mib.
|
||||
type MIB uint16
|
||||
|
||||
// These additional MIB types are not defined in IANA. They are added because
|
||||
// they are common and defined within the text repo.
|
||||
const (
|
||||
// Unofficial marks the start of encodings not registered by IANA.
|
||||
Unofficial MIB = 10000 + iota
|
||||
|
||||
// Replacement is the WhatWG replacement encoding.
|
||||
Replacement
|
||||
|
||||
// XUserDefined is the code for x-user-defined.
|
||||
XUserDefined
|
||||
|
||||
// MacintoshCyrillic is the code for x-mac-cyrillic.
|
||||
MacintoshCyrillic
|
||||
)
|
||||
1619
vendor/golang.org/x/text/encoding/internal/identifier/mib.go
generated
vendored
1619
vendor/golang.org/x/text/encoding/internal/identifier/mib.go
generated
vendored
File diff suppressed because it is too large
Load Diff
709
vendor/golang.org/x/text/transform/transform.go
generated
vendored
709
vendor/golang.org/x/text/transform/transform.go
generated
vendored
@@ -1,709 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package transform provides reader and writer wrappers that transform the
|
||||
// bytes passing through as well as various transformations. Example
|
||||
// transformations provided by other packages include normalization and
|
||||
// conversion between character sets.
|
||||
package transform // import "golang.org/x/text/transform"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrShortDst means that the destination buffer was too short to
|
||||
// receive all of the transformed bytes.
|
||||
ErrShortDst = errors.New("transform: short destination buffer")
|
||||
|
||||
// ErrShortSrc means that the source buffer has insufficient data to
|
||||
// complete the transformation.
|
||||
ErrShortSrc = errors.New("transform: short source buffer")
|
||||
|
||||
// ErrEndOfSpan means that the input and output (the transformed input)
|
||||
// are not identical.
|
||||
ErrEndOfSpan = errors.New("transform: input and output are not identical")
|
||||
|
||||
// errInconsistentByteCount means that Transform returned success (nil
|
||||
// error) but also returned nSrc inconsistent with the src argument.
|
||||
errInconsistentByteCount = errors.New("transform: inconsistent byte count returned")
|
||||
|
||||
// errShortInternal means that an internal buffer is not large enough
|
||||
// to make progress and the Transform operation must be aborted.
|
||||
errShortInternal = errors.New("transform: short internal buffer")
|
||||
)
|
||||
|
||||
// Transformer transforms bytes.
|
||||
type Transformer interface {
|
||||
// Transform writes to dst the transformed bytes read from src, and
|
||||
// returns the number of dst bytes written and src bytes read. The
|
||||
// atEOF argument tells whether src represents the last bytes of the
|
||||
// input.
|
||||
//
|
||||
// Callers should always process the nDst bytes produced and account
|
||||
// for the nSrc bytes consumed before considering the error err.
|
||||
//
|
||||
// A nil error means that all of the transformed bytes (whether freshly
|
||||
// transformed from src or left over from previous Transform calls)
|
||||
// were written to dst. A nil error can be returned regardless of
|
||||
// whether atEOF is true. If err is nil then nSrc must equal len(src);
|
||||
// the converse is not necessarily true.
|
||||
//
|
||||
// ErrShortDst means that dst was too short to receive all of the
|
||||
// transformed bytes. ErrShortSrc means that src had insufficient data
|
||||
// to complete the transformation. If both conditions apply, then
|
||||
// either error may be returned. Other than the error conditions listed
|
||||
// here, implementations are free to report other errors that arise.
|
||||
Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||
|
||||
// Reset resets the state and allows a Transformer to be reused.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// SpanningTransformer extends the Transformer interface with a Span method
|
||||
// that determines how much of the input already conforms to the Transformer.
|
||||
type SpanningTransformer interface {
|
||||
Transformer
|
||||
|
||||
// Span returns a position in src such that transforming src[:n] results in
|
||||
// identical output src[:n] for these bytes. It does not necessarily return
|
||||
// the largest such n. The atEOF argument tells whether src represents the
|
||||
// last bytes of the input.
|
||||
//
|
||||
// Callers should always account for the n bytes consumed before
|
||||
// considering the error err.
|
||||
//
|
||||
// A nil error means that all input bytes are known to be identical to the
|
||||
// output produced by the Transformer. A nil error can be returned
|
||||
// regardless of whether atEOF is true. If err is nil, then n must
|
||||
// equal len(src); the converse is not necessarily true.
|
||||
//
|
||||
// ErrEndOfSpan means that the Transformer output may differ from the
|
||||
// input after n bytes. Note that n may be len(src), meaning that the output
|
||||
// would contain additional bytes after otherwise identical output.
|
||||
// ErrShortSrc means that src had insufficient data to determine whether the
|
||||
// remaining bytes would change. Other than the error conditions listed
|
||||
// here, implementations are free to report other errors that arise.
|
||||
//
|
||||
// Calling Span can modify the Transformer state as a side effect. In
|
||||
// effect, it does the transformation just as calling Transform would, only
|
||||
// without copying to a destination buffer and only up to a point it can
|
||||
// determine the input and output bytes are the same. This is obviously more
|
||||
// limited than calling Transform, but can be more efficient in terms of
|
||||
// copying and allocating buffers. Calls to Span and Transform may be
|
||||
// interleaved.
|
||||
Span(src []byte, atEOF bool) (n int, err error)
|
||||
}
|
||||
|
||||
// NopResetter can be embedded by implementations of Transformer to add a nop
|
||||
// Reset method.
|
||||
type NopResetter struct{}
|
||||
|
||||
// Reset implements the Reset method of the Transformer interface.
|
||||
func (NopResetter) Reset() {}
|
||||
|
||||
// Reader wraps another io.Reader by transforming the bytes read.
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
t Transformer
|
||||
err error
|
||||
|
||||
// dst[dst0:dst1] contains bytes that have been transformed by t but
|
||||
// not yet copied out via Read.
|
||||
dst []byte
|
||||
dst0, dst1 int
|
||||
|
||||
// src[src0:src1] contains bytes that have been read from r but not
|
||||
// yet transformed through t.
|
||||
src []byte
|
||||
src0, src1 int
|
||||
|
||||
// transformComplete is whether the transformation is complete,
|
||||
// regardless of whether or not it was successful.
|
||||
transformComplete bool
|
||||
}
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
// NewReader returns a new Reader that wraps r by transforming the bytes read
|
||||
// via t. It calls Reset on t.
|
||||
func NewReader(r io.Reader, t Transformer) *Reader {
|
||||
t.Reset()
|
||||
return &Reader{
|
||||
r: r,
|
||||
t: t,
|
||||
dst: make([]byte, defaultBufSize),
|
||||
src: make([]byte, defaultBufSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface.
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := 0, error(nil)
|
||||
for {
|
||||
// Copy out any transformed bytes and return the final error if we are done.
|
||||
if r.dst0 != r.dst1 {
|
||||
n = copy(p, r.dst[r.dst0:r.dst1])
|
||||
r.dst0 += n
|
||||
if r.dst0 == r.dst1 && r.transformComplete {
|
||||
return n, r.err
|
||||
}
|
||||
return n, nil
|
||||
} else if r.transformComplete {
|
||||
return 0, r.err
|
||||
}
|
||||
|
||||
// Try to transform some source bytes, or to flush the transformer if we
|
||||
// are out of source bytes. We do this even if r.r.Read returned an error.
|
||||
// As the io.Reader documentation says, "process the n > 0 bytes returned
|
||||
// before considering the error".
|
||||
if r.src0 != r.src1 || r.err != nil {
|
||||
r.dst0 = 0
|
||||
r.dst1, n, err = r.t.Transform(r.dst, r.src[r.src0:r.src1], r.err == io.EOF)
|
||||
r.src0 += n
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
if r.src0 != r.src1 {
|
||||
r.err = errInconsistentByteCount
|
||||
}
|
||||
// The Transform call was successful; we are complete if we
|
||||
// cannot read more bytes into src.
|
||||
r.transformComplete = r.err != nil
|
||||
continue
|
||||
case err == ErrShortDst && (r.dst1 != 0 || n != 0):
|
||||
// Make room in dst by copying out, and try again.
|
||||
continue
|
||||
case err == ErrShortSrc && r.src1-r.src0 != len(r.src) && r.err == nil:
|
||||
// Read more bytes into src via the code below, and try again.
|
||||
default:
|
||||
r.transformComplete = true
|
||||
// The reader error (r.err) takes precedence over the
|
||||
// transformer error (err) unless r.err is nil or io.EOF.
|
||||
if r.err == nil || r.err == io.EOF {
|
||||
r.err = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Move any untransformed source bytes to the start of the buffer
|
||||
// and read more bytes.
|
||||
if r.src0 != 0 {
|
||||
r.src0, r.src1 = 0, copy(r.src, r.src[r.src0:r.src1])
|
||||
}
|
||||
n, r.err = r.r.Read(r.src[r.src1:])
|
||||
r.src1 += n
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement ReadByte (and ReadRune??).
|
||||
|
||||
// Writer wraps another io.Writer by transforming the bytes read.
|
||||
// The user needs to call Close to flush unwritten bytes that may
|
||||
// be buffered.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
t Transformer
|
||||
dst []byte
|
||||
|
||||
// src[:n] contains bytes that have not yet passed through t.
|
||||
src []byte
|
||||
n int
|
||||
}
|
||||
|
||||
// NewWriter returns a new Writer that wraps w by transforming the bytes written
|
||||
// via t. It calls Reset on t.
|
||||
func NewWriter(w io.Writer, t Transformer) *Writer {
|
||||
t.Reset()
|
||||
return &Writer{
|
||||
w: w,
|
||||
t: t,
|
||||
dst: make([]byte, defaultBufSize),
|
||||
src: make([]byte, defaultBufSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface. If there are not enough
|
||||
// bytes available to complete a Transform, the bytes will be buffered
|
||||
// for the next write. Call Close to convert the remaining bytes.
|
||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||
src := data
|
||||
if w.n > 0 {
|
||||
// Append bytes from data to the last remainder.
|
||||
// TODO: limit the amount copied on first try.
|
||||
n = copy(w.src[w.n:], data)
|
||||
w.n += n
|
||||
src = w.src[:w.n]
|
||||
}
|
||||
for {
|
||||
nDst, nSrc, err := w.t.Transform(w.dst, src, false)
|
||||
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
||||
return n, werr
|
||||
}
|
||||
src = src[nSrc:]
|
||||
if w.n == 0 {
|
||||
n += nSrc
|
||||
} else if len(src) <= n {
|
||||
// Enough bytes from w.src have been consumed. We make src point
|
||||
// to data instead to reduce the copying.
|
||||
w.n = 0
|
||||
n -= len(src)
|
||||
src = data[n:]
|
||||
if n < len(data) && (err == nil || err == ErrShortSrc) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
switch err {
|
||||
case ErrShortDst:
|
||||
// This error is okay as long as we are making progress.
|
||||
if nDst > 0 || nSrc > 0 {
|
||||
continue
|
||||
}
|
||||
case ErrShortSrc:
|
||||
if len(src) < len(w.src) {
|
||||
m := copy(w.src, src)
|
||||
// If w.n > 0, bytes from data were already copied to w.src and n
|
||||
// was already set to the number of bytes consumed.
|
||||
if w.n == 0 {
|
||||
n += m
|
||||
}
|
||||
w.n = m
|
||||
err = nil
|
||||
} else if nDst > 0 || nSrc > 0 {
|
||||
// Not enough buffer to store the remainder. Keep processing as
|
||||
// long as there is progress. Without this case, transforms that
|
||||
// require a lookahead larger than the buffer may result in an
|
||||
// error. This is not something one may expect to be common in
|
||||
// practice, but it may occur when buffers are set to small
|
||||
// sizes during testing.
|
||||
continue
|
||||
}
|
||||
case nil:
|
||||
if w.n > 0 {
|
||||
err = errInconsistentByteCount
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface.
|
||||
func (w *Writer) Close() error {
|
||||
src := w.src[:w.n]
|
||||
for {
|
||||
nDst, nSrc, err := w.t.Transform(w.dst, src, true)
|
||||
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
||||
return werr
|
||||
}
|
||||
if err != ErrShortDst {
|
||||
return err
|
||||
}
|
||||
src = src[nSrc:]
|
||||
}
|
||||
}
|
||||
|
||||
type nop struct{ NopResetter }
|
||||
|
||||
func (nop) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
n := copy(dst, src)
|
||||
if n < len(src) {
|
||||
err = ErrShortDst
|
||||
}
|
||||
return n, n, err
|
||||
}
|
||||
|
||||
func (nop) Span(src []byte, atEOF bool) (n int, err error) {
|
||||
return len(src), nil
|
||||
}
|
||||
|
||||
type discard struct{ NopResetter }
|
||||
|
||||
func (discard) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
return 0, len(src), nil
|
||||
}
|
||||
|
||||
var (
|
||||
// Discard is a Transformer for which all Transform calls succeed
|
||||
// by consuming all bytes and writing nothing.
|
||||
Discard Transformer = discard{}
|
||||
|
||||
// Nop is a SpanningTransformer that copies src to dst.
|
||||
Nop SpanningTransformer = nop{}
|
||||
)
|
||||
|
||||
// chain is a sequence of links. A chain with N Transformers has N+1 links and
|
||||
// N+1 buffers. Of those N+1 buffers, the first and last are the src and dst
|
||||
// buffers given to chain.Transform and the middle N-1 buffers are intermediate
|
||||
// buffers owned by the chain. The i'th link transforms bytes from the i'th
|
||||
// buffer chain.link[i].b at read offset chain.link[i].p to the i+1'th buffer
|
||||
// chain.link[i+1].b at write offset chain.link[i+1].n, for i in [0, N).
|
||||
type chain struct {
|
||||
link []link
|
||||
err error
|
||||
// errStart is the index at which the error occurred plus 1. Processing
|
||||
// errStart at this level at the next call to Transform. As long as
|
||||
// errStart > 0, chain will not consume any more source bytes.
|
||||
errStart int
|
||||
}
|
||||
|
||||
func (c *chain) fatalError(errIndex int, err error) {
|
||||
if i := errIndex + 1; i > c.errStart {
|
||||
c.errStart = i
|
||||
c.err = err
|
||||
}
|
||||
}
|
||||
|
||||
type link struct {
|
||||
t Transformer
|
||||
// b[p:n] holds the bytes to be transformed by t.
|
||||
b []byte
|
||||
p int
|
||||
n int
|
||||
}
|
||||
|
||||
func (l *link) src() []byte {
|
||||
return l.b[l.p:l.n]
|
||||
}
|
||||
|
||||
func (l *link) dst() []byte {
|
||||
return l.b[l.n:]
|
||||
}
|
||||
|
||||
// Chain returns a Transformer that applies t in sequence.
|
||||
func Chain(t ...Transformer) Transformer {
|
||||
if len(t) == 0 {
|
||||
return nop{}
|
||||
}
|
||||
c := &chain{link: make([]link, len(t)+1)}
|
||||
for i, tt := range t {
|
||||
c.link[i].t = tt
|
||||
}
|
||||
// Allocate intermediate buffers.
|
||||
b := make([][defaultBufSize]byte, len(t)-1)
|
||||
for i := range b {
|
||||
c.link[i+1].b = b[i][:]
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Reset resets the state of Chain. It calls Reset on all the Transformers.
|
||||
func (c *chain) Reset() {
|
||||
for i, l := range c.link {
|
||||
if l.t != nil {
|
||||
l.t.Reset()
|
||||
}
|
||||
c.link[i].p, c.link[i].n = 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make chain use Span (is going to be fun to implement!)
|
||||
|
||||
// Transform applies the transformers of c in sequence.
|
||||
func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
// Set up src and dst in the chain.
|
||||
srcL := &c.link[0]
|
||||
dstL := &c.link[len(c.link)-1]
|
||||
srcL.b, srcL.p, srcL.n = src, 0, len(src)
|
||||
dstL.b, dstL.n = dst, 0
|
||||
var lastFull, needProgress bool // for detecting progress
|
||||
|
||||
// i is the index of the next Transformer to apply, for i in [low, high].
|
||||
// low is the lowest index for which c.link[low] may still produce bytes.
|
||||
// high is the highest index for which c.link[high] has a Transformer.
|
||||
// The error returned by Transform determines whether to increase or
|
||||
// decrease i. We try to completely fill a buffer before converting it.
|
||||
for low, i, high := c.errStart, c.errStart, len(c.link)-2; low <= i && i <= high; {
|
||||
in, out := &c.link[i], &c.link[i+1]
|
||||
nDst, nSrc, err0 := in.t.Transform(out.dst(), in.src(), atEOF && low == i)
|
||||
out.n += nDst
|
||||
in.p += nSrc
|
||||
if i > 0 && in.p == in.n {
|
||||
in.p, in.n = 0, 0
|
||||
}
|
||||
needProgress, lastFull = lastFull, false
|
||||
switch err0 {
|
||||
case ErrShortDst:
|
||||
// Process the destination buffer next. Return if we are already
|
||||
// at the high index.
|
||||
if i == high {
|
||||
return dstL.n, srcL.p, ErrShortDst
|
||||
}
|
||||
if out.n != 0 {
|
||||
i++
|
||||
// If the Transformer at the next index is not able to process any
|
||||
// source bytes there is nothing that can be done to make progress
|
||||
// and the bytes will remain unprocessed. lastFull is used to
|
||||
// detect this and break out of the loop with a fatal error.
|
||||
lastFull = true
|
||||
continue
|
||||
}
|
||||
// The destination buffer was too small, but is completely empty.
|
||||
// Return a fatal error as this transformation can never complete.
|
||||
c.fatalError(i, errShortInternal)
|
||||
case ErrShortSrc:
|
||||
if i == 0 {
|
||||
// Save ErrShortSrc in err. All other errors take precedence.
|
||||
err = ErrShortSrc
|
||||
break
|
||||
}
|
||||
// Source bytes were depleted before filling up the destination buffer.
|
||||
// Verify we made some progress, move the remaining bytes to the errStart
|
||||
// and try to get more source bytes.
|
||||
if needProgress && nSrc == 0 || in.n-in.p == len(in.b) {
|
||||
// There were not enough source bytes to proceed while the source
|
||||
// buffer cannot hold any more bytes. Return a fatal error as this
|
||||
// transformation can never complete.
|
||||
c.fatalError(i, errShortInternal)
|
||||
break
|
||||
}
|
||||
// in.b is an internal buffer and we can make progress.
|
||||
in.p, in.n = 0, copy(in.b, in.src())
|
||||
fallthrough
|
||||
case nil:
|
||||
// if i == low, we have depleted the bytes at index i or any lower levels.
|
||||
// In that case we increase low and i. In all other cases we decrease i to
|
||||
// fetch more bytes before proceeding to the next index.
|
||||
if i > low {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
default:
|
||||
c.fatalError(i, err0)
|
||||
}
|
||||
// Exhausted level low or fatal error: increase low and continue
|
||||
// to process the bytes accepted so far.
|
||||
i++
|
||||
low = i
|
||||
}
|
||||
|
||||
// If c.errStart > 0, this means we found a fatal error. We will clear
|
||||
// all upstream buffers. At this point, no more progress can be made
|
||||
// downstream, as Transform would have bailed while handling ErrShortDst.
|
||||
if c.errStart > 0 {
|
||||
for i := 1; i < c.errStart; i++ {
|
||||
c.link[i].p, c.link[i].n = 0, 0
|
||||
}
|
||||
err, c.errStart, c.err = c.err, 0, nil
|
||||
}
|
||||
return dstL.n, srcL.p, err
|
||||
}
|
||||
|
||||
// Deprecated: Use runes.Remove instead.
|
||||
func RemoveFunc(f func(r rune) bool) Transformer {
|
||||
return removeF(f)
|
||||
}
|
||||
|
||||
type removeF func(r rune) bool
|
||||
|
||||
func (removeF) Reset() {}
|
||||
|
||||
// Transform implements the Transformer interface.
|
||||
func (t removeF) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for r, sz := rune(0), 0; len(src) > 0; src = src[sz:] {
|
||||
|
||||
if r = rune(src[0]); r < utf8.RuneSelf {
|
||||
sz = 1
|
||||
} else {
|
||||
r, sz = utf8.DecodeRune(src)
|
||||
|
||||
if sz == 1 {
|
||||
// Invalid rune.
|
||||
if !atEOF && !utf8.FullRune(src) {
|
||||
err = ErrShortSrc
|
||||
break
|
||||
}
|
||||
// We replace illegal bytes with RuneError. Not doing so might
|
||||
// otherwise turn a sequence of invalid UTF-8 into valid UTF-8.
|
||||
// The resulting byte sequence may subsequently contain runes
|
||||
// for which t(r) is true that were passed unnoticed.
|
||||
if !t(r) {
|
||||
if nDst+3 > len(dst) {
|
||||
err = ErrShortDst
|
||||
break
|
||||
}
|
||||
nDst += copy(dst[nDst:], "\uFFFD")
|
||||
}
|
||||
nSrc++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !t(r) {
|
||||
if nDst+sz > len(dst) {
|
||||
err = ErrShortDst
|
||||
break
|
||||
}
|
||||
nDst += copy(dst[nDst:], src[:sz])
|
||||
}
|
||||
nSrc += sz
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// grow returns a new []byte that is longer than b, and copies the first n bytes
|
||||
// of b to the start of the new slice.
|
||||
func grow(b []byte, n int) []byte {
|
||||
m := len(b)
|
||||
if m <= 32 {
|
||||
m = 64
|
||||
} else if m <= 256 {
|
||||
m *= 2
|
||||
} else {
|
||||
m += m >> 1
|
||||
}
|
||||
buf := make([]byte, m)
|
||||
copy(buf, b[:n])
|
||||
return buf
|
||||
}
|
||||
|
||||
const initialBufSize = 128
|
||||
|
||||
// String returns a string with the result of converting s[:n] using t, where
|
||||
// n <= len(s). If err == nil, n will be len(s). It calls Reset on t.
|
||||
func String(t Transformer, s string) (result string, n int, err error) {
|
||||
t.Reset()
|
||||
if s == "" {
|
||||
// Fast path for the common case for empty input. Results in about a
|
||||
// 86% reduction of running time for BenchmarkStringLowerEmpty.
|
||||
if _, _, err := t.Transform(nil, nil, true); err == nil {
|
||||
return "", 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate only once. Note that both dst and src escape when passed to
|
||||
// Transform.
|
||||
buf := [2 * initialBufSize]byte{}
|
||||
dst := buf[:initialBufSize:initialBufSize]
|
||||
src := buf[initialBufSize : 2*initialBufSize]
|
||||
|
||||
// The input string s is transformed in multiple chunks (starting with a
|
||||
// chunk size of initialBufSize). nDst and nSrc are per-chunk (or
|
||||
// per-Transform-call) indexes, pDst and pSrc are overall indexes.
|
||||
nDst, nSrc := 0, 0
|
||||
pDst, pSrc := 0, 0
|
||||
|
||||
// pPrefix is the length of a common prefix: the first pPrefix bytes of the
|
||||
// result will equal the first pPrefix bytes of s. It is not guaranteed to
|
||||
// be the largest such value, but if pPrefix, len(result) and len(s) are
|
||||
// all equal after the final transform (i.e. calling Transform with atEOF
|
||||
// being true returned nil error) then we don't need to allocate a new
|
||||
// result string.
|
||||
pPrefix := 0
|
||||
for {
|
||||
// Invariant: pDst == pPrefix && pSrc == pPrefix.
|
||||
|
||||
n := copy(src, s[pSrc:])
|
||||
nDst, nSrc, err = t.Transform(dst, src[:n], pSrc+n == len(s))
|
||||
pDst += nDst
|
||||
pSrc += nSrc
|
||||
|
||||
// TODO: let transformers implement an optional Spanner interface, akin
|
||||
// to norm's QuickSpan. This would even allow us to avoid any allocation.
|
||||
if !bytes.Equal(dst[:nDst], src[:nSrc]) {
|
||||
break
|
||||
}
|
||||
pPrefix = pSrc
|
||||
if err == ErrShortDst {
|
||||
// A buffer can only be short if a transformer modifies its input.
|
||||
break
|
||||
} else if err == ErrShortSrc {
|
||||
if nSrc == 0 {
|
||||
// No progress was made.
|
||||
break
|
||||
}
|
||||
// Equal so far and !atEOF, so continue checking.
|
||||
} else if err != nil || pPrefix == len(s) {
|
||||
return string(s[:pPrefix]), pPrefix, err
|
||||
}
|
||||
}
|
||||
// Post-condition: pDst == pPrefix + nDst && pSrc == pPrefix + nSrc.
|
||||
|
||||
// We have transformed the first pSrc bytes of the input s to become pDst
|
||||
// transformed bytes. Those transformed bytes are discontiguous: the first
|
||||
// pPrefix of them equal s[:pPrefix] and the last nDst of them equal
|
||||
// dst[:nDst]. We copy them around, into a new dst buffer if necessary, so
|
||||
// that they become one contiguous slice: dst[:pDst].
|
||||
if pPrefix != 0 {
|
||||
newDst := dst
|
||||
if pDst > len(newDst) {
|
||||
newDst = make([]byte, len(s)+nDst-nSrc)
|
||||
}
|
||||
copy(newDst[pPrefix:pDst], dst[:nDst])
|
||||
copy(newDst[:pPrefix], s[:pPrefix])
|
||||
dst = newDst
|
||||
}
|
||||
|
||||
// Prevent duplicate Transform calls with atEOF being true at the end of
|
||||
// the input. Also return if we have an unrecoverable error.
|
||||
if (err == nil && pSrc == len(s)) ||
|
||||
(err != nil && err != ErrShortDst && err != ErrShortSrc) {
|
||||
return string(dst[:pDst]), pSrc, err
|
||||
}
|
||||
|
||||
// Transform the remaining input, growing dst and src buffers as necessary.
|
||||
for {
|
||||
n := copy(src, s[pSrc:])
|
||||
atEOF := pSrc+n == len(s)
|
||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], atEOF)
|
||||
pDst += nDst
|
||||
pSrc += nSrc
|
||||
|
||||
// If we got ErrShortDst or ErrShortSrc, do not grow as long as we can
|
||||
// make progress. This may avoid excessive allocations.
|
||||
if err == ErrShortDst {
|
||||
if nDst == 0 {
|
||||
dst = grow(dst, pDst)
|
||||
}
|
||||
} else if err == ErrShortSrc {
|
||||
if atEOF {
|
||||
return string(dst[:pDst]), pSrc, err
|
||||
}
|
||||
if nSrc == 0 {
|
||||
src = grow(src, 0)
|
||||
}
|
||||
} else if err != nil || pSrc == len(s) {
|
||||
return string(dst[:pDst]), pSrc, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns a new byte slice with the result of converting b[:n] using t,
|
||||
// where n <= len(b). If err == nil, n will be len(b). It calls Reset on t.
|
||||
func Bytes(t Transformer, b []byte) (result []byte, n int, err error) {
|
||||
return doAppend(t, 0, make([]byte, len(b)), b)
|
||||
}
|
||||
|
||||
// Append appends the result of converting src[:n] using t to dst, where
|
||||
// n <= len(src), If err == nil, n will be len(src). It calls Reset on t.
|
||||
func Append(t Transformer, dst, src []byte) (result []byte, n int, err error) {
|
||||
if len(dst) == cap(dst) {
|
||||
n := len(src) + len(dst) // It is okay for this to be 0.
|
||||
b := make([]byte, n)
|
||||
dst = b[:copy(b, dst)]
|
||||
}
|
||||
return doAppend(t, len(dst), dst[:cap(dst)], src)
|
||||
}
|
||||
|
||||
func doAppend(t Transformer, pDst int, dst, src []byte) (result []byte, n int, err error) {
|
||||
t.Reset()
|
||||
pSrc := 0
|
||||
for {
|
||||
nDst, nSrc, err := t.Transform(dst[pDst:], src[pSrc:], true)
|
||||
pDst += nDst
|
||||
pSrc += nSrc
|
||||
if err != ErrShortDst {
|
||||
return dst[:pDst], pSrc, err
|
||||
}
|
||||
|
||||
// Grow the destination buffer, but do not grow as long as we can make
|
||||
// progress. This may avoid excessive allocations.
|
||||
if nDst == 0 {
|
||||
dst = grow(dst, pDst)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
vendor/gopkg.in/yaml.v2/.travis.yml
generated
vendored
17
vendor/gopkg.in/yaml.v2/.travis.yml
generated
vendored
@@ -1,17 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.4.x"
|
||||
- "1.5.x"
|
||||
- "1.6.x"
|
||||
- "1.7.x"
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "tip"
|
||||
|
||||
go_import_path: gopkg.in/yaml.v2
|
||||
201
vendor/gopkg.in/yaml.v2/LICENSE
generated
vendored
201
vendor/gopkg.in/yaml.v2/LICENSE
generated
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
31
vendor/gopkg.in/yaml.v2/LICENSE.libyaml
generated
vendored
31
vendor/gopkg.in/yaml.v2/LICENSE.libyaml
generated
vendored
@@ -1,31 +0,0 @@
|
||||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original copyright and license:
|
||||
|
||||
apic.go
|
||||
emitterc.go
|
||||
parserc.go
|
||||
readerc.go
|
||||
scannerc.go
|
||||
writerc.go
|
||||
yamlh.go
|
||||
yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
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.
|
||||
13
vendor/gopkg.in/yaml.v2/NOTICE
generated
vendored
13
vendor/gopkg.in/yaml.v2/NOTICE
generated
vendored
@@ -1,13 +0,0 @@
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
133
vendor/gopkg.in/yaml.v2/README.md
generated
vendored
133
vendor/gopkg.in/yaml.v2/README.md
generated
vendored
@@ -1,133 +0,0 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.1 and 1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v2*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v2
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
* [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
744
vendor/gopkg.in/yaml.v2/apic.go
generated
vendored
744
vendor/gopkg.in/yaml.v2/apic.go
generated
vendored
@@ -1,744 +0,0 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
var disableLineWrapping = false
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
}
|
||||
if disableLineWrapping {
|
||||
emitter.best_width = -1
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
//// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create ALIAS.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
|
||||
//{
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// anchor_copy *yaml_char_t = NULL
|
||||
//
|
||||
// assert(event) // Non-NULL event object is expected.
|
||||
// assert(anchor) // Non-NULL anchor is expected.
|
||||
//
|
||||
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
|
||||
//
|
||||
// anchor_copy = yaml_strdup(anchor)
|
||||
// if (!anchor_copy)
|
||||
// return 0
|
||||
//
|
||||
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
||||
815
vendor/gopkg.in/yaml.v2/decode.go
generated
vendored
815
vendor/gopkg.in/yaml.v2/decode.go
generated
vendored
@@ -1,815 +0,0 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
documentNode = 1 << iota
|
||||
mappingNode
|
||||
sequenceNode
|
||||
scalarNode
|
||||
aliasNode
|
||||
)
|
||||
|
||||
type node struct {
|
||||
kind int
|
||||
line, column int
|
||||
tag string
|
||||
// For an alias node, alias holds the resolved alias.
|
||||
alias *node
|
||||
value string
|
||||
implicit bool
|
||||
children []*node
|
||||
anchors map[string]*node
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parser, produces a node tree out of a libyaml event stream.
|
||||
|
||||
type parser struct {
|
||||
parser yaml_parser_t
|
||||
event yaml_event_t
|
||||
doc *node
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newParser(b []byte) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
b = []byte{'\n'}
|
||||
}
|
||||
yaml_parser_set_input_string(&p.parser, b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func newParserFromReader(r io.Reader) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
yaml_parser_set_input_reader(&p.parser, r)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *parser) init() {
|
||||
if p.doneInit {
|
||||
return
|
||||
}
|
||||
p.expect(yaml_STREAM_START_EVENT)
|
||||
p.doneInit = true
|
||||
}
|
||||
|
||||
func (p *parser) destroy() {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
yaml_event_delete(&p.event)
|
||||
}
|
||||
yaml_parser_delete(&p.parser)
|
||||
}
|
||||
|
||||
// expect consumes an event from the event stream and
|
||||
// checks that it's of the expected type.
|
||||
func (p *parser) expect(e yaml_event_type_t) {
|
||||
if p.event.typ == yaml_NO_EVENT {
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
}
|
||||
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||
failf("attempted to go past the end of stream; corrupted value?")
|
||||
}
|
||||
if p.event.typ != e {
|
||||
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
|
||||
p.fail()
|
||||
}
|
||||
yaml_event_delete(&p.event)
|
||||
p.event.typ = yaml_NO_EVENT
|
||||
}
|
||||
|
||||
// peek peeks at the next event in the event stream,
|
||||
// puts the results into p.event and returns the event type.
|
||||
func (p *parser) peek() yaml_event_type_t {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
}
|
||||
|
||||
func (p *parser) fail() {
|
||||
var where string
|
||||
var line int
|
||||
if p.parser.problem_mark.line != 0 {
|
||||
line = p.parser.problem_mark.line
|
||||
// Scanner errors don't iterate line before returning error
|
||||
if p.parser.error == yaml_SCANNER_ERROR {
|
||||
line++
|
||||
}
|
||||
} else if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
}
|
||||
if line != 0 {
|
||||
where = "line " + strconv.Itoa(line) + ": "
|
||||
}
|
||||
var msg string
|
||||
if len(p.parser.problem) > 0 {
|
||||
msg = p.parser.problem
|
||||
} else {
|
||||
msg = "unknown problem parsing YAML content"
|
||||
}
|
||||
failf("%s%s", where, msg)
|
||||
}
|
||||
|
||||
func (p *parser) anchor(n *node, anchor []byte) {
|
||||
if anchor != nil {
|
||||
p.doc.anchors[string(anchor)] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parse() *node {
|
||||
p.init()
|
||||
switch p.peek() {
|
||||
case yaml_SCALAR_EVENT:
|
||||
return p.scalar()
|
||||
case yaml_ALIAS_EVENT:
|
||||
return p.alias()
|
||||
case yaml_MAPPING_START_EVENT:
|
||||
return p.mapping()
|
||||
case yaml_SEQUENCE_START_EVENT:
|
||||
return p.sequence()
|
||||
case yaml_DOCUMENT_START_EVENT:
|
||||
return p.document()
|
||||
case yaml_STREAM_END_EVENT:
|
||||
// Happens when attempting to decode an empty buffer.
|
||||
return nil
|
||||
default:
|
||||
panic("attempted to parse unknown event: " + p.event.typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) node(kind int) *node {
|
||||
return &node{
|
||||
kind: kind,
|
||||
line: p.event.start_mark.line,
|
||||
column: p.event.start_mark.column,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) document() *node {
|
||||
n := p.node(documentNode)
|
||||
n.anchors = make(map[string]*node)
|
||||
p.doc = n
|
||||
p.expect(yaml_DOCUMENT_START_EVENT)
|
||||
n.children = append(n.children, p.parse())
|
||||
p.expect(yaml_DOCUMENT_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) alias() *node {
|
||||
n := p.node(aliasNode)
|
||||
n.value = string(p.event.anchor)
|
||||
n.alias = p.doc.anchors[n.value]
|
||||
if n.alias == nil {
|
||||
failf("unknown anchor '%s' referenced", n.value)
|
||||
}
|
||||
p.expect(yaml_ALIAS_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) scalar() *node {
|
||||
n := p.node(scalarNode)
|
||||
n.value = string(p.event.value)
|
||||
n.tag = string(p.event.tag)
|
||||
n.implicit = p.event.implicit
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SCALAR_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) sequence() *node {
|
||||
n := p.node(sequenceNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SEQUENCE_START_EVENT)
|
||||
for p.peek() != yaml_SEQUENCE_END_EVENT {
|
||||
n.children = append(n.children, p.parse())
|
||||
}
|
||||
p.expect(yaml_SEQUENCE_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) mapping() *node {
|
||||
n := p.node(mappingNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_MAPPING_START_EVENT)
|
||||
for p.peek() != yaml_MAPPING_END_EVENT {
|
||||
n.children = append(n.children, p.parse(), p.parse())
|
||||
}
|
||||
p.expect(yaml_MAPPING_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decoder, unmarshals a node into a provided value.
|
||||
|
||||
type decoder struct {
|
||||
doc *node
|
||||
aliases map[*node]bool
|
||||
mapType reflect.Type
|
||||
terrors []string
|
||||
strict bool
|
||||
|
||||
decodeCount int
|
||||
aliasCount int
|
||||
aliasDepth int
|
||||
}
|
||||
|
||||
var (
|
||||
mapItemType = reflect.TypeOf(MapItem{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
|
||||
ifaceType = defaultMapType.Elem()
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
ptrTimeType = reflect.TypeOf(&time.Time{})
|
||||
)
|
||||
|
||||
func newDecoder(strict bool) *decoder {
|
||||
d := &decoder{mapType: defaultMapType, strict: strict}
|
||||
d.aliases = make(map[*node]bool)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decoder) terror(n *node, tag string, out reflect.Value) {
|
||||
if n.tag != "" {
|
||||
tag = n.tag
|
||||
}
|
||||
value := n.value
|
||||
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
|
||||
if len(value) > 10 {
|
||||
value = " `" + value[:7] + "...`"
|
||||
} else {
|
||||
value = " `" + value + "`"
|
||||
}
|
||||
}
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
|
||||
}
|
||||
|
||||
func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
|
||||
terrlen := len(d.terrors)
|
||||
err := u.UnmarshalYAML(func(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
d.unmarshal(n, reflect.ValueOf(v))
|
||||
if len(d.terrors) > terrlen {
|
||||
issues := d.terrors[terrlen:]
|
||||
d.terrors = d.terrors[:terrlen]
|
||||
return &TypeError{issues}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if e, ok := err.(*TypeError); ok {
|
||||
d.terrors = append(d.terrors, e.Errors...)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
|
||||
// if a value is found to implement it.
|
||||
// It returns the initialized and dereferenced out value, whether
|
||||
// unmarshalling was already done by UnmarshalYAML, and if so whether
|
||||
// its types unmarshalled appropriately.
|
||||
//
|
||||
// If n holds a null value, prepare returns before doing anything.
|
||||
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
|
||||
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
|
||||
return out, false, false
|
||||
}
|
||||
again := true
|
||||
for again {
|
||||
again = false
|
||||
if out.Kind() == reflect.Ptr {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(out.Type().Elem()))
|
||||
}
|
||||
out = out.Elem()
|
||||
again = true
|
||||
}
|
||||
if out.CanAddr() {
|
||||
if u, ok := out.Addr().Interface().(Unmarshaler); ok {
|
||||
good = d.callUnmarshaler(n, u)
|
||||
return out, true, good
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, false, false
|
||||
}
|
||||
|
||||
const (
|
||||
// 400,000 decode operations is ~500kb of dense object declarations, or
|
||||
// ~5kb of dense object declarations with 10000% alias expansion
|
||||
alias_ratio_range_low = 400000
|
||||
|
||||
// 4,000,000 decode operations is ~5MB of dense object declarations, or
|
||||
// ~4.5MB of dense object declarations with 10% alias expansion
|
||||
alias_ratio_range_high = 4000000
|
||||
|
||||
// alias_ratio_range is the range over which we scale allowed alias ratios
|
||||
alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low)
|
||||
)
|
||||
|
||||
func allowedAliasRatio(decodeCount int) float64 {
|
||||
switch {
|
||||
case decodeCount <= alias_ratio_range_low:
|
||||
// allow 99% to come from alias expansion for small-to-medium documents
|
||||
return 0.99
|
||||
case decodeCount >= alias_ratio_range_high:
|
||||
// allow 10% to come from alias expansion for very large documents
|
||||
return 0.10
|
||||
default:
|
||||
// scale smoothly from 99% down to 10% over the range.
|
||||
// this maps to 396,000 - 400,000 allowed alias-driven decodes over the range.
|
||||
// 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps).
|
||||
return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
||||
d.decodeCount++
|
||||
if d.aliasDepth > 0 {
|
||||
d.aliasCount++
|
||||
}
|
||||
if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) {
|
||||
failf("document contains excessive aliasing")
|
||||
}
|
||||
switch n.kind {
|
||||
case documentNode:
|
||||
return d.document(n, out)
|
||||
case aliasNode:
|
||||
return d.alias(n, out)
|
||||
}
|
||||
out, unmarshaled, good := d.prepare(n, out)
|
||||
if unmarshaled {
|
||||
return good
|
||||
}
|
||||
switch n.kind {
|
||||
case scalarNode:
|
||||
good = d.scalar(n, out)
|
||||
case mappingNode:
|
||||
good = d.mapping(n, out)
|
||||
case sequenceNode:
|
||||
good = d.sequence(n, out)
|
||||
default:
|
||||
panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
|
||||
}
|
||||
return good
|
||||
}
|
||||
|
||||
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
||||
if len(n.children) == 1 {
|
||||
d.doc = n
|
||||
d.unmarshal(n.children[0], out)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||
if d.aliases[n] {
|
||||
// TODO this could actually be allowed in some circumstances.
|
||||
failf("anchor '%s' value contains itself", n.value)
|
||||
}
|
||||
d.aliases[n] = true
|
||||
d.aliasDepth++
|
||||
good = d.unmarshal(n.alias, out)
|
||||
d.aliasDepth--
|
||||
delete(d.aliases, n)
|
||||
return good
|
||||
}
|
||||
|
||||
var zeroValue reflect.Value
|
||||
|
||||
func resetMap(out reflect.Value) {
|
||||
for _, k := range out.MapKeys() {
|
||||
out.SetMapIndex(k, zeroValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) scalar(n *node, out reflect.Value) bool {
|
||||
var tag string
|
||||
var resolved interface{}
|
||||
if n.tag == "" && !n.implicit {
|
||||
tag = yaml_STR_TAG
|
||||
resolved = n.value
|
||||
} else {
|
||||
tag, resolved = resolve(n.tag, n.value)
|
||||
if tag == yaml_BINARY_TAG {
|
||||
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||
if err != nil {
|
||||
failf("!!binary value contains invalid base64 data")
|
||||
}
|
||||
resolved = string(data)
|
||||
}
|
||||
}
|
||||
if resolved == nil {
|
||||
if out.Kind() == reflect.Map && !out.CanAddr() {
|
||||
resetMap(out)
|
||||
} else {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
// We've resolved to exactly the type we want, so use that.
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
// Perhaps we can use the value as a TextUnmarshaler to
|
||||
// set its value.
|
||||
if out.CanAddr() {
|
||||
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
if ok {
|
||||
var text []byte
|
||||
if tag == yaml_BINARY_TAG {
|
||||
text = []byte(resolved.(string))
|
||||
} else {
|
||||
// We let any value be unmarshaled into TextUnmarshaler.
|
||||
// That might be more lax than we'd like, but the
|
||||
// TextUnmarshaler itself should bowl out any dubious values.
|
||||
text = []byte(n.value)
|
||||
}
|
||||
err := u.UnmarshalText(text)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch out.Kind() {
|
||||
case reflect.String:
|
||||
if tag == yaml_BINARY_TAG {
|
||||
out.SetString(resolved.(string))
|
||||
return true
|
||||
}
|
||||
if resolved != nil {
|
||||
out.SetString(n.value)
|
||||
return true
|
||||
}
|
||||
case reflect.Interface:
|
||||
if resolved == nil {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
} else if tag == yaml_TIMESTAMP_TAG {
|
||||
// It looks like a timestamp but for backward compatibility
|
||||
// reasons we set it as a string, so that code that unmarshals
|
||||
// timestamp-like values into interface{} will continue to
|
||||
// see a string and not a time.Time.
|
||||
// TODO(v3) Drop this.
|
||||
out.Set(reflect.ValueOf(n.value))
|
||||
} else {
|
||||
out.Set(reflect.ValueOf(resolved))
|
||||
}
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if !out.OverflowInt(resolved) {
|
||||
out.SetInt(resolved)
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case string:
|
||||
if out.Type() == durationType {
|
||||
d, err := time.ParseDuration(resolved)
|
||||
if err == nil {
|
||||
out.SetInt(int64(d))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch resolved := resolved.(type) {
|
||||
case bool:
|
||||
out.SetBool(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case int64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case uint64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case float64:
|
||||
out.SetFloat(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Struct:
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
||||
// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
|
||||
elem := reflect.New(out.Type().Elem())
|
||||
elem.Elem().Set(reflect.ValueOf(resolved))
|
||||
out.Set(elem)
|
||||
return true
|
||||
}
|
||||
}
|
||||
d.terror(n, tag, out)
|
||||
return false
|
||||
}
|
||||
|
||||
func settableValueOf(i interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(i)
|
||||
sv := reflect.New(v.Type()).Elem()
|
||||
sv.Set(v)
|
||||
return sv
|
||||
}
|
||||
|
||||
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
||||
l := len(n.children)
|
||||
|
||||
var iface reflect.Value
|
||||
switch out.Kind() {
|
||||
case reflect.Slice:
|
||||
out.Set(reflect.MakeSlice(out.Type(), l, l))
|
||||
case reflect.Array:
|
||||
if l != out.Len() {
|
||||
failf("invalid array: want %d elements but got %d", out.Len(), l)
|
||||
}
|
||||
case reflect.Interface:
|
||||
// No type hints. Will have to use a generic sequence.
|
||||
iface = out
|
||||
out = settableValueOf(make([]interface{}, l))
|
||||
default:
|
||||
d.terror(n, yaml_SEQ_TAG, out)
|
||||
return false
|
||||
}
|
||||
et := out.Type().Elem()
|
||||
|
||||
j := 0
|
||||
for i := 0; i < l; i++ {
|
||||
e := reflect.New(et).Elem()
|
||||
if ok := d.unmarshal(n.children[i], e); ok {
|
||||
out.Index(j).Set(e)
|
||||
j++
|
||||
}
|
||||
}
|
||||
if out.Kind() != reflect.Array {
|
||||
out.Set(out.Slice(0, j))
|
||||
}
|
||||
if iface.IsValid() {
|
||||
iface.Set(out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
||||
switch out.Kind() {
|
||||
case reflect.Struct:
|
||||
return d.mappingStruct(n, out)
|
||||
case reflect.Slice:
|
||||
return d.mappingSlice(n, out)
|
||||
case reflect.Map:
|
||||
// okay
|
||||
case reflect.Interface:
|
||||
if d.mapType.Kind() == reflect.Map {
|
||||
iface := out
|
||||
out = reflect.MakeMap(d.mapType)
|
||||
iface.Set(out)
|
||||
} else {
|
||||
slicev := reflect.New(d.mapType).Elem()
|
||||
if !d.mappingSlice(n, slicev) {
|
||||
return false
|
||||
}
|
||||
out.Set(slicev)
|
||||
return true
|
||||
}
|
||||
default:
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
outt := out.Type()
|
||||
kt := outt.Key()
|
||||
et := outt.Elem()
|
||||
|
||||
mapType := d.mapType
|
||||
if outt.Key() == ifaceType && outt.Elem() == ifaceType {
|
||||
d.mapType = outt
|
||||
}
|
||||
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
}
|
||||
l := len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
}
|
||||
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||
failf("invalid map key: %#v", k.Interface())
|
||||
}
|
||||
e := reflect.New(et).Elem()
|
||||
if d.unmarshal(n.children[i+1], e) {
|
||||
d.setMapIndex(n.children[i+1], out, k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
|
||||
if d.strict && out.MapIndex(k) != zeroValue {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
|
||||
return
|
||||
}
|
||||
out.SetMapIndex(k, v)
|
||||
}
|
||||
|
||||
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
|
||||
outt := out.Type()
|
||||
if outt.Elem() != mapItemType {
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
|
||||
mapType := d.mapType
|
||||
d.mapType = outt
|
||||
|
||||
var slice []MapItem
|
||||
var l = len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
item := MapItem{}
|
||||
k := reflect.ValueOf(&item.Key).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
v := reflect.ValueOf(&item.Value).Elem()
|
||||
if d.unmarshal(n.children[i+1], v) {
|
||||
slice = append(slice, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
out.Set(reflect.ValueOf(slice))
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
||||
sinfo, err := getStructInfo(out.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := settableValueOf("")
|
||||
l := len(n.children)
|
||||
|
||||
var inlineMap reflect.Value
|
||||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
var doneFields []bool
|
||||
if d.strict {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.children[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
if d.strict {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
|
||||
continue
|
||||
}
|
||||
doneFields[info.Id] = true
|
||||
}
|
||||
var field reflect.Value
|
||||
if info.Inline == nil {
|
||||
field = out.Field(info.Num)
|
||||
} else {
|
||||
field = out.FieldByIndex(info.Inline)
|
||||
}
|
||||
d.unmarshal(n.children[i+1], field)
|
||||
} else if sinfo.InlineMap != -1 {
|
||||
if inlineMap.IsNil() {
|
||||
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
|
||||
}
|
||||
value := reflect.New(elemType).Elem()
|
||||
d.unmarshal(n.children[i+1], value)
|
||||
d.setMapIndex(n.children[i+1], inlineMap, name, value)
|
||||
} else if d.strict {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func failWantMap() {
|
||||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *node, out reflect.Value) {
|
||||
switch n.kind {
|
||||
case mappingNode:
|
||||
d.unmarshal(n, out)
|
||||
case aliasNode:
|
||||
if n.alias != nil && n.alias.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
case sequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.children) - 1; i >= 0; i-- {
|
||||
ni := n.children[i]
|
||||
if ni.kind == aliasNode {
|
||||
if ni.alias != nil && ni.alias.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
} else if ni.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(ni, out)
|
||||
}
|
||||
default:
|
||||
failWantMap()
|
||||
}
|
||||
}
|
||||
|
||||
func isMerge(n *node) bool {
|
||||
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
||||
}
|
||||
1685
vendor/gopkg.in/yaml.v2/emitterc.go
generated
vendored
1685
vendor/gopkg.in/yaml.v2/emitterc.go
generated
vendored
File diff suppressed because it is too large
Load Diff
390
vendor/gopkg.in/yaml.v2/encode.go
generated
vendored
390
vendor/gopkg.in/yaml.v2/encode.go
generated
vendored
@@ -1,390 +0,0 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// jsonNumber is the interface of the encoding/json.Number datatype.
|
||||
// Repeating the interface here avoids a dependency on encoding/json, and also
|
||||
// supports other libraries like jsoniter, which use a similar datatype with
|
||||
// the same interface. Detecting this interface is useful when dealing with
|
||||
// structures containing json.Number, which is a string under the hood. The
|
||||
// encoder should prefer the use of Int64(), Float64() and string(), in that
|
||||
// order, when encoding this type.
|
||||
type jsonNumber interface {
|
||||
Float64() (float64, error)
|
||||
Int64() (int64, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
emitter yaml_emitter_t
|
||||
event yaml_event_t
|
||||
out []byte
|
||||
flow bool
|
||||
// doneInit holds whether the initial stream_start_event has been
|
||||
// emitted.
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newEncoder() *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func newEncoderWithWriter(w io.Writer) *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_writer(&e.emitter, w)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) init() {
|
||||
if e.doneInit {
|
||||
return
|
||||
}
|
||||
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
||||
e.emit()
|
||||
e.doneInit = true
|
||||
}
|
||||
|
||||
func (e *encoder) finish() {
|
||||
e.emitter.open_ended = false
|
||||
yaml_stream_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) destroy() {
|
||||
yaml_emitter_delete(&e.emitter)
|
||||
}
|
||||
|
||||
func (e *encoder) emit() {
|
||||
// This will internally delete the e.event value.
|
||||
e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
||||
}
|
||||
|
||||
func (e *encoder) must(ok bool) {
|
||||
if !ok {
|
||||
msg := e.emitter.problem
|
||||
if msg == "" {
|
||||
msg = "unknown problem generating YAML content"
|
||||
}
|
||||
failf("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
||||
e.init()
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.emit()
|
||||
e.marshal(tag, in)
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
iface := in.Interface()
|
||||
switch m := iface.(type) {
|
||||
case jsonNumber:
|
||||
integer, err := m.Int64()
|
||||
if err == nil {
|
||||
// In this case the json.Number is a valid int64
|
||||
in = reflect.ValueOf(integer)
|
||||
break
|
||||
}
|
||||
float, err := m.Float64()
|
||||
if err == nil {
|
||||
// In this case the json.Number is a valid float64
|
||||
in = reflect.ValueOf(float)
|
||||
break
|
||||
}
|
||||
// fallback case - no number could be obtained
|
||||
in = reflect.ValueOf(m.String())
|
||||
case time.Time, *time.Time:
|
||||
// Although time.Time implements TextMarshaler,
|
||||
// we don't want to treat it as a string for YAML
|
||||
// purposes because YAML has special support for
|
||||
// timestamps.
|
||||
case Marshaler:
|
||||
v, err := m.MarshalYAML()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if v == nil {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
in = reflect.ValueOf(v)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := m.MarshalText()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
in = reflect.ValueOf(string(text))
|
||||
case nil:
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Interface:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Map:
|
||||
e.mapv(tag, in)
|
||||
case reflect.Ptr:
|
||||
if in.Type() == ptrTimeType {
|
||||
e.timev(tag, in.Elem())
|
||||
} else {
|
||||
e.marshal(tag, in.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
if in.Type() == timeType {
|
||||
e.timev(tag, in)
|
||||
} else {
|
||||
e.structv(tag, in)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if in.Type().Elem() == mapItemType {
|
||||
e.itemsv(tag, in)
|
||||
} else {
|
||||
e.slicev(tag, in)
|
||||
}
|
||||
case reflect.String:
|
||||
e.stringv(tag, in)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if in.Type() == durationType {
|
||||
e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
|
||||
} else {
|
||||
e.intv(tag, in)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
e.uintv(tag, in)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
e.floatv(tag, in)
|
||||
case reflect.Bool:
|
||||
e.boolv(tag, in)
|
||||
default:
|
||||
panic("cannot marshal type: " + in.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
keys := keyList(in.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
e.marshal("", k)
|
||||
e.marshal("", in.MapIndex(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) itemsv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
|
||||
for _, item := range slice {
|
||||
e.marshal("", reflect.ValueOf(item.Key))
|
||||
e.marshal("", reflect.ValueOf(item.Value))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||
sinfo, err := getStructInfo(in.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.mappingv(tag, func() {
|
||||
for _, info := range sinfo.FieldsList {
|
||||
var value reflect.Value
|
||||
if info.Inline == nil {
|
||||
value = in.Field(info.Num)
|
||||
} else {
|
||||
value = in.FieldByIndex(info.Inline)
|
||||
}
|
||||
if info.OmitEmpty && isZero(value) {
|
||||
continue
|
||||
}
|
||||
e.marshal("", reflect.ValueOf(info.Key))
|
||||
e.flow = info.Flow
|
||||
e.marshal("", value)
|
||||
}
|
||||
if sinfo.InlineMap >= 0 {
|
||||
m := in.Field(sinfo.InlineMap)
|
||||
if m.Len() > 0 {
|
||||
e.flow = false
|
||||
keys := keyList(m.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if _, found := sinfo.FieldsMap[k.String()]; found {
|
||||
panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
|
||||
}
|
||||
e.marshal("", k)
|
||||
e.flow = false
|
||||
e.marshal("", m.MapIndex(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) mappingv(tag string, f func()) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
||||
e.emit()
|
||||
f()
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||
e.emit()
|
||||
n := in.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
e.marshal("", in.Index(i))
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.emit()
|
||||
}
|
||||
|
||||
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||
//
|
||||
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||
// the time being for compatibility with other parsers.
|
||||
func isBase60Float(s string) (result bool) {
|
||||
// Fast path.
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||
return false
|
||||
}
|
||||
// Do the full match.
|
||||
return base60float.MatchString(s)
|
||||
}
|
||||
|
||||
// From http://yaml.org/type/float.html, except the regular expression there
|
||||
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||
|
||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||
var style yaml_scalar_style_t
|
||||
s := in.String()
|
||||
canUsePlain := true
|
||||
switch {
|
||||
case !utf8.ValidString(s):
|
||||
if tag == yaml_BINARY_TAG {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = yaml_BINARY_TAG
|
||||
s = encodeBase64(s)
|
||||
case tag == "":
|
||||
// Check to see if it would resolve to a specific
|
||||
// tag when encoded unquoted. If it doesn't,
|
||||
// there's no need to quote it.
|
||||
rtag, _ := resolve("", s)
|
||||
canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
|
||||
}
|
||||
// Note: it's possible for user code to emit invalid YAML
|
||||
// if they explicitly specify a tag and a string containing
|
||||
// text that's incompatible with that tag.
|
||||
switch {
|
||||
case strings.Contains(s, "\n"):
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case canUsePlain:
|
||||
style = yaml_PLAIN_SCALAR_STYLE
|
||||
default:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
e.emitScalar(s, "", tag, style)
|
||||
}
|
||||
|
||||
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||
var s string
|
||||
if in.Bool() {
|
||||
s = "true"
|
||||
} else {
|
||||
s = "false"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatInt(in.Int(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatUint(in.Uint(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) timev(tag string, in reflect.Value) {
|
||||
t := in.Interface().(time.Time)
|
||||
s := t.Format(time.RFC3339Nano)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||
// Issue #352: When formatting, use the precision of the underlying value
|
||||
precision := 64
|
||||
if in.Kind() == reflect.Float32 {
|
||||
precision = 32
|
||||
}
|
||||
|
||||
s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
|
||||
switch s {
|
||||
case "+Inf":
|
||||
s = ".inf"
|
||||
case "-Inf":
|
||||
s = "-.inf"
|
||||
case "NaN":
|
||||
s = ".nan"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) nilv() {
|
||||
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
||||
implicit := tag == ""
|
||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||
e.emit()
|
||||
}
|
||||
1095
vendor/gopkg.in/yaml.v2/parserc.go
generated
vendored
1095
vendor/gopkg.in/yaml.v2/parserc.go
generated
vendored
File diff suppressed because it is too large
Load Diff
412
vendor/gopkg.in/yaml.v2/readerc.go
generated
vendored
412
vendor/gopkg.in/yaml.v2/readerc.go
generated
vendored
@@ -1,412 +0,0 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Set the reader error and return 0.
|
||||
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||
parser.error = yaml_READER_ERROR
|
||||
parser.problem = problem
|
||||
parser.problem_offset = offset
|
||||
parser.problem_value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Byte order marks.
|
||||
const (
|
||||
bom_UTF8 = "\xef\xbb\xbf"
|
||||
bom_UTF16LE = "\xff\xfe"
|
||||
bom_UTF16BE = "\xfe\xff"
|
||||
)
|
||||
|
||||
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||
// Ensure that we had enough bytes in the raw buffer.
|
||||
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the encoding.
|
||||
buf := parser.raw_buffer
|
||||
pos := parser.raw_buffer_pos
|
||||
avail := len(buf) - pos
|
||||
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||
parser.encoding = yaml_UTF16LE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||
parser.encoding = yaml_UTF16BE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
parser.raw_buffer_pos += 3
|
||||
parser.offset += 3
|
||||
} else {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update the raw buffer.
|
||||
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||
size_read := 0
|
||||
|
||||
// Return if the raw buffer is full.
|
||||
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return on EOF.
|
||||
if parser.eof {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move the remaining bytes in the raw buffer to the beginning.
|
||||
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||
}
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||
parser.raw_buffer_pos = 0
|
||||
|
||||
// Call the read handler to fill the buffer.
|
||||
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||
if err == io.EOF {
|
||||
parser.eof = true
|
||||
} else if err != nil {
|
||||
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the buffer contains at least `length` characters.
|
||||
// Return true on success, false on failure.
|
||||
//
|
||||
// The length is supposed to be significantly less that the buffer size.
|
||||
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||
if parser.read_handler == nil {
|
||||
panic("read handler must be set")
|
||||
}
|
||||
|
||||
// [Go] This function was changed to guarantee the requested length size at EOF.
|
||||
// The fact we need to do this is pretty awful, but the description above implies
|
||||
// for that to be the case, and there are tests
|
||||
|
||||
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
// [Go] ACTUALLY! Read the documentation of this function above.
|
||||
// This is just broken. To return true, we need to have the
|
||||
// given length in the buffer. Not doing that means every single
|
||||
// check that calls this function to make sure the buffer has a
|
||||
// given length is Go) panicking; or C) accessing invalid memory.
|
||||
//return true
|
||||
}
|
||||
|
||||
// Return if the buffer contains enough characters.
|
||||
if parser.unread >= length {
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the input encoding if it is not known yet.
|
||||
if parser.encoding == yaml_ANY_ENCODING {
|
||||
if !yaml_parser_determine_encoding(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Move the unread characters to the beginning of the buffer.
|
||||
buffer_len := len(parser.buffer)
|
||||
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||
buffer_len -= parser.buffer_pos
|
||||
parser.buffer_pos = 0
|
||||
} else if parser.buffer_pos == buffer_len {
|
||||
buffer_len = 0
|
||||
parser.buffer_pos = 0
|
||||
}
|
||||
|
||||
// Open the whole buffer for writing, and cut it before returning.
|
||||
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||
|
||||
// Fill the buffer until it has enough characters.
|
||||
first := true
|
||||
for parser.unread < length {
|
||||
|
||||
// Fill the raw buffer if necessary.
|
||||
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return false
|
||||
}
|
||||
}
|
||||
first = false
|
||||
|
||||
// Decode the raw buffer.
|
||||
inner:
|
||||
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||
var value rune
|
||||
var width int
|
||||
|
||||
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||
|
||||
// Decode the next character.
|
||||
switch parser.encoding {
|
||||
case yaml_UTF8_ENCODING:
|
||||
// Decode a UTF-8 character. Check RFC 3629
|
||||
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||
//
|
||||
// The following table (taken from the RFC) is used for
|
||||
// decoding.
|
||||
//
|
||||
// Char. number range | UTF-8 octet sequence
|
||||
// (hexadecimal) | (binary)
|
||||
// --------------------+------------------------------------
|
||||
// 0000 0000-0000 007F | 0xxxxxxx
|
||||
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||
// are prohibited as they are reserved for use with UTF-16
|
||||
// surrogate pairs.
|
||||
|
||||
// Determine the length of the UTF-8 sequence.
|
||||
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
width = 1
|
||||
case octet&0xE0 == 0xC0:
|
||||
width = 2
|
||||
case octet&0xF0 == 0xE0:
|
||||
width = 3
|
||||
case octet&0xF8 == 0xF0:
|
||||
width = 4
|
||||
default:
|
||||
// The leading octet is invalid.
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid leading UTF-8 octet",
|
||||
parser.offset, int(octet))
|
||||
}
|
||||
|
||||
// Check if the raw buffer contains an incomplete character.
|
||||
if width > raw_unread {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-8 octet sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Decode the leading octet.
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
value = rune(octet & 0x7F)
|
||||
case octet&0xE0 == 0xC0:
|
||||
value = rune(octet & 0x1F)
|
||||
case octet&0xF0 == 0xE0:
|
||||
value = rune(octet & 0x0F)
|
||||
case octet&0xF8 == 0xF0:
|
||||
value = rune(octet & 0x07)
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Check and decode the trailing octets.
|
||||
for k := 1; k < width; k++ {
|
||||
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||
|
||||
// Check if the octet is valid.
|
||||
if (octet & 0xC0) != 0x80 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid trailing UTF-8 octet",
|
||||
parser.offset+k, int(octet))
|
||||
}
|
||||
|
||||
// Decode the octet.
|
||||
value = (value << 6) + rune(octet&0x3F)
|
||||
}
|
||||
|
||||
// Check the length of the sequence against the value.
|
||||
switch {
|
||||
case width == 1:
|
||||
case width == 2 && value >= 0x80:
|
||||
case width == 3 && value >= 0x800:
|
||||
case width == 4 && value >= 0x10000:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid length of a UTF-8 sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
|
||||
// Check the range of the value.
|
||||
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid Unicode character",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||
var low, high int
|
||||
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||
low, high = 0, 1
|
||||
} else {
|
||||
low, high = 1, 0
|
||||
}
|
||||
|
||||
// The UTF-16 encoding is not as simple as one might
|
||||
// naively think. Check RFC 2781
|
||||
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||
//
|
||||
// Normally, two subsequent bytes describe a Unicode
|
||||
// character. However a special technique (called a
|
||||
// surrogate pair) is used for specifying character
|
||||
// values larger than 0xFFFF.
|
||||
//
|
||||
// A surrogate pair consists of two pseudo-characters:
|
||||
// high surrogate area (0xD800-0xDBFF)
|
||||
// low surrogate area (0xDC00-0xDFFF)
|
||||
//
|
||||
// The following formulas are used for decoding
|
||||
// and encoding characters using surrogate pairs:
|
||||
//
|
||||
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||
// W1 = 110110yyyyyyyyyy
|
||||
// W2 = 110111xxxxxxxxxx
|
||||
//
|
||||
// where U is the character value, W1 is the high surrogate
|
||||
// area, W2 is the low surrogate area.
|
||||
|
||||
// Check for incomplete UTF-16 character.
|
||||
if raw_unread < 2 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 character",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the character.
|
||||
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||
|
||||
// Check for unexpected low surrogate area.
|
||||
if value&0xFC00 == 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"unexpected low surrogate area",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Check for a high surrogate area.
|
||||
if value&0xFC00 == 0xD800 {
|
||||
width = 4
|
||||
|
||||
// Check for incomplete surrogate pair.
|
||||
if raw_unread < 4 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 surrogate pair",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the next character.
|
||||
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||
|
||||
// Check for a low surrogate area.
|
||||
if value2&0xFC00 != 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"expected low surrogate area",
|
||||
parser.offset+2, int(value2))
|
||||
}
|
||||
|
||||
// Generate the value of the surrogate pair.
|
||||
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||
} else {
|
||||
width = 2
|
||||
}
|
||||
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
// Check if the character is in the allowed range:
|
||||
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||
// | [#x10000-#x10FFFF] (32 bit)
|
||||
switch {
|
||||
case value == 0x09:
|
||||
case value == 0x0A:
|
||||
case value == 0x0D:
|
||||
case value >= 0x20 && value <= 0x7E:
|
||||
case value == 0x85:
|
||||
case value >= 0xA0 && value <= 0xD7FF:
|
||||
case value >= 0xE000 && value <= 0xFFFD:
|
||||
case value >= 0x10000 && value <= 0x10FFFF:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"control characters are not allowed",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Move the raw pointers.
|
||||
parser.raw_buffer_pos += width
|
||||
parser.offset += width
|
||||
|
||||
// Finally put the character into the buffer.
|
||||
if value <= 0x7F {
|
||||
// 0000 0000-0000 007F . 0xxxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(value)
|
||||
buffer_len += 1
|
||||
} else if value <= 0x7FF {
|
||||
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 2
|
||||
} else if value <= 0xFFFF {
|
||||
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 3
|
||||
} else {
|
||||
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 4
|
||||
}
|
||||
|
||||
parser.unread++
|
||||
}
|
||||
|
||||
// On EOF, put NUL into the buffer and return.
|
||||
if parser.eof {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
parser.unread++
|
||||
break
|
||||
}
|
||||
}
|
||||
// [Go] Read the documentation of this function above. To return true,
|
||||
// we need to have the given length in the buffer. Not doing that means
|
||||
// every single check that calls this function to make sure the buffer
|
||||
// has a given length is Go) panicking; or C) accessing invalid memory.
|
||||
// This happens here due to the EOF above breaking early.
|
||||
for buffer_len < length {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
}
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return true
|
||||
}
|
||||
258
vendor/gopkg.in/yaml.v2/resolve.go
generated
vendored
258
vendor/gopkg.in/yaml.v2/resolve.go
generated
vendored
@@ -1,258 +0,0 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resolveMapItem struct {
|
||||
value interface{}
|
||||
tag string
|
||||
}
|
||||
|
||||
var resolveTable = make([]byte, 256)
|
||||
var resolveMap = make(map[string]resolveMapItem)
|
||||
|
||||
func init() {
|
||||
t := resolveTable
|
||||
t[int('+')] = 'S' // Sign
|
||||
t[int('-')] = 'S'
|
||||
for _, c := range "0123456789" {
|
||||
t[int(c)] = 'D' // Digit
|
||||
}
|
||||
for _, c := range "yYnNtTfFoO~" {
|
||||
t[int(c)] = 'M' // In map
|
||||
}
|
||||
t[int('.')] = '.' // Float (potentially in map)
|
||||
|
||||
var resolveMapList = []struct {
|
||||
v interface{}
|
||||
tag string
|
||||
l []string
|
||||
}{
|
||||
{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
|
||||
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
|
||||
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
|
||||
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
|
||||
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
|
||||
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
|
||||
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
|
||||
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||
{"<<", yaml_MERGE_TAG, []string{"<<"}},
|
||||
}
|
||||
|
||||
m := resolveMap
|
||||
for _, item := range resolveMapList {
|
||||
for _, s := range item.l {
|
||||
m[s] = resolveMapItem{item.v, item.tag}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const longTagPrefix = "tag:yaml.org,2002:"
|
||||
|
||||
func shortTag(tag string) string {
|
||||
// TODO This can easily be made faster and produce less garbage.
|
||||
if strings.HasPrefix(tag, longTagPrefix) {
|
||||
return "!!" + tag[len(longTagPrefix):]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func longTag(tag string) string {
|
||||
if strings.HasPrefix(tag, "!!") {
|
||||
return longTagPrefix + tag[2:]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func resolvableTag(tag string) bool {
|
||||
switch tag {
|
||||
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
|
||||
|
||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||
if !resolvableTag(tag) {
|
||||
return tag, in
|
||||
}
|
||||
|
||||
defer func() {
|
||||
switch tag {
|
||||
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
|
||||
return
|
||||
case yaml_FLOAT_TAG:
|
||||
if rtag == yaml_INT_TAG {
|
||||
switch v := out.(type) {
|
||||
case int64:
|
||||
rtag = yaml_FLOAT_TAG
|
||||
out = float64(v)
|
||||
return
|
||||
case int:
|
||||
rtag = yaml_FLOAT_TAG
|
||||
out = float64(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
|
||||
}()
|
||||
|
||||
// Any data is accepted as a !!str or !!binary.
|
||||
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||
hint := byte('N')
|
||||
if in != "" {
|
||||
hint = resolveTable[in[0]]
|
||||
}
|
||||
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
|
||||
// Handle things we can lookup in a map.
|
||||
if item, ok := resolveMap[in]; ok {
|
||||
return item.tag, item.value
|
||||
}
|
||||
|
||||
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||
// are purposefully unsupported here. They're still quoted on
|
||||
// the way out for compatibility with other parser, though.
|
||||
|
||||
switch hint {
|
||||
case 'M':
|
||||
// We've already checked the map above.
|
||||
|
||||
case '.':
|
||||
// Not in the map, so maybe a normal float.
|
||||
floatv, err := strconv.ParseFloat(in, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
|
||||
case 'D', 'S':
|
||||
// Int, float, or timestamp.
|
||||
// Only try values as a timestamp if the value is unquoted or there's an explicit
|
||||
// !!timestamp tag.
|
||||
if tag == "" || tag == yaml_TIMESTAMP_TAG {
|
||||
t, ok := parseTimestamp(in)
|
||||
if ok {
|
||||
return yaml_TIMESTAMP_TAG, t
|
||||
}
|
||||
}
|
||||
|
||||
plain := strings.Replace(in, "_", "", -1)
|
||||
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain, 0, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
if yamlStyleFloat.MatchString(plain) {
|
||||
floatv, err := strconv.ParseFloat(plain, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(plain, "0b") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0b") {
|
||||
intv, err := strconv.ParseInt("-" + plain[3:], 2, 64)
|
||||
if err == nil {
|
||||
if true || intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
|
||||
}
|
||||
}
|
||||
return yaml_STR_TAG, in
|
||||
}
|
||||
|
||||
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||
// as appropriate for the resulting length.
|
||||
func encodeBase64(s string) string {
|
||||
const lineLen = 70
|
||||
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
lines := encLen/lineLen + 1
|
||||
buf := make([]byte, encLen*2+lines)
|
||||
in := buf[0:encLen]
|
||||
out := buf[encLen:]
|
||||
base64.StdEncoding.Encode(in, []byte(s))
|
||||
k := 0
|
||||
for i := 0; i < len(in); i += lineLen {
|
||||
j := i + lineLen
|
||||
if j > len(in) {
|
||||
j = len(in)
|
||||
}
|
||||
k += copy(out[k:], in[i:j])
|
||||
if lines > 1 {
|
||||
out[k] = '\n'
|
||||
k++
|
||||
}
|
||||
}
|
||||
return string(out[:k])
|
||||
}
|
||||
|
||||
// This is a subset of the formats allowed by the regular expression
|
||||
// defined at http://yaml.org/type/timestamp.html.
|
||||
var allowedTimestampFormats = []string{
|
||||
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
|
||||
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
|
||||
"2006-1-2 15:4:5.999999999", // space separated with no time zone
|
||||
"2006-1-2", // date only
|
||||
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
|
||||
// from the set of examples.
|
||||
}
|
||||
|
||||
// parseTimestamp parses s as a timestamp string and
|
||||
// returns the timestamp and reports whether it succeeded.
|
||||
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
|
||||
func parseTimestamp(s string) (time.Time, bool) {
|
||||
// TODO write code to check all the formats supported by
|
||||
// http://yaml.org/type/timestamp.html instead of using time.Parse.
|
||||
|
||||
// Quick check: all date formats start with YYYY-.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if c := s[i]; c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i != 4 || i == len(s) || s[i] != '-' {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, format := range allowedTimestampFormats {
|
||||
if t, err := time.Parse(format, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user