Skip to content

Commit b1df561

Browse files
authored
Merge pull request #105 from clue-labs/txt
Support parsing TXT records
2 parents 5c570db + 2f89304 commit b1df561

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

examples/11-query-any.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
case Message::TYPE_CNAME:
3838
$type = 'CNAME';
3939
break;
40+
case Message::TYPE_TXT:
41+
// TXT records can contain a list of (binary) strings for each record.
42+
// here, we assume this is printable ASCII and simply concatenate output
43+
$type = 'TXT';
44+
$data = implode('', $data);
45+
break;
4046
default:
4147
// unknown type uses HEX format
4248
$type = 'Type ' . $answer->type;

src/Model/Record.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ class Record
3737
* IPv6 address string, for example "::1".
3838
* - CNAME / PTR / NS:
3939
* The hostname without trailing dot, for example "reactphp.org".
40+
* - TXT:
41+
* List of string values, for example `["v=spf1 include:example.com"]`.
42+
* This is commonly a list with only a single string value, but this
43+
* technically allows multiple strings (0-255 bytes each) in a single
44+
* record. This is rarely used and depending on application you may want
45+
* to join these together or handle them separately. Each string can
46+
* transport any binary data, its character encoding is not defined (often
47+
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
48+
* suggests using key-value pairs such as `["name=test","version=1"]`, but
49+
* interpretation of this is not enforced and left up to consumers of this
50+
* library (used for DNS-SD/Zeroconf and others).
4051
* - Any other unknown type:
4152
* An opaque binary string containing the RDATA as transported in the DNS
4253
* record. For forwards compatibility, you should not rely on this format
@@ -45,7 +56,7 @@ class Record
4556
* considered a BC break. See the format definition of known types above
4657
* for more details.
4758
*
48-
* @var string
59+
* @var string|string[]
4960
*/
5061
public $data;
5162

src/Protocol/Parser.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ public function parseAnswer(Message $message)
168168
list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed);
169169

170170
$rdata = implode('.', $bodyLabels);
171+
} elseif (Message::TYPE_TXT === $type) {
172+
$rdata = array();
173+
$remaining = $rdLength;
174+
while ($remaining) {
175+
$len = ord($message->data[$consumed]);
176+
$rdata[] = substr($message->data, $consumed + 1, $len);
177+
$consumed += $len + 1;
178+
$remaining -= $len + 1;
179+
}
171180
} else {
172181
// unknown types simply parse rdata as an opaque binary string
173182
$rdata = substr($message->data, $consumed, $rdLength);

tests/Protocol/ParserTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,56 @@ public function testParseAAAAResponse()
256256
$this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data);
257257
}
258258

259+
public function testParseTXTResponse()
260+
{
261+
$data = "";
262+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
263+
$data .= "00 10 00 01"; // answer: type TXT, class IN
264+
$data .= "00 01 51 80"; // answer: ttl 86400
265+
$data .= "00 06"; // answer: rdlength 6
266+
$data .= "05 68 65 6c 6c 6f"; // answer: rdata length 5: hello
267+
268+
$data = $this->convertTcpDumpToBinary($data);
269+
270+
$response = new Message();
271+
$response->header->set('anCount', 1);
272+
$response->data = $data;
273+
274+
$this->parser->parseAnswer($response);
275+
276+
$this->assertCount(1, $response->answers);
277+
$this->assertSame('igor.io', $response->answers[0]->name);
278+
$this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
279+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
280+
$this->assertSame(86400, $response->answers[0]->ttl);
281+
$this->assertSame(array('hello'), $response->answers[0]->data);
282+
}
283+
284+
public function testParseTXTResponseMultiple()
285+
{
286+
$data = "";
287+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
288+
$data .= "00 10 00 01"; // answer: type TXT, class IN
289+
$data .= "00 01 51 80"; // answer: ttl 86400
290+
$data .= "00 0C"; // answer: rdlength 12
291+
$data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: rdata length 5: hello, length 5: world
292+
293+
$data = $this->convertTcpDumpToBinary($data);
294+
295+
$response = new Message();
296+
$response->header->set('anCount', 1);
297+
$response->data = $data;
298+
299+
$this->parser->parseAnswer($response);
300+
301+
$this->assertCount(1, $response->answers);
302+
$this->assertSame('igor.io', $response->answers[0]->name);
303+
$this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
304+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
305+
$this->assertSame(86400, $response->answers[0]->ttl);
306+
$this->assertSame(array('hello', 'world'), $response->answers[0]->data);
307+
}
308+
259309
public function testParseResponseWithTwoAnswers()
260310
{
261311
$data = "";

0 commit comments

Comments
 (0)