1package message
2
3import (
4 "bytes"
5 "fmt"
6 "net/mail"
7 "net/textproto"
8)
9
10// ParseHeaderFields parses only the header fields in "fields" from the complete
11// header buffer "header". It uses "scratch" as temporary space, which can be
12// reused across calls, potentially saving lots of unneeded allocations when only a
13// few headers are needed and/or many messages are parsed.
14func ParseHeaderFields(header []byte, scratch []byte, fields [][]byte) (textproto.MIMEHeader, error) {
15 // todo: should not use mail.ReadMessage, it allocates a bufio.Reader. should implement header parsing ourselves.
16
17 // Gather the raw lines for the fields, with continuations, without the other
18 // headers. Put them in a byte slice and only parse those headers. For now, use
19 // mail.ReadMessage without letting it do allocations for all headers.
20 scratch = scratch[:0]
21 var keepcontinuation bool
22 for len(header) > 0 {
23 if header[0] == ' ' || header[0] == '\t' {
24 // Continuation.
25 i := bytes.IndexByte(header, '\n')
26 if i < 0 {
27 i = len(header)
28 } else {
29 i++
30 }
31 if keepcontinuation {
32 scratch = append(scratch, header[:i]...)
33 }
34 header = header[i:]
35 continue
36 }
37 i := bytes.IndexByte(header, ':')
38 if i < 0 || i > 0 && (header[i-1] == ' ' || header[i-1] == '\t') {
39 i = bytes.IndexByte(header, '\n')
40 if i < 0 {
41 break
42 }
43 header = header[i+1:]
44 keepcontinuation = false
45 continue
46 }
47 k := header[:i]
48 keepcontinuation = false
49 for _, f := range fields {
50 if bytes.EqualFold(k, f) {
51 keepcontinuation = true
52 break
53 }
54 }
55 i = bytes.IndexByte(header, '\n')
56 if i < 0 {
57 i = len(header)
58 } else {
59 i++
60 }
61 if keepcontinuation {
62 scratch = append(scratch, header[:i]...)
63 }
64 header = header[i:]
65 }
66
67 if len(scratch) == 0 {
68 return nil, nil
69 }
70
71 scratch = append(scratch, "\r\n"...)
72
73 msg, err := mail.ReadMessage(bytes.NewReader(scratch))
74 if err != nil {
75 return nil, fmt.Errorf("reading message header")
76 }
77 return textproto.MIMEHeader(msg.Header), nil
78}
79