Skip to content
Merged
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
3 changes: 1 addition & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ install(TARGETS skyr-url
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib)


install(EXPORT ${PROJECT_NAME}-targets
install(EXPORT ${PROJECT_NAME}-targets
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}"
NAMESPACE skyr::
FILE "${PROJECT_NAME}-targets.cmake")
Expand Down
22 changes: 22 additions & 0 deletions include/skyr/url.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <skyr/url/url_record.hpp>
#include <skyr/url/url_error.hpp>
#include <skyr/url/url_search_parameters.hpp>
#include <skyr/url/ipv4_address.hpp>
#include <skyr/url/ipv6_address.hpp>
#include <skyr/url/details/to_bytes.hpp>

#if defined(SKYR_PLATFORM_MSVC)
Expand Down Expand Up @@ -364,6 +366,26 @@ class url {
/// \returns An error on failure to parse the new URL
tl::expected<void, std::error_code> set_hostname(string_view hostname);

/// Checks if the hostname is a valid IPv4 address
[[nodiscard]] bool is_ipv4_address() const;

/// Returns an optional ipv4_address value if the hostname is a
/// valid IPv4 address
[[nodiscard]] std::optional<skyr::ipv4_address> ipv4_address() const;

/// Checks if the hostname is a valid IPv6 address
[[nodiscard]] bool is_ipv6_address() const;

/// Returns an optional ipv6_address value if the hostname is a
/// valid IPv6 address
[[nodiscard]] std::optional<skyr::ipv6_address> ipv6_address() const;

/// Checks if the hostname is a valid domain name
[[nodiscard]] bool is_domain() const;

/// Checks if the hostname is a valid domain name
[[nodiscard]] bool is_opaque() const;

/// Returns the [URL port](https://url.spec.whatwg.org/#dom-url-port)
///
/// \returns The [URL port](https://url.spec.whatwg.org/#dom-url-port)
Expand Down
42 changes: 42 additions & 0 deletions include/skyr/url/details/endianness.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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_URL_ENDIANNESS_HPP
#define SKYR_URL_ENDIANNESS_HPP

#include <type_traits>
#include <array>

namespace skyr {
inline namespace v1 {
namespace details {
inline bool is_big_endian() noexcept {
const auto word = 0x0001;
auto bytes = static_cast<const unsigned char *>(static_cast<const void *>(&word));
return bytes[0] != 0x01;
}

template <typename intT>
inline intT swap_endianness(intT v, typename std::enable_if<std::is_integral<intT>::value>::type * = nullptr) noexcept {
constexpr auto byte_count = sizeof(v);
std::array<unsigned char, byte_count> bytes{};
for (auto i = 0UL; i < byte_count; ++i) {
bytes[i] = (v >> (i * 8));
}
return *static_cast<const intT *>(static_cast<const void *>(bytes.data()));
}

inline unsigned int to_network_byte_order(unsigned int v) noexcept {
return (is_big_endian()) ? v : swap_endianness(v);
}

inline unsigned int from_network_byte_order(unsigned int v) noexcept {
return (is_big_endian()) ? v : swap_endianness(v);
}
} // namespace details
} // namespace v1
} // namespace skyr

#endif //SKYR_URL_ENDIANNESS_HPP
19 changes: 16 additions & 3 deletions src/url/ipv4_address.hpp → include/skyr/url/ipv4_address.hpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// Copyright 2018 Glyn Matthews.
// Copyright 2018-20 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_IPV4_ADDRESS_INC
#define SKYR_IPV4_ADDRESS_INC

#include <array>
#include <string>
#include <string_view>
#include <system_error>
#include <optional>
#include <tl/expected.hpp>
#include <skyr/url/details/endianness.hpp>

namespace skyr {
inline namespace v1 {
Expand Down Expand Up @@ -45,12 +47,23 @@ class ipv4_address {
/// Constructor
/// \param address Sets the IPv4 address to `address`
explicit ipv4_address(unsigned int address)
: address_(address) {}
: address_(details::to_network_byte_order(address)) {}

/// The address value
/// \returns The address value
[[nodiscard]] unsigned int address() const noexcept {
return address_;
return details::from_network_byte_order(address_);
}

/// The address in bytes in network byte order
/// \returns The address in bytes
[[nodiscard]] std::array<unsigned char, 4> to_bytes() const noexcept {
return {{
static_cast<unsigned char>(address_ >> 24u),
static_cast<unsigned char>(address_ >> 16u),
static_cast<unsigned char>(address_ >> 8u),
static_cast<unsigned char>(address_ >> 0u)
}};
}

/// \returns The address as a string
Expand Down
25 changes: 19 additions & 6 deletions src/url/ipv6_address.hpp → include/skyr/url/ipv6_address.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 Glyn Matthews.
// Copyright 2018-20 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)
Expand All @@ -13,6 +13,7 @@
#include <iterator>
#include <system_error>
#include <tl/expected.hpp>
#include <skyr/url/details/endianness.hpp>

namespace skyr {
inline namespace v1 {
Expand Down Expand Up @@ -42,19 +43,31 @@ class ipv6_address {

std::array<unsigned short, 8> address_ = {0, 0, 0, 0, 0, 0, 0, 0};

using repr_type = decltype(address_);

public:

/// Constructor
ipv6_address() = default;

/// Constructor
/// \param address Sets the IPv6 address to `address`
explicit ipv6_address(std::array<unsigned short, 8> address)
: address_(address) {}
explicit ipv6_address(std::array<unsigned short, 8> address) {
for (auto i = 0UL; i < address.size(); ++i) {
address_[i] = details::to_network_byte_order(address[i]);
}
}

/// The address in bytes in network byte order
/// \returns The address in bytes
[[nodiscard]] std::array<unsigned char, 16> to_bytes() const noexcept {
std::array<unsigned char, 16> bytes{};
for (auto i = 0UL; i < address_.size(); ++i) {
bytes[i * 2 ] = static_cast<unsigned char>(address_[i] >> 8u);
bytes[i * 2 + 1] = static_cast<unsigned char>(address_[i]);
}
return bytes;
}

/// \returns The IPv4 address as a string
/// \returns The IPv6 address as a string
[[nodiscard]] std::string to_string() const;

};
Expand Down
10 changes: 4 additions & 6 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ set(skyr_SRCS
url/url_parser_context.cpp
url/url_record.cpp
url/ipv4_address.cpp
url/ipv4_address.hpp
url/ipv6_address.cpp
url/ipv6_address.hpp
url/algorithms.hpp
url/url_parse.cpp
url/url_parse_impl.hpp
Expand All @@ -33,7 +31,6 @@ set(skyr_SRCS

${PROJECT_SOURCE_DIR}/include/skyr/config.hpp
${PROJECT_SOURCE_DIR}/include/skyr/version.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/details/to_bytes.hpp
${PROJECT_SOURCE_DIR}/include/skyr/traits/string_traits.hpp
${PROJECT_SOURCE_DIR}/include/skyr/unicode/errors.hpp
${PROJECT_SOURCE_DIR}/include/skyr/unicode/core.hpp
Expand All @@ -48,10 +45,14 @@ set(skyr_SRCS
${PROJECT_SOURCE_DIR}/include/skyr/unicode/ranges/transforms/u32_transform.hpp
${PROJECT_SOURCE_DIR}/include/skyr/unicode/idna.hpp
${PROJECT_SOURCE_DIR}/include/skyr/unicode/domain.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/details/to_bytes.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/details/endianness.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/errors.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_decode_range.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_encode_range.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_encoded_char.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/ipv4_address.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/ipv6_address.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/url_record.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/url_parse.hpp
${PROJECT_SOURCE_DIR}/include/skyr/url/url_serialize.hpp
Expand Down Expand Up @@ -82,6 +83,3 @@ target_include_directories(skyr-url
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
PRIVATE
${PROJECT_SOURCE_DIR}/src)
#
##propagate sources to parent scope for one-lib-build
#set(skyr_SRCS ${skyr_SRCS} PARENT_SCOPE)
2 changes: 1 addition & 1 deletion src/url/ipv4_address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <optional>
#include <cstdlib>
#include <cerrno>
#include "ipv4_address.hpp"
#include <skyr/url/ipv4_address.hpp>

namespace skyr {
inline namespace v1 {
Expand Down
4 changes: 2 additions & 2 deletions src/url/ipv6_address.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 Glyn Matthews.
// Copyright 2018-20 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)
Expand All @@ -10,7 +10,7 @@
#include <locale>
#include <algorithm>
#include <optional>
#include "ipv6_address.hpp"
#include <skyr/url/ipv6_address.hpp>
#include "algorithms.hpp"

namespace skyr {
Expand Down
41 changes: 41 additions & 0 deletions src/url/url.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,47 @@ tl::expected<void, std::error_code> url::set_hostname(string_view hostname) {
});
}

bool url::is_ipv4_address() const {
return parse_ipv4_address(hostname()).has_value();
}

std::optional<skyr::ipv4_address> url::ipv4_address() const {
auto address = parse_ipv4_address(hostname());
return address.has_value() ? std::make_optional(address.value()) : std::nullopt;
}

bool url::is_ipv6_address() const {
if (!url_.host) {
return false;
}
auto view = std::string_view(url_.host.value());
if ((view.size() <= 2) || view.front() != '[' || view.back() != ']') {
return false;
}
return parse_ipv6_address(view.substr(1, view.size() - 2)).has_value();
}

std::optional<skyr::ipv6_address> url::ipv6_address() const {
if (!url_.host) {
return std::nullopt;
}
auto view = std::string_view(url_.host.value());
if ((view.size() <= 2) || view.front() != '[' || view.back() != ']') {
return std::nullopt;
}

auto address = parse_ipv6_address(view.substr(1, view.size() - 2));
return address.has_value() ? std::make_optional(address.value()) : std::nullopt;
}

bool url::is_domain() const {
return details::is_special(url_.scheme) && !hostname().empty() && !is_ipv4_address() && !is_ipv6_address();
}

bool url::is_opaque() const {
return !details::is_special(url_.scheme) && !hostname().empty();
}

url::string_type url::port() const {
if (!url_.port) {
return {};
Expand Down
4 changes: 2 additions & 2 deletions src/url/url_parser_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
#include <skyr/url/percent_encoding/percent_encode_range.hpp>
#include "url_parser_context.hpp"
#include "url_schemes.hpp"
#include "ipv4_address.hpp"
#include "ipv6_address.hpp"
#include "skyr/url/ipv4_address.hpp"
#include "skyr/url/ipv6_address.hpp"
#include "algorithms.hpp"

namespace skyr {
Expand Down
22 changes: 21 additions & 1 deletion tests/url/ipv4_address_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include "../../src/url/ipv4_address.hpp"
#include <skyr/url/ipv4_address.hpp>

TEST_CASE("ipv4 addresses", "[ipv4]") {
using namespace std::string_literals;
Expand Down Expand Up @@ -46,6 +46,13 @@ TEST_CASE("ipv4 addresses", "[ipv4]") {
CHECK(0x814ff5fc == instance.value().address());
}

SECTION("parse_address_looks_short_test") {
const auto address = "129.79.245"s;
auto instance = skyr::parse_ipv4_address(address);
REQUIRE(instance);
CHECK(0x814f00f5 == instance.value().address());
}

SECTION("parse_address_with_hex") {
const auto address = "0x7f.0.0.0x7f"s;
auto instance = skyr::parse_ipv4_address(address);
Expand All @@ -58,4 +65,17 @@ TEST_CASE("ipv4 addresses", "[ipv4]") {
auto instance = skyr::parse_ipv4_address(address);
REQUIRE_FALSE(instance);
}

SECTION("loopback_as_bytes") {
auto instance = skyr::ipv4_address(0x7f000001);
std::array<unsigned char, 4> bytes{{0x7f, 0x00, 0x00, 0x01}};
CHECK(bytes == instance.to_bytes());
}

SECTION("parse_loopback_test_as_bytes") {
auto instance = skyr::parse_ipv4_address("127.0.0.1"s);
REQUIRE(instance);
std::array<unsigned char, 4> bytes{{0x7f, 0x00, 0x00, 0x01}};
CHECK(bytes == instance.value().to_bytes());
}
}
12 changes: 11 additions & 1 deletion tests/url/ipv6_address_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include "../../src/url/ipv6_address.hpp"
#include <skyr/url/ipv6_address.hpp>

TEST_CASE("ipv6_address_tests", "[ipv6]") {
using namespace std::string_literals;
Expand Down Expand Up @@ -133,4 +133,14 @@ TEST_CASE("ipv6_address_tests", "[ipv6]") {
REQUIRE(instance);
CHECK("::ffff:c000:280" == instance.value().to_string());
}

SECTION("loopback_test") {
auto address = std::array<unsigned short, 8>{{0, 0, 0, 0, 0, 0, 0, 1}};
auto instance = skyr::ipv6_address(address);
std::array<unsigned char, 16> bytes{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
}};
CHECK(bytes == instance.to_bytes());
}
}
Loading