{
makeUsRelative(zone.operator const DNSName&());
}
+
+std::string_view::size_type ZoneName::findVariantSeparator(std::string_view name)
+{
+ std::string_view::size_type pos{0};
+
+ // Try to be as fast as possible in the non-variant case and exit
+ // quickly if we don't find two dots in a row.
+ while ((pos = name.find('.', pos)) != std::string_view::npos) {
+ ++pos;
+ if (pos >= name.length()) { // trailing single dot
+ return std::string_view::npos;
+ }
+ if (name.at(pos) == '.') {
+ // We have found two dots in a row, but the first dot might have been
+ // escaped. So we now need to count how many \ characters we can find a
+ // row before it; if their number is odd, the first dot is escaped and
+ // we need to keep searching.
+ size_t slashes{0};
+ while (pos >= 2 + slashes && name.at(pos - 2 - slashes) == '\\') {
+ ++slashes;
+ }
+ if ((slashes % 2) == 0) {
+ break;
+ }
+ }
+ }
+ return pos;
+}
+
+ZoneName::ZoneName(std::string_view name)
+{
+ if (auto sep = findVariantSeparator(name); sep != std::string_view::npos) {
+ setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
+ name = name.substr(0, sep); // keep trailing dot in zone name
+ }
+ d_name = DNSName(name);
+}
+
+ZoneName::ZoneName(std::string_view name, std::string_view::size_type sep)
+{
+ if (sep != std::string_view::npos) {
+ setVariant(name.substr(sep + 1)); // ignore leading dot in variant name
+ name = name.substr(0, sep); // keep trailing dot in zone name
+ }
+ d_name = DNSName(name);
+}
+
+void ZoneName::setVariant(std::string_view variant)
+{
+ if (variant.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789_-") != std::string_view::npos) {
+ throw std::out_of_range("invalid character in variant name '" + std::string{variant} + "'");
+ }
+ d_variant = variant;
+}
+
+std::string ZoneName::toLogString() const
+{
+ std::string ret = d_name.toLogString();
+ if (!d_variant.empty()) {
+ // Because toLogString() above uses toStringRootDot(), we do not want to
+ // output one too many dots if this is a root-with-variant.
+ ret.push_back('.');
+ if (!d_name.isRoot()) {
+ ret.push_back('.');
+ }
+ ret += d_variant;
+ }
+ return ret;
+}
+
+size_t ZoneName::hash(size_t init) const
+{
+ if (!d_variant.empty()) {
+ init = burtleCI(reinterpret_cast<const unsigned char *>(d_variant.data()), d_variant.length(), init); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+ }
+
+ return d_name.hash(init);
+}
+
+bool ZoneName::operator<(const ZoneName& rhs) const
+{
+ // Order by DNSName first, by variant second.
+ // Unfortunately we can't use std::lexicographical_compare_three_way() yet
+ // as this would require C++20.
+ const auto *iter1 = d_name.getStorage().cbegin();
+ const auto *last1 = d_name.getStorage().cend();
+ const auto *iter2 = rhs.d_name.getStorage().cbegin();
+ const auto *last2 = rhs.d_name.getStorage().cend();
+ while (iter1 != last1 && iter2 != last2) {
+ auto char1 = dns_tolower(*iter1);
+ auto char2 = dns_tolower(*iter2);
+ if (char1 < char2) {
+ return true;
+ }
+ if (char1 > char2) {
+ return false;
+ }
+ ++iter1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ ++iter2; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ }
+ if (iter1 == last1) {
+ if (iter2 != last2) {
+ return true; // our DNSName is shorter (subset) than the other
+ }
+ }
+ else {
+ return false; // our DNSName is longer (superset) than the other
+ }
+ // At this point, both DNSName compare equal, we have to compare
+ // variants (which are case-sensitive).
+ return d_variant < rhs.d_variant;
+}
+
+bool ZoneName::canonCompare(const ZoneName& rhs) const
+{
+ // Similarly to operator< above, this compares DNSName first, variant
+ // second. Unfortunately because DNSName::canonCompare() is complicated,
+ // it can't pragmatically be duplicated here, hence the two calls.
+ // TODO: change DNSName::canonCompare() to return a three-state value
+ // (lt, eq, ge) in order to be able to call it only once.
+ if (!d_name.canonCompare(rhs.d_name)) {
+ return false;
+ }
+ if (!rhs.d_name.canonCompare(d_name)) {
+ return true;
+ }
+ // Both DNSName compare equal.
+ return d_variant < rhs.d_variant;
+}
#endif // ]
#include <unordered_set>
#include <string_view>
+using namespace std::string_view_literals;
+
#include <boost/version.hpp>
#include <boost/container/string.hpp>
extern const DNSName g_rootdnsname, g_wildcarddnsname;
-// ZoneName: this is equivalent to DNSName, but intended to only store zone
-// names.
#if defined(PDNS_AUTH) // [
-// Conversions between DNSName and ZoneName are allowed, but must be explicit.
+// ZoneName: this is equivalent to DNSName, but intended to only store zone
+// names. In addition to the name, an optional variant is allowed. The
+// variant is never part of a DNS packet; it can only be used by backends to
+// perform specific extra processing.
+// Variant names are limited to [a-z0-9_-].
+// Conversions between DNSName and ZoneName are allowed, but must be explicit;
+// conversions to DNSName lose the variant part.
class ZoneName
{
public:
{
if (this != &rhs) {
d_name = rhs.d_name;
+ d_variant = rhs.d_variant;
}
return *this;
}
{
if (this != &rhs) {
d_name = std::move(rhs.d_name);
+ d_variant = std::move(rhs.d_variant);
}
return *this;
}
ZoneName(const ZoneName& a) = default;
ZoneName(ZoneName&& a) = default;
- explicit ZoneName(std::string_view name) : d_name(name) {}
- explicit ZoneName(const DNSName& name) : d_name(name) {}
+ explicit ZoneName(std::string_view name);
+ explicit ZoneName(std::string_view name, std::string_view variant) : d_name(name), d_variant(variant) {}
+ explicit ZoneName(const DNSName& name, std::string_view variant = ""sv) : d_name(name), d_variant(variant) {}
+ explicit ZoneName(std::string_view name, std::string_view::size_type sep);
bool isPartOf(const ZoneName& rhs) const { return d_name.isPartOf(rhs.d_name); }
bool isPartOf(const DNSName& rhs) const { return d_name.isPartOf(rhs); }
- bool operator==(const ZoneName& rhs) const { return d_name == rhs.d_name; }
- bool operator!=(const ZoneName& rhs) const { return d_name != rhs.d_name; }
+ bool operator==(const ZoneName& rhs) const { return d_name == rhs.d_name && d_variant == rhs.d_variant; }
+ bool operator!=(const ZoneName& rhs) const { return !operator==(rhs); }
+ // IMPORTANT! None of the "toString" routines will output the variant, but toLogString().
std::string toString(const std::string& separator=".", const bool trailing=true) const { return d_name.toString(separator, trailing); }
void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const { d_name.toString(output, separator, trailing); }
- std::string toLogString() const { return d_name.toLogString(); }
+ std::string toLogString() const;
std::string toStringNoDot() const { return d_name.toStringNoDot(); }
std::string toStringRootDot() const { return d_name.toStringRootDot(); }
}
void makeUsLowerCase() { d_name.makeUsLowerCase(); }
bool empty() const { return d_name.empty(); }
- void clear() { d_name.clear(); }
+ void clear() { d_name.clear(); d_variant.clear(); }
void trimToLabels(unsigned int trim) { d_name.trimToLabels(trim); }
- size_t hash(size_t init=0) const { return d_name.hash(init); }
+ size_t hash(size_t init=0) const;
- bool operator<(const ZoneName& rhs) const { return d_name.operator<(rhs.d_name); }
+ bool operator<(const ZoneName& rhs) const;
- bool canonCompare(const ZoneName& rhs) const { return d_name.canonCompare(rhs.d_name); }
+ bool canonCompare(const ZoneName& rhs) const;
// Conversion from ZoneName to DNSName
explicit operator const DNSName&() const { return d_name; }
explicit operator DNSName&() { return d_name; }
+
+ bool hasVariant() const { return !d_variant.empty(); }
+ std::string getVariant() const { return d_variant; }
+ void setVariant(std::string_view);
+
+ // Search for a variant separator: mandatory (when variants are used) trailing
+ // dot followed by another dot and the variant name, and return the length of
+ // the zone name without its variant part, or npos if there is no variant
+ // present.
+ static std::string_view::size_type findVariantSeparator(std::string_view name);
+
private:
DNSName d_name;
+ std::string d_variant{};
};
size_t hash_value(ZoneName const& zone);
BOOST_CHECK_EQUAL(name5.getCommonLabels(name1), DNSName());
}
+#if defined(PDNS_AUTH)
+BOOST_AUTO_TEST_CASE(test_variantnames) {
+ ZoneName zone1("..variant");
+ ZoneName zone2("bug.less..variant");
+ ZoneName zone3(R"(actually\..not.a.variant)");
+ ZoneName zone4(R"(still\\\..not.a.variant)");
+ ZoneName zone5(R"(anti-\\..variant)");
+ ZoneName zone6(R"(sl\\\\\..a\\\..sh\...overflow)");
+
+ BOOST_CHECK(zone1.hasVariant());
+ BOOST_CHECK(zone1.operator const DNSName&().isRoot());
+
+ BOOST_CHECK(zone2.hasVariant());
+ BOOST_CHECK_EQUAL(zone2.operator const DNSName&().toString(), "bug.less.");
+ BOOST_CHECK_EQUAL(zone2.getVariant(), "variant");
+
+ BOOST_CHECK(!zone3.hasVariant());
+ BOOST_CHECK(!zone4.hasVariant());
+
+ BOOST_CHECK(zone5.hasVariant());
+ BOOST_CHECK_EQUAL(zone5.operator const DNSName&().toString(), R"(anti-\\.)");
+ BOOST_CHECK_EQUAL(zone5.getVariant(), "variant");
+
+ BOOST_CHECK(zone6.hasVariant());
+ BOOST_CHECK_EQUAL(zone6.getVariant(), "overflow");
+
+ BOOST_CHECK_THROW(ZoneName zone("variants.r.us..dot..dot...dot....dot.....dots"),std::out_of_range);
+}
+#endif
+
BOOST_AUTO_TEST_SUITE_END()