Coverage Report

Created: 2026-05-30 06:31

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