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

PowerDNS / pdns / 17795654203

17 Sep 2025 11:10AM UTC coverage: 66.037% (+0.03%) from 66.012%
17795654203

Pull #15808

github

web-flow
Merge 103733592 into a7f305b95
Pull Request #15808: rec: allow overriding remote address from preoutquery()

42442 of 92980 branches covered (45.65%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 2 files covered. (100.0%)

18 existing lines in 6 files now uncovered.

128910 of 166497 relevant lines covered (77.42%)

5194927.21 hits per line

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

90.56
/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,022,134✔
42
   /* remove whitespace */
43
   if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
2,022,134✔
44
     boost::trim_if(d_string, dns_isspace);
3✔
45
   d_end = d_string.size();
2,022,134✔
46
}
2,022,134✔
47

48
void RecordTextReader::xfr48BitInt(uint64_t &val)
49
{
6✔
50
  auto oldpos = d_pos;
6✔
51
  xfr64BitInt(val);
6✔
52
  if (val >= (1ULL << 48)) {
6!
53
    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
  }
×
55
}
6✔
56

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

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

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

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

76
void RecordTextReader::xfr64BitInt(uint64_t &val)
77
{
626,002✔
78
  skipSpaces();
626,002✔
79

80
  if(!isdigit(d_string.at(d_pos)))
626,002!
81
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
82

83
  size_t pos;
626,002✔
84
  val=std::stoull(d_string.substr(d_pos), &pos);
626,002✔
85

86
  d_pos += pos;
626,002✔
87
}
626,002✔
88

89

90
void RecordTextReader::xfr32BitInt(uint32_t &val)
91
{
2,361,672✔
92
  skipSpaces();
2,361,672✔
93

94
  if(!isdigit(d_string.at(d_pos)))
2,361,672!
95
    throw RecordTextException("expected digits at position "+std::to_string(d_pos)+" in '"+d_string+"'");
×
96

97
  size_t pos;
2,361,672✔
98
  val = pdns::checked_stoi<uint32_t>(d_string.c_str() + d_pos, &pos);
2,361,672✔
99

100
  d_pos += pos;
2,361,672✔
101
}
2,361,672✔
102

103
void RecordTextReader::xfrTime(uint32_t &val)
104
{
625,996✔
105
  struct tm tm;
625,996✔
106
  memset(&tm, 0, sizeof(tm));
625,996✔
107

108
  uint64_t itmp;
625,996✔
109
  xfr64BitInt(itmp);
625,996✔
110

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

117
  ostringstream tmp;
625,990✔
118

119
  tmp<<itmp;
625,990✔
120

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

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

133
void RecordTextReader::xfrIP(uint32_t &val)
134
{
1,291,923✔
135
  skipSpaces();
1,291,923✔
136

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

140
  uint32_t octet=0;
1,291,919✔
141
  val=0;
1,291,919✔
142
  char count=0;
1,291,919✔
143
  bool last_was_digit = false;
1,291,919✔
144

145
  for(;;) {
16,176,903✔
146
    if(d_string.at(d_pos)=='.') {
16,176,903✔
147
      if (!last_was_digit)
3,875,732✔
148
        throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
3✔
149
      last_was_digit = false;
3,875,729✔
150
      val<<=8;
3,875,729✔
151
      val+=octet;
3,875,729✔
152
      octet=0;
3,875,729✔
153
      count++;
3,875,729✔
154
      if(count > 3)
3,875,729✔
155
        throw RecordTextException(string("unable to parse IP address, too many dots"));
3✔
156
    }
3,875,729✔
157
    else if(isdigit(d_string.at(d_pos))) {
12,301,171✔
158
      last_was_digit = true;
12,301,165✔
159
      octet*=10;
12,301,165✔
160
      octet+=d_string.at(d_pos) - '0';
12,301,165✔
161
      if(octet > 255)
12,301,165✔
162
        throw RecordTextException("unable to parse IP address");
6✔
163
    }
12,301,165✔
164
    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
6!
165
      break;
6✔
UNCOV
166
    else {
×
UNCOV
167
      throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
×
UNCOV
168
    }
×
169
    d_pos++;
16,176,885✔
170
    if(d_pos == d_string.length())
16,176,885✔
171
      break;
1,291,901✔
172
  }
16,176,885✔
173
  if (count != 3)
1,291,907✔
174
    throw RecordTextException(string("unable to parse IP address, not enough dots"));
3✔
175
  if (!last_was_digit)
1,291,904✔
176
    throw RecordTextException(string("unable to parse IP address, trailing dot"));
3✔
177
  val<<=8;
1,291,901✔
178
  val+=octet;
1,291,901✔
179
  val=ntohl(val);
1,291,901✔
180
}
1,291,901✔
181

182

183
void RecordTextReader::xfrIP6(std::string &val)
184
{
6,289✔
185
  struct in6_addr tmpbuf;
6,289✔
186

187
  skipSpaces();
6,289✔
188

189
  size_t len;
6,289✔
190
  // lookup end of value - think of ::ffff encoding too, has dots in it!
191
  for(len=0;
6,289✔
192
      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,412✔
193
    len++);
100,123✔
194

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

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

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

205
  val = std::string((char*)tmpbuf.s6_addr, 16);
6,280✔
206

207
  d_pos += len;
6,280✔
208
}
6,280✔
209

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

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

232
bool RecordTextReader::eof()
233
{
2,594,653✔
234
  return d_pos==d_end;
2,594,653✔
235
}
2,594,653✔
236

237
void RecordTextReader::xfr16BitInt(uint16_t &val)
238
{
656,171✔
239
  auto oldpos = d_pos;
656,171✔
240
  uint32_t tmp{0};
656,171✔
241
  xfr32BitInt(tmp);
656,171✔
242
  val=tmp;
656,171✔
243
  if(val!=tmp) {
656,171!
244
    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");
×
245
  }
×
246
}
656,171✔
247

248
void RecordTextReader::xfr8BitInt(uint8_t &val)
249
{
1,287,994✔
250
  auto oldpos = d_pos;
1,287,994✔
251
  uint32_t tmp{0};
1,287,994✔
252
  xfr32BitInt(tmp);
1,287,994✔
253
  val=tmp;
1,287,994✔
254
  if(val!=tmp) {
1,287,994!
255
    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");
×
256
  }
×
257
}
1,287,994✔
258

259
// this code should leave all the escapes around
260
void RecordTextReader::xfrName(DNSName& val, [[maybe_unused]] bool compress)
261
{
402,742✔
262
  skipSpaces();
402,742✔
263
  DNSName sval;
402,742✔
264

265
  string::size_type begin_pos = d_pos;
402,742✔
266
  while (d_pos < d_end) {
4,762,741✔
267
    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
4,717,262✔
268
      break;
357,263✔
269
    }
357,263✔
270

271
    d_pos++;
4,359,999✔
272
  }
4,359,999✔
273

274
  {
402,742✔
275
    std::string_view view(d_string);
402,742✔
276
    sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
402,742✔
277
  }
402,742✔
278

279
  if (sval.empty()) {
402,742!
280
    sval = DNSName(d_zone);
×
281
  }
×
282
  else if (!d_zone.empty()) {
402,742✔
283
    sval += DNSName(d_zone);
393,856✔
284
  }
393,856✔
285
  val = std::move(sval);
402,742✔
286
}
402,742✔
287

288
static bool isbase64(char c, bool acceptspace)
289
{
23,940,893✔
290
  if(dns_isspace(c))
23,940,893✔
291
    return acceptspace;
175✔
292
  if(c >= '0' && c <= '9')
23,940,718✔
293
    return true;
3,582,826✔
294
  if(c >= 'a' && c <= 'z')
20,357,892!
295
    return true;
9,669,350✔
296
  if(c >= 'A' && c <= 'Z')
10,688,542✔
297
    return true;
9,452,975✔
298
  if(c=='+' || c=='/' || c=='=')
1,235,567✔
299
    return true;
1,235,562✔
300
  return false;
2,147,483,668✔
301
}
1,235,567✔
302

303
void RecordTextReader::xfrBlobNoSpaces(string& val, int len)
304
{
39✔
305
  skipSpaces();
39✔
306
  auto pos = d_pos;
39✔
307
  const char* strptr=d_string.c_str();
39✔
308
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
309
  while(d_pos < d_end && isbase64(strptr[d_pos], false)) {
951✔
310
    d_pos++;
912✔
311
  }
912✔
312

313
  string tmp;
39✔
314
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
39✔
315
  boost::erase_all(tmp," ");
39✔
316
  val.clear();
39✔
317
  B64Decode(tmp, val);
39✔
318

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

323
void RecordTextReader::xfrBlob(string& val, int)
324
{
314,319✔
325
  skipSpaces();
314,319✔
326
  auto pos = d_pos;
314,319✔
327
  const char* strptr=d_string.c_str();
314,319✔
328
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
329
  while(d_pos < d_end && isbase64(strptr[d_pos], true)) {
24,254,268✔
330
    d_pos++;
23,939,949✔
331
  }
23,939,949✔
332

333
  string tmp;
314,319✔
334
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
314,319✔
335
  boost::erase_all(tmp," ");
314,319✔
336
  val.clear();
314,319✔
337
  B64Decode(tmp, val);
314,319✔
338
}
314,319✔
339

340
void RecordTextReader::xfrRFC1035CharString(string &val) {
353✔
341
  auto ctr = parseRFC1035CharString(d_string.substr(d_pos, d_end - d_pos), val);
353✔
342
  d_pos += ctr;
353✔
343
}
353✔
344

345
void RecordTextReader::xfrSVCBValueList(vector<string> &val) {
682✔
346
  auto ctr = parseSVCBValueList(d_string.substr(d_pos, d_end - d_pos), val);
682✔
347
  d_pos += ctr;
682✔
348
}
682✔
349

350
void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readability-function-cognitive-complexity)
351
{
778✔
352
  while (d_pos != d_end) {
1,795✔
353
    skipSpaces();
1,116✔
354
    if (d_pos == d_end)
1,116!
355
      return;
×
356

357
    // Find the SvcParamKey
358
    auto pos = d_pos;
1,116✔
359
    while (d_pos != d_end) {
6,468✔
360
      if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
6,432✔
361
        break;
1,080✔
362
      }
1,080✔
363
      d_pos++;
5,352✔
364
    }
5,352✔
365

366
    // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
367
    string k = d_string.substr(pos, d_pos - pos);
1,116✔
368
    SvcParam::SvcParamKey key;
1,116✔
369
    bool generic;
1,116✔
370
    try {
1,116✔
371
      key = SvcParam::keyFromString(k, generic);
1,116✔
372
    } catch (const std::invalid_argument &e) {
1,116✔
373
      throw RecordTextException(e.what());
3✔
374
    }
3✔
375

376
    if (d_pos != d_end && d_string.at(d_pos) == '=') {
1,113✔
377
      d_pos++; // Now on the first character after '='
1,074✔
378
      if (d_pos == d_end || d_string.at(d_pos) == ' ') {
1,074✔
379
        throw RecordTextException("expected value after " + k + "=");
33✔
380
      }
33✔
381
    }
1,074✔
382

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

553
static inline uint8_t hextodec(uint8_t val)
554
{
1,701,682✔
555
  if(val >= '0' && val<='9')
1,701,684✔
556
    return val-'0';
328,403✔
557
  else if(val >= 'A' && val<='F')
1,373,280✔
558
    return 10+(val-'A');
9,166✔
559
  else if(val >= 'a' && val<='f')
1,364,114✔
560
    return 10+(val-'a');
1,364,115✔
561
  else
4,294,967,294✔
562
    throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
4,294,967,294✔
563
}
1,701,682✔
564

565

566
static void HEXDecode(std::string_view chunk, string& out)
567
{
329,630✔
568
  out.clear();
329,630✔
569
  if (chunk.length() == 1 && chunk[0] == '-') {
329,630!
570
    return;
35✔
571
  }
35✔
572
  out.reserve(chunk.length() / 2);
329,595✔
573
  bool lowdigit{false};
329,595✔
574
  uint8_t val{0};
329,595✔
575
  for (auto chr : chunk) {
1,701,855✔
576
    if(isalnum(chr) == 0) {
1,701,855✔
577
      continue;
170✔
578
    }
170✔
579
    if (!lowdigit) {
1,701,685✔
580
      val = 16*hextodec(chr);
850,843✔
581
      lowdigit = true;
850,843✔
582
    } else {
850,846✔
583
      val += hextodec(chr);
850,842✔
584
      out.append(1, (char) val);
850,842✔
585
      lowdigit = false;
850,842✔
586
      val = 0;
850,842✔
587
    }
850,842✔
588
  }
1,701,685✔
589
  if (lowdigit) {
329,595✔
590
    throw RecordTextException("Hexadecimal blob '" + std::string(chunk) + "' contains an odd number of hex digits");
3✔
591
  }
3✔
592
}
329,595✔
593

594
void RecordTextReader::xfrHexBlob(string& val, bool keepReading)
595
{
329,631✔
596
  skipSpaces();
329,631✔
597
  auto pos = d_pos;
329,631✔
598
  while(d_pos < d_end && (keepReading || !dns_isspace(d_string[d_pos]))) {
2,031,521✔
599
    d_pos++;
1,701,890✔
600
  }
1,701,890✔
601

602
  HEXDecode(std::string_view(d_string).substr(pos, d_pos - pos), val);
329,631✔
603
}
329,631✔
604

605
void RecordTextReader::xfrBase32HexBlob(string& val)
606
{
282,589✔
607
  skipSpaces();
282,589✔
608
  auto pos = d_pos;
282,589✔
609
  while(d_pos < d_end && !dns_isspace(d_string[d_pos])) {
8,365,293✔
610
    d_pos++;
8,082,704✔
611
  }
8,082,704✔
612

613
  val=fromBase32Hex(string(d_string.c_str()+pos, d_pos-pos));
282,589✔
614
}
282,589✔
615

616

617
void RecordTextWriter::xfrBase32HexBlob(const string& val)
618
{
261,840✔
619
  if(!d_string.empty())
261,840!
620
    d_string.append(1,' ');
261,840✔
621

622
  d_string.append(toUpper(toBase32Hex(val)));
261,840✔
623
}
261,840✔
624

625

626
void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
627
{
11,210✔
628
  val.clear();
11,210✔
629
  val.reserve(d_end - d_pos);
11,210✔
630

631
  while (d_pos != d_end) {
18,646✔
632
    if (!val.empty()) {
12,564✔
633
      val.append(1, ' ');
1,356✔
634
    }
1,356✔
635

636
    skipSpaces();
12,564✔
637
    char delimiter{'"'};
12,564✔
638
    bool quoted = d_string[d_pos] == '"';
12,564✔
639
    // If the word is quoted, process up to the next quote; otherwise,
640
    // process up to the next whitespace (but output it in quotes).
641
    val.append(1, '"');
12,564✔
642
    if (quoted) {
12,564✔
643
      ++d_pos;
12,493✔
644
    }
12,493✔
645
    else {
71✔
646
      // RFC1035: ``a contiguous set of characters without interior spaces''
647
      delimiter = ' ';
71✔
648
    }
71✔
649
    while (d_pos != d_end && d_string[d_pos] != delimiter) {
425,146✔
650
      if (d_string[d_pos] == '\\' && d_pos + 1 != d_end) {
412,592!
651
        val.append(1, d_string[d_pos++]); // copy escape slash
2,078✔
652
        char chr = d_string[d_pos];
2,078✔
653
        if (chr >= '0' && chr <= '9') {
2,078✔
654
          bool valid{false};
1,274✔
655
          // Must be a three-digit character escape sequence
656
          if (d_end - d_pos >= 3) {
1,274!
657
            char chr2 = d_string[d_pos + 1];
1,274✔
658
            char chr3 = d_string[d_pos + 2];
1,274✔
659
            if (chr2 >= '0' && chr2 <= '9' && chr3 >= '0' && chr3 <= '9') {
1,274!
660
              valid = 100 * (chr - '0') + 10 * (chr2 - '0') + chr3 - '0' < 256;
1,271✔
661
            }
1,271✔
662
          }
1,274✔
663
          if (!valid) {
1,274✔
664
            throw RecordTextException("Data field in DNS contains an invalid escape at position "+std::to_string(d_pos)+" of '"+d_string+"'");
6✔
665
          }
6✔
666
        }
1,274✔
667
        // Not advancing d_pos, we'll append the next 1 or 3 characters as
668
        // part of the regular case.
669
      }
2,078✔
670
      if (!quoted && d_string[d_pos] == '"') {
412,586✔
671
        // Bind allows a non-quoted text to be immediately followed by a
672
        // quoted text, without any whitespace in between, so handle this
673
        // as a delimiter.
674
        break;
4✔
675
      }
4✔
676
      val.append(1, d_string[d_pos]);
412,582✔
677
      ++d_pos;
412,582✔
678
    }
412,582✔
679
    val.append(1,'"');
12,558✔
680
    if (quoted) {
12,558✔
681
      // If we reached the end in a quoted section, the closing quote is missing.
682
      if (d_pos == d_end) {
12,493✔
683
        throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
3✔
684
      }
3✔
685
      // Skip closing quote
686
      ++d_pos;
12,490✔
687
    }
12,490✔
688
    if (!multi) {
12,555✔
689
      break;
5,119✔
690
    }
5,119✔
691
  }
12,555✔
692
}
11,210✔
693

694
void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
695
{
21✔
696
  val.clear();
21✔
697
  val.reserve(d_end - d_pos);
21✔
698

699
  if(!val.empty())
21!
700
    val.append(1, ' ');
×
701

702
  skipSpaces();
21✔
703
  val.append(1, d_string[d_pos]);
21✔
704
  while(++d_pos < d_end && d_string[d_pos] != ' '){
105!
705
    val.append(1, d_string[d_pos]);
84✔
706
  }
84✔
707
}
21✔
708

709
void RecordTextReader::xfrType(uint16_t& val)
710
{
925,497✔
711
  skipSpaces();
925,497✔
712
  auto pos = d_pos;
925,497✔
713
  while(d_pos < d_end && !dns_isspace(d_string[d_pos])) {
3,830,816✔
714
    d_pos++;
2,905,319✔
715
  }
2,905,319✔
716

717
  string tmp;
925,497✔
718
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
925,497✔
719

720
  val=DNSRecordContent::TypeToNumber(tmp);
925,497✔
721
}
925,497✔
722

723

724
void RecordTextReader::skipSpaces()
725
{
6,554,413✔
726
  const char* strptr = d_string.c_str();
6,554,413✔
727
  while(d_pos < d_end && dns_isspace(strptr[d_pos]))
11,093,350✔
728
    d_pos++;
4,538,933✔
729
  if(d_pos == d_end)
6,554,413!
730
    throw RecordTextException("missing field at the end of record content '"+d_string+"'");
×
731
}
6,554,413✔
732

733

734
RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
735
{
2,734,604✔
736
  d_string.clear();
2,734,604✔
737
  d_nodot=noDot;
2,734,604✔
738
}
2,734,604✔
739

740
void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
741
{
21✔
742
  if(!d_string.empty()) {
21✔
743
    d_string.append(1,' ');
18✔
744
  }
18✔
745

746
  size_t ctr = 0;
21✔
747
  char tmp[5];
21✔
748
  for (auto const &c : val.content) {
168✔
749
    snprintf(tmp, sizeof(tmp), "%02X", c);
168✔
750
    d_string+=tmp;
168✔
751
    ctr++;
168✔
752
    if (ctr % 2 == 0 && ctr != 8) {
168✔
753
      d_string+=':';
63✔
754
    }
63✔
755
  }
168✔
756
}
21✔
757

758
void RecordTextWriter::xfr48BitInt(const uint64_t& val)
759
{
81✔
760
  if(!d_string.empty())
81!
761
    d_string.append(1,' ');
81✔
762
  d_string+=std::to_string(val);
81✔
763
}
81✔
764

765

766
void RecordTextWriter::xfr32BitInt(const uint32_t& val)
767
{
4,456,498✔
768
  if(!d_string.empty())
4,456,498✔
769
    d_string.append(1,' ');
4,166,375✔
770
  d_string+=std::to_string(val);
4,456,498✔
771
}
4,456,498✔
772

773
void RecordTextWriter::xfrType(const uint16_t& val)
774
{
873,714✔
775
  if(!d_string.empty())
873,714!
776
    d_string.append(1,' ');
×
777
  d_string+=DNSRecordContent::NumberToType(val);
873,714✔
778
}
873,714✔
779

780
// this function is on the fast path for the pdns_recursor
781
void RecordTextWriter::xfrIP(const uint32_t& val)
782
{
1,225,528✔
783
  if(!d_string.empty())
1,225,528✔
784
    d_string.append(1,' ');
18✔
785

786
  char tmp[17];
1,225,528✔
787
  uint32_t ip=val;
1,225,528✔
788
  uint8_t vals[4];
1,225,528✔
789

790
  memcpy(&vals[0], &ip, sizeof(ip));
1,225,528✔
791

792
  char *pos=tmp;
1,225,528✔
793

794
  for(int n=0; n < 4; ++n) {
6,127,640✔
795
    if(vals[n]<10) {
4,902,112✔
796
      *(pos++)=vals[n]+'0';
1,308,313✔
797
    } else if(vals[n] < 100) {
3,598,043✔
798
      *(pos++)=(vals[n]/10) +'0';
487,238✔
799
      *(pos++)=(vals[n]%10) +'0';
487,238✔
800
    } else {
3,106,561✔
801
      *(pos++)=(vals[n]/100) +'0';
3,106,561✔
802
      vals[n]%=100;
3,106,561✔
803
      *(pos++)=(vals[n]/10) +'0';
3,106,561✔
804
      *(pos++)=(vals[n]%10) +'0';
3,106,561✔
805
    }
3,106,561✔
806
    if(n!=3)
4,902,112✔
807
      *(pos++)='.';
3,676,584✔
808
  }
4,902,112✔
809
  *pos=0;
1,225,528✔
810
  d_string.append(tmp, pos);
1,225,528✔
811
}
1,225,528✔
812

813
void RecordTextWriter::xfrIP6(const std::string& val)
814
{
2,563✔
815
  char tmpbuf[16];
2,563✔
816
  char addrbuf[40];
2,563✔
817

818
  if(!d_string.empty())
2,563✔
819
   d_string.append(1,' ');
12✔
820

821
  val.copy(tmpbuf,16);
2,563✔
822

823
  if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
2,563!
824
    throw RecordTextException("Unable to convert to ipv6 address");
×
825

826
  d_string += std::string(addrbuf);
2,563✔
827
}
2,563✔
828

829
void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
830
{
×
831
  string ip = val.toString();
×
832

833
  if(!d_string.empty())
×
834
    d_string.append(1,' ');
×
835

836
  d_string += ip;
×
837
}
×
838

839
void RecordTextWriter::xfrCAPort(ComboAddress &val)
840
{
×
841
  xfr16BitInt(val.sin4.sin_port);
×
842
}
×
843

844
void RecordTextWriter::xfrTime(const uint32_t& val)
845
{
1,747,428✔
846
  if(!d_string.empty())
1,747,428✔
847
    d_string.append(1,' ');
1,747,427✔
848

849
  struct tm tm;
1,747,428✔
850
  time_t time=val; // Y2038 bug!
1,747,428✔
851
  gmtime_r(&time, &tm);
1,747,428✔
852

853
  static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
1,747,428✔
854
  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,428✔
855
}
1,747,428✔
856

857

858
void RecordTextWriter::xfr16BitInt(const uint16_t& val)
859
{
1,174,524✔
860
  xfr32BitInt(val);
1,174,524✔
861
}
1,174,524✔
862

863
void RecordTextWriter::xfr8BitInt(const uint8_t& val)
864
{
2,291,638✔
865
  xfr32BitInt(val);
2,291,638✔
866
}
2,291,638✔
867

868
// should not mess with the escapes
869
void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */)
870
{
1,099,974✔
871
  if(!d_string.empty())
1,099,974✔
872
    d_string.append(1,' ');
914,744✔
873

874
  if(d_nodot) {
1,099,974✔
875
    d_string+=val.toStringRootDot();
721,413✔
876
  }
721,413✔
877
  else
378,561✔
878
  {
378,561✔
879
    d_string+=val.toString();
378,561✔
880
  }
378,561✔
881
}
1,099,974✔
882

883
void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
884
{
131✔
885
  xfrBlob(val, size);
131✔
886
}
131✔
887

888
void RecordTextWriter::xfrBlob(const string& val, int)
889
{
901,092✔
890
  if(!d_string.empty())
901,092✔
891
    d_string.append(1,' ');
878,251✔
892

893
  d_string+=Base64Encode(val);
901,092✔
894
}
901,092✔
895

896
void RecordTextWriter::xfrHexBlob(const string& val, bool)
897
{
267,550✔
898
  if(!d_string.empty())
267,550!
899
    d_string.append(1,' ');
267,550✔
900

901
  if(val.empty()) {
267,550✔
902
    d_string.append(1,'-');
9✔
903
    return;
9✔
904
  }
9✔
905

906
  string::size_type limit=val.size();
267,541✔
907
  char tmp[5];
267,541✔
908
  for(string::size_type n = 0; n < limit; ++n) {
931,290✔
909
    snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
663,749✔
910
    d_string+=tmp;
663,749✔
911
  }
663,749✔
912
}
267,541✔
913

914
void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
1,137✔
915
  bool shouldQuote{false};
1,137✔
916
  vector<string> escaped;
1,137✔
917
  escaped.reserve(val.size());
1,137✔
918
  for (auto const &v : val) {
1,417✔
919
    if (v.find_first_of(' ') != string::npos) {
1,417✔
920
      shouldQuote = true;
6✔
921
    }
6✔
922
    string tmp = txtEscape(v);
1,417✔
923
    string unescaped;
1,417✔
924
    unescaped.reserve(tmp.size() + 4);
1,417✔
925
    for (auto const &ch : tmp) {
3,044✔
926
      if (ch == '\\') {
3,044✔
927
        unescaped += R"F(\\)F";
36✔
928
        continue;
36✔
929
      }
36✔
930
      if (ch == ',') {
3,008✔
931
        unescaped += R"F(\\,)F";
30✔
932
        continue;
30✔
933
      }
30✔
934
      unescaped += ch;
2,978✔
935
    }
2,978✔
936
    escaped.push_back(std::move(unescaped));
1,417✔
937
  }
1,417✔
938
  if (shouldQuote) {
1,137✔
939
    d_string.append(1, '"');
6✔
940
  }
6✔
941
  d_string.append(boost::join(escaped, ","));
1,137✔
942
  if (shouldQuote) {
1,137✔
943
    d_string.append(1, '"');
6✔
944
  }
6✔
945
}
1,137✔
946

947
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
1,281✔
948
  for (auto const &param : val) {
1,948✔
949
    if (!d_string.empty())
1,948✔
950
      d_string.append(1, ' ');
1,903✔
951

952
    d_string.append(SvcParam::keyToString(param.getKey()));
1,948✔
953
    if (param.getKey() != SvcParam::no_default_alpn) {
1,948✔
954
      d_string.append(1, '=');
1,939✔
955
    }
1,939✔
956

957
    switch (param.getKey())
1,948✔
958
    {
1,948✔
959
    case SvcParam::no_default_alpn:
9✔
960
      break;
9✔
961
    case SvcParam::ipv4hint: /* fall-through */
53✔
962
    case SvcParam::ipv6hint:
80✔
963
      // TODO use xfrCA and put commas in between?
964
      if (param.getAutoHint()) {
80!
965
        d_string.append("auto");
×
966
        break;
×
967
      }
×
968
      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
80✔
969
      break;
80✔
970
    case SvcParam::alpn:
1,137✔
971
      xfrSVCBValueList(param.getALPN());
1,137✔
972
      break;
1,137✔
973
    case SvcParam::mandatory:
53✔
974
    {
53✔
975
      bool doComma = false;
53✔
976
      for (auto const &k: param.getMandatory()) {
62✔
977
        if (doComma)
62✔
978
          d_string.append(1, ',');
9✔
979
        d_string.append(SvcParam::keyToString(k));
62✔
980
        doComma = true;
62✔
981
      }
62✔
982
      break;
53✔
983
    }
80✔
984
    case SvcParam::port: {
589✔
985
      auto str = d_string;
589✔
986
      d_string.clear();
589✔
987
      xfr16BitInt(param.getPort());
589✔
988
      d_string = str + d_string;
589✔
989
      break;
589✔
990
    }
80✔
991
    case SvcParam::ech: {
32✔
992
      auto str = d_string;
32✔
993
      d_string.clear();
32✔
994
      xfrBlobNoSpaces(param.getECH());
32✔
995
      d_string = str + '"' + d_string + '"';
32✔
996
      break;
32✔
997
    }
80✔
998
    default:
48✔
999
      auto str = d_string;
48✔
1000
      d_string.clear();
48✔
1001
      xfrText(param.getValue(), false, false);
48✔
1002
      d_string = str + '"' + txtEscape(d_string) + '"';
48✔
1003
      break;
48✔
1004
    }
1,948✔
1005
  }
1,948✔
1006
}
1,281✔
1007

1008
void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
1009
{
144,667✔
1010
  if(!d_string.empty())
144,667✔
1011
    d_string.append(1,' ');
12,122✔
1012

1013
  if (val.empty()) {
144,667!
1014
    d_string.append(2, '"');
×
1015
  }
×
1016
  else {
144,667✔
1017
    d_string.append(val);
144,667✔
1018
  }
144,667✔
1019
}
144,667✔
1020

1021
void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
1022
{
42✔
1023
  if(!d_string.empty())
42!
1024
    d_string.append(1,' ');
42✔
1025
  d_string.append(val);
42✔
1026
}
42✔
1027

1028
#ifdef TESTING
1029

1030
int main(int argc, char**argv)
1031
try
1032
{
1033
  RecordTextReader rtr(argv[1], argv[2]);
1034

1035
  unsigned int order, pref;
1036
  string flags, services, regexp, replacement;
1037
  string mx;
1038

1039
  rtr.xfrInt(order);
1040
  rtr.xfrInt(pref);
1041
  rtr.xfrText(flags);
1042
  rtr.xfrText(services);
1043
  rtr.xfrText(regexp);
1044
  rtr.xfrName(replacement);
1045

1046
  cout<<"order: "<<order<<", pref: "<<pref<<"\n";
1047
  cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
1048

1049
  string out;
1050
  RecordTextWriter rtw(out);
1051

1052
  rtw.xfrInt(order);
1053
  rtw.xfrInt(pref);
1054
  rtw.xfrText(flags);
1055
  rtw.xfrText(services);
1056
  rtw.xfrText(regexp);
1057
  rtw.xfrName(replacement);
1058

1059
  cout<<"Regenerated: '"<<out<<"'\n";
1060

1061
}
1062
catch(std::exception& e)
1063
{
1064
  cerr<<"Fatal: "<<e.what()<<endl;
1065
}
1066

1067
#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