• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 17551617099

08 Sep 2025 12:59PM UTC coverage: 66.015% (+0.01%) from 66.004%
17551617099

Pull #16077

github

web-flow
Merge 9e72f6ac2 into 5e4ad748a
Pull Request #16077: Move to C++20

42208 of 92550 branches covered (45.61%)

Branch coverage included in aggregate %.

20 of 20 new or added lines in 5 files covered. (100.0%)

99 existing lines in 11 files now uncovered.

128368 of 165838 relevant lines covered (77.41%)

5592083.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.53
/pdns/rcpgenerator.cc
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
  d_string(std::move(str)), d_zone(std::move(zone))
41
{
2,021,816✔
42
   /* remove whitespace */
43
   if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
2,021,816✔
44
     boost::trim_if(d_string, dns_isspace);
7✔
45
   d_end = d_string.size();
2,021,816✔
46
}
2,021,816✔
47

48
void RecordTextReader::xfr48BitInt(uint64_t &val)
49
{
6✔
50
  xfr64BitInt(val);
6✔
51
  if (val > 281474976710655LL)
6!
52
    throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
×
53
}
6✔
54

55
void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID& val) {
12✔
56
  skipSpaces();
12✔
57
  size_t len;
12✔
58
  for(len=0;
12✔
59
      d_pos+len < d_string.length() && (isxdigit(d_string.at(d_pos+len)) || d_string.at(d_pos+len) == ':');
240!
60
      len++) ;   // find length of ID
228✔
61

62
  // Parse as v6, and then strip the final 64 zero bytes
63
  struct in6_addr tmpbuf;
12✔
64
  string to_parse = d_string.substr(d_pos, len) + ":0:0:0:0";
12✔
65

66
  if (inet_pton(AF_INET6, to_parse.c_str(), &tmpbuf) != 1) {
12!
67
    throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string.substr(d_pos, len) + "' is invalid");
×
68
  }
×
69

70
  std::memcpy(&val.content, tmpbuf.s6_addr, sizeof(val.content));
12✔
71
  d_pos += len;
12✔
72
}
12✔
73

74
void RecordTextReader::xfr64BitInt(uint64_t &val)
75
{
625,984✔
76
  skipSpaces();
625,984✔
77

78
  if(!isdigit(d_string.at(d_pos)))
625,984!
79
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
80

81
  size_t pos;
625,984✔
82
  val=std::stoull(d_string.substr(d_pos), &pos);
625,984✔
83

84
  d_pos += pos;
625,984✔
85
}
625,984✔
86

87

88
void RecordTextReader::xfr32BitInt(uint32_t &val)
89
{
2,360,923✔
90
  skipSpaces();
2,360,923✔
91

92
  if(!isdigit(d_string.at(d_pos)))
2,360,923!
93
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
94

95
  size_t pos;
2,360,923✔
96
  val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
2,360,923✔
97

98
  d_pos += pos;
2,360,923✔
99
}
2,360,923✔
100

101
void RecordTextReader::xfrTime(uint32_t &val)
102
{
625,978✔
103
  struct tm tm;
625,978✔
104
  memset(&tm, 0, sizeof(tm));
625,978✔
105

106
  uint64_t itmp;
625,978✔
107
  xfr64BitInt(itmp);
625,978✔
108

109
  if (itmp <= (uint32_t)~0) {
625,978✔
110
    // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
111
    val = (uint32_t) itmp;
6✔
112
    return;
6✔
113
  }
6✔
114

115
  ostringstream tmp;
625,972✔
116

117
  tmp<<itmp;
625,972✔
118

119
  if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
625,972!
120
             &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
625,972✔
121
             &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
625,972✔
122
    throw RecordTextException("unable to parse '"+std::to_string(itmp)+"' into a valid time at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
123
  }
×
124

125
  tm.tm_year-=1900;
625,972✔
126
  tm.tm_mon-=1;
625,972✔
127
  // coverity[store_truncates_time_t]
128
  val=(uint32_t)Utility::timegm(&tm);
625,972✔
129
}
625,972✔
130

