Skip to content

Commit 7c07f35

Browse files
committed
fix: TXT decode preserves string-boundary info
1 parent 887d9aa commit 7c07f35

4 files changed

Lines changed: 35 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
44

55
### Unreleased
66

7+
- **BREAKING** fix(packet): TXT decode preserves character-string boundaries (RFC 1035 §3.3.14); `data` is now always an array of strings
78
- fix(packet): IPv6 `::` compression for leading-zero address #123
89
- fix(packet): Name decode rejects pointer cycles (RFC 1035) #124
910
- fix(packet): EDNS exposes extendedRcode/version/doFlag #124

packet.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -661,21 +661,24 @@ Packet.Resource.CNAME = {
661661
*/
662662
Packet.Resource.SPF =
663663
Packet.Resource.TXT = {
664+
// RFC 1035 §3.3.14: TXT RDATA is one or more length-prefixed
665+
// <character-string> items. Preserve those boundaries by returning an
666+
// array — joining them silently corrupts SPF/DKIM and other multi-string
667+
// records whose semantics depend on segmentation.
664668
decode: function(reader, length) {
665-
const parts = [];
666-
let bytesRead = 0; let chunkLength;
667-
669+
const strings = [];
670+
let bytesRead = 0;
668671
while (bytesRead < length) {
669-
chunkLength = reader.read(8); // text length
672+
const chunkLength = reader.read(8);
670673
bytesRead++;
671-
672-
while (chunkLength--) {
673-
parts.push(reader.read(8));
674+
const bytes = [];
675+
for (let i = 0; i < chunkLength; i++) {
676+
bytes.push(reader.read(8));
674677
bytesRead++;
675678
}
679+
strings.push(Buffer.from(bytes).toString('utf8'));
676680
}
677-
678-
this.data = Buffer.from(parts).toString('utf8');
681+
this.data = strings;
679682
return this;
680683
},
681684
encode: function(record, writer) {

test/packet.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ test('Packet#encode', function() {
211211
type : Packet.TYPE.TXT,
212212
class : Packet.CLASS.IN,
213213
ttl : 300,
214-
data : '#v=spf1 include:_spf.google.com ~all',
214+
// TXT data is an array of <character-string> items (RFC 1035 §3.3.14).
215+
data : [ '#v=spf1 include:_spf.google.com ~all' ],
215216
});
216217

217218
assert.deepEqual(Packet.parse(response.toBuffer()), response);
@@ -235,7 +236,7 @@ test('Packet#encode array of character strings', function() {
235236
data : dkim,
236237
});
237238

238-
assert.equal(Packet.parse(response.toBuffer()).answers[0].data, dkim.join(''));
239+
assert.deepEqual(Packet.parse(response.toBuffer()).answers[0].data, dkim);
239240
});
240241

241242
test('EDNS.ECS#encode', function() {
@@ -394,9 +395,9 @@ test('Resource#TXT round-trip single string', function() {
394395
type : Packet.TYPE.TXT,
395396
class : Packet.CLASS.IN,
396397
ttl : 300,
397-
data : 'hello world',
398+
data : 'hello world', // encoder normalizes string → [string]
398399
});
399-
assert.equal(out.data, 'hello world');
400+
assert.deepEqual(out.data, [ 'hello world' ]);
400401
});
401402

402403
test('Resource#TXT round-trip with utf-8', function() {
@@ -407,7 +408,20 @@ test('Resource#TXT round-trip with utf-8', function() {
407408
ttl : 300,
408409
data : 'café résumé 日本',
409410
});
410-
assert.equal(out.data, 'café résumé 日本');
411+
assert.deepEqual(out.data, [ 'café résumé 日本' ]);
412+
});
413+
414+
test('Resource#TXT preserves character-string boundaries', function() {
415+
// SPF/DKIM-style multi-string TXT records must not be merged on decode.
416+
const chunks = [ 'part-one ', 'part-two ', 'part-three' ];
417+
const out = roundTripAnswer({
418+
name : 'multi.example',
419+
type : Packet.TYPE.TXT,
420+
class : Packet.CLASS.IN,
421+
ttl : 60,
422+
data : chunks,
423+
});
424+
assert.deepEqual(out.data, chunks);
411425
});
412426

413427
test('Resource#SOA round-trip', function() {

test/server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ test('server/udp-tcp#simple-request-async-response', async() => {
119119
assert.ok(servers.tcp.port > 1000);
120120
const tcpClient = TCPClient({ dns: '127.0.0.1', port: servers.tcp.port });
121121
const udpClient = UDPClient({ dns: '127.0.0.1', port: servers.udp.port });
122-
const expected = [ { name: 'test.com', ttl: 300, type: 16, class: 1, data: 'Hello World' } ];
122+
const expected = [ { name: 'test.com', ttl: 300, type: 16, class: 1, data: [ 'Hello World' ] } ];
123123
assert.deepEqual((await tcpClient('test.com')).answers, expected);
124124
assert.deepEqual((await udpClient('test.com')).answers, expected);
125125
await server.close();
@@ -207,7 +207,7 @@ test('server/doh#POST end-to-end', async() => {
207207
type : Packet.TYPE.TXT,
208208
class : Packet.CLASS.IN,
209209
ttl : 60,
210-
data : 'post-ok',
210+
data : [ 'post-ok' ],
211211
});
212212
send(response);
213213
});
@@ -243,7 +243,7 @@ test('server/doh#POST end-to-end', async() => {
243243
});
244244
const parsed = Packet.parse(body);
245245
assert.equal(parsed.answers.length, 1);
246-
assert.equal(parsed.answers[0].data, 'post-ok');
246+
assert.deepEqual(parsed.answers[0].data, [ 'post-ok' ]);
247247
server.close();
248248
});
249249

0 commit comments

Comments
 (0)