/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/util/dns_name.h" #include "mongo/stdx/utility.h" #include "mongo/unittest/unittest.h" using namespace std::literals::string_literals; namespace mongo { namespace { // To silence some warnings on some compilers at some aggressive warning levels, we use an "enum // wrapper" struct which provides a legitimate implicit conversion from the checked value to the // underlying type. template struct Checked { static_assert(std::is_enum::value, "Checked Value test data type must be an enum."); Enum value; Checked(Enum v) : value(v) {} using underlying_type = typename std::underlying_type::type; operator underlying_type() const { return underlying_type(value); } }; TEST(DNSNameTest, CorrectParsing) { enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false }; const struct { std::string input; std::vector parsedDomains; Checked isFQDN; } tests[] = { {"com."s, {"com"s}, kIsFQDN}, {"com"s, {"com"s}, kNotFQDN}, {"mongodb.com."s, {"com"s, "mongodb"s}, kIsFQDN}, {"mongodb.com"s, {"com"s, "mongodb"s}, kNotFQDN}, {"atlas.mongodb.com."s, {"com"s, "mongodb"s, "atlas"s}, kIsFQDN}, {"atlas.mongodb.com"s, {"com"s, "mongodb"s, "atlas"s}, kNotFQDN}, {"server.atlas.mongodb.com."s, {"com"s, "mongodb"s, "atlas"s, "server"s}, kIsFQDN}, {"server.atlas.mongodb.com"s, {"com"s, "mongodb"s, "atlas"s, "server"s}, kNotFQDN}, }; for (const auto& test : tests) { const ::mongo::dns::HostName host(test.input); ASSERT_EQ(host.nameComponents().size(), test.parsedDomains.size()); for (std::size_t i = 0; i < host.nameComponents().size(); ++i) { ASSERT_EQ(host.nameComponents()[i], test.parsedDomains[i]); } ASSERT(host.isFQDN() == test.isFQDN); } } TEST(DNSNameTest, CanonicalName) { const struct { std::string input; std::string result; } tests[] = { {"com."s, "com."s}, {"com"s, "com"s}, {"mongodb.com."s, "mongodb.com."s}, {"mongodb.com"s, "mongodb.com"s}, {"atlas.mongodb.com."s, "atlas.mongodb.com."s}, {"atlas.mongodb.com"s, "atlas.mongodb.com"s}, {"server.atlas.mongodb.com."s, "server.atlas.mongodb.com."s}, {"server.atlas.mongodb.com"s, "server.atlas.mongodb.com"s}, }; for (const auto& test : tests) { const ::mongo::dns::HostName host(test.input); ASSERT_EQ(host.canonicalName(), test.result); } } TEST(DNSNameTest, NoncanonicalName) { const struct { std::string input; std::string result; } tests[] = { {"com."s, "com"s}, {"com"s, "com"s}, {"mongodb.com."s, "mongodb.com"s}, {"mongodb.com"s, "mongodb.com"s}, {"atlas.mongodb.com."s, "atlas.mongodb.com"s}, {"atlas.mongodb.com"s, "atlas.mongodb.com"s}, {"server.atlas.mongodb.com."s, "server.atlas.mongodb.com"s}, {"server.atlas.mongodb.com"s, "server.atlas.mongodb.com"s}, }; for (const auto& test : tests) { const ::mongo::dns::HostName host(test.input); ASSERT_EQ(host.noncanonicalName(), test.result); } } TEST(DNSNameTest, Contains) { enum IsSubdomain : bool { kIsSubdomain = true, kNotSubdomain = false }; enum TripsCheck : bool { kFailure = true, kSuccess = false }; const struct { std::string domain; std::string subdomain; Checked isSubdomain; Checked tripsCheck; } tests[] = { {"com."s, "mongodb.com."s, kIsSubdomain, kSuccess}, {"com"s, "mongodb.com"s, kIsSubdomain, kFailure}, {"com."s, "mongodb.com"s, kNotSubdomain, kFailure}, {"com"s, "mongodb.com."s, kNotSubdomain, kFailure}, {"com."s, "atlas.mongodb.com."s, kIsSubdomain, kSuccess}, {"com"s, "atlas.mongodb.com"s, kIsSubdomain, kFailure}, {"com."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"com"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure}, {"org."s, "atlas.mongodb.com."s, kNotSubdomain, kSuccess}, {"org"s, "atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"org."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"org"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure}, {"com."s, "com."s, kNotSubdomain, kSuccess}, {"com"s, "com."s, kNotSubdomain, kFailure}, {"com."s, "com"s, kNotSubdomain, kFailure}, {"com"s, "com"s, kNotSubdomain, kFailure}, {"mongodb.com."s, "mongodb.com."s, kNotSubdomain, kSuccess}, {"mongodb.com."s, "mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.com"s, "mongodb.com."s, kNotSubdomain, kFailure}, {"mongodb.com"s, "mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.com."s, "atlas.mongodb.com."s, kIsSubdomain, kSuccess}, {"mongodb.com"s, "atlas.mongodb.com"s, kIsSubdomain, kFailure}, {"mongodb.com."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.com"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure}, {"mongodb.com."s, "server.atlas.mongodb.com."s, kIsSubdomain, kSuccess}, {"mongodb.com"s, "server.atlas.mongodb.com"s, kIsSubdomain, kFailure}, {"mongodb.com."s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.com"s, "server.atlas.mongodb.com."s, kNotSubdomain, kFailure}, {"mongodb.org."s, "server.atlas.mongodb.com."s, kNotSubdomain, kSuccess}, {"mongodb.org"s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.org."s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure}, {"mongodb.org"s, "server.atlas.mongodb.com."s, kNotSubdomain, kFailure}, }; for (const auto& test : tests) { const ::mongo::dns::HostName domain(test.domain); const ::mongo::dns::HostName subdomain(test.subdomain); try { ASSERT(test.isSubdomain == IsSubdomain(domain.contains(subdomain))); ASSERT(!test.tripsCheck); } catch (const ExceptionFor&) { ASSERT(test.tripsCheck); } } } TEST(DNSNameTest, Resolution) { enum Failure : bool { kFails = true, kSucceeds = false }; enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false }; const struct { std::string domain; std::string subdomain; std::string result; Checked fails; Checked isFQDN; } tests[] = { {"mongodb.com."s, "atlas"s, "atlas.mongodb.com."s, kSucceeds, kIsFQDN}, {"mongodb.com"s, "atlas"s, "atlas.mongodb.com"s, kSucceeds, kNotFQDN}, {"mongodb.com."s, "server.atlas"s, "server.atlas.mongodb.com."s, kSucceeds, kIsFQDN}, {"mongodb.com"s, "server.atlas"s, "server.atlas.mongodb.com"s, kSucceeds, kNotFQDN}, {"mongodb.com."s, "atlas."s, "FAILS"s, kFails, kNotFQDN}, {"mongodb.com"s, "atlas."s, "FAILS"s, kFails, kNotFQDN}, }; for (const auto& test : tests) { try { const ::mongo::dns::HostName domain(test.domain); const ::mongo::dns::HostName subdomain(test.subdomain); const ::mongo::dns::HostName resolved = [&] { try { const ::mongo::dns::HostName rv = subdomain.resolvedIn(domain); return rv; } catch (const ExceptionFor&) { ASSERT(test.fails); throw; } }(); ASSERT(!test.fails); ASSERT_EQ(test.result, resolved.canonicalName()); ASSERT(test.isFQDN == resolved.isFQDN()); } catch (const ExceptionFor&) { ASSERT(test.fails); } } } TEST(DNSNameTest, ForceQualification) { enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false }; using Qualification = ::mongo::dns::HostName::Qualification; const struct { std::string domain; Checked startedFQDN; ::mongo::dns::HostName::Qualification forced; Checked becameFQDN; std::string becameCanonical; } tests[] = { {"mongodb.com."s, kIsFQDN, Qualification::kFullyQualified, kIsFQDN, "mongodb.com."s}, {"mongodb.com"s, kNotFQDN, Qualification::kFullyQualified, kIsFQDN, "mongodb.com."s}, {"atlas.mongodb.com."s, kIsFQDN, Qualification::kFullyQualified, kIsFQDN, "atlas.mongodb.com."s}, {"atlas.mongodb.com"s, kNotFQDN, Qualification::kFullyQualified, kIsFQDN, "atlas.mongodb.com."s}, {"mongodb.com."s, kIsFQDN, Qualification::kRelativeName, kNotFQDN, "mongodb.com"s}, {"mongodb.com"s, kNotFQDN, Qualification::kRelativeName, kNotFQDN, "mongodb.com"s}, {"atlas.mongodb.com."s, kIsFQDN, Qualification::kRelativeName, kNotFQDN, "atlas.mongodb.com"s}, {"atlas.mongodb.com"s, kNotFQDN, Qualification::kRelativeName, kNotFQDN, "atlas.mongodb.com"s}, }; for (const auto& test : tests) { ::mongo::dns::HostName domain(test.domain); ASSERT(stdx::as_const(domain).isFQDN() == test.startedFQDN); domain.forceQualification(test.forced); ASSERT(stdx::as_const(domain).isFQDN() == test.becameFQDN); ASSERT_EQ(stdx::as_const(domain).canonicalName(), test.becameCanonical); } } } // namespace } // namespace mongo