131
void RecordTextReader::xfrIP(uint32_t &val)
132
{
1,291,872✔
133
  skipSpaces();
1,291,872✔
134

135
  if(!isdigit(d_string.at(d_pos)))
1,291,872✔
136
    throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
4✔
137

138
  uint32_t octet=0;
1,291,868✔
139
  val=0;
1,291,868✔
140
  char count=0;
1,291,868✔
141
  bool last_was_digit = false;
1,291,868✔
142

143
  for(;;) {
16,176,451✔
144
    if(d_string.at(d_pos)=='.') {
16,176,451✔
145
      if (!last_was_digit)
3,875,578✔
146
        throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
3✔
147
      last_was_digit = false;
3,875,575✔
148
      val<<=8;
3,875,575✔
149
      val+=octet;
3,875,575✔
150
      octet=0;
3,875,575✔
151
      count++;
3,875,575✔
152
      if(count > 3)
3,875,575✔
153
        throw RecordTextException(string("unable to parse IP address, too many dots"));
3✔
154
    }
3,875,575✔
155
    else if(isdigit(d_string.at(d_pos))) {
12,300,873✔
156
      last_was_digit = true;
12,300,866✔
157
      octet*=10;
12,300,866✔
158
      octet+=d_string.at(d_pos) - '0';
12,300,866✔
159
      if(octet > 255)
12,300,866✔
160
        throw RecordTextException("unable to parse IP address");
6✔
161
    }
12,300,866✔
162
    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
7!
163
      break;
6✔
164
    else {
1✔
165
      throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
1✔
166
    }
1✔
167
    d_pos++;
16,176,432✔
168
    if(d_pos == d_string.length())
16,176,432✔
169
      break;
1,291,850✔
170
  }
16,176,432✔
171
  if (count != 3)
1,291,855✔
172
    throw RecordTextException(string("unable to parse IP address, not enough dots"));
3✔
173
  if (!last_was_digit)
1,291,852✔
174
    throw RecordTextException(string("unable to parse IP address, trailing dot"));
3✔
175
  val<<=8;
1,291,849✔
176
  val+=octet;
1,291,849✔
177
  val=ntohl(val);
1,291,849✔
178
}
1,291,849✔
179

180

181
void RecordTextReader::xfrIP6(std::string &val)
182
{
6,286✔
183
  struct in6_addr tmpbuf;
6,286✔
184

185
  skipSpaces();
6,286✔
186

187
  size_t len;
6,286✔
188
  // lookup end of value - think of ::ffff encoding too, has dots in it!
189
  for(len=0;
6,286✔
190
      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)=='.');
106,400✔
191
    len++);
100,114✔
192

193
  if(!len)
6,286!
194
    throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
195

196
  // end of value is here, try parse as IPv6
197
  string address=d_string.substr(d_pos, len);
6,286✔
198

199
  if (inet_pton(AF_INET6, address.c_str(), &tmpbuf) != 1) {
6,286✔
200
    throw RecordTextException("while parsing IPv6 address: '" + address + "' is invalid");
9✔
201
  }
9✔
202

203
  val = std::string((char*)tmpbuf.s6_addr, 16);
6,277✔
204

205
  d_pos += len;
6,277✔
206
}
6,277✔
207

208
void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
209
{
×
210
  if (version == 4) {
×
211
    uint32_t ip;
×
212
    xfrIP(ip);
×
213
    val = makeComboAddressFromRaw(4, string((const char*) &ip, 4));
×
214
  }
×
215
  else if (version == 6) {
×
216
    string ip;
×
217
    xfrIP6(ip);
×
218
    val = makeComboAddressFromRaw(6, ip);
×
219
  }
×
220
  else throw RecordTextException("invalid address family");
×
221
}
×
222

223
void RecordTextReader::xfrCAPort(ComboAddress &val)
224
{
×
225
  uint16_t port;
×
226
  xfr16BitInt(port);
×
227
  val.sin4.sin_port = port;
×
228
}
×
229

230
bool RecordTextReader::eof()
231
{
2,594,386✔
232
  return d_pos==d_end;
2,594,386✔
233
}
2,594,386✔
234

235
void RecordTextReader::xfr16BitInt(uint16_t &val)
236
{
656,009✔
237
  uint32_t tmp;
656,009✔
238
  xfr32BitInt(tmp);
656,009✔
239
  val=tmp;
656,009✔
240
  if(val!=tmp)
656,009!
241
    throw RecordTextException("Overflow reading 16 bit integer from record content"); // fixme improve
×
242
}
656,009✔
243

244
void RecordTextReader::xfr8BitInt(uint8_t &val)
245
{
1,287,663✔
246
  uint32_t tmp;
1,287,663✔
247
  xfr32BitInt(tmp);
1,287,663✔
248
  val=tmp;
1,287,663✔
249
  if(val!=tmp)
1,287,663!
250
    throw RecordTextException("Overflow reading 8 bit integer from record content"); // fixme improve
×
251
}
1,287,663✔
252

253
// this code should leave all the escapes around
254
void RecordTextReader::xfrName(DNSName& val, [[maybe_unused]] bool compress)
255
{
402,596✔
256
  skipSpaces();
402,596✔
257
  DNSName sval;
402,596✔
258

259
  string::size_type begin_pos = d_pos;
402,596✔
260
  while (d_pos < d_end) {
4,763,684✔
261
    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
4,718,249✔
262
      break;
357,157✔
263
    }
357,157✔
264

265
    d_pos++;
4,361,088✔
266
  }
4,361,088✔
267

268
  {
402,596✔
269
    std::string_view view(d_string);
402,596✔
270
    sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
402,596✔
271
  }
402,596✔
272

273
  if (sval.empty()) {
402,596!
274
    sval = DNSName(d_zone);
×
275
  }
×
276
  else if (!d_zone.empty()) {
402,597✔
277
    sval += DNSName(d_zone);
393,709✔
278
  }
393,709✔
279
  val = std::move(sval);
402,596✔
280
}
402,596✔
281

