Skip to content

Commit d3869e0

Browse files
Fix counting of escape sequences when splitting TXT strings
`endingToTxtSlice`, used by TXT, SPF and a few other types, parses a string such as `"hello world"` from an RR's content in a zone file. These strings are limited to 255 characters, and `endingToTxtSlice` automatically splits them if they're longer than that. However, it didn't count the length correctly: escape sequences such as `\\` or `\123` were counted as multiple characters (2 and 4 respectively in these examples), but they should only count as one character because they represent a single byte in wire format (which is where this 255 character limit comes from). This commit fixes that.
1 parent 2230854 commit d3869e0

File tree

3 files changed

+81
-25
lines changed

3 files changed

+81
-25
lines changed

parse_test.go

+11-8
Original file line numberDiff line numberDiff line change
@@ -1098,18 +1098,21 @@ func TestTXT(t *testing.T) {
10981098
}
10991099
}
11001100

1101-
// Test TXT record with chunk larger than 255 bytes, they should be split up, by the parser
1102-
s := ""
1103-
for i := 0; i < 255; i++ {
1104-
s += "a"
1105-
}
1106-
s += "b"
1101+
// Test TXT record with chunk larger than 255 bytes; they should be split
1102+
// up by the parser. Add some escape sequences too to ensure their length
1103+
// is counted correctly.
1104+
s := `\;\\\120` + strings.Repeat("a", 255) + "b"
11071105
rr, err = NewRR(`test.local. 60 IN TXT "` + s + `"`)
11081106
if err != nil {
11091107
t.Error("failed to parse empty-string TXT record", err)
11101108
}
1111-
if rr.(*TXT).Txt[1] != "b" {
1112-
t.Errorf("Txt should have two chunk, last one my be 'b', but is %s", rr.(*TXT).Txt[1])
1109+
if rr.(*TXT).Txt[1] != "aaab" {
1110+
t.Errorf("Txt should have two chunks, last one must be 'aaab', but is %s", rr.(*TXT).Txt[1])
1111+
}
1112+
rrContent := strings.Replace(rr.String(), rr.Header().String(), "", 1)
1113+
expectedRRContent := `";\\x` + strings.Repeat("a", 252) + `" "aaab"`
1114+
if expectedRRContent != rrContent {
1115+
t.Errorf("Expected TXT RR content to be %#q but got %#q", expectedRRContent, rrContent)
11131116
}
11141117
}
11151118

scan_rr.go

+42-17
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,21 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
5151
switch l.value {
5252
case zString:
5353
empty = false
54-
if len(l.token) > 255 {
55-
// split up tokens that are larger than 255 into 255-chunks
56-
sx := []string{}
57-
p, i := 0, 255
58-
for {
59-
if i <= len(l.token) {
60-
sx = append(sx, l.token[p:i])
61-
} else {
62-
sx = append(sx, l.token[p:])
63-
break
64-
65-
}
66-
p, i = p+255, i+255
54+
// split up tokens that are larger than 255 into 255-chunks
55+
sx := []string{}
56+
p := 0
57+
for {
58+
i := escapedStringOffset(l.token[p:], 255)
59+
if i != -1 {
60+
sx = append(sx, l.token[p:p+i])
61+
} else {
62+
sx = append(sx, l.token[p:])
63+
break
64+
6765
}
68-
s = append(s, sx...)
69-
break
66+
p += i
7067
}
71-
72-
s = append(s, l.token)
68+
s = append(s, sx...)
7369
case zBlank:
7470
if quote {
7571
// zBlank can only be seen in between txt parts.
@@ -1920,3 +1916,32 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
19201916
rr.Prefixes = prefixes
19211917
return nil
19221918
}
1919+
1920+
// escapedStringOffset finds the offset within a string (which may contain escape
1921+
// sequences) that corresponds to a certain byte offset. If the input offset is
1922+
// out of bounds, -1 is returned.
1923+
func escapedStringOffset(s string, byteOffset int) int {
1924+
if byteOffset == 0 {
1925+
return 0
1926+
}
1927+
1928+
offset := 0
1929+
for i := 0; i < len(s); i++ {
1930+
offset += 1
1931+
1932+
// Skip escape sequences
1933+
if s[i] != '\\' {
1934+
// Not an escape sequence; nothing to do.
1935+
} else if isDDD(s[i+1:]) {
1936+
i += 3
1937+
} else {
1938+
i++
1939+
}
1940+
1941+
if offset >= byteOffset {
1942+
return i + 1
1943+
}
1944+
}
1945+
1946+
return -1
1947+
}

scan_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,31 @@ func BenchmarkZoneParser(b *testing.B) {
427427
}
428428
}
429429
}
430+
431+
func TestEscapedStringOffset(t *testing.T) {
432+
var cases = []struct {
433+
input string
434+
inputOffset int
435+
expectedOffset int
436+
}{
437+
{"simple string with no escape sequences", 20, 20},
438+
{"simple string with no escape sequences", 500, -1},
439+
{`\;\088\\\;\120\\`, 0, 0},
440+
{`\;\088\\\;\120\\`, 1, 2},
441+
{`\;\088\\\;\120\\`, 2, 6},
442+
{`\;\088\\\;\120\\`, 3, 8},
443+
{`\;\088\\\;\120\\`, 4, 10},
444+
{`\;\088\\\;\120\\`, 5, 14},
445+
{`\;\088\\\;\120\\`, 6, 16},
446+
{`\;\088\\\;\120\\`, 7, -1},
447+
}
448+
for i, test := range cases {
449+
outputOffset := escapedStringOffset(test.input, test.inputOffset)
450+
if outputOffset != test.expectedOffset {
451+
t.Errorf(
452+
"Test %d (input %#q offset %d) returned offset %d but expected %d",
453+
i, test.input, test.inputOffset, outputOffset, test.expectedOffset,
454+
)
455+
}
456+
}
457+
}

0 commit comments

Comments
 (0)