diff --git a/include/skyr/v1/containers/static_vector.hpp b/include/skyr/v1/containers/static_vector.hpp new file mode 100644 index 00000000..66577f4a --- /dev/null +++ b/include/skyr/v1/containers/static_vector.hpp @@ -0,0 +1,168 @@ +// Copyright 2020 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP +#define SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP + +#include +#include +#include +#include + +namespace skyr { +inline namespace v1 { +/// +/// \tparam T +/// \tparam Capacity +template < + class T, + std::size_t Capacity + > +class static_vector { + private: + + using impl_type = std::array; + + impl_type impl_; + std::size_t size_ = 0; + + public: + + /// + using value_type = T; + /// + using const_reference = const T &; + /// + using reference = T &; + /// + using const_pointer = const T *; + /// + using pointer = T *; + /// + using size_type = std::size_t; + /// + using difference_type = std::ptrdiff_t; + /// + using const_iterator = typename impl_type::const_iterator; + /// + using iterator = typename impl_type::iterator; + + /// Constructor + constexpr static_vector() = default; + + /// Gets the first const element in the vector + /// \return a const T & + /// \pre `size() > 0` + constexpr auto front() const noexcept -> const_reference { + return impl_[0]; + } + + /// Gets the first element in the vector + /// \return a T & + /// \pre `size() > 0` + constexpr auto front() noexcept -> reference { + return impl_[0]; + } + + /// + /// \return + /// \pre `size() > 0` + constexpr auto back() const noexcept -> const_reference { + return impl_[size_ - 1]; + } + + /// + /// \return + /// \pre `size() > 0` + constexpr auto back() noexcept -> reference { + return impl_[size_ - 1]; + } + + /// + /// \param value + /// \return + /// \pre `size() < `capacity()` + /// \post `size() > 0 && size() <= capacity()` + constexpr auto push_back(const_reference value) noexcept -> reference { + impl_[size_++] = value; + return impl_[size_ - 1]; + } + + /// + /// \tparam Args + /// \param args + /// \return + /// \pre `size() < `capacity()` + /// \post `size() > 0 && size() <= capacity()` + template + constexpr auto emplace_back(Args &&... args) + noexcept(std::is_trivially_move_assignable_v) -> reference { + impl_[size_++] = value_type{args...}; + return impl_[size_ - 1]; + } + + /// + /// \pre `size() > 0` + constexpr void pop_back() noexcept { + --size_; + } + + /// + /// \return + [[nodiscard]] constexpr auto data() const noexcept -> const value_type * { + return impl_.data(); + } + + /// + /// \return + [[nodiscard]] constexpr auto size() const noexcept -> size_type { + return size_; + } + + /// + /// \return + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type { + return Capacity; + } + + /// + /// \return `true` if there are elements + [[nodiscard]] constexpr auto empty() const noexcept -> bool { + return size_ == 0; + } + + /// + /// \return + [[nodiscard]] constexpr auto begin() noexcept -> iterator { + return impl_.begin(); + } + + /// + /// \return + [[nodiscard]] constexpr auto end() noexcept -> iterator { + auto last = impl_.begin(); + std::advance(last, size_); + return last; + } + + /// + /// \return + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator { + return impl_.begin(); + } + + /// + /// \return + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator { + auto last = impl_.begin(); + std::advance(last, size_); + return last; + } + +}; +} // namespace v1 +} // namespace skyr + +#endif // SKYR_V1_CONTAINERS_STATIC_VECTOR_HPP diff --git a/include/skyr/v1/domain/errors.hpp b/include/skyr/v1/domain/errors.hpp index c2a9c0fe..1fc47272 100644 --- a/include/skyr/v1/domain/errors.hpp +++ b/include/skyr/v1/domain/errors.hpp @@ -23,6 +23,8 @@ enum class domain_errc { invalid_length, /// Empty domain empty_string, + /// The number of labels in the domain is too large + too_many_labels, }; } // namespace v1 } // namespace skyr diff --git a/include/skyr/v1/unicode/traits/range_iterator.hpp b/include/skyr/v1/unicode/traits/range_iterator.hpp index 26857d5a..ecf8bdae 100644 --- a/include/skyr/v1/unicode/traits/range_iterator.hpp +++ b/include/skyr/v1/unicode/traits/range_iterator.hpp @@ -14,7 +14,7 @@ namespace unicode::traits { template class range_iterator { public: - using type = typename Range::const_iterator; + using type = typename std::decay_t::const_iterator; }; /// diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0fbb37c..658f80b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources(skyr-url v1/domain/domain.cpp v1/domain/idna.hpp v1/domain/idna.cpp + v1/domain/idna_code_point_map_iterator.hpp v1/domain/punycode.hpp v1/domain/punycode.cpp v1/url/url.cpp @@ -50,6 +51,7 @@ target_sources(skyr-url ${PROJECT_SOURCE_DIR}/include/skyr/v1/unicode/ranges/sentinel.hpp ${PROJECT_SOURCE_DIR}/include/skyr/v1/unicode/details/to_u8.hpp ${PROJECT_SOURCE_DIR}/include/skyr/v1/string/starts_with.hpp + ${PROJECT_SOURCE_DIR}/include/skyr/v1/containers/static_vector.hpp ${PROJECT_SOURCE_DIR}/include/skyr/v1/domain/errors.hpp ${PROJECT_SOURCE_DIR}/include/skyr/v1/domain/domain.hpp ${PROJECT_SOURCE_DIR}/include/skyr/v1/platform/endianness.hpp diff --git a/src/v1/core/host.cpp b/src/v1/core/host.cpp index b24b4a82..16e081b6 100644 --- a/src/v1/core/host.cpp +++ b/src/v1/core/host.cpp @@ -8,8 +8,13 @@ #include #include #include +#include #include +#if !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH) +#define SKYR_DOMAIN_MAX_DOMAIN_LENGTH 253 +#endif // !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH) + namespace skyr { inline namespace v1 { namespace { @@ -73,12 +78,16 @@ auto parse_host( [] (auto &&h) -> tl::expected { return host{h}; }); } - auto domain = percent_decode(input); - if (!domain) { - return tl::make_unexpected(url_parse_errc::cannot_decode_host_point); + auto domain = static_vector{}; + auto range = percent_encoding::percent_decode_range{input}; + for (auto it = std::cbegin(range); it != std::cend(range); ++it) { + if ((domain.size() == domain.max_size()) || !*it) { + return tl::make_unexpected(url_parse_errc::cannot_decode_host_point); + } + domain.push_back((*it).value()); } - auto ascii_domain = domain_to_ascii(domain.value()); + auto ascii_domain = domain_to_ascii(std::string_view(domain.data(), domain.size())); if (!ascii_domain) { return tl::make_unexpected(url_parse_errc::domain_error); } diff --git a/src/v1/core/url_parser_context.cpp b/src/v1/core/url_parser_context.cpp index 19f411d1..7fbe8899 100644 --- a/src/v1/core/url_parser_context.cpp +++ b/src/v1/core/url_parser_context.cpp @@ -75,24 +75,11 @@ inline auto is_windows_drive_letter(std::string_view segment) noexcept { } inline auto is_single_dot_path_segment(std::string_view segment) noexcept { - return - (segment == ".") || - (segment == "%2e") || - (segment == "%2E"); + return (segment == ".") || (segment == "%2E"); } auto is_double_dot_path_segment(std::string_view segment) noexcept { - constexpr static auto to_lower = [] (auto byte) -> decltype(byte) { - return std::tolower(byte, std::locale::classic()); - }; - - auto lower = std::string(segment); - std::transform(begin(lower), end(lower), begin(lower), to_lower); - return ( - (lower == "..") || - (lower == ".%2e") || - (lower == "%2e.") || - (lower == "%2e%2e")); + return (segment == "..") || (segment == "%2E.") || (segment == ".%2E") || (segment == "%2E%2E"); } void shorten_path(std::string_view scheme, std::vector &path) { diff --git a/src/v1/domain/domain.cpp b/src/v1/domain/domain.cpp index 700999b6..1b271182 100644 --- a/src/v1/domain/domain.cpp +++ b/src/v1/domain/domain.cpp @@ -4,75 +4,58 @@ // http://www.boost.org/LICENSE_1_0.txt) #include -#include +#include #include #include #include -#include +#include +#include +#include #include #include -#include #include -#include +#include #include "idna.hpp" +#include "idna_code_point_map_iterator.hpp" +#include "punycode.hpp" + +#if !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH) +#define SKYR_DOMAIN_MAX_DOMAIN_LENGTH 253 +#endif // !defined(SKYR_DOMAIN_MAX_DOMAIN_LENGTH) + +#if !defined(SKYR_DOMAIN_MAX_LABEL_LENGTH) +#define SKYR_DOMAIN_MAX_LABEL_LENGTH 63 +#endif // !defined(SKYR_LABEL_MAX_DOMAIN_LENGTH) + +/// How many labels can be in a domain? +/// https://www.farsightsecurity.com/blog/txt-record/rrlabel-20171013/ +#if !defined(SKYR_DOMAIN_MAX_NUM_LABELS) +#define SKYR_DOMAIN_MAX_NUM_LABELS 32 +#endif // !defined(SKYR_DOMAIN_MAX_NUM_LABELS) namespace skyr { inline namespace v1 { namespace { +template auto map_code_points( - std::u32string_view domain_name, bool use_std3_ascii_rules, - bool transitional_processing) - -> tl::expected { - auto result = std::u32string(); - auto error = false; - - auto first = begin(domain_name), last = end(domain_name); - auto it = first; - - while (it != last) { - switch (idna::code_point_status(*it)) { - case idna::idna_status::disallowed: - error = true; - break; - case idna::idna_status::disallowed_std3_valid: - if (use_std3_ascii_rules) { - error = true; - } else { - result += *it; - } - break; - case idna::idna_status::disallowed_std3_mapped: - if (use_std3_ascii_rules) { - error = true; - } else { - result += idna::map_code_point(*it); - } - break; - case idna::idna_status::ignored: - break; - case idna::idna_status::mapped: - result += idna::map_code_point(*it); - break; - case idna::idna_status::deviation: - if (transitional_processing) { - result += idna::map_code_point(*it); - } else { - result += *it; - } - break; - case idna::idna_status::valid: - result += *it; - break; + U32Range &&domain_name, + bool use_std3_ascii_rules, + bool transitional_processing, + static_vector *result) + -> tl::expected { + auto range = views::map_code_points(domain_name, use_std3_ascii_rules, transitional_processing); + auto first = std::cbegin(range); + auto last = std::cend(range); + for (auto it = first; it != last; ++it) { + if (!*it) { + return tl::make_unexpected((*it).error()); + } + result->emplace_back((*it).value()); + if (result->size() == result->max_size()) { + return tl::make_unexpected(domain_errc::invalid_length); } - - ++it; - } - - if (error) { - return tl::make_unexpected(domain_errc::disallowed_code_point); } - - return result; + return {}; } auto validate_label(std::u32string_view label, [[maybe_unused]] bool use_std3_ascii_rules, bool check_hyphens, @@ -121,91 +104,78 @@ auto validate_label(std::u32string_view label, [[maybe_unused]] bool use_std3_as return {}; } -auto idna_process(std::u32string_view domain_name, bool use_std3_ascii_rules, bool check_hyphens, - bool check_bidi, bool check_joiners, bool transitional_processing) - -> tl::expected { - using namespace std::string_view_literals; - - static constexpr auto to_string_view = [] (auto &&label) { - return std::u32string_view(std::addressof(*std::begin(label)), ranges::distance(label)); - }; - - auto result = map_code_points(domain_name, use_std3_ascii_rules, transitional_processing); - if (result) { - for (auto &&label : result.value() | ranges::views::split(U'.') | ranges::views::transform(to_string_view)) { - if ((label.size() >= 4) && (label.substr(0, 4) == U"xn--")) { - auto decoded = punycode_decode(label.substr(4)); - if (!decoded) { - return tl::make_unexpected(decoded.error()); - } - - auto validated = - validate_label(decoded.value(), use_std3_ascii_rules, check_hyphens, check_bidi, check_joiners, false); - if (!validated) { - return tl::make_unexpected(validated.error()); - } - } else { - auto validated = validate_label(label, use_std3_ascii_rules, check_hyphens, check_bidi, check_joiners, - transitional_processing); - if (!validated) { - return tl::make_unexpected(validated.error()); - } - } - } - } - return result; -} - -namespace { -inline auto is_ascii(std::u32string_view input) noexcept { - constexpr static auto is_in_ascii_set = [](auto c) { return static_cast(c) <= 0x7eu; }; - - auto first = cbegin(input), last = cend(input); - return last == std::find_if_not(first, last, is_in_ascii_set); -} -} // namespace - auto domain_to_ascii( std::string_view domain_name, bool check_hyphens, bool check_bidi, bool check_joiners, bool use_std3_ascii_rules, bool transitional_processing, bool verify_dns_length) -> tl::expected { /// https://www.unicode.org/reports/tr46/#ToASCII - auto utf32 = unicode::as(unicode::views::as_u8(domain_name) | unicode::transforms::to_u32); - if (!utf32) { - return tl::make_unexpected(domain_errc::encoding_error); - } + using namespace std::string_view_literals; - auto domain = idna_process( - utf32.value(), use_std3_ascii_rules, check_hyphens, check_bidi, check_joiners, transitional_processing); - if (!domain) { - return tl::make_unexpected(domain.error()); + auto u32domain_name = unicode::views::as_u8(domain_name) | unicode::transforms::to_u32; + auto mapped_domain_name = static_vector{}; + auto result = map_code_points(u32domain_name, use_std3_ascii_rules, transitional_processing, &mapped_domain_name); + if (!result) { + return tl::make_unexpected(result.error()); } static constexpr auto to_string_view = [] (auto &&label) { return std::u32string_view(std::addressof(*std::begin(label)), ranges::distance(label)); }; - auto labels = std::vector{}; - for (auto &&label : domain.value() | ranges::views::split(U'.') | ranges::views::transform(to_string_view)) { + /// TODO: try this without allocating strings (e.g. for large strings that don't use SBO) + auto labels = static_vector, SKYR_DOMAIN_MAX_NUM_LABELS>{}; + for (auto &&label : mapped_domain_name | ranges::views::split(U'.') | ranges::views::transform(to_string_view)) { + if (labels.size() == labels.max_size()) { + return tl::make_unexpected(domain_errc::too_many_labels); + } + + if ((label.size() >= 4) && (label.substr(0, 4) == U"xn--")) { + auto decoded = punycode_decode(label.substr(4)); + if (!decoded) { + return tl::make_unexpected(decoded.error()); + } + + auto validated = + validate_label(decoded.value(), use_std3_ascii_rules, check_hyphens, check_bidi, check_joiners, false); + if (!validated) { + return tl::make_unexpected(validated.error()); + } + } else { + auto validated = validate_label(label, use_std3_ascii_rules, check_hyphens, check_bidi, check_joiners, + transitional_processing); + if (!validated) { + return tl::make_unexpected(validated.error()); + } + } + + constexpr static auto is_ascii = [] (std::u32string_view input) noexcept { + constexpr static auto is_in_ascii_set = [](auto c) { return static_cast(c) <= 0x7eu; }; + + auto first = cbegin(input), last = cend(input); + return last == std::find_if_not(first, last, is_in_ascii_set); + }; + + labels.emplace_back(); if (!is_ascii(label)) { auto encoded = punycode_encode(label); if (!encoded) { return tl::make_unexpected(encoded.error()); } - labels.emplace_back("xn--" + encoded.value()); + ranges::copy(U"xn--"sv, ranges::back_inserter(labels.back())); + ranges::copy(encoded.value(), ranges::back_inserter(labels.back())); } else { - labels.emplace_back(begin(label), end(label)); + ranges::copy(label, ranges::back_inserter(labels.back())); } } - if (domain.value().back() == U'.') { + if (mapped_domain_name.back() == U'.') { labels.emplace_back(); } if (verify_dns_length) { - auto length = domain.value().size(); + auto length = mapped_domain_name.size(); if ((length < 1) || (length > 253)) { return tl::make_unexpected(domain_errc::invalid_length); } @@ -238,23 +208,28 @@ auto domain_to_ascii( auto domain_to_u8(std::string_view domain_name, [[maybe_unused]] bool *validation_error) -> tl::expected { - auto labels = std::vector{}; static constexpr auto to_string_view = [] (auto &&label) { return std::string_view(std::addressof(*std::begin(label)), ranges::distance(label)); }; + auto labels = static_vector, SKYR_DOMAIN_MAX_NUM_LABELS>{}; for (auto &&label : domain_name | ranges::views::split('.') | ranges::views::transform(to_string_view)) { + if (labels.size() == labels.max_size()) { + return tl::make_unexpected(domain_errc::too_many_labels); + } + + labels.emplace_back(); if (label.substr(0, 4) == "xn--") { label.remove_prefix(4); auto decoded = punycode_decode(label); if (!decoded) { return tl::make_unexpected(decoded.error()); } - labels.emplace_back(decoded.value()); + ranges::copy(decoded.value(), ranges::back_inserter(labels.back())); } else { - labels.emplace_back(begin(label), end(label)); + ranges::copy(label, ranges::back_inserter(labels.back())); } } diff --git a/src/v1/domain/idna.cpp b/src/v1/domain/idna.cpp index b47a395e..89451c37 100644 --- a/src/v1/domain/idna.cpp +++ b/src/v1/domain/idna.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include "idna.hpp" diff --git a/src/v1/domain/idna_code_point_map_iterator.hpp b/src/v1/domain/idna_code_point_map_iterator.hpp new file mode 100644 index 00000000..6e937695 --- /dev/null +++ b/src/v1/domain/idna_code_point_map_iterator.hpp @@ -0,0 +1,174 @@ +// Copyright 2020 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef SKYR_V1_DOMAIN_IDNA_ITERATOR_HPP +#define SKYR_V1_DOMAIN_IDNA_ITERATOR_HPP + +#include +#include +#include +#include +#include "idna.hpp" + +namespace skyr { +inline namespace v1 { +template < + class Iterator, + class Sentinel=unicode::sentinel + > +class idna_code_point_map_iterator { + public: + + using iterator_category = std::forward_iterator_tag; + using value_type = tl::expected; + using const_reference = value_type; + using reference = const_reference; + using const_pointer = const value_type *; + using pointer = const_pointer; + using difference_type = typename Iterator::difference_type; + using size_type = typename Iterator::size_type; + + explicit constexpr idna_code_point_map_iterator( + Iterator first, + Sentinel last, + bool use_std3_ascii_rules, + bool transitional_processing) + : it_(first) + , last_(last) + , use_std3_ascii_rules_(use_std3_ascii_rules) + , transitional_processing_(transitional_processing) {} + + constexpr auto operator ++ () noexcept -> idna_code_point_map_iterator & { + increment(); + return *this; + } + + constexpr auto operator ++ (int) noexcept -> idna_code_point_map_iterator { + auto result = *this; + increment(); + return result; + } + + constexpr auto operator * () const noexcept -> const_reference { + constexpr auto map_code_point = [] (char32_t code_point, bool use_std3_ascii_rules, bool transitional_processing) + -> tl::expected { + switch (idna::code_point_status(code_point)) { + case idna::idna_status::disallowed: + return tl::make_unexpected(domain_errc::disallowed_code_point); + case idna::idna_status::disallowed_std3_valid: + if (use_std3_ascii_rules) { + return tl::make_unexpected(domain_errc::disallowed_code_point); + } else { + return code_point; + } + break; + case idna::idna_status::disallowed_std3_mapped: + if (use_std3_ascii_rules) { + return tl::make_unexpected(domain_errc::disallowed_code_point); + } else { + return idna::map_code_point(code_point); + } + case idna::idna_status::ignored: + return tl::make_unexpected(domain_errc::disallowed_code_point); + case idna::idna_status::mapped: + return idna::map_code_point(code_point); + case idna::idna_status::deviation: + if (transitional_processing) { + return idna::map_code_point(code_point); + } else { + return code_point; + } + case idna::idna_status::valid: + return code_point; + } + return code_point; + }; + + auto code_point = unicode::u32_value(*it_); + if (code_point) { + return map_code_point(code_point.value(), use_std3_ascii_rules_, transitional_processing_); + } + else { + return tl::make_unexpected(domain_errc::encoding_error); + } + } + + [[nodiscard]] constexpr bool operator == ([[maybe_unused]] unicode::sentinel sentinel) const noexcept { + return it_ == last_; + } + + [[nodiscard]] constexpr bool operator != (unicode::sentinel sentinel) const noexcept { + return !(*this == sentinel); + } + + private: + + constexpr void increment() { + constexpr auto is_ignored = [] (auto code_point) { + auto value = unicode::u32_value(code_point); + return (value && idna::code_point_status(value.value()) == idna::idna_status::ignored); + }; + + ++it_; + while ((it_ != last_) && is_ignored(*it_)) { + ++it_; + } + } + + Iterator it_; + Sentinel last_; + bool use_std3_ascii_rules_; + bool transitional_processing_; + +}; + +template +class idna_code_point_map_range { + public: + + constexpr idna_code_point_map_range( + Range &&range, + bool use_std3_ascii_rules, + bool transitional_processing) + : range_(range) + , use_std3_ascii_rules_(use_std3_ascii_rules) + , transitional_processing_(transitional_processing) {} + + [[nodiscard]] constexpr auto cbegin() const noexcept { + return idna_code_point_map_iterator>( + std::cbegin(range_), std::cend(range_), use_std3_ascii_rules_, transitional_processing_); + } + + [[nodiscard]] constexpr auto cend() const noexcept { + return unicode::sentinel{}; + } + + [[nodiscard]] constexpr auto begin() const noexcept { + return cbegin(); + } + + [[nodiscard]] constexpr auto end() const noexcept { + return cend(); + } + + private: + + Range range_; + bool use_std3_ascii_rules_; + bool transitional_processing_; + +}; + +namespace views { +template +constexpr inline auto map_code_points(Range &&range, bool use_std3_ascii_rules, bool transitional_processing) + -> idna_code_point_map_range { + return {range, use_std3_ascii_rules, transitional_processing}; +} +} // +} // namespace v1 +} // namespace skyr + +#endif // SKYR_V1_DOMAIN_IDNA_ITERATOR_HPP diff --git a/src/v1/network/ipv4_address.cpp b/src/v1/network/ipv4_address.cpp index b07e649d..7edb22df 100644 --- a/src/v1/network/ipv4_address.cpp +++ b/src/v1/network/ipv4_address.cpp @@ -6,8 +6,10 @@ #include #include #include -#include #include +#include +#include +#include namespace skyr { inline namespace v1 { auto ipv4_address::serialize() const -> std::string { @@ -63,14 +65,17 @@ auto parse_ipv4_address( std::string_view input, bool *validation_error) -> tl::expected { using namespace std::string_view_literals; - std::vector parts; - parts.emplace_back(); - for (auto ch : input) { - if (ch == '.') { - parts.emplace_back(); - } else { - parts.back().push_back(ch); + static constexpr auto to_string_view = [] (auto &&part) { + return std::string_view(std::addressof(*std::begin(part)), ranges::distance(part)); + }; + + auto parts = static_vector{}; + for (auto &&part : input | ranges::views::split('.') | ranges::views::transform(to_string_view)) { + if (parts.size() == parts.max_size()) { + *validation_error |= true; + return tl::make_unexpected(ipv4_address_errc::too_many_segments); } + parts.emplace_back(part); } if (parts.back().empty()) { @@ -85,7 +90,7 @@ auto parse_ipv4_address( return tl::make_unexpected(ipv4_address_errc::too_many_segments); } - auto numbers = std::vector(); + auto numbers = static_vector{}; for (const auto &part : parts) { if (part.empty()) { @@ -104,7 +109,7 @@ auto parse_ipv4_address( constexpr static auto greater_than_255 = [] (auto number) { return number> 255; }; - auto numbers_first = begin(numbers), numbers_last = end(numbers); + auto numbers_first = std::begin(numbers), numbers_last = std::end(numbers); auto numbers_it = std::find_if(numbers_first, numbers_last, greater_than_255); if (numbers_it != numbers_last) { @@ -128,10 +133,11 @@ auto parse_ipv4_address( numbers.pop_back(); auto counter = 0UL; - for (auto number : numbers) { + for (auto &&number : numbers) { ipv4 += number * static_cast(std::pow(256, 3 - counter)); ++counter; } return ipv4_address(static_cast(ipv4)); } -}} +} // namespace v1 +} // namespace skyr diff --git a/src/v1/network/ipv6_address.cpp b/src/v1/network/ipv6_address.cpp index 1b6362f2..da7e6f93 100644 --- a/src/v1/network/ipv6_address.cpp +++ b/src/v1/network/ipv6_address.cpp @@ -3,20 +3,20 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include #include #include #include #include +#include namespace skyr { inline namespace v1 { [[nodiscard]] auto ipv6_address::serialize() const -> std::string { using namespace std::string_literals; auto output = ""s; - auto compress = std::optional(); + auto compress = std::optional(); - auto sequences = std::vector>(); + auto sequences = static_vector, 8>{}; auto in_sequence = false; auto first = std::begin(address_), last = std::end(address_); @@ -240,4 +240,5 @@ auto parse_ipv6_address( return ipv6_address(address); } -}} +} // namespace v1 +} // namespace skyr diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46773901..a16e034e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,3 +44,4 @@ endif() if (skyr_ENABLE_JSON_FUNCTIONS) add_subdirectory(json) endif() +add_subdirectory(allocations) diff --git a/tests/allocations/CMakeLists.txt b/tests/allocations/CMakeLists.txt new file mode 100644 index 00000000..0d94bc54 --- /dev/null +++ b/tests/allocations/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (c) Glyn Matthews 2020. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +include(${PROJECT_SOURCE_DIR}/cmake/skyr-url-functions.cmake) + +set(full_warnings $) +set(warnings_as_errors $) +set(no_exceptions $) +set(no_rtti $) + +set(gnu $) +set(clang $,$>) +set(libcxx $>) +set(msvc $) + +foreach( + file_name + host_parsing_tests.cpp +) + skyr_remove_extension(${file_name} test) + add_executable(${test} ${file_name}) + add_dependencies(${test} skyr-url) + target_compile_options( + ${test} + PRIVATE + $<$,${full_warnings}>:-Wall> + $<$,${warnings_as_errors}>:-Werror> + $<$,${no_exceptions}>:-fno-exceptions> + $<$,${no_rtti}>:-fno-rtti> + $<$,$>:-flto> + $<${libcxx}:-stdlib=libc++> + + $<$:/W4> + $<$:/WX> + $<$>:/EHsc> + $<$:/GR-> + ) + target_link_libraries( + ${test} + PRIVATE + skyr-url + fmt::fmt + ) + set_target_properties( + ${test} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/tests/allocations/ + ) +endforeach() diff --git a/tests/allocations/allocations.hpp b/tests/allocations/allocations.hpp new file mode 100644 index 00000000..aaa36cef --- /dev/null +++ b/tests/allocations/allocations.hpp @@ -0,0 +1,43 @@ +// Copyright 2020 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef SKYR_ALLOCATIONS_HPP +#define SKYR_ALLOCATIONS_HPP + +#include +#include +#include +#include + + +auto num_allocations = std::optional{}; + +struct skyr_allocation_test_runner { + skyr_allocation_test_runner() { + num_allocations = 0u; + } + ~skyr_allocation_test_runner() { + std::cout << "There were " << num_allocations.value() << " allocations\n\n"; + } +}; + +auto operator new (std::size_t num_bytes) -> void * { + if (num_allocations) { + std::cout << "[" << fmt::format("{:3d}", ++num_allocations.value()) << "] " << num_bytes << std::endl; + } + return std::malloc(num_bytes); // NOLINT +} + +void operator delete (void *p) noexcept { + std::free(p); // NOLINT +} + + +#define SKYR_ALLOCATIONS_START_COUNTING(message) \ + std::cout << "::::: " << message << " ----- \n"; \ + [[maybe_unused]] auto counting_object_ = skyr_allocation_test_runner(); + + +#endif // SKYR_ALLOCATIONS_HPP diff --git a/tests/allocations/host_parsing_tests.cpp b/tests/allocations/host_parsing_tests.cpp new file mode 100644 index 00000000..3f1b763f --- /dev/null +++ b/tests/allocations/host_parsing_tests.cpp @@ -0,0 +1,28 @@ +// Copyright 2020 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt of copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include "allocations.hpp" + +using namespace std::string_view_literals; + +int main() { + const auto host_strings = std::vector{ + "example.com"sv, + "192.168.0.1"sv, + "[2001:0db8:0:0::1428:57ab]"sv, + "localhost"sv, + "a.b.c.d.e.f.g.h.i.j.k.l.example.com"sv, + "sub.llanfairpwllgwyngyllgogerychwndrwbwllllantysiliogogogoch.com"sv + }; + + for (auto &&host_string : host_strings) { + SKYR_ALLOCATIONS_START_COUNTING("skyr::parse_host(\"" << host_string << "\")"); + auto host = skyr::parse_host(host_string); + } +} diff --git a/tests/domain/domain_tests.cpp b/tests/domain/domain_tests.cpp index 4e5f1fc6..de0ae0fb 100644 --- a/tests/domain/domain_tests.cpp +++ b/tests/domain/domain_tests.cpp @@ -14,6 +14,7 @@ TEST_CASE("valid domains to ascii", "[domain]") { auto domain = GENERATE( param{"example.com", "example.com"}, + param{"sub.example.com", "sub.example.com"}, param{"⌘.ws", "xn--bih.ws"}, param{"你好你好", "xn--6qqa088eba"}, param{"你好你好.com", "xn--6qqa088eba.com"}, diff --git a/tools/make_idna_table.py b/tools/make_idna_table.py index 79ebff75..e831b388 100644 --- a/tools/make_idna_table.py +++ b/tools/make_idna_table.py @@ -55,16 +55,6 @@ def can_be_16_bit(self): return self.range[0] <= 0xffff and self.mapped is not None and self.mapped <= 0xffff -def squeeze(code_points): - code_points_copy = [code_points[0]] - for code_point in code_points[1:]: - if code_points_copy[-1].status == code_point.status: - code_points_copy[-1].range[1] = code_point.range[1] - else: - code_points_copy.append(code_point) - return code_points_copy - - def main(): input, output = sys.argv[1], sys.argv[2] @@ -92,7 +82,6 @@ def main(): #include #include -#include #include #include "idna.hpp"