282
static bool isbase64(char c, bool acceptspace)
283
{
23,940,004✔
284
  if(dns_isspace(c))
23,940,004✔
285
    return acceptspace;
175✔
286
  if(c >= '0' && c <= '9')
23,939,829✔
287
    return true;
3,581,451✔
288
  if(c >= 'a' && c <= 'z')
20,358,378!
289
    return true;
9,668,398✔
290
  if(c >= 'A' && c <= 'Z')
10,689,980✔
291
    return true;
9,455,243✔
292
  if(c=='+' || c=='/' || c=='=')
1,234,737✔
293
    return true;
1,234,794✔
294
  return false;
2,147,483,668✔
295
}
1,234,737✔
296

297
void RecordTextReader::xfrBlobNoSpaces(string& val, int len) {
39✔
298
  skipSpaces();
39✔
299
  int pos=(int)d_pos;
39✔
300
  const char* strptr=d_string.c_str();
39✔
301
  while(d_pos < d_end && isbase64(strptr[d_pos], false))
951✔
302
    d_pos++;
912✔
303

304
  string tmp;
39✔
305
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
39✔
306
  boost::erase_all(tmp," ");
39✔
307
  val.clear();
39✔
308
  B64Decode(tmp, val);
39✔
309

310
  if (len>-1 && val.size() != static_cast<size_t>(len))
39!
311
    throw RecordTextException("Record length "+std::to_string(val.size()) + " does not match expected length '"+std::to_string(len));
×
312
}
39✔
313

314
void RecordTextReader::xfrBlob(string& val, int)
315
{
314,308✔
316
  skipSpaces();
314,308✔
317
  int pos=(int)d_pos;
314,308✔
318
  const char* strptr=d_string.c_str();
314,308✔
319
  while(d_pos < d_end && isbase64(strptr[d_pos], true))
24,253,381✔
320
    d_pos++;
23,939,073✔
321

322
  string tmp;
314,308✔
323
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
314,308✔
324
  boost::erase_all(tmp," ");
314,308✔
325
  val.clear();
314,308✔
326
  B64Decode(tmp, val);
314,308✔
327
}
314,308✔
328

329
void RecordTextReader::xfrRFC1035CharString(string &val) {
353✔
330
  auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
353✔
331
  d_pos += ctr;
353✔
332
}
353✔
333

334
void RecordTextReader::xfrSVCBValueList(vector<string> &val) {
682✔
335
  auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val);
682✔
336
  d_pos += ctr;
682✔
337
}
682✔
338

