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:
parent
064ba4b789
commit
978b9a827a
15
msg.go
15
msg.go
@ -398,17 +398,12 @@ Loop:
|
||||
return "", lenmsg, ErrLongDomain
|
||||
}
|
||||
for _, b := range msg[off : off+c] {
|
||||
switch b {
|
||||
case '.', '(', ')', ';', ' ', '@':
|
||||
fallthrough
|
||||
case '"', '\\':
|
||||
if isDomainNameLabelSpecial(b) {
|
||||
s = append(s, '\\', b)
|
||||
default:
|
||||
if b < ' ' || b > '~' { // unprintable, use \DDD
|
||||
s = append(s, escapeByte(b)...)
|
||||
} else {
|
||||
s = append(s, b)
|
||||
}
|
||||
} else if b < ' ' || b > '~' {
|
||||
s = append(s, escapeByte(b)...)
|
||||
} else {
|
||||
s = append(s, b)
|
||||
}
|
||||
}
|
||||
s = append(s, '.')
|
||||
|
52
types.go
52
types.go
@ -445,45 +445,38 @@ func sprintName(s string) string {
|
||||
var dst strings.Builder
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
|
||||
if s[i] == '.' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteString(s[i : i+2])
|
||||
dst.WriteByte('.')
|
||||
}
|
||||
i += 2
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
b, n := nextByte(s, i)
|
||||
if n == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if b == '.' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte('.')
|
||||
// Drop "dangling" incomplete escapes.
|
||||
if dst.Len() == 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i += n
|
||||
continue
|
||||
break
|
||||
}
|
||||
switch b {
|
||||
case ' ', '\'', '@', ';', '(', ')', '"', '\\': // additional chars to escape
|
||||
if isDomainNameLabelSpecial(b) {
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteByte('\\')
|
||||
dst.WriteByte(b)
|
||||
default:
|
||||
if ' ' <= b && b <= '~' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
} else {
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteString(escapeByte(b))
|
||||
} else if b < ' ' || b > '~' { // unprintable, use \DDD
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteString(escapeByte(b))
|
||||
} else {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
}
|
||||
i += n
|
||||
@ -585,6 +578,17 @@ func escapeByte(b byte) string {
|
||||
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) {
|
||||
if offset >= len(s) {
|
||||
return 0, 0
|
||||
|
@ -74,10 +74,33 @@ func TestSplitN(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 {
|
||||
t.Errorf("expected %q, got %q", want, got)
|
||||
// Numeric escaping of nonprintable characters.
|
||||
"\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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user