]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add a variant of parseRFC1035CharString, suitable for TXT record contents.
authorMiod Vallat <miod.vallat@powerdns.com>
Fri, 15 May 2026 12:03:05 +0000 (14:03 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Thu, 21 May 2026 05:59:32 +0000 (07:59 +0200)
parseRFC1035CharString will always reject ( ) and ;, the latter which is
quite often used. The new parseRFC1035CharStringRelaxed automaton behaves
similarly to parseRFC1035CharString, but allows these three characters when
they appear within quoted sections.

Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
pdns/dnslabeltext.rl
pdns/misc.hh
pdns/test-misc_hh.cc

index 3b02e594ca0e3fd3fae84281d9c92a680482d021..2fa19c4a0acdd55f5227f3755d1170943c7641ce 100644 (file)
@@ -241,6 +241,76 @@ size_t parseRFC1035CharString(std::string_view in, std::string &val) {
   return counter;
 }
 
+// Similar to above, but allows ( ) ; within quoted parts.
+size_t parseRFC1035CharStringRelaxed(std::string_view in, std::string &val) {
+
+  val.clear();
+  val.reserve(in.size());
+  const char *p = in.data();
+  const char *pe = p + in.size();
+  int cs = 0;
+  uint8_t escaped_octet = 0;
+  // Keeps track of how many chars we read from the source string
+  size_t counter=0;
+
+/* This parses an RFC 1035 char-string, but allows ( ) and ; to occur in
+ * quoted parts.
+ */
+%%{
+  machine dns_text_to_string_r;
+
+  action doEscapedNumber {
+    escaped_octet *= 10;
+    escaped_octet += fc-'0';
+    counter++;
+  }
+
+  action doneEscapedNumber {
+    val += escaped_octet;
+    escaped_octet = 0;
+  }
+
+  action addToVal {
+    val += fc;
+    counter++;
+  }
+
+  action incrementCounter {
+    counter++;
+  }
+
+  # generated rules, define required actions
+  DIGIT = 0x30..0x39;
+  DQUOTE = "\"";
+  HTAB = "\t";
+  SP = " ";
+  WSP = (SP | HTAB)@addToVal;
+  non_special = "!" | 0x23..0x27 | 0x2a..0x3a | 0x3c..0x5b | 0x5d..0x7e;
+  special = 0x28..0x29 | 0x3b;
+  non_digit = 0x21..0x2f | 0x3a..0x7e;
+  dec_octet = ( ( "0" | "1" ) DIGIT{2} ) | ( "2" ( ( 0x30..0x34 DIGIT ) | ( "5" 0x30..0x35 ) ) );
+  escaped = '\\'@incrementCounter ( non_digit$addToVal | dec_octet$doEscapedNumber@doneEscapedNumber );
+  contiguous = ( non_special$addToVal | escaped )+;
+  # rules differ from parseRFC1035CharString starting from here
+  quotedcontiguous = ( non_special$addToVal | special$addToVal | escaped )+;
+  quoted = DQUOTE@incrementCounter ( quotedcontiguous | ( '\\'? WSP ) )* DQUOTE@incrementCounter;
+  char_string = (contiguous | quoted);
+
+  # instantiate machine rules
+  main := char_string;
+  write data;
+  write init;
+}%%
+
+  // silence warnings
+  (void) dns_text_to_string_r_first_final;
+  (void) dns_text_to_string_r_error;
+  (void) dns_text_to_string_r_en_main;
+  %% write exec;
+
+  return counter;
+}
+
 size_t parseSVCBValueListFromParsedRFC1035CharString(const std::string &in, std::vector<std::string> &val) {
   val.clear();
   const char *p = in.c_str();
index 28508b9f2139fe048cc6695a509362d3b4ef2fcd..11a58c6cf57b9778c4cd9aa4cb25e4bf8e15d18f 100644 (file)
@@ -835,10 +835,12 @@ std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath);
 
 DNSName reverseNameFromIP(const ComboAddress& ip);
 
-// The following two routines are generated from Ragel code.
+// The following three routines are generated from Ragel code.
 // Note that parseRFC1035CharString will return zero if the first character
 // being processed is < 0x20, >= 07f, or equal to 0x28, 0x29 or 0x3b.
+// parseRFC1035CharStringRelaxed will too, except within a quoted section.
 size_t parseRFC1035CharString(std::string_view in, std::string &val);
+size_t parseRFC1035CharStringRelaxed(std::string_view in, std::string &val);
 size_t parseSVCBValueListFromParsedRFC1035CharString(const std::string &in, vector<std::string> &val);
 size_t parseSVCBValueList(const std::string &in, vector<std::string> &val);
 
index 5b39f4a3868afafe9d66243c289e964277600bb8..40f48cd1924637ee8fa249877637356f4ffc1ad8 100644 (file)
@@ -297,6 +297,96 @@ BOOST_AUTO_TEST_CASE(test_parseRFC1035CharString)
   BOOST_CHECK_EQUAL(out, expected);
 }
 
+BOOST_AUTO_TEST_CASE(test_parseRFC1035CharStringRelaxed)
+{
+  string in;
+  string out;
+  string expected;
+  size_t amount;
+
+  // Same tests as for parseRFC1035CharString
+
+  in = "foobar123";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, "foobar123");
+
+  in = "foobar123\\,bazquux456";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, "foobar123,bazquux456");
+
+  in = string("\"")+string(16262, 'A')+string("\"");
+  expected = string(16262, 'A');
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = "hello\\044world\\002";
+  expected = "hello,world\x02";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = "\"hello\\044world\"";
+  expected = "hello,world";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  // Here we'll only read until the space
+  in = "hello world";
+  expected = "hello";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, 5U);
+  BOOST_CHECK_EQUAL(out, expected);
+
+  // \032 is a space, but it is read because it is escaped
+  in = "hello\\032world";
+  expected = "hello world";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = "\"hello\\032world\"";
+  expected = "hello world";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = "\"hello\\032world XXXX\"";
+  expected = "hello world XXXX";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  // From draft-ietf-dnsop-svcb-https-03
+  expected = R"FOO(part1,part2,part3\,part4\\)FOO";
+  in = R"FOO("part1,part2,part3\\,part4\\\\)FOO";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = R"FOO(part1\,\p\a\r\t2\044part3\092,part4\092\\)FOO";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  // Specific checks for ( ) ;
+
+  in = "\"();etc\"";
+  expected = "();etc";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, in.size());
+  BOOST_CHECK_EQUAL(out, expected);
+
+  in = ";login";
+  expected = "";
+  amount = parseRFC1035CharStringRelaxed(in, out);
+  BOOST_CHECK_EQUAL(amount, 0);
+  BOOST_CHECK_EQUAL(out, expected);
+}
+
 BOOST_AUTO_TEST_CASE(test_parseSVCBValueList)
 {
   vector<string> out;