Fix #117: Implement email alerts

This commit is contained in:
TwiN
2021-12-02 21:05:17 -05:00
parent 0331c18401
commit f6336eac4e
53 changed files with 3062 additions and 626 deletions

17
vendor/gopkg.in/mail.v2/.gitignore generated vendored Normal file
View File

@ -0,0 +1,17 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# IDE's
.idea/

25
vendor/gopkg.in/mail.v2/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,25 @@
language: go
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- master
# safelist
branches:
only:
- master
- v2
notifications:
email: false
before_install:
- mkdir -p $GOPATH/src/gopkg.in &&
ln -s ../github.com/go-mail/mail $GOPATH/src/gopkg.in/mail.v2

88
vendor/gopkg.in/mail.v2/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,88 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## *Unreleased*
## [2.3.1] - 2018-11-12
### Fixed
- #39: Reverts addition of Go modules `go.mod` manifest.
## [2.3.0] - 2018-11-10
### Added
- #12: Adds `SendError` to provide additional info about the cause and index of
a failed attempt to transmit a batch of messages.
- go-gomail#78: Adds new `Message` methods for attaching and embedding
`io.Reader`s: `AttachReader` and `EmbedReader`.
### Fixed
- #26: Fixes RFC 1341 compliance by properly capitalizing the
`MIME-Version` header.
- #30: Fixes IO errors being silently dropped in `Message.WriteTo`.
## [2.2.0] - 2018-03-01
### Added
- #20: Adds `Message.SetBoundary` to allow specifying a custom MIME boundary.
- #22: Adds `Message.SetBodyWriter` to make it easy to use text/template and
html/template for message bodies. Contributed by Quantcast.
- #25: Adds `Dialer.StartTLSPolicy` so that `MandatoryStartTLS` can be required,
or `NoStartTLS` can disable it. Contributed by Quantcast.
## [2.1.0] - 2017-12-14
### Added
- go-gomail#40: Adds `Dialer.LocalName` field to allow specifying the hostname
sent with SMTP's HELO command.
- go-gomail#47: `Message.SetBody`, `Message.AddAlternative`, and
`Message.AddAlternativeWriter` allow specifying the encoding of message parts.
- `Dialer.Dial`'s returned `SendCloser` automatically redials after a timeout.
- go-gomail#55, go-gomail#56: Adds `Rename` to allow specifying filename
of an attachment.
- go-gomail#100: Exports `NetDialTimeout` to allow setting a custom dialer.
- go-gomail#70: Adds `Dialer.Timeout` field to allow specifying a timeout for
dials, reads, and writes.
### Changed
- go-gomail#52: `Dialer.Dial` automatically uses CRAM-MD5 when available.
- `Dialer.Dial` specifies a default timeout of 10 seconds.
- Gomail is forked from <https://github.com/go-gomail/gomail/> to
<https://github.com/go-mail/mail/>.
### Deprecated
- go-gomail#52: `NewPlainDialer` is deprecated in favor of `NewDialer`.
### Fixed
- go-gomail#41, go-gomail#42: Fixes a panic when a `Message` contains a
nil header.
- go-gomail#44: Fixes `AddAlternativeWriter` replacing the message body instead
of adding a body part.
- go-gomail#53: Folds long header lines for RFC 2047 compliance.
- go-gomail#54: Fixes `Message.FormatAddress` when name is blank.
## [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.

20
vendor/gopkg.in/mail.v2/CONTRIBUTING.md generated vendored Normal file
View File

@ -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.

20
vendor/gopkg.in/mail.v2/LICENSE generated vendored Normal file
View File

@ -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.

129
vendor/gopkg.in/mail.v2/README.md generated vendored Normal file
View File

