Skip to content

Commit d262af7

Browse files
dvalteremersion
authored andcommitted
textproto: check header characters
Add checks for header field name according to RFC 6532 and disallow newline characters in field values.
1 parent 5b97b1b commit d262af7

File tree

2 files changed

+111
-20
lines changed

2 files changed

+111
-20
lines changed

textproto/header.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@ func newHeaderField(k, v string, b []byte) *headerField {
2020
return &headerField{k: textproto.CanonicalMIMEHeaderKey(k), v: v, b: b}
2121
}
2222

23-
func (f *headerField) raw() []byte {
23+
func (f *headerField) raw() ([]byte, error) {
2424
if f.b != nil {
25-
return f.b
25+
return f.b, nil
2626
} else {
27-
return []byte(formatHeaderField(f.k, f.v))
27+
for pos, ch := range f.k {
28+
// check if character is a printable US-ASCII except ':'
29+
if !(ch >= '!' && ch < ':' || ch > ':' && ch <= '~') {
30+
return nil, fmt.Errorf("field name contains incorrect symbols (\\x%x at %v)", ch, pos)
31+
}
32+
}
33+
34+
if pos := strings.IndexAny(f.v, "\r\n"); pos != -1 {
35+
return nil, fmt.Errorf("field value contains \\r\\n (at %v)", pos)
36+
}
37+
38+
return []byte(formatHeaderField(f.k, f.v)), nil
2839
}
2940
}
3041

@@ -97,6 +108,9 @@ func (h *Header) AddRaw(kv []byte) {
97108

98109
// Add adds the key, value pair to the header. It prepends to any existing
99110
// fields associated with key.
111+
//
112+
// Key and value should obey character requirements of RFC 6532.
113+
// If you need to format/fold lines manually, use AddRaw
100114
func (h *Header) Add(k, v string) {
101115
k = textproto.CanonicalMIMEHeaderKey(k)
102116

@@ -126,10 +140,12 @@ func (h *Header) Get(k string) string {
126140
//
127141
// The returned slice should not be modified and becomes invalid when the
128142
// header is updated.
129-
func (h *Header) Raw(k string) []byte {
143+
//
144+
// Error is returned if header contains incorrect characters (RFC 6532)
145+
func (h *Header) Raw(k string) ([]byte, error) {
130146
fields := h.m[textproto.CanonicalMIMEHeaderKey(k)]
131147
if len(fields) == 0 {
132-
return nil
148+
return nil, nil
133149
}
134150
return fields[len(fields)-1].raw()
135151
}
@@ -185,7 +201,7 @@ type HeaderFields interface {
185201
// Value returns the value of the current field.
186202
Value() string
187203
// Raw returns the raw current header field. See Header.Raw.
188-
Raw() []byte
204+
Raw() ([]byte, error)
189205
// Del deletes the current field.
190206
Del()
191207
// Len returns the amount of header fields in the subset of header iterated
@@ -228,7 +244,7 @@ func (fs *headerFields) Value() string {
228244
return fs.field().v
229245
}
230246

231-
func (fs *headerFields) Raw() []byte {
247+
func (fs *headerFields) Raw() ([]byte, error) {
232248
return fs.field().raw()
233249
}
234250

@@ -298,7 +314,7 @@ func (fs *headerFieldsByKey) Value() string {
298314
return fs.field().v
299315
}
300316

301-
func (fs *headerFieldsByKey) Raw() []byte {
317+
func (fs *headerFieldsByKey) Raw() ([]byte, error) {
302318
return fs.field().raw()
303319
}
304320

@@ -628,8 +644,12 @@ func formatHeaderField(k, v string) string {
628644
func WriteHeader(w io.Writer, h Header) error {
629645
for i := len(h.l) - 1; i >= 0; i-- {
630646
f := h.l[i]
631-
if _, err := w.Write(f.raw()); err != nil {
632-
return err
647+
if rawField, err := f.raw(); err == nil {
648+
if _, err := w.Write(rawField); err != nil {
649+
return err
650+
}
651+
} else {
652+
return fmt.Errorf("failed to write header field #%v (%q): %w", len(h.l)-i, f.k, err)
633653
}
634654
}
635655

textproto/header_test.go

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -429,16 +429,6 @@ var formatHeaderFieldTests = []struct {
429429
v: "InCaseOfVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongStringWeStillShouldComplyToTheHardLimitOf998Symbols",
430430
formatted: "Subject: InCaseOfVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongStringWeStillSho\r\n uldComplyToTheHardLimitOf998Symbols\r\n",
431431
},
432-
{
433-
k: "DKIM-Signature",
434-
v: "v=1;\r\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version;\r\n d=example.org\r\n",
435-
formatted: "Dkim-Signature: v=1;\r\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version;\r\n d=example.org\r\n",
436-
},
437-
{
438-
k: "DKIM-Signature",
439-
v: "v=1; h=From; d=example.org; b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6x NAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrI x0orEtZV4bmp/YzhwvcubU4=\r\n",
440-
formatted: "Dkim-Signature: v=1; h=From; d=example.org;\r\n b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6x\r\n NAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrI\r\n x0orEtZV4bmp/YzhwvcubU4=\r\n",
441-
},
442432
{
443433
k: "Bcc",
444434
v: "",
@@ -465,3 +455,84 @@ func TestWriteHeader_continued(t *testing.T) {
465455
}
466456
}
467457
}
458+
459+
var incorrectFormatHeaderFieldTests = []struct {
460+
k, v string
461+
}{
462+
{
463+
k: "DKIM Signature",
464+
v: "v=1; h=From; d=example.org; b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6x NAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrI x0orEtZV4bmp/YzhwvcubU4=\r\n",
465+
},
466+
{
467+
// Unicode, Cyrillic
468+
k: "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a",
469+
v: "Value",
470+
},
471+
{
472+
k: "Header:",
473+
v: "Value",
474+
},
475+
{
476+
k: "DKIM-Signature",
477+
v: "v=1;\r\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version;\r\n d=example.org\r\n",
478+
},
479+
{
480+
k: "DKIM-Signature",
481+
v: "v=1;\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version; d=example.org",
482+
},
483+
{
484+
k: "DKIM-Signature",
485+
v: "v=1;\r h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version; d=example.org",
486+
},
487+
}
488+
489+
func TestWriteHeader_failed(t *testing.T) {
490+
for _, test := range incorrectFormatHeaderFieldTests {
491+
var h Header
492+
h.Add(test.k, test.v)
493+
494+
var b bytes.Buffer
495+
if err := WriteHeader(&b, h); err == nil {
496+
t.Errorf("Expected header \n%v: %v\n to be incorrect, but it was accepted", test.k, test.v)
497+
}
498+
}
499+
}
500+
501+
var incorrectFormatMultipleHeaderFieldTests = []struct {
502+
k1, k2, v1, v2 string
503+
}{
504+
{
505+
// Incorrect first
506+
k1: "DKIM Signature",
507+
v1: "v=1; h=From; d=example.org; b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6x NAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrI x0orEtZV4bmp/YzhwvcubU4=\r\n",
508+
k2: "From",
509+
510+
},
511+
{
512+
// Incorrect both
513+
k1: "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a",
514+
v1: "Value",
515+
k2: "Header:",
516+
v2: "Value",
517+
},
518+
{
519+
// Incorrect second
520+
k1: "DKIM-Signature",
521+
v1: "v=1; h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version; d=example.org",
522+
k2: "DKIM-Signature",
523+
v2: "v=1;\r\n h=From:To:Reply-To:Subject:Message-ID:References:In-Reply-To:MIME-Version;\r\n d=example.org\r\n",
524+
},
525+
}
526+
527+
func TestWriteHeader_failed_multiple(t *testing.T) {
528+
for _, test := range incorrectFormatMultipleHeaderFieldTests {
529+
var h Header
530+
h.Add(test.k1, test.v1)
531+
h.Add(test.k2, test.v2)
532+
533+
var b bytes.Buffer
534+
if err := WriteHeader(&b, h); err == nil {
535+
t.Errorf("Expected headers \n%v: %v\n%v: %v\n to be incorrect, but it was accepted", test.k1, test.v2, test.k2, test.v2)
536+
}
537+
}
538+
}

0 commit comments

Comments
 (0)