/src/pdns/pdns/rcpgenerator.cc
Line | Count | Source |
1 | | /* |
2 | | * This file is part of PowerDNS or dnsdist. |
3 | | * Copyright -- PowerDNS.COM B.V. and its contributors |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of version 2 of the GNU General Public License as |
7 | | * published by the Free Software Foundation. |
8 | | * |
9 | | * In addition, for the avoidance of any doubt, permission is granted to |
10 | | * link this program with OpenSSL and to (re)distribute the binaries |
11 | | * produced as the result of such linking. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | #ifdef HAVE_CONFIG_H |
23 | | #include "config.h" |
24 | | #endif |
25 | | #include "rcpgenerator.hh" |
26 | | #include "dnsparser.hh" |
27 | | #include "misc.hh" |
28 | | #include "utility.hh" |
29 | | #include <boost/algorithm/string.hpp> |
30 | | #include <boost/algorithm/string/classification.hpp> |
31 | | #include <boost/algorithm/string/replace.hpp> |
32 | | #include <boost/format.hpp> |
33 | | |
34 | | #include <iostream> |
35 | | #include "base32.hh" |
36 | | #include "base64.hh" |
37 | | #include "namespaces.hh" |
38 | | |
39 | | RecordTextReader::RecordTextReader(string str, ZoneName zone) : |
40 | 7.47k | d_string(std::move(str)), d_zone(std::move(zone)) |
41 | 7.47k | { |
42 | | /* remove whitespace */ |
43 | 7.47k | if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) )) |
44 | 370 | boost::trim_if(d_string, dns_isspace); |
45 | 7.47k | d_end = d_string.size(); |
46 | 7.47k | } |
47 | | |
48 | | void RecordTextReader::xfr48BitInt(uint64_t &val) |
49 | 0 | { |
50 | 0 | auto oldpos = d_pos; |
51 | 0 | xfr64BitInt(val); |
52 | 0 | if (val >= (1ULL << 48)) { |
53 | 0 | throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 48-bit integer"); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | 138 | void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID& val) { |
58 | 138 | skipSpaces(); |
59 | 138 | size_t len; |
60 | 138 | for(len=0; |
61 | 308k | d_pos+len < d_string.length() && (isxdigit(static_cast<unsigned char>(d_string.at(d_pos+len))) != 0 || d_string.at(d_pos+len) == ':'); |
62 | 308k | len++) ; // find length of ID |
63 | | |
64 | | // Parse as v6, and then strip the final 64 zero bytes |
65 | 138 | struct in6_addr tmpbuf; |
66 | 138 | string to_parse = d_string.substr(d_pos, len) + ":0:0:0:0"; |
67 | | |
68 | 138 | if (inet_pton(AF_INET6, to_parse.c_str(), &tmpbuf) != 1) { |
69 | 121 | throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string.substr(d_pos, len) + "' is invalid"); |
70 | 121 | } |
71 | | |
72 | 17 | std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content)); |
73 | 17 | d_pos += len; |
74 | 17 | } |
75 | | |
76 | | void RecordTextReader::xfr64BitInt(uint64_t &val) |
77 | 356 | { |
78 | 356 | skipSpaces(); |
79 | | |
80 | 356 | if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) { |
81 | 28 | throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
82 | 28 | } |
83 | 328 | size_t pos; |
84 | 328 | val=std::stoull(d_string.substr(d_pos), &pos); |
85 | | |
86 | 328 | d_pos += pos; |
87 | 328 | } |
88 | | |
89 | | |
90 | | void RecordTextReader::xfr32BitInt(uint32_t &val) |
91 | 6.53k | { |
92 | 6.53k | skipSpaces(); |
93 | | |
94 | 6.53k | if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) { |
95 | 583 | throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
96 | 583 | } |
97 | 5.95k | size_t pos; |
98 | 5.95k | val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos); |
99 | | |
100 | 5.95k | d_pos += pos; |
101 | 5.95k | } |
102 | | |
103 | | void RecordTextReader::xfrTime(uint32_t &val) |
104 | 356 | { |
105 | 356 | struct tm tm; |
106 | 356 | memset(&tm, 0, sizeof(tm)); |
107 | | |
108 | 356 | uint64_t itmp; |
109 | 356 | xfr64BitInt(itmp); |
110 | | |
111 | 356 | if (itmp <= (uint32_t)~0) { |
112 | | // formatted as seconds since epoch, not as YYYYMMDDHHmmSS: |
113 | 111 | val = (uint32_t) itmp; |
114 | 111 | return; |
115 | 111 | } |
116 | | |
117 | 245 | ostringstream tmp; |
118 | | |
119 | 245 | tmp<<itmp; |
120 | | |
121 | 245 | if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d", |
122 | 245 | &tm.tm_year, &tm.tm_mon, &tm.tm_mday, |
123 | 245 | &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { |
124 | 6 | throw RecordTextException("unable to parse '"+std::to_string(itmp)+"' into a valid time at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
125 | 6 | } |
126 | | |
127 | | // Note tm_mon is still in 1..12 range at this point |
128 | 239 | if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || tm.tm_min > 59 || |
129 | 90 | tm.tm_hour < 0 || tm.tm_hour > 23 || tm.tm_mday < 0 || tm.tm_mday > 31 || |
130 | 77 | tm.tm_mon < 1 || tm.tm_mon > 12) { |
131 | 77 | throw RecordTextException("invalid time specification '"+std::to_string(itmp)+"' at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
132 | 77 | } |
133 | | |
134 | 162 | tm.tm_year-=1900; |
135 | 162 | tm.tm_mon-=1; |
136 | | // coverity[store_truncates_time_t] |
137 | 162 | val=(uint32_t)Utility::timegm(&tm); |
138 | 162 | } |
139 | | |
140 | | void RecordTextReader::xfrIP(uint32_t &val) |
141 | 109 | { |
142 | 109 | skipSpaces(); |
143 | | |
144 | 109 | if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) == 0) { |
145 | 25 | throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
146 | 25 | } |
147 | 84 | uint32_t octet=0; |
148 | 84 | val=0; |
149 | 84 | char count=0; |
150 | 84 | bool last_was_digit = false; |
151 | | |
152 | 658 | for(;;) { |
153 | 658 | if(d_string.at(d_pos)=='.') { |
154 | 57 | if (!last_was_digit) |
155 | 2 | throw RecordTextException(string("unable to parse IP address, dot without previous digit")); |
156 | 55 | last_was_digit = false; |
157 | 55 | val<<=8; |
158 | 55 | val+=octet; |
159 | 55 | octet=0; |
160 | 55 | count++; |
161 | 55 | if(count > 3) |
162 | 1 | throw RecordTextException(string("unable to parse IP address, too many dots")); |
163 | 55 | } |
164 | 601 | else if (isdigit(static_cast<unsigned char>(d_string.at(d_pos))) != 0) { |
165 | 568 | last_was_digit = true; |
166 | 568 | octet*=10; |
167 | 568 | octet+=d_string.at(d_pos) - '0'; |
168 | 568 | if(octet > 255) |
169 | 7 | throw RecordTextException("unable to parse IP address"); |
170 | 568 | } |
171 | 33 | else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',') |
172 | 10 | break; |
173 | 23 | else { |
174 | 23 | throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos)); |
175 | 23 | } |
176 | 615 | d_pos++; |
177 | 615 | if(d_pos == d_string.length()) |
178 | 33 | break; |
179 | 615 | } |
180 | 51 | if (count != 3) |
181 | 29 | throw RecordTextException(string("unable to parse IP address, not enough dots")); |
182 | 22 | if (!last_was_digit) |
183 | 1 | throw RecordTextException(string("unable to parse IP address, trailing dot")); |
184 | 21 | val<<=8; |
185 | 21 | val+=octet; |
186 | 21 | val=ntohl(val); |
187 | 21 | } |
188 | | |
189 | | |
190 | | void RecordTextReader::xfrIP6(std::string &val) |
191 | 129 | { |
192 | 129 | struct in6_addr tmpbuf; |
193 | | |
194 | 129 | skipSpaces(); |
195 | | |
196 | 129 | size_t len; |
197 | | // lookup end of value - think of ::ffff encoding too, has dots in it! |
198 | 129 | for(len=0; |
199 | 271k | d_pos+len < d_string.length() && (isxdigit(static_cast<unsigned char>(d_string.at(d_pos+len))) != 0 || d_string.at(d_pos+len) == ':' || d_string.at(d_pos+len)=='.'); |
200 | 271k | len++); |
201 | | |
202 | 129 | if(!len) |
203 | 14 | throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'"); |
204 | | |
205 | | // end of value is here, try parse as IPv6 |
206 | 115 | string address=d_string.substr(d_pos, len); |
207 | | |
208 | 115 | if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) { |
209 | 100 | throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid"); |
210 | 100 | } |
211 | | |
212 | 15 | val = std::string((char*)tmpbuf.s6_addr, 16); |
213 | | |
214 | 15 | d_pos += len; |
215 | 15 | } |
216 | | |
217 | | void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val) |
218 | 0 | { |
219 | 0 | if (version == 4) { |
220 | 0 | uint32_t ip; |
221 | 0 | xfrIP(ip); |
222 | 0 | val = makeComboAddressFromRaw(4, string((const char*) &ip, 4)); |
223 | 0 | } |
224 | 0 | else if (version == 6) { |
225 | 0 | string ip; |
226 | 0 | xfrIP6(ip); |
227 | 0 | val = makeComboAddressFromRaw(6, ip); |
228 | 0 | } |
229 | 0 | else throw RecordTextException("invalid address family"); |
230 | 0 | } |
231 | | |
232 | | void RecordTextReader::xfrCAPort(ComboAddress &val) |
233 | 0 | { |
234 | 0 | uint16_t port; |
235 | 0 | xfr16BitInt(port); |
236 | 0 | val.sin4.sin_port = port; |
237 | 0 | } |
238 | | |
239 | | bool RecordTextReader::eof() const |
240 | 8.39k | { |
241 | 8.39k | return d_pos==d_end; |
242 | 8.39k | } |
243 | | |
244 | | void RecordTextReader::xfr16BitInt(uint16_t &val) |
245 | 4.13k | { |
246 | 4.13k | auto oldpos = d_pos; |
247 | 4.13k | uint32_t tmp{0}; |
248 | 4.13k | xfr32BitInt(tmp); |
249 | 4.13k | val=tmp; |
250 | 4.13k | if(val!=tmp) { |
251 | 32 | throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 16-bit integer"); |
252 | 32 | } |
253 | 4.13k | } |
254 | | |
255 | | void RecordTextReader::xfr8BitInt(uint8_t &val) |
256 | 1.93k | { |
257 | 1.93k | auto oldpos = d_pos; |
258 | 1.93k | uint32_t tmp{0}; |
259 | 1.93k | xfr32BitInt(tmp); |
260 | 1.93k | val=tmp; |
261 | 1.93k | if(val!=tmp) { |
262 | 46 | throw RecordTextException("Numerical value " + d_string.substr(oldpos, d_pos - oldpos) + " at position " + std::to_string(oldpos) + " is too large for a 8-bit integer"); |
263 | 46 | } |
264 | 1.93k | } |
265 | | |
266 | | // this code should leave all the escapes around |
267 | | void RecordTextReader::xfrName(DNSName& val, [[maybe_unused]] bool compress) |
268 | 4.48k | { |
269 | 4.48k | skipSpaces(); |
270 | 4.48k | DNSName sval; |
271 | | |
272 | 4.48k | string::size_type begin_pos = d_pos; |
273 | 2.05M | while (d_pos < d_end) { |
274 | 2.05M | if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) { |
275 | 3.24k | break; |
276 | 3.24k | } |
277 | | |
278 | 2.04M | d_pos++; |
279 | 2.04M | } |
280 | | |
281 | 4.48k | { |
282 | 4.48k | std::string_view view(d_string); |
283 | 4.48k | sval = DNSName(view.substr(begin_pos, d_pos - begin_pos)); |
284 | 4.48k | } |
285 | | |
286 | 4.48k | if (sval.empty()) { |
287 | 0 | sval = DNSName(d_zone); |
288 | 0 | } |
289 | 4.48k | else if (!d_zone.empty()) { |
290 | 3.47k | sval += DNSName(d_zone); |
291 | 3.47k | } |
292 | 4.48k | val = std::move(sval); |
293 | 4.48k | } |
294 | | |
295 | | static bool isbase64(char c, bool acceptspace) |
296 | 827k | { |
297 | 827k | if(dns_isspace(c)) |
298 | 6.58k | return acceptspace; |
299 | 821k | if(c >= '0' && c <= '9') |
300 | 520k | return true; |
301 | 300k | if(c >= 'a' && c <= 'z') |
302 | 138k | return true; |
303 | 162k | if(c >= 'A' && c <= 'Z') |
304 | 89.6k | return true; |
305 | 72.4k | if(c=='+' || c=='/' || c=='=') |
306 | 72.2k | return true; |
307 | 194 | return false; |
308 | 72.4k | } |
309 | | |
310 | | void RecordTextReader::xfrBlobNoSpaces(string& val, int len) |
311 | 218 | { |
312 | 218 | skipSpaces(); |
313 | 218 | auto pos = d_pos; |
314 | 218 | const char* strptr=d_string.c_str(); |
315 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) |
316 | 115k | while(d_pos < d_end && isbase64(strptr[d_pos], false)) { |
317 | 115k | d_pos++; |
318 | 115k | } |
319 | | |
320 | 218 | string tmp; |
321 | 218 | tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos); |
322 | 218 | boost::erase_all(tmp," "); |
323 | 218 | val.clear(); |
324 | 218 | B64Decode(tmp, val); |
325 | | |
326 | 218 | if (len>-1 && val.size() != static_cast<size_t>(len)) |
327 | 0 | throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len)); |
328 | 218 | } |
329 | | |
330 | | void RecordTextReader::xfrBlob(string& val, int len) |
331 | 355 | { |
332 | 355 | skipSpaces(); |
333 | 355 | auto pos = d_pos; |
334 | 355 | const char* strptr=d_string.c_str(); |
335 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) |
336 | 712k | while(d_pos < d_end && isbase64(strptr[d_pos], true)) { |
337 | 712k | d_pos++; |
338 | 712k | } |
339 | | |
340 | 355 | string tmp; |
341 | 355 | tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos); |
342 | 355 | boost::erase_all(tmp," "); |
343 | 355 | val.clear(); |
344 | 355 | B64Decode(tmp, val); |
345 | | |
346 | 355 | if (len>-1 && val.size() != static_cast<size_t>(len)) { |
347 | 0 | throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len)); |
348 | 0 | } |
349 | 355 | } |
350 | | |
351 | 3.69k | void RecordTextReader::xfrRFC1035CharString(string &val) { |
352 | 3.69k | auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val); |
353 | 3.69k | d_pos += ctr; |
354 | 3.69k | } |
355 | | |
356 | 1.13k | void RecordTextReader::xfrSVCBValueList(vector<string> &val) { |
357 | 1.13k | auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val); |
358 | 1.13k | d_pos += ctr; |
359 | 1.13k | } |
360 | | |
361 | | void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readability-function-cognitive-complexity) |
362 | 2.65k | { |
363 | 2.65k | set<SvcParam::SvcParamKey> seenKeys; |
364 | 7.13k | while (d_pos != d_end) { |
365 | 5.72k | skipSpaces(); |
366 | 5.72k | if (d_pos == d_end) |
367 | 0 | return; |
368 | | |
369 | | // Find the SvcParamKey |
370 | 5.72k | auto pos = d_pos; |
371 | 1.25M | while (d_pos != d_end) { |
372 | 1.25M | if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') { |
373 | 5.17k | break; |
374 | 5.17k | } |
375 | 1.25M | d_pos++; |
376 | 1.25M | } |
377 | | |
378 | | // We've reached a space or equals-sign or the end of the string (d_pos is at this char) |
379 | 5.72k | string k = d_string.substr(pos, d_pos - pos); |
380 | 5.72k | SvcParam::SvcParamKey key; |
381 | 5.72k | bool generic; |
382 | 5.72k | try { |
383 | 5.72k | key = SvcParam::keyFromString(k, generic); |
384 | 5.72k | } catch (const std::invalid_argument &e) { |
385 | 394 | throw RecordTextException(e.what()); |
386 | 394 | } |
387 | | |
388 | 5.33k | if (!seenKeys.insert(key).second) { |
389 | 13 | throw RecordTextException("SvcParamKey '" + k + "' appears more than once"); |
390 | 13 | } |
391 | | |
392 | 5.32k | if (d_pos != d_end && d_string.at(d_pos) == '=') { |
393 | 3.23k | d_pos++; // Now on the first character after '=' |
394 | 3.23k | if (d_pos == d_end || d_string.at(d_pos) == ' ') { |
395 | 3 | throw RecordTextException("expected value after " + k + "="); |
396 | 3 | } |
397 | 3.23k | } |
398 | | |
399 | 5.31k | switch (key) { |
400 | 157 | case SvcParam::no_default_alpn: |
401 | 266 | case SvcParam::ohttp: |
402 | 266 | if (d_pos != d_end && d_string.at(d_pos) != ' ') { |
403 | 30 | throw RecordTextException(k + " key can not have values"); |
404 | 30 | } |
405 | 236 | val.insert(SvcParam(key)); |
406 | 236 | break; |
407 | 275 | case SvcParam::ipv4hint: /* fall-through */ |
408 | 563 | case SvcParam::ipv6hint: { |
409 | 563 | vector<ComboAddress> hints; |
410 | 563 | bool doAuto{false}; |
411 | 563 | if (generic) { |
412 | 202 | string value; |
413 | 202 | xfrRFC1035CharString(value); |
414 | 202 | size_t len = key == SvcParam::ipv4hint ? 4 : 16; |
415 | 202 | if (value.empty()) { |
416 | 34 | throw RecordTextException("value is required for SVC Param " + k); |
417 | 34 | } |
418 | 168 | if (value.size() % len != 0) { |
419 | 38 | throw RecordTextException(k + " in generic format has wrong number of bytes"); |
420 | 38 | } |
421 | 120k | for (size_t i=0; i<value.size(); i += len) { |
422 | 120k | auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len); |
423 | 120k | hints.push_back(hint); |
424 | 120k | } |
425 | 361 | } else { |
426 | 361 | vector<string> value; |
427 | 361 | xfrSVCBValueList(value); |
428 | 250k | for (auto const &v: value) { |
429 | 250k | if (v == "auto") { |
430 | 26 | doAuto = true; |
431 | 26 | hints.clear(); |
432 | 26 | break; |
433 | 26 | } |
434 | 250k | hints.push_back(ComboAddress(v)); |
435 | 250k | } |
436 | 361 | } |
437 | 491 | if (!doAuto && hints.empty()) { |
438 | 5 | throw RecordTextException("value is required for SVC Param " + k); |
439 | 5 | } |
440 | 486 | try { |
441 | 486 | auto p = SvcParam(key, std::move(hints)); |
442 | 486 | p.setAutoHint(doAuto); |
443 | 486 | val.insert(std::move(p)); |
444 | 486 | } |
445 | 486 | catch (const std::invalid_argument& e) { |
446 | 30 | throw RecordTextException(e.what()); |
447 | 30 | } |
448 | 414 | break; |
449 | 486 | } |
450 | 533 | case SvcParam::alpn: { |
451 | 533 | vector<string> value; |
452 | 533 | if (generic) { |
453 | 160 | string v; |
454 | 160 | xfrRFC1035CharString(v); |
455 | 160 | size_t spos{0}, len; |
456 | 102k | while (spos < v.length()) { |
457 | 102k | len = v.at(spos); |
458 | 102k | spos += 1; |
459 | 102k | if (len == 0) { |
460 | 3 | throw RecordTextException("ALPN values cannot be empty strings"); |
461 | 3 | } |
462 | 102k | if (len > 255) { |
463 | 3 | throw RecordTextException("Length of ALPN value goes over 255"); |
464 | 3 | } |
465 | 102k | if (len > v.length() - spos) { |
466 | 38 | throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param"); |
467 | 38 | } |
468 | 102k | value.push_back(v.substr(spos, len)); |
469 | 102k | spos += len; |
470 | 102k | } |
471 | 373 | } else { |
472 | 373 | xfrSVCBValueList(value); |
473 | 193k | for (const auto& item : value) { |
474 | 193k | if (item.length() > 255) { |
475 | 1 | throw RecordTextException("Length of SVC value goes over 255"); |
476 | 1 | } |
477 | 193k | } |
478 | 373 | } |
479 | 488 | if (value.empty()) { |
480 | 11 | throw RecordTextException("value is required for SVC Param " + k); |
481 | 11 | } |
482 | 290k | for (const auto &alpn_value : value) { |
483 | 290k | if (alpn_value.empty()) { |
484 | 0 | throw RecordTextException("ALPN values cannot be empty strings"); |
485 | 0 | } |
486 | 290k | } |
487 | 477 | val.insert(SvcParam(key, std::move(value))); |
488 | 477 | break; |
489 | 477 | } |
490 | 732 | case SvcParam::mandatory: { |
491 | 732 | if (generic) { |
492 | 329 | string v; |
493 | 329 | xfrRFC1035CharString(v); |
494 | 329 | if (v.empty()) { |
495 | 50 | throw RecordTextException("value is required for SVC Param " + k); |
496 | 50 | } |
497 | 279 | if (v.length() % 2 != 0) { |
498 | 35 | throw RecordTextException("Wrong number of bytes in SVC Param " + k); |
499 | 35 | } |
500 | 244 | std::set<SvcParam::SvcParamKey> keys; |
501 | 15.5k | for (size_t i=0; i < v.length(); i += 2) { |
502 | 15.3k | uint16_t mand = (static_cast<uint8_t>(v.at(i)) << 8); |
503 | 15.3k | mand += static_cast<uint8_t>(v.at(i+1)); |
504 | 15.3k | keys.insert(SvcParam::SvcParamKey(mand)); |
505 | 15.3k | } |
506 | 244 | val.insert(SvcParam(key, std::move(keys))); |
507 | 244 | break; |
508 | 279 | } |
509 | 403 | vector<string> parts; |
510 | 403 | xfrSVCBValueList(parts); |
511 | 403 | if (parts.empty()) { |
512 | 3 | throw RecordTextException("value is required for SVC Param " + k); |
513 | 3 | } |
514 | 400 | set<string> values(parts.begin(), parts.end()); |
515 | 400 | val.insert(SvcParam(key, std::move(values))); |
516 | 400 | break; |
517 | 403 | } |
518 | 130 | case SvcParam::port: { |
519 | 130 | uint16_t port; |
520 | 130 | if (generic) { |
521 | 109 | string v; |
522 | 109 | xfrRFC1035CharString(v); |
523 | 109 | if (v.empty()) { |
524 | 7 | throw RecordTextException("value is required for SVC Param " + k); |
525 | 7 | } |
526 | 102 | if (v.length() != 2) { |
527 | 22 | throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length())); |
528 | 22 | } |
529 | 80 | port = static_cast<uint8_t>(v.at(0)) << 8; |
530 | 80 | port += static_cast<uint8_t>(v.at(1)); |
531 | 80 | } else { |
532 | 21 | string portstring; |
533 | 21 | xfrRFC1035CharString(portstring); |
534 | 21 | if (portstring.empty()) { |
535 | 2 | throw RecordTextException("value is required for SVC Param " + k); |
536 | 2 | } |
537 | 19 | try { |
538 | 19 | pdns::checked_stoi_into(port, portstring); |
539 | 19 | } catch (const std::exception &e) { |
540 | 4 | throw RecordTextException(e.what()); |
541 | 4 | } |
542 | 19 | } |
543 | 95 | val.insert(SvcParam(key, port)); |
544 | 95 | break; |
545 | 130 | } |
546 | 307 | case SvcParam::ech: { |
547 | 307 | string value; |
548 | 307 | if (generic) { |
549 | 83 | xfrRFC1035CharString(value); |
550 | 224 | } else { |
551 | 224 | bool haveQuote = d_string.at(d_pos) == '"'; |
552 | 224 | if (haveQuote) { |
553 | 8 | d_pos++; |
554 | 8 | } |
555 | 224 | xfrBlobNoSpaces(value); |
556 | 224 | if (haveQuote) { |
557 | 7 | if (d_string.at(d_pos) != '"') { |
558 | 6 | throw RecordTextException("ech value starts, but does not end with a '\"' symbol"); |
559 | 6 | } |
560 | 1 | d_pos++; |
561 | 1 | } |
562 | 224 | } |
563 | 301 | if (value.empty()) { |
564 | 28 | throw RecordTextException("value is required for SVC Param " + k); |
565 | 28 | } |
566 | 273 | val.insert(SvcParam(key, value)); |
567 | 273 | break; |
568 | 301 | } |
569 | 194 | case SvcParam::tls_supported_groups: { |
570 | 194 | string string_value; |
571 | 194 | xfrRFC1035CharString(string_value); |
572 | 194 | if (string_value.empty()) { |
573 | 6 | throw RecordTextException("Value is required for SVC Param " + k); |
574 | 6 | } |
575 | | |
576 | 188 | vector<string> parts; |
577 | 188 | stringtok(parts, string_value, ","); |
578 | | |
579 | 188 | vector<uint16_t> values; |
580 | 188 | values.reserve(parts.size()); |
581 | 119k | for (const auto& part : parts) { |
582 | 119k | uint16_t int_part{0}; |
583 | 119k | try { |
584 | 119k | pdns::checked_stoi_into(int_part, part); |
585 | 119k | } catch (const std::invalid_argument&) { |
586 | 64 | throw RecordTextException("Value in invalid format for SVC Param " + k); |
587 | 64 | } |
588 | 119k | values.emplace_back(int_part); |
589 | 119k | } |
590 | | |
591 | 114 | val.insert(SvcParam(key, std::move(values))); |
592 | 114 | break; |
593 | 188 | } |
594 | 61 | case SvcParam::dohpath: { |
595 | 61 | string value; |
596 | 61 | xfrRFC1035CharString(value); |
597 | 61 | if (value.empty()) { |
598 | 3 | throw RecordTextException("Value is required for SVC Param " + k); |
599 | 3 | } |
600 | 58 | val.insert(SvcParam(key, value)); |
601 | 58 | break; |
602 | 61 | } |
603 | 2.53k | default: { |
604 | 2.53k | string value; |
605 | 2.53k | xfrRFC1035CharString(value); |
606 | 2.53k | if (!generic && value.empty()) { |
607 | | // for generic format, we do not know. |
608 | | // Known keys which forbid having a value need to implement a switch case, above. |
609 | 0 | throw RecordTextException("value is required for SVC Param " + k); |
610 | 0 | } |
611 | 2.53k | val.insert(SvcParam(key, value)); |
612 | 2.53k | break; |
613 | 2.53k | } |
614 | 5.31k | } |
615 | 5.31k | } |
616 | 2.65k | } |
617 | | |
618 | | static inline uint8_t hextodec(uint8_t val) |
619 | 188k | { |
620 | 188k | if(val >= '0' && val<='9') |
621 | 168k | return val-'0'; |
622 | 20.1k | else if(val >= 'A' && val<='F') |
623 | 2.75k | return 10+(val-'A'); |
624 | 17.4k | else if(val >= 'a' && val<='f') |
625 | 17.3k | return 10+(val-'a'); |
626 | 39 | else |
627 | 39 | throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'"); |
628 | 188k | } |
629 | | |
630 | | |
631 | | static void HEXDecode(std::string_view chunk, string& out) |
632 | 428 | { |
633 | 428 | out.clear(); |
634 | 428 | if (chunk.length() == 1 && chunk[0] == '-') { |
635 | 3 | return; |
636 | 3 | } |
637 | 425 | out.reserve(chunk.length() / 2); |
638 | 425 | bool lowdigit{false}; |
639 | 425 | uint8_t val{0}; |
640 | 881k | for (auto chr : chunk) { |
641 | 881k | if(isalnum(static_cast<unsigned char>(chr)) == 0) { |
642 | 692k | continue; |
643 | 692k | } |
644 | 188k | if (!lowdigit) { |
645 | 94.3k | val = 16*hextodec(chr); |
646 | 94.3k | lowdigit = true; |
647 | 94.3k | } else { |
648 | 94.2k | val += hextodec(chr); |
649 | 94.2k | out.append(1, (char) val); |
650 | 94.2k | lowdigit = false; |
651 | 94.2k | val = 0; |
652 | 94.2k | } |
653 | 188k | } |
654 | 425 | if (lowdigit) { |
655 | 50 | throw RecordTextException("Hexadecimal blob '" + std::string(chunk) + "' contains an odd number of hex digits"); |
656 | 50 | } |
657 | 425 | } |
658 | | |
659 | | void RecordTextReader::xfrHexBlob(string& val, bool keepReading) |
660 | 434 | { |
661 | 434 | skipSpaces(); |
662 | 434 | auto pos = d_pos; |
663 | 1.02M | while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos]))) { |
664 | 1.02M | d_pos++; |
665 | 1.02M | } |
666 | | |
667 | 434 | HEXDecode(std::string_view(d_string).substr(pos, d_pos - pos), val); |
668 | 434 | } |
669 | | |
670 | | void RecordTextReader::xfrBase32HexBlob(string& val) |
671 | 229 | { |
672 | 229 | skipSpaces(); |
673 | 229 | auto pos = d_pos; |
674 | 610k | while(d_pos < d_end && !dns_isspace(d_string[d_pos])) { |
675 | 610k | d_pos++; |
676 | 610k | } |
677 | | |
678 | 229 | val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos)); |
679 | 229 | } |
680 | | |
681 | | |
682 | | void RecordTextWriter::xfrBase32HexBlob(const string& val) |
683 | 424 | { |
684 | 424 | if(!d_string.empty()) |
685 | 424 | d_string.append(1,' '); |
686 | | |
687 | 424 | d_string.append(toUpper(toBase32Hex(val))); |
688 | 424 | } |
689 | | |
690 | | |
691 | | void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */) |
692 | 1.12k | { |
693 | 1.12k | val.clear(); |
694 | 1.12k | val.reserve(d_end - d_pos); |
695 | | |
696 | 295k | while (d_pos != d_end) { |
697 | 295k | if (!val.empty()) { |
698 | 294k | val.append(1, ' '); |
699 | 294k | } |
700 | | |
701 | 295k | skipSpaces(); |
702 | 295k | char delimiter{'"'}; |
703 | 295k | bool quoted = d_string[d_pos] == '"'; |
704 | | // If the word is quoted, process up to the next quote; otherwise, |
705 | | // process up to the next whitespace (but output it in quotes). |
706 | 295k | val.append(1, '"'); |
707 | 295k | if (quoted) { |
708 | 196k | ++d_pos; |
709 | 196k | } |
710 | 98.6k | else { |
711 | | // RFC1035: ``a contiguous set of characters without interior spaces'' |
712 | 98.6k | delimiter = ' '; |
713 | 98.6k | } |
714 | 2.16M | while (d_pos != d_end && d_string[d_pos] != delimiter) { |
715 | 1.95M | if (d_string[d_pos] == '\\' && d_pos + 1 != d_end) { |
716 | 21.1k | val.append(1, d_string[d_pos++]); // copy escape slash |
717 | 21.1k | char chr = d_string[d_pos]; |
718 | 21.1k | if (chr >= '0' && chr <= '9') { |
719 | 2.07k | bool valid{false}; |
720 | | // Must be a three-digit character escape sequence |
721 | 2.07k | if (d_end - d_pos >= 3) { |
722 | 2.07k | char chr2 = d_string[d_pos + 1]; |
723 | 2.07k | char chr3 = d_string[d_pos + 2]; |
724 | 2.07k | if (chr2 >= '0' && chr2 <= '9' && chr3 >= '0' && chr3 <= '9') { |
725 | 2.04k | valid = 100 * (chr - '0') + 10 * (chr2 - '0') + chr3 - '0' < 256; |
726 | 2.04k | } |
727 | 2.07k | } |
728 | 2.07k | if (!valid) { |
729 | 37 | throw RecordTextException("Data field in DNS contains an invalid escape at position "+std::to_string(d_pos)+" of '"+d_string+"'"); |
730 | 37 | } |
731 | 2.07k | } |
732 | | // Not advancing d_pos, we'll append the next 1 or 3 characters as |
733 | | // part of the regular case. |
734 | 21.1k | } |
735 | 1.95M | if (!quoted && d_string[d_pos] == '"') { |
736 | | // Bind allows a non-quoted text to be immediately followed by a |
737 | | // quoted text, without any whitespace in between, so handle this |
738 | | // as a delimiter. |
739 | 90.8k | break; |
740 | 90.8k | } |
741 | 1.86M | val.append(1, d_string[d_pos]); |
742 | 1.86M | ++d_pos; |
743 | 1.86M | } |
744 | 295k | val.append(1,'"'); |
745 | 295k | if (quoted) { |
746 | | // If we reached the end in a quoted section, the closing quote is missing. |
747 | 196k | if (d_pos == d_end) { |
748 | 56 | throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'"); |
749 | 56 | } |
750 | | // Skip closing quote |
751 | 196k | ++d_pos; |
752 | 196k | } |
753 | 295k | if (!multi) { |
754 | 367 | break; |
755 | 367 | } |
756 | 295k | } |
757 | 1.12k | } |
758 | | |
759 | | void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */) |
760 | 121 | { |
761 | 121 | val.clear(); |
762 | 121 | val.reserve(d_end - d_pos); |
763 | | |
764 | 121 | if(!val.empty()) |
765 | 0 | val.append(1, ' '); |
766 | | |
767 | 121 | skipSpaces(); |
768 | 121 | val.append(1, d_string[d_pos]); |
769 | 228k | while(++d_pos < d_end && d_string[d_pos] != ' '){ |
770 | 228k | val.append(1, d_string[d_pos]); |
771 | 228k | } |
772 | 121 | } |
773 | | |
774 | | void RecordTextReader::xfrType(uint16_t& val) |
775 | 5.21k | { |
776 | 5.21k | skipSpaces(); |
777 | 5.21k | auto pos = d_pos; |
778 | 656k | while(d_pos < d_end && !dns_isspace(d_string[d_pos])) { |
779 | 650k | d_pos++; |
780 | 650k | } |
781 | | |
782 | 5.21k | string tmp; |
783 | 5.21k | tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos); |
784 | | |
785 | 5.21k | val=DNSRecordContent::TypeToNumber(tmp); |
786 | 5.21k | } |
787 | | |
788 | | |
789 | | void RecordTextReader::skipSpaces() |
790 | 319k | { |
791 | 319k | const char* strptr = d_string.c_str(); |
792 | 341k | while(d_pos < d_end && dns_isspace(strptr[d_pos])) |
793 | 22.1k | d_pos++; |
794 | 319k | if(d_pos == d_end) |
795 | 380 | throw RecordTextException("missing field at the end of record content '"+d_string+"'"); |
796 | 319k | } |
797 | | |
798 | | |
799 | 8.30k | RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str) |
800 | 8.30k | { |
801 | 8.30k | d_string.clear(); |
802 | 8.30k | d_nodot=noDot; |
803 | 8.30k | } |
804 | | |
805 | | void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val) |
806 | 28 | { |
807 | 28 | if(!d_string.empty()) { |
808 | 28 | d_string.append(1,' '); |
809 | 28 | } |
810 | | |
811 | 28 | size_t ctr = 0; |
812 | 28 | char tmp[5]; |
813 | 224 | for (auto const &c : val.content) { |
814 | 224 | snprintf(tmp, sizeof(tmp), "%02X", c); |
815 | 224 | d_string+=tmp; |
816 | 224 | ctr++; |
817 | 224 | if (ctr % 2 == 0 && ctr != 8) { |
818 | 84 | d_string+=':'; |
819 | 84 | } |
820 | 224 | } |
821 | 28 | } |
822 | | |
823 | | void RecordTextWriter::xfr48BitInt(const uint64_t& val) |
824 | 0 | { |
825 | 0 | if(!d_string.empty()) |
826 | 0 | d_string.append(1,' '); |
827 | 0 | d_string+=std::to_string(val); |
828 | 0 | } |
829 | | |
830 | | |
831 | | void RecordTextWriter::xfr32BitInt(const uint32_t& val) |
832 | 7.82k | { |
833 | 7.82k | if(!d_string.empty()) |
834 | 2.79k | d_string.append(1,' '); |
835 | 7.82k | d_string+=std::to_string(val); |
836 | 7.82k | } |
837 | | |
838 | | void RecordTextWriter::xfrType(const uint16_t& val) |
839 | 75 | { |
840 | 75 | if(!d_string.empty()) |
841 | 0 | d_string.append(1,' '); |
842 | 75 | d_string+=DNSRecordContent::NumberToType(val); |
843 | 75 | } |
844 | | |
845 | | // this function is on the fast path for the pdns_recursor |
846 | | void RecordTextWriter::xfrIP(const uint32_t& val) |
847 | 41 | { |
848 | 41 | if(!d_string.empty()) |
849 | 10 | d_string.append(1,' '); |
850 | | |
851 | 41 | char tmp[17]; |
852 | 41 | uint32_t ip=val; |
853 | 41 | uint8_t vals[4]; |
854 | | |
855 | 41 | memcpy(&vals[0], &ip, sizeof(ip)); |
856 | | |
857 | 41 | char *pos=tmp; |
858 | | |
859 | 205 | for(int n=0; n < 4; ++n) { |
860 | 164 | if(vals[n]<10) { |
861 | 74 | *(pos++)=vals[n]+'0'; |
862 | 90 | } else if(vals[n] < 100) { |
863 | 51 | *(pos++)=(vals[n]/10) +'0'; |
864 | 51 | *(pos++)=(vals[n]%10) +'0'; |
865 | 51 | } else { |
866 | 39 | *(pos++)=(vals[n]/100) +'0'; |
867 | 39 | vals[n]%=100; |
868 | 39 | *(pos++)=(vals[n]/10) +'0'; |
869 | 39 | *(pos++)=(vals[n]%10) +'0'; |
870 | 39 | } |
871 | 164 | if(n!=3) |
872 | 123 | *(pos++)='.'; |
873 | 164 | } |
874 | 41 | *pos=0; |
875 | 41 | d_string.append(tmp, pos); |
876 | 41 | } |
877 | | |
878 | | void RecordTextWriter::xfrIP6(const std::string& val) |
879 | 33 | { |
880 | 33 | char tmpbuf[16]; |
881 | 33 | char addrbuf[40]; |
882 | | |
883 | 33 | if(!d_string.empty()) |
884 | 7 | d_string.append(1,' '); |
885 | | |
886 | 33 | val.copy(tmpbuf,16); |
887 | | |
888 | 33 | if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr) |
889 | 0 | throw RecordTextException("Unable to convert to ipv6 address"); |
890 | | |
891 | 33 | d_string += std::string(addrbuf); |
892 | 33 | } |
893 | | |
894 | | void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val) |
895 | 0 | { |
896 | 0 | string ip = val.toString(); |
897 | |
|
898 | 0 | if(!d_string.empty()) |
899 | 0 | d_string.append(1,' '); |
900 | |
|
901 | 0 | d_string += ip; |
902 | 0 | } |
903 | | |
904 | | void RecordTextWriter::xfrCAPort(ComboAddress &val) |
905 | 0 | { |
906 | 0 | xfr16BitInt(val.sin4.sin_port); |
907 | 0 | } |
908 | | |
909 | | void RecordTextWriter::xfrTime(const uint32_t& val) |
910 | 150 | { |
911 | 150 | if(!d_string.empty()) |
912 | 150 | d_string.append(1,' '); |
913 | | |
914 | 150 | struct tm tm; |
915 | 150 | time_t time=val; // Y2038 bug! |
916 | 150 | gmtime_r(&time, &tm); |
917 | | |
918 | 150 | static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d"); |
919 | 150 | d_string += boost::str(boost::format(fmt) % (tm.tm_year+1900) % (tm.tm_mon+1) % tm.tm_mday % tm.tm_hour % tm.tm_min % tm.tm_sec); |
920 | 150 | } |
921 | | |
922 | | |
923 | | void RecordTextWriter::xfr16BitInt(const uint16_t& val) |
924 | 4.70k | { |
925 | 4.70k | xfr32BitInt(val); |
926 | 4.70k | } |
927 | | |
928 | | void RecordTextWriter::xfr8BitInt(const uint8_t& val) |
929 | 2.69k | { |
930 | 2.69k | xfr32BitInt(val); |
931 | 2.69k | } |
932 | | |
933 | | // should not mess with the escapes |
934 | | void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */) |
935 | 5.41k | { |
936 | 5.41k | if(!d_string.empty()) |
937 | 3.66k | d_string.append(1,' '); |
938 | | |
939 | 5.41k | if(d_nodot) { |
940 | 0 | d_string+=val.toStringRootDot(); |
941 | 0 | } |
942 | 5.41k | else |
943 | 5.41k | { |
944 | 5.41k | d_string+=val.toString(); |
945 | 5.41k | } |
946 | 5.41k | } |
947 | | |
948 | | void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size) |
949 | 446 | { |
950 | 446 | xfrBlob(val, size); |
951 | 446 | } |
952 | | |
953 | | void RecordTextWriter::xfrBlob(const string& val, int) |
954 | 1.27k | { |
955 | 1.27k | if(!d_string.empty()) |
956 | 270 | d_string.append(1,' '); |
957 | | |
958 | 1.27k | d_string+=Base64Encode(val); |
959 | 1.27k | } |
960 | | |
961 | | void RecordTextWriter::xfrHexBlob(const string& val, bool) |
962 | 917 | { |
963 | 917 | if(!d_string.empty()) |
964 | 917 | d_string.append(1,' '); |
965 | | |
966 | 917 | if(val.empty()) { |
967 | 429 | d_string.append(1,'-'); |
968 | 429 | return; |
969 | 429 | } |
970 | | |
971 | 488 | string::size_type limit=val.size(); |
972 | 488 | char tmp[5]; |
973 | 671k | for(string::size_type n = 0; n < limit; ++n) { |
974 | 670k | snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]); |
975 | 670k | d_string+=tmp; |
976 | 670k | } |
977 | 488 | } |
978 | | |
979 | 857 | void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) { |
980 | 857 | bool shouldQuote{false}; |
981 | 857 | vector<string> escaped; |
982 | 857 | escaped.reserve(val.size()); |
983 | 534k | for (auto const &v : val) { |
984 | 534k | if (v.find_first_of(' ') != string::npos) { |
985 | 698 | shouldQuote = true; |
986 | 698 | } |
987 | 534k | string tmp = txtEscape(v); |
988 | 534k | string unescaped; |
989 | 534k | unescaped.reserve(tmp.size() + 4); |
990 | 10.8M | for (auto const &ch : tmp) { |
991 | 10.8M | if (ch == '\\') { |
992 | 2.17M | unescaped += R"F(\\)F"; |
993 | 2.17M | continue; |
994 | 2.17M | } |
995 | 8.71M | if (ch == ',') { |
996 | 18.8k | unescaped += R"F(\\,)F"; |
997 | 18.8k | continue; |
998 | 18.8k | } |
999 | 8.70M | unescaped += ch; |
1000 | 8.70M | } |
1001 | 534k | escaped.push_back(std::move(unescaped)); |
1002 | 534k | } |
1003 | 857 | if (shouldQuote) { |
1004 | 70 | d_string.append(1, '"'); |
1005 | 70 | } |
1006 | 857 | d_string.append(boost::join(escaped, ",")); |
1007 | 857 | if (shouldQuote) { |
1008 | 70 | d_string.append(1, '"'); |
1009 | 70 | } |
1010 | 857 | } |
1011 | | |
1012 | 3.08k | void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) { |
1013 | 7.36k | for (auto const ¶m : val) { |
1014 | 7.36k | if (!d_string.empty()) |
1015 | 7.36k | d_string.append(1, ' '); |
1016 | | |
1017 | 7.36k | d_string.append(SvcParam::keyToString(param.getKey())); |
1018 | 7.36k | if (param.getKey() != SvcParam::no_default_alpn && param.getKey() != SvcParam::ohttp) { |
1019 | 7.05k | d_string.append(1, '='); |
1020 | 7.05k | } |
1021 | | |
1022 | 7.36k | switch (param.getKey()) |
1023 | 7.36k | { |
1024 | 188 | case SvcParam::no_default_alpn: |
1025 | 188 | break; |
1026 | 384 | case SvcParam::ipv4hint: /* fall-through */ |
1027 | 772 | case SvcParam::ipv6hint: |
1028 | | // TODO use xfrCA and put commas in between? |
1029 | 772 | if (param.getAutoHint()) { |
1030 | 15 | d_string.append("auto"); |
1031 | 15 | break; |
1032 | 15 | } |
1033 | 757 | d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false)); |
1034 | 757 | break; |
1035 | 857 | case SvcParam::alpn: |
1036 | 857 | xfrSVCBValueList(param.getALPN()); |
1037 | 857 | break; |
1038 | 596 | case SvcParam::mandatory: |
1039 | 596 | { |
1040 | 596 | bool doComma = false; |
1041 | 24.4k | for (auto const &k: param.getMandatory()) { |
1042 | 24.4k | if (doComma) |
1043 | 23.8k | d_string.append(1, ','); |
1044 | 24.4k | d_string.append(SvcParam::keyToString(k)); |
1045 | 24.4k | doComma = true; |
1046 | 24.4k | } |
1047 | 596 | break; |
1048 | 772 | } |
1049 | 174 | case SvcParam::port: { |
1050 | 174 | auto str = d_string; |
1051 | 174 | d_string.clear(); |
1052 | 174 | xfr16BitInt(param.getPort()); |
1053 | 174 | d_string = str + d_string; |
1054 | 174 | break; |
1055 | 772 | } |
1056 | 446 | case SvcParam::ech: { |
1057 | 446 | auto str = d_string; |
1058 | 446 | d_string.clear(); |
1059 | 446 | xfrBlobNoSpaces(param.getECH()); |
1060 | 446 | d_string = str + '"' + d_string + '"'; |
1061 | 446 | break; |
1062 | 772 | } |
1063 | 128 | case SvcParam::ohttp: |
1064 | | // no value |
1065 | 128 | break; |
1066 | 203 | case SvcParam::tls_supported_groups: { |
1067 | 203 | auto str = d_string; |
1068 | 203 | d_string.clear(); |
1069 | 203 | bool first = true; |
1070 | 193k | for (auto const &group: param.getTLSSupportedGroups()) { |
1071 | 193k | if (!first) { |
1072 | 193k | str += ','; |
1073 | 193k | } |
1074 | 193k | str += std::to_string(group); |
1075 | 193k | first = false; |
1076 | 193k | } |
1077 | 203 | d_string = std::move(str); |
1078 | 203 | break; |
1079 | 772 | } |
1080 | 106 | case SvcParam::dohpath: |
1081 | 4.00k | default: |
1082 | 4.00k | auto str = d_string; |
1083 | 4.00k | d_string.clear(); |
1084 | 4.00k | xfrText(param.getValue(), false, false); |
1085 | 4.00k | d_string = str + '"' + txtEscape(d_string) + '"'; |
1086 | 4.00k | break; |
1087 | 7.36k | } |
1088 | 7.36k | } |
1089 | 3.08k | } |
1090 | | |
1091 | | void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */) |
1092 | 5.81k | { |
1093 | 5.81k | if(!d_string.empty()) |
1094 | 801 | d_string.append(1,' '); |
1095 | | |
1096 | 5.81k | if (val.empty()) { |
1097 | 3.18k | d_string.append(2, '"'); |
1098 | 3.18k | } |
1099 | 2.62k | else { |
1100 | 2.62k | d_string.append(val); |
1101 | 2.62k | } |
1102 | 5.81k | } |
1103 | | |
1104 | | void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */) |
1105 | 269 | { |
1106 | 269 | if(!d_string.empty()) |
1107 | 269 | d_string.append(1,' '); |
1108 | 269 | d_string.append(val); |
1109 | 269 | } |
1110 | | |
1111 | | #ifdef TESTING |
1112 | | |
1113 | | int main(int argc, char**argv) |
1114 | | try |
1115 | | { |
1116 | | RecordTextReader rtr(argv[1], argv[2]); |
1117 | | |
1118 | | unsigned int order, pref; |
1119 | | string flags, services, regexp, replacement; |
1120 | | string mx; |
1121 | | |
1122 | | rtr.xfrInt(order); |
1123 | | rtr.xfrInt(pref); |
1124 | | rtr.xfrText(flags); |
1125 | | rtr.xfrText(services); |
1126 | | rtr.xfrText(regexp); |
1127 | | rtr.xfrName(replacement); |
1128 | | |
1129 | | cout<<"order: "<<order<<", pref: "<<pref<<"\n"; |
1130 | | cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n"; |
1131 | | |
1132 | | string out; |
1133 | | RecordTextWriter rtw(out); |
1134 | | |
1135 | | rtw.xfrInt(order); |
1136 | | rtw.xfrInt(pref); |
1137 | | rtw.xfrText(flags); |
1138 | | rtw.xfrText(services); |
1139 | | rtw.xfrText(regexp); |
1140 | | rtw.xfrName(replacement); |
1141 | | |
1142 | | cout<<"Regenerated: '"<<out<<"'\n"; |
1143 | | |
1144 | | } |
1145 | | catch(std::exception& e) |
1146 | | { |
1147 | | cerr<<"Fatal: "<<e.what()<<endl; |
1148 | | } |
1149 | | |
1150 | | #endif |