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