mox/message/parseheaderfields.go

78 lines
2 KiB
Go

package message
import (
"bytes"
"fmt"
"net/mail"
"net/textproto"
)
// ParseHeaderFields parses only the header fields in "fields" from the complete
// header buffer "header". It uses "scratch" as temporary space, which can be
// reused across calls, potentially saving lots of unneeded allocations when only a
// few headers are needed and/or many messages are parsed.
func ParseHeaderFields(header []byte, scratch []byte, fields [][]byte) (textproto.MIMEHeader, error) {
// todo: should not use mail.ReadMessage, it allocates a bufio.Reader. should implement header parsing ourselves.
// Gather the raw lines for the fields, with continuations, without the other
// headers. Put them in a byte slice and only parse those headers. For now, use
// mail.ReadMessage without letting it do allocations for all headers.
scratch = scratch[:0]
var keepcontinuation bool
for len(header) > 0 {
if header[0] == ' ' || header[0] == '\t' {
// Continuation.
i := bytes.IndexByte(header, '\n')
if i < 0 {
i = len(header)
} else {
i++
}
if keepcontinuation {
scratch = append(scratch, header[:i]...)
}
header = header[i:]
continue
}
i := bytes.IndexByte(header, ':')
if i < 0 || i > 0 && (header[i-1] == ' ' || header[i-1] == '\t') {
i = bytes.IndexByte(header, '\n')
if i < 0 {
break
}
header = header[i+1:]
keepcontinuation = false
continue
}
k := header[:i]
keepcontinuation = false
for _, f := range fields {
if bytes.EqualFold(k, f) {
keepcontinuation = true
break
}
}
i = bytes.IndexByte(header, '\n')
if i < 0 {
i = len(header)
} else {
i++
}
if keepcontinuation {
scratch = append(scratch, header[:i]...)
}
header = header[i:]
}
if len(scratch) == 0 {
return nil, nil
}
scratch = append(scratch, "\r\n"...)
msg, err := mail.ReadMessage(bytes.NewReader(scratch))
if err != nil {
return nil, fmt.Errorf("reading message header")
}
return textproto.MIMEHeader(msg.Header), nil
}