mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-25 14:30:28 -05:00
* adds pkg/jmap/jmap_integration_test.go
* uses ghcr.io/stalwartlabs/stalwart:v0.13.2-alpine
* can be disabled by setting one of the following environment
variables, in the same fashion as ca0493b28
- CI=woodpecker
- CI_SYSTEM_NAME=woodpecker
- USE_TESTCONTAINERS=false
* dependencies:
- bump github.com/go-test/deep from 1.1.0 to 1.1.1
- add github.com/cention-sany/utf7
- add github.com/dustinkirkland/golang-petname
- add github.com/emersion/go-imap/v2
- add github.com/emersion/go-message
- add github.com/emersion/go-sasl
- add github.com/go-crypt/crypt
- add github.com/go-crypt/x
- add github.com/gogs/chardet
- add github.com/inbucket/html2text
- add github.com/jhilleryerd/enmime/v2
- add github.com/ssor/bom
- add gopkg.in/loremipsum.v1
678 lines
16 KiB
Go
678 lines
16 KiB
Go
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
|
|
}
|