diff --git a/msg.go b/msg.go index 182b2893..47ac6cf2 100644 --- a/msg.go +++ b/msg.go @@ -531,6 +531,10 @@ func dddToByte(s []byte) byte { return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) } +func dddStringToByte(s string) byte { + return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) +} + // Helper function for packing and unpacking func intToBytes(i *big.Int, length int) []byte { buf := i.Bytes() diff --git a/msg_helpers.go b/msg_helpers.go index d58ff1d7..81fc2b1b 100644 --- a/msg_helpers.go +++ b/msg_helpers.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "encoding/hex" "net" - "strconv" "strings" ) @@ -276,13 +275,7 @@ func unpackString(msg []byte, off int) (string, int, error) { s.WriteByte('\\') s.WriteByte(b) case b < ' ' || b > '~': // unprintable - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s.WriteByte('\\') - for i := len(bufs); i < 3; i++ { - s.WriteByte('0') - } - s.Write(bufs) + writeEscapedByte(&s, b) default: s.WriteByte(b) } diff --git a/types.go b/types.go index a64f4d7d..115f2c7b 100644 --- a/types.go +++ b/types.go @@ -419,128 +419,130 @@ type TXT struct { func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } func sprintName(s string) string { - src := []byte(s) - dst := make([]byte, 0, len(src)) - for i := 0; i < len(src); { - if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { - dst = append(dst, src[i:i+2]...) + var dst strings.Builder + dst.Grow(len(s)) + for i := 0; i < len(s); { + if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' { + dst.WriteString(s[i : i+2]) i += 2 - } else { - b, n := nextByte(src, i) - if n == 0 { - i++ // dangling back slash - } else if b == '.' { - dst = append(dst, b) - } else { - dst = appendDomainNameByte(dst, b) - } - i += n + continue } + + b, n := nextByte(s, i) + switch { + case n == 0: + i++ // dangling back slash + case b == '.': + dst.WriteByte('.') + default: + writeDomainNameByte(&dst, b) + } + i += n } - return string(dst) + return dst.String() } func sprintTxtOctet(s string) string { - src := []byte(s) - dst := make([]byte, 0, len(src)) - dst = append(dst, '"') - for i := 0; i < len(src); { - if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { - dst = append(dst, src[i:i+2]...) + var dst strings.Builder + dst.Grow(2 + len(s)) + dst.WriteByte('"') + for i := 0; i < len(s); { + if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' { + dst.WriteString(s[i : i+2]) i += 2 - } else { - b, n := nextByte(src, i) - if n == 0 { - i++ // dangling back slash - } else if b == '.' { - dst = append(dst, b) - } else { - if b < ' ' || b > '~' { - dst = appendByte(dst, b) - } else { - dst = append(dst, b) - } - } - i += n + continue } + + b, n := nextByte(s, i) + switch { + case n == 0: + i++ // dangling back slash + case b == '.': + dst.WriteByte('.') + case b < ' ' || b > '~': + writeEscapedByte(&dst, b) + default: + dst.WriteByte(b) + } + i += n } - dst = append(dst, '"') - return string(dst) + dst.WriteByte('"') + return dst.String() } func sprintTxt(txt []string) string { - var out []byte + var out strings.Builder for i, s := range txt { + out.Grow(3 + len(s)) if i > 0 { - out = append(out, ` "`...) + out.WriteString(` "`) } else { - out = append(out, '"') + out.WriteByte('"') } - bs := []byte(s) - for j := 0; j < len(bs); { - b, n := nextByte(bs, j) + for j := 0; j < len(s); { + b, n := nextByte(s, j) if n == 0 { break } - out = appendTXTStringByte(out, b) + writeTXTStringByte(&out, b) j += n } - out = append(out, '"') + out.WriteByte('"') } - return string(out) + return out.String() } -func appendDomainNameByte(s []byte, b byte) []byte { +func writeDomainNameByte(s *strings.Builder, b byte) { switch b { case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape - return append(s, '\\', b) + s.WriteByte('\\') + s.WriteByte(b) + default: + writeTXTStringByte(s, b) } - return appendTXTStringByte(s, b) } -func appendTXTStringByte(s []byte, b byte) []byte { - switch b { - case '"', '\\': - return append(s, '\\', b) +func writeTXTStringByte(s *strings.Builder, b byte) { + switch { + case b == '"' || b == '\\': + s.WriteByte('\\') + s.WriteByte(b) + case b < ' ' || b > '~': + writeEscapedByte(s, b) + default: + s.WriteByte(b) } - if b < ' ' || b > '~' { - return appendByte(s, b) - } - return append(s, b) } -func appendByte(s []byte, b byte) []byte { +func writeEscapedByte(s *strings.Builder, b byte) { var buf [3]byte bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') + s.WriteByte('\\') + for i := len(bufs); i < 3; i++ { + s.WriteByte('0') } - for _, r := range bufs { - s = append(s, r) - } - return s + s.Write(bufs) } -func nextByte(b []byte, offset int) (byte, int) { - if offset >= len(b) { +func nextByte(s string, offset int) (byte, int) { + if offset >= len(s) { return 0, 0 } - if b[offset] != '\\' { + if s[offset] != '\\' { // not an escape sequence - return b[offset], 1 + return s[offset], 1 } - switch len(b) - offset { + switch len(s) - offset { case 1: // dangling escape return 0, 0 case 2, 3: // too short to be \ddd default: // maybe \ddd - if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) { - return dddToByte(b[offset+1:]), 4 + if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) { + return dddStringToByte(s[offset+1:]), 4 } } // not \ddd, just an RFC 1035 "quoted" character - return b[offset+1], 2 + return s[offset+1], 2 } // SPF RR. See RFC 4408, Section 3.1.1. diff --git a/types_test.go b/types_test.go index 3dbddee1..81a67fd9 100644 --- a/types_test.go +++ b/types_test.go @@ -72,3 +72,65 @@ func TestSplitN(t *testing.T) { t.Errorf("failure to split 510 char long string: %d", len(xs)) } } + +func TestSprintName(t *testing.T) { + got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\") + + if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want { + t.Errorf("expected %q, got %q", got, want) + } +} + +func TestSprintTxtOctet(t *testing.T) { + got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\") + + if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want { + t.Errorf("expected %q, got %q", got, want) + } +} + +func TestSprintTxt(t *testing.T) { + got := sprintTxt([]string{ + "abc\\.def\007\"\127@\255\x05\xef\\", + "example.com", + }) + + if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want { + t.Errorf("expected %q, got %q", got, want) + } +} + +func BenchmarkSprintName(b *testing.B) { + for n := 0; n < b.N; n++ { + got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\") + + if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want { + b.Fatalf("expected %q, got %q", got, want) + } + } +} + +func BenchmarkSprintTxtOctet(b *testing.B) { + for n := 0; n < b.N; n++ { + got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\") + + if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want { + b.Fatalf("expected %q, got %q", got, want) + } + } +} + +func BenchmarkSprintTxt(b *testing.B) { + txt := []string{ + "abc\\.def\007\"\127@\255\x05\xef\\", + "example.com", + } + + for n := 0; n < b.N; n++ { + got := sprintTxt(txt) + + if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want { + b.Fatalf("expected %q, got %q", got, want) + } + } +}