339
void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readability-function-cognitive-complexity)
340
{
778✔
341
  while (d_pos != d_end) {
1,795✔
342
    skipSpaces();
1,116✔
343
    if (d_pos == d_end)
1,116!
344
      return;
×
345

346
    // Find the SvcParamKey
347
    size_t pos = d_pos;
1,116✔
348
    while (d_pos != d_end) {
6,468✔
349
      if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
6,432✔
350
        break;
1,080✔
351
      }
1,080✔
352
      d_pos++;
5,352✔
353
    }
5,352✔
354

355
    // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
356
    string k = d_string.substr(pos, d_pos - pos);
1,116✔
357
    SvcParam::SvcParamKey key;
1,116✔
358
    bool generic;
1,116✔
359
    try {
1,116✔
360
      key = SvcParam::keyFromString(k, generic);
1,116✔
361
    } catch (const std::invalid_argument &e) {
1,116✔
362
      throw RecordTextException(e.what());
3✔
363
    }
3✔
364

365
    if (d_pos != d_end && d_string.at(d_pos) == '=') {
1,113✔
366
      d_pos++; // Now on the first character after '='
1,074✔
367
      if (d_pos == d_end || d_string.at(d_pos) == ' ') {
1,074✔
368
        throw RecordTextException("expected value after " + k + "=");
33✔
369
      }
33✔
370
    }
1,074✔
371

372
    switch (key) {
1,080✔
373
    case SvcParam::no_default_alpn:
21✔
374
      if (d_pos != d_end && d_string.at(d_pos) != ' ') {
21✔
375
        throw RecordTextException(k + " key can not have values");
6✔
376
      }
6✔
377
      val.insert(SvcParam(key));
15✔
378
      break;
15✔
379
    case SvcParam::ipv4hint: /* fall-through */
81✔
380
    case SvcParam::ipv6hint: {
138✔
381
      vector<ComboAddress> hints;
138✔
382
      bool doAuto{false};
138✔
383
      if (generic) {
138✔
384
        string value;
18✔
385
        xfrRFC1035CharString(value);
18✔
386
        size_t len = key == SvcParam::ipv4hint ? 4 : 16;
18✔
387
        if (value.size() % len != 0) {
18✔
388
          throw RecordTextException(k + " in generic format has wrong number of bytes");
6✔
389
        }
6✔
390
        for (size_t i=0; i<value.size(); i += len) {
30✔
391
          auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len);
18✔
392
          hints.push_back(hint);
18✔
393
        }
18✔
394
      } else {
120✔
395
        vector<string> value;
120✔
396
        xfrSVCBValueList(value);
120✔
397
        for (auto const &v: value) {
159✔
398
          if (v == "auto") {
159✔
399
            doAuto = true;
35✔
400
            hints.clear();
35✔
401
            break;
35✔
402
          }
35✔
403
          hints.push_back(ComboAddress(v));
124✔
404
        }
124✔
405
      }
120✔
406
      if (!doAuto && hints.empty()) {
132!
407
        throw RecordTextException("value is required for SVC Param " + k);
×
408
      }
×
409
      try {
132✔
410
        auto p = SvcParam(key, std::move(hints));
132✔
411
        p.setAutoHint(doAuto);
132✔
412
        val.insert(std::move(p));
132✔
413
      }
132✔
414
      catch (const std::invalid_argument& e) {
132✔
415
        throw RecordTextException(e.what());
12✔
416
      }
12✔
417
      break;
120✔
418
    }
132✔
419
    case SvcParam::alpn: {
538✔
420
      vector<string> value;
538✔
421
      if (generic) {
538✔
422
        string v;
15✔
423
        xfrRFC1035CharString(v);
15✔
424
        size_t spos{0}, len;
15✔
425
        while (spos < v.length()) {
30✔
426
          len = v.at(spos);
24✔
427
          spos += 1;
24✔
428
          if (len == 0) {
24✔
429
            throw RecordTextException("ALPN values cannot be empty strings");
3✔
430
          }
3✔
431
          if (len > v.length() - spos) {
21✔
432
            throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
6✔
433
          }
6✔
434
          value.push_back(v.substr(spos, len));
15✔
435
          spos += len;
15✔
436
        }
15✔
437
      } else {
523✔
438
        xfrSVCBValueList(value);
523✔
439
      }
523✔
440
      if (value.empty()) {
529✔
441
        throw RecordTextException("value is required for SVC Param " + k);
3✔
442
      }
3✔
443
      for (const auto &alpn_value : value) {
689✔
444
        if (alpn_value.empty()) {
689!
445
          throw RecordTextException("ALPN values cannot be empty strings");
×
446
        }
×
447
      }
689✔
448
      val.insert(SvcParam(key, std::move(value)));
526✔
449
      break;
526✔
450
    }
526✔
451
    case SvcParam::mandatory: {
48✔
452
      if (generic) {
48✔
453
        string v;
9✔
454
        xfrRFC1035CharString(v);
9✔
455
        if (v.empty()) {
9✔
456
          throw RecordTextException("value is required for SVC Param " + k);
3✔
457
        }
3✔
458
        if (v.length() % 2 != 0) {
6✔
459
          throw RecordTextException("Wrong number of bytes in SVC Param " + k);
3✔
460
        }
3✔
461
        std::set<SvcParam::SvcParamKey> keys;
3✔
462
        for (size_t i=0; i < v.length(); i += 2) {
9✔
463
          uint16_t mand = (v.at(i) << 8);
6✔
464
          mand += v.at(i+1);
6✔
465
          keys.insert(SvcParam::SvcParamKey(mand));
6✔
466
        }
6✔
467
        val.insert(SvcParam(key, std::move(keys)));
3✔
468
        break;
3✔
469
      }
6✔
470
      vector<string> parts;
39✔
471
      xfrSVCBValueList(parts);
39✔
472
      if (parts.empty()) {
39!
473
        throw RecordTextException("value is required for SVC Param " + k);
×
474
      }
×
475
      set<string> values(parts.begin(), parts.end());
39✔
476
      val.insert(SvcParam(key, std::move(values)));
39✔
477
      break;
39✔
478
    }
39✔
479
    case SvcParam::port: {
260✔
480
      uint16_t port;
260✔
481
      if (generic) {
260✔
482
        string v;
6✔
483
        xfrRFC1035CharString(v);
6✔
484
        if (v.length() != 2) {
6✔
485
          throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length()));
3✔
486
        }
3✔
487
        port = (v.at(0) << 8);
3✔
488
        port += v.at(1);
3✔
489
      } else {
254✔
490
        string portstring;
254✔
491
        xfrRFC1035CharString(portstring);
254✔
492
        if (portstring.empty()) {
254✔
493
          throw RecordTextException("value is required for SVC Param " + k);
3✔
494
        }
3✔
495
        try {
251✔
496
          pdns::checked_stoi_into(port, portstring);
251✔
497
        } catch (const std::exception &e) {
251✔
498
          throw RecordTextException(e.what());
6✔
499
        }
6✔
500
      }
251✔
501
      val.insert(SvcParam(key, port));
248✔
502
      break;
248✔
503
    }
260✔
504
    case SvcParam::ech: {
30✔
505
      string value;
30✔
506
      if (generic) {
30✔
507
        xfrRFC1035CharString(value);
6✔
508
      } else {
24✔
509
        bool haveQuote = d_string.at(d_pos) == '"';
24✔
510
        if (haveQuote) {
24✔
511
          d_pos++;
21✔
512
        }
21✔
513
        xfrBlobNoSpaces(value);
24✔
514
        if (haveQuote) {
24✔
515
          if (d_string.at(d_pos) != '"') {
21!
516
            throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
×
517
          }
×
518
          d_pos++;
21✔
519
        }
21✔
520
      }
24✔
521
      if (value.empty()) {
30✔
522
        throw RecordTextException("value is required for SVC Param " + k);
6✔
523
      }
6✔
524
      val.insert(SvcParam(key, value));
24✔
525
      break;
24✔
526
    }
