fix: escape DNS label text per RFC 1035 §5.1 (closes #36) #54
@@ -180,13 +180,7 @@ impl BytePacketBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write a qname in wire format, parsing RFC 1035 §5.1 text escapes.
|
/// Write a qname in wire format, parsing RFC 1035 §5.1 text escapes.
|
||||||
///
|
/// See `read_qname` for the escape grammar.
|
||||||
/// Dots are label separators unless escaped as `\.`. `\\` yields a literal
|
|
||||||
/// backslash, and `\DDD` (three decimal digits) yields an arbitrary byte.
|
|
||||||
///
|
|
||||||
/// Streams directly into the buffer by reserving a length byte, writing
|
|
||||||
/// the label body, then backpatching the length. Zero intermediate
|
|
||||||
/// allocations on the common path.
|
|
||||||
pub fn write_qname(&mut self, qname: &str) -> Result<()> {
|
pub fn write_qname(&mut self, qname: &str) -> Result<()> {
|
||||||
if qname.is_empty() || qname == "." {
|
if qname.is_empty() || qname == "." {
|
||||||
self.write_u8(0)?;
|
self.write_u8(0)?;
|
||||||
@@ -368,6 +362,19 @@ mod tests {
|
|||||||
assert_eq!(&buf.buf[..buf.pos], b"\x04\x00foo\x03com\x00");
|
assert_eq!(&buf.buf[..buf.pos], b"\x04\x00foo\x03com\x00");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_skips_empty_labels() {
|
||||||
|
// Leading dot — first (empty) label is rolled back.
|
||||||
|
let mut buf = BytePacketBuffer::new();
|
||||||
|
buf.write_qname(".foo.com").unwrap();
|
||||||
|
assert_eq!(&buf.buf[..buf.pos], b"\x03foo\x03com\x00");
|
||||||
|
|
||||||
|
// Consecutive dots — middle empty label is rolled back.
|
||||||
|
let mut buf = BytePacketBuffer::new();
|
||||||
|
buf.write_qname("foo..com").unwrap();
|
||||||
|
assert_eq!(&buf.buf[..buf.pos], b"\x03foo\x03com\x00");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_rejects_out_of_range_decimal_escape() {
|
fn write_rejects_out_of_range_decimal_escape() {
|
||||||
let mut buf = BytePacketBuffer::new();
|
let mut buf = BytePacketBuffer::new();
|
||||||
@@ -386,6 +393,17 @@ mod tests {
|
|||||||
assert!(buf.write_qname("\\1").is_err());
|
assert!(buf.write_qname("\\1").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_rejects_label_over_63_bytes() {
|
||||||
|
// 64 bytes exceeds the wire-format label cap.
|
||||||
|
let mut buf = BytePacketBuffer::new();
|
||||||
|
assert!(buf.write_qname(&"a".repeat(64)).is_err());
|
||||||
|
|
||||||
|
// 63 bytes is the maximum permitted label length.
|
||||||
|
let mut buf = BytePacketBuffer::new();
|
||||||
|
assert!(buf.write_qname(&"a".repeat(63)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_preserves_dot_in_label() {
|
fn roundtrip_preserves_dot_in_label() {
|
||||||
assert_eq!(write_then_read("exa\\.mple.com"), "exa\\.mple.com");
|
assert_eq!(write_then_read("exa\\.mple.com"), "exa\\.mple.com");
|
||||||
|
|||||||
@@ -722,14 +722,10 @@ pub fn verify_ds(ds: &DnsRecord, dnskey: &DnsRecord, owner: &str) -> bool {
|
|||||||
// -- Canonical wire format --
|
// -- Canonical wire format --
|
||||||
|
|
||||||
/// Encode a DNS name in canonical wire form per RFC 4034 §6.2:
|
/// Encode a DNS name in canonical wire form per RFC 4034 §6.2:
|
||||||
/// uncompressed, with all ASCII letters lowercased.
|
/// uncompressed, with ASCII letters lowercased.
|
||||||
///
|
///
|
||||||
/// Label parsing and RFC 1035 §5.1 escape handling live in
|
/// Lowercasing happens *after* escape resolution because `\065` yields
|
||||||
/// `BytePacketBuffer::write_qname`; this function then walks the emitted
|
/// `'A'`, which canonical form must convert to `'a'`.
|
||||||
/// wire bytes once to lowercase label bodies (length bytes stay untouched).
|
|
||||||
/// Lowercasing has to happen post-escape-resolution because a decimal
|
|
||||||
/// escape like `\065` yields `'A'`, which canonical form must convert
|
|
||||||
/// to `'a'`.
|
|
||||||
pub fn name_to_wire(name: &str) -> Vec<u8> {
|
pub fn name_to_wire(name: &str) -> Vec<u8> {
|
||||||
let mut buf = BytePacketBuffer::new();
|
let mut buf = BytePacketBuffer::new();
|
||||||
buf.write_qname(name)
|
buf.write_qname(name)
|
||||||
|
|||||||
Reference in New Issue
Block a user