|
@@ -9,6 +9,13 @@
|
|
|
#include <unordered_map>
|
|
#include <unordered_map>
|
|
|
#include <utility>
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
+#if __has_include(<ada/idna/to_unicode.h>)
|
|
|
|
|
+#define JVALIDATE_HAS_IDNA
|
|
|
|
|
+#include <ada/idna/to_unicode.h>
|
|
|
|
|
+#include <ada/idna/validity.h>
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+#include <jvalidate/detail/idna_special_cases.h>
|
|
|
#include <jvalidate/detail/pointer.h>
|
|
#include <jvalidate/detail/pointer.h>
|
|
|
#include <jvalidate/detail/relative_pointer.h>
|
|
#include <jvalidate/detail/relative_pointer.h>
|
|
|
#include <jvalidate/detail/string.h>
|
|
#include <jvalidate/detail/string.h>
|
|
@@ -18,8 +25,6 @@
|
|
|
#define UTF32(FN) format::utf32<format::FN<char32_t>>
|
|
#define UTF32(FN) format::utf32<format::FN<char32_t>>
|
|
|
|
|
|
|
|
namespace jvalidate::format::detail {
|
|
namespace jvalidate::format::detail {
|
|
|
-using namespace jvalidate::detail;
|
|
|
|
|
-
|
|
|
|
|
struct result {
|
|
struct result {
|
|
|
ptrdiff_t consumed;
|
|
ptrdiff_t consumed;
|
|
|
bool valid;
|
|
bool valid;
|
|
@@ -151,31 +156,70 @@ inline bool duration(std::string_view dur) {
|
|
|
return dur.empty();
|
|
return dur.empty();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+template <typename CharT> bool is_invalid_host_char(CharT c) {
|
|
|
|
|
+ return c != '-' && not(std::isalnum(c) || c > 0x7F);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+template <typename CharT>
|
|
|
|
|
+bool is_invalid_size_or_boundary_hostname(std::basic_string_view<CharT> name) {
|
|
|
|
|
+ using delim = detail::char_delimiters<CharT>;
|
|
|
|
|
+ return (name.empty() || detail::to_u8(name).size() >= 64 ||
|
|
|
|
|
+ (name.size() >= 4 && name.substr(2).starts_with(delim::illegal_dashes_ulabel)) ||
|
|
|
|
|
+ name[0] == '-' || name.back() == '-');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#ifndef JVALIDATE_HAS_IDNA
|
|
|
|
|
+inline bool hostname_part(std::string_view name) {
|
|
|
|
|
+ using delim = detail::char_delimiters<char>;
|
|
|
|
|
+
|
|
|
|
|
+ if (is_invalid_size_or_boundary_hostname(name)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return std::ranges::none_of(name, [](char c) { return c != '-' && not std::isalnum(c); });
|
|
|
|
|
+}
|
|
|
|
|
+#else
|
|
|
|
|
+template <typename CharT> inline bool hostname_part(std::basic_string_view<CharT> name) {
|
|
|
|
|
+ using delim = detail::char_delimiters<CharT>;
|
|
|
|
|
+ if (name.starts_with(delim::punycode_prefix)) {
|
|
|
|
|
+ std::u32string decoded = detail::to_u32(ada::idna::to_unicode(detail::to_u8(name)));
|
|
|
|
|
+ return (decoded != detail::to_u32(name)) && hostname_part<char32_t>(decoded);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (is_invalid_size_or_boundary_hostname(name)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if constexpr (std::is_same_v<char, CharT>) {
|
|
|
|
|
+ return std::ranges::none_of(name, [](char c) { return c != '-' && not std::isalnum(c); });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return ada::idna::is_label_valid(name);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
// Limitation - does not inspect graphemes, so it cannot check idn-hostname
|
|
// Limitation - does not inspect graphemes, so it cannot check idn-hostname
|
|
|
// to fix this - we'd need to
|
|
// to fix this - we'd need to
|
|
|
template <typename CharT = char> inline bool hostname(std::basic_string_view<CharT> name) {
|
|
template <typename CharT = char> inline bool hostname(std::basic_string_view<CharT> name) {
|
|
|
- auto hostname_part = [&name](size_t end) {
|
|
|
|
|
- if (end == 0 || end >= 64 || name[0] == '-' || name[end - 1] == '-') {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- for (size_t i = 0; i < end; ++i) {
|
|
|
|
|
- if (name[i] != '-' && not std::isalnum(name[i])) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ using delim = detail::char_delimiters<CharT>;
|
|
|
|
|
+ if (name.find_first_of(delim::illegal_hostname_chars) != name.npos) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (name.size() > (name.back() == '.' ? 254 : 253)) {
|
|
|
|
|
|
|
+ if (detail::to_u8(name).size() > (name.back() == '.' ? 254 : 253)) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (not std::ranges::all_of(delim::special_cases,
|
|
|
|
|
+ [name](auto & sc) { return sc.accepts(name); })) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
for (size_t n = name.find('.'); n != std::string::npos;
|
|
for (size_t n = name.find('.'); n != std::string::npos;
|
|
|
name.remove_prefix(n + 1), n = name.find('.')) {
|
|
name.remove_prefix(n + 1), n = name.find('.')) {
|
|
|
- if (not hostname_part(n)) {
|
|
|
|
|
|
|
+ if (not hostname_part(name.substr(0, n))) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return name.empty() || hostname_part(name.size());
|
|
|
|
|
|
|
+ return name.empty() || hostname_part(name);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
inline bool ipv4(std::string_view ip) {
|
|
inline bool ipv4(std::string_view ip) {
|
|
@@ -190,7 +234,7 @@ inline bool ipv4(std::string_view ip) {
|
|
|
if (size_t n = ip.find(".0"); n != std::string::npos && std::isdigit(ip[n + 2])) {
|
|
if (size_t n = ip.find(".0"); n != std::string::npos && std::isdigit(ip[n + 2])) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- if (sscanf(ip.data(), "%3u.%3u.%3u.%3u%c", &ip0, &ip1, &ip2, &ip3, &eof) != 4) {
|
|
|
|
|
|
|
+ if (sscanf(std::string(ip).c_str(), "%3u.%3u.%3u.%3u%c", &ip0, &ip1, &ip2, &ip3, &eof) != 4) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
return ip0 <= 0xFF && ip1 <= 0xFF && ip2 <= 0xFF && ip3 <= 0xFF;
|
|
return ip0 <= 0xFF && ip1 <= 0xFF && ip2 <= 0xFF && ip3 <= 0xFF;
|
|
@@ -252,6 +296,7 @@ inline bool ipv6(std::string_view ip) {
|
|
|
// complex grammar - as long as it has an '@' sign with at least one character
|
|
// complex grammar - as long as it has an '@' sign with at least one character
|
|
|
// on each side, we ought to call it an email.
|
|
// on each side, we ought to call it an email.
|
|
|
template <typename CharT = char> inline bool email(std::basic_string_view<CharT> em) {
|
|
template <typename CharT = char> inline bool email(std::basic_string_view<CharT> em) {
|
|
|
|
|
+ using delim = detail::char_delimiters<CharT>;
|
|
|
size_t n = em.find_last_of('@');
|
|
size_t n = em.find_last_of('@');
|
|
|
if (n == 0 || n >= em.size() - 1) {
|
|
if (n == 0 || n >= em.size() - 1) {
|
|
|
return false;
|
|
return false;
|
|
@@ -262,7 +307,7 @@ template <typename CharT = char> inline bool email(std::basic_string_view<CharT>
|
|
|
// No validation
|
|
// No validation
|
|
|
} else if (who.starts_with('.') || who.ends_with('.')) {
|
|
} else if (who.starts_with('.') || who.ends_with('.')) {
|
|
|
return false;
|
|
return false;
|
|
|
- } else if (CharT const dots[3] = {'.', '.', '\0'}; em.substr(0, n).find(dots) != em.npos) {
|
|
|
|
|
|
|
+ } else if (em.substr(0, n).find(delim::dotdot) != em.npos) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -273,13 +318,11 @@ template <typename CharT = char> inline bool email(std::basic_string_view<CharT>
|
|
|
domain.remove_prefix(1);
|
|
domain.remove_prefix(1);
|
|
|
domain.remove_suffix(1);
|
|
domain.remove_suffix(1);
|
|
|
|
|
|
|
|
- std::string ip(domain.size(), '\0'); // Re-acquiring the NULL terminator
|
|
|
|
|
- std::ranges::copy(domain, ip.begin());
|
|
|
|
|
-
|
|
|
|
|
- if (ip.starts_with("IPv6:")) {
|
|
|
|
|
|
|
+ if (auto ip = detail::to_u8(domain); ip.starts_with("IPv6:")) {
|
|
|
return ipv6(ip.substr(5));
|
|
return ipv6(ip.substr(5));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return ipv4(ip);
|
|
|
}
|
|
}
|
|
|
- return ipv4(ip);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <typename T> inline bool ctor_as_valid(std::string_view str) {
|
|
template <typename T> inline bool ctor_as_valid(std::string_view str) {
|