Be consistent about domain name label character escaping (#1122)

* Improve sprintName tests

* Fix sprintName handling of escaped dots

* Make sprintName consistently drop dangling incomplete escapes

* Be consistent about domain name label character escaping

Fixes #1121

* Replace strings.IndexByte with faster special-purpose function
This commit is contained in:
Richard Gibson 2020-07-06 04:07:56 -04:00 committed by GitHub
parent 064ba4b789
commit 978b9a827a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 37 deletions

15
msg.go
View File

@ -398,17 +398,12 @@ Loop:
return "", lenmsg, ErrLongDomain return "", lenmsg, ErrLongDomain
} }
for _, b := range msg[off : off+c] { for _, b := range msg[off : off+c] {
switch b { if isDomainNameLabelSpecial(b) {
case '.', '(', ')', ';', ' ', '@':
fallthrough
case '"', '\\':
s = append(s, '\\', b) s = append(s, '\\', b)
default: } else if b < ' ' || b > '~' {
if b < ' ' || b > '~' { // unprintable, use \DDD s = append(s, escapeByte(b)...)
s = append(s, escapeByte(b)...) } else {
} else { s = append(s, b)
s = append(s, b)
}
} }
} }
s = append(s, '.') s = append(s, '.')

View File

@ -445,45 +445,38 @@ func sprintName(s string) string {
var dst strings.Builder var dst strings.Builder
for i := 0; i < len(s); { for i := 0; i < len(s); {
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' { if s[i] == '.' {
if dst.Len() != 0 { if dst.Len() != 0 {
dst.WriteString(s[i : i+2]) dst.WriteByte('.')
} }
i += 2 i++
continue continue
} }
b, n := nextByte(s, i) b, n := nextByte(s, i)
if n == 0 { if n == 0 {
i++ // Drop "dangling" incomplete escapes.
continue if dst.Len() == 0 {
} return s[:i]
if b == '.' {
if dst.Len() != 0 {
dst.WriteByte('.')
} }
i += n break
continue
} }
switch b { if isDomainNameLabelSpecial(b) {
case ' ', '\'', '@', ';', '(', ')', '"', '\\': // additional chars to escape
if dst.Len() == 0 { if dst.Len() == 0 {
dst.Grow(len(s) * 2) dst.Grow(len(s) * 2)
dst.WriteString(s[:i]) dst.WriteString(s[:i])
} }
dst.WriteByte('\\') dst.WriteByte('\\')
dst.WriteByte(b) dst.WriteByte(b)
default: } else if b < ' ' || b > '~' { // unprintable, use \DDD
if ' ' <= b && b <= '~' { if dst.Len() == 0 {
if dst.Len() != 0 { dst.Grow(len(s) * 2)
dst.WriteByte(b) dst.WriteString(s[:i])
} }
} else { dst.WriteString(escapeByte(b))
if dst.Len() == 0 { } else {
dst.Grow(len(s) * 2) if dst.Len() != 0 {
dst.WriteString(s[:i]) dst.WriteByte(b)
}
dst.WriteString(escapeByte(b))
} }
} }
i += n i += n
@ -585,6 +578,17 @@ func escapeByte(b byte) string {
return escapedByteLarge[int(b)*4 : int(b)*4+4] return escapedByteLarge[int(b)*4 : int(b)*4+4]
} }
// isDomainNameLabelSpecial returns true if
// a domain name label byte should be prefixed
// with an escaping backslash.
func isDomainNameLabelSpecial(b byte) bool {
switch b {
case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\':
return true
}
return false
}
func nextByte(s string, offset int) (byte, int) { func nextByte(s string, offset int) (byte, int) {
if offset >= len(s) { if offset >= len(s) {
return 0, 0 return 0, 0

View File

@ -74,10 +74,33 @@ func TestSplitN(t *testing.T) {
} }
func TestSprintName(t *testing.T) { func TestSprintName(t *testing.T) {
got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\") tests := map[string]string{
// Non-numeric escaping of special printable characters.
" '@;()\"\\..example": `\ \'\@\;\(\)\"\..example`,
"\\032\\039\\064\\059\\040\\041\\034\\046\\092.example": `\ \'\@\;\(\)\"\.\\.example`,
if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want { // Numeric escaping of nonprintable characters.
t.Errorf("expected %q, got %q", want, got) "\x00\x07\x09\x0a\x1f.\x7f\x80\xad\xef\xff": `\000\007\009\010\031.\127\128\173\239\255`,
"\\000\\007\\009\\010\\031.\\127\\128\\173\\239\\255": `\000\007\009\010\031.\127\128\173\239\255`,
// No escaping of other printable characters, at least after a prior escape.
";[a-zA-Z0-9_]+/*.~": `\;[a-zA-Z0-9_]+/*.~`,
";\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `\;[a-zA-Z0-9_]+/*.~`,
// "\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `[a-zA-Z0-9_]+/*.~`,
// Incomplete "dangling" escapes are dropped regardless of prior escaping.
"a\\": `a`,
";\\": `\;`,
// Escaped dots stay escaped regardless of prior escaping.
"a\\.\\046.\\.\\046": `a\.\..\.\.`,
"a\\046\\..\\046\\.": `a\.\..\.\.`,
}
for input, want := range tests {
got := sprintName(input)
if got != want {
t.Errorf("input %q: expected %q, got %q", input, want, got)
}
} }
} }