net/mail: fix quadratic consumePhrase behavior
Fixes #78987
Fixes CVE-2026-42499
diff --git a/src/net/mail/message.go b/src/net/mail/message.go
index fbf1fca..7282832 100644
--- a/src/net/mail/message.go
+++ b/src/net/mail/message.go
@@ -575,8 +575,11 @@
func (p *addrParser) consumePhrase() (phrase string, err error) {
debug.Printf("consumePhrase: [%s]", p.s)
// phrase = 1*word
- var words []string
- var isPrevEncoded bool
+ var (
+ words []string
+ isPrevEncoded bool
+ sb strings.Builder
+ )
for {
// obs-phrase allows CFWS after one word
if len(words) > 0 {
@@ -608,13 +611,25 @@
break
}
debug.Printf("consumePhrase: consumed %q", word)
- if isPrevEncoded && isEncoded {
- words[len(words)-1] += word
- } else {
- words = append(words, word)
+ switch {
+ case isPrevEncoded && isEncoded:
+ sb.WriteString(word)
+ isPrevEncoded = isEncoded
+ continue
+ case isPrevEncoded && sb.Len() > 0:
+ words[len(words)-1] = sb.String()
+ sb.Reset()
+ case isEncoded:
+ sb.WriteString(word)
}
+ words = append(words, word)
isPrevEncoded = isEncoded
}
+
+ if sb.Len() > 0 {
+ words[len(words)-1] = sb.String()
+ }
+
// Ignore any error if we got at least one word.
if err != nil && len(words) == 0 {
debug.Printf("consumePhrase: hit err: %v", err)
diff --git a/src/net/mail/message_test.go b/src/net/mail/message_test.go
index 3393b03..80a4b2d 100644
--- a/src/net/mail/message_test.go
+++ b/src/net/mail/message_test.go
@@ -1262,6 +1262,17 @@
}
}
+func BenchmarkConsumePhrase(b *testing.B) {
+ for _, n := range []int{10, 100, 1000, 10000} {
+ b.Run(fmt.Sprintf("words-%d", n), func(b *testing.B) {
+ input := strings.Repeat("=?utf-8?q?hello?= ", n) + "<us...@example.com>"
+ for b.Loop() {
+ (&addrParser{s: input}).consumePhrase()
+ }
+ })
+ }
+}
+
func BenchmarkConsumeComment(b *testing.B) {
for _, n := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("depth-%d", n), func(b *testing.B) {
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +2 |
isPrevEncoded = isEncoded`isPrevEncoded = isEncoded` would be a no-op here since `case isPrevEncoded && isEncoded`.
How about the following?
```
switch {
case isEncoded:
sb.WriteString(word)
case !isEncoded && sb.Len() > 0:
words = append(words, sb.String())
sb.Reset()
words = append(words, word)
default:
words = append(words, word)
}
```
And down below:
```
if sb.Len() > 0 {
words = append(words, sb.String())
}
```
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
`isPrevEncoded = isEncoded` would be a no-op here since `case isPrevEncoded && isEncoded`.
How about the following?
```
switch {
case isEncoded:
sb.WriteString(word)
case !isEncoded && sb.Len() > 0:
words = append(words, sb.String())
sb.Reset()
words = append(words, word)
default:
words = append(words, word)
}
```And down below:
```
if sb.Len() > 0 {
words = append(words, sb.String())
}
```
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
2 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.
net/mail: fix quadratic consumePhrase behavior
Updates #78987
Fixes CVE-2026-42499
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |