1 package message
2 3 import (
4 "mime"
5 6 "github.com/emersion/go-message/textproto"
7 )
8 9 func parseHeaderWithParams(s string) (f string, params map[string]string, err error) {
10 f, params, err = mime.ParseMediaType(s)
11 if err != nil {
12 return s, nil, err
13 }
14 for k, v := range params {
15 params[k], _ = decodeHeader(v)
16 }
17 return
18 }
19 20 func formatHeaderWithParams(f string, params map[string]string) string {
21 encParams := make(map[string]string)
22 for k, v := range params {
23 encParams[k] = encodeHeader(v)
24 }
25 return mime.FormatMediaType(f, encParams)
26 }
27 28 // HeaderFields iterates over header fields.
29 type HeaderFields interface {
30 textproto.HeaderFields
31 32 // Text parses the value of the current field as plaintext. The field
33 // charset is decoded to UTF-8. If the header field's charset is unknown,
34 // the raw field value is returned and the error verifies IsUnknownCharset.
35 Text() (string, error)
36 }
37 38 type headerFields struct {
39 textproto.HeaderFields
40 }
41 42 func (hf *headerFields) Text() (string, error) {
43 return decodeHeader(hf.Value())
44 }
45 46 // A Header represents the key-value pairs in a message header.
47 type Header struct {
48 textproto.Header
49 }
50 51 // HeaderFromMap creates a header from a map of header fields.
52 //
53 // This function is provided for interoperability with the standard library.
54 // If possible, ReadHeader should be used instead to avoid loosing information.
55 // The map representation looses the ordering of the fields, the capitalization
56 // of the header keys, and the whitespace of the original header.
57 func HeaderFromMap(m map[string][]string) Header {
58 return Header{textproto.HeaderFromMap(m)}
59 }
60 61 // ContentType parses the Content-Type header field.
62 //
63 // If no Content-Type is specified, it returns "text/plain".
64 func (h *Header) ContentType() (t string, params map[string]string, err error) {
65 v := h.Get("Content-Type")
66 if v == "" {
67 return "text/plain", nil, nil
68 }
69 return parseHeaderWithParams(v)
70 }
71 72 // SetContentType formats the Content-Type header field.
73 func (h *Header) SetContentType(t string, params map[string]string) {
74 h.Set("Content-Type", formatHeaderWithParams(t, params))
75 }
76 77 // ContentDisposition parses the Content-Disposition header field, as defined in
78 // RFC 2183.
79 func (h *Header) ContentDisposition() (disp string, params map[string]string, err error) {
80 return parseHeaderWithParams(h.Get("Content-Disposition"))
81 }
82 83 // SetContentDisposition formats the Content-Disposition header field, as
84 // defined in RFC 2183.
85 func (h *Header) SetContentDisposition(disp string, params map[string]string) {
86 h.Set("Content-Disposition", formatHeaderWithParams(disp, params))
87 }
88 89 // Text parses a plaintext header field. The field charset is automatically
90 // decoded to UTF-8. If the header field's charset is unknown, the raw field
91 // value is returned and the error verifies IsUnknownCharset.
92 func (h *Header) Text(k string) (string, error) {
93 return decodeHeader(h.Get(k))
94 }
95 96 // SetText sets a plaintext header field.
97 func (h *Header) SetText(k, v string) {
98 h.Set(k, encodeHeader(v))
99 }
100 101 // Copy creates a stand-alone copy of the header.
102 func (h *Header) Copy() Header {
103 return Header{h.Header.Copy()}
104 }
105 106 // Fields iterates over all the header fields.
107 //
108 // The header may not be mutated while iterating, except using HeaderFields.Del.
109 func (h *Header) Fields() HeaderFields {
110 return &headerFields{h.Header.Fields()}
111 }
112 113 // FieldsByKey iterates over all fields having the specified key.
114 //
115 // The header may not be mutated while iterating, except using HeaderFields.Del.
116 func (h *Header) FieldsByKey(k string) HeaderFields {
117 return &headerFields{h.Header.FieldsByKey(k)}
118 }
119