add vendoring
This commit is contained in:
238
vendor/github.com/jhillyerd/enmime/encode.go
generated
vendored
Normal file
238
vendor/github.com/jhillyerd/enmime/encode.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
package enmime
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/quotedprintable"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jhillyerd/enmime/internal/coding"
|
||||
"github.com/jhillyerd/enmime/internal/stringutil"
|
||||
)
|
||||
|
||||
// b64Percent determines the percent of non-ASCII characters enmime will tolerate before switching
|
||||
// from quoted-printable to base64 encoding.
|
||||
const b64Percent = 20
|
||||
|
||||
type transferEncoding byte
|
||||
|
||||
const (
|
||||
te7Bit transferEncoding = iota
|
||||
teQuoted
|
||||
teBase64
|
||||
)
|
||||
|
||||
var crnl = []byte{'\r', '\n'}
|
||||
|
||||
// Encode writes this Part and all its children to the specified writer in MIME format.
|
||||
func (p *Part) Encode(writer io.Writer) error {
|
||||
if p.Header == nil {
|
||||
p.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
cte := p.setupMIMEHeaders()
|
||||
// Encode this part.
|
||||
b := bufio.NewWriter(writer)
|
||||
if err := p.encodeHeader(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p.Content) > 0 {
|
||||
if _, err := b.Write(crnl); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.encodeContent(b, cte); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.FirstChild == nil {
|
||||
return b.Flush()
|
||||
}
|
||||
// Encode children.
|
||||
endMarker := []byte("\r\n--" + p.Boundary + "--")
|
||||
marker := endMarker[:len(endMarker)-2]
|
||||
c := p.FirstChild
|
||||
for c != nil {
|
||||
if _, err := b.Write(marker); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := b.Write(crnl); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Encode(b); err != nil {
|
||||
return err
|
||||
}
|
||||
c = c.NextSibling
|
||||
}
|
||||
if _, err := b.Write(endMarker); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := b.Write(crnl); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Flush()
|
||||
}
|
||||
|
||||
// setupMIMEHeaders determines content transfer encoding, generates a boundary string if required,
|
||||
// then sets the Content-Type (type, charset, filename, boundary) and Content-Disposition headers.
|
||||
func (p *Part) setupMIMEHeaders() transferEncoding {
|
||||
// Determine content transfer encoding.
|
||||
|
||||
// If we are encoding a part that previously had content-transfer-encoding set, unset it so
|
||||
// the correct encoding detection can be done below.
|
||||
p.Header.Del(hnContentEncoding)
|
||||
|
||||
cte := te7Bit
|
||||
if len(p.Content) > 0 {
|
||||
cte = teBase64
|
||||
if p.TextContent() {
|
||||
cte = selectTransferEncoding(p.Content, false)
|
||||
if p.Charset == "" {
|
||||
p.Charset = utf8
|
||||
}
|
||||
}
|
||||
// RFC 2045: 7bit is assumed if CTE header not present.
|
||||
switch cte {
|
||||
case teBase64:
|
||||
p.Header.Set(hnContentEncoding, cteBase64)
|
||||
case teQuoted:
|
||||
p.Header.Set(hnContentEncoding, cteQuotedPrintable)
|
||||
}
|
||||
}
|
||||
// Setup headers.
|
||||
if p.FirstChild != nil && p.Boundary == "" {
|
||||
// Multipart, generate random boundary marker.
|
||||
p.Boundary = "enmime-" + stringutil.UUID()
|
||||
}
|
||||
if p.ContentID != "" {
|
||||
p.Header.Set(hnContentID, coding.ToIDHeader(p.ContentID))
|
||||
}
|
||||
fileName := p.FileName
|
||||
switch selectTransferEncoding([]byte(p.FileName), true) {
|
||||
case teBase64:
|
||||
fileName = mime.BEncoding.Encode(utf8, p.FileName)
|
||||
case teQuoted:
|
||||
fileName = mime.QEncoding.Encode(utf8, p.FileName)
|
||||
}
|
||||
if p.ContentType != "" {
|
||||
// Build content type header.
|
||||
param := make(map[string]string)
|
||||
for k, v := range p.ContentTypeParams {
|
||||
param[k] = v
|
||||
}
|
||||
setParamValue(param, hpCharset, p.Charset)
|
||||
setParamValue(param, hpName, fileName)
|
||||
setParamValue(param, hpBoundary, p.Boundary)
|
||||
if mt := mime.FormatMediaType(p.ContentType, param); mt != "" {
|
||||
p.ContentType = mt
|
||||
}
|
||||
p.Header.Set(hnContentType, p.ContentType)
|
||||
}
|
||||
if p.Disposition != "" {
|
||||
// Build disposition header.
|
||||
param := make(map[string]string)
|
||||
setParamValue(param, hpFilename, fileName)
|
||||
if !p.FileModDate.IsZero() {
|
||||
setParamValue(param, hpModDate, p.FileModDate.Format(time.RFC822))
|
||||
}
|
||||
if mt := mime.FormatMediaType(p.Disposition, param); mt != "" {
|
||||
p.Disposition = mt
|
||||
}
|
||||
p.Header.Set(hnContentDisposition, p.Disposition)
|
||||
}
|
||||
return cte
|
||||
}
|
||||
|
||||
// encodeHeader writes out a sorted list of headers.
|
||||
func (p *Part) encodeHeader(b *bufio.Writer) error {
|
||||
keys := make([]string, 0, len(p.Header))
|
||||
for k := range p.Header {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
for _, v := range p.Header[k] {
|
||||
encv := v
|
||||
switch selectTransferEncoding([]byte(v), true) {
|
||||
case teBase64:
|
||||
encv = mime.BEncoding.Encode(utf8, v)
|
||||
case teQuoted:
|
||||
encv = mime.QEncoding.Encode(utf8, v)
|
||||
}
|
||||
// _ used to prevent early wrapping
|
||||
wb := stringutil.Wrap(76, k, ":_", encv, "\r\n")
|
||||
wb[len(k)+1] = ' '
|
||||
if _, err := b.Write(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeContent writes out the content in the selected encoding.
|
||||
func (p *Part) encodeContent(b *bufio.Writer, cte transferEncoding) (err error) {
|
||||
switch cte {
|
||||
case teBase64:
|
||||
enc := base64.StdEncoding
|
||||
text := make([]byte, enc.EncodedLen(len(p.Content)))
|
||||
base64.StdEncoding.Encode(text, p.Content)
|
||||
// Wrap lines.
|
||||
lineLen := 76
|
||||
for len(text) > 0 {
|
||||
if lineLen > len(text) {
|
||||
lineLen = len(text)
|
||||
}
|
||||
if _, err = b.Write(text[:lineLen]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := b.Write(crnl); err != nil {
|
||||
return err
|
||||
}
|
||||
text = text[lineLen:]
|
||||
}
|
||||
case teQuoted:
|
||||
qp := quotedprintable.NewWriter(b)
|
||||
if _, err = qp.Write(p.Content); err != nil {
|
||||
return err
|
||||
}
|
||||
err = qp.Close()
|
||||
default:
|
||||
_, err = b.Write(p.Content)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// selectTransferEncoding scans content for non-ASCII characters and selects 'b' or 'q' encoding.
|
||||
func selectTransferEncoding(content []byte, quoteLineBreaks bool) transferEncoding {
|
||||
if len(content) == 0 {
|
||||
return te7Bit
|
||||
}
|
||||
// Binary chars remaining before we choose b64 encoding.
|
||||
threshold := b64Percent * len(content) / 100
|
||||
bincount := 0
|
||||
for _, b := range content {
|
||||
if (b < ' ' || '~' < b) && b != '\t' {
|
||||
if !quoteLineBreaks && (b == '\r' || b == '\n') {
|
||||
continue
|
||||
}
|
||||
bincount++
|
||||
if bincount >= threshold {
|
||||
return teBase64
|
||||
}
|
||||
}
|
||||
}
|
||||
if bincount == 0 {
|
||||
return te7Bit
|
||||
}
|
||||
return teQuoted
|
||||
}
|
||||
|
||||
// setParamValue will ignore empty values
|
||||
func setParamValue(p map[string]string, k, v string) {
|
||||
if v != "" {
|
||||
p[k] = v
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user