30✔
527
    default: {
45✔
528
      string value;
45✔
529
      xfrRFC1035CharString(value);
45✔
530
      if (!generic && value.empty()) {
45!
531
        // for generic format, we do not know.
532
        // Known keys which forbid having a value need to implement a switch case, above.
533
        throw RecordTextException("value is required for SVC Param " + k);
×
534
      }
×
535
      val.insert(SvcParam(key, value));
45✔
536
      break;
45✔
537
    }
45✔
538
    }
1,080✔
539
  }
1,080✔
540
}
778✔
541

542
static inline uint8_t hextodec(uint8_t val)
543
{
1,695,085✔
544
  if(val >= '0' && val<='9')
1,695,085!
545
    return val-'0';
324,239✔
546
  else if(val >= 'A' && val<='F')
1,370,846✔
547
    return 10+(val-'A');
9,166✔
548
  else if(val >= 'a' && val<='f')
1,361,680✔
549
    return 10+(val-'a');
1,361,680✔
UNCOV
550
  else
×
UNCOV
551
    throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
×
552
}
1,695,085✔
553

554

555
static void HEXDecode(const char* begin, const char* end, string& out)
556
{
329,476✔
557
  if(end - begin == 1 && *begin=='-') {
329,476!
558
    out.clear();
35✔
559
    return;
35✔
560
  }
35✔
561
  out.clear();
329,441✔
562
  out.reserve((end-begin)/2);
329,441✔
563
  uint8_t mode=0, val=0;
329,441✔
564
  for(; begin != end; ++begin) {
2,024,696✔
565
    if(!isalnum(*begin))
1,695,255✔
566
      continue;
170✔
567
    if(mode==0) {
1,695,085✔
568
      val = 16*hextodec(*begin);
847,543✔
569
      mode=1;
847,543✔
570
    } else {
847,545✔
571
      val += hextodec(*begin);
847,542✔
572
      out.append(1, (char) val);
847,542✔
573
      mode = 0;
847,542✔
574
      val = 0;
847,542✔
575
    }
847,542✔
576
  }
1,695,085✔
577
  if(mode)
329,441✔
578
    throw RecordTextException("Hexadecimal blob with odd number of characters");
3✔
579
}
329,441✔
580

581
void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
582
{
329,477✔
583
  skipSpaces();
329,477✔
584
  int pos=(int)d_pos;
329,477✔
585
  while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos])))
2,024,767✔
586
    d_pos++;
1,695,290✔
587

588
  HEXDecode(d_string.c_str()+pos, d_string.c_str() + d_pos, val);
329,477✔
589
}
329,477✔
590

591
void RecordTextReader::xfrBase32HexBlob(string& val)
592
{
282,589✔
593
  skipSpaces();
282,589✔
594
  int pos=(int)d_pos;
282,589✔
595
  while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
8,365,293✔
596
    d_pos++;
8,082,704✔
597

598
  val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos));
282,589✔
599
}
282,589✔
600

601

602
void RecordTextWriter::xfrBase32HexBlob(const string& val)
603
{
261,812✔
604
  if(!d_string.empty())
261,812!
605
    d_string.append(1,' ');
261,812✔
606

607
  d_string.append(toUpper(toBase32Hex(val)));
261,812✔
608
}
261,812✔
609

610

