Coverage Report

Created: 2025-08-05 06:59

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