@ -0,0 +1,129 @@
# Gomail
[![Build Status](https://travis-ci.org/go-mail/mail.svg?branch=master)](https://travis-ci.org/go-mail/mail) [![Code Coverage](http://gocover.io/_badge/github.com/go-mail/mail)](http://gocover.io/github.com/go-mail/mail) [![Documentation](https://godoc.org/github.com/go-mail/mail?status.svg)](https://godoc.org/github.com/go-mail/mail)
This is an actively maintained fork of [Gomail][1] and includes fixes and
improvements for a number of outstanding issues. The current progress is
as follows:
- [x] Timeouts and retries can be specified outside of the 10 second default.
- [x] Proxying is supported through specifying a custom [NetDialTimeout][2].
- [ ] Filenames are properly encoded for non-ASCII characters.
- [ ] Email addresses are properly encoded for non-ASCII characters.
- [ ] Embedded files and attachments are tested for their existence.
- [ ] An `io.Reader` can be supplied when embedding and attaching files.
See [Transitioning Existing Codebases][3] for more information on switching.
[1]: https://github.com/go-gomail/gomail
[2]: https://godoc.org/gopkg.in/mail.v2#NetDialTimeout
[3]: #transitioning-existing-codebases
## 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 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/github.com/go-mail/mail
## Download
If you're already using a dependency manager, like [dep][dep], use the following
import path:
```
github.com/go-mail/mail
```
If you *aren't* using vendoring, `go get` the [Gopkg.in](http://gopkg.in)
import path:
```
gopkg.in/mail.v2
```
[dep]: https://github.com/golang/dep#readme
## Examples
See the [examples in the documentation](https://godoc.org/github.com/go-mail/mail#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`:
```go
package main
import (
"crypto/tls"
"gopkg.in/mail.v2"
)
func main() {
d := mail.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.
### Transitioning Existing Codebases
If you're already using the original Gomail, switching is as easy as updating
the import line to:
```
import gomail "gopkg.in/mail.v2"
```
## Contribute
Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
more info.
## Change log
See [CHANGELOG.md](CHANGELOG.md).
## License
[MIT](LICENSE)
## Support & Contact
You can ask questions on the [Gomail
thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
in the Go mailing-list.

49
vendor/gopkg.in/mail.v2/auth.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package mail
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)
}
}

6
vendor/gopkg.in/mail.v2/doc.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
// Package gomail provides a simple interface to compose emails and to mail them
// efficiently.
//
// More info on Github: https://github.com/go-mail/mail
//
package mail

16
vendor/gopkg.in/mail.v2/errors.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package mail
import "fmt"
// A SendError represents the failure to transmit a Message, detailing the cause
// of the failure and index of the Message within a batch.
type SendError struct {
// Index specifies the index of the Message within a batch.
Index uint
Cause error
}
func (err *SendError) Error() string {
return fmt.Sprintf("gomail: could not send email %d: %v",
err.Index+1, err.Cause)
}

359
vendor/gopkg.in/mail.v2/message.go generated vendored Normal file
View File

@ -0,0 +1,359 @@
package mail
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
boundary string
}
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"
)
// SetBoundary sets a custom multipart boundary.
func (m *Message) SetBoundary(boundary string) {
m.boundary = boundary
}
// 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, SetBodyWriter, AddAlternative or AddAlternativeWriter.
func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
m.SetBodyWriter(contentType, newCopier(body), settings...)
}
// SetBodyWriter sets the body of the message. It can be useful with the
// text/template or html/template packages.
func (m *Message) SetBodyWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
m.parts = []*part{m.newPart(contentType, f, 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.SetBodyWriter, 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
}
}
// AttachReader attaches a file using an io.Reader
func (m *Message) AttachReader(name string, r io.Reader, settings ...FileSetting) {
m.attachments = m.appendFile(m.attachments, fileFromReader(name, r), settings)
}
// Attach attaches the files to the email.
func (m *Message) Attach(filename string, settings ...FileSetting) {
m.attachments = m.appendFile(m.attachments, fileFromFilename(filename), settings)
}
// EmbedReader embeds the images to the email.
func (m *Message) EmbedReader(name string, r io.Reader, settings ...FileSetting) {
m.embedded = m.appendFile(m.embedded, fileFromReader(name, r), settings)
}
// Embed embeds the images to the email.
func (m *Message) Embed(filename string, settings ...FileSetting) {
m.embedded = m.appendFile(m.embedded, fileFromFilename(filename), settings)
}
func fileFromFilename(name string) *file {
return &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()
},
}
}
func fileFromReader(name string, r io.Reader) *file {
return &file{
Name: filepath.Base(name),
Header: make(map[string][]string),
CopyFunc: func(w io.Writer) error {
if _, err := io.Copy(w, r); err != nil {
return err
}
return nil
},
}
}
func (m *Message) appendFile(list []*file, f *file, settings []FileSetting) []*file {
for _, s := range settings {
s(f)
}
if list == nil {
return []*file{f}
}
return append(list, f)
}

21
vendor/gopkg.in/mail.v2/mime.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
// +build go1.5
package mail
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
)

25
vendor/gopkg.in/mail.v2/mime_go14.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// +build !go1.5
package mail
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
}
)

116
vendor/gopkg.in/mail.v2/send.go generated vendored Normal file
View File

@ -0,0 +1,116 @@
package mail
import (
"errors"
"fmt"
"io"
stdmail "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 &SendError{Cause: err, Index: uint(i)}
}
}
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 := stdmail.ParseAddress(field)
if err != nil {
return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
}
return addr.Address, nil
}

292
vendor/gopkg.in/mail.v2/smtp.go generated vendored Normal file
View File

@ -0,0 +1,292 @@
package mail
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
// TLSConfig represents the TLS configuration used for the TLS (when the
// STARTTLS extension is used) or SSL connection.
TLSConfig *tls.Config
// StartTLSPolicy represents the TLS security level required to
// communicate with the SMTP server.
//
// This defaults to OpportunisticStartTLS for backwards compatibility,
// but we recommend MandatoryStartTLS for all modern SMTP servers.
//
// This option has no effect if SSL is set to true.
StartTLSPolicy StartTLSPolicy
// LocalName is the hostname sent to the SMTP server with the HELO command.
// By default, "localhost" is sent.
LocalName string
// Timeout to use for read/write operations. Defaults to 10 seconds, can
// be set to 0 to disable timeouts.
Timeout time.Duration
// Whether we should retry mailing if the connection returned an error,
// defaults to true.
RetryFailure bool
}
// 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,
Timeout: 10 * time.Second,
RetryFailure: true,
}
}
// 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)
}
// NetDialTimeout specifies the DialTimeout function to establish a connection
// to the SMTP server. This can be used to override dialing in the case that a
// proxy or other special behavior is needed.
var NetDialTimeout = net.DialTimeout
// 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), d.Timeout)
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.Timeout > 0 {
conn.SetDeadline(time.Now().Add(d.Timeout))
}
if d.LocalName != "" {
if err := c.Hello(d.LocalName); err != nil {
return nil, err
}
}
if !d.SSL && d.StartTLSPolicy != NoStartTLS {
ok, _ := c.Extension("STARTTLS")
if !ok && d.StartTLSPolicy == MandatoryStartTLS {
err := StartTLSUnsupportedError{
Policy: d.StartTLSPolicy}
return nil, err
}
if 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, conn, d}, nil
}
func (d *Dialer) tlsConfig() *tls.Config {
if d.TLSConfig == nil {
return &tls.Config{ServerName: d.Host}
}
return d.TLSConfig
}
// StartTLSPolicy constants are valid values for Dialer.StartTLSPolicy.
type StartTLSPolicy int
const (
// OpportunisticStartTLS means that SMTP transactions are encrypted if
// STARTTLS is supported by the SMTP server. Otherwise, messages are
// sent in the clear. This is the default setting.
OpportunisticStartTLS StartTLSPolicy = iota
// MandatoryStartTLS means that SMTP transactions must be encrypted.
// SMTP transactions are aborted unless STARTTLS is supported by the
// SMTP server.
MandatoryStartTLS
// NoStartTLS means encryption is disabled and messages are sent in the
// clear.
NoStartTLS = -1
)
func (policy *StartTLSPolicy) String() string {
switch *policy {
case OpportunisticStartTLS:
return "OpportunisticStartTLS"
case MandatoryStartTLS:
return "MandatoryStartTLS"
case NoStartTLS:
return "NoStartTLS"
default:
return fmt.Sprintf("StartTLSPolicy:%v", *policy)
}
}
// StartTLSUnsupportedError is returned by Dial when connecting to an SMTP
// server that does not support STARTTLS.
type StartTLSUnsupportedError struct {
Policy StartTLSPolicy
}
func (e StartTLSUnsupportedError) Error() string {
return "gomail: " + e.Policy.String() + " required, but " +
"SMTP server does not support STARTTLS"
}
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
conn net.Conn
d *Dialer
}
func (c *smtpSender) retryError(err error) bool {
if !c.d.RetryFailure {
return false
}
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return true
}
return err == io.EOF
}
func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
if c.d.Timeout > 0 {
c.conn.SetDeadline(time.Now().Add(c.d.Timeout))
}
if err := c.Mail(from); err != nil {
if c.retryError(err) {
// 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 (
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
}

313
vendor/gopkg.in/mail.v2/writeto.go generated vendored Normal file
View File

@ -0,0 +1,313 @@
package mail
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", m.boundary)
}
if m.hasRelatedPart() {
w.openMultipart("related", m.boundary)
}
if m.hasAlternativePart() {
w.openMultipart("alternative", m.boundary)
}
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, boundary string) {
mw := multipart.NewWriter(w)
if boundary != "" {
mw.SetBoundary(boundary)
}
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) {
if w.err != nil { // do nothing when in error
return
}
var n int
n, w.err = 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