611
void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
612
{
11,192✔
613
  val.clear();
11,192✔
614
  val.reserve(d_end - d_pos);
11,192✔
615

616
  while(d_pos != d_end) {
18,560✔
617
    if(!val.empty())
12,531✔
618
      val.append(1, ' ');
1,341✔
619

620
    skipSpaces();
12,531✔
621
    if(d_string[d_pos]!='"') { // special case 'plenus' - without quotes
12,531✔
622
      string::size_type pos = d_pos;
55✔
623
      while(pos != d_end && isalnum(d_string[pos]))
1,921✔
624
        pos++;
1,866✔
625
      if(pos == d_end) {
55✔
626
        val.append(1, '"');
49✔
627
        val.append(d_string.c_str() + d_pos, d_end - d_pos);
49✔
628
        val.append(1, '"');
49✔
629
        d_pos = d_end;
49✔
630
        break;
49✔
631
      }
49✔
632
      throw RecordTextException("Data field in DNS should start with quote (\") at position "+std::to_string(d_pos)+" of '"+d_string+"'");
6✔
633
    }
55✔
634
    val.append(1, '"');
12,476✔
635
    while(++d_pos < d_end && d_string[d_pos]!='"') {
423,052!
636
      if(d_string[d_pos]=='\\' && d_pos+1!=d_end) {
410,580!
637
        val.append(1, d_string[d_pos++]);
2,076✔
638
        char chr = d_string[d_pos];
2,076✔
639
        if (chr >= '0' && chr <= '9') {
2,076✔
640
          bool valid{false};
1,272✔
641
          // Must be a three-digit character escape sequence
642
          if (d_end - d_pos >= 3) {
1,272!
643
            char chr2 = d_string[d_pos + 1];
1,272✔
644
            char chr3 = d_string[d_pos + 2];
1,272✔
645
            if (chr2 >= '0' && chr2 <= '9' && chr3 >= '0' && chr3 <= '9') {
1,272!
646
              valid = true;
1,268✔
647
            }
1,268✔
648
          }
1,272✔
649
          if (!valid) {
1,272✔
650
            throw RecordTextException("Data field in DNS contains an invalid escape at position "+std::to_string(d_pos)+" of '"+d_string+"'");
4✔
651
          }
4✔
652
        }
1,272✔
653
        // Not advancing d_pos, we'll append the next 1 or 3 characters as
654
        // part of the regular case.
655
      }
2,076✔
656
      val.append(1, d_string[d_pos]);
410,576✔
657
    }
410,576✔
658
    val.append(1,'"');
12,472✔
659
    if(d_pos == d_end)
12,472!
660
      throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
×
661
    d_pos++;
12,472✔
662
    if(!multi)
12,472✔
663
      break;
5,104✔
664
  }
12,472✔
665
}
11,192✔
666

667
void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
668
{
21✔
669
  val.clear();
21✔
670
  val.reserve(d_end - d_pos);
21✔
671

672
  if(!val.empty())
21!
673
    val.append(1, ' ');
×
674

675
  skipSpaces();
21✔
676
  val.append(1, d_string[d_pos]);
21✔
677
  while(++d_pos < d_end && d_string[d_pos] != ' '){
105!
678
    val.append(1, d_string[d_pos]);
84✔
679
  }
84✔
680
}
21✔
681

682
void RecordTextReader::xfrType(uint16_t& val)
683
{
925,488✔
684
  skipSpaces();
925,488✔
685
  int pos=(int)d_pos;
925,488✔
686
  while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
3,830,778✔
687
    d_pos++;
2,905,290✔
688

689
  string tmp;
925,488✔
690
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
925,488✔
691

692
  val=DNSRecordContent::TypeToNumber(tmp);
925,488✔
693
}
925,488✔
694

695

696
void RecordTextReader::skipSpaces()
697
{
6,553,239✔
698
  const char* strptr = d_string.c_str();
6,553,239✔
699
  while(d_pos < d_end && dns_isspace(strptr[d_pos]))
11,091,327✔
700
    d_pos++;
4,538,088✔
701
  if(d_pos == d_end)
6,553,239!
702
    throw RecordTextException("missing field at the end of record content '"+d_string+"'");
×
703
}
6,553,239✔
704

705

706
RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
707
{
2,734,477✔
708
  d_string.clear();
2,734,477✔
709
  d_nodot=noDot;
2,734,477✔
710
}
2,734,477✔
711

712
void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
713
{
21✔
714
  if(!d_string.empty()) {
21✔
715
    d_string.append(1,' ');
18✔
716
  }
18✔
717

718
  size_t ctr = 0;
21✔
719
  char tmp[5];
21✔
720
  for (auto const &c : val.content) {
168✔
721
    snprintf(tmp, sizeof(tmp), "%02X", c);
168✔
722
    d_string+=tmp;
168✔
723
    ctr++;
168✔
724
    if (ctr % 2 == 0 && ctr != 8) {
168✔
725
      d_string+=':';
63✔
726
    }
63✔
727
  }
168✔
728
}
21✔
729

730
void RecordTextWriter::xfr48BitInt(const uint64_t& val)
731
{
81✔
732
  if(!d_string.empty())
81!
733
    d_string.append(1,' ');
81✔
734
  d_string+=std::to_string(val);
81✔
735
}
81✔
736

737

738
void RecordTextWriter::xfr32BitInt(const uint32_t& val)
739
{
4,456,605✔
740
  if(!d_string.empty())
4,456,605✔
741
    d_string.append(1,' ');
4,166,516✔
742
  d_string+=std::to_string(val);
4,456,605✔
743
}
4,456,605✔
744

745
void RecordTextWriter::xfrType(const uint16_t& val)
746
{
873,560✔
747
  if(!d_string.empty())
873,560!
748
    d_string.append(1,' ');
×
749
  d_string+=DNSRecordContent::NumberToType(val);
873,560✔
750
}
873,560✔
751

