Coverage Report

Created: 2026-08-13 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsname.hh
Line
Count
Source
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#pragma once
23
#include <array>
24
#include <cstring>
25
#include <optional>
26
#include <string>
27
#include <utility>
28
#include <vector>
29
#include <set>
30
#include <strings.h>
31
#include <stdexcept>
32
#include <sstream>
33
#include <iterator>
34
#include <unordered_set>
35
#include <string_view>
36
37
using namespace std::string_view_literals;
38
39
#include <boost/version.hpp>
40
#include <boost/container/string.hpp>
41
42
inline bool dns_isspace(char chr) __attribute__((const));
43
inline bool dns_isspace(char chr)
44
{
45
  return chr == ' ' || chr == '\t' || chr == '\r' || chr == '\n';
46
}
47
48
extern const unsigned char dns_toupper_table[256], dns_tolower_table[256];
49
50
inline unsigned char dns_toupper(unsigned char chr) __attribute__((pure));
51
inline unsigned char dns_toupper(unsigned char chr)
52
964M
{
53
964M
  return dns_toupper_table[chr];
54
964M
}
55
56
inline unsigned char dns_tolower(unsigned char chr) __attribute__((pure));
57
inline unsigned char dns_tolower(unsigned char chr)
58
5.13M
{
59
5.13M
  return dns_tolower_table[chr];
60
5.13M
}
61
62
inline int pdns_ilexicographical_compare_three_way(std::string_view a, std::string_view b)  __attribute__((pure));
63
inline int pdns_ilexicographical_compare_three_way(const std::string_view a, const std::string_view b)
64
277k
{
65
277k
  const unsigned char *aPtr = (const unsigned char*)a.data(), *bPtr = (const unsigned char*)b.data();
66
277k
  const unsigned char *aEptr = aPtr + a.length(), *bEptr = bPtr + b.length();
67
1.00M
  while(aPtr != aEptr && bPtr != bEptr) {
68
763k
    if (*aPtr != *bPtr) {
69
478k
      if (int rc = dns_tolower(*aPtr) - dns_tolower(*bPtr); rc != 0) {
70
36.8k
        return rc;
71
36.8k
      }
72
478k
    }
73
726k
    aPtr++;
74
726k
    bPtr++;
75
726k
  }
76
  // At this point, one of the strings has been completely processed.
77
  // Either both have the same length, and they are equal, or one of them
78
  // is larger, and compares as higher.
79
240k
  if (aPtr == aEptr) {
80
240k
    if (bPtr != bEptr) {
81
0
      return -1; // a < b
82
0
    }
83
240k
  }
84
0
  else {
85
0
    return 1; // a > b
86
0
  }
87
240k
  return 0; // a == b
88
240k
}
89
90
inline bool pdns_ilexicographical_compare(const std::string& a, const std::string& b)  __attribute__((pure));
91
inline bool pdns_ilexicographical_compare(const std::string& a, const std::string& b)
92
0
{
93
0
  return pdns_ilexicographical_compare_three_way(a, b) < 0;
94
0
}
95
96
inline bool pdns_iequals(const std::string& a, const std::string& b) __attribute__((pure));
97
inline bool pdns_iequals(const std::string& a, const std::string& b)
98
576k
{
99
576k
  if (a.length() != b.length())
100
309k
    return false;
101
102
266k
  return pdns_ilexicographical_compare_three_way(a, b) == 0;
103
576k
}
104
105
inline bool pdns_iequals_ch(const char a, const char b) __attribute__((pure));
106
inline bool pdns_iequals_ch(const char a, const char b)
107
0
{
108
0
  if ((a != b) && (dns_tolower(a) != dns_tolower(b)))
109
0
    return false;
110
0
111
0
  return true;
112
0
}
113
114
#include "burtle.hh"
115
#include "views.hh"
116
117
/* Quest in life:
118
     accept escaped ascii presentations of DNS names and store them "natively"
119
     accept a DNS packet with an offset, and extract a DNS name from it
120
     build up DNSNames with prepend and append of 'raw' unescaped labels
121
122
   Be able to turn them into ASCII and "DNS name in a packet" again on request
123
124
   Provide some common operators for comparison, detection of being part of another domain
125
126
   NOTE: For now, everything MUST be . terminated, otherwise it is an error
127
*/
128
129
// DNSName: represents a case-insensitive string, allowing for non-printable
130
// characters. It is used for all kinds of name (of hosts, domains, keys,
131
// algorithm...) overall the PowerDNS codebase.
132
//
133
// The following type traits are provided:
134
// - EqualityComparable
135
// - LessThanComparable
136
// - Hash
137
#if defined(PDNS_AUTH)
138
class ZoneName;
139
#endif
140
class DNSName
141
{
142
public:
143
  static constexpr size_t s_maxDNSNameLength = 255;
144
  static constexpr size_t s_maxDNSLabelLength = 63;
145
146
418k
  DNSName() = default; //!< Constructs an *empty* DNSName, NOT the root!
147
  // Work around assertion in some boost versions that do not like self-assignment of boost::container::string
148
  DNSName& operator=(const DNSName& rhs)
149
221k
  {
150
221k
    if (this != &rhs) {
151
221k
      d_storage = rhs.d_storage;
152
221k
    }
153
221k
    return *this;
154
221k
  }
155
  DNSName& operator=(DNSName&& rhs) noexcept
156
612k
  {
157
612k
    if (this != &rhs) {
158
612k
      d_storage = std::move(rhs.d_storage);
159
612k
    }
160
612k
    return *this;
161
612k
  }
162
357k
  DNSName(const DNSName& a) = default;
163
237k
  DNSName(DNSName&& a) = default;
164
165
  explicit DNSName(std::string_view sw); //!< Constructs from a human formatted, escaped presentation
166
  DNSName(const char* p, size_t len, size_t offset, bool uncompress, uint16_t* qtype = nullptr, uint16_t* qclass = nullptr, unsigned int* consumed = nullptr, uint16_t minOffset = 0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
167
168
  bool isPartOf(const DNSName& rhs) const;   //!< Are we part of the rhs name? Note that name.isPartOf(name).
169
  inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
170
0
  bool operator!=(const DNSName& other) const { return !(*this == other); }
171
  // !< DNS-native (case insensitive) comparison against raw data in (uncompressed) wire format. The view has to start with the DNS name, but does not have to contain only a DNS name. Roughly, passing a view of a DNS packet starting just after the DNS header is OK, everything else is not because any names present later in the packet might be compressed.
172
  bool matchesUncompressedName(const std::string_view& wire_uncompressed) const;
173
174
  std::string toString(const std::string& separator=".", const bool trailing=true) const;              //!< Our human-friendly, escaped, representation
175
  void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const;
176
  std::string toLogString() const; //!< like plain toString, but returns (empty) on empty names
177
0
  std::string toStringNoDot() const { return toString(".", false); }
178
38.5k
  std::string toStringRootDot() const { if(isRoot()) return "."; else return toString(".", false); }
179
  std::string toDNSString() const;           //!< Our representation in DNS native format
180
  std::string toDNSStringLC() const;           //!< Our representation in DNS native format, lower cased
181
  void appendRawLabel(const std::string& str); //!< Append this unescaped label
182
  void appendRawLabel(const char* start, unsigned int length); //!< Append this unescaped label
183
  void prependRawLabel(const std::string& str); //!< Prepend this unescaped label
184
  std::vector<std::string> getRawLabels() const; //!< Individual raw unescaped labels
185
  std::string getRawLabel(unsigned int pos) const; //!< Get the specified raw unescaped label
186
  DNSName getLastLabel() const; //!< Get the DNSName of the last label
187
  bool chopOff();                               //!< Turn www.powerdns.com. into powerdns.com., returns false for .
188
  DNSName makeRelative(const DNSName& zone) const;
189
  DNSName makeLowerCase() const
190
0
  {
191
0
    DNSName ret(*this);
192
0
    ret.makeUsLowerCase();
193
0
    return ret;
194
0
  }
195
  void makeUsLowerCase()
196
0
  {
197
0
    for(auto & c : d_storage) {
198
0
      c=dns_tolower(c);
199
0
    }
200
0
  }
201
  void makeUsRelative(const DNSName& zone);
202
  DNSName getCommonLabels(const DNSName& other) const; //!< Return the list of common labels from the top, for example 'c.d' for 'a.b.c.d' and 'x.y.c.d'
203
  DNSName labelReverse() const;
204
  bool isWildcard() const;
205
  bool isHostname(bool allowUnderscore = false) const;
206
  unsigned int countLabels() const;
207
  size_t wirelength() const; //!< Number of total bytes in the name
208
272k
  bool empty() const { return d_storage.empty(); }
209
124k
  bool isRoot() const { return d_storage.size()==1 && d_storage[0]==0; }
210
0
  bool hasLabels() const { return !empty() && !isRoot(); }
211
0
  void clear() { d_storage.clear(); }
212
  void trimToLabels(unsigned int);
213
  size_t hash(size_t init=0) const
214
0
  {
215
0
    return burtleCI(d_storage, init);
216
0
  }
217
  DNSName& operator+=(const DNSName& rhs)
218
0
  {
219
0
    if(d_storage.size() + rhs.d_storage.size() > s_maxDNSNameLength + 1) // one extra byte for the second root label
220
0
      throwSafeRangeError("resulting name too long", rhs.d_storage.data(), rhs.d_storage.size());
221
0
    if(rhs.empty())
222
0
      return *this;
223
0
224
0
    if(d_storage.empty())
225
0
      d_storage+=rhs.d_storage;
226
0
    else
227
0
      d_storage.replace(d_storage.length()-1, rhs.d_storage.length(), rhs.d_storage);
228
0
229
0
    return *this;
230
0
  }
231
232
  bool operator<(const DNSName& rhs)  const // this delivers _some_ kind of ordering, but not one useful in a DNS context. Really fast though.
233
0
  {
234
0
    struct DNSNameCompare
235
0
    {
236
0
      bool operator()(const unsigned char& lhs, const unsigned char& rhs) const
237
0
      {
238
0
        return dns_tolower(lhs) < dns_tolower(rhs);
239
0
      }
240
0
    };
241
242
    // note that this is case insensitive, including on the label lengths
243
0
    return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(),
244
0
             rhs.d_storage.rbegin(), rhs.d_storage.rend(), DNSNameCompare());
245
0
  }
246
247
  int slowCanonCompare_three_way(const DNSName& rhs) const;
248
  int canonCompare_three_way(const DNSName& rhs, bool pretty = false) const;
249
0
  inline bool canonCompare(const DNSName& rhs, bool pretty = false) const { return canonCompare_three_way(rhs, pretty) < 0; }
250
251
  typedef boost::container::string string_t;
252
253
30.0k
  const string_t& getStorage() const {
254
30.0k
    return d_storage;
255
30.0k
  }
256
257
  [[nodiscard]] size_t sizeEstimate() const
258
0
  {
259
0
    return d_storage.size(); // knowingly overestimating small strings as most string
260
                             // implementations have internal capacity and we always include
261
                             // sizeof(*this)
262
0
  }
263
264
  bool has8bitBytes() const; /* returns true if at least one byte of the labels forming the name is not included in [A-Za-z0-9_*./@ \\:-] */
265
266
  class RawLabelsVisitor
267
  {
268
  public:
269
    /* Zero-copy, zero-allocation raw labels visitor.
270
       The general idea is that we walk the labels in the constructor,
271
       filling up our array of labels position and setting the initial
272
       value of d_position at the number of labels.
273
       We then can easily provide string_view into the first and last label.
274
       pop_back() moves d_position one label closer to the start, so we
275
       can also easily walk back the labels in reverse order.
276
       There is no copy because we use a reference into the DNSName storage,
277
       so it is absolutely forbidden to alter the DNSName for as long as we
278
       exist, and no allocation because we use a static array (there cannot
279
       be more than 128 labels in a DNSName).
280
    */
281
    RawLabelsVisitor(const string_t& storage);
282
    std::string_view front() const;
283
    std::string_view back() const;
284
    bool pop_back();
285
    bool empty() const;
286
  private:
287
    std::array<uint8_t, 128> d_labelPositions;
288
    const string_t& d_storage;
289
    size_t d_position{0};
290
  };
291
  RawLabelsVisitor getRawLabelsVisitor() const;
292
293
#if defined(PDNS_AUTH) // [
294
  // Sugar while ZoneName::operator DNSName are made explicit
295
  bool isPartOf(const ZoneName& rhs) const;
296
  DNSName makeRelative(const ZoneName& zone) const;
297
  void makeUsRelative(const ZoneName& zone);
298
#endif // ]
299
300
private:
301
  string_t d_storage;
302
303
  void packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
304
  size_t parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t position, bool uncompress);
305
  static void appendEscapedLabel(std::string& appendTo, const char* orig, size_t len);
306
  static std::string unescapeLabel(const std::string& orig);
307
  static void throwSafeRangeError(const std::string& msg, const char* buf, size_t length);
308
};
309
310
size_t hash_value(DNSName const& d);
311
312
struct CanonDNSNameCompare
313
{
314
  bool operator()(const DNSName&a, const DNSName& b) const
315
0
  {
316
0
    return a.canonCompare(b);
317
0
  }
318
};
319
320
inline DNSName operator+(const DNSName& lhs, const DNSName& rhs)
321
0
{
322
0
  DNSName ret=lhs;
323
0
  ret += rhs;
324
0
  return ret;
325
0
}
326
327
extern const DNSName g_rootdnsname;          // .
328
extern const DNSName g_wildcarddnsname;      // *
329
330
extern const DNSName g_coodnsname;           // coo
331
extern const DNSName g_groupdnsname;         // group
332
extern const DNSName g_versiondnsname;       // version
333
extern const DNSName g_zonesdnsname;         // zones
334
335
extern const DNSName g_gsstsigdnsname;       // gss-tsig
336
extern const DNSName g_hmacmd5dnsname;       // hmac-md5
337
extern const DNSName g_hmacmd5dnsname_long;  // hmac-md5.sig-alg.reg.int
338
extern const DNSName g_hmacsha1dnsname;      // hmac-sha1
339
extern const DNSName g_hmacsha224dnsname;    // hmac-sha224
340
extern const DNSName g_hmacsha256dnsname;    // hmac-sha256
341
extern const DNSName g_hmacsha384dnsname;    // hmac-sha384
342
extern const DNSName g_hmacsha512dnsname;    // hmac-sha512
343
344
#if defined(PDNS_AUTH) // [
345
// ZoneName: this is equivalent to DNSName, but intended to only store zone
346
// names. In addition to the name, an optional variant is allowed. The
347
// variant is never part of a DNS packet; it can only be used by backends to
348
// perform specific extra processing.
349
// Variant names are limited to [a-z0-9_-].
350
// Conversions between DNSName and ZoneName are allowed, but must be explicit;
351
// conversions to DNSName lose the variant part.
352
class ZoneName
353
{
354
public:
355
  ZoneName() = default; //!< Constructs an *empty* ZoneName, NOT the root!
356
  // Work around assertion in some boost versions that do not like self-assignment of boost::container::string
357
  ZoneName& operator=(const ZoneName& rhs)
358
  {
359
    if (this != &rhs) {
360
      d_name = rhs.d_name;
361
      d_variant = rhs.d_variant;
362
    }
363
    return *this;
364
  }
365
  ZoneName& operator=(ZoneName&& rhs) noexcept
366
  {
367
    if (this != &rhs) {
368
      d_name = std::move(rhs.d_name);
369
      d_variant = std::move(rhs.d_variant);
370
    }
371
    return *this;
372
  }
373
  ZoneName(const ZoneName& a) = default;
374
  ZoneName(ZoneName&& a) = default;
375
376
  explicit ZoneName(std::string_view name);
377
  explicit ZoneName(std::string_view name, std::string_view variant) : d_name(name), d_variant(variant) {}
378
  explicit ZoneName(const DNSName& name, std::string_view variant = ""sv) : d_name(name), d_variant(variant) {}
379
  explicit ZoneName(std::string_view name, std::string_view::size_type sep);
380
381
  bool isPartOf(const ZoneName& rhs) const { return d_name.isPartOf(rhs.d_name); }
382
  bool isPartOf(const DNSName& rhs) const { return d_name.isPartOf(rhs); }
383
  bool operator==(const ZoneName& rhs) const { return d_name == rhs.d_name && d_variant == rhs.d_variant; }
384
  bool operator!=(const ZoneName& rhs) const { return !operator==(rhs); }
385
386
  std::string toString(const std::string& separator=".", const bool trailing=true) const;
387
  void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const { output = toString(separator, trailing); }
388
  std::string toLogString() const;
389
  std::string toStringNoDot() const;
390
  std::string toStringRootDot() const;
391
392
  bool chopOff() { return d_name.chopOff(); }
393
  ZoneName makeLowerCase() const
394
  {
395
    ZoneName ret(*this);
396
    ret.d_name.makeUsLowerCase();
397
    return ret;
398
  }
399
  void makeUsLowerCase() { d_name.makeUsLowerCase(); }
400
  bool empty() const { return d_name.empty(); }
401
  void clear() { d_name.clear(); d_variant.clear(); }
402
  void trimToLabels(unsigned int trim) { d_name.trimToLabels(trim); }
403
  size_t hash(size_t init=0) const;
404
405
  bool operator<(const ZoneName& rhs)  const;
406
407
  int canonCompare_three_way(const ZoneName& rhs) const;
408
  inline bool canonCompare(const ZoneName& rhs) const { return canonCompare_three_way(rhs) < 0; }
409
410
  // Conversion from ZoneName to DNSName
411
  explicit operator const DNSName&() const { return d_name; }
412
  explicit operator DNSName&() { return d_name; }
413
414
  bool hasVariant() const { return !d_variant.empty(); }
415
  std::string getVariant() const { return d_variant; }
416
  void setVariant(std::string_view);
417
418
  // Search for a variant separator: mandatory (when variants are used) trailing
419
  // dot followed by another dot and the variant name, and return the length of
420
  // the zone name without its variant part, or npos if there is no variant
421
  // present.
422
  static std::string_view::size_type findVariantSeparator(std::string_view name);
423
424
private:
425
  DNSName d_name;
426
  std::string d_variant{};
427
};
428
429
size_t hash_value(ZoneName const& zone);
430
431
std::ostream & operator<<(std::ostream &ostr, const ZoneName& zone);
432
namespace std {
433
    template <>
434
    struct hash<ZoneName> {
435
        size_t operator () (const ZoneName& dn) const { return dn.hash(0); }
436
    };
437
}
438
439
struct CanonZoneNameCompare
440
{
441
  bool operator()(const ZoneName& a, const ZoneName& b) const
442
  {
443
    return a.canonCompare(b);
444
  }
445
};
446
#else // ] [
447
using ZoneName = DNSName;
448
using CanonZoneNameCompare = CanonDNSNameCompare;
449
#endif // ]
450
451
extern const ZoneName g_rootzonename;
452
453
template<typename T>
454
struct SuffixMatchTree
455
{
456
  SuffixMatchTree(std::string name = "", bool endNode_ = false) :
457
    d_name(std::move(name)), endNode(endNode_)
458
  {}
459
460
  SuffixMatchTree(const SuffixMatchTree& rhs): d_name(rhs.d_name), children(rhs.children), endNode(rhs.endNode)
461
  {
462
    if (endNode) {
463
      d_value = rhs.d_value;
464
    }
465
  }
466
  SuffixMatchTree & operator=(const SuffixMatchTree &rhs)
467
  {
468
    d_name = rhs.d_name;
469
    children = rhs.children;
470
    endNode = rhs.endNode;
471
    if (endNode) {
472
      d_value = rhs.d_value;
473
    }
474
    return *this;
475
  }
476
  bool operator<(const SuffixMatchTree& rhs) const
477
0
  {
478
0
    return pdns_ilexicographical_compare(d_name, rhs.d_name);
479
0
  }
480
481
  std::string d_name;
482
  mutable std::set<SuffixMatchTree, std::less<>> children;
483
  mutable bool endNode;
484
  mutable T d_value{};
485
486
  /* this structure is used to do a lookup without allocating and
487
     copying a string, using C++14's heterogeneous lookups in ordered
488
     containers */
489
  struct LightKey
490
  {
491
    std::string_view d_name;
492
    bool operator<(const SuffixMatchTree& smt) const
493
0
    {
494
0
      // This whole logic unfortunately can't be rewritten as
495
0
      // pdns_ilexicographical_compare_three_way(this->d_name, smt.d_name)
496
0
      // for, when the strings differ in length, the last return statement
497
0
      // in the code below returns the opposite value.
498
0
      auto compareUpTo = std::min(this->d_name.size(), smt.d_name.size());
499
0
      auto this_name = std::string_view(this->d_name.data(), compareUpTo);
500
0
      auto smt_name = std::string_view(smt.d_name.data(), compareUpTo);
501
0
      auto ret = pdns_ilexicographical_compare_three_way(this_name, smt_name);
502
0
      if (ret != 0) {
503
0
        return ret < 0;
504
0
      }
505
0
      if (this->d_name.size() == smt.d_name.size()) {
506
0
        return 0;
507
0
      }
508
0
      return this->d_name.size() < smt.d_name.size();
509
0
    }
510
  };
511
512
  bool operator<(const LightKey& lk) const
513
0
  {
514
0
    // This whole logic unfortunately can't be rewritten as
515
0
    // pdns_ilexicographical_compare_three_way(this->d_name, lk.d_name)
516
0
    // for, when the strings differ in length, the last return statement
517
0
    // in the code below returns the opposite value.
518
0
    auto compareUpTo = std::min(this->d_name.size(), lk.d_name.size());
519
0
    auto this_name = std::string_view(this->d_name.data(), compareUpTo);
520
0
    auto lk_name = std::string_view(lk.d_name.data(), compareUpTo);
521
0
    auto ret = pdns_ilexicographical_compare_three_way(this_name, lk_name);
522
0
    if (ret != 0) {
523
0
      return ret < 0;
524
0
    }
525
0
    if (this->d_name.size() == lk.d_name.size()) {
526
0
      return 0;
527
0
    }
528
0
    return this->d_name.size() < lk.d_name.size();
529
0
  }
530
531
  template<typename V>
532
  void visit(const V& v) const {
533
    for(const auto& c : children) {
534
      c.visit(v);
535
    }
536
537
    if (endNode) {
538
      v(*this);
539
    }
540
  }
541
542
  void add(const DNSName& name, T&& t)
543
0
  {
544
0
    auto labels = name.getRawLabels();
545
0
    add(labels, std::move(t));
546
0
  }
547
548
  void add(std::vector<std::string>& labels, T&& value) const
549
0
  {
550
0
    if (labels.empty()) { // this allows insertion of the root
551
0
      endNode = true;
552
0
      d_value = std::move(value);
553
0
    }
554
0
    else if(labels.size()==1) {
555
0
      auto res = children.emplace(*labels.begin(), true);
556
0
      if (!res.second) {
557
0
        // we might already have had the node as an
558
0
        // intermediary one, but it's now an end node
559
0
        if (!res.first->endNode) {
560
0
          res.first->endNode = true;
561
0
        }
562
0
      }
563
0
      res.first->d_value = std::move(value);
564
0
    }
565
0
    else {
566
0
      auto res = children.emplace(*labels.rbegin(), false);
567
0
      labels.pop_back();
568
0
      res.first->add(labels, std::move(value));
569
0
    }
570
0
  }
571
572
  void remove(const DNSName &name, bool subtree=false) const
573
0
  {
574
0
    auto labels = name.getRawLabels();
575
0
    remove(labels, subtree);
576
0
  }
577
578
  /* Removes the node at `labels`, also make sure that no empty
579
   * children will be left behind in memory
580
   */
581
  void remove(std::vector<std::string>& labels, bool subtree = false) const
582
0
  {
583
0
    if (labels.empty()) { // this allows removal of the root
584
0
      endNode = false;
585
0
      if (subtree) {
586
0
        children.clear();
587
0
      }
588
0
      return;
589
0
    }
590
0
591
0
    SuffixMatchTree smt(*labels.rbegin());
592
0
    auto child = children.find(smt);
593
0
    if (child == children.end()) {
594
0
      // No subnode found, we're done
595
0
      return;
596
0
    }
597
0
598
0
    // We have found a child
599
0
    labels.pop_back();
600
0
    if (labels.empty()) {
601
0
      // The child is no longer an endnode
602
0
      child->endNode = false;
603
0
604
0
      if (subtree) {
605
0
        child->children.clear();
606
0
      }
607
0
608
0
      // If the child has no further children, just remove it from the set.
609
0
      if (child->children.empty()) {
610
0
        children.erase(child);
611
0
      }
612
0
      return;
613
0
    }
614
0
615
0
    // We are not at the end, let the child figure out what to do
616
0
    child->remove(labels);
617
0
  }
618
619
  T* lookup(const DNSName& name) const
620
0
  {
621
0
    auto bestNode = getBestNode(name);
622
0
    if (bestNode) {
623
0
      return &bestNode->d_value;
624
0
    }
625
0
    return nullptr;
626
0
  }
627
628
  std::optional<DNSName> getBestMatch(const DNSName& name) const
629
0
  {
630
0
    if (children.empty()) { // speed up empty set
631
0
      return endNode ? std::optional<DNSName>(g_rootdnsname) : std::nullopt;
632
0
    }
633
0
634
0
    auto visitor = name.getRawLabelsVisitor();
635
0
    return getBestMatch(visitor);
636
0
  }
637
638
  // Returns all end-nodes, fully qualified (not as separate labels)
639
  std::vector<DNSName> getNodes() const {
640
    std::vector<DNSName> ret;
641
    if (endNode) {
642
      ret.push_back(DNSName(d_name));
643
    }
644
    for (const auto& child : children) {
645
      auto nodes = child.getNodes();
646
      ret.reserve(ret.size() + nodes.size());
647
      for (const auto &node: nodes) {
648
        ret.push_back(node + DNSName(d_name));
649
      }
650
    }
651
    return ret;
652
  }
653
654
private:
655
  const SuffixMatchTree* getBestNode(const DNSName& name)  const
656
0
  {
657
0
    if (children.empty()) { // speed up empty set
658
0
      if (endNode) {
659
0
        return this;
660
0
      }
661
0
      return nullptr;
662
0
    }
663
0
664
0
    auto visitor = name.getRawLabelsVisitor();
665
0
    return getBestNode(visitor);
666
0
  }
667
668
  const SuffixMatchTree* getBestNode(DNSName::RawLabelsVisitor& visitor) const
669
0
  {
670
0
    if (visitor.empty()) { // optimization
671
0
      if (endNode) {
672
0
        return this;
673
0
      }
674
0
      return nullptr;
675
0
    }
676
0
677
0
    const LightKey lk{visitor.back()};
678
0
    auto child = children.find(lk);
679
0
    if (child == children.end()) {
680
0
      if (endNode) {
681
0
        return this;
682
0
      }
683
0
      return nullptr;
684
0
    }
685
0
    visitor.pop_back();
686
0
    auto result = child->getBestNode(visitor);
687
0
    if (result) {
688
0
      return result;
689
0
    }
690
0
    return endNode ? this : nullptr;
691
0
  }
692
693
  std::optional<DNSName> getBestMatch(DNSName::RawLabelsVisitor& visitor) const
694
0
  {
695
0
    if (visitor.empty()) { // optimization
696
0
      if (endNode) {
697
0
        return std::optional<DNSName>(d_name);
698
0
      }
699
0
      return std::nullopt;
700
0
    }
701
0
702
0
    const LightKey lk{visitor.back()};
703
0
    auto child = children.find(lk);
704
0
    if (child == children.end()) {
705
0
      if (endNode) {
706
0
        return std::optional<DNSName>(d_name);
707
0
      }
708
0
      return std::nullopt;
709
0
    }
710
0
    visitor.pop_back();
711
0
    auto result = child->getBestMatch(visitor);
712
0
    if (result) {
713
0
      if (!d_name.empty()) {
714
0
        result->appendRawLabel(d_name);
715
0
      }
716
0
      return result;
717
0
    }
718
0
    return endNode ? std::optional<DNSName>(d_name) : std::nullopt;
719
0
  }
720
};
721
722
/* Quest in life: serve as a rapid block list. If you add a DNSName to a root SuffixMatchNode,
723
   anything part of that domain will return 'true' in check */
724
struct SuffixMatchNode
725
{
726
  public:
727
    SuffixMatchNode() = default;
728
    SuffixMatchTree<bool> d_tree;
729
730
    void add(const DNSName& dnsname)
731
0
    {
732
0
      d_tree.add(dnsname, true);
733
0
      d_nodes.insert(dnsname);
734
0
    }
735
736
    void add(const std::string& name)
737
0
    {
738
0
      add(DNSName(name));
739
0
    }
740
741
    void add(std::vector<std::string> labels)
742
0
    {
743
0
      d_tree.add(labels, true);
744
0
      DNSName tmp;
745
0
      while (!labels.empty()) {
746
0
        tmp.appendRawLabel(labels.back());
747
0
        labels.pop_back(); // This is safe because we have a copy of labels
748
0
      }
749
0
      d_nodes.insert(tmp);
750
0
    }
751
752
    void remove(const DNSName& name)
753
0
    {
754
0
      d_tree.remove(name);
755
0
      d_nodes.erase(name);
756
0
    }
757
758
    void remove(std::vector<std::string> labels)
759
0
    {
760
0
      d_tree.remove(labels);
761
0
      DNSName tmp;
762
0
      while (!labels.empty()) {
763
0
        tmp.appendRawLabel(labels.back());
764
0
        labels.pop_back(); // This is safe because we have a copy of labels
765
0
      }
766
0
      d_nodes.erase(tmp);
767
0
    }
768
769
    bool check(const DNSName& dnsname) const
770
0
    {
771
0
      return d_tree.lookup(dnsname) != nullptr;
772
0
    }
773
774
    std::optional<DNSName> getBestMatch(const DNSName& name) const
775
0
    {
776
0
      return d_tree.getBestMatch(name);
777
0
    }
778
779
    std::string toString() const
780
0
    {
781
0
      std::string ret;
782
0
      bool first = true;
783
0
      for (const auto& n : d_nodes) {
784
0
        if (!first) {
785
0
          ret += ", ";
786
0
        }
787
0
        first = false;
788
0
        ret += n.toString();
789
0
      }
790
0
      return ret;
791
0
    }
792
793
  std::vector<DNSName> toVector() const
794
0
    {
795
0
      std::vector<DNSName> ret;
796
0
      ret.reserve(d_nodes.size());
797
0
      for (const auto& n : d_nodes) {
798
0
        ret.emplace_back(n);
799
0
      }
800
0
      return ret;
801
0
    }
802
803
  private:
804
    mutable std::set<DNSName> d_nodes; // Only used for string generation
805
};
806
807
std::ostream & operator<<(std::ostream &os, const DNSName& d);
808
namespace std {
809
    template <>
810
    struct hash<DNSName> {
811
0
        size_t operator () (const DNSName& dn) const { return dn.hash(0); }
812
    };
813
}
814
815
DNSName::string_t segmentDNSNameRaw(const char* input, size_t inputlen); // from ragel
816
817
bool DNSName::operator==(const DNSName& rhs) const
818
7.04k
{
819
7.04k
  if (rhs.empty() != empty() || rhs.d_storage.size() != d_storage.size()) {
820
7.04k
    return false;
821
7.04k
  }
822
823
0
  const auto* us = d_storage.cbegin();
824
0
  const auto* p = rhs.d_storage.cbegin();
825
0
  for (; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) {
826
0
    if (dns_tolower(*p) != dns_tolower(*us)) {
827
0
      return false;
828
0
    }
829
0
  }
830
0
  return true;
831
0
}
832
833
struct DNSNameSet: public std::unordered_set<DNSName> {
834
0
    std::string toString() const {
835
0
        std::ostringstream oss;
836
0
        std::copy(begin(), end(), std::ostream_iterator<DNSName>(oss, "\n"));
837
0
        return oss.str();
838
0
    }
839
};