#include "dnsname.hh"
#include "namespaces.hh"
#include "dnswriter.hh"
+#include "misc.hh"
namespace {
void appendSplit(vector<string>& ret, string& segment, char c)
return ret;
};
+// Reads an RFC 1035 character string from 'in', puts the resulting bytes in 'out'.
+// Returns the amount of bytes read from 'in'
+size_t parseRFC1035CharString(const std::string &in, std::string &val) {
+
+ val.clear();
+ val.reserve(in.size());
+ const char *p = in.c_str();
+ const char *pe = p + in.size() + 1;
+ 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.
+ * It was created from the ABNF in draft-ietf-dnsop-svcb-https-02 with
+ * https://github.com/zinid/abnfc and modified to put all the characters in the
+ * right place.
+ */
+%%{
+ machine dns_text_to_string;
+
+ 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;
+ 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 )+;
+ quoted = DQUOTE@incrementCounter ( contiguous | ( '\\'? WSP ) )* DQUOTE@incrementCounter;
+ char_string = (contiguous | quoted);
+
+ # instantiate machine rules
+ main := char_string;
+ write data;
+ write init;
+}%%
+
+ // silence warnings
+ (void) dns_text_to_string_first_final;
+ (void) dns_text_to_string_error;
+ (void) dns_text_to_string_en_main;
+ %% write exec;
+
+ return counter;
+}
+
#if 0
break;
default:
xfr16BitInt(param.getValue().size());
- xfrUnquotedText(param.getValue(), false);
+ xfrBlob(param.getValue());
break;
}
}
DNSName reverseNameFromIP(const ComboAddress& ip);
std::string getCarbonHostName();
+size_t parseRFC1035CharString(const std::string &in, std::string &val); // from ragel
B64Decode(tmp, val);
}
+void RecordTextReader::xfrRFC1035CharString(string &val) {
+ auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
+ d_pos += ctr;
+}
+
void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
{
while (d_pos != d_end) {
}
default: {
string value;
- if (d_string.at(d_pos) == '"') {
- xfrText(value);
- }
- else {
- xfrUnquotedText(value);
- }
+ xfrRFC1035CharString(value);
val.insert(SvcParam(key, value));
break;
}
}
}
+// FIXME copied from dnsparser.cc, see #6010 and #3503 if you want a proper solution
+static string txtEscape(const string &name)
+{
+ string ret;
+ char ebuf[5];
+
+ for(string::const_iterator i=name.begin();i!=name.end();++i) {
+ if((unsigned char) *i >= 127 || (unsigned char) *i < 32) {
+ snprintf(ebuf, sizeof(ebuf), "\\%03u", (unsigned char)*i);
+ ret += ebuf;
+ }
+ else if(*i=='"' || *i=='\\'){
+ ret += '\\';
+ ret += *i;
+ }
+ else
+ ret += *i;
+ }
+ return ret;
+}
+
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
for (auto const ¶m : val) {
if (!d_string.empty())
break;
}
default:
- d_string.append(param.getValue());
+ auto str = d_string;
+ d_string.clear();
+ xfrText(param.getValue(), false, false);
+ d_string = str + '"' + txtEscape(d_string) + '"';
break;
}
}
void xfrBlob(string& val, int len=-1);
void xfrSvcParamKeyVals(set<SvcParam>& val);
+ void xfrRFC1035CharString(string &val);
const string getRemaining() const {
return d_string.substr(d_pos);
}
+
bool eof();
private:
string d_string;
(CASE_L(QType::SVCB, "1 foo.powerdns.org. echconfig=aGVsbG8=", "1 foo.powerdns.org. echconfig=\"aGVsbG8=\"", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x05\x00\x05hello"))
(CASE_S(QType::SVCB, "1 foo.powerdns.org. ipv6hint=2001:db8::1,2001:db8::53:1", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x06\x00\x20\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01"))
+ (CASE_S(QType::SVCB, "1 foo.powerdns.org. key666=\"hello\"", "\0\x01\3foo\x08powerdns\x03org\x00\x02\x9a\x00\x005hello"))
+ (CASE_L(QType::SVCB, "1 foo.powerdns.org. key666=hello\\210qoo", "1 foo.powerdns.org. key666=\"hello\\210qoo\"", "\0\x01\3foo\x08powerdns\x03org\x00\x02\x9a\x00\x009hello\xd2qoo"))
+ (CASE_S(QType::SVCB, "1 foo.powerdns.org. key666=\"hello\\210qoo\"", "\0\x01\3foo\x08powerdns\x03org\x00\x02\x9a\x00\x009hello\xd2qoo"))
+ (CASE_S(QType::SVCB, "1 foo.powerdns.org. key666=\"hello\\210qoo bar\"", "\0\x01\3foo\x08powerdns\x03org\x00\x02\x9a\x00\x0dhello\xd2qoo bar"))
+
(CASE_S(QType::SVCB, "16 foo.powerdns.org. mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1", "\0\x10\3foo\x08powerdns\x03org\x00\x00\x00\x00\x02\x00\x01\x00\x01\x00\x06\x02h2\x02h3\x00\x04\x00\x04\xc0\x00\x02\x01"))
(CASE_L(QType::SVCB, "16 foo.powerdns.org. alpn=h2,h3 mandatory=alpn ipv4hint=192.0.2.1", "16 foo.powerdns.org. mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1", "\0\x10\3foo\x08powerdns\x03org\x00\x00\x00\x00\x02\x00\x01\x00\x01\x00\x06\x02h2\x02h3\x00\x04\x00\x04\xc0\x00\x02\x01"))
string target;
RecordTextWriter rtw(target);
rtw.xfrSvcParamKeyVals(v);
- BOOST_CHECK_EQUAL(target, source);
+ BOOST_CHECK_EQUAL(target, "key666=\"foobar\"");
v.clear();
RecordTextReader rtr2("key666=");
k = v.begin()->getKey();
BOOST_CHECK(k == SvcParam::keyFromString("key666"));
val = v.begin()->getValue();
- BOOST_CHECK_EQUAL(val, "\"blablabla\"");
+ BOOST_CHECK_EQUAL(val, "blablabla");
// Check the writer
target.clear();
RecordTextWriter rtw2(target);
rtw2.xfrSvcParamKeyVals(v);
BOOST_CHECK_EQUAL(target, source);
+
+ v.clear();
+ source = "key666=\"foo\\123 bar\"";
+ RecordTextReader rtr5(source);
+ rtr5.xfrSvcParamKeyVals(v);
+ BOOST_CHECK_EQUAL(v.size(), 1U);
+ k = v.begin()->getKey();
+ BOOST_CHECK(k == SvcParam::keyFromString("key666"));
+ val = v.begin()->getValue();
+ BOOST_CHECK_EQUAL(val, "foo{ bar");
+
+ // Check the writer
+ target.clear();
+ RecordTextWriter rtw3(target);
+ rtw3.xfrSvcParamKeyVals(v);
+ BOOST_CHECK_EQUAL("key666=\"foo{ bar\"", target);
+
+ v.clear();
+ RecordTextReader rtr6("key665= blabla");
+ BOOST_CHECK_THROW(rtr6.xfrSvcParamKeyVals(v), RecordTextException);
+
+ v.clear();
+ RecordTextReader rtr7("key665=bla bla");
+ BOOST_CHECK_THROW(rtr7.xfrSvcParamKeyVals(v), RecordTextException);
}
BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_multiple) {
string target;
RecordTextWriter rtw(target);
rtw.xfrSvcParamKeyVals(v);
- BOOST_CHECK_EQUAL(target, "mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1,192.0.2.2 echconfig=\"dG90YWxseSBib2d1cyBlY2hjb25maWcgdmFsdWU=\" ipv6hint=2001:db8::1 key666=foobar");
+ BOOST_CHECK_EQUAL(target, "mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1,192.0.2.2 echconfig=\"dG90YWxseSBib2d1cyBlY2hjb25maWcgdmFsdWU=\" ipv6hint=2001:db8::1 key666=\"foobar\"");
}
BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_echconfig) {