summaryrefslogtreecommitdiffstats
path: root/vendor/gopkg.in
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2017-05-17 16:51:25 -0400
committerGitHub <noreply@github.com>2017-05-17 16:51:25 -0400
commitd103ed6ca97ca5a2669f6cf5fe4b3d2a9c945f26 (patch)
treedbde13123c6add150448f7b75753ac022d862475 /vendor/gopkg.in
parentcd23b8139a9463b67e3096744321f6f4eb0ca40a (diff)
downloadchat-d103ed6ca97ca5a2669f6cf5fe4b3d2a9c945f26.tar.gz
chat-d103ed6ca97ca5a2669f6cf5fe4b3d2a9c945f26.tar.bz2
chat-d103ed6ca97ca5a2669f6cf5fe4b3d2a9c945f26.zip
Upgrading server dependancies (#6431)
Diffstat (limited to 'vendor/gopkg.in')
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE20
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md16
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go279
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword_test.go281
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go26
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go24
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go121
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader_test.go200
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go166
-rw-r--r--vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer_test.go154
-rw-r--r--vendor/gopkg.in/asn1-ber.v1/.travis.yml3
-rw-r--r--vendor/gopkg.in/asn1-ber.v1/LICENSE43
-rw-r--r--vendor/gopkg.in/asn1-ber.v1/length.go14
-rw-r--r--vendor/gopkg.in/asn1-ber.v1/length_test.go45
-rw-r--r--vendor/gopkg.in/gomail.v2/.travis.yml9
-rw-r--r--vendor/gopkg.in/gomail.v2/CHANGELOG.md20
-rw-r--r--vendor/gopkg.in/gomail.v2/CONTRIBUTING.md20
-rw-r--r--vendor/gopkg.in/gomail.v2/LICENSE20
-rw-r--r--vendor/gopkg.in/gomail.v2/README.md92
-rw-r--r--vendor/gopkg.in/gomail.v2/auth.go49
-rw-r--r--vendor/gopkg.in/gomail.v2/auth_test.go100
-rw-r--r--vendor/gopkg.in/gomail.v2/doc.go5
-rw-r--r--vendor/gopkg.in/gomail.v2/example_test.go223
-rw-r--r--vendor/gopkg.in/gomail.v2/message.go322
-rw-r--r--vendor/gopkg.in/gomail.v2/message_test.go745
-rw-r--r--vendor/gopkg.in/gomail.v2/mime.go21
-rw-r--r--vendor/gopkg.in/gomail.v2/mime_go14.go25
-rw-r--r--vendor/gopkg.in/gomail.v2/send.go116
-rw-r--r--vendor/gopkg.in/gomail.v2/send_test.go80
-rw-r--r--vendor/gopkg.in/gomail.v2/smtp.go202
-rw-r--r--vendor/gopkg.in/gomail.v2/smtp_test.go292
-rw-r--r--vendor/gopkg.in/gomail.v2/writeto.go306
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/.travis.yml2
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/CONTRIBUTORS3
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/bulk.go16
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/client.go23
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/client_test.go6
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/indices_put_mapping_test.go2
-rwxr-xr-xvendor/gopkg.in/olivere/elastic.v5/run-es-5.4.0.sh1
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/search_queries_nested.go23
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/search_queries_nested_test.go34
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix.go2
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_example_test.go35
-rw-r--r--vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_test.go2
44 files changed, 4128 insertions, 60 deletions
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
new file mode 100644
index 000000000..5f5c12af7
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Alexandre Cesaro
+
+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.
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
new file mode 100644
index 000000000..98ddf829f
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
@@ -0,0 +1,16 @@
+# quotedprintable
+
+## Introduction
+
+Package quotedprintable implements quoted-printable and message header encoding
+as specified by RFC 2045 and RFC 2047.
+
+It is a copy of the Go 1.5 package `mime/quotedprintable`. It also includes
+the new functions of package `mime` concerning RFC 2047.
+
+This code has minor changes with the standard library code in order to work
+with Go 1.0 and newer.
+
+## Documentation
+
+https://godoc.org/gopkg.in/alexcesaro/quotedprintable.v3
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
new file mode 100644
index 000000000..cfd02617c
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
@@ -0,0 +1,279 @@
+package quotedprintable
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// A WordEncoder is a RFC 2047 encoded-word encoder.
+type WordEncoder byte
+
+const (
+ // BEncoding represents Base64 encoding scheme as defined by RFC 2045.
+ BEncoding = WordEncoder('b')
+ // QEncoding represents the Q-encoding scheme as defined by RFC 2047.
+ QEncoding = WordEncoder('q')
+)
+
+var (
+ errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
+)
+
+// Encode returns the encoded-word form of s. If s is ASCII without special
+// characters, it is returned unchanged. The provided charset is the IANA
+// charset name of s. It is case insensitive.
+func (e WordEncoder) Encode(charset, s string) string {
+ if !needsEncoding(s) {
+ return s
+ }
+ return e.encodeWord(charset, s)
+}
+
+func needsEncoding(s string) bool {
+ for _, b := range s {
+ if (b < ' ' || b > '~') && b != '\t' {
+ return true
+ }
+ }
+ return false
+}
+
+// encodeWord encodes a string into an encoded-word.
+func (e WordEncoder) encodeWord(charset, s string) string {
+ buf := getBuffer()
+ defer putBuffer(buf)
+
+ buf.WriteString("=?")
+ buf.WriteString(charset)
+ buf.WriteByte('?')
+ buf.WriteByte(byte(e))
+ buf.WriteByte('?')
+
+ if e == BEncoding {
+ w := base64.NewEncoder(base64.StdEncoding, buf)
+ io.WriteString(w, s)
+ w.Close()
+ } else {
+ enc := make([]byte, 3)
+ for i := 0; i < len(s); i++ {
+ b := s[i]
+ switch {
+ case b == ' ':
+ buf.WriteByte('_')
+ case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
+ buf.WriteByte(b)
+ default:
+ enc[0] = '='
+ enc[1] = upperhex[b>>4]
+ enc[2] = upperhex[b&0x0f]
+ buf.Write(enc)
+ }
+ }
+ }
+ buf.WriteString("?=")
+ return buf.String()
+}
+
+const upperhex = "0123456789ABCDEF"
+
+// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
+type WordDecoder struct {
+ // 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, iso-8859-1 and us-ascii charsets
+ // are handled by default.
+ // One of the the CharsetReader's result values must be non-nil.
+ CharsetReader func(charset string, input io.Reader) (io.Reader, error)
+}
+
+// Decode decodes an encoded-word. If word is not a valid RFC 2047 encoded-word,
+// word is returned unchanged.
+func (d *WordDecoder) Decode(word string) (string, error) {
+ fields := strings.Split(word, "?") // TODO: remove allocation?
+ if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
+ return "", errInvalidWord
+ }
+
+ content, err := decode(fields[2][0], fields[3])
+ if err != nil {
+ return "", err
+ }
+
+ buf := getBuffer()
+ defer putBuffer(buf)
+
+ if err := d.convert(buf, fields[1], content); err != nil {
+ return "", err
+ }
+
+ return buf.String(), nil
+}
+
+// DecodeHeader decodes all encoded-words of the given string. It returns an
+// error if and only if CharsetReader of d returns an error.
+func (d *WordDecoder) DecodeHeader(header string) (string, error) {
+ // If there is no encoded-word, returns before creating a buffer.
+ i := strings.Index(header, "=?")
+ if i == -1 {
+ return header, nil
+ }
+
+ buf := getBuffer()
+ defer putBuffer(buf)
+
+ buf.WriteString(header[:i])
+ header = header[i:]
+
+ betweenWords := false
+ for {
+ start := strings.Index(header, "=?")
+ if start == -1 {
+ break
+ }
+ cur := start + len("=?")
+
+ i := strings.Index(header[cur:], "?")
+ if i == -1 {
+ break
+ }
+ charset := header[cur : cur+i]
+ cur += i + len("?")
+
+ if len(header) < cur+len("Q??=") {
+ break
+ }
+ encoding := header[cur]
+ cur++
+
+ if header[cur] != '?' {
+ break
+ }
+ cur++
+
+ j := strings.Index(header[cur:], "?=")
+ if j == -1 {
+ break
+ }
+ text := header[cur : cur+j]
+ end := cur + j + len("?=")
+
+ content, err := decode(encoding, text)
+ if err != nil {
+ betweenWords = false
+ buf.WriteString(header[:start+2])
+ header = header[start+2:]
+ continue
+ }
+
+ // Write characters before the encoded-word. White-space and newline
+ // characters separating two encoded-words must be deleted.
+ if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) {
+ buf.WriteString(header[:start])
+ }
+
+ if err := d.convert(buf, charset, content); err != nil {
+ return "", err
+ }
+
+ header = header[end:]
+ betweenWords = true
+ }
+
+ if len(header) > 0 {
+ buf.WriteString(header)
+ }
+
+ return buf.String(), nil
+}
+
+func decode(encoding byte, text string) ([]byte, error) {
+ switch encoding {
+ case 'B', 'b':
+ return base64.StdEncoding.DecodeString(text)
+ case 'Q', 'q':
+ return qDecode(text)
+ }
+ return nil, errInvalidWord
+}
+
+func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error {
+ switch {
+ case strings.EqualFold("utf-8", charset):
+ buf.Write(content)
+ case strings.EqualFold("iso-8859-1", charset):
+ for _, c := range content {
+ buf.WriteRune(rune(c))
+ }
+ case strings.EqualFold("us-ascii", charset):
+ for _, c := range content {
+ if c >= utf8.RuneSelf {
+ buf.WriteRune(unicode.ReplacementChar)
+ } else {
+ buf.WriteByte(c)
+ }
+ }
+ default:
+ if d.CharsetReader == nil {
+ return fmt.Errorf("mime: unhandled charset %q", charset)
+ }
+ r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content))
+ if err != nil {
+ return err
+ }
+ if _, err = buf.ReadFrom(r); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// hasNonWhitespace reports whether s (assumed to be ASCII) contains at least
+// one byte of non-whitespace.
+func hasNonWhitespace(s string) bool {
+ for _, b := range s {
+ switch b {
+ // Encoded-words can only be separated by linear white spaces which does
+ // not include vertical tabs (\v).
+ case ' ', '\t', '\n', '\r':
+ default:
+ return true
+ }
+ }
+ return false
+}
+
+// qDecode decodes a Q encoded string.
+func qDecode(s string) ([]byte, error) {
+ dec := make([]byte, len(s))
+ n := 0
+ for i := 0; i < len(s); i++ {
+ switch c := s[i]; {
+ case c == '_':
+ dec[n] = ' '
+ case c == '=':
+ if i+2 >= len(s) {
+ return nil, errInvalidWord
+ }
+ b, err := readHexByte(s[i+1], s[i+2])
+ if err != nil {
+ return nil, err
+ }
+ dec[n] = b
+ i += 2
+ case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t':
+ dec[n] = c
+ default:
+ return nil, errInvalidWord
+ }
+ n++
+ }
+
+ return dec[:n], nil
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword_test.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword_test.go
new file mode 100644
index 000000000..368794fe7
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword_test.go
@@ -0,0 +1,281 @@
+package quotedprintable
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+func ExampleWordEncoder_Encode() {
+ fmt.Println(QEncoding.Encode("utf-8", "¡Hola, señor!"))
+ fmt.Println(QEncoding.Encode("utf-8", "Hello!"))
+ fmt.Println(BEncoding.Encode("UTF-8", "¡Hola, señor!"))
+ fmt.Println(QEncoding.Encode("ISO-8859-1", "Caf\xE9"))
+ // Output:
+ // =?utf-8?q?=C2=A1Hola,_se=C3=B1or!?=
+ // Hello!
+ // =?UTF-8?b?wqFIb2xhLCBzZcOxb3Ih?=
+ // =?ISO-8859-1?q?Caf=E9?=
+}
+
+func ExampleWordDecoder_Decode() {
+ dec := new(WordDecoder)
+ header, err := dec.Decode("=?utf-8?q?=C2=A1Hola,_se=C3=B1or!?=")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(header)
+
+ dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
+ switch charset {
+ case "x-case":
+ // Fake character set for example.
+ // Real use would integrate with packages such
+ // as code.google.com/p/go-charset
+ content, err := ioutil.ReadAll(input)
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewReader(bytes.ToUpper(content)), nil
+ }
+ return nil, fmt.Errorf("unhandled charset %q", charset)
+ }
+ header, err = dec.Decode("=?x-case?q?hello!?=")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(header)
+ // Output:
+ // ¡Hola, señor!
+ // HELLO!
+}
+
+func ExampleWordDecoder_DecodeHeader() {
+ dec := new(WordDecoder)
+ header, err := dec.DecodeHeader("=?utf-8?q?=C3=89ric?= <eric@example.org>, =?utf-8?q?Ana=C3=AFs?= <anais@example.org>")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(header)
+
+ header, err = dec.DecodeHeader("=?utf-8?q?=C2=A1Hola,?= =?utf-8?q?_se=C3=B1or!?=")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(header)
+
+ dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
+ switch charset {
+ case "x-case":
+ // Fake character set for example.
+ // Real use would integrate with packages such
+ // as code.google.com/p/go-charset
+ content, err := ioutil.ReadAll(input)
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewReader(bytes.ToUpper(content)), nil
+ }
+ return nil, fmt.Errorf("unhandled charset %q", charset)
+ }
+ header, err = dec.DecodeHeader("=?x-case?q?hello_?= =?x-case?q?world!?=")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(header)
+ // Output:
+ // Éric <eric@example.org>, Anaïs <anais@example.org>
+ // ¡Hola, señor!
+ // HELLO WORLD!
+}
+
+func TestEncodeWord(t *testing.T) {
+ utf8, iso88591 := "utf-8", "iso-8859-1"
+ tests := []struct {
+ enc WordEncoder
+ charset string
+ src, exp string
+ }{
+ {QEncoding, utf8, "François-Jérôme", "=?utf-8?q?Fran=C3=A7ois-J=C3=A9r=C3=B4me?="},
+ {BEncoding, utf8, "Café", "=?utf-8?b?Q2Fmw6k=?="},
+ {QEncoding, iso88591, "La Seleção", "=?iso-8859-1?q?La_Sele=C3=A7=C3=A3o?="},
+ {QEncoding, utf8, "", ""},
+ {QEncoding, utf8, "A", "A"},
+ {QEncoding, iso88591, "a", "a"},
+ {QEncoding, utf8, "123 456", "123 456"},
+ {QEncoding, utf8, "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~", "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~"},
+ }
+
+ for _, test := range tests {
+ if s := test.enc.Encode(test.charset, test.src); s != test.exp {
+ t.Errorf("Encode(%q) = %q, want %q", test.src, s, test.exp)
+ }
+ }
+}
+
+func TestDecodeWord(t *testing.T) {
+ tests := []struct {
+ src, exp string
+ hasErr bool
+ }{
+ {"=?UTF-8?Q?=C2=A1Hola,_se=C3=B1or!?=", "¡Hola, señor!", false},
+ {"=?UTF-8?Q?Fran=C3=A7ois-J=C3=A9r=C3=B4me?=", "François-Jérôme", false},
+ {"=?UTF-8?q?ascii?=", "ascii", false},
+ {"=?utf-8?B?QW5kcsOp?=", "André", false},
+ {"=?ISO-8859-1?Q?Rapha=EBl_Dupont?=", "Raphaël Dupont", false},
+ {"=?utf-8?b?IkFudG9uaW8gSm9zw6kiIDxqb3NlQGV4YW1wbGUub3JnPg==?=", `"Antonio José" <jose@example.org>`, false},
+ {"=?UTF-8?A?Test?=", "", true},
+ {"=?UTF-8?Q?A=B?=", "", true},
+ {"=?UTF-8?Q?=A?=", "", true},
+ {"=?UTF-8?A?A?=", "", true},
+ }
+
+ for _, test := range tests {
+ dec := new(WordDecoder)
+ s, err := dec.Decode(test.src)
+ if test.hasErr && err == nil {
+ t.Errorf("Decode(%q) should return an error", test.src)
+ continue
+ }
+ if !test.hasErr && err != nil {
+ t.Errorf("Decode(%q): %v", test.src, err)
+ continue
+ }
+ if s != test.exp {
+ t.Errorf("Decode(%q) = %q, want %q", test.src, s, test.exp)
+ }
+ }
+}
+
+func TestDecodeHeader(t *testing.T) {
+ tests := []struct {
+ src, exp string
+ }{
+ {"=?UTF-8?Q?=C2=A1Hola,_se=C3=B1or!?=", "¡Hola, señor!"},
+ {"=?UTF-8?Q?Fran=C3=A7ois-J=C3=A9r=C3=B4me?=", "François-Jérôme"},
+ {"=?UTF-8?q?ascii?=", "ascii"},
+ {"=?utf-8?B?QW5kcsOp?=", "André"},
+ {"=?ISO-8859-1?Q?Rapha=EBl_Dupont?=", "Raphaël Dupont"},
+ {"Jean", "Jean"},
+ {"=?utf-8?b?IkFudG9uaW8gSm9zw6kiIDxqb3NlQGV4YW1wbGUub3JnPg==?=", `"Antonio José" <jose@example.org>`},
+ {"=?UTF-8?A?Test?=", "=?UTF-8?A?Test?="},
+ {"=?UTF-8?Q?A=B?=", "=?UTF-8?Q?A=B?="},
+ {"=?UTF-8?Q?=A?=", "=?UTF-8?Q?=A?="},
+ {"=?UTF-8?A?A?=", "=?UTF-8?A?A?="},
+ // Incomplete words
+ {"=?", "=?"},
+ {"=?UTF-8?", "=?UTF-8?"},
+ {"=?UTF-8?=", "=?UTF-8?="},
+ {"=?UTF-8?Q", "=?UTF-8?Q"},
+ {"=?UTF-8?Q?", "=?UTF-8?Q?"},
+ {"=?UTF-8?Q?=", "=?UTF-8?Q?="},
+ {"=?UTF-8?Q?A", "=?UTF-8?Q?A"},
+ {"=?UTF-8?Q?A?", "=?UTF-8?Q?A?"},
+ // Tests from RFC 2047
+ {"=?ISO-8859-1?Q?a?=", "a"},
+ {"=?ISO-8859-1?Q?a?= b", "a b"},
+ {"=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=", "ab"},
+ {"=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=", "ab"},
+ {"=?ISO-8859-1?Q?a?= \r\n\t =?ISO-8859-1?Q?b?=", "ab"},
+ {"=?ISO-8859-1?Q?a_b?=", "a b"},
+ }
+
+ for _, test := range tests {
+ dec := new(WordDecoder)
+ s, err := dec.DecodeHeader(test.src)
+ if err != nil {
+ t.Errorf("DecodeHeader(%q): %v", test.src, err)
+ }
+ if s != test.exp {
+ t.Errorf("DecodeHeader(%q) = %q, want %q", test.src, s, test.exp)
+ }
+ }
+}
+
+func TestCharsetDecoder(t *testing.T) {
+ tests := []struct {
+ src string
+ want string
+ charsets []string
+ content []string
+ }{
+ {"=?utf-8?b?Q2Fmw6k=?=", "Café", nil, nil},
+ {"=?ISO-8859-1?Q?caf=E9?=", "café", nil, nil},
+ {"=?US-ASCII?Q?foo_bar?=", "foo bar", nil, nil},
+ {"=?utf-8?Q?=?=", "=?utf-8?Q?=?=", nil, nil},
+ {"=?utf-8?Q?=A?=", "=?utf-8?Q?=A?=", nil, nil},
+ {
+ "=?ISO-8859-15?Q?f=F5=F6?= =?windows-1252?Q?b=E0r?=",
+ "f\xf5\xf6b\xe0r",
+ []string{"iso-8859-15", "windows-1252"},
+ []string{"f\xf5\xf6", "b\xe0r"},
+ },
+ }
+
+ for _, test := range tests {
+ i := 0
+ dec := &WordDecoder{
+ CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
+ if charset != test.charsets[i] {
+ t.Errorf("DecodeHeader(%q), got charset %q, want %q", test.src, charset, test.charsets[i])
+ }
+ content, err := ioutil.ReadAll(input)
+ if err != nil {
+ t.Errorf("DecodeHeader(%q), error in reader: %v", test.src, err)
+ }
+ got := string(content)
+ if got != test.content[i] {
+ t.Errorf("DecodeHeader(%q), got content %q, want %q", test.src, got, test.content[i])
+ }
+ i++
+
+ return strings.NewReader(got), nil
+ },
+ }
+ got, err := dec.DecodeHeader(test.src)
+ if err != nil {
+ t.Errorf("DecodeHeader(%q): %v", test.src, err)
+ }
+ if got != test.want {
+ t.Errorf("DecodeHeader(%q) = %q, want %q", test.src, got, test.want)
+ }
+ }
+}
+
+func TestCharsetDecoderError(t *testing.T) {
+ dec := &WordDecoder{
+ CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
+ return nil, errors.New("Test error")
+ },
+ }
+
+ if _, err := dec.DecodeHeader("=?charset?Q?foo?="); err == nil {
+ t.Error("DecodeHeader should return an error")
+ }
+}
+
+func BenchmarkQEncodeWord(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ QEncoding.Encode("UTF-8", "¡Hola, señor!")
+ }
+}
+
+func BenchmarkQDecodeWord(b *testing.B) {
+ dec := new(WordDecoder)
+
+ for i := 0; i < b.N; i++ {
+ dec.Decode("=?utf-8?q?=C2=A1Hola,_se=C3=B1or!?=")
+ }
+}
+
+func BenchmarkQDecodeHeader(b *testing.B) {
+ dec := new(WordDecoder)
+
+ for i := 0; i < b.N; i++ {
+ dec.Decode("=?utf-8?q?=C2=A1Hola,_se=C3=B1or!?=")
+ }
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
new file mode 100644
index 000000000..24283c52f
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
@@ -0,0 +1,26 @@
+// +build go1.3
+
+package quotedprintable
+
+import (
+ "bytes"
+ "sync"
+)
+
+var bufPool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+}
+
+func getBuffer() *bytes.Buffer {
+ return bufPool.Get().(*bytes.Buffer)
+}
+
+func putBuffer(buf *bytes.Buffer) {
+ if buf.Len() > 1024 {
+ return
+ }
+ buf.Reset()
+ bufPool.Put(buf)
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
new file mode 100644
index 000000000..d335b4ab5
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
@@ -0,0 +1,24 @@
+// +build !go1.3
+
+package quotedprintable
+
+import "bytes"
+
+var ch = make(chan *bytes.Buffer, 32)
+
+func getBuffer() *bytes.Buffer {
+ select {
+ case buf := <-ch:
+ return buf
+ default:
+ }
+ return new(bytes.Buffer)
+}
+
+func putBuffer(buf *bytes.Buffer) {
+ buf.Reset()
+ select {
+ case ch <- buf:
+ default:
+ }
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
new file mode 100644
index 000000000..955edca29
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
@@ -0,0 +1,121 @@
+// Package quotedprintable implements quoted-printable encoding as specified by
+// RFC 2045.
+package quotedprintable
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+)
+
+// Reader is a quoted-printable decoder.
+type Reader struct {
+ br *bufio.Reader
+ rerr error // last read error
+ line []byte // to be consumed before more of br
+}
+
+// NewReader returns a quoted-printable reader, decoding from r.
+func NewReader(r io.Reader) *Reader {
+ return &Reader{
+ br: bufio.NewReader(r),
+ }
+}
+
+func fromHex(b byte) (byte, error) {
+ switch {
+ case b >= '0' && b <= '9':
+ return b - '0', nil
+ case b >= 'A' && b <= 'F':
+ return b - 'A' + 10, nil
+ // Accept badly encoded bytes.
+ case b >= 'a' && b <= 'f':
+ return b - 'a' + 10, nil
+ }
+ return 0, fmt.Errorf("quotedprintable: invalid hex byte 0x%02x", b)
+}
+
+func readHexByte(a, b byte) (byte, error) {
+ var hb, lb byte
+ var err error
+ if hb, err = fromHex(a); err != nil {
+ return 0, err
+ }
+ if lb, err = fromHex(b); err != nil {
+ return 0, err
+ }
+ return hb<<4 | lb, nil
+}
+
+func isQPDiscardWhitespace(r rune) bool {
+ switch r {
+ case '\n', '\r', ' ', '\t':
+ return true
+ }
+ return false
+}
+
+var (
+ crlf = []byte("\r\n")
+ lf = []byte("\n")
+ softSuffix = []byte("=")
+)
+
+// Read reads and decodes quoted-printable data from the underlying reader.
+func (r *Reader) Read(p []byte) (n int, err error) {
+ // Deviations from RFC 2045:
+ // 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
+ // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
+ // with other broken QP encoders & decoders.
+ for len(p) > 0 {
+ if len(r.line) == 0 {
+ if r.rerr != nil {
+ return n, r.rerr
+ }
+ r.line, r.rerr = r.br.ReadSlice('\n')
+
+ // Does the line end in CRLF instead of just LF?
+ hasLF := bytes.HasSuffix(r.line, lf)
+ hasCR := bytes.HasSuffix(r.line, crlf)
+ wholeLine := r.line
+ r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace)
+ if bytes.HasSuffix(r.line, softSuffix) {
+ rightStripped := wholeLine[len(r.line):]
+ r.line = r.line[:len(r.line)-1]
+ if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) {
+ r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped)
+ }
+ } else if hasLF {
+ if hasCR {
+ r.line = append(r.line, '\r', '\n')
+ } else {
+ r.line = append(r.line, '\n')
+ }
+ }
+ continue
+ }
+ b := r.line[0]
+
+ switch {
+ case b == '=':
+ if len(r.line[1:]) < 2 {
+ return n, io.ErrUnexpectedEOF
+ }
+ b, err = readHexByte(r.line[1], r.line[2])
+ if err != nil {
+ return n, err
+ }
+ r.line = r.line[2:] // 2 of the 3; other 1 is done below
+ case b == '\t' || b == '\r' || b == '\n':
+ break
+ case b < ' ' || b > '~':
+ return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b)
+ }
+ p[0] = b
+ p = p[1:]
+ r.line = r.line[1:]
+ n++
+ }
+ return n, nil
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader_test.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader_test.go
new file mode 100644
index 000000000..01ba8f75b
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader_test.go
@@ -0,0 +1,200 @@
+package quotedprintable
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os/exec"
+ "regexp"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestReader(t *testing.T) {
+ tests := []struct {
+ in, want string
+ err interface{}
+ }{
+ {in: "", want: ""},
+ {in: "foo bar", want: "foo bar"},
+ {in: "foo bar=3D", want: "foo bar="},
+ {in: "foo bar=3d", want: "foo bar="}, // lax.
+ {in: "foo bar=\n", want: "foo bar"},
+ {in: "foo bar\n", want: "foo bar\n"}, // somewhat lax.
+ {in: "foo bar=0", want: "foo bar", err: io.ErrUnexpectedEOF},
+ {in: "foo bar=0D=0A", want: "foo bar\r\n"},
+ {in: " A B \r\n C ", want: " A B\r\n C"},
+ {in: " A B =\r\n C ", want: " A B C"},
+ {in: " A B =\n C ", want: " A B C"}, // lax. treating LF as CRLF
+ {in: "foo=\nbar", want: "foobar"},
+ {in: "foo\x00bar", want: "foo", err: "quotedprintable: invalid unescaped byte 0x00 in body"},
+ {in: "foo bar\xff", want: "foo bar", err: "quotedprintable: invalid unescaped byte 0xff in body"},
+
+ // Equal sign.
+ {in: "=3D30\n", want: "=30\n"},
+ {in: "=00=FF0=\n", want: "\x00\xff0"},
+
+ // Trailing whitespace
+ {in: "foo \n", want: "foo\n"},
+ {in: "foo \n\nfoo =\n\nfoo=20\n\n", want: "foo\n\nfoo \nfoo \n\n"},
+
+ // Tests that we allow bare \n and \r through, despite it being strictly
+ // not permitted per RFC 2045, Section 6.7 Page 22 bullet (4).
+ {in: "foo\nbar", want: "foo\nbar"},
+ {in: "foo\rbar", want: "foo\rbar"},
+ {in: "foo\r\nbar", want: "foo\r\nbar"},
+
+ // Different types of soft line-breaks.
+ {in: "foo=\r\nbar", want: "foobar"},
+ {in: "foo=\nbar", want: "foobar"},
+ {in: "foo=\rbar", want: "foo", err: "quotedprintable: invalid hex byte 0x0d"},
+ {in: "foo=\r\r\r \nbar", want: "foo", err: `quotedprintable: invalid bytes after =: "\r\r\r \n"`},
+
+ // Example from RFC 2045:
+ {in: "Now's the time =\n" + "for all folk to come=\n" + " to the aid of their country.",
+ want: "Now's the time for all folk to come to the aid of their country."},
+ }
+ for _, tt := range tests {
+ var buf bytes.Buffer
+ _, err := io.Copy(&buf, NewReader(strings.NewReader(tt.in)))
+ if got := buf.String(); got != tt.want {
+ t.Errorf("for %q, got %q; want %q", tt.in, got, tt.want)
+ }
+ switch verr := tt.err.(type) {
+ case nil:
+ if err != nil {
+ t.Errorf("for %q, got unexpected error: %v", tt.in, err)
+ }
+ case string:
+ if got := fmt.Sprint(err); got != verr {
+ t.Errorf("for %q, got error %q; want %q", tt.in, got, verr)
+ }
+ case error:
+ if err != verr {
+ t.Errorf("for %q, got error %q; want %q", tt.in, err, verr)
+ }
+ }
+ }
+
+}
+
+func everySequence(base, alpha string, length int, fn func(string)) {
+ if len(base) == length {
+ fn(base)
+ return
+ }
+ for i := 0; i < len(alpha); i++ {
+ everySequence(base+alpha[i:i+1], alpha, length, fn)
+ }
+}
+
+var useQprint = flag.Bool("qprint", false, "Compare against the 'qprint' program.")
+
+var badSoftRx = regexp.MustCompile(`=([^\r\n]+?\n)|([^\r\n]+$)|(\r$)|(\r[^\n]+\n)|( \r\n)`)
+
+func TestExhaustive(t *testing.T) {
+ if *useQprint {
+ _, err := exec.LookPath("qprint")
+ if err != nil {
+ t.Fatalf("Error looking for qprint: %v", err)
+ }
+ }
+
+ var buf bytes.Buffer
+ res := make(map[string]int)
+ everySequence("", "0A \r\n=", 6, func(s string) {
+ if strings.HasSuffix(s, "=") || strings.Contains(s, "==") {
+ return
+ }
+ buf.Reset()
+ _, err := io.Copy(&buf, NewReader(strings.NewReader(s)))
+ if err != nil {
+ errStr := err.Error()
+ if strings.Contains(errStr, "invalid bytes after =:") {
+ errStr = "invalid bytes after ="
+ }
+ res[errStr]++
+ if strings.Contains(errStr, "invalid hex byte ") {
+ if strings.HasSuffix(errStr, "0x20") && (strings.Contains(s, "=0 ") || strings.Contains(s, "=A ") || strings.Contains(s, "= ")) {
+ return
+ }
+ if strings.HasSuffix(errStr, "0x3d") && (strings.Contains(s, "=0=") || strings.Contains(s, "=A=")) {
+ return
+ }
+ if strings.HasSuffix(errStr, "0x0a") || strings.HasSuffix(errStr, "0x0d") {
+ // bunch of cases; since whitespace at the end of a line before \n is removed.
+ return
+ }
+ }
+ if strings.Contains(errStr, "unexpected EOF") {
+ return
+ }
+ if errStr == "invalid bytes after =" && badSoftRx.MatchString(s) {
+ return
+ }
+ t.Errorf("decode(%q) = %v", s, err)
+ return
+ }
+ if *useQprint {
+ cmd := exec.Command("qprint", "-d")
+ cmd.Stdin = strings.NewReader(s)
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ panic(err)
+ }
+ qpres := make(chan interface{}, 2)
+ go func() {
+ br := bufio.NewReader(stderr)
+ s, _ := br.ReadString('\n')
+ if s != "" {
+ qpres <- errors.New(s)
+ if cmd.Process != nil {
+ // It can get stuck on invalid input, like:
+ // echo -n "0000= " | qprint -d
+ cmd.Process.Kill()
+ }
+ }
+ }()
+ go func() {
+ want, err := cmd.Output()
+ if err == nil {
+ qpres <- want
+ }
+ }()
+ select {
+ case got := <-qpres:
+ if want, ok := got.([]byte); ok {
+ if string(want) != buf.String() {
+ t.Errorf("go decode(%q) = %q; qprint = %q", s, want, buf.String())
+ }
+ } else {
+ t.Logf("qprint -d(%q) = %v", s, got)
+ }
+ case <-time.After(5 * time.Second):
+ t.Logf("qprint timeout on %q", s)
+ }
+ }
+ res["OK"]++
+ })
+ var outcomes []string
+ for k, v := range res {
+ outcomes = append(outcomes, fmt.Sprintf("%v: %d", k, v))
+ }
+ sort.Strings(outcomes)
+ got := strings.Join(outcomes, "\n")
+ want := `OK: 21576
+invalid bytes after =: 3397
+quotedprintable: invalid hex byte 0x0a: 1400
+quotedprintable: invalid hex byte 0x0d: 2700
+quotedprintable: invalid hex byte 0x20: 2490
+quotedprintable: invalid hex byte 0x3d: 440
+unexpected EOF: 3122`
+ if got != want {
+ t.Errorf("Got:\n%s\nWant:\n%s", got, want)
+ }
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
new file mode 100644
index 000000000..43359d51d
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
@@ -0,0 +1,166 @@
+package quotedprintable
+
+import "io"
+
+const lineMaxLen = 76
+
+// A Writer is a quoted-printable writer that implements io.WriteCloser.
+type Writer struct {
+ // Binary mode treats the writer's input as pure binary and processes end of
+ // line bytes as binary data.
+ Binary bool
+
+ w io.Writer
+ i int
+ line [78]byte
+ cr bool
+}
+
+// NewWriter returns a new Writer that writes to w.
+func NewWriter(w io.Writer) *Writer {
+ return &Writer{w: w}
+}
+
+// Write encodes p using quoted-printable encoding and writes it to the
+// underlying io.Writer. It limits line length to 76 characters. The encoded
+// bytes are not necessarily flushed until the Writer is closed.
+func (w *Writer) Write(p []byte) (n int, err error) {
+ for i, b := range p {
+ switch {
+ // Simple writes are done in batch.
+ case b >= '!' && b <= '~' && b != '=':
+ continue
+ case isWhitespace(b) || !w.Binary && (b == '\n' || b == '\r'):
+ continue
+ }
+
+ if i > n {
+ if err := w.write(p[n:i]); err != nil {
+ return n, err
+ }
+ n = i
+ }
+
+ if err := w.encode(b); err != nil {
+ return n, err
+ }
+ n++
+ }
+
+ if n == len(p) {
+ return n, nil
+ }
+
+ if err := w.write(p[n:]); err != nil {
+ return n, err
+ }
+
+ return len(p), nil
+}
+
+// Close closes the Writer, flushing any unwritten data to the underlying
+// io.Writer, but does not close the underlying io.Writer.
+func (w *Writer) Close() error {
+ if err := w.checkLastByte(); err != nil {
+ return err
+ }
+
+ return w.flush()
+}
+
+// write limits text encoded in quoted-printable to 76 characters per line.
+func (w *Writer) write(p []byte) error {
+ for _, b := range p {
+ if b == '\n' || b == '\r' {
+ // If the previous byte was \r, the CRLF has already been inserted.
+ if w.cr && b == '\n' {
+ w.cr = false
+ continue
+ }
+
+ if b == '\r' {
+ w.cr = true
+ }
+
+ if err := w.checkLastByte(); err != nil {
+ return err
+ }
+ if err := w.insertCRLF(); err != nil {
+ return err
+ }
+ continue
+ }
+
+ if w.i == lineMaxLen-1 {
+ if err := w.insertSoftLineBreak(); err != nil {
+ return err
+ }
+ }
+
+ w.line[w.i] = b
+ w.i++
+ w.cr = false
+ }
+
+ return nil
+}
+
+func (w *Writer) encode(b byte) error {
+ if lineMaxLen-1-w.i < 3 {
+ if err := w.insertSoftLineBreak(); err != nil {
+ return err
+ }
+ }
+
+ w.line[w.i] = '='
+ w.line[w.i+1] = upperhex[b>>4]
+ w.line[w.i+2] = upperhex[b&0x0f]
+ w.i += 3
+
+ return nil
+}
+
+// checkLastByte encodes the last buffered byte if it is a space or a tab.
+func (w *Writer) checkLastByte() error {
+ if w.i == 0 {
+ return nil
+ }
+
+ b := w.line[w.i-1]
+ if isWhitespace(b) {
+ w.i--
+ if err := w.encode(b); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (w *Writer) insertSoftLineBreak() error {
+ w.line[w.i] = '='
+ w.i++
+
+ return w.insertCRLF()
+}
+
+func (w *Writer) insertCRLF() error {
+ w.line[w.i] = '\r'
+ w.line[w.i+1] = '\n'
+ w.i += 2
+
+ return w.flush()
+}
+
+func (w *Writer) flush() error {
+ if _, err := w.w.Write(w.line[:w.i]); err != nil {
+ return err
+ }
+
+ w.i = 0
+ return nil
+}
+
+func isWhitespace(b byte) bool {
+ return b == ' ' || b == '\t'
+}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer_test.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer_test.go
new file mode 100644
index 000000000..ed8507289
--- /dev/null
+++ b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer_test.go
@@ -0,0 +1,154 @@
+package quotedprintable
+
+import (
+ "bytes"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+func TestWriter(t *testing.T) {
+ testWriter(t, false)
+}
+
+func TestWriterBinary(t *testing.T) {
+ testWriter(t, true)
+}
+
+func testWriter(t *testing.T, binary bool) {
+ tests := []struct {
+ in, want, wantB string
+ }{
+ {in: "", want: ""},
+ {in: "foo bar", want: "foo bar"},
+ {in: "foo bar=", want: "foo bar=3D"},
+ {in: "foo bar\r", want: "foo bar\r\n", wantB: "foo bar=0D"},
+ {in: "foo bar\r\r", want: "foo bar\r\n\r\n", wantB: "foo bar=0D=0D"},
+ {in: "foo bar\n", want: "foo bar\r\n", wantB: "foo bar=0A"},
+ {in: "foo bar\r\n", want: "foo bar\r\n", wantB: "foo bar=0D=0A"},
+ {in: "foo bar\r\r\n", want: "foo bar\r\n\r\n", wantB: "foo bar=0D=0D=0A"},
+ {in: "foo bar ", want: "foo bar=20"},
+ {in: "foo bar\t", want: "foo bar=09"},
+ {in: "foo bar ", want: "foo bar =20"},
+ {in: "foo bar \n", want: "foo bar=20\r\n", wantB: "foo bar =0A"},
+ {in: "foo bar \r", want: "foo bar=20\r\n", wantB: "foo bar =0D"},
+ {in: "foo bar \r\n", want: "foo bar=20\r\n", wantB: "foo bar =0D=0A"},
+ {in: "foo bar \n", want: "foo bar =20\r\n", wantB: "foo bar =0A"},
+ {in: "foo bar \n ", want: "foo bar =20\r\n=20", wantB: "foo bar =0A=20"},
+ {in: "¡Hola Señor!", want: "=C2=A1Hola Se=C3=B1or!"},
+ {
+ in: "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~",
+ want: "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~",
+ },
+ {
+ in: strings.Repeat("a", 75),
+ want: strings.Repeat("a", 75),
+ },
+ {
+ in: strings.Repeat("a", 76),
+ want: strings.Repeat("a", 75) + "=\r\na",
+ },
+ {
+ in: strings.Repeat("a", 72) + "=",
+ want: strings.Repeat("a", 72) + "=3D",
+ },
+ {
+ in: strings.Repeat("a", 73) + "=",
+ want: strings.Repeat("a", 73) + "=\r\n=3D",
+ },
+ {
+ in: strings.Repeat("a", 74) + "=",
+ want: strings.Repeat("a", 74) + "=\r\n=3D",
+ },
+ {
+ in: strings.Repeat("a", 75) + "=",
+ want: strings.Repeat("a", 75) + "=\r\n=3D",
+ },
+ {
+ in: strings.Repeat(" ", 73),
+ want: strings.Repeat(" ", 72) + "=20",
+ },
+ {
+ in: strings.Repeat(" ", 74),
+ want: strings.Repeat(" ", 73) + "=\r\n=20",
+ },
+ {
+ in: strings.Repeat(" ", 75),
+ want: strings.Repeat(" ", 74) + "=\r\n=20",
+ },
+ {
+ in: strings.Repeat(" ", 76),
+ want: strings.Repeat(" ", 75) + "=\r\n=20",
+ },
+ {
+ in: strings.Repeat(" ", 77),
+ want: strings.Repeat(" ", 75) + "=\r\n =20",
+ },
+ }
+
+ for _, tt := range tests {
+ buf := new(bytes.Buffer)
+ w := NewWriter(buf)
+
+ want := tt.want
+ if binary {
+ w.Binary = true
+ if tt.wantB != "" {
+ want = tt.wantB
+ }
+ }
+
+ if _, err := w.Write([]byte(tt.in)); err != nil {
+ t.Errorf("Write(%q): %v", tt.in, err)
+ continue
+ }
+ if err := w.Close(); err != nil {
+ t.Errorf("Close(): %v", err)
+ continue
+ }
+ got := buf.String()
+ if got != want {
+ t.Errorf("Write(%q), got:\n%q\nwant:\n%q", tt.in, got, want)
+ }
+ }
+}
+
+func TestRoundTrip(t *testing.T) {
+ buf := new(bytes.Buffer)
+ w := NewWriter(buf)
+ if _, err := w.Write(testMsg); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+
+ r := NewReader(buf)
+ gotBytes, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Fatalf("Error while reading from Reader: %v", err)
+ }
+ got := string(gotBytes)
+ if got != string(testMsg) {
+ t.Errorf("Encoding and decoding changed the message, got:\n%s", got)
+ }
+}
+
+// From http://fr.wikipedia.org/wiki/Quoted-Printable
+var testMsg = []byte("Quoted-Printable (QP) est un format d'encodage de données codées sur 8 bits, qui utilise exclusivement les caractères alphanumériques imprimables du code ASCII (7 bits).\r\n" +
+ "\r\n" +
+ "En effet, les différents codages comprennent de nombreux caractères qui ne sont pas représentables en ASCII (par exemple les caractères accentués), ainsi que des caractères dits « non-imprimables ».\r\n" +
+ "\r\n" +
+ "L'encodage Quoted-Printable permet de remédier à ce problème, en procédant de la manière suivante :\r\n" +
+ "\r\n" +
+ "Un octet correspondant à un caractère imprimable de l'ASCII sauf le signe égal (donc un caractère de code ASCII entre 33 et 60 ou entre 62 et 126) ou aux caractères de saut de ligne (codes ASCII 13 et 10) ou une suite de tabulations et espaces non situées en fin de ligne (de codes ASCII respectifs 9 et 32) est représenté tel quel.\r\n" +
+ "Un octet qui ne correspond pas à la définition ci-dessus (caractère non imprimable de l'ASCII, tabulation ou espaces non suivies d'un caractère imprimable avant la fin de la ligne ou signe égal) est représenté par un signe égal, suivi de son numéro, exprimé en hexadécimal.\r\n" +
+ "Enfin, un signe égal suivi par un saut de ligne (donc la suite des trois caractères de codes ASCII 61, 13 et 10) peut être inséré n'importe où, afin de limiter la taille des lignes produites si nécessaire. Une limite de 76 caractères par ligne est généralement respectée.\r\n")
+
+func BenchmarkWriter(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ w := NewWriter(ioutil.Discard)
+ w.Write(testMsg)
+ w.Close()
+ }
+}
diff --git a/vendor/gopkg.in/asn1-ber.v1/.travis.yml b/vendor/gopkg.in/asn1-ber.v1/.travis.yml
index 44aa48b87..53063d075 100644
--- a/vendor/gopkg.in/asn1-ber.v1/.travis.yml
+++ b/vendor/gopkg.in/asn1-ber.v1/.travis.yml
@@ -4,6 +4,9 @@ go:
- 1.3
- 1.4
- 1.5
+ - 1.6
+ - 1.7
+ - 1.8
- tip
go_import_path: gopkg.in/asn-ber.v1
install:
diff --git a/vendor/gopkg.in/asn1-ber.v1/LICENSE b/vendor/gopkg.in/asn1-ber.v1/LICENSE
index 744875676..23f942534 100644
--- a/vendor/gopkg.in/asn1-ber.v1/LICENSE
+++ b/vendor/gopkg.in/asn1-ber.v1/LICENSE
@@ -1,27 +1,22 @@
-Copyright (c) 2012 The Go Authors. All rights reserved.
+The MIT License (MIT)
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
+Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
+Portions copyright (c) 2015-2016 go-asn1-ber Authors
- * 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.
+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:
-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.
+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.
diff --git a/vendor/gopkg.in/asn1-ber.v1/length.go b/vendor/gopkg.in/asn1-ber.v1/length.go
index 8e2ae4ddd..750e8f443 100644
--- a/vendor/gopkg.in/asn1-ber.v1/length.go
+++ b/vendor/gopkg.in/asn1-ber.v1/length.go
@@ -38,6 +38,9 @@ func readLength(reader io.Reader) (length int, read int, err error) {
if lengthBytes > 8 {
return 0, read, errors.New("long-form length overflow")
}
+
+ // Accumulate into a 64-bit variable
+ var length64 int64
for i := 0; i < lengthBytes; i++ {
b, err = readByte(reader)
if err != nil {
@@ -49,8 +52,15 @@ func readLength(reader io.Reader) (length int, read int, err error) {
read++
// x.600, 8.1.3.5
- length <<= 8
- length |= int(b)
+ length64 <<= 8
+ length64 |= int64(b)
+ }
+
+ // Cast to a platform-specific integer
+ length = int(length64)
+ // Ensure we didn't overflow
+ if int64(length) != length64 {
+ return 0, read, errors.New("long-form length overflow")
}
default:
diff --git a/vendor/gopkg.in/asn1-ber.v1/length_test.go b/vendor/gopkg.in/asn1-ber.v1/length_test.go
index afe0e8037..289510a8d 100644
--- a/vendor/gopkg.in/asn1-ber.v1/length_test.go
+++ b/vendor/gopkg.in/asn1-ber.v1/length_test.go
@@ -11,7 +11,7 @@ func TestReadLength(t *testing.T) {
testcases := map[string]struct {
Data []byte
- ExpectedLength int
+ ExpectedLength int64
ExpectedBytesRead int
ExpectedError string
}{
@@ -68,7 +68,19 @@ func TestReadLength(t *testing.T) {
ExpectedLength: 127,
ExpectedBytesRead: 2,
},
- "long-definite-form max length": {
+ "long-definite-form max length (32-bit)": {
+ Data: []byte{
+ LengthLongFormBitmask | 4,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ },
+ ExpectedLength: math.MaxInt32,
+ ExpectedBytesRead: 5,
+ },
+ "long-definite-form max length (64-bit)": {
Data: []byte{
LengthLongFormBitmask | 8,
0x7F,
@@ -86,6 +98,11 @@ func TestReadLength(t *testing.T) {
}
for k, tc := range testcases {
+ // Skip tests requiring 64-bit integers on platforms that don't support them
+ if tc.ExpectedLength != int64(int(tc.ExpectedLength)) {
+ continue
+ }
+
reader := bytes.NewBuffer(tc.Data)
length, read, err := readLength(reader)
@@ -104,7 +121,7 @@ func TestReadLength(t *testing.T) {
t.Errorf("%s: expected read %d, got %d", k, tc.ExpectedBytesRead, read)
}
- if length != tc.ExpectedLength {
+ if int64(length) != tc.ExpectedLength {
t.Errorf("%s: expected length %d, got %d", k, tc.ExpectedLength, length)
}
}
@@ -112,7 +129,7 @@ func TestReadLength(t *testing.T) {
func TestEncodeLength(t *testing.T) {
testcases := map[string]struct {
- Length int
+ Length int64
ExpectedBytes []byte
}{
"0": {
@@ -133,7 +150,18 @@ func TestEncodeLength(t *testing.T) {
ExpectedBytes: []byte{LengthLongFormBitmask | 1, 128},
},
- "max long-form length": {
+ "max long-form length (32-bit)": {
+ Length: math.MaxInt32,
+ ExpectedBytes: []byte{
+ LengthLongFormBitmask | 4,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ },
+ },
+
+ "max long-form length (64-bit)": {
Length: math.MaxInt64,
ExpectedBytes: []byte{
LengthLongFormBitmask | 8,
@@ -150,7 +178,12 @@ func TestEncodeLength(t *testing.T) {
}
for k, tc := range testcases {
- b := encodeLength(tc.Length)
+ // Skip tests requiring 64-bit integers on platforms that don't support them
+ if tc.Length != int64(int(tc.Length)) {
+ continue
+ }
+
+ b := encodeLength(int(tc.Length))
if bytes.Compare(tc.ExpectedBytes, b) != 0 {
t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedBytes, b)
}
diff --git a/vendor/gopkg.in/gomail.v2/.travis.yml b/vendor/gopkg.in/gomail.v2/.travis.yml
new file mode 100644
index 000000000..48915e737
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - 1.2
+ - 1.3
+ - 1.4
+ - 1.5
+ - 1.6
+ - tip
diff --git a/vendor/gopkg.in/gomail.v2/CHANGELOG.md b/vendor/gopkg.in/gomail.v2/CHANGELOG.md
new file mode 100644
index 000000000..a797ab4c0
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Change Log
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [2.0.0] - 2015-09-02
+
+- Mailer has been removed. It has been replaced by Dialer and Sender.
+- `File` type and the `CreateFile` and `OpenFile` functions have been removed.
+- `Message.Attach` and `Message.Embed` have a new signature.
+- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
+instead.
+- `Message.Export` has been removed. `Message.WriteTo` can be used instead.
+- `Message.DelHeader` has been removed.
+- The `Bcc` header field is no longer sent. It is far more simpler and
+efficient: the same message is sent to all recipients instead of sending a
+different email to each Bcc address.
+- LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN
+authentication mechanism when needed.
+- Go 1.2 is now required instead of Go 1.3. No external dependency are used when
+using Go 1.5.
diff --git a/vendor/gopkg.in/gomail.v2/CONTRIBUTING.md b/vendor/gopkg.in/gomail.v2/CONTRIBUTING.md
new file mode 100644
index 000000000..d5601c257
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/CONTRIBUTING.md
@@ -0,0 +1,20 @@
+Thank you for contributing to Gomail! Here are a few guidelines:
+
+## Bugs
+
+If you think you found a bug, create an issue and supply the minimum amount
+of code triggering the bug so it can be reproduced.
+
+
+## Fixing a bug
+
+If you want to fix a bug, you can send a pull request. It should contains a
+new test or update an existing one to cover that bug.
+
+
+## New feature proposal
+
+If you think Gomail lacks a feature, you can open an issue or send a pull
+request. I want to keep Gomail code and API as simple as possible so please
+describe your needs so we can discuss whether this feature should be added to
+Gomail or not.
diff --git a/vendor/gopkg.in/gomail.v2/LICENSE b/vendor/gopkg.in/gomail.v2/LICENSE
new file mode 100644
index 000000000..5f5c12af7
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Alexandre Cesaro
+
+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.
diff --git a/vendor/gopkg.in/gomail.v2/README.md b/vendor/gopkg.in/gomail.v2/README.md
new file mode 100644
index 000000000..b3be9e146
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/README.md
@@ -0,0 +1,92 @@
+# Gomail
+[![Build Status](https://travis-ci.org/go-gomail/gomail.svg?branch=v2)](https://travis-ci.org/go-gomail/gomail) [![Code Coverage](http://gocover.io/_badge/gopkg.in/gomail.v2)](http://gocover.io/gopkg.in/gomail.v2) [![Documentation](https://godoc.org/gopkg.in/gomail.v2?status.svg)](https://godoc.org/gopkg.in/gomail.v2)
+
+## Introduction
+
+Gomail is a simple and efficient package to send emails. It is well tested and
+documented.
+
+Gomail can only send emails using an SMTP server. But the API is flexible and it
+is easy to implement other methods for sending emails using a local Postfix, an
+API, etc.
+
+It is versioned using [gopkg.in](https://gopkg.in) so I promise
+there will never be backward incompatible changes within each version.
+
+It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used.
+
+
+## Features
+
+Gomail supports:
+- Attachments
+- Embedded images
+- HTML and text templates
+- Automatic encoding of special characters
+- SSL and TLS
+- Sending multiple emails with the same SMTP connection
+
+
+## Documentation
+
+https://godoc.org/gopkg.in/gomail.v2
+
+
+## Download
+
+ go get gopkg.in/gomail.v2
+
+
+## Examples
+
+See the [examples in the documentation](https://godoc.org/gopkg.in/gomail.v2#example-package).
+
+
+## FAQ
+
+### x509: certificate signed by unknown authority
+
+If you get this error it means the certificate used by the SMTP server is not
+considered valid by the client running Gomail. As a quick workaround you can
+bypass the verification of the server's certificate chain and host name by using
+`SetTLSConfig`:
+
+ package main
+
+ import (
+ "crypto/tls"
+
+ "gopkg.in/gomail.v2"
+ )
+
+ func main() {
+ d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+
+ // Send emails using d.
+ }
+
+Note, however, that this is insecure and should not be used in production.
+
+
+## Contribute
+
+Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
+more info.
+
+
+## Change log
+
+See [CHANGELOG.md](CHANGELOG.md).
+
+
+## License
+
+[MIT](LICENSE)
+
+
+## Contact
+
+You can ask questions on the [Gomail
+thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
+in the Go mailing-list.
diff --git a/vendor/gopkg.in/gomail.v2/auth.go b/vendor/gopkg.in/gomail.v2/auth.go
new file mode 100644
index 000000000..d28b83ab7
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/auth.go
@@ -0,0 +1,49 @@
+package gomail
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net/smtp"
+)
+
+// loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
+type loginAuth struct {
+ username string
+ password string
+ host string
+}
+
+func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
+ if !server.TLS {
+ advertised := false
+ for _, mechanism := range server.Auth {
+ if mechanism == "LOGIN" {
+ advertised = true
+ break
+ }
+ }
+ if !advertised {
+ return "", nil, errors.New("gomail: unencrypted connection")
+ }
+ }
+ if server.Name != a.host {
+ return "", nil, errors.New("gomail: wrong host name")
+ }
+ return "LOGIN", nil, nil
+}
+
+func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
+ if !more {
+ return nil, nil
+ }
+
+ switch {
+ case bytes.Equal(fromServer, []byte("Username:")):
+ return []byte(a.username), nil
+ case bytes.Equal(fromServer, []byte("Password:")):
+ return []byte(a.password), nil
+ default:
+ return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
+ }
+}
diff --git a/vendor/gopkg.in/gomail.v2/auth_test.go b/vendor/gopkg.in/gomail.v2/auth_test.go
new file mode 100644
index 000000000..428ef3467
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/auth_test.go
@@ -0,0 +1,100 @@
+package gomail
+
+import (
+ "net/smtp"
+ "testing"
+)
+
+const (
+ testUser = "user"
+ testPwd = "pwd"
+ testHost = "smtp.example.com"
+)
+
+type authTest struct {
+ auths []string
+ challenges []string
+ tls bool
+ wantData []string
+ wantError bool
+}
+
+func TestNoAdvertisement(t *testing.T) {
+ testLoginAuth(t, &authTest{
+ auths: []string{},
+ tls: false,
+ wantError: true,
+ })
+}
+
+func TestNoAdvertisementTLS(t *testing.T) {
+ testLoginAuth(t, &authTest{
+ auths: []string{},
+ challenges: []string{"Username:", "Password:"},
+ tls: true,
+ wantData: []string{"", testUser, testPwd},
+ })
+}
+
+func TestLogin(t *testing.T) {
+ testLoginAuth(t, &authTest{
+ auths: []string{"PLAIN", "LOGIN"},
+ challenges: []string{"Username:", "Password:"},
+ tls: false,
+ wantData: []string{"", testUser, testPwd},
+ })
+}
+
+func TestLoginTLS(t *testing.T) {
+ testLoginAuth(t, &authTest{
+ auths: []string{"LOGIN"},
+ challenges: []string{"Username:", "Password:"},
+ tls: true,
+ wantData: []string{"", testUser, testPwd},
+ })
+}
+
+func testLoginAuth(t *testing.T, test *authTest) {
+ auth := &loginAuth{
+ username: testUser,
+ password: testPwd,
+ host: testHost,
+ }
+ server := &smtp.ServerInfo{
+ Name: testHost,
+ TLS: test.tls,
+ Auth: test.auths,
+ }
+ proto, toServer, err := auth.Start(server)
+ if err != nil && !test.wantError {
+ t.Fatalf("loginAuth.Start(): %v", err)
+ }
+ if err != nil && test.wantError {
+ return
+ }
+ if proto != "LOGIN" {
+ t.Errorf("invalid protocol, got %q, want LOGIN", proto)
+ }
+
+ i := 0
+ got := string(toServer)
+ if got != test.wantData[i] {
+ t.Errorf("Invalid response, got %q, want %q", got, test.wantData[i])
+ }
+
+ for _, challenge := range test.challenges {
+ i++
+ if i >= len(test.wantData) {
+ t.Fatalf("unexpected challenge: %q", challenge)
+ }
+
+ toServer, err = auth.Next([]byte(challenge), true)
+ if err != nil {
+ t.Fatalf("loginAuth.Auth(): %v", err)
+ }
+ got = string(toServer)
+ if got != test.wantData[i] {
+ t.Errorf("Invalid response, got %q, want %q", got, test.wantData[i])
+ }
+ }
+}
diff --git a/vendor/gopkg.in/gomail.v2/doc.go b/vendor/gopkg.in/gomail.v2/doc.go
new file mode 100644
index 000000000..a8f5091f5
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/doc.go
@@ -0,0 +1,5 @@
+// Package gomail provides a simple interface to compose emails and to mail them
+// efficiently.
+//
+// More info on Github: https://github.com/go-gomail/gomail
+package gomail
diff --git a/vendor/gopkg.in/gomail.v2/example_test.go b/vendor/gopkg.in/gomail.v2/example_test.go
new file mode 100644
index 000000000..90008abe8
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/example_test.go
@@ -0,0 +1,223 @@
+package gomail_test
+
+import (
+ "fmt"
+ "html/template"
+ "io"
+ "log"
+ "time"
+
+ "gopkg.in/gomail.v2"
+)
+
+func Example() {
+ m := gomail.NewMessage()
+ m.SetHeader("From", "alex@example.com")
+ m.SetHeader("To", "bob@example.com", "cora@example.com")
+ m.SetAddressHeader("Cc", "dan@example.com", "Dan")
+ m.SetHeader("Subject", "Hello!")
+ m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
+ m.Attach("/home/Alex/lolcat.jpg")
+
+ d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
+
+ // Send the email to Bob, Cora and Dan.
+ if err := d.DialAndSend(m); err != nil {
+ panic(err)
+ }
+}
+
+// A daemon that listens to a channel and sends all incoming messages.
+func Example_daemon() {
+ ch := make(chan *gomail.Message)
+
+ go func() {
+ d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
+
+ var s gomail.SendCloser
+ var err error
+ open := false
+ for {
+ select {
+ case m, ok := <-ch:
+ if !ok {
+ return
+ }
+ if !open {
+ if s, err = d.Dial(); err != nil {
+ panic(err)
+ }
+ open = true
+ }
+ if err := gomail.Send(s, m); err != nil {
+ log.Print(err)
+ }
+ // Close the connection to the SMTP server if no email was sent in
+ // the last 30 seconds.
+ case <-time.After(30 * time.Second):
+ if open {
+ if err := s.Close(); err != nil {
+ panic(err)
+ }
+ open = false
+ }
+ }
+ }
+ }()
+
+ // Use the channel in your program to send emails.
+
+ // Close the channel to stop the mail daemon.
+ close(ch)
+}
+
+// Efficiently send a customized newsletter to a list of recipients.
+func Example_newsletter() {
+ // The list of recipients.
+ var list []struct {
+ Name string
+ Address string
+ }
+
+ d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
+ s, err := d.Dial()
+ if err != nil {
+ panic(err)
+ }
+
+ m := gomail.NewMessage()
+ for _, r := range list {
+ m.SetHeader("From", "no-reply@example.com")
+ m.SetAddressHeader("To", r.Address, r.Name)
+ m.SetHeader("Subject", "Newsletter #1")
+ m.SetBody("text/html", fmt.Sprintf("Hello %s!", r.Name))
+
+ if err := gomail.Send(s, m); err != nil {
+ log.Printf("Could not send email to %q: %v", r.Address, err)
+ }
+ m.Reset()
+ }
+}
+
+// Send an email using a local SMTP server.
+func Example_noAuth() {
+ m := gomail.NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetHeader("Subject", "Hello!")
+ m.SetBody("text/plain", "Hello!")
+
+ d := gomail.Dialer{Host: "localhost", Port: 587}
+ if err := d.DialAndSend(m); err != nil {
+ panic(err)
+ }
+}
+
+// Send an email using an API or postfix.
+func Example_noSMTP() {
+ m := gomail.NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetHeader("Subject", "Hello!")
+ m.SetBody("text/plain", "Hello!")
+
+ s := gomail.SendFunc(func(from string, to []string, msg io.WriterTo) error {
+ // Implements you email-sending function, for example by calling
+ // an API, or running postfix, etc.
+ fmt.Println("From:", from)
+ fmt.Println("To:", to)
+ return nil
+ })
+
+ if err := gomail.Send(s, m); err != nil {
+ panic(err)
+ }
+ // Output:
+ // From: from@example.com
+ // To: [to@example.com]
+}
+
+var m *gomail.Message
+
+func ExampleSetCopyFunc() {
+ m.Attach("foo.txt", gomail.SetCopyFunc(func(w io.Writer) error {
+ _, err := w.Write([]byte("Content of foo.txt"))
+ return err
+ }))
+}
+
+func ExampleSetHeader() {
+ h := map[string][]string{"Content-ID": {"<foo@bar.mail>"}}
+ m.Attach("foo.jpg", gomail.SetHeader(h))
+}
+
+func ExampleRename() {
+ m.Attach("/tmp/0000146.jpg", gomail.Rename("picture.jpg"))
+}
+
+func ExampleMessage_AddAlternative() {
+ m.SetBody("text/plain", "Hello!")
+ m.AddAlternative("text/html", "<p>Hello!</p>")
+}
+
+func ExampleMessage_AddAlternativeWriter() {
+ t := template.Must(template.New("example").Parse("Hello {{.}}!"))
+ m.AddAlternativeWriter("text/plain", func(w io.Writer) error {
+ return t.Execute(w, "Bob")
+ })
+}
+
+func ExampleMessage_Attach() {
+ m.Attach("/tmp/image.jpg")
+}
+
+func ExampleMessage_Embed() {
+ m.Embed("/tmp/image.jpg")
+ m.SetBody("text/html", `<img src="cid:image.jpg" alt="My image" />`)
+}
+
+func ExampleMessage_FormatAddress() {
+ m.SetHeader("To", m.FormatAddress("bob@example.com", "Bob"), m.FormatAddress("cora@example.com", "Cora"))
+}
+
+func ExampleMessage_FormatDate() {
+ m.SetHeaders(map[string][]string{
+ "X-Date": {m.FormatDate(time.Now())},
+ })
+}
+
+func ExampleMessage_SetAddressHeader() {
+ m.SetAddressHeader("To", "bob@example.com", "Bob")
+}
+
+func ExampleMessage_SetBody() {
+ m.SetBody("text/plain", "Hello!")
+}
+
+func ExampleMessage_SetDateHeader() {
+ m.SetDateHeader("X-Date", time.Now())
+}
+
+func ExampleMessage_SetHeader() {
+ m.SetHeader("Subject", "Hello!")
+}
+
+func ExampleMessage_SetHeaders() {
+ m.SetHeaders(map[string][]string{
+ "From": {m.FormatAddress("alex@example.com", "Alex")},
+ "To": {"bob@example.com", "cora@example.com"},
+ "Subject": {"Hello"},
+ })
+}
+
+func ExampleSetCharset() {
+ m = gomail.NewMessage(gomail.SetCharset("ISO-8859-1"))
+}
+
+func ExampleSetEncoding() {
+ m = gomail.NewMessage(gomail.SetEncoding(gomail.Base64))
+}
+
+func ExampleSetPartEncoding() {
+ m.SetBody("text/plain", "Hello!", gomail.SetPartEncoding(gomail.Unencoded))
+}
diff --git a/vendor/gopkg.in/gomail.v2/message.go b/vendor/gopkg.in/gomail.v2/message.go
new file mode 100644
index 000000000..4bffb1e7f
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/message.go
@@ -0,0 +1,322 @@
+package gomail
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+// Message represents an email.
+type Message struct {
+ header header
+ parts []*part
+ attachments []*file
+ embedded []*file
+ charset string
+ encoding Encoding
+ hEncoder mimeEncoder
+ buf bytes.Buffer
+}
+
+type header map[string][]string
+
+type part struct {
+ contentType string
+ copier func(io.Writer) error
+ encoding Encoding
+}
+
+// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
+// by default.
+func NewMessage(settings ...MessageSetting) *Message {
+ m := &Message{
+ header: make(header),
+ charset: "UTF-8",
+ encoding: QuotedPrintable,
+ }
+
+ m.applySettings(settings)
+
+ if m.encoding == Base64 {
+ m.hEncoder = bEncoding
+ } else {
+ m.hEncoder = qEncoding
+ }
+
+ return m
+}
+
+// Reset resets the message so it can be reused. The message keeps its previous
+// settings so it is in the same state that after a call to NewMessage.
+func (m *Message) Reset() {
+ for k := range m.header {
+ delete(m.header, k)
+ }
+ m.parts = nil
+ m.attachments = nil
+ m.embedded = nil
+}
+
+func (m *Message) applySettings(settings []MessageSetting) {
+ for _, s := range settings {
+ s(m)
+ }
+}
+
+// A MessageSetting can be used as an argument in NewMessage to configure an
+// email.
+type MessageSetting func(m *Message)
+
+// SetCharset is a message setting to set the charset of the email.
+func SetCharset(charset string) MessageSetting {
+ return func(m *Message) {
+ m.charset = charset
+ }
+}
+
+// SetEncoding is a message setting to set the encoding of the email.
+func SetEncoding(enc Encoding) MessageSetting {
+ return func(m *Message) {
+ m.encoding = enc
+ }
+}
+
+// Encoding represents a MIME encoding scheme like quoted-printable or base64.
+type Encoding string
+
+const (
+ // QuotedPrintable represents the quoted-printable encoding as defined in
+ // RFC 2045.
+ QuotedPrintable Encoding = "quoted-printable"
+ // Base64 represents the base64 encoding as defined in RFC 2045.
+ Base64 Encoding = "base64"
+ // Unencoded can be used to avoid encoding the body of an email. The headers
+ // will still be encoded using quoted-printable encoding.
+ Unencoded Encoding = "8bit"
+)
+
+// SetHeader sets a value to the given header field.
+func (m *Message) SetHeader(field string, value ...string) {
+ m.encodeHeader(value)
+ m.header[field] = value
+}
+
+func (m *Message) encodeHeader(values []string) {
+ for i := range values {
+ values[i] = m.encodeString(values[i])
+ }
+}
+
+func (m *Message) encodeString(value string) string {
+ return m.hEncoder.Encode(m.charset, value)
+}
+
+// SetHeaders sets the message headers.
+func (m *Message) SetHeaders(h map[string][]string) {
+ for k, v := range h {
+ m.SetHeader(k, v...)
+ }
+}
+
+// SetAddressHeader sets an address to the given header field.
+func (m *Message) SetAddressHeader(field, address, name string) {
+ m.header[field] = []string{m.FormatAddress(address, name)}
+}
+
+// FormatAddress formats an address and a name as a valid RFC 5322 address.
+func (m *Message) FormatAddress(address, name string) string {
+ if name == "" {
+ return address
+ }
+
+ enc := m.encodeString(name)
+ if enc == name {
+ m.buf.WriteByte('"')
+ for i := 0; i < len(name); i++ {
+ b := name[i]
+ if b == '\\' || b == '"' {
+ m.buf.WriteByte('\\')
+ }
+ m.buf.WriteByte(b)
+ }
+ m.buf.WriteByte('"')
+ } else if hasSpecials(name) {
+ m.buf.WriteString(bEncoding.Encode(m.charset, name))
+ } else {
+ m.buf.WriteString(enc)
+ }
+ m.buf.WriteString(" <")
+ m.buf.WriteString(address)
+ m.buf.WriteByte('>')
+
+ addr := m.buf.String()
+ m.buf.Reset()
+ return addr
+}
+
+func hasSpecials(text string) bool {
+ for i := 0; i < len(text); i++ {
+ switch c := text[i]; c {
+ case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
+ return true
+ }
+ }
+
+ return false
+}
+
+// SetDateHeader sets a date to the given header field.
+func (m *Message) SetDateHeader(field string, date time.Time) {
+ m.header[field] = []string{m.FormatDate(date)}
+}
+
+// FormatDate formats a date as a valid RFC 5322 date.
+func (m *Message) FormatDate(date time.Time) string {
+ return date.Format(time.RFC1123Z)
+}
+
+// GetHeader gets a header field.
+func (m *Message) GetHeader(field string) []string {
+ return m.header[field]
+}
+
+// SetBody sets the body of the message. It replaces any content previously set
+// by SetBody, AddAlternative or AddAlternativeWriter.
+func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
+ m.parts = []*part{m.newPart(contentType, newCopier(body), settings)}
+}
+
+// AddAlternative adds an alternative part to the message.
+//
+// It is commonly used to send HTML emails that default to the plain text
+// version for backward compatibility. AddAlternative appends the new part to
+// the end of the message. So the plain text part should be added before the
+// HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
+func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
+ m.AddAlternativeWriter(contentType, newCopier(body), settings...)
+}
+
+func newCopier(s string) func(io.Writer) error {
+ return func(w io.Writer) error {
+ _, err := io.WriteString(w, s)
+ return err
+ }
+}
+
+// AddAlternativeWriter adds an alternative part to the message. It can be
+// useful with the text/template or html/template packages.
+func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
+ m.parts = append(m.parts, m.newPart(contentType, f, settings))
+}
+
+func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
+ p := &part{
+ contentType: contentType,
+ copier: f,
+ encoding: m.encoding,
+ }
+
+ for _, s := range settings {
+ s(p)
+ }
+
+ return p
+}
+
+// A PartSetting can be used as an argument in Message.SetBody,
+// Message.AddAlternative or Message.AddAlternativeWriter to configure the part
+// added to a message.
+type PartSetting func(*part)
+
+// SetPartEncoding sets the encoding of the part added to the message. By
+// default, parts use the same encoding than the message.
+func SetPartEncoding(e Encoding) PartSetting {
+ return PartSetting(func(p *part) {
+ p.encoding = e
+ })
+}
+
+type file struct {
+ Name string
+ Header map[string][]string
+ CopyFunc func(w io.Writer) error
+}
+
+func (f *file) setHeader(field, value string) {
+ f.Header[field] = []string{value}
+}
+
+// A FileSetting can be used as an argument in Message.Attach or Message.Embed.
+type FileSetting func(*file)
+
+// SetHeader is a file setting to set the MIME header of the message part that
+// contains the file content.
+//
+// Mandatory headers are automatically added if they are not set when sending
+// the email.
+func SetHeader(h map[string][]string) FileSetting {
+ return func(f *file) {
+ for k, v := range h {
+ f.Header[k] = v
+ }
+ }
+}
+
+// Rename is a file setting to set the name of the attachment if the name is
+// different than the filename on disk.
+func Rename(name string) FileSetting {
+ return func(f *file) {
+ f.Name = name
+ }
+}
+
+// SetCopyFunc is a file setting to replace the function that runs when the
+// message is sent. It should copy the content of the file to the io.Writer.
+//
+// The default copy function opens the file with the given filename, and copy
+// its content to the io.Writer.
+func SetCopyFunc(f func(io.Writer) error) FileSetting {
+ return func(fi *file) {
+ fi.CopyFunc = f
+ }
+}
+
+func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file {
+ f := &file{
+ Name: filepath.Base(name),
+ Header: make(map[string][]string),
+ CopyFunc: func(w io.Writer) error {
+ h, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(w, h); err != nil {
+ h.Close()
+ return err
+ }
+ return h.Close()
+ },
+ }
+
+ for _, s := range settings {
+ s(f)
+ }
+
+ if list == nil {
+ return []*file{f}
+ }
+
+ return append(list, f)
+}
+
+// Attach attaches the files to the email.
+func (m *Message) Attach(filename string, settings ...FileSetting) {
+ m.attachments = m.appendFile(m.attachments, filename, settings)
+}
+
+// Embed embeds the images to the email.
+func (m *Message) Embed(filename string, settings ...FileSetting) {
+ m.embedded = m.appendFile(m.embedded, filename, settings)
+}
diff --git a/vendor/gopkg.in/gomail.v2/message_test.go b/vendor/gopkg.in/gomail.v2/message_test.go
new file mode 100644
index 000000000..acceff2a6
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/message_test.go
@@ -0,0 +1,745 @@
+package gomail
+
+import (
+ "bytes"
+ "encoding/base64"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+)
+
+func init() {
+ now = func() time.Time {
+ return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
+ }
+}
+
+type message struct {
+ from string
+ to []string
+ content string
+}
+
+func TestMessage(t *testing.T) {
+ m := NewMessage()
+ m.SetAddressHeader("From", "from@example.com", "Señor From")
+ m.SetHeader("To", m.FormatAddress("to@example.com", "Señor To"), "tobis@example.com")
+ m.SetAddressHeader("Cc", "cc@example.com", "A, B")
+ m.SetAddressHeader("X-To", "ccbis@example.com", "à, b")
+ m.SetDateHeader("X-Date", now())
+ m.SetHeader("X-Date-2", m.FormatDate(now()))
+ m.SetHeader("Subject", "¡Hola, señor!")
+ m.SetHeaders(map[string][]string{
+ "X-Headers": {"Test", "Café"},
+ })
+ m.SetBody("text/plain", "¡Hola, señor!")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{
+ "to@example.com",
+ "tobis@example.com",
+ "cc@example.com",
+ },
+ content: "From: =?UTF-8?q?Se=C3=B1or_From?= <from@example.com>\r\n" +
+ "To: =?UTF-8?q?Se=C3=B1or_To?= <to@example.com>, tobis@example.com\r\n" +
+ "Cc: \"A, B\" <cc@example.com>\r\n" +
+ "X-To: =?UTF-8?b?w6AsIGI=?= <ccbis@example.com>\r\n" +
+ "X-Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+ "X-Date-2: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+ "X-Headers: Test, =?UTF-8?q?Caf=C3=A9?=\r\n" +
+ "Subject: =?UTF-8?q?=C2=A1Hola,_se=C3=B1or!?=\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1Hola, se=C3=B1or!",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestCustomMessage(t *testing.T) {
+ m := NewMessage(SetCharset("ISO-8859-1"), SetEncoding(Base64))
+ m.SetHeaders(map[string][]string{
+ "From": {"from@example.com"},
+ "To": {"to@example.com"},
+ "Subject": {"Café"},
+ })
+ m.SetBody("text/html", "¡Hola, señor!")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Subject: =?ISO-8859-1?b?Q2Fmw6k=?=\r\n" +
+ "Content-Type: text/html; charset=ISO-8859-1\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ "wqFIb2xhLCBzZcOxb3Ih",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestUnencodedMessage(t *testing.T) {
+ m := NewMessage(SetEncoding(Unencoded))
+ m.SetHeaders(map[string][]string{
+ "From": {"from@example.com"},
+ "To": {"to@example.com"},
+ "Subject": {"Café"},
+ })
+ m.SetBody("text/html", "¡Hola, señor!")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Subject: =?UTF-8?q?Caf=C3=A9?=\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: 8bit\r\n" +
+ "\r\n" +
+ "¡Hola, señor!",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestRecipients(t *testing.T) {
+ m := NewMessage()
+ m.SetHeaders(map[string][]string{
+ "From": {"from@example.com"},
+ "To": {"to@example.com"},
+ "Cc": {"cc@example.com"},
+ "Bcc": {"bcc1@example.com", "bcc2@example.com"},
+ "Subject": {"Hello!"},
+ })
+ m.SetBody("text/plain", "Test message")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com", "cc@example.com", "bcc1@example.com", "bcc2@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Cc: cc@example.com\r\n" +
+ "Subject: Hello!\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test message",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestAlternative(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "¡Hola, señor!")
+ m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/alternative;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1Hola, se=C3=B1or!\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestPartSetting(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded))
+ m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/alternative;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: 8bit\r\n" +
+ "\r\n" +
+ "¡Hola, señor!\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestBodyWriter(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.AddAlternativeWriter("text/plain", func(w io.Writer) error {
+ _, err := w.Write([]byte("Test message"))
+ return err
+ })
+ m.AddAlternativeWriter("text/html", func(w io.Writer) error {
+ _, err := w.Write([]byte("Test HTML"))
+ return err
+ })
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/alternative;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test message\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test HTML\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestAttachmentOnly(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.Attach(mockCopyFile("/tmp/test.pdf"))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")),
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestAttachment(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "Test")
+ m.Attach(mockCopyFile("/tmp/test.pdf"))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/mixed;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestRename(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "Test")
+ name, copy := mockCopyFile("/tmp/test.pdf")
+ rename := Rename("another.pdf")
+ m.Attach(name, copy, rename)
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/mixed;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/pdf; name=\"another.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"another.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestAttachmentsOnly(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.Attach(mockCopyFile("/tmp/test.pdf"))
+ m.Attach(mockCopyFile("/tmp/test.zip"))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/mixed;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/zip; name=\"test.zip\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestAttachments(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "Test")
+ m.Attach(mockCopyFile("/tmp/test.pdf"))
+ m.Attach(mockCopyFile("/tmp/test.zip"))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/mixed;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/zip; name=\"test.zip\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestEmbedded(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.Embed(mockCopyFileWithHeader(m, "image1.jpg", map[string][]string{"Content-ID": {"<test-content-id>"}}))
+ m.Embed(mockCopyFile("image2.jpg"))
+ m.SetBody("text/plain", "Test")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/related;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: image/jpeg; name=\"image1.jpg\"\r\n" +
+ "Content-Disposition: inline; filename=\"image1.jpg\"\r\n" +
+ "Content-ID: <test-content-id>\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of image1.jpg")) + "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: image/jpeg; name=\"image2.jpg\"\r\n" +
+ "Content-Disposition: inline; filename=\"image2.jpg\"\r\n" +
+ "Content-ID: <image2.jpg>\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of image2.jpg")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 1, want)
+}
+
+func TestFullMessage(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "¡Hola, señor!")
+ m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
+ m.Attach(mockCopyFile("test.pdf"))
+ m.Embed(mockCopyFile("image.jpg"))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: multipart/mixed;\r\n" +
+ " boundary=_BOUNDARY_1_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: multipart/related;\r\n" +
+ " boundary=_BOUNDARY_2_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_2_\r\n" +
+ "Content-Type: multipart/alternative;\r\n" +
+ " boundary=_BOUNDARY_3_\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_3_\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1Hola, se=C3=B1or!\r\n" +
+ "--_BOUNDARY_3_\r\n" +
+ "Content-Type: text/html; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
+ "--_BOUNDARY_3_--\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_2_\r\n" +
+ "Content-Type: image/jpeg; name=\"image.jpg\"\r\n" +
+ "Content-Disposition: inline; filename=\"image.jpg\"\r\n" +
+ "Content-ID: <image.jpg>\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of image.jpg")) + "\r\n" +
+ "--_BOUNDARY_2_--\r\n" +
+ "\r\n" +
+ "--_BOUNDARY_1_\r\n" +
+ "Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+ "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
+ "--_BOUNDARY_1_--\r\n",
+ }
+
+ testMessage(t, m, 3, want)
+
+ want = &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ "Test reset",
+ }
+ m.Reset()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", "Test reset")
+ testMessage(t, m, 0, want)
+}
+
+func TestQpLineLength(t *testing.T) {
+ m := NewMessage()
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain",
+ strings.Repeat("0", 76)+"\r\n"+
+ strings.Repeat("0", 75)+"à\r\n"+
+ strings.Repeat("0", 74)+"à\r\n"+
+ strings.Repeat("0", 73)+"à\r\n"+
+ strings.Repeat("0", 72)+"à\r\n"+
+ strings.Repeat("0", 75)+"\r\n"+
+ strings.Repeat("0", 76)+"\n")
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ strings.Repeat("0", 75) + "=\r\n0\r\n" +
+ strings.Repeat("0", 75) + "=\r\n=C3=A0\r\n" +
+ strings.Repeat("0", 74) + "=\r\n=C3=A0\r\n" +
+ strings.Repeat("0", 73) + "=\r\n=C3=A0\r\n" +
+ strings.Repeat("0", 72) + "=C3=\r\n=A0\r\n" +
+ strings.Repeat("0", 75) + "\r\n" +
+ strings.Repeat("0", 75) + "=\r\n0\r\n",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestBase64LineLength(t *testing.T) {
+ m := NewMessage(SetCharset("UTF-8"), SetEncoding(Base64))
+ m.SetHeader("From", "from@example.com")
+ m.SetHeader("To", "to@example.com")
+ m.SetBody("text/plain", strings.Repeat("0", 58))
+
+ want := &message{
+ from: "from@example.com",
+ to: []string{"to@example.com"},
+ content: "From: from@example.com\r\n" +
+ "To: to@example.com\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: base64\r\n" +
+ "\r\n" +
+ strings.Repeat("MDAw", 19) + "\r\nMA==",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestEmptyName(t *testing.T) {
+ m := NewMessage()
+ m.SetAddressHeader("From", "from@example.com", "")
+
+ want := &message{
+ from: "from@example.com",
+ content: "From: from@example.com\r\n",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func TestEmptyHeader(t *testing.T) {
+ m := NewMessage()
+ m.SetHeaders(map[string][]string{
+ "From": {"from@example.com"},
+ "X-Empty": nil,
+ })
+
+ want := &message{
+ from: "from@example.com",
+ content: "From: from@example.com\r\n" +
+ "X-Empty:\r\n",
+ }
+
+ testMessage(t, m, 0, want)
+}
+
+func testMessage(t *testing.T, m *Message, bCount int, want *message) {
+ err := Send(stubSendMail(t, bCount, want), m)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func stubSendMail(t *testing.T, bCount int, want *message) SendFunc {
+ return func(from string, to []string, m io.WriterTo) error {
+ if from != want.from {
+ t.Fatalf("Invalid from, got %q, want %q", from, want.from)
+ }
+
+ if len(to) != len(want.to) {
+ t.Fatalf("Invalid recipient count, \ngot %d: %q\nwant %d: %q",
+ len(to), to,
+ len(want.to), want.to,
+ )
+ }
+ for i := range want.to {
+ if to[i] != want.to[i] {
+ t.Fatalf("Invalid recipient, got %q, want %q",
+ to[i], want.to[i],
+ )
+ }
+ }
+
+ buf := new(bytes.Buffer)
+ _, err := m.WriteTo(buf)
+ if err != nil {
+ t.Error(err)
+ }
+ got := buf.String()
+ wantMsg := string("Mime-Version: 1.0\r\n" +
+ "Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+ want.content)
+ if bCount > 0 {
+ boundaries := getBoundaries(t, bCount, got)
+ for i, b := range boundaries {
+ wantMsg = strings.Replace(wantMsg, "_BOUNDARY_"+strconv.Itoa(i+1)+"_", b, -1)
+ }
+ }
+
+ compareBodies(t, got, wantMsg)
+
+ return nil
+ }
+}
+
+func compareBodies(t *testing.T, got, want string) {
+ // We cannot do a simple comparison since the ordering of headers' fields
+ // is random.
+ gotLines := strings.Split(got, "\r\n")
+ wantLines := strings.Split(want, "\r\n")
+
+ // We only test for too many lines, missing lines are tested after
+ if len(gotLines) > len(wantLines) {
+ t.Fatalf("Message has too many lines, \ngot %d:\n%s\nwant %d:\n%s", len(gotLines), got, len(wantLines), want)
+ }
+
+ isInHeader := true
+ headerStart := 0
+ for i, line := range wantLines {
+ if line == gotLines[i] {
+ if line == "" {
+ isInHeader = false
+ } else if !isInHeader && len(line) > 2 && line[:2] == "--" {
+ isInHeader = true
+ headerStart = i + 1
+ }
+ continue
+ }
+
+ if !isInHeader {
+ missingLine(t, line, got, want)
+ }
+
+ isMissing := true
+ for j := headerStart; j < len(gotLines); j++ {
+ if gotLines[j] == "" {
+ break
+ }
+ if gotLines[j] == line {
+ isMissing = false
+ break
+ }
+ }
+ if isMissing {
+ missingLine(t, line, got, want)
+ }
+ }
+}
+
+func missingLine(t *testing.T, line, got, want string) {
+ t.Fatalf("Missing line %q\ngot:\n%s\nwant:\n%s", line, got, want)
+}
+
+func getBoundaries(t *testing.T, count int, m string) []string {
+ if matches := boundaryRegExp.FindAllStringSubmatch(m, count); matches != nil {
+ boundaries := make([]string, count)
+ for i, match := range matches {
+ boundaries[i] = match[1]
+ }
+ return boundaries
+ }
+
+ t.Fatal("Boundary not found in body")
+ return []string{""}
+}
+
+var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
+
+func mockCopyFile(name string) (string, FileSetting) {
+ return name, SetCopyFunc(func(w io.Writer) error {
+ _, err := w.Write([]byte("Content of " + filepath.Base(name)))
+ return err
+ })
+}
+
+func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (string, FileSetting, FileSetting) {
+ name, f := mockCopyFile(name)
+ return name, f, SetHeader(h)
+}
+
+func BenchmarkFull(b *testing.B) {
+ discardFunc := SendFunc(func(from string, to []string, m io.WriterTo) error {
+ _, err := m.WriteTo(ioutil.Discard)
+ return err
+ })
+
+ m := NewMessage()
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ m.SetAddressHeader("From", "from@example.com", "Señor From")
+ m.SetHeaders(map[string][]string{
+ "To": {"to@example.com"},
+ "Cc": {"cc@example.com"},
+ "Bcc": {"bcc1@example.com", "bcc2@example.com"},
+ "Subject": {"¡Hola, señor!"},
+ })
+ m.SetBody("text/plain", "¡Hola, señor!")
+ m.AddAlternative("text/html", "<p>¡Hola, señor!</p>")
+ m.Attach(mockCopyFile("benchmark.txt"))
+ m.Embed(mockCopyFile("benchmark.jpg"))
+
+ if err := Send(discardFunc, m); err != nil {
+ panic(err)
+ }
+ m.Reset()
+ }
+}
diff --git a/vendor/gopkg.in/gomail.v2/mime.go b/vendor/gopkg.in/gomail.v2/mime.go
new file mode 100644
index 000000000..194d4a769
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/mime.go
@@ -0,0 +1,21 @@
+// +build go1.5
+
+package gomail
+
+import (
+ "mime"
+ "mime/quotedprintable"
+ "strings"
+)
+
+var newQPWriter = quotedprintable.NewWriter
+
+type mimeEncoder struct {
+ mime.WordEncoder
+}
+
+var (
+ bEncoding = mimeEncoder{mime.BEncoding}
+ qEncoding = mimeEncoder{mime.QEncoding}
+ lastIndexByte = strings.LastIndexByte
+)
diff --git a/vendor/gopkg.in/gomail.v2/mime_go14.go b/vendor/gopkg.in/gomail.v2/mime_go14.go
new file mode 100644
index 000000000..3dc26aa2a
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/mime_go14.go
@@ -0,0 +1,25 @@
+// +build !go1.5
+
+package gomail
+
+import "gopkg.in/alexcesaro/quotedprintable.v3"
+
+var newQPWriter = quotedprintable.NewWriter
+
+type mimeEncoder struct {
+ quotedprintable.WordEncoder
+}
+
+var (
+ bEncoding = mimeEncoder{quotedprintable.BEncoding}
+ qEncoding = mimeEncoder{quotedprintable.QEncoding}
+ lastIndexByte = func(s string, c byte) int {
+ for i := len(s) - 1; i >= 0; i-- {
+
+ if s[i] == c {
+ return i
+ }
+ }
+ return -1
+ }
+)
diff --git a/vendor/gopkg.in/gomail.v2/send.go b/vendor/gopkg.in/gomail.v2/send.go
new file mode 100644
index 000000000..9115ebe72
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/send.go
@@ -0,0 +1,116 @@
+package gomail
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net/mail"
+)
+
+// Sender is the interface that wraps the Send method.
+//
+// Send sends an email to the given addresses.
+type Sender interface {
+ Send(from string, to []string, msg io.WriterTo) error
+}
+
+// SendCloser is the interface that groups the Send and Close methods.
+type SendCloser interface {
+ Sender
+ Close() error
+}
+
+// A SendFunc is a function that sends emails to the given addresses.
+//
+// The SendFunc type is an adapter to allow the use of ordinary functions as
+// email senders. If f is a function with the appropriate signature, SendFunc(f)
+// is a Sender object that calls f.
+type SendFunc func(from string, to []string, msg io.WriterTo) error
+
+// Send calls f(from, to, msg).
+func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
+ return f(from, to, msg)
+}
+
+// Send sends emails using the given Sender.
+func Send(s Sender, msg ...*Message) error {
+ for i, m := range msg {
+ if err := send(s, m); err != nil {
+ return fmt.Errorf("gomail: could not send email %d: %v", i+1, err)
+ }
+ }
+
+ return nil
+}
+
+func send(s Sender, m *Message) error {
+ from, err := m.getFrom()
+ if err != nil {
+ return err
+ }
+
+ to, err := m.getRecipients()
+ if err != nil {
+ return err
+ }
+
+ if err := s.Send(from, to, m); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Message) getFrom() (string, error) {
+ from := m.header["Sender"]
+ if len(from) == 0 {
+ from = m.header["From"]
+ if len(from) == 0 {
+ return "", errors.New(`gomail: invalid message, "From" field is absent`)
+ }
+ }
+
+ return parseAddress(from[0])
+}
+
+func (m *Message) getRecipients() ([]string, error) {
+ n := 0
+ for _, field := range []string{"To", "Cc", "Bcc"} {
+ if addresses, ok := m.header[field]; ok {
+ n += len(addresses)
+ }
+ }
+ list := make([]string, 0, n)
+
+ for _, field := range []string{"To", "Cc", "Bcc"} {
+ if addresses, ok := m.header[field]; ok {
+ for _, a := range addresses {
+ addr, err := parseAddress(a)
+ if err != nil {
+ return nil, err
+ }
+ list = addAddress(list, addr)
+ }
+ }
+ }
+
+ return list, nil
+}
+
+func addAddress(list []string, addr string) []string {
+ for _, a := range list {
+ if addr == a {
+ return list
+ }
+ }
+
+ return append(list, addr)
+}
+
+func parseAddress(field string) (string, error) {
+ addr, err := mail.ParseAddress(field)
+ if err != nil {
+ return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
+ }
+ return addr.Address, nil
+}
diff --git a/vendor/gopkg.in/gomail.v2/send_test.go b/vendor/gopkg.in/gomail.v2/send_test.go
new file mode 100644
index 000000000..ba59cd3dc
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/send_test.go
@@ -0,0 +1,80 @@
+package gomail
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "testing"
+)
+
+const (
+ testTo1 = "to1@example.com"
+ testTo2 = "to2@example.com"
+ testFrom = "from@example.com"
+ testBody = "Test message"
+ testMsg = "To: " + testTo1 + ", " + testTo2 + "\r\n" +
+ "From: " + testFrom + "\r\n" +
+ "Mime-Version: 1.0\r\n" +
+ "Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+ "Content-Type: text/plain; charset=UTF-8\r\n" +
+ "Content-Transfer-Encoding: quoted-printable\r\n" +
+ "\r\n" +
+ testBody
+)
+
+type mockSender SendFunc
+
+func (s mockSender) Send(from string, to []string, msg io.WriterTo) error {
+ return s(from, to, msg)
+}
+
+type mockSendCloser struct {
+ mockSender
+ close func() error
+}
+
+func (s *mockSendCloser) Close() error {
+ return s.close()
+}
+
+func TestSend(t *testing.T) {
+ s := &mockSendCloser{
+ mockSender: stubSend(t, testFrom, []string{testTo1, testTo2}, testMsg),
+ close: func() error {
+ t.Error("Close() should not be called in Send()")
+ return nil
+ },
+ }
+ if err := Send(s, getTestMessage()); err != nil {
+ t.Errorf("Send(): %v", err)
+ }
+}
+
+func getTestMessage() *Message {
+ m := NewMessage()
+ m.SetHeader("From", testFrom)
+ m.SetHeader("To", testTo1, testTo2)
+ m.SetBody("text/plain", testBody)
+
+ return m
+}
+
+func stubSend(t *testing.T, wantFrom string, wantTo []string, wantBody string) mockSender {
+ return func(from string, to []string, msg io.WriterTo) error {
+ if from != wantFrom {
+ t.Errorf("invalid from, got %q, want %q", from, wantFrom)
+ }
+ if !reflect.DeepEqual(to, wantTo) {
+ t.Errorf("invalid to, got %v, want %v", to, wantTo)
+ }
+
+ buf := new(bytes.Buffer)
+ _, err := msg.WriteTo(buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ compareBodies(t, buf.String(), wantBody)
+
+ return nil
+ }
+}
diff --git a/vendor/gopkg.in/gomail.v2/smtp.go b/vendor/gopkg.in/gomail.v2/smtp.go
new file mode 100644
index 000000000..2aa49c8b6
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/smtp.go
@@ -0,0 +1,202 @@
+package gomail
+
+import (
+ "crypto/tls"
+ "fmt"
+ "io"
+ "net"
+ "net/smtp"
+ "strings"
+ "time"
+)
+
+// A Dialer is a dialer to an SMTP server.
+type Dialer struct {
+ // Host represents the host of the SMTP server.
+ Host string
+ // Port represents the port of the SMTP server.
+ Port int
+ // Username is the username to use to authenticate to the SMTP server.
+ Username string
+ // Password is the password to use to authenticate to the SMTP server.
+ Password string
+ // Auth represents the authentication mechanism used to authenticate to the
+ // SMTP server.
+ Auth smtp.Auth
+ // SSL defines whether an SSL connection is used. It should be false in
+ // most cases since the authentication mechanism should use the STARTTLS
+ // extension instead.
+ SSL bool
+ // TSLConfig represents the TLS configuration used for the TLS (when the
+ // STARTTLS extension is used) or SSL connection.
+ TLSConfig *tls.Config
+ // LocalName is the hostname sent to the SMTP server with the HELO command.
+ // By default, "localhost" is sent.
+ LocalName string
+}
+
+// NewDialer returns a new SMTP Dialer. The given parameters are used to connect
+// to the SMTP server.
+func NewDialer(host string, port int, username, password string) *Dialer {
+ return &Dialer{
+ Host: host,
+ Port: port,
+ Username: username,
+ Password: password,
+ SSL: port == 465,
+ }
+}
+
+// NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
+// connect to the SMTP server.
+//
+// Deprecated: Use NewDialer instead.
+func NewPlainDialer(host string, port int, username, password string) *Dialer {
+ return NewDialer(host, port, username, password)
+}
+
+// Dial dials and authenticates to an SMTP server. The returned SendCloser
+// should be closed when done using it.
+func (d *Dialer) Dial() (SendCloser, error) {
+ conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
+ if err != nil {
+ return nil, err
+ }
+
+ if d.SSL {
+ conn = tlsClient(conn, d.tlsConfig())
+ }
+
+ c, err := smtpNewClient(conn, d.Host)
+ if err != nil {
+ return nil, err
+ }
+
+ if d.LocalName != "" {
+ if err := c.Hello(d.LocalName); err != nil {
+ return nil, err
+ }
+ }
+
+ if !d.SSL {
+ if ok, _ := c.Extension("STARTTLS"); ok {
+ if err := c.StartTLS(d.tlsConfig()); err != nil {
+ c.Close()
+ return nil, err
+ }
+ }
+ }
+
+ if d.Auth == nil && d.Username != "" {
+ if ok, auths := c.Extension("AUTH"); ok {
+ if strings.Contains(auths, "CRAM-MD5") {
+ d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
+ } else if strings.Contains(auths, "LOGIN") &&
+ !strings.Contains(auths, "PLAIN") {
+ d.Auth = &loginAuth{
+ username: d.Username,
+ password: d.Password,
+ host: d.Host,
+ }
+ } else {
+ d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
+ }
+ }
+ }
+
+ if d.Auth != nil {
+ if err = c.Auth(d.Auth); err != nil {
+ c.Close()
+ return nil, err
+ }
+ }
+
+ return &smtpSender{c, d}, nil
+}
+
+func (d *Dialer) tlsConfig() *tls.Config {
+ if d.TLSConfig == nil {
+ return &tls.Config{ServerName: d.Host}
+ }
+ return d.TLSConfig
+}
+
+func addr(host string, port int) string {
+ return fmt.Sprintf("%s:%d", host, port)
+}
+
+// DialAndSend opens a connection to the SMTP server, sends the given emails and
+// closes the connection.
+func (d *Dialer) DialAndSend(m ...*Message) error {
+ s, err := d.Dial()
+ if err != nil {
+ return err
+ }
+ defer s.Close()
+
+ return Send(s, m...)
+}
+
+type smtpSender struct {
+ smtpClient
+ d *Dialer
+}
+
+func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
+ if err := c.Mail(from); err != nil {
+ if err == io.EOF {
+ // This is probably due to a timeout, so reconnect and try again.
+ sc, derr := c.d.Dial()
+ if derr == nil {
+ if s, ok := sc.(*smtpSender); ok {
+ *c = *s
+ return c.Send(from, to, msg)
+ }
+ }
+ }
+ return err
+ }
+
+ for _, addr := range to {
+ if err := c.Rcpt(addr); err != nil {
+ return err
+ }
+ }
+
+ w, err := c.Data()
+ if err != nil {
+ return err
+ }
+
+ if _, err = msg.WriteTo(w); err != nil {
+ w.Close()
+ return err
+ }
+
+ return w.Close()
+}
+
+func (c *smtpSender) Close() error {
+ return c.Quit()
+}
+
+// Stubbed out for tests.
+var (
+ netDialTimeout = net.DialTimeout
+ tlsClient = tls.Client
+ smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
+ return smtp.NewClient(conn, host)
+ }
+)
+
+type smtpClient interface {
+ Hello(string) error
+ Extension(string) (bool, string)
+ StartTLS(*tls.Config) error
+ Auth(smtp.Auth) error
+ Mail(string) error
+ Rcpt(string) error
+ Data() (io.WriteCloser, error)
+ Quit() error
+ Close() error
+}
diff --git a/vendor/gopkg.in/gomail.v2/smtp_test.go b/vendor/gopkg.in/gomail.v2/smtp_test.go
new file mode 100644
index 000000000..b6f91555b
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/smtp_test.go
@@ -0,0 +1,292 @@
+package gomail
+
+import (
+ "bytes"
+ "crypto/tls"
+ "io"
+ "net"
+ "net/smtp"
+ "reflect"
+ "testing"
+ "time"
+)
+
+const (
+ testPort = 587
+ testSSLPort = 465
+)
+
+var (
+ testConn = &net.TCPConn{}
+ testTLSConn = &tls.Conn{}
+ testConfig = &tls.Config{InsecureSkipVerify: true}
+ testAuth = smtp.PlainAuth("", testUser, testPwd, testHost)
+)
+
+func TestDialer(t *testing.T) {
+ d := NewDialer(testHost, testPort, "user", "pwd")
+ testSendMail(t, d, []string{
+ "Extension STARTTLS",
+ "StartTLS",
+ "Extension AUTH",
+ "Auth",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+func TestDialerSSL(t *testing.T) {
+ d := NewDialer(testHost, testSSLPort, "user", "pwd")
+ testSendMail(t, d, []string{
+ "Extension AUTH",
+ "Auth",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+func TestDialerConfig(t *testing.T) {
+ d := NewDialer(testHost, testPort, "user", "pwd")
+ d.LocalName = "test"
+ d.TLSConfig = testConfig
+ testSendMail(t, d, []string{
+ "Hello test",
+ "Extension STARTTLS",
+ "StartTLS",
+ "Extension AUTH",
+ "Auth",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+func TestDialerSSLConfig(t *testing.T) {
+ d := NewDialer(testHost, testSSLPort, "user", "pwd")
+ d.LocalName = "test"
+ d.TLSConfig = testConfig
+ testSendMail(t, d, []string{
+ "Hello test",
+ "Extension AUTH",
+ "Auth",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+func TestDialerNoAuth(t *testing.T) {
+ d := &Dialer{
+ Host: testHost,
+ Port: testPort,
+ }
+ testSendMail(t, d, []string{
+ "Extension STARTTLS",
+ "StartTLS",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+func TestDialerTimeout(t *testing.T) {
+ d := &Dialer{
+ Host: testHost,
+ Port: testPort,
+ }
+ testSendMailTimeout(t, d, []string{
+ "Extension STARTTLS",
+ "StartTLS",
+ "Mail " + testFrom,
+ "Extension STARTTLS",
+ "StartTLS",
+ "Mail " + testFrom,
+ "Rcpt " + testTo1,
+ "Rcpt " + testTo2,
+ "Data",
+ "Write message",
+ "Close writer",
+ "Quit",
+ "Close",
+ })
+}
+
+type mockClient struct {
+ t *testing.T
+ i int
+ want []string
+ addr string
+ config *tls.Config
+ timeout bool
+}
+
+func (c *mockClient) Hello(localName string) error {
+ c.do("Hello " + localName)
+ return nil
+}
+
+func (c *mockClient) Extension(ext string) (bool, string) {
+ c.do("Extension " + ext)
+ return true, ""
+}
+
+func (c *mockClient) StartTLS(config *tls.Config) error {
+ assertConfig(c.t, config, c.config)
+ c.do("StartTLS")
+ return nil
+}
+
+func (c *mockClient) Auth(a smtp.Auth) error {
+ if !reflect.DeepEqual(a, testAuth) {
+ c.t.Errorf("Invalid auth, got %#v, want %#v", a, testAuth)
+ }
+ c.do("Auth")
+ return nil
+}
+
+func (c *mockClient) Mail(from string) error {
+ c.do("Mail " + from)
+ if c.timeout {
+ c.timeout = false
+ return io.EOF
+ }
+ return nil
+}
+
+func (c *mockClient) Rcpt(to string) error {
+ c.do("Rcpt " + to)
+ return nil
+}
+
+func (c *mockClient) Data() (io.WriteCloser, error) {
+ c.do("Data")
+ return &mockWriter{c: c, want: testMsg}, nil
+}
+
+func (c *mockClient) Quit() error {
+ c.do("Quit")
+ return nil
+}
+
+func (c *mockClient) Close() error {
+ c.do("Close")
+ return nil
+}
+
+func (c *mockClient) do(cmd string) {
+ if c.i >= len(c.want) {
+ c.t.Fatalf("Invalid command %q", cmd)
+ }
+
+ if cmd != c.want[c.i] {
+ c.t.Fatalf("Invalid command, got %q, want %q", cmd, c.want[c.i])
+ }
+ c.i++
+}
+
+type mockWriter struct {
+ want string
+ c *mockClient
+ buf bytes.Buffer
+}
+
+func (w *mockWriter) Write(p []byte) (int, error) {
+ if w.buf.Len() == 0 {
+ w.c.do("Write message")
+ }
+ w.buf.Write(p)
+ return len(p), nil
+}
+
+func (w *mockWriter) Close() error {
+ compareBodies(w.c.t, w.buf.String(), w.want)
+ w.c.do("Close writer")
+ return nil
+}
+
+func testSendMail(t *testing.T, d *Dialer, want []string) {
+ doTestSendMail(t, d, want, false)
+}
+
+func testSendMailTimeout(t *testing.T, d *Dialer, want []string) {
+ doTestSendMail(t, d, want, true)
+}
+
+func doTestSendMail(t *testing.T, d *Dialer, want []string, timeout bool) {
+ testClient := &mockClient{
+ t: t,
+ want: want,
+ addr: addr(d.Host, d.Port),
+ config: d.TLSConfig,
+ timeout: timeout,
+ }
+
+ netDialTimeout = func(network, address string, d time.Duration) (net.Conn, error) {
+ if network != "tcp" {
+ t.Errorf("Invalid network, got %q, want tcp", network)
+ }
+ if address != testClient.addr {
+ t.Errorf("Invalid address, got %q, want %q",
+ address, testClient.addr)
+ }
+ return testConn, nil
+ }
+
+ tlsClient = func(conn net.Conn, config *tls.Config) *tls.Conn {
+ if conn != testConn {
+ t.Errorf("Invalid conn, got %#v, want %#v", conn, testConn)
+ }
+ assertConfig(t, config, testClient.config)
+ return testTLSConn
+ }
+
+ smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
+ if host != testHost {
+ t.Errorf("Invalid host, got %q, want %q", host, testHost)
+ }
+ return testClient, nil
+ }
+
+ if err := d.DialAndSend(getTestMessage()); err != nil {
+ t.Error(err)
+ }
+}
+
+func assertConfig(t *testing.T, got, want *tls.Config) {
+ if want == nil {
+ want = &tls.Config{ServerName: testHost}
+ }
+ if got.ServerName != want.ServerName {
+ t.Errorf("Invalid field ServerName in config, got %q, want %q", got.ServerName, want.ServerName)
+ }
+ if got.InsecureSkipVerify != want.InsecureSkipVerify {
+ t.Errorf("Invalid field InsecureSkipVerify in config, got %v, want %v", got.InsecureSkipVerify, want.InsecureSkipVerify)
+ }
+}
diff --git a/vendor/gopkg.in/gomail.v2/writeto.go b/vendor/gopkg.in/gomail.v2/writeto.go
new file mode 100644
index 000000000..9fb6b86e8
--- /dev/null
+++ b/vendor/gopkg.in/gomail.v2/writeto.go
@@ -0,0 +1,306 @@
+package gomail
+
+import (
+ "encoding/base64"
+ "errors"
+ "io"
+ "mime"
+ "mime/multipart"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// WriteTo implements io.WriterTo. It dumps the whole message into w.
+func (m *Message) WriteTo(w io.Writer) (int64, error) {
+ mw := &messageWriter{w: w}
+ mw.writeMessage(m)
+ return mw.n, mw.err
+}
+
+func (w *messageWriter) writeMessage(m *Message) {
+ if _, ok := m.header["Mime-Version"]; !ok {
+ w.writeString("Mime-Version: 1.0\r\n")
+ }
+ if _, ok := m.header["Date"]; !ok {
+ w.writeHeader("Date", m.FormatDate(now()))
+ }
+ w.writeHeaders(m.header)
+
+ if m.hasMixedPart() {
+ w.openMultipart("mixed")
+ }
+
+ if m.hasRelatedPart() {
+ w.openMultipart("related")
+ }
+
+ if m.hasAlternativePart() {
+ w.openMultipart("alternative")
+ }
+ for _, part := range m.parts {
+ w.writePart(part, m.charset)
+ }
+ if m.hasAlternativePart() {
+ w.closeMultipart()
+ }
+
+ w.addFiles(m.embedded, false)
+ if m.hasRelatedPart() {
+ w.closeMultipart()
+ }
+
+ w.addFiles(m.attachments, true)
+ if m.hasMixedPart() {
+ w.closeMultipart()
+ }
+}
+
+func (m *Message) hasMixedPart() bool {
+ return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
+}
+
+func (m *Message) hasRelatedPart() bool {
+ return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
+}
+
+func (m *Message) hasAlternativePart() bool {
+ return len(m.parts) > 1
+}
+
+type messageWriter struct {
+ w io.Writer
+ n int64
+ writers [3]*multipart.Writer
+ partWriter io.Writer
+ depth uint8
+ err error
+}
+
+func (w *messageWriter) openMultipart(mimeType string) {
+ mw := multipart.NewWriter(w)
+ contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
+ w.writers[w.depth] = mw
+
+ if w.depth == 0 {
+ w.writeHeader("Content-Type", contentType)
+ w.writeString("\r\n")
+ } else {
+ w.createPart(map[string][]string{
+ "Content-Type": {contentType},
+ })
+ }
+ w.depth++
+}
+
+func (w *messageWriter) createPart(h map[string][]string) {
+ w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
+}
+
+func (w *messageWriter) closeMultipart() {
+ if w.depth > 0 {
+ w.writers[w.depth-1].Close()
+ w.depth--
+ }
+}
+
+func (w *messageWriter) writePart(p *part, charset string) {
+ w.writeHeaders(map[string][]string{
+ "Content-Type": {p.contentType + "; charset=" + charset},
+ "Content-Transfer-Encoding": {string(p.encoding)},
+ })
+ w.writeBody(p.copier, p.encoding)
+}
+
+func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
+ for _, f := range files {
+ if _, ok := f.Header["Content-Type"]; !ok {
+ mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
+ if mediaType == "" {
+ mediaType = "application/octet-stream"
+ }
+ f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
+ }
+
+ if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
+ f.setHeader("Content-Transfer-Encoding", string(Base64))
+ }
+
+ if _, ok := f.Header["Content-Disposition"]; !ok {
+ var disp string
+ if isAttachment {
+ disp = "attachment"
+ } else {
+ disp = "inline"
+ }
+ f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
+ }
+
+ if !isAttachment {
+ if _, ok := f.Header["Content-ID"]; !ok {
+ f.setHeader("Content-ID", "<"+f.Name+">")
+ }
+ }
+ w.writeHeaders(f.Header)
+ w.writeBody(f.CopyFunc, Base64)
+ }
+}
+
+func (w *messageWriter) Write(p []byte) (int, error) {
+ if w.err != nil {
+ return 0, errors.New("gomail: cannot write as writer is in error")
+ }
+
+ var n int
+ n, w.err = w.w.Write(p)
+ w.n += int64(n)
+ return n, w.err
+}
+
+func (w *messageWriter) writeString(s string) {
+ n, _ := io.WriteString(w.w, s)
+ w.n += int64(n)
+}
+
+func (w *messageWriter) writeHeader(k string, v ...string) {
+ w.writeString(k)
+ if len(v) == 0 {
+ w.writeString(":\r\n")
+ return
+ }
+ w.writeString(": ")
+
+ // Max header line length is 78 characters in RFC 5322 and 76 characters
+ // in RFC 2047. So for the sake of simplicity we use the 76 characters
+ // limit.
+ charsLeft := 76 - len(k) - len(": ")
+
+ for i, s := range v {
+ // If the line is already too long, insert a newline right away.
+ if charsLeft < 1 {
+ if i == 0 {
+ w.writeString("\r\n ")
+ } else {
+ w.writeString(",\r\n ")
+ }
+ charsLeft = 75
+ } else if i != 0 {
+ w.writeString(", ")
+ charsLeft -= 2
+ }
+
+ // While the header content is too long, fold it by inserting a newline.
+ for len(s) > charsLeft {
+ s = w.writeLine(s, charsLeft)
+ charsLeft = 75
+ }
+ w.writeString(s)
+ if i := lastIndexByte(s, '\n'); i != -1 {
+ charsLeft = 75 - (len(s) - i - 1)
+ } else {
+ charsLeft -= len(s)
+ }
+ }
+ w.writeString("\r\n")
+}
+
+func (w *messageWriter) writeLine(s string, charsLeft int) string {
+ // If there is already a newline before the limit. Write the line.
+ if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
+ w.writeString(s[:i+1])
+ return s[i+1:]
+ }
+
+ for i := charsLeft - 1; i >= 0; i-- {
+ if s[i] == ' ' {
+ w.writeString(s[:i])
+ w.writeString("\r\n ")
+ return s[i+1:]
+ }
+ }
+
+ // We could not insert a newline cleanly so look for a space or a newline
+ // even if it is after the limit.
+ for i := 75; i < len(s); i++ {
+ if s[i] == ' ' {
+ w.writeString(s[:i])
+ w.writeString("\r\n ")
+ return s[i+1:]
+ }
+ if s[i] == '\n' {
+ w.writeString(s[:i+1])
+ return s[i+1:]
+ }
+ }
+
+ // Too bad, no space or newline in the whole string. Just write everything.
+ w.writeString(s)
+ return ""
+}
+
+func (w *messageWriter) writeHeaders(h map[string][]string) {
+ if w.depth == 0 {
+ for k, v := range h {
+ if k != "Bcc" {
+ w.writeHeader(k, v...)
+ }
+ }
+ } else {
+ w.createPart(h)
+ }
+}
+
+func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
+ var subWriter io.Writer
+ if w.depth == 0 {
+ w.writeString("\r\n")
+ subWriter = w.w
+ } else {
+ subWriter = w.partWriter
+ }
+
+ if enc == Base64 {
+ wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
+ w.err = f(wc)
+ wc.Close()
+ } else if enc == Unencoded {
+ w.err = f(subWriter)
+ } else {
+ wc := newQPWriter(subWriter)
+ w.err = f(wc)
+ wc.Close()
+ }
+}
+
+// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
+// RFC 2045, 6.8. (page 25) for base64.
+const maxLineLen = 76
+
+// base64LineWriter limits text encoded in base64 to 76 characters per line
+type base64LineWriter struct {
+ w io.Writer
+ lineLen int
+}
+
+func newBase64LineWriter(w io.Writer) *base64LineWriter {
+ return &base64LineWriter{w: w}
+}
+
+func (w *base64LineWriter) Write(p []byte) (int, error) {
+ n := 0
+ for len(p)+w.lineLen > maxLineLen {
+ w.w.Write(p[:maxLineLen-w.lineLen])
+ w.w.Write([]byte("\r\n"))
+ p = p[maxLineLen-w.lineLen:]
+ n += maxLineLen - w.lineLen
+ w.lineLen = 0
+ }
+
+ w.w.Write(p)
+ w.lineLen += len(p)
+
+ return n + len(p), nil
+}
+
+// Stubbed out for testing.
+var now = time.Now
diff --git a/vendor/gopkg.in/olivere/elastic.v5/.travis.yml b/vendor/gopkg.in/olivere/elastic.v5/.travis.yml
index 31fe3bd12..c2aa4b378 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/.travis.yml
+++ b/vendor/gopkg.in/olivere/elastic.v5/.travis.yml
@@ -13,5 +13,5 @@ before_script:
- mkdir -p /tmp/elasticsearch/config
- cp -r config/* /tmp/elasticsearch/config/
- sudo sysctl -w vm.max_map_count=262144
- - docker run --rm --privileged=true -p 9200:9200 -p 9300:9300 -v "/tmp/elasticsearch/config:/usr/share/elasticsearch/config" -e ES_JAVA_OPTS='-Xms1g -Xmx1g' elasticsearch:5.3.0 elasticsearch >& /dev/null &
+ - docker run --rm --privileged=true -p 9200:9200 -p 9300:9300 -v "/tmp/elasticsearch/config:/usr/share/elasticsearch/config" -e ES_JAVA_OPTS='-Xms1g -Xmx1g' elasticsearch:5.4.0 elasticsearch >& /dev/null &
- sleep 15
diff --git a/vendor/gopkg.in/olivere/elastic.v5/CONTRIBUTORS b/vendor/gopkg.in/olivere/elastic.v5/CONTRIBUTORS
index 39a71f90b..7e63e9eac 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/CONTRIBUTORS
+++ b/vendor/gopkg.in/olivere/elastic.v5/CONTRIBUTORS
@@ -10,18 +10,21 @@
Adam Alix [@adamalix](https://github.com/adamalix)
Adam Weiner [@adamweiner](https://github.com/adamweiner)
Adrian Lungu [@AdrianLungu](https://github.com/AdrianLungu)
+alehano [@alehano](https://github.com/alehano)
Alex [@akotlar](https://github.com/akotlar)
Alexandre Olivier [@aliphen](https://github.com/aliphen)
Alexey Sharov [@nizsheanez](https://github.com/nizsheanez)
AndreKR [@AndreKR](https://github.com/AndreKR)
Andrew Dunham [@andrew-d](https://github.com/andrew-d)
Andrew Gaul [@andrewgaul](https://github.com/andrewgaul)
+Arquivei [@arquivei](https://github.com/arquivei)
Benjamin Fernandes [@LotharSee](https://github.com/LotharSee)
Benjamin Zarzycki [@kf6nux](https://github.com/kf6nux)
Braden Bassingthwaite [@bbassingthwaite-va](https://github.com/bbassingthwaite-va)
Brady Love [@bradylove](https://github.com/bradylove)
Bryan Conklin [@bmconklin](https://github.com/bmconklin)
Bruce Zhou [@brucez-isell](https://github.com/brucez-isell)
+cforbes [@cforbes](https://github.com/cforbes)
Chris M [@tebriel](https://github.com/tebriel)
Christophe Courtaut [@kri5](https://github.com/kri5)
Conrad Pankoff [@deoxxa](https://github.com/deoxxa)
diff --git a/vendor/gopkg.in/olivere/elastic.v5/bulk.go b/vendor/gopkg.in/olivere/elastic.v5/bulk.go
index efb85aeed..eac240ddb 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/bulk.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/bulk.go
@@ -300,13 +300,15 @@ type BulkResponse struct {
// BulkResponseItem is the result of a single bulk request.
type BulkResponseItem struct {
- Index string `json:"_index,omitempty"`
- Type string `json:"_type,omitempty"`
- Id string `json:"_id,omitempty"`
- Version int64 `json:"_version,omitempty"`
- Status int `json:"status,omitempty"`
- Found bool `json:"found,omitempty"`
- Error *ErrorDetails `json:"error,omitempty"`
+ Index string `json:"_index,omitempty"`
+ Type string `json:"_type,omitempty"`
+ Id string `json:"_id,omitempty"`
+ Version int64 `json:"_version,omitempty"`
+ Status int `json:"status,omitempty"`
+ Result string `json:"result,omitempty"`
+ ForcedRefresh bool `json:"forced_refresh,omitempty"`
+ Found bool `json:"found,omitempty"`
+ Error *ErrorDetails `json:"error,omitempty"`
}
// Indexed returns all bulk request results of "index" actions.
diff --git a/vendor/gopkg.in/olivere/elastic.v5/client.go b/vendor/gopkg.in/olivere/elastic.v5/client.go
index 438de164d..df0d9a7dd 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/client.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/client.go
@@ -8,7 +8,6 @@ import (
"bytes"
"context"
"encoding/json"
- "errors"
"fmt"
"net/http"
"net/http/httputil"
@@ -17,11 +16,13 @@ import (
"strings"
"sync"
"time"
+
+ "github.com/pkg/errors"
)
const (
// Version is the current version of Elastic.
- Version = "5.0.36"
+ Version = "5.0.38"
// DefaultURL is the default endpoint of Elasticsearch on the local machine.
// It is used e.g. when initializing a new Client without a specific URL.
@@ -811,7 +812,7 @@ func (c *Client) sniff(timeout time.Duration) error {
c.connsMu.RUnlock()
if len(urls) == 0 {
- return ErrNoClient
+ return errors.Wrap(ErrNoClient, "no URLs found")
}
// Start sniffing on all found URLs
@@ -830,7 +831,7 @@ func (c *Client) sniff(timeout time.Duration) error {
}
case <-time.After(timeout):
// We get here if no cluster responds in time
- return ErrNoClient
+ return errors.Wrap(ErrNoClient, "sniff timeout")
}
}
}
@@ -1063,7 +1064,7 @@ func (c *Client) startupHealthcheck(timeout time.Duration) error {
break
}
}
- return ErrNoClient
+ return errors.Wrap(ErrNoClient, "health check timeout")
}
// next returns the next available connection, or ErrNoClient.
@@ -1102,7 +1103,7 @@ func (c *Client) next() (*conn, error) {
}
// We tried hard, but there is no node available
- return nil, ErrNoClient
+ return nil, errors.Wrap(ErrNoClient, "no avaiable connection")
}
// mustActiveConn returns nil if there is an active connection,
@@ -1116,7 +1117,7 @@ func (c *Client) mustActiveConn() error {
return nil
}
}
- return ErrNoClient
+ return errors.Wrap(ErrNoClient, "no active connection found")
}
// PerformRequest does a HTTP request to Elasticsearch.
@@ -1157,7 +1158,7 @@ func (c *Client) PerformRequest(ctx context.Context, method, path string, params
// Get a connection
conn, err = c.next()
- if err == ErrNoClient {
+ if errors.Cause(err) == ErrNoClient {
n++
if !retried {
// Force a healtcheck as all connections seem to be dead.
@@ -1716,3 +1717,9 @@ func (c *Client) WaitForGreenStatus(timeout string) error {
func (c *Client) WaitForYellowStatus(timeout string) error {
return c.WaitForStatus("yellow", timeout)
}
+
+// IsConnError unwraps the given error value and checks if it is equal to
+// elastic.ErrNoClient.
+func IsConnErr(err error) bool {
+ return errors.Cause(err) == ErrNoClient
+}
diff --git a/vendor/gopkg.in/olivere/elastic.v5/client_test.go b/vendor/gopkg.in/olivere/elastic.v5/client_test.go
index f038b4855..46aa42619 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/client_test.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/client_test.go
@@ -271,7 +271,7 @@ func TestClientHealthcheckStartupTimeout(t *testing.T) {
start := time.Now()
_, err := NewClient(SetURL("http://localhost:9299"), SetHealthcheckTimeoutStartup(5*time.Second))
duration := time.Now().Sub(start)
- if err != ErrNoClient {
+ if !IsConnErr(err) {
t.Fatal(err)
}
if duration < 5*time.Second {
@@ -647,9 +647,9 @@ func TestClientSelectConnAllDead(t *testing.T) {
client.conns[1].MarkAsDead()
// If all connections are dead, next should make them alive again, but
- // still return ErrNoClient when it first finds out.
+ // still return an error when it first finds out.
c, err := client.next()
- if err != ErrNoClient {
+ if !IsConnErr(err) {
t.Fatal(err)
}
if c != nil {
diff --git a/vendor/gopkg.in/olivere/elastic.v5/indices_put_mapping_test.go b/vendor/gopkg.in/olivere/elastic.v5/indices_put_mapping_test.go
index 7f1489697..ffac0d0f2 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/indices_put_mapping_test.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/indices_put_mapping_test.go
@@ -52,7 +52,7 @@ func TestMappingLifecycle(t *testing.T) {
"tweetdoc":{
"properties":{
"field":{
- "type":"string"
+ "type":"keyword"
}
}
}
diff --git a/vendor/gopkg.in/olivere/elastic.v5/run-es-5.4.0.sh b/vendor/gopkg.in/olivere/elastic.v5/run-es-5.4.0.sh
new file mode 100755
index 000000000..6d33c1f13
--- /dev/null
+++ b/vendor/gopkg.in/olivere/elastic.v5/run-es-5.4.0.sh
@@ -0,0 +1 @@
+docker run --rm --privileged=true -p 9200:9200 -p 9300:9300 -v "$PWD/config:/usr/share/elasticsearch/config" -e ES_JAVA_OPTS='-Xms1g -Xmx1g' elasticsearch:5.4.0 elasticsearch
diff --git a/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested.go b/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested.go
index 11e3bb1c6..a95cc2b80 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested.go
@@ -12,12 +12,13 @@ package elastic
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/5.2/query-dsl-nested-query.html
type NestedQuery struct {
- query Query
- path string
- scoreMode string
- boost *float64
- queryName string
- innerHit *InnerHit
+ query Query
+ path string
+ scoreMode string
+ boost *float64
+ queryName string
+ innerHit *InnerHit
+ ignoreUnmapped *bool
}
// NewNestedQuery creates and initializes a new NestedQuery.
@@ -51,6 +52,13 @@ func (q *NestedQuery) InnerHit(innerHit *InnerHit) *NestedQuery {
return q
}
+// IgnoreUnmapped sets the ignore_unmapped option for the filter that ignores
+// unmapped nested fields
+func (q *NestedQuery) IgnoreUnmapped(value bool) *NestedQuery {
+ q.ignoreUnmapped = &value
+ return q
+}
+
// Source returns JSON for the query.
func (q *NestedQuery) Source() (interface{}, error) {
query := make(map[string]interface{})
@@ -74,6 +82,9 @@ func (q *NestedQuery) Source() (interface{}, error) {
if q.queryName != "" {
nq["_name"] = q.queryName
}
+ if q.ignoreUnmapped != nil {
+ nq["ignore_unmapped"] = *q.ignoreUnmapped
+ }
if q.innerHit != nil {
src, err := q.innerHit.Source()
if err != nil {
diff --git a/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested_test.go b/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested_test.go
index af9740553..c7a5322a6 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested_test.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/search_queries_nested_test.go
@@ -50,3 +50,37 @@ func TestNestedQueryWithInnerHit(t *testing.T) {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
+
+func TestNestedQueryWithIgnoreUnmapped(t *testing.T) {
+ var tests = []struct {
+ query *BoolQuery
+ expected string
+ }{
+ {
+ NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test"))),
+ `{"bool":{"must":{"nested":{"path":"path","query":{"term":{"test":"test"}}}}}}`,
+ },
+ {
+ NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test")).IgnoreUnmapped(true)),
+ `{"bool":{"must":{"nested":{"ignore_unmapped":true,"path":"path","query":{"term":{"test":"test"}}}}}}`,
+ },
+ {
+ NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test")).IgnoreUnmapped(false)),
+ `{"bool":{"must":{"nested":{"ignore_unmapped":false,"path":"path","query":{"term":{"test":"test"}}}}}}`,
+ },
+ }
+ for _, test := range tests {
+ src, err := test.query.Source()
+ if err != nil {
+ t.Fatal(err)
+ }
+ data, err := json.Marshal(src)
+ if err != nil {
+ t.Fatalf("marshaling to JSON failed: %v", err)
+ }
+ got := string(data)
+ if got != test.expected {
+ t.Errorf("expected\n%s\n,got:\n%s", test.expected, got)
+ }
+ }
+}
diff --git a/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix.go b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix.go
index dcf20ea70..0d5077553 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix.go
@@ -50,7 +50,7 @@ func (q *PrefixQuery) Source() (interface{}, error) {
query[q.name] = q.prefix
} else {
subQuery := make(map[string]interface{})
- subQuery["prefix"] = q.prefix
+ subQuery["value"] = q.prefix
if q.boost != nil {
subQuery["boost"] = *q.boost
}
diff --git a/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_example_test.go b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_example_test.go
new file mode 100644
index 000000000..1a421784e
--- /dev/null
+++ b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_example_test.go
@@ -0,0 +1,35 @@
+// Copyright 2012-present Oliver Eilhard. All rights reserved.
+// Use of this source code is governed by a MIT-license.
+// See http://olivere.mit-license.org/license.txt for details.
+
+package elastic_test
+
+import (
+ "context"
+
+ "gopkg.in/olivere/elastic.v5"
+)
+
+func ExamplePrefixQuery() {
+ // Get a client to the local Elasticsearch instance.
+ client, err := elastic.NewClient()
+ if err != nil {
+ // Handle error
+ panic(err)
+ }
+
+ // Define wildcard query
+ q := elastic.NewPrefixQuery("user", "oli")
+ q = q.QueryName("my_query_name")
+
+ searchResult, err := client.Search().
+ Index("twitter").
+ Query(q).
+ Pretty(true).
+ Do(context.Background())
+ if err != nil {
+ // Handle error
+ panic(err)
+ }
+ _ = searchResult
+}
diff --git a/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_test.go b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_test.go
index dcd47e2a1..78d27b600 100644
--- a/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_test.go
+++ b/vendor/gopkg.in/olivere/elastic.v5/search_queries_prefix_test.go
@@ -38,7 +38,7 @@ func TestPrefixQueryWithOptions(t *testing.T) {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
- expected := `{"prefix":{"user":{"_name":"my_query_name","prefix":"ki"}}}`
+ expected := `{"prefix":{"user":{"_name":"my_query_name","value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}