Initialize module and dependencies
This commit is contained in:
381
vendor/github.com/emersion/go-message/mail/header.go
generated
vendored
Normal file
381
vendor/github.com/emersion/go-message/mail/header.go
generated
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/emersion/go-message"
|
||||
)
|
||||
|
||||
const dateLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
|
||||
type headerParser struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (p *headerParser) len() int {
|
||||
return len(p.s)
|
||||
}
|
||||
|
||||
func (p *headerParser) empty() bool {
|
||||
return p.len() == 0
|
||||
}
|
||||
|
||||
func (p *headerParser) peek() byte {
|
||||
return p.s[0]
|
||||
}
|
||||
|
||||
func (p *headerParser) consume(c byte) bool {
|
||||
if p.empty() || p.peek() != c {
|
||||
return false
|
||||
}
|
||||
p.s = p.s[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
// skipSpace skips the leading space and tab characters.
|
||||
func (p *headerParser) skipSpace() {
|
||||
p.s = strings.TrimLeft(p.s, " \t")
|
||||
}
|
||||
|
||||
// skipCFWS skips CFWS as defined in RFC5322. It returns false if the CFWS is
|
||||
// malformed.
|
||||
func (p *headerParser) skipCFWS() bool {
|
||||
p.skipSpace()
|
||||
|
||||
for {
|
||||
if !p.consume('(') {
|
||||
break
|
||||
}
|
||||
|
||||
if _, ok := p.consumeComment(); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
p.skipSpace()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *headerParser) consumeComment() (string, bool) {
|
||||
// '(' already consumed.
|
||||
depth := 1
|
||||
|
||||
var comment string
|
||||
for {
|
||||
if p.empty() || depth == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if p.peek() == '\\' && p.len() > 1 {
|
||||
p.s = p.s[1:]
|
||||
} else if p.peek() == '(' {
|
||||
depth++
|
||||
} else if p.peek() == ')' {
|
||||
depth--
|
||||
}
|
||||
|
||||
if depth > 0 {
|
||||
comment += p.s[:1]
|
||||
}
|
||||
|
||||
p.s = p.s[1:]
|
||||
}
|
||||
|
||||
return comment, depth == 0
|
||||
}
|
||||
|
||||
func (p *headerParser) parseAtomText(dot bool) (string, error) {
|
||||
i := 0
|
||||
for {
|
||||
r, size := utf8.DecodeRuneInString(p.s[i:])
|
||||
if size == 1 && r == utf8.RuneError {
|
||||
return "", fmt.Errorf("mail: invalid UTF-8 in atom-text: %q", p.s)
|
||||
} else if size == 0 || !isAtext(r, dot) {
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if i == 0 {
|
||||
return "", errors.New("mail: invalid string")
|
||||
}
|
||||
|
||||
var atom string
|
||||
atom, p.s = p.s[:i], p.s[i:]
|
||||
return atom, nil
|
||||
}
|
||||
|
||||
func isAtext(r rune, dot bool) bool {
|
||||
switch r {
|
||||
case '.':
|
||||
return dot
|
||||
// RFC 5322 3.2.3 specials
|
||||
case '(', ')', '[', ']', ';', '@', '\\', ',':
|
||||
return false
|
||||
case '<', '>', '"', ':':
|
||||
return false
|
||||
}
|
||||
return isVchar(r)
|
||||
}
|
||||
|
||||
// isVchar reports whether r is an RFC 5322 VCHAR character.
|
||||
func isVchar(r rune) bool {
|
||||
// Visible (printing) characters
|
||||
return '!' <= r && r <= '~' || isMultibyte(r)
|
||||
}
|
||||
|
||||
// isMultibyte reports whether r is a multi-byte UTF-8 character
|
||||
// as supported by RFC 6532
|
||||
func isMultibyte(r rune) bool {
|
||||
return r >= utf8.RuneSelf
|
||||
}
|
||||
|
||||
func (p *headerParser) parseNoFoldLiteral() (string, error) {
|
||||
if !p.consume('[') {
|
||||
return "", errors.New("mail: missing '[' in no-fold-literal")
|
||||
}
|
||||
|
||||
i := 0
|
||||
for {
|
||||
r, size := utf8.DecodeRuneInString(p.s[i:])
|
||||
if size == 1 && r == utf8.RuneError {
|
||||
return "", fmt.Errorf("mail: invalid UTF-8 in no-fold-literal: %q", p.s)
|
||||
} else if size == 0 || !isDtext(r) {
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
var lit string
|
||||
lit, p.s = p.s[:i], p.s[i:]
|
||||
|
||||
if !p.consume(']') {
|
||||
return "", errors.New("mail: missing ']' in no-fold-literal")
|
||||
}
|
||||
return "[" + lit + "]", nil
|
||||
}
|
||||
|
||||
func isDtext(r rune) bool {
|
||||
switch r {
|
||||
case '[', ']', '\\':
|
||||
return false
|
||||
}
|
||||
return isVchar(r)
|
||||
}
|
||||
|
||||
func (p *headerParser) parseMsgID() (string, error) {
|
||||
if !p.skipCFWS() {
|
||||
return "", errors.New("mail: malformed parenthetical comment")
|
||||
}
|
||||
|
||||
if !p.consume('<') {
|
||||
return "", errors.New("mail: missing '<' in msg-id")
|
||||
}
|
||||
|
||||
left, err := p.parseAtomText(true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !p.consume('@') {
|
||||
return "", errors.New("mail: missing '@' in msg-id")
|
||||
}
|
||||
|
||||
var right string
|
||||
if !p.empty() && p.peek() == '[' {
|
||||
// no-fold-literal
|
||||
right, err = p.parseNoFoldLiteral()
|
||||
} else {
|
||||
right, err = p.parseAtomText(true)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !p.consume('>') {
|
||||
return "", errors.New("mail: missing '>' in msg-id")
|
||||
}
|
||||
|
||||
if !p.skipCFWS() {
|
||||
return "", errors.New("mail: malformed parenthetical comment")
|
||||
}
|
||||
|
||||
return left + "@" + right, nil
|
||||
}
|
||||
|
||||
// A Header is a mail header.
|
||||
type Header struct {
|
||||
message.Header
|
||||
}
|
||||
|
||||
// HeaderFromMap creates a header from a map of header fields.
|
||||
//
|
||||
// This function is provided for interoperability with the standard library.
|
||||
// If possible, ReadHeader should be used instead to avoid loosing information.
|
||||
// The map representation looses the ordering of the fields, the capitalization
|
||||
// of the header keys, and the whitespace of the original header.
|
||||
func HeaderFromMap(m map[string][]string) Header {
|
||||
return Header{message.HeaderFromMap(m)}
|
||||
}
|
||||
|
||||
// AddressList parses the named header field as a list of addresses. If the
|
||||
// header field is missing, it returns nil.
|
||||
//
|
||||
// This can be used on From, Sender, Reply-To, To, Cc and Bcc header fields.
|
||||
func (h *Header) AddressList(key string) ([]*Address, error) {
|
||||
v := h.Get(key)
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return ParseAddressList(v)
|
||||
}
|
||||
|
||||
// SetAddressList formats the named header field to the provided list of
|
||||
// addresses.
|
||||
//
|
||||
// This can be used on From, Sender, Reply-To, To, Cc and Bcc header fields.
|
||||
func (h *Header) SetAddressList(key string, addrs []*Address) {
|
||||
if len(addrs) > 0 {
|
||||
h.Set(key, formatAddressList(addrs))
|
||||
} else {
|
||||
h.Del(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Date parses the Date header field. If the header field is missing, it
|
||||
// returns the zero time.
|
||||
func (h *Header) Date() (time.Time, error) {
|
||||
v := h.Get("Date")
|
||||
if v == "" {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
return mail.ParseDate(v)
|
||||
}
|
||||
|
||||
// SetDate formats the Date header field.
|
||||
func (h *Header) SetDate(t time.Time) {
|
||||
if !t.IsZero() {
|
||||
h.Set("Date", t.Format(dateLayout))
|
||||
} else {
|
||||
h.Del("Date")
|
||||
}
|
||||
}
|
||||
|
||||
// Subject parses the Subject header field. If there is an error, the raw field
|
||||
// value is returned alongside the error.
|
||||
func (h *Header) Subject() (string, error) {
|
||||
return h.Text("Subject")
|
||||
}
|
||||
|
||||
// SetSubject formats the Subject header field.
|
||||
func (h *Header) SetSubject(s string) {
|
||||
h.SetText("Subject", s)
|
||||
}
|
||||
|
||||
// MessageID parses the Message-ID field. It returns the message identifier,
|
||||
// without the angle brackets. If the message doesn't have a Message-ID header
|
||||
// field, it returns an empty string.
|
||||
func (h *Header) MessageID() (string, error) {
|
||||
v := h.Get("Message-Id")
|
||||
if v == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
p := headerParser{v}
|
||||
return p.parseMsgID()
|
||||
}
|
||||
|
||||
// MsgIDList parses a list of message identifiers. It returns message
|
||||
// identifiers without angle brackets. If the header field is missing, it
|
||||
// returns nil.
|
||||
//
|
||||
// This can be used on In-Reply-To and References header fields.
|
||||
func (h *Header) MsgIDList(key string) ([]string, error) {
|
||||
v := h.Get(key)
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
p := headerParser{v}
|
||||
var l []string
|
||||
for !p.empty() {
|
||||
msgID, err := p.parseMsgID()
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
l = append(l, msgID)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// GenerateMessageID wraps GenerateMessageIDWithHostname and therefore uses the
|
||||
// hostname of the local machine. This is done to not break existing software.
|
||||
// Wherever possible better use GenerateMessageIDWithHostname, because the local
|
||||
// hostname of a machine tends to not be unique nor a FQDN which especially
|
||||
// brings problems with spam filters.
|
||||
func (h *Header) GenerateMessageID() error {
|
||||
var err error
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.GenerateMessageIDWithHostname(hostname)
|
||||
}
|
||||
|
||||
// GenerateMessageIDWithHostname generates an RFC 2822-compliant Message-Id
|
||||
// based on the informational draft "Recommendations for generating Message
|
||||
// IDs", it takes an hostname as argument, so that software using this library
|
||||
// could use a hostname they know to be unique
|
||||
func (h *Header) GenerateMessageIDWithHostname(hostname string) error {
|
||||
now := uint64(time.Now().UnixNano())
|
||||
|
||||
nonceByte := make([]byte, 8)
|
||||
if _, err := rand.Read(nonceByte); err != nil {
|
||||
return err
|
||||
}
|
||||
nonce := binary.BigEndian.Uint64(nonceByte)
|
||||
|
||||
msgID := fmt.Sprintf("%s.%s@%s", base36(now), base36(nonce), hostname)
|
||||
h.SetMessageID(msgID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func base36(input uint64) string {
|
||||
return strings.ToUpper(strconv.FormatUint(input, 36))
|
||||
}
|
||||
|
||||
// SetMessageID sets the Message-ID field. id is the message identifier,
|
||||
// without the angle brackets.
|
||||
func (h *Header) SetMessageID(id string) {
|
||||
if id != "" {
|
||||
h.Set("Message-Id", "<"+id+">")
|
||||
} else {
|
||||
h.Del("Message-Id")
|
||||
}
|
||||
}
|
||||
|
||||
// SetMsgIDList formats a list of message identifiers. Message identifiers
|
||||
// don't include angle brackets.
|
||||
//
|
||||
// This can be used on In-Reply-To and References header fields.
|
||||
func (h *Header) SetMsgIDList(key string, l []string) {
|
||||
if len(l) > 0 {
|
||||
h.Set(key, "<"+strings.Join(l, "> <")+">")
|
||||
} else {
|
||||
h.Del(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy creates a stand-alone copy of the header.
|
||||
func (h *Header) Copy() Header {
|
||||
return Header{h.Header.Copy()}
|
||||
}
|
||||
Reference in New Issue
Block a user