752
// this function is on the fast path for the pdns_recursor
753
void RecordTextWriter::xfrIP(const uint32_t& val)
754
{
1,225,525✔
755
  if(!d_string.empty())
1,225,525✔
756
    d_string.append(1,' ');
18✔
757

758
  char tmp[17];
1,225,525✔
759
  uint32_t ip=val;
1,225,525✔
760
  uint8_t vals[4];
1,225,525✔
761

762
  memcpy(&vals[0], &ip, sizeof(ip));
1,225,525✔
763

764
  char *pos=tmp;
1,225,525✔
765

766
  for(int n=0; n < 4; ++n) {
6,127,625✔
767
    if(vals[n]<10) {
4,902,100✔
768
      *(pos++)=vals[n]+'0';
1,307,948✔
769
    } else if(vals[n] < 100) {
3,598,296✔
770
      *(pos++)=(vals[n]/10) +'0';
487,475✔
771
      *(pos++)=(vals[n]%10) +'0';
487,475✔
772
    } else {
3,106,677✔
773
      *(pos++)=(vals[n]/100) +'0';
3,106,677✔
774
      vals[n]%=100;
3,106,677✔
775
      *(pos++)=(vals[n]/10) +'0';
3,106,677✔
776
      *(pos++)=(vals[n]%10) +'0';
3,106,677✔
777
    }
3,106,677✔
778
    if(n!=3)
4,902,100✔
779
      *(pos++)='.';
3,676,575✔
780
  }
4,902,100✔
781
  *pos=0;
1,225,525✔
782
  d_string.append(tmp, pos);
1,225,525✔
783
}
1,225,525✔
784

785
void RecordTextWriter::xfrIP6(const std::string& val)
786
{
2,569✔
787
  char tmpbuf[16];
2,569✔
788
  char addrbuf[40];
2,569✔
789

790
  if(!d_string.empty())
2,569✔
791
   d_string.append(1,' ');
12✔
792

793
  val.copy(tmpbuf,16);
2,569✔
794

795
  if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
2,569!
796
    throw RecordTextException("Unable to convert to ipv6 address");
×
797

798
  d_string += std::string(addrbuf);
2,569✔
799
}
2,569✔
800

801
void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
802
{
×
803
  string ip = val.toString();
×
804

805
  if(!d_string.empty())
×
806
    d_string.append(1,' ');
×
807

808
  d_string += ip;
×
809
}
×
810

811
void RecordTextWriter::xfrCAPort(ComboAddress &val)
812
{
×
813
  xfr16BitInt(val.sin4.sin_port);
×
814
}
×
815

816
void RecordTextWriter::xfrTime(const uint32_t& val)
817
{
1,747,120✔
818
  if(!d_string.empty())
1,747,120!
819
    d_string.append(1,' ');
1,747,120✔
820

821
  struct tm tm;
1,747,120✔
822
  time_t time=val; // Y2038 bug!
1,747,120✔
823
  gmtime_r(&time, &tm);
1,747,120✔
824

825
  static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
1,747,120✔
826
  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);
1,747,120✔
827
}
1,747,120✔
828

829

830
void RecordTextWriter::xfr16BitInt(const uint16_t& val)
831
{
1,174,376✔
832
  xfr32BitInt(val);
1,174,376✔
833
}
1,174,376✔
834

835
void RecordTextWriter::xfr8BitInt(const uint8_t& val)
836
{
2,291,176✔
837
  xfr32BitInt(val);
2,291,176✔
838
}
2,291,176✔
839

840
// should not mess with the escapes
841
void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */)
842
{
1,100,111✔
843
  if(!d_string.empty())
1,100,111✔
844
    d_string.append(1,' ');
914,805✔
845

846
  if(d_nodot) {
1,100,111✔
847
    d_string+=val.toStringRootDot();
721,244✔
848
  }
721,244✔
849
  else
378,867✔
850
  {
378,867✔
851
    d_string+=val.toString();
378,867✔
852
  }
378,867✔
853
}
1,100,111✔
854

855
void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
856
{
131✔
857
  xfrBlob(val, size);
131✔
858
}
131✔
859

860
void RecordTextWriter::xfrBlob(const string& val, int)
861
{
900,907✔
862
  if(!d_string.empty())
900,907✔
863
    d_string.append(1,' ');
878,066✔
864

865
  d_string+=Base64Encode(val);
900,907✔
866
}
900,907✔
867

868
void RecordTextWriter::xfrHexBlob(const string& val, bool)
869
{
267,504✔
870
  if(!d_string.empty())
267,504!
871
    d_string.append(1,' ');
267,504✔
872

873
  if(val.empty()) {
267,504✔
874
    d_string.append(1,'-');
9✔
875
    return;
9✔
876
  }
9✔
877

878
  string::size_type limit=val.size();
267,495✔
879
  char tmp[5];
267,495✔
880
  for(string::size_type n = 0; n < limit; ++n) {
930,828✔
881
    snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
663,333✔
882
    d_string+=tmp;
663,333✔
883
  }
663,333✔
884
}
267,495✔
885

886
void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
1,139✔
887
  bool shouldQuote{false};
1,139✔
888
  vector<string> escaped;
1,139✔
889
  escaped.reserve(val.size());
1,139✔
890
  for (auto const &v : val) {
1,419✔
891
    if (v.find_first_of(' ') != string::npos) {
1,419✔
892
      shouldQuote = true;
6✔
893
    }
6✔
894
    string tmp = txtEscape(v);
1,419✔
895
    string unescaped;
1,419✔
896
    unescaped.reserve(tmp.size() + 4);
1,419✔
897
    for (auto const &ch : tmp) {
3,048✔
898
      if (ch == '\\') {
3,048✔
899
        unescaped += R"F(\\)F";
36✔
900
        continue;
36✔
901
      }
36✔
902
      if (ch == ',') {
3,012✔
903
        unescaped += R"F(\\,)F";
30✔
904
        continue;
30✔
905
      }
30✔
906
      unescaped += ch;
2,982✔
907
    }
2,982✔
908
    escaped.push_back(std::move(unescaped));
1,419✔
909
  }
1,419✔
910
  if (shouldQuote) {
1,139✔
911
    d_string.append(1, '"');
6✔
912
  }
6✔
913
  d_string.append(boost::join(escaped, ","));
1,139✔
914
  if (shouldQuote) {
1,139✔
915
    d_string.append(1, '"');
6✔
916
  }
6✔
917
}
1,139✔
918

919
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
1,283✔
920
  for (auto const &param : val) {
1,951✔
921
    if (!d_string.empty())
1,951✔
922
      d_string.append(1, ' ');
1,906✔
923

924
    d_string.append(SvcParam::keyToString(param.getKey()));
1,951✔
925
    if (param.getKey() != SvcParam::no_default_alpn) {
1,951✔
926
      d_string.append(1, '=');
1,942✔
927
    }
1,942✔
928

929
    switch (param.getKey())
1,951✔
930
    {
1,951✔
931
    case SvcParam::no_default_alpn:
9✔
932
      break;
9✔
933
    case SvcParam::ipv4hint: /* fall-through */
53✔
934
    case SvcParam::ipv6hint:
80✔
935
      // TODO use xfrCA and put commas in between?
936
      if (param.getAutoHint()) {
80!
937
        d_string.append("auto");
×
938
        break;
×
939
      }
×
940
      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
80✔
941
      break;
80✔
942
    case SvcParam::alpn:
1,139✔
943
      xfrSVCBValueList(param.getALPN());
1,139✔
944
      break;
1,139✔
945
    case SvcParam::mandatory:
53✔
946
    {
53✔
947
      bool doComma = false;
53✔
948
      for (auto const &k: param.getMandatory()) {
62✔
949
        if (doComma)
62✔
950
          d_string.append(1, ',');
9✔
951
        d_string.append(SvcParam::keyToString(k));
62✔
952
        doComma = true;
62✔
953
      }
62✔
954
      break;
53✔
955
    }
80✔
956
    case SvcParam::port: {
590✔
957
      auto str = d_string;
590✔
958
      d_string.clear();
590✔
959
      xfr16BitInt(param.getPort());
590✔
960
      d_string = str + d_string;
590✔
961
      break;
590✔
962
    }
80✔
963
    case SvcParam::ech: {
32✔
964
      auto str = d_string;
32✔
965
      d_string.clear();
32✔
966
      xfrBlobNoSpaces(param.getECH());
32✔
967
      d_string = str + '"' + d_string + '"';
32✔
968
      break;
32✔
969
    }
80✔
970
    default:
48✔
971
      auto str = d_string;
48✔
972
      d_string.clear();
48✔
973
      xfrText(param.getValue(), false, false);
48✔
974
      d_string = str + '"' + txtEscape(d_string) + '"';
48✔
975
      break;
48✔
976
    }
1,951✔
977
  }
1,951✔
978
}
1,283✔
979

980
void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
981
{
144,759✔
982
  if(!d_string.empty())
144,759✔
983
    d_string.append(1,' ');
12,230✔
984

985
  d_string.append(val);
144,759✔
986
}
144,759✔
987

988
void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
989
{
42✔
990
  if(!d_string.empty())
42!
991
    d_string.append(1,' ');
42✔
992
  d_string.append(val);
42✔
993
}
42✔
994

995
#ifdef TESTING
996

997
int main(int argc, char**argv)
998
try
999
{
1000
  RecordTextReader rtr(argv[1], argv[2]);
1001

1002
  unsigned int order, pref;
1003
  string flags, services, regexp, replacement;
1004
  string mx;
1005

1006
  rtr.xfrInt(order);
1007
  rtr.xfrInt(pref);
1008
  rtr.xfrText(flags);
1009
  rtr.xfrText(services);
1010
  rtr.xfrText(regexp);
1011
  rtr.xfrName(replacement);
1012

1013
  cout<<"order: "<<order<<", pref: "<<pref<<"\n";
1014
  cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
1015

1016
  string out;
1017
  RecordTextWriter rtw(out);
1018

1019
  rtw.xfrInt(order);
1020
  rtw.xfrInt(pref);
1021
  rtw.xfrText(flags);
1022
  rtw.xfrText(services);
1023
  rtw.xfrText(regexp);
1024
  rtw.xfrName(replacement);
1025

1026
  cout<<"Regenerated: '"<<out<<"'\n";
1027

1028
}
1029
catch(std::exception& e)
1030
{
1031
  cerr<<"Fatal: "<<e.what()<<endl;
1032
}
1033

1034
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc