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

PowerDNS / pdns / 18225652081

03 Oct 2025 02:49PM UTC coverage: 65.825%. Remained the same
18225652081

Pull #16211

github

web-flow
Merge a109c2507 into feeb24672
Pull Request #16211: auth 5.0.x: backport "grow auth source tarballs fatter"

42048 of 92452 branches covered (45.48%)

Branch coverage included in aggregate %.

127983 of 165855 relevant lines covered (77.17%)

5370244.34 hits per line

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

90.64
/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
{
1,958,176✔
42
   /* remove whitespace */
43
   if(!d_string.empty() && ( dns_isspace(*d_string.begin()) || dns_isspace(*d_string.rbegin()) ))
1,958,176!
44
     boost::trim_if(d_string, dns_isspace);
7✔
45
   d_end = d_string.size();
1,958,176✔
46
}
1,958,176✔
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,640✔
76
  skipSpaces();
625,640✔
77

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

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

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

87

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

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

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

98
  d_pos += pos;
2,358,130✔
99
}
2,358,130✔
100

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

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

109
  if (itmp <= (uint32_t)~0) {
625,633✔
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,627✔
116

117
  tmp<<itmp;
625,627✔
118

119
  if (sscanf(tmp.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
625,627!
120
             &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
625,627✔
121
             &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
625,627✔
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,627✔
126
  tm.tm_mon-=1;
625,627✔
127
  // coverity[store_truncates_time_t]
128
  val=(uint32_t)Utility::timegm(&tm);
625,627✔
129
}
625,627✔
130

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

135
  if(!isdigit(d_string.at(d_pos)))
1,230,138✔
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,230,134✔
139
  val=0;
1,230,134✔
140
  char count=0;
1,230,134✔
141
  bool last_was_digit = false;
1,230,134✔
142

143
  for(;;) {
15,405,654✔
144
    if(d_string.at(d_pos)=='.') {
15,405,654✔
145
      if (!last_was_digit)
3,690,378✔
146
        throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
3✔
147
      last_was_digit = false;
3,690,375✔
148
      val<<=8;
3,690,375✔
149
      val+=octet;
3,690,375✔
150
      octet=0;
3,690,375✔
151
      count++;
3,690,375✔
152
      if(count > 3)
3,690,375✔
153
        throw RecordTextException(string("unable to parse IP address, too many dots"));
3✔
154
    }
3,690,375✔
155
    else if(isdigit(d_string.at(d_pos))) {
11,715,276✔
156
      last_was_digit = true;
11,715,270✔
157
      octet*=10;
11,715,270✔
158
      octet+=d_string.at(d_pos) - '0';
11,715,270✔
159
      if(octet > 255)
11,715,270✔
160
        throw RecordTextException("unable to parse IP address");
6✔
161
    }
11,715,270✔
162
    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ',')
6!
163
      break;
6✔
164
    else {
×
165
      throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
×
166
    }
×
167
    d_pos++;
15,405,636✔
168
    if(d_pos == d_string.length())
15,405,636✔
169
      break;
1,230,116✔
170
  }
15,405,636✔
171
  if (count != 3)
1,230,122✔
172
    throw RecordTextException(string("unable to parse IP address, not enough dots"));
3✔
173
  if (!last_was_digit)
1,230,119✔
174
    throw RecordTextException(string("unable to parse IP address, trailing dot"));
3✔
175
  val<<=8;
1,230,116✔
176
  val+=octet;
1,230,116✔
177
  val=ntohl(val);
1,230,116✔
178
}
1,230,116✔
179

180

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

185
  skipSpaces();
6,278✔
186

187
  size_t len;
6,278✔
188
  // lookup end of value - think of ::ffff encoding too, has dots in it!
189
  for(len=0;
6,278✔
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,191✔
191
    len++);
99,913✔
192

193
  if(!len)
6,278!
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,278✔
198

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

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

205
  d_pos += len;
6,269✔
206
}
6,269✔
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,530,703✔
232
  return d_pos==d_end;
2,530,703✔
233
}
2,530,703✔
234

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

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

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

259
  string::size_type begin_pos = d_pos;
400,544✔
260
  while (d_pos < d_end) {
4,725,830✔
261
    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
4,681,466✔
262
      break;
356,177✔
263
    }
356,177✔
264

265
    d_pos++;
4,325,286✔
266
  }
4,325,286✔
267

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

273
  if (sval.empty()) {
400,544!
274
    sval = DNSName(d_zone);
×
275
  }
×
276
  else if (!d_zone.empty()) {
400,544✔
277
    sval += DNSName(d_zone);
391,658✔
278
  }
391,658✔
279
  val = std::move(sval);
400,544✔
280
}
400,544✔
281

282
static bool isbase64(char c, bool acceptspace)
283
{
23,924,382✔
284
  if(dns_isspace(c))
23,924,382✔
285
    return acceptspace;
175✔
286
  if(c >= '0' && c <= '9')
23,924,207✔
287
    return true;
3,582,334✔
288
  if(c >= 'a' && c <= 'z')
20,341,873✔
289
    return true;
9,662,784✔
290
  if(c >= 'A' && c <= 'Z')
10,679,089!
291
    return true;
9,446,194✔
292
  if(c=='+' || c=='/' || c=='=')
1,232,895✔
293
    return true;
1,232,925✔
294
  return false;
2,147,483,668✔
295
}
1,232,895✔
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,130✔
316
  skipSpaces();
314,130✔
317
  int pos=(int)d_pos;
314,130✔
318
  const char* strptr=d_string.c_str();
314,130✔
319
  while(d_pos < d_end && isbase64(strptr[d_pos], true))
24,237,577✔
320
    d_pos++;
23,923,447✔
321

322
  string tmp;
314,130✔
323
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
314,130✔
324
  boost::erase_all(tmp," ");
314,130✔
325
  val.clear();
314,130✔
326
  B64Decode(tmp, val);
314,130✔
327
}
314,130✔
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,694,768✔
544
  if(val >= '0' && val<='9')
1,694,769✔
545
    return val-'0';
323,924✔
546
  else if(val >= 'A' && val<='F')
1,370,845✔
547
    return 10+(val-'A');
9,166✔
548
  else if(val >= 'a' && val<='f')
1,361,679✔
549
    return 10+(val-'a');
1,361,679✔
550
  else
2,147,483,647✔
551
    throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val)+"'");
2,147,483,647✔
552
}
1,694,768✔
553

554

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

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

588
  HEXDecode(d_string.c_str()+pos, d_string.c_str() + d_pos, val);
329,515✔
589
}
329,515✔
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,800✔
604
  if(!d_string.empty())
261,800!
605
    d_string.append(1,' ');
261,800✔
606

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

610

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

616
  while(d_pos != d_end) {
17,980✔
617
    if(!val.empty())
12,224✔
618
      val.append(1, ' ');
1,326✔
619

620
    skipSpaces();
12,224✔
621
    if(d_string[d_pos]!='"') { // special case 'plenus' - without quotes
12,224✔
622
      string::size_type pos = d_pos;
55✔
623
      while(pos != d_end && isalnum(d_string[pos]))
1,922✔
624
        pos++;
1,867✔
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,169✔
635
    while(++d_pos < d_end && d_string[d_pos]!='"') {
415,298✔
636
      if(d_string[d_pos]=='\\' && d_pos+1!=d_end) {
403,129!
637
        val.append(1, d_string[d_pos++]);
2,020✔
638
      }
2,020✔
639
      val.append(1, d_string[d_pos]);
403,129✔
640
    }
403,129✔
641
    val.append(1,'"');
12,169✔
642
    if(d_pos == d_end)
12,169!
643
      throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string+"'");
×
644
    d_pos++;
12,169✔
645
    if(!multi)
12,169✔
646
      break;
5,089✔
647
  }
12,169✔
648
}
10,900✔
649

650
void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
651
{
21✔
652
  val.clear();
21✔
653
  val.reserve(d_end - d_pos);
21✔
654

655
  if(!val.empty())
21!
656
    val.append(1, ' ');
×
657

658
  skipSpaces();
21✔
659
  val.append(1, d_string[d_pos]);
21✔
660
  while(++d_pos < d_end && d_string[d_pos] != ' '){
105!
661
    val.append(1, d_string[d_pos]);
84✔
662
  }
84✔
663
}
21✔
664

665
void RecordTextReader::xfrType(uint16_t& val)
666
{
925,316✔
667
  skipSpaces();
925,316✔
668
  int pos=(int)d_pos;
925,316✔
669
  while(d_pos < d_end && !dns_isspace(d_string[d_pos]))
3,829,864✔
670
    d_pos++;
2,904,548✔
671

672
  string tmp;
925,316✔
673
  tmp.assign(d_string.c_str()+pos, d_string.c_str() + d_pos);
925,316✔
674

675
  val=DNSRecordContent::TypeToNumber(tmp);
925,316✔
676
}
925,316✔
677

678

679
void RecordTextReader::skipSpaces()
680
{
6,485,691✔
681
  const char* strptr = d_string.c_str();
6,485,691✔
682
  while(d_pos < d_end && dns_isspace(strptr[d_pos]))
11,019,865✔
683
    d_pos++;
4,534,174✔
684
  if(d_pos == d_end)
6,485,691!
685
    throw RecordTextException("missing field at the end of record content '"+d_string+"'");
×
686
}
6,485,691✔
687

688

689
RecordTextWriter::RecordTextWriter(string& str, bool noDot) : d_string(str)
690
{
2,666,976✔
691
  d_string.clear();
2,666,976✔
692
  d_nodot=noDot;
2,666,976✔
693
}
2,666,976✔
694

695
void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
696
{
21✔
697
  if(!d_string.empty()) {
21✔
698
    d_string.append(1,' ');
18✔
699
  }
18✔
700

701
  size_t ctr = 0;
21✔
702
  char tmp[5];
21✔
703
  for (auto const &c : val.content) {
168✔
704
    snprintf(tmp, sizeof(tmp), "%02X", c);
168✔
705
    d_string+=tmp;
168✔
706
    ctr++;
168✔
707
    if (ctr % 2 == 0 && ctr != 8) {
168✔
708
      d_string+=':';
63✔
709
    }
63✔
710
  }
168✔
711
}
21✔
712

713
void RecordTextWriter::xfr48BitInt(const uint64_t& val)
714
{
81✔
715
  if(!d_string.empty())
81!
716
    d_string.append(1,' ');
81✔
717
  d_string+=std::to_string(val);
81✔
718
}
81✔
719

720

721
void RecordTextWriter::xfr32BitInt(const uint32_t& val)
722
{
4,452,382✔
723
  if(!d_string.empty())
4,452,382✔
724
    d_string.append(1,' ');
4,162,671✔
725
  d_string+=std::to_string(val);
4,452,382✔
726
}
4,452,382✔
727

728
void RecordTextWriter::xfrType(const uint16_t& val)
729
{
873,524✔
730
  if(!d_string.empty())
873,524!
731
    d_string.append(1,' ');
×
732
  d_string+=DNSRecordContent::NumberToType(val);
873,524✔
733
}
873,524✔
734

735
// this function is on the fast path for the pdns_recursor
736
void RecordTextWriter::xfrIP(const uint32_t& val)
737
{
1,160,848✔
738
  if(!d_string.empty())
1,160,848✔
739
    d_string.append(1,' ');
18✔
740

741
  char tmp[17];
1,160,848✔
742
  uint32_t ip=val;
1,160,848✔
743
  uint8_t vals[4];
1,160,848✔
744

745
  memcpy(&vals[0], &ip, sizeof(ip));
1,160,848✔
746

747
  char *pos=tmp;
1,160,848✔
748

749
  for(int n=0; n < 4; ++n) {
5,804,240✔
750
    if(vals[n]<10) {
4,643,392✔
751
      *(pos++)=vals[n]+'0';
1,239,881✔
752
    } else if(vals[n] < 100) {
3,407,347✔
753
      *(pos++)=(vals[n]/10) +'0';
460,989✔
754
      *(pos++)=(vals[n]%10) +'0';
460,989✔
755
    } else {
2,942,522✔
756
      *(pos++)=(vals[n]/100) +'0';
2,942,522✔
757
      vals[n]%=100;
2,942,522✔
758
      *(pos++)=(vals[n]/10) +'0';
2,942,522✔
759
      *(pos++)=(vals[n]%10) +'0';
2,942,522✔
760
    }
2,942,522✔
761
    if(n!=3)
4,643,392✔
762
      *(pos++)='.';
3,482,544✔
763
  }
4,643,392✔
764
  *pos=0;
1,160,848✔
765
  d_string.append(tmp, pos);
1,160,848✔
766
}
1,160,848✔
767

768
void RecordTextWriter::xfrIP6(const std::string& val)
769
{
2,564✔
770
  char tmpbuf[16];
2,564✔
771
  char addrbuf[40];
2,564✔
772

773
  if(!d_string.empty())
2,564✔
774
   d_string.append(1,' ');
12✔
775

776
  val.copy(tmpbuf,16);
2,564✔
777

778
  if (inet_ntop(AF_INET6, tmpbuf, addrbuf, sizeof addrbuf) == nullptr)
2,564!
779
    throw RecordTextException("Unable to convert to ipv6 address");
×
780

781
  d_string += std::string(addrbuf);
2,564✔
782
}
2,564✔
783

784
void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
785
{
×
786
  string ip = val.toString();
×
787

788
  if(!d_string.empty())
×
789
    d_string.append(1,' ');
×
790

791
  d_string += ip;
×
792
}
×
793

794
void RecordTextWriter::xfrCAPort(ComboAddress &val)
795
{
×
796
  xfr16BitInt(val.sin4.sin_port);
×
797
}
×
798

799
void RecordTextWriter::xfrTime(const uint32_t& val)
800
{
1,747,048✔
801
  if(!d_string.empty())
1,747,048!
802
    d_string.append(1,' ');
1,747,048✔
803

804
  struct tm tm;
1,747,048✔
805
  time_t time=val; // Y2038 bug!
1,747,048✔
806
  gmtime_r(&time, &tm);
1,747,048✔
807

808
  static const boost::format fmt("%04d%02d%02d" "%02d%02d%02d");
1,747,048✔
809
  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,048✔
810
}
1,747,048✔
811

812

813
void RecordTextWriter::xfr16BitInt(const uint16_t& val)
814
{
1,173,901✔
815
  xfr32BitInt(val);
1,173,901✔
816
}
1,173,901✔
817

818
void RecordTextWriter::xfr8BitInt(const uint8_t& val)
819
{
2,291,078✔
820
  xfr32BitInt(val);
2,291,078✔
821
}
2,291,078✔
822

823
// should not mess with the escapes
824
void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */)
825
{
1,096,930✔
826
  if(!d_string.empty())
1,096,930✔
827
    d_string.append(1,' ');
913,680✔
828

829
  if(d_nodot) {
1,096,930✔
830
    d_string+=val.toStringRootDot();
721,167✔
831
  }
721,167✔
832
  else
375,763✔
833
  {
375,763✔
834
    d_string+=val.toString();
375,763✔
835
  }
375,763✔
836
}
1,096,930✔
837

838
void RecordTextWriter::xfrBlobNoSpaces(const string& val, int size)
839
{
131✔
840
  xfrBlob(val, size);
131✔
841
}
131✔
842

843
void RecordTextWriter::xfrBlob(const string& val, int)
844
{
900,885✔
845
  if(!d_string.empty())
900,885✔
846
    d_string.append(1,' ');
878,043✔
847

848
  d_string+=Base64Encode(val);
900,885✔
849
}
900,885✔
850

851
void RecordTextWriter::xfrHexBlob(const string& val, bool)
852
{
267,477✔
853
  if(!d_string.empty())
267,477!
854
    d_string.append(1,' ');
267,477✔
855

856
  if(val.empty()) {
267,477✔
857
    d_string.append(1,'-');
7✔
858
    return;
7✔
859
  }
7✔
860

861
  string::size_type limit=val.size();
267,470✔
862
  char tmp[5];
267,470✔
863
  for(string::size_type n = 0; n < limit; ++n) {
930,363✔
864
    snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)val[n]);
662,893✔
865
    d_string+=tmp;
662,893✔
866
  }
662,893✔
867
}
267,470✔
868

869
void RecordTextWriter::xfrSVCBValueList(const vector<string> &val) {
1,137✔
870
  bool shouldQuote{false};
1,137✔
871
  vector<string> escaped;
1,137✔
872
  escaped.reserve(val.size());
1,137✔
873
  for (auto const &v : val) {
1,417✔
874
    if (v.find_first_of(' ') != string::npos) {
1,417✔
875
      shouldQuote = true;
6✔
876
    }
6✔
877
    string tmp = txtEscape(v);
1,417✔
878
    string unescaped;
1,417✔
879
    unescaped.reserve(tmp.size() + 4);
1,417✔
880
    for (auto const &ch : tmp) {
3,044✔
881
      if (ch == '\\') {
3,044✔
882
        unescaped += R"F(\\)F";
36✔
883
        continue;
36✔
884
      }
36✔
885
      if (ch == ',') {
3,008✔
886
        unescaped += R"F(\\,)F";
30✔
887
        continue;
30✔
888
      }
30✔
889
      unescaped += ch;
2,978✔
890
    }
2,978✔
891
    escaped.push_back(std::move(unescaped));
1,417✔
892
  }
1,417✔
893
  if (shouldQuote) {
1,137✔
894
    d_string.append(1, '"');
6✔
895
  }
6✔
896
  d_string.append(boost::join(escaped, ","));
1,137✔
897
  if (shouldQuote) {
1,137✔
898
    d_string.append(1, '"');
6✔
899
  }
6✔
900
}
1,137✔
901

902
void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
1,281✔
903
  for (auto const &param : val) {
1,948✔
904
    if (!d_string.empty())
1,948✔
905
      d_string.append(1, ' ');
1,903✔
906

907
    d_string.append(SvcParam::keyToString(param.getKey()));
1,948✔
908
    if (param.getKey() != SvcParam::no_default_alpn) {
1,948✔
909
      d_string.append(1, '=');
1,939✔
910
    }
1,939✔
911

912
    switch (param.getKey())
1,948✔
913
    {
1,948✔
914
    case SvcParam::no_default_alpn:
9✔
915
      break;
9✔
916
    case SvcParam::ipv4hint: /* fall-through */
53✔
917
    case SvcParam::ipv6hint:
80✔
918
      // TODO use xfrCA and put commas in between?
919
      if (param.getAutoHint()) {
80!
920
        d_string.append("auto");
×
921
        break;
×
922
      }
×
923
      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
80✔
924
      break;
80✔
925
    case SvcParam::alpn:
1,137✔
926
      xfrSVCBValueList(param.getALPN());
1,137✔
927
      break;
1,137✔
928
    case SvcParam::mandatory:
53✔
929
    {
53✔
930
      bool doComma = false;
53✔
931
      for (auto const &k: param.getMandatory()) {
62✔
932
        if (doComma)
62✔
933
          d_string.append(1, ',');
9✔
934
        d_string.append(SvcParam::keyToString(k));
62✔
935
        doComma = true;
62✔
936
      }
62✔
937
      break;
53✔
938
    }
80✔
939
    case SvcParam::port: {
589✔
940
      auto str = d_string;
589✔
941
      d_string.clear();
589✔
942
      xfr16BitInt(param.getPort());
589✔
943
      d_string = str + d_string;
589✔
944
      break;
589✔
945
    }
80✔
946
    case SvcParam::ech: {
32✔
947
      auto str = d_string;
32✔
948
      d_string.clear();
32✔
949
      xfrBlobNoSpaces(param.getECH());
32✔
950
      d_string = str + '"' + d_string + '"';
32✔
951
      break;
32✔
952
    }
80✔
953
    default:
48✔
954
      auto str = d_string;
48✔
955
      d_string.clear();
48✔
956
      xfrText(param.getValue(), false, false);
48✔
957
      d_string = str + '"' + txtEscape(d_string) + '"';
48✔
958
      break;
48✔
959
    }
1,948✔
960
  }
1,948✔
961
}
1,281✔
962

963
void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
964
{
144,349✔
965
  if(!d_string.empty())
144,349✔
966
    d_string.append(1,' ');
12,170✔
967

968
  d_string.append(val);
144,349✔
969
}
144,349✔
970

971
void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
972
{
42✔
973
  if(!d_string.empty())
42!
974
    d_string.append(1,' ');
42✔
975
  d_string.append(val);
42✔
976
}
42✔
977

978
#ifdef TESTING
979

980
int main(int argc, char**argv)
981
try
982
{
983
  RecordTextReader rtr(argv[1], argv[2]);
984

985
  unsigned int order, pref;
986
  string flags, services, regexp, replacement;
987
  string mx;
988

989
  rtr.xfrInt(order);
990
  rtr.xfrInt(pref);
991
  rtr.xfrText(flags);
992
  rtr.xfrText(services);
993
  rtr.xfrText(regexp);
994
  rtr.xfrName(replacement);
995

996
  cout<<"order: "<<order<<", pref: "<<pref<<"\n";
997
  cout<<"flags: \""<<flags<<"\", services: \""<<services<<"\", regexp: \""<<regexp<<"\", replacement: "<<replacement<<"\n";
998

999
  string out;
1000
  RecordTextWriter rtw(out);
1001

1002
  rtw.xfrInt(order);
1003
  rtw.xfrInt(pref);
1004
  rtw.xfrText(flags);
1005
  rtw.xfrText(services);
1006
  rtw.xfrText(regexp);
1007
  rtw.xfrName(replacement);
1008

1009
  cout<<"Regenerated: '"<<out<<"'\n";
1010

1011
}
1012
catch(std::exception& e)
1013
{
1014
  cerr<<"Fatal: "<<e.what()<<endl;
1015
}
1016

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