Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ option(SOURCEMETA_CORE_CRYPTO "Build the Sourcemeta Core Crypto library" ON)
option(SOURCEMETA_CORE_CRYPTO_USE_SYSTEM_OPENSSL "Use system OpenSSL for the Sourcemeta Core Crypto library" OFF)
option(SOURCEMETA_CORE_REGEX "Build the Sourcemeta Core Regex library" ON)
option(SOURCEMETA_CORE_IP "Build the Sourcemeta Core IP library" ON)
option(SOURCEMETA_CORE_DNS "Build the Sourcemeta Core DNS library" ON)
option(SOURCEMETA_CORE_URI "Build the Sourcemeta Core URI library" ON)
option(SOURCEMETA_CORE_URITEMPLATE "Build the Sourcemeta Core URI Template library" ON)
option(SOURCEMETA_CORE_JSON "Build the Sourcemeta Core JSON library" ON)
Expand Down Expand Up @@ -118,6 +119,10 @@ if(SOURCEMETA_CORE_IP)
add_subdirectory(src/core/ip)
endif()

if(SOURCEMETA_CORE_DNS)
add_subdirectory(src/core/dns)
endif()

if(SOURCEMETA_CORE_URI)
add_subdirectory(src/core/uri)
endif()
Expand Down Expand Up @@ -237,6 +242,10 @@ if(SOURCEMETA_CORE_TESTS)
add_subdirectory(test/ip)
endif()

if(SOURCEMETA_CORE_DNS)
add_subdirectory(test/dns)
endif()

if(SOURCEMETA_CORE_URI)
add_subdirectory(test/uri)
endif()
Expand Down
6 changes: 6 additions & 0 deletions src/core/dns/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME dns
SOURCES hostname.cc)

if(SOURCEMETA_CORE_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME dns)
endif()
76 changes: 76 additions & 0 deletions src/core/dns/hostname.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <sourcemeta/core/dns.h>

namespace sourcemeta::core {

// RFC 952 §B: let-dig = ALPHA / DIGIT
// RFC 1123 §2.1: first character of a label is letter or digit
static constexpr auto is_let_dig(const char character) -> bool {
return (character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z') ||
(character >= '0' && character <= '9');
}

// RFC 952 §B: let-dig-hyp = ALPHA / DIGIT / "-"
static constexpr auto is_let_dig_hyp(const char character) -> bool {
return is_let_dig(character) || character == '-';
}

auto is_hostname(const std::string_view value) -> bool {
// RFC 952 §B: <hname> requires at least one <name>
if (value.empty()) {
return false;
}

// RFC 1123 §2.1: SHOULD handle host names of up to 255 characters
if (value.size() > 255) {
return false;
}

std::string_view::size_type position{0};

while (position < value.size()) {
// Start of a new label
const auto label_start{position};

// RFC 1123 §2.1: first character is letter or digit
if (!is_let_dig(value[position])) {
return false;
}
position += 1;

// Walk interior characters and the last character of the label
while (position < value.size() && value[position] != '.') {
// RFC 952 §B: interior characters are let-dig-hyp
if (!is_let_dig_hyp(value[position])) {
return false;
}
position += 1;
}

const auto label_length{position - label_start};

// RFC 1123 §2.1: MUST handle host names of up to 63 characters (per label)
if (label_length > 63) {
return false;
}

// RFC 952 §B + ASSUMPTIONS: last character must not be a minus sign
if (value[position - 1] == '-') {
return false;
}

// If we stopped on a dot, there must be another label following it
if (position < value.size()) {
// value[position] == '.'
position += 1;
// Trailing dot: JSON Schema test suite requires rejection (TS d7+ #15)
if (position >= value.size()) {
return false;
}
}
}

return true;
}

} // namespace sourcemeta::core
46 changes: 46 additions & 0 deletions src/core/dns/include/sourcemeta/core/dns.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef SOURCEMETA_CORE_DNS_H_
#define SOURCEMETA_CORE_DNS_H_

#ifndef SOURCEMETA_CORE_DNS_EXPORT
#include <sourcemeta/core/dns_export.h>
#endif

#include <string_view> // std::string_view

/// @defgroup dns DNS
/// @brief DNS and hostname validation utilities.
///
/// This functionality is included as follows:
///
/// ```cpp
/// #include <sourcemeta/core/dns.h>
/// ```

namespace sourcemeta::core {

/// @ingroup dns
/// Check whether the given string is a valid Internet host name per
/// RFC 1123 Section 2.1, which relaxes the first-character rule of
/// RFC 952 to allow either a letter or a digit. This matches the
/// definition used by the JSON Schema `hostname` format. For example:
///
/// ```cpp
/// #include <sourcemeta/core/dns.h>
///
/// #include <cassert>
///
/// assert(sourcemeta::core::is_hostname("www.example.com"));
/// assert(sourcemeta::core::is_hostname("1host"));
/// assert(!sourcemeta::core::is_hostname("-bad"));
/// assert(!sourcemeta::core::is_hostname("example."));
/// ```
///
/// This function implements RFC 1123 §2.1 (ASCII only). It does not
/// perform A-label or Punycode decoding — those belong to the separate
/// `idn-hostname` format.
SOURCEMETA_CORE_DNS_EXPORT
auto is_hostname(std::string_view value) -> bool;

} // namespace sourcemeta::core

#endif
5 changes: 5 additions & 0 deletions test/dns/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME dns
SOURCES hostname_test.cc)

target_link_libraries(sourcemeta_core_dns_unit
PRIVATE sourcemeta::core::dns)
Loading
Loading