Initialize module and dependencies

This commit is contained in:
dwrz
2026-01-04 20:57:40 +00:00
commit a3b390c008
514 changed files with 310495 additions and 0 deletions

20
vendor/github.com/emersion/go-message/.build.yml generated vendored Normal file
View File

@@ -0,0 +1,20 @@
image: alpine/latest
packages:
- go
sources:
- https://github.com/emersion/go-message
artifacts:
- coverage.html
tasks:
- build: |
cd go-message
go build -v ./...
- test: |
cd go-message
go test -coverprofile=coverage.txt -covermode=atomic ./...
- coverage: |
cd go-message
go tool cover -html=coverage.txt -o ~/coverage.html
- gofmt: |
cd go-message
test -z $(gofmt -l .)

24
vendor/github.com/emersion/go-message/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

21
vendor/github.com/emersion/go-message/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 emersion
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
vendor/github.com/emersion/go-message/README.md generated vendored Normal file
View File

@@ -0,0 +1,30 @@
# go-message
[![Go Reference](https://pkg.go.dev/badge/github.com/emersion/go-message.svg)](https://pkg.go.dev/github.com/emersion/go-message)
A Go library for the Internet Message Format. It implements:
* [RFC 5322]: Internet Message Format
* [RFC 2045], [RFC 2046] and [RFC 2047]: Multipurpose Internet Mail Extensions
* [RFC 2183]: Content-Disposition Header Field
## Features
* Streaming API
* Automatic encoding and charset handling (to decode all charsets, add
`import _ "github.com/emersion/go-message/charset"` to your application)
* A [`mail`](https://godocs.io/github.com/emersion/go-message/mail) subpackage
to read and write mail messages
* DKIM-friendly
* A [`textproto`](https://godocs.io/github.com/emersion/go-message/textproto)
subpackage that just implements the wire format
## License
MIT
[RFC 5322]: https://tools.ietf.org/html/rfc5322
[RFC 2045]: https://tools.ietf.org/html/rfc2045
[RFC 2046]: https://tools.ietf.org/html/rfc2046
[RFC 2047]: https://tools.ietf.org/html/rfc2047
[RFC 2183]: https://tools.ietf.org/html/rfc2183

66
vendor/github.com/emersion/go-message/charset.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
package message
import (
"errors"
"fmt"
"io"
"mime"
"strings"
)
type UnknownCharsetError struct {
e error
}
func (u UnknownCharsetError) Unwrap() error { return u.e }
func (u UnknownCharsetError) Error() string {
return "unknown charset: " + u.e.Error()
}
// IsUnknownCharset returns a boolean indicating whether the error is known to
// report that the charset advertised by the entity is unknown.
func IsUnknownCharset(err error) bool {
return errors.As(err, new(UnknownCharsetError))
}
// CharsetReader, if non-nil, defines a function to generate charset-conversion
// readers, converting from the provided charset into UTF-8. Charsets are always
// lower-case. utf-8 and us-ascii charsets are handled by default. One of the
// the CharsetReader's result values must be non-nil.
//
// Importing github.com/emersion/go-message/charset will set CharsetReader to
// a function that handles most common charsets. Alternatively, CharsetReader
// can be set to e.g. golang.org/x/net/html/charset.NewReaderLabel.
var CharsetReader func(charset string, input io.Reader) (io.Reader, error)
// charsetReader calls CharsetReader if non-nil.
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
charset = strings.ToLower(charset)
if charset == "utf-8" || charset == "us-ascii" {
return input, nil
}
if CharsetReader != nil {
r, err := CharsetReader(charset, input)
if err != nil {
return r, UnknownCharsetError{err}
}
return r, nil
}
return input, UnknownCharsetError{fmt.Errorf("message: unhandled charset %q", charset)}
}
// decodeHeader decodes an internationalized header field. If it fails, it
// returns the input string and the error.
func decodeHeader(s string) (string, error) {
wordDecoder := mime.WordDecoder{CharsetReader: charsetReader}
dec, err := wordDecoder.DecodeHeader(s)
if err != nil {
return s, err
}
return dec, nil
}
func encodeHeader(s string) string {
return mime.QEncoding.Encode("utf-8", s)
}

View File

@@ -0,0 +1,64 @@
// Package charset provides functions to decode and encode charsets.
//
// It imports all supported charsets, which adds about 1MiB to binaries size.
// Importing the package automatically sets message.CharsetReader.
package charset
import (
"fmt"
"io"
"strings"
"github.com/emersion/go-message"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/ianaindex"
"golang.org/x/text/encoding/unicode"
)
// Quirks table for charsets not handled by ianaindex
//
// A nil entry disables the charset.
//
// For aliases, see
// https://www.iana.org/assignments/character-sets/character-sets.xhtml
var charsets = map[string]encoding.Encoding{
"ansi_x3.110-1983": charmap.ISO8859_1, // see RFC 1345 page 62, mostly superset of ISO 8859-1
"x-utf_8j": unicode.UTF8, // alias for UTF-8, see https://icu4c-demos.unicode.org/icu-bin/convexp?s=ALL
}
func init() {
message.CharsetReader = Reader
}
// Reader returns an io.Reader that converts the provided charset to UTF-8.
func Reader(charset string, input io.Reader) (io.Reader, error) {
var err error
enc, ok := charsets[strings.ToLower(charset)]
if ok && enc == nil {
return nil, fmt.Errorf("charset %q: charset is disabled", charset)
} else if !ok {
enc, err = ianaindex.MIME.Encoding(charset)
}
if enc == nil {
enc, err = ianaindex.MIME.Encoding("cs" + charset)
}
if enc == nil {
enc, err = htmlindex.Get(charset)
}
if err != nil {
return nil, fmt.Errorf("charset %q: %v", charset, err)
}
// See https://github.com/golang/go/issues/19421
if enc == nil {
return nil, fmt.Errorf("charset %q: unsupported charset", charset)
}
return enc.NewDecoder().Reader(input), nil
}
// RegisterEncoding registers an encoding. This is intended to be called from
// the init function in packages that want to support additional charsets.
func RegisterEncoding(name string, enc encoding.Encoding) {
charsets[name] = enc
}

155
vendor/github.com/emersion/go-message/encoding.go generated vendored Normal file
View File

@@ -0,0 +1,155 @@
package message
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"mime/quotedprintable"
"strings"
)
type UnknownEncodingError struct {
e error
}
func (u UnknownEncodingError) Unwrap() error { return u.e }
func (u UnknownEncodingError) Error() string {
return "encoding error: " + u.e.Error()
}
// IsUnknownEncoding returns a boolean indicating whether the error is known to
// report that the encoding advertised by the entity is unknown.
func IsUnknownEncoding(err error) bool {
return errors.As(err, new(UnknownEncodingError))
}
func encodingReader(enc string, r io.Reader) (io.Reader, error) {
var dec io.Reader
switch strings.ToLower(enc) {
case "quoted-printable":
dec = quotedprintable.NewReader(r)
case "base64":
wrapped := &whitespaceReplacingReader{wrapped: r}
dec = base64.NewDecoder(base64.StdEncoding, wrapped)
case "7bit", "8bit", "binary", "":
dec = r
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return dec, nil
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error {
return nil
}
func encodingWriter(enc string, w io.Writer) (io.WriteCloser, error) {
var wc io.WriteCloser
switch strings.ToLower(enc) {
case "quoted-printable":
wc = quotedprintable.NewWriter(w)
case "base64":
wc = base64.NewEncoder(base64.StdEncoding, &lineWrapper{w: w, maxLineLen: 76})
case "7bit", "8bit":
wc = nopCloser{&lineWrapper{w: w, maxLineLen: 998}}
case "binary", "":
wc = nopCloser{w}
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return wc, nil
}
// whitespaceReplacingReader replaces space and tab characters with a LF so
// base64 bodies with a continuation indent can be decoded by the base64 decoder
// even though it is against the spec.
type whitespaceReplacingReader struct {
wrapped io.Reader
}
func (r *whitespaceReplacingReader) Read(p []byte) (int, error) {
n, err := r.wrapped.Read(p)
for i := 0; i < n; i++ {
if p[i] == ' ' || p[i] == '\t' {
p[i] = '\n'
}
}
return n, err
}
type lineWrapper struct {
w io.Writer
maxLineLen int
curLineLen int
cr bool
}
func (w *lineWrapper) Write(b []byte) (int, error) {
var written int
for len(b) > 0 {
var l []byte
l, b = cutLine(b, w.maxLineLen-w.curLineLen)
lf := bytes.HasSuffix(l, []byte("\n"))
l = bytes.TrimSuffix(l, []byte("\n"))
n, err := w.w.Write(l)
if err != nil {
return written, err
}
written += n
cr := bytes.HasSuffix(l, []byte("\r"))
if len(l) == 0 {
cr = w.cr
}
if !lf && len(b) == 0 {
w.curLineLen += len(l)
w.cr = cr
break
}
w.curLineLen = 0
ending := []byte("\r\n")
if cr {
ending = []byte("\n")
}
_, err = w.w.Write(ending)
if err != nil {
return written, err
}
// If the written `\n` was part of the input bytes slice, then account for it.
if lf {
written++
}
w.cr = false
}
return written, nil
}
func cutLine(b []byte, max int) ([]byte, []byte) {
for i := 0; i < len(b); i++ {
if b[i] == '\r' && i == max {
continue
}
if b[i] == '\n' {
return b[:i+1], b[i+1:]
}
if i >= max {
return b[:i], b[i:]
}
}
return b, nil
}

264
vendor/github.com/emersion/go-message/entity.go generated vendored Normal file
View File

@@ -0,0 +1,264 @@
package message
import (
"bufio"
"errors"
"io"
"math"
"strings"
"github.com/emersion/go-message/textproto"
)
// An Entity is either a whole message or a one of the parts in the body of a
// multipart entity.
type Entity struct {
Header Header // The entity's header.
Body io.Reader // The decoded entity's body.
mediaType string
mediaParams map[string]string
}
// New makes a new message with the provided header and body. The entity's
// transfer encoding and charset are automatically decoded to UTF-8.
//
// If the message uses an unknown transfer encoding or charset, New returns an
// error that verifies IsUnknownCharset, but also returns an Entity that can
// be read.
func New(header Header, body io.Reader) (*Entity, error) {
var err error
mediaType, mediaParams, _ := header.ContentType()
// QUIRK: RFC 2045 section 6.4 specifies that multipart messages can't have
// a Content-Transfer-Encoding other than "7bit", "8bit" or "binary".
// However some messages in the wild are non-conformant and have it set to
// e.g. "quoted-printable". So we just ignore it for multipart.
// See https://github.com/emersion/go-message/issues/48
if !strings.HasPrefix(mediaType, "multipart/") {
enc := header.Get("Content-Transfer-Encoding")
if decoded, encErr := encodingReader(enc, body); encErr != nil {
err = UnknownEncodingError{encErr}
} else {
body = decoded
}
}
// RFC 2046 section 4.1.2: charset only applies to text/*
if strings.HasPrefix(mediaType, "text/") {
if ch, ok := mediaParams["charset"]; ok {
if converted, charsetErr := charsetReader(ch, body); charsetErr != nil {
err = UnknownCharsetError{charsetErr}
} else {
body = converted
}
}
}
return &Entity{
Header: header,
Body: body,
mediaType: mediaType,
mediaParams: mediaParams,
}, err
}
// NewMultipart makes a new multipart message with the provided header and
// parts. The Content-Type header must begin with "multipart/".
//
// If the message uses an unknown transfer encoding, NewMultipart returns an
// error that verifies IsUnknownCharset, but also returns an Entity that can
// be read.
func NewMultipart(header Header, parts []*Entity) (*Entity, error) {
r := &multipartBody{
header: header,
parts: parts,
}
return New(header, r)
}
const defaultMaxHeaderBytes = 1 << 20 // 1 MB
var errHeaderTooBig = errors.New("message: header exceeds maximum size")
// limitedReader is the same as io.LimitedReader, but returns a custom error.
type limitedReader struct {
R io.Reader
N int64
}
func (lr *limitedReader) Read(p []byte) (int, error) {
if lr.N <= 0 {
return 0, errHeaderTooBig
}
if int64(len(p)) > lr.N {
p = p[0:lr.N]
}
n, err := lr.R.Read(p)
lr.N -= int64(n)
return n, err
}
// ReadOptions are options for ReadWithOptions.
type ReadOptions struct {
// MaxHeaderBytes limits the maximum permissible size of a message header
// block. If exceeded, an error will be returned.
//
// Set to -1 for no limit, set to 0 for the default value (1MB).
MaxHeaderBytes int64
}
// withDefaults returns a sanitised version of the options with defaults/special
// values accounted for.
func (o *ReadOptions) withDefaults() *ReadOptions {
var out ReadOptions
if o != nil {
out = *o
}
if out.MaxHeaderBytes == 0 {
out.MaxHeaderBytes = defaultMaxHeaderBytes
} else if out.MaxHeaderBytes < 0 {
out.MaxHeaderBytes = math.MaxInt64
}
return &out
}
// ReadWithOptions see Read, but allows overriding some parameters with
// ReadOptions.
//
// If the message uses an unknown transfer encoding or charset, ReadWithOptions
// returns an error that verifies IsUnknownCharset or IsUnknownEncoding, but
// also returns an Entity that can be read.
func ReadWithOptions(r io.Reader, opts *ReadOptions) (*Entity, error) {
opts = opts.withDefaults()
lr := &limitedReader{R: r, N: opts.MaxHeaderBytes}
br := bufio.NewReader(lr)
h, err := textproto.ReadHeader(br)
if err != nil {
return nil, err
}
lr.N = math.MaxInt64
return New(Header{h}, br)
}
// Read reads a message from r. The message's encoding and charset are
// automatically decoded to raw UTF-8. Note that this function only reads the
// message header.
//
// If the message uses an unknown transfer encoding or charset, Read returns an
// error that verifies IsUnknownCharset or IsUnknownEncoding, but also returns
// an Entity that can be read.
func Read(r io.Reader) (*Entity, error) {
return ReadWithOptions(r, nil)
}
// MultipartReader returns a MultipartReader that reads parts from this entity's
// body. If this entity is not multipart, it returns nil.
func (e *Entity) MultipartReader() MultipartReader {
if !strings.HasPrefix(e.mediaType, "multipart/") {
return nil
}
if mb, ok := e.Body.(*multipartBody); ok {
return mb
}
return &multipartReader{textproto.NewMultipartReader(e.Body, e.mediaParams["boundary"])}
}
// writeBodyTo writes this entity's body to w (without the header).
func (e *Entity) writeBodyTo(w *Writer) error {
var err error
if mb, ok := e.Body.(*multipartBody); ok {
err = mb.writeBodyTo(w)
} else {
_, err = io.Copy(w, e.Body)
}
return err
}
// WriteTo writes this entity's header and body to w.
func (e *Entity) WriteTo(w io.Writer) error {
ew, err := CreateWriter(w, e.Header)
if err != nil {
return err
}
if err := e.writeBodyTo(ew); err != nil {
ew.Close()
return err
}
return ew.Close()
}
// WalkFunc is the type of the function called for each part visited by Walk.
//
// The path argument is a list of multipart indices leading to the part. The
// root part has a nil path.
//
// If there was an encoding error walking to a part, the incoming error will
// describe the problem and the function can decide how to handle that error.
//
// Unlike IMAP part paths, indices start from 0 (instead of 1) and a
// non-multipart message has a nil path (instead of {1}).
//
// If an error is returned, processing stops.
type WalkFunc func(path []int, entity *Entity, err error) error
// Walk walks the entity's multipart tree, calling walkFunc for each part in
// the tree, including the root entity.
//
// Walk consumes the entity.
func (e *Entity) Walk(walkFunc WalkFunc) error {
var multipartReaders []MultipartReader
var path []int
part := e
for {
var err error
if part == nil {
if len(multipartReaders) == 0 {
break
}
// Get the next part from the last multipart reader
mr := multipartReaders[len(multipartReaders)-1]
part, err = mr.NextPart()
if err == io.EOF {
multipartReaders = multipartReaders[:len(multipartReaders)-1]
path = path[:len(path)-1]
continue
} else if IsUnknownEncoding(err) || IsUnknownCharset(err) {
// Forward the error to walkFunc
} else if err != nil {
return err
}
path[len(path)-1]++
}
// Copy the path since we'll mutate it on the next iteration
var pathCopy []int
if len(path) > 0 {
pathCopy = make([]int, len(path))
copy(pathCopy, path)
}
if err := walkFunc(pathCopy, part, err); err != nil {
return err
}
if mr := part.MultipartReader(); mr != nil {
multipartReaders = append(multipartReaders, mr)
path = append(path, -1)
}
part = nil
}
return nil
}

118
vendor/github.com/emersion/go-message/header.go generated vendored Normal file
View File

@@ -0,0 +1,118 @@
package message
import (
"mime"
"github.com/emersion/go-message/textproto"
)
func parseHeaderWithParams(s string) (f string, params map[string]string, err error) {
f, params, err = mime.ParseMediaType(s)
if err != nil {
return s, nil, err
}
for k, v := range params {
params[k], _ = decodeHeader(v)
}
return
}
func formatHeaderWithParams(f string, params map[string]string) string {
encParams := make(map[string]string)
for k, v := range params {
encParams[k] = encodeHeader(v)
}
return mime.FormatMediaType(f, encParams)
}
// HeaderFields iterates over header fields.
type HeaderFields interface {
textproto.HeaderFields
// Text parses the value of the current field as plaintext. The field
// charset is decoded to UTF-8. If the header field's charset is unknown,
// the raw field value is returned and the error verifies IsUnknownCharset.
Text() (string, error)
}
type headerFields struct {
textproto.HeaderFields
}
func (hf *headerFields) Text() (string, error) {
return decodeHeader(hf.Value())
}
// A Header represents the key-value pairs in a message header.
type Header struct {
textproto.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{textproto.HeaderFromMap(m)}
}
// ContentType parses the Content-Type header field.
//
// If no Content-Type is specified, it returns "text/plain".
func (h *Header) ContentType() (t string, params map[string]string, err error) {
v := h.Get("Content-Type")
if v == "" {
return "text/plain", nil, nil
}
return parseHeaderWithParams(v)
}
// SetContentType formats the Content-Type header field.
func (h *Header) SetContentType(t string, params map[string]string) {
h.Set("Content-Type", formatHeaderWithParams(t, params))
}
// ContentDisposition parses the Content-Disposition header field, as defined in
// RFC 2183.
func (h *Header) ContentDisposition() (disp string, params map[string]string, err error) {
return parseHeaderWithParams(h.Get("Content-Disposition"))
}
// SetContentDisposition formats the Content-Disposition header field, as
// defined in RFC 2183.
func (h *Header) SetContentDisposition(disp string, params map[string]string) {
h.Set("Content-Disposition", formatHeaderWithParams(disp, params))
}
// Text parses a plaintext header field. The field charset is automatically
// decoded to UTF-8. If the header field's charset is unknown, the raw field
// value is returned and the error verifies IsUnknownCharset.
func (h *Header) Text(k string) (string, error) {
return decodeHeader(h.Get(k))
}
// SetText sets a plaintext header field.
func (h *Header) SetText(k, v string) {
h.Set(k, encodeHeader(v))
}
// Copy creates a stand-alone copy of the header.
func (h *Header) Copy() Header {
return Header{h.Header.Copy()}
}
// Fields iterates over all the header fields.
//
// The header may not be mutated while iterating, except using HeaderFields.Del.
func (h *Header) Fields() HeaderFields {
return &headerFields{h.Header.Fields()}
}
// FieldsByKey iterates over all fields having the specified key.
//
// The header may not be mutated while iterating, except using HeaderFields.Del.
func (h *Header) FieldsByKey(k string) HeaderFields {
return &headerFields{h.Header.FieldsByKey(k)}
}

42
vendor/github.com/emersion/go-message/mail/address.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package mail
import (
"mime"
"net/mail"
"strings"
"github.com/emersion/go-message"
)
// Address represents a single mail address.
// The type alias ensures that a net/mail.Address can be used wherever an
// Address is expected
type Address = mail.Address
func formatAddressList(l []*Address) string {
formatted := make([]string, len(l))
for i, a := range l {
formatted[i] = a.String()
}
return strings.Join(formatted, ", ")
}
// ParseAddress parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
// Use this function only if you parse from a string, if you have a Header use
// Header.AddressList instead
func ParseAddress(address string) (*Address, error) {
parser := mail.AddressParser{
&mime.WordDecoder{message.CharsetReader},
}
return parser.Parse(address)
}
// ParseAddressList parses the given string as a list of addresses.
// Use this function only if you parse from a string, if you have a Header use
// Header.AddressList instead
func ParseAddressList(list string) ([]*Address, error) {
parser := mail.AddressParser{
&mime.WordDecoder{message.CharsetReader},
}
return parser.ParseList(list)
}

View File

@@ -0,0 +1,30 @@
package mail
import (
"github.com/emersion/go-message"
)
// An AttachmentHeader represents an attachment's header.
type AttachmentHeader struct {
message.Header
}
// Filename parses the attachment's filename.
func (h *AttachmentHeader) Filename() (string, error) {
_, params, err := h.ContentDisposition()
filename, ok := params["filename"]
if !ok {
// Using "name" in Content-Type is discouraged
_, params, err = h.ContentType()
filename = params["name"]
}
return filename, err
}
// SetFilename formats the attachment's filename.
func (h *AttachmentHeader) SetFilename(filename string) {
dispParams := map[string]string{"filename": filename}
h.SetContentDisposition("attachment", dispParams)
}

381
vendor/github.com/emersion/go-message/mail/header.go generated vendored Normal file
View 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()}
}

10
vendor/github.com/emersion/go-message/mail/inline.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package mail
import (
"github.com/emersion/go-message"
)
// A InlineHeader represents a message text header.
type InlineHeader struct {
message.Header
}

9
vendor/github.com/emersion/go-message/mail/mail.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// Package mail implements reading and writing mail messages.
//
// This package assumes that a mail message contains one or more text parts and
// zero or more attachment parts. Each text part represents a different version
// of the message content (e.g. a different type, a different language and so
// on).
//
// RFC 5322 defines the Internet Message Format.
package mail

130
vendor/github.com/emersion/go-message/mail/reader.go generated vendored Normal file
View File

@@ -0,0 +1,130 @@
package mail
import (
"container/list"
"io"
"strings"
"github.com/emersion/go-message"
)
// A PartHeader is a mail part header. It contains convenience functions to get
// and set header fields.
type PartHeader interface {
// Add adds the key, value pair to the header.
Add(key, value string)
// Del deletes the values associated with key.
Del(key string)
// Get gets the first value associated with the given key. If there are no
// values associated with the key, Get returns "".
Get(key string) string
// Set sets the header entries associated with key to the single element
// value. It replaces any existing values associated with key.
Set(key, value string)
}
// A Part is either a mail text or an attachment. Header is either a InlineHeader
// or an AttachmentHeader.
type Part struct {
Header PartHeader
Body io.Reader
}
// A Reader reads a mail message.
type Reader struct {
Header Header
e *message.Entity
readers *list.List
}
// NewReader creates a new mail reader.
func NewReader(e *message.Entity) *Reader {
mr := e.MultipartReader()
if mr == nil {
// Artificially create a multipart entity
// With this header, no error will be returned by message.NewMultipart
var h message.Header
h.Set("Content-Type", "multipart/mixed")
me, _ := message.NewMultipart(h, []*message.Entity{e})
mr = me.MultipartReader()
}
l := list.New()
l.PushBack(mr)
return &Reader{Header{e.Header}, e, l}
}
// CreateReader reads a mail header from r and returns a new mail reader.
//
// If the message uses an unknown transfer encoding or charset, CreateReader
// returns an error that verifies message.IsUnknownCharset, but also returns a
// Reader that can be used.
func CreateReader(r io.Reader) (*Reader, error) {
e, err := message.Read(r)
if err != nil && !message.IsUnknownCharset(err) {
return nil, err
}
return NewReader(e), err
}
// NextPart returns the next mail part. If there is no more part, io.EOF is
// returned as error.
//
// The returned Part.Body must be read completely before the next call to
// NextPart, otherwise it will be discarded.
//
// If the part uses an unknown transfer encoding or charset, NextPart returns an
// error that verifies message.IsUnknownCharset, but also returns a Part that
// can be used.
func (r *Reader) NextPart() (*Part, error) {
for r.readers.Len() > 0 {
e := r.readers.Back()
mr := e.Value.(message.MultipartReader)
p, err := mr.NextPart()
if err == io.EOF {
// This whole multipart entity has been read, continue with the next one
r.readers.Remove(e)
continue
} else if err != nil && !message.IsUnknownCharset(err) {
return nil, err
}
if pmr := p.MultipartReader(); pmr != nil {
// This is a multipart part, read it
r.readers.PushBack(pmr)
} else {
// This is a non-multipart part, return a mail part
mp := &Part{Body: p.Body}
t, _, _ := p.Header.ContentType()
disp, _, _ := p.Header.ContentDisposition()
if disp == "inline" || (disp != "attachment" && strings.HasPrefix(t, "text/")) {
mp.Header = &InlineHeader{p.Header}
} else {
mp.Header = &AttachmentHeader{p.Header}
}
return mp, err
}
}
return nil, io.EOF
}
// Close finishes the reader.
func (r *Reader) Close() error {
for r.readers.Len() > 0 {
e := r.readers.Back()
mr := e.Value.(message.MultipartReader)
if err := mr.Close(); err != nil {
return err
}
r.readers.Remove(e)
}
return nil
}

132
vendor/github.com/emersion/go-message/mail/writer.go generated vendored Normal file
View File

@@ -0,0 +1,132 @@
package mail
import (
"io"
"strings"
"github.com/emersion/go-message"
)
func initInlineContentTransferEncoding(h *message.Header) {
if !h.Has("Content-Transfer-Encoding") {
t, _, _ := h.ContentType()
if strings.HasPrefix(t, "text/") {
h.Set("Content-Transfer-Encoding", "quoted-printable")
} else {
h.Set("Content-Transfer-Encoding", "base64")
}
}
}
func initInlineHeader(h *InlineHeader) {
h.Set("Content-Disposition", "inline")
initInlineContentTransferEncoding(&h.Header)
}
func initAttachmentHeader(h *AttachmentHeader) {
disp, _, _ := h.ContentDisposition()
if disp != "attachment" {
h.Set("Content-Disposition", "attachment")
}
if !h.Has("Content-Transfer-Encoding") {
h.Set("Content-Transfer-Encoding", "base64")
}
}
// A Writer writes a mail message. A mail message contains one or more text
// parts and zero or more attachments.
type Writer struct {
mw *message.Writer
}
// CreateWriter writes a mail header to w and creates a new Writer.
func CreateWriter(w io.Writer, header Header) (*Writer, error) {
header = header.Copy() // don't modify the caller's view
header.Set("Content-Type", "multipart/mixed")
mw, err := message.CreateWriter(w, header.Header)
if err != nil {
return nil, err
}
return &Writer{mw}, nil
}
// CreateInlineWriter writes a mail header to w. The mail will contain an
// inline part, allowing to represent the same text in different formats.
// Attachments cannot be included.
func CreateInlineWriter(w io.Writer, header Header) (*InlineWriter, error) {
header = header.Copy() // don't modify the caller's view
header.Set("Content-Type", "multipart/alternative")
mw, err := message.CreateWriter(w, header.Header)
if err != nil {
return nil, err
}
return &InlineWriter{mw}, nil
}
// CreateSingleInlineWriter writes a mail header to w. The mail will contain a
// single inline part. The body of the part should be written to the returned
// io.WriteCloser. Only one single inline part should be written, use
// CreateWriter if you want multiple parts.
func CreateSingleInlineWriter(w io.Writer, header Header) (io.WriteCloser, error) {
header = header.Copy() // don't modify the caller's view
initInlineContentTransferEncoding(&header.Header)
return message.CreateWriter(w, header.Header)
}
// CreateInline creates a InlineWriter. One or more parts representing the same
// text in different formats can be written to a InlineWriter.
func (w *Writer) CreateInline() (*InlineWriter, error) {
var h message.Header
h.Set("Content-Type", "multipart/alternative")
mw, err := w.mw.CreatePart(h)
if err != nil {
return nil, err
}
return &InlineWriter{mw}, nil
}
// CreateSingleInline creates a new single text part with the provided header.
// The body of the part should be written to the returned io.WriteCloser. Only
// one single text part should be written, use CreateInline if you want multiple
// text parts.
func (w *Writer) CreateSingleInline(h InlineHeader) (io.WriteCloser, error) {
h = InlineHeader{h.Header.Copy()} // don't modify the caller's view
initInlineHeader(&h)
return w.mw.CreatePart(h.Header)
}
// CreateAttachment creates a new attachment with the provided header. The body
// of the part should be written to the returned io.WriteCloser.
func (w *Writer) CreateAttachment(h AttachmentHeader) (io.WriteCloser, error) {
h = AttachmentHeader{h.Header.Copy()} // don't modify the caller's view
initAttachmentHeader(&h)
return w.mw.CreatePart(h.Header)
}
// Close finishes the Writer.
func (w *Writer) Close() error {
return w.mw.Close()
}
// InlineWriter writes a mail message's text.
type InlineWriter struct {
mw *message.Writer
}
// CreatePart creates a new text part with the provided header. The body of the
// part should be written to the returned io.WriteCloser.
func (w *InlineWriter) CreatePart(h InlineHeader) (io.WriteCloser, error) {
h = InlineHeader{h.Header.Copy()} // don't modify the caller's view
initInlineHeader(&h)
return w.mw.CreatePart(h.Header)
}
// Close finishes the InlineWriter.
func (w *InlineWriter) Close() error {
return w.mw.Close()
}

15
vendor/github.com/emersion/go-message/message.go generated vendored Normal file
View File

@@ -0,0 +1,15 @@
// Package message implements reading and writing multipurpose messages.
//
// RFC 2045, RFC 2046 and RFC 2047 defines MIME, and RFC 2183 defines the
// Content-Disposition header field.
//
// Add this import to your package if you want to handle most common charsets
// by default:
//
// import (
// _ "github.com/emersion/go-message/charset"
// )
//
// Note, non-UTF-8 charsets are only supported when reading messages. Only
// UTF-8 is supported when writing messages.
package message

116
vendor/github.com/emersion/go-message/multipart.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
package message
import (
"io"
"github.com/emersion/go-message/textproto"
)
// MultipartReader is an iterator over parts in a MIME multipart body.
type MultipartReader interface {
io.Closer
// NextPart returns the next part in the multipart or an error. When there are
// no more parts, the error io.EOF is returned.
//
// Entity.Body must be read completely before the next call to NextPart,
// otherwise it will be discarded.
NextPart() (*Entity, error)
}
type multipartReader struct {
r *textproto.MultipartReader
}
// NextPart implements MultipartReader.
func (r *multipartReader) NextPart() (*Entity, error) {
p, err := r.r.NextPart()
if err != nil {
return nil, err
}
return New(Header{p.Header}, p)
}
// Close implements io.Closer.
func (r *multipartReader) Close() error {
return nil
}
type multipartBody struct {
header Header
parts []*Entity
r *io.PipeReader
w *Writer
i int
}
// Read implements io.Reader.
func (m *multipartBody) Read(p []byte) (n int, err error) {
if m.r == nil {
r, w := io.Pipe()
m.r = r
var err error
m.w, err = createWriter(w, &m.header)
if err != nil {
return 0, err
}
// Prevent calls to NextPart to succeed
m.i = len(m.parts)
go func() {
if err := m.writeBodyTo(m.w); err != nil {
w.CloseWithError(err)
return
}
if err := m.w.Close(); err != nil {
w.CloseWithError(err)
return
}
w.Close()
}()
}
return m.r.Read(p)
}
// Close implements io.Closer.
func (m *multipartBody) Close() error {
if m.r != nil {
m.r.Close()
}
return nil
}
// NextPart implements MultipartReader.
func (m *multipartBody) NextPart() (*Entity, error) {
if m.i >= len(m.parts) {
return nil, io.EOF
}
part := m.parts[m.i]
m.i++
return part, nil
}
func (m *multipartBody) writeBodyTo(w *Writer) error {
for _, p := range m.parts {
pw, err := w.CreatePart(p.Header)
if err != nil {
return err
}
if err := p.writeBodyTo(pw); err != nil {
return err
}
if err := pw.Close(); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,677 @@
package textproto
import (
"bufio"
"bytes"
"fmt"
"io"
"net/textproto"
"sort"
"strings"
)
type headerField struct {
b []byte // Raw header field, including whitespace
k string
v string
}
func newHeaderField(k, v string, b []byte) *headerField {
return &headerField{k: textproto.CanonicalMIMEHeaderKey(k), v: v, b: b}
}
func (f *headerField) raw() ([]byte, error) {
if f.b != nil {
return f.b, nil
} else {
for pos, ch := range f.k {
// check if character is a printable US-ASCII except ':'
if !(ch >= '!' && ch < ':' || ch > ':' && ch <= '~') {
return nil, fmt.Errorf("field name contains incorrect symbols (\\x%x at %v)", ch, pos)
}
}
if pos := strings.IndexAny(f.v, "\r\n"); pos != -1 {
return nil, fmt.Errorf("field value contains \\r\\n (at %v)", pos)
}
return []byte(formatHeaderField(f.k, f.v)), nil
}
}
// A Header represents the key-value pairs in a message header.
//
// The header representation is idempotent: if the header can be read and
// written, the result will be exactly the same as the original (including
// whitespace and header field ordering). This is required for e.g. DKIM.
//
// Mutating the header is restricted: the only two allowed operations are
// inserting a new header field at the top and deleting a header field. This is
// again necessary for DKIM.
type Header struct {
// Fields are in reverse order so that inserting a new field at the top is
// cheap.
l []*headerField
m map[string][]*headerField
}
func makeHeaderMap(fs []*headerField) map[string][]*headerField {
if len(fs) == 0 {
return nil
}
m := make(map[string][]*headerField, len(fs))
for i, f := range fs {
m[f.k] = append(m[f.k], fs[i])
}
return m
}
func newHeader(fs []*headerField) Header {
// Reverse order
for i := len(fs)/2 - 1; i >= 0; i-- {
opp := len(fs) - 1 - i
fs[i], fs[opp] = fs[opp], fs[i]
}
return Header{l: fs, m: makeHeaderMap(fs)}
}
// 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 {
fs := make([]*headerField, 0, len(m))
for k, vs := range m {
for _, v := range vs {
fs = append(fs, newHeaderField(k, v, nil))
}
}
sort.SliceStable(fs, func(i, j int) bool {
return fs[i].k < fs[j].k
})
return newHeader(fs)
}
// AddRaw adds the raw key, value pair to the header.
//
// The supplied byte slice should be a complete field in the "Key: Value" form
// including trailing CRLF. If there is no comma in the input - AddRaw panics.
// No changes are made to kv contents and it will be copied into WriteHeader
// output as is.
//
// kv is directly added to the underlying structure and therefore should not be
// modified after the AddRaw call.
func (h *Header) AddRaw(kv []byte) {
colon := bytes.IndexByte(kv, ':')
if colon == -1 {
panic("textproto: Header.AddRaw: missing colon")
}
k := textproto.CanonicalMIMEHeaderKey(string(trim(kv[:colon])))
v := trimAroundNewlines(kv[colon+1:])
if h.m == nil {
h.m = make(map[string][]*headerField)
}
f := newHeaderField(k, v, kv)
h.l = append(h.l, f)
h.m[k] = append(h.m[k], f)
}
// Add adds the key, value pair to the header. It prepends to any existing
// fields associated with key.
//
// Key and value should obey character requirements of RFC 6532.
// If you need to format or fold lines manually, use AddRaw.
func (h *Header) Add(k, v string) {
k = textproto.CanonicalMIMEHeaderKey(k)
if h.m == nil {
h.m = make(map[string][]*headerField)
}
f := newHeaderField(k, v, nil)
h.l = append(h.l, f)
h.m[k] = append(h.m[k], f)
}
// Get gets the first value associated with the given key. If there are no
// values associated with the key, Get returns "".
func (h *Header) Get(k string) string {
fields := h.m[textproto.CanonicalMIMEHeaderKey(k)]
if len(fields) == 0 {
return ""
}
return fields[len(fields)-1].v
}
// Raw gets the first raw header field associated with the given key.
//
// The returned bytes contain a complete field in the "Key: value" form,
// including trailing CRLF.
//
// The returned slice should not be modified and becomes invalid when the
// header is updated.
//
// An error is returned if the header field contains incorrect characters (see
// RFC 6532).
func (h *Header) Raw(k string) ([]byte, error) {
fields := h.m[textproto.CanonicalMIMEHeaderKey(k)]
if len(fields) == 0 {
return nil, nil
}
return fields[len(fields)-1].raw()
}
// Values returns all values associated with the given key.
//
// The returned slice should not be modified and becomes invalid when the
// header is updated.
func (h *Header) Values(k string) []string {
fields := h.m[textproto.CanonicalMIMEHeaderKey(k)]
if len(fields) == 0 {
return nil
}
l := make([]string, len(fields))
for i, field := range fields {
l[len(fields)-i-1] = field.v
}
return l
}
// Set sets the header fields associated with key to the single field value.
// It replaces any existing values associated with key.
func (h *Header) Set(k, v string) {
h.Del(k)
h.Add(k, v)
}
// Del deletes the values associated with key.
func (h *Header) Del(k string) {
k = textproto.CanonicalMIMEHeaderKey(k)
delete(h.m, k)
// Delete existing keys
for i := len(h.l) - 1; i >= 0; i-- {
if h.l[i].k == k {
h.l = append(h.l[:i], h.l[i+1:]...)
}
}
}
// Has checks whether the header has a field with the specified key.
func (h *Header) Has(k string) bool {
_, ok := h.m[textproto.CanonicalMIMEHeaderKey(k)]
return ok
}
// Copy creates an independent copy of the header.
func (h *Header) Copy() Header {
l := make([]*headerField, len(h.l))
copy(l, h.l)
m := makeHeaderMap(l)
return Header{l: l, m: m}
}
// Len returns the number of fields in the header.
func (h *Header) Len() int {
return len(h.l)
}
// Map returns all header fields as a map.
//
// This function is provided for interoperability with the standard library.
// If possible, Fields 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 (h *Header) Map() map[string][]string {
m := make(map[string][]string, h.Len())
fields := h.Fields()
for fields.Next() {
m[fields.Key()] = append(m[fields.Key()], fields.Value())
}
return m
}
// HeaderFields iterates over header fields. Its cursor starts before the first
// field of the header. Use Next to advance from field to field.
type HeaderFields interface {
// Next advances to the next header field. It returns true on success, or
// false if there is no next field.
Next() (more bool)
// Key returns the key of the current field.
Key() string
// Value returns the value of the current field.
Value() string
// Raw returns the raw current header field. See Header.Raw.
Raw() ([]byte, error)
// Del deletes the current field.
Del()
// Len returns the amount of header fields in the subset of header iterated
// by this HeaderFields instance.
//
// For Fields(), it will return the amount of fields in the whole header section.
// For FieldsByKey(), it will return the amount of fields with certain key.
Len() int
}
type headerFields struct {
h *Header
cur int
}
func (fs *headerFields) Next() bool {
fs.cur++
return fs.cur < len(fs.h.l)
}
func (fs *headerFields) index() int {
if fs.cur < 0 {
panic("message: HeaderFields method called before Next")
}
if fs.cur >= len(fs.h.l) {
panic("message: HeaderFields method called after Next returned false")
}
return len(fs.h.l) - fs.cur - 1
}
func (fs *headerFields) field() *headerField {
return fs.h.l[fs.index()]
}
func (fs *headerFields) Key() string {
return fs.field().k
}
func (fs *headerFields) Value() string {
return fs.field().v
}
func (fs *headerFields) Raw() ([]byte, error) {
return fs.field().raw()
}
func (fs *headerFields) Del() {
f := fs.field()
ok := false
for i, ff := range fs.h.m[f.k] {
if ff == f {
ok = true
fs.h.m[f.k] = append(fs.h.m[f.k][:i], fs.h.m[f.k][i+1:]...)
if len(fs.h.m[f.k]) == 0 {
delete(fs.h.m, f.k)
}
break
}
}
if !ok {
panic("message: field not found in Header.m")
}
fs.h.l = append(fs.h.l[:fs.index()], fs.h.l[fs.index()+1:]...)
fs.cur--
}
func (fs *headerFields) Len() int {
return len(fs.h.l)
}
// Fields iterates over all the header fields.
//
// The header may not be mutated while iterating, except using HeaderFields.Del.
func (h *Header) Fields() HeaderFields {
return &headerFields{h, -1}
}
type headerFieldsByKey struct {
h *Header
k string
cur int
}
func (fs *headerFieldsByKey) Next() bool {
fs.cur++
return fs.cur < len(fs.h.m[fs.k])
}
func (fs *headerFieldsByKey) index() int {
if fs.cur < 0 {
panic("message: headerfields.key or value called before next")
}
if fs.cur >= len(fs.h.m[fs.k]) {
panic("message: headerfields.key or value called after next returned false")
}
return len(fs.h.m[fs.k]) - fs.cur - 1
}
func (fs *headerFieldsByKey) field() *headerField {
return fs.h.m[fs.k][fs.index()]
}
func (fs *headerFieldsByKey) Key() string {
return fs.field().k
}
func (fs *headerFieldsByKey) Value() string {
return fs.field().v
}
func (fs *headerFieldsByKey) Raw() ([]byte, error) {
return fs.field().raw()
}
func (fs *headerFieldsByKey) Del() {
f := fs.field()
ok := false
for i := range fs.h.l {
if f == fs.h.l[i] {
ok = true
fs.h.l = append(fs.h.l[:i], fs.h.l[i+1:]...)
break
}
}
if !ok {
panic("message: field not found in Header.l")
}
fs.h.m[fs.k] = append(fs.h.m[fs.k][:fs.index()], fs.h.m[fs.k][fs.index()+1:]...)
if len(fs.h.m[fs.k]) == 0 {
delete(fs.h.m, fs.k)
}
fs.cur--
}
func (fs *headerFieldsByKey) Len() int {
return len(fs.h.m[fs.k])
}
// FieldsByKey iterates over all fields having the specified key.
//
// The header may not be mutated while iterating, except using HeaderFields.Del.
func (h *Header) FieldsByKey(k string) HeaderFields {
return &headerFieldsByKey{h, textproto.CanonicalMIMEHeaderKey(k), -1}
}
func readLineSlice(r *bufio.Reader, line []byte) ([]byte, error) {
for {
l, more, err := r.ReadLine()
line = append(line, l...)
if err != nil {
return line, err
}
if !more {
break
}
}
return line, nil
}
func isSpace(c byte) bool {
return c == ' ' || c == '\t'
}
func validHeaderKeyByte(b byte) bool {
c := int(b)
return c >= 33 && c <= 126 && c != ':'
}
// trim returns s with leading and trailing spaces and tabs removed.
// It does not assume Unicode or UTF-8.
func trim(s []byte) []byte {
i := 0
for i < len(s) && isSpace(s[i]) {
i++
}
n := len(s)
for n > i && isSpace(s[n-1]) {
n--
}
return s[i:n]
}
func hasContinuationLine(r *bufio.Reader) bool {
c, err := r.ReadByte()
if err != nil {
return false // bufio will keep err until next read.
}
r.UnreadByte()
return isSpace(c)
}
func readContinuedLineSlice(r *bufio.Reader) ([]byte, error) {
// Read the first line. We preallocate slice that it enough
// for most fields.
line, err := readLineSlice(r, make([]byte, 0, 256))
if err == io.EOF && len(line) == 0 {
// Header without a body
return nil, nil
} else if err != nil {
return nil, err
}
if len(line) == 0 { // blank line - no continuation
return line, nil
}
line = append(line, '\r', '\n')
// Read continuation lines.
for hasContinuationLine(r) {
line, err = readLineSlice(r, line)
if err != nil {
break // bufio will keep err until next read.
}
line = append(line, '\r', '\n')
}
return line, nil
}
func writeContinued(b *strings.Builder, l []byte) {
// Strip trailing \r, if any
if len(l) > 0 && l[len(l)-1] == '\r' {
l = l[:len(l)-1]
}
l = trim(l)
if len(l) == 0 {
return
}
if b.Len() > 0 {
b.WriteByte(' ')
}
b.Write(l)
}
// Strip newlines and spaces around newlines.
func trimAroundNewlines(v []byte) string {
var b strings.Builder
b.Grow(len(v))
for {
i := bytes.IndexByte(v, '\n')
if i < 0 {
writeContinued(&b, v)
break
}
writeContinued(&b, v[:i])
v = v[i+1:]
}
return b.String()
}
// ReadHeader reads a MIME header from r. The header is a sequence of possibly
// continued "Key: Value" lines ending in a blank line.
//
// To avoid denial of service attacks, the provided bufio.Reader should be
// reading from an io.LimitedReader or a similar Reader to bound the size of
// headers.
func ReadHeader(r *bufio.Reader) (Header, error) {
fs := make([]*headerField, 0, 32)
// The first line cannot start with a leading space.
if buf, err := r.Peek(1); err == nil && isSpace(buf[0]) {
line, err := readLineSlice(r, nil)
if err != nil {
return newHeader(fs), err
}
return newHeader(fs), fmt.Errorf("message: malformed MIME header initial line: %v", string(line))
}
for {
kv, err := readContinuedLineSlice(r)
if len(kv) == 0 {
return newHeader(fs), err
}
// Key ends at first colon; should not have trailing spaces but they
// appear in the wild, violating specs, so we remove them if present.
i := bytes.IndexByte(kv, ':')
if i < 0 {
return newHeader(fs), fmt.Errorf("message: malformed MIME header line: %v", string(kv))
}
keyBytes := trim(kv[:i])
// Verify that there are no invalid characters in the header key.
// See RFC 5322 Section 2.2
for _, c := range keyBytes {
if !validHeaderKeyByte(c) {
return newHeader(fs), fmt.Errorf("message: malformed MIME header key: %v", string(keyBytes))
}
}
key := textproto.CanonicalMIMEHeaderKey(string(keyBytes))
// As per RFC 7230 field-name is a token, tokens consist of one or more
// chars. We could return a an error here, but better to be liberal in
// what we accept, so if we get an empty key, skip it.
if key == "" {
continue
}
i++ // skip colon
v := kv[i:]
value := trimAroundNewlines(v)
fs = append(fs, newHeaderField(key, value, kv))
if err != nil {
return newHeader(fs), err
}
}
}
func foldLine(v string, maxlen int) (line, next string, ok bool) {
ok = true
// We'll need to fold before maxlen
foldBefore := maxlen + 1
foldAt := len(v)
var folding string
if foldBefore > len(v) {
// We reached the end of the string
if v[len(v)-1] != '\n' {
// If there isn't already a trailing CRLF, insert one
folding = "\r\n"
}
} else {
// Find the closest whitespace before maxlen
foldAt = strings.LastIndexAny(v[:foldBefore], " \t\n")
if foldAt == 0 {
// The whitespace we found was the previous folding WSP
foldAt = foldBefore - 1
} else if foldAt < 0 {
// We didn't find any whitespace, we have to insert one
foldAt = foldBefore - 2
}
switch v[foldAt] {
case ' ', '\t':
if v[foldAt-1] != '\n' {
folding = "\r\n" // The next char will be a WSP, don't need to insert one
}
case '\n':
folding = "" // There is already a CRLF, nothing to do
default:
// Another char, we need to insert CRLF + WSP. This will insert an
// extra space in the string, so this should be avoided if
// possible.
folding = "\r\n "
ok = false
}
}
return v[:foldAt] + folding, v[foldAt:], ok
}
const (
preferredHeaderLen = 76
maxHeaderLen = 998
)
// formatHeaderField formats a header field, ensuring each line is no longer
// than 76 characters. It tries to fold lines at whitespace characters if
// possible. If the header contains a word longer than this limit, it will be
// split.
func formatHeaderField(k, v string) string {
s := k + ": "
if v == "" {
return s + "\r\n"
}
first := true
for len(v) > 0 {
// If this is the first line, substract the length of the key
keylen := 0
if first {
keylen = len(s)
}
// First try with a soft limit
l, next, ok := foldLine(v, preferredHeaderLen-keylen)
if !ok {
// Folding failed to preserve the original header field value. Try
// with a larger, hard limit.
l, next, _ = foldLine(v, maxHeaderLen-keylen)
}
v = next
s += l
first = false
}
return s
}
// WriteHeader writes a MIME header to w.
func WriteHeader(w io.Writer, h Header) error {
for i := len(h.l) - 1; i >= 0; i-- {
f := h.l[i]
if rawField, err := f.raw(); err == nil {
if _, err := w.Write(rawField); err != nil {
return err
}
} else {
return fmt.Errorf("failed to write header field #%v (%q): %w", len(h.l)-i, f.k, err)
}
}
_, err := w.Write([]byte{'\r', '\n'})
return err
}

View File

@@ -0,0 +1,474 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
package textproto
// Multipart is defined in RFC 2046.
import (
"bufio"
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"io/ioutil"
)
var emptyParams = make(map[string]string)
// This constant needs to be at least 76 for this package to work correctly.
// This is because \r\n--separator_of_len_70- would fill the buffer and it
// wouldn't be safe to consume a single byte from it.
const peekBufferSize = 4096
// A Part represents a single part in a multipart body.
type Part struct {
Header Header
mr *MultipartReader
// r is either a reader directly reading from mr
r io.Reader
n int // known data bytes waiting in mr.bufReader
total int64 // total data bytes read already
err error // error to return when n == 0
readErr error // read error observed from mr.bufReader
}
// NewMultipartReader creates a new multipart reader reading from r using the
// given MIME boundary.
//
// The boundary is usually obtained from the "boundary" parameter of
// the message's "Content-Type" header. Use mime.ParseMediaType to
// parse such headers.
func NewMultipartReader(r io.Reader, boundary string) *MultipartReader {
b := []byte("\r\n--" + boundary + "--")
return &MultipartReader{
bufReader: bufio.NewReaderSize(&stickyErrorReader{r: r}, peekBufferSize),
nl: b[:2],
nlDashBoundary: b[:len(b)-2],
dashBoundaryDash: b[2:],
dashBoundary: b[2 : len(b)-2],
}
}
// stickyErrorReader is an io.Reader which never calls Read on its
// underlying Reader once an error has been seen. (the io.Reader
// interface's contract promises nothing about the return values of
// Read calls after an error, yet this package does do multiple Reads
// after error)
type stickyErrorReader struct {
r io.Reader
err error
}
func (r *stickyErrorReader) Read(p []byte) (n int, _ error) {
if r.err != nil {
return 0, r.err
}
n, r.err = r.r.Read(p)
return n, r.err
}
func newPart(mr *MultipartReader) (*Part, error) {
bp := &Part{mr: mr}
if err := bp.populateHeaders(); err != nil {
return nil, err
}
bp.r = partReader{bp}
return bp, nil
}
func (bp *Part) populateHeaders() error {
header, err := ReadHeader(bp.mr.bufReader)
if err == nil {
bp.Header = header
}
return err
}
// Read reads the body of a part, after its headers and before the
// next part (if any) begins.
func (p *Part) Read(d []byte) (n int, err error) {
return p.r.Read(d)
}
// partReader implements io.Reader by reading raw bytes directly from the
// wrapped *Part, without doing any Transfer-Encoding decoding.
type partReader struct {
p *Part
}
func (pr partReader) Read(d []byte) (int, error) {
p := pr.p
br := p.mr.bufReader
// Read into buffer until we identify some data to return,
// or we find a reason to stop (boundary or read error).
for p.n == 0 && p.err == nil {
peek, _ := br.Peek(br.Buffered())
p.n, p.err = scanUntilBoundary(peek, p.mr.dashBoundary, p.mr.nlDashBoundary, p.total, p.readErr)
if p.n == 0 && p.err == nil {
// Force buffered I/O to read more into buffer.
_, p.readErr = br.Peek(len(peek) + 1)
if p.readErr == io.EOF {
p.readErr = io.ErrUnexpectedEOF
}
}
}
// Read out from "data to return" part of buffer.
if p.n == 0 {
return 0, p.err
}
n := len(d)
if n > p.n {
n = p.n
}
n, _ = br.Read(d[:n])
p.total += int64(n)
p.n -= n
if p.n == 0 {
return n, p.err
}
return n, nil
}
// scanUntilBoundary scans buf to identify how much of it can be safely
// returned as part of the Part body.
// dashBoundary is "--boundary".
// nlDashBoundary is "\r\n--boundary" or "\n--boundary", depending on what mode we are in.
// The comments below (and the name) assume "\n--boundary", but either is accepted.
// total is the number of bytes read out so far. If total == 0, then a leading "--boundary" is recognized.
// readErr is the read error, if any, that followed reading the bytes in buf.
// scanUntilBoundary returns the number of data bytes from buf that can be
// returned as part of the Part body and also the error to return (if any)
// once those data bytes are done.
func scanUntilBoundary(buf, dashBoundary, nlDashBoundary []byte, total int64, readErr error) (int, error) {
if total == 0 {
// At beginning of body, allow dashBoundary.
if bytes.HasPrefix(buf, dashBoundary) {
switch matchAfterPrefix(buf, dashBoundary, readErr) {
case -1:
return len(dashBoundary), nil
case 0:
return 0, nil
case +1:
return 0, io.EOF
}
}
if bytes.HasPrefix(dashBoundary, buf) {
return 0, readErr
}
}
// Search for "\n--boundary".
if i := bytes.Index(buf, nlDashBoundary); i >= 0 {
switch matchAfterPrefix(buf[i:], nlDashBoundary, readErr) {
case -1:
return i + len(nlDashBoundary), nil
case 0:
return i, nil
case +1:
return i, io.EOF
}
}
if bytes.HasPrefix(nlDashBoundary, buf) {
return 0, readErr
}
// Otherwise, anything up to the final \n is not part of the boundary
// and so must be part of the body.
// Also if the section from the final \n onward is not a prefix of the boundary,
// it too must be part of the body.
i := bytes.LastIndexByte(buf, nlDashBoundary[0])
if i >= 0 && bytes.HasPrefix(nlDashBoundary, buf[i:]) {
return i, nil
}
return len(buf), readErr
}
// matchAfterPrefix checks whether buf should be considered to match the boundary.
// The prefix is "--boundary" or "\r\n--boundary" or "\n--boundary",
// and the caller has verified already that bytes.HasPrefix(buf, prefix) is true.
//
// matchAfterPrefix returns +1 if the buffer does match the boundary,
// meaning the prefix is followed by a dash, space, tab, cr, nl, or end of input.
// It returns -1 if the buffer definitely does NOT match the boundary,
// meaning the prefix is followed by some other character.
// For example, "--foobar" does not match "--foo".
// It returns 0 more input needs to be read to make the decision,
// meaning that len(buf) == len(prefix) and readErr == nil.
func matchAfterPrefix(buf, prefix []byte, readErr error) int {
if len(buf) == len(prefix) {
if readErr != nil {
return +1
}
return 0
}
c := buf[len(prefix)]
if c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '-' {
return +1
}
return -1
}
func (p *Part) Close() error {
io.Copy(ioutil.Discard, p)
return nil
}
// MultipartReader is an iterator over parts in a MIME multipart body.
// MultipartReader's underlying parser consumes its input as needed. Seeking
// isn't supported.
type MultipartReader struct {
bufReader *bufio.Reader
currentPart *Part
partsRead int
nl []byte // "\r\n" or "\n" (set after seeing first boundary line)
nlDashBoundary []byte // nl + "--boundary"
dashBoundaryDash []byte // "--boundary--"
dashBoundary []byte // "--boundary"
}
// NextPart returns the next part in the multipart or an error.
// When there are no more parts, the error io.EOF is returned.
func (r *MultipartReader) NextPart() (*Part, error) {
if r.currentPart != nil {
r.currentPart.Close()
}
if string(r.dashBoundary) == "--" {
return nil, fmt.Errorf("multipart: boundary is empty")
}
expectNewPart := false
for {
line, err := r.bufReader.ReadSlice('\n')
if err == io.EOF && r.isFinalBoundary(line) {
// If the buffer ends in "--boundary--" without the
// trailing "\r\n", ReadSlice will return an error
// (since it's missing the '\n'), but this is a valid
// multipart EOF so we need to return io.EOF instead of
// a fmt-wrapped one.
return nil, io.EOF
}
if err != nil {
return nil, fmt.Errorf("multipart: NextPart: %v", err)
}
if r.isBoundaryDelimiterLine(line) {
r.partsRead++
bp, err := newPart(r)
if err != nil {
return nil, err
}
r.currentPart = bp
return bp, nil
}
if r.isFinalBoundary(line) {
// Expected EOF
return nil, io.EOF
}
if expectNewPart {
return nil, fmt.Errorf("multipart: expecting a new Part; got line %q", string(line))
}
if r.partsRead == 0 {
// skip line
continue
}
// Consume the "\n" or "\r\n" separator between the
// body of the previous part and the boundary line we
// now expect will follow. (either a new part or the
// end boundary)
if bytes.Equal(line, r.nl) {
expectNewPart = true
continue
}
return nil, fmt.Errorf("multipart: unexpected line in Next(): %q", line)
}
}
// isFinalBoundary reports whether line is the final boundary line
// indicating that all parts are over.
// It matches `^--boundary--[ \t]*(\r\n)?$`
func (mr *MultipartReader) isFinalBoundary(line []byte) bool {
if !bytes.HasPrefix(line, mr.dashBoundaryDash) {
return false
}
rest := line[len(mr.dashBoundaryDash):]
rest = skipLWSPChar(rest)
return len(rest) == 0 || bytes.Equal(rest, mr.nl)
}
func (mr *MultipartReader) isBoundaryDelimiterLine(line []byte) (ret bool) {
// https://tools.ietf.org/html/rfc2046#section-5.1
// The boundary delimiter line is then defined as a line
// consisting entirely of two hyphen characters ("-",
// decimal value 45) followed by the boundary parameter
// value from the Content-Type header field, optional linear
// whitespace, and a terminating CRLF.
if !bytes.HasPrefix(line, mr.dashBoundary) {
return false
}
rest := line[len(mr.dashBoundary):]
rest = skipLWSPChar(rest)
// On the first part, see our lines are ending in \n instead of \r\n
// and switch into that mode if so. This is a violation of the spec,
// but occurs in practice.
if mr.partsRead == 0 && len(rest) == 1 && rest[0] == '\n' {
mr.nl = mr.nl[1:]
mr.nlDashBoundary = mr.nlDashBoundary[1:]
}
return bytes.Equal(rest, mr.nl)
}
// skipLWSPChar returns b with leading spaces and tabs removed.
// RFC 822 defines:
//
// LWSP-char = SPACE / HTAB
func skipLWSPChar(b []byte) []byte {
for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') {
b = b[1:]
}
return b
}
// A MultipartWriter generates multipart messages.
type MultipartWriter struct {
w io.Writer
boundary string
lastpart *part
}
// NewMultipartWriter returns a new multipart Writer with a random boundary,
// writing to w.
func NewMultipartWriter(w io.Writer) *MultipartWriter {
return &MultipartWriter{
w: w,
boundary: randomBoundary(),
}
}
// Boundary returns the Writer's boundary.
func (w *MultipartWriter) Boundary() string {
return w.boundary
}
// SetBoundary overrides the Writer's default randomly-generated
// boundary separator with an explicit value.
//
// SetBoundary must be called before any parts are created, may only
// contain certain ASCII characters, and must be non-empty and
// at most 70 bytes long.
func (w *MultipartWriter) SetBoundary(boundary string) error {
if w.lastpart != nil {
return errors.New("mime: SetBoundary called after write")
}
// rfc2046#section-5.1.1
if len(boundary) < 1 || len(boundary) > 70 {
return errors.New("mime: invalid boundary length")
}
end := len(boundary) - 1
for i, b := range boundary {
if 'A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' {
continue
}
switch b {
case '\'', '(', ')', '+', '_', ',', '-', '.', '/', ':', '=', '?':
continue
case ' ':
if i != end {
continue
}
}
return errors.New("mime: invalid boundary character")
}
w.boundary = boundary
return nil
}
func randomBoundary() string {
var buf [30]byte
_, err := io.ReadFull(rand.Reader, buf[:])
if err != nil {
panic(err)
}
return fmt.Sprintf("%x", buf[:])
}
// CreatePart creates a new multipart section with the provided
// header. The body of the part should be written to the returned
// Writer. After calling CreatePart, any previous part may no longer
// be written to.
func (w *MultipartWriter) CreatePart(header Header) (io.Writer, error) {
if w.lastpart != nil {
if err := w.lastpart.close(); err != nil {
return nil, err
}
}
var b bytes.Buffer
if w.lastpart != nil {
fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
} else {
fmt.Fprintf(&b, "--%s\r\n", w.boundary)
}
WriteHeader(&b, header)
_, err := io.Copy(w.w, &b)
if err != nil {
return nil, err
}
p := &part{
mw: w,
}
w.lastpart = p
return p, nil
}
// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
func (w *MultipartWriter) Close() error {
if w.lastpart != nil {
if err := w.lastpart.close(); err != nil {
return err
}
w.lastpart = nil
}
_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
return err
}
type part struct {
mw *MultipartWriter
closed bool
we error // last error that occurred writing
}
func (p *part) close() error {
p.closed = true
return p.we
}
func (p *part) Write(d []byte) (n int, err error) {
if p.closed {
return 0, errors.New("multipart: can't write to finished part")
}
n, err = p.mw.w.Write(d)
if err != nil {
p.we = err
}
return
}

View File

@@ -0,0 +1,2 @@
// Package textproto implements low-level manipulation of MIME messages.
package textproto

134
vendor/github.com/emersion/go-message/writer.go generated vendored Normal file
View File

@@ -0,0 +1,134 @@
package message
import (
"errors"
"fmt"
"io"
"strings"
"github.com/emersion/go-message/textproto"
)
// Writer writes message entities.
//
// If the message is not multipart, it should be used as a WriteCloser. Don't
// forget to call Close.
//
// If the message is multipart, users can either use CreatePart to write child
// parts or Write to directly pipe a multipart message. In any case, Close must
// be called at the end.
type Writer struct {
w io.Writer
c io.Closer
mw *textproto.MultipartWriter
}
// createWriter creates a new Writer writing to w with the provided header.
// Nothing is written to w when it is called. header is modified in-place.
func createWriter(w io.Writer, header *Header) (*Writer, error) {
ww := &Writer{w: w}
mediaType, mediaParams, _ := header.ContentType()
if strings.HasPrefix(mediaType, "multipart/") {
ww.mw = textproto.NewMultipartWriter(ww.w)
// Do not set ww's io.Closer for now: if this is a multipart entity but
// CreatePart is not used (only Write is used), then the final boundary
// is expected to be written by the user too. In this case, ww.Close
// shouldn't write the final boundary.
if mediaParams["boundary"] != "" {
ww.mw.SetBoundary(mediaParams["boundary"])
} else {
mediaParams["boundary"] = ww.mw.Boundary()
header.SetContentType(mediaType, mediaParams)
}
header.Del("Content-Transfer-Encoding")
} else {
wc, err := encodingWriter(header.Get("Content-Transfer-Encoding"), ww.w)
if err != nil {
return nil, err
}
ww.w = wc
ww.c = wc
}
switch strings.ToLower(mediaParams["charset"]) {
case "", "us-ascii", "utf-8":
// This is OK
default:
// Anything else is invalid
return nil, fmt.Errorf("unhandled charset %q", mediaParams["charset"])
}
return ww, nil
}
// CreateWriter creates a new message writer to w. If header contains an
// encoding, data written to the Writer will automatically be encoded with it.
// The charset needs to be utf-8 or us-ascii.
func CreateWriter(w io.Writer, header Header) (*Writer, error) {
// Ensure that modifications are invisible to the caller
header = header.Copy()
// If the message uses MIME, it has to include MIME-Version
if !header.Has("Mime-Version") {
header.Set("MIME-Version", "1.0")
}
ww, err := createWriter(w, &header)
if err != nil {
return nil, err
}
if err := textproto.WriteHeader(w, header.Header); err != nil {
return nil, err
}
return ww, nil
}
// Write implements io.Writer.
func (w *Writer) Write(b []byte) (int, error) {
return w.w.Write(b)
}
// Close implements io.Closer.
func (w *Writer) Close() error {
if w.c != nil {
return w.c.Close()
}
return nil
}
// CreatePart returns a Writer to a new part in this multipart entity. If this
// entity is not multipart, it fails. The body of the part should be written to
// the returned io.WriteCloser.
func (w *Writer) CreatePart(header Header) (*Writer, error) {
if w.mw == nil {
return nil, errors.New("cannot create a part in a non-multipart message")
}
if w.c == nil {
// We know that the user calls CreatePart so Close should write the final
// boundary
w.c = w.mw
}
// cw -> ww -> pw -> w.mw -> w.w
ww := &struct{ io.Writer }{nil}
// ensure that modifications are invisible to the caller
header = header.Copy()
cw, err := createWriter(ww, &header)
if err != nil {
return nil, err
}
pw, err := w.mw.CreatePart(header.Header)
if err != nil {
return nil, err
}
ww.Writer = pw
return cw, nil
}