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

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