diff options
-rw-r--r-- | CMakeLists.txt | 31 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | README.md | 53 | ||||
-rw-r--r-- | include/Uri/Uri.hpp | 342 | ||||
-rw-r--r-- | src/CharacterSet.cpp | 84 | ||||
-rw-r--r-- | src/CharacterSet.hpp | 102 | ||||
-rw-r--r-- | src/PercentEncodedCharacterDecoder.cpp | 103 | ||||
-rw-r--r-- | src/PercentEncodedCharacterDecoder.hpp | 85 | ||||
-rw-r--r-- | src/Uri.cpp | 1470 | ||||
-rw-r--r-- | test/CMakeLists.txt | 29 | ||||
-rw-r--r-- | test/src/CharacterSetTests.cpp | 122 | ||||
-rw-r--r-- | test/src/PercentEncodedCharacterDecoderTests.cpp | 50 | ||||
-rw-r--r-- | test/src/UriTests.cpp | 964 |
13 files changed, 0 insertions, 3438 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 0602120..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# CMakeLists.txt for Uri -# -# © 2018 by Richard Walters - -cmake_minimum_required(VERSION 3.8) -set(This Uri) - -set(Headers - include/Uri/Uri.hpp - src/CharacterSet.hpp - src/PercentEncodedCharacterDecoder.hpp -) - -set(Sources - src/CharacterSet.cpp - src/PercentEncodedCharacterDecoder.cpp - src/Uri.cpp -) - -add_library(${This} STATIC ${Sources} ${Headers}) -set_target_properties(${This} PROPERTIES - FOLDER Libraries -) - -target_include_directories(${This} PUBLIC include) - -target_link_libraries(${This} PUBLIC - StringExtensions -) - -add_subdirectory(test) @@ -10,9 +10,6 @@ categories = ["parser-implementations", "web-programming"] keywords = ["uri", "parser"] repository = "https://github.com/rhymu8354/Uri.git" exclude = [ - "*.hpp", - "*.cpp", - "CMakeLists.txt", "notes.md" ] @@ -26,59 +26,6 @@ The purpose of this library is to provide a `Uri` type to represent a URI, with functions to parse URIs from their string representations, as well as assemble URIs from their various components. -This is a multi-language library containing independent implementations -for the following programming languages: - -* C++ -* Rust - -## Building the C++ Implementation - -A portable library is built which depends only on the C++11 compiler and -standard library, so it should be supported on almost any platform. The -following are recommended toolchains for popular platforms. - -* Windows -- [Visual Studio](https://www.visualstudio.com/) (Microsoft Visual - C++) -* Linux -- clang or gcc -* MacOS -- Xcode (clang) - -This library is not intended to stand alone. It is intended to be included in -a larger solution which uses [CMake](https://cmake.org/) to generate the build -system and build applications which will link with the library. - -There are two distinct steps in the build process: - -1. Generation of the build system, using CMake -2. Compiling, linking, etc., using CMake-compatible toolchain - -### Prerequisites - -* [CMake](https://cmake.org/) version 3.8 or newer -* C++11 toolchain compatible with CMake for your development platform (e.g. - [Visual Studio](https://www.visualstudio.com/) on Windows) - -### Build system generation - -Generate the build system using [CMake](https://cmake.org/) from the solution -root. For example: - -```bash -mkdir build -cd build -cmake -G "Visual Studio 15 2017" -A "x64" .. -``` - -### Compiling, linking, et cetera - -Either use [CMake](https://cmake.org/) or your toolchain's IDE to build. -For [CMake](https://cmake.org/): - -```bash -cd build -cmake --build . --config Release -``` - ## License Licensed under the [MIT license](LICENSE.txt). diff --git a/include/Uri/Uri.hpp b/include/Uri/Uri.hpp deleted file mode 100644 index 431203e..0000000 --- a/include/Uri/Uri.hpp +++ /dev/null @@ -1,342 +0,0 @@ -#ifndef URI_HPP -#define URI_HPP - -/** - * @file Uri.hpp - * - * This module declares the Uri::Uri class. - * - * © 2018 by Richard Walters - */ - -#include <memory> -#include <stdint.h> -#include <string> -#include <vector> - -namespace Uri { - - /** - * This class represents a Uniform Resource Identifier (URI), - * as defined in RFC 3986 (https://tools.ietf.org/html/rfc3986). - */ - class Uri { - // Lifecycle management - public: - ~Uri() noexcept; - Uri(const Uri& other); - Uri(Uri&&) noexcept; - Uri& operator=(const Uri& other); - Uri& operator=(Uri&&) noexcept; - - // Public methods - public: - /** - * This is the default constructor. - */ - Uri(); - - /** - * This is the equality comparison operator for the class. - * - * @param[in] other - * This is the other URI to which to compare this URI. - * - * @return - * An indication of whether or not the two URIs are - * equal is returned. - */ - bool operator==(const Uri& other) const; - - /** - * This is the inequality comparison operator for the class. - * - * @param[in] other - * This is the other URI to which to compare this URI. - * - * @return - * An indication of whether or not the two URIs are - * not equal is returned. - */ - bool operator!=(const Uri& other) const; - - /** - * This method builds the URI from the elements parsed - * from the given string rendering of a URI. - * - * @param[in] uriString - * This is the string rendering of the URI to parse. - * - * @return - * An indication of whether or not the URI was - * parsed successfully is returned. - */ - bool ParseFromString(const std::string& uriString); - - /** - * This method returns the "scheme" element of the URI. - * - * @return - * The "scheme" element of the URI is returned. - * - * @retval "" - * This is returned if there is no "scheme" element in the URI. - */ - std::string GetScheme() const; - - /** - * This method returns the "UserInfo" element of the URI. - * - * @return - * The "UserInfo" element of the URI is returned. - * - * @retval "" - * This is returned if there is no "UserInfo" element in the URI. - */ - std::string GetUserInfo() const; - - /** - * This method returns the "host" element of the URI. - * - * @return - * The "host" element of the URI is returned. - * - * @retval "" - * This is returned if there is no "host" element in the URI. - */ - std::string GetHost() const; - - /** - * This method returns the "path" element of the URI, - * as a sequence of segments. - * - * @note - * If the first segment of the path is an empty string, - * then the URI has an absolute path. - * - * @return - * The "path" element of the URI is returned - * as a sequence of segments. - */ - std::vector< std::string > GetPath() const; - - /** - * This method returns an indication of whether or not the - * URI includes a port number. - * - * @return - * An indication of whether or not the - * URI includes a port number is returned. - */ - bool HasPort() const; - - /** - * This method returns the port number element of the URI, - * if it has one. - * - * @return - * The port number element of the URI is returned. - * - * @note - * The returned port number is only valid if the - * HasPort method returns true. - */ - uint16_t GetPort() const; - - /** - * This method returns an indication of whether or not - * the URI is a relative reference. - * - * @return - * An indication of whether or not the URI is a - * relative reference is returned. - */ - bool IsRelativeReference() const; - - /** - * This method returns an indication of whether or not - * the URI contains a relative path. - * - * @return - * An indication of whether or not the URI contains a - * relative path is returned. - */ - bool ContainsRelativePath() const; - - /** - * This method returns an indication of whether or not the - * URI includes a query. - * - * @return - * An indication of whether or not the - * URI includes a query is returned. - */ - bool HasQuery() const; - - /** - * This method returns the "query" element of the URI, - * if it has one. - * - * @return - * The "query" element of the URI is returned. - * - * @retval "" - * This is returned if there is no "query" element in the URI. - */ - std::string GetQuery() const; - - /** - * This method returns an indication of whether or not the - * URI includes a fragment. - * - * @return - * An indication of whether or not the - * URI includes a fragment is returned. - */ - bool HasFragment() const; - - /** - * This method returns the "fragment" element of the URI, - * if it has one. - * - * @return - * The "fragment" element of the URI is returned. - * - * @retval "" - * This is returned if there is no "fragment" element in the URI. - */ - std::string GetFragment() const; - - /** - * This method applies the "remove_dot_segments" routine talked about - * in RFC 3986 (https://tools.ietf.org/html/rfc3986) to the path - * segments of the URI, in order to normalize the path - * (apply and remove "." and ".." segments). - */ - void NormalizePath(); - - /** - * This method resolves the given relative reference, based on the given - * base URI, returning the resolved target URI. - * - * @param[in] relativeReference - * This describes how to get to the target starting at the base. - * - * @return - * The resolved target URI is returned. - * - * @note - * It only makes sense to call this method on an absolute URI - * (in which I mean, the base URI should be absolute, - * as in IsRelativeReference() should return false). - */ - Uri Resolve(const Uri& relativeReference) const; - - /** - * This method sets the scheme element of the URI. - * - * @param[in] scheme - * This is the scheme to set for the URI. - */ - void SetScheme(const std::string& scheme); - - /** - * This method sets the userinfo element of the URI. - * - * @param[in] userinfo - * This is the userinfo to set for the URI. - */ - void SetUserInfo(const std::string& userinfo); - - /** - * This method sets the host element of the URI. - * - * @param[in] host - * This is the host to set for the URI. - */ - void SetHost(const std::string& host); - - /** - * This method sets the port element of the URI. - * - * @param[in] port - * This is the port to set for the URI. - */ - void SetPort(uint16_t port); - - /** - * This method removes the port element from the URI. - */ - void ClearPort(); - - /** - * This method sets the path element of the URI. - * - * @param[in] path - * This is the sequence of segments to use to form the path - * to set for the URI. - * - * An empty string segment can be used at the front to - * indicate an absolute path (as opposed to a relative one). - * - * An empty string segment can be used at the back to - * make sure the path ends in a delimiter (forward slash) - * when printed out or when combined with another URI - * via the Resolve() method. - */ - void SetPath(const std::vector< std::string >& path); - - /** - * This method removes the query element from the URI. - */ - void ClearQuery(); - - /** - * This method sets the query element of the URI. - * - * @param[in] query - * This is the query to set for the URI. - */ - void SetQuery(const std::string& query); - - /** - * This method removes the fragment element from the URI. - */ - void ClearFragment(); - - /** - * This method sets the fragment element of the URI. - * - * @param[in] fragment - * This is the fragment to set for the URI. - */ - void SetFragment(const std::string& fragment); - - /** - * This method constructs and returns the string - * rendering of the URI, according to the rules - * in RFC 3986 (https://tools.ietf.org/html/rfc3986). - * - * @return - * The string rendering of the URI is returned. - */ - std::string GenerateString() const; - - // Private properties - private: - /** - * This is the type of structure that contains the private - * properties of the instance. It is defined in the implementation - * and declared here to ensure that it is scoped inside the class. - */ - struct Impl; - - /** - * This contains the private properties of the instance. - */ - std::unique_ptr< Impl > impl_; - }; - -} - -#endif /* URI_HPP */ diff --git a/src/CharacterSet.cpp b/src/CharacterSet.cpp deleted file mode 100644 index d0b31a4..0000000 --- a/src/CharacterSet.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @file IsCharacterInSet.cpp - * - * This module contains the implementation of the - * Uri::CharacterSet class. - * - * © 2018 by Richard Walters - */ - -#include "CharacterSet.hpp" - -#include <algorithm> -#include <set> - -namespace Uri { - - /** - * This contains the private properties of the CharacterSet class. - */ - struct CharacterSet::Impl { - /** - * This holds the characters in the set. - */ - std::set< char > charactersInSet; - }; - - CharacterSet::~CharacterSet() noexcept = default; - CharacterSet::CharacterSet(const CharacterSet& other) - : impl_(new Impl(*other.impl_)) - { - } - CharacterSet::CharacterSet(CharacterSet&& other) noexcept = default; - CharacterSet& CharacterSet::operator=(const CharacterSet& other) { - if (this != &other) { - *impl_ = *other.impl_; - } - return *this; - } - CharacterSet& CharacterSet::operator=(CharacterSet&& other) noexcept = default; - - CharacterSet::CharacterSet() - : impl_(new Impl) - { - } - - CharacterSet::CharacterSet(char c) - : impl_(new Impl) - { - (void)impl_->charactersInSet.insert(c); - } - - CharacterSet::CharacterSet(char first, char last) - : impl_(new Impl) - { - if (first > last) { - std::swap(first, last); - } - for (char c = first; c < last + 1; ++c) { - (void)impl_->charactersInSet.insert(c); - } - } - - CharacterSet::CharacterSet( - std::initializer_list< const CharacterSet > characterSets - ) - : impl_(new Impl) - { - for ( - auto characterSet = characterSets.begin(); - characterSet != characterSets.end(); - ++characterSet - ) { - impl_->charactersInSet.insert( - characterSet->impl_->charactersInSet.begin(), - characterSet->impl_->charactersInSet.end() - ); - } - } - - bool CharacterSet::Contains(char c) const { - return impl_->charactersInSet.find(c) != impl_->charactersInSet.end(); - } - -} diff --git a/src/CharacterSet.hpp b/src/CharacterSet.hpp deleted file mode 100644 index 5abefdc..0000000 --- a/src/CharacterSet.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef URI_CHARACTER_SET_HPP -#define URI_CHARACTER_SET_HPP - -/** - * @file CharacterSet.hpp - * - * This module declares the Uri::CharacterSet class. - * - * © 2018 by Richard Walters - */ - -#include <initializer_list> -#include <memory> - -namespace Uri { - - /** - * This represents a set of characters which can be queried - * to find out if a character is in the set or not. - */ - class CharacterSet { - // Lifecycle management - public: - ~CharacterSet() noexcept; - CharacterSet(const CharacterSet&); - CharacterSet(CharacterSet&&) noexcept; - CharacterSet& operator=(const CharacterSet&); - CharacterSet& operator=(CharacterSet&&) noexcept; - - // Methods - public: - /** - * This is the default constructor. - */ - CharacterSet(); - - /** - * This constructs a character set that contains - * just the given character. - * - * @param[in] c - * This is the only character to put in the set. - */ - CharacterSet(char c); - - /** - * This constructs a character set that contains all the - * characters between the given "first" and "last" - * characters, inclusive. - * - * @param[in] first - * This is the first of the range of characters - * to put in the set. - * - * @param[in] last - * This is the last of the range of characters - * to put in the set. - */ - CharacterSet(char first, char last); - - /** - * This constructs a character set that contains all the - * characters in all the other given character sets. - * - * @param[in] characterSets - * These are the character sets to include. - */ - CharacterSet( - std::initializer_list< const CharacterSet > characterSets - ); - - /** - * This method checks to see if the given character - * is in the character set. - * - * @param[in] c - * This is the character to check. - * - * @return - * An indication of whether or not the given character - * is in the character set is returned. - */ - bool Contains(char c) const; - - // Private Properties - private: - /** - * This is the type of structure that contains the private - * properties of the instance. It is defined in the implementation - * and declared here to ensure that it is scoped inside the class. - */ - struct Impl; - - /** - * This contains the private properties of the instance. - */ - std::unique_ptr< Impl > impl_; - }; - -} - -#endif /* URI_CHARACTER_SET_HPP */ diff --git a/src/PercentEncodedCharacterDecoder.cpp b/src/PercentEncodedCharacterDecoder.cpp deleted file mode 100644 index 442befe..0000000 --- a/src/PercentEncodedCharacterDecoder.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @file PercentEncodedCharacterDecoder.cpp - * - * This module contains the implementation of the - * Uri::PercentEncodedCharacterDecoder class. - * - * © 2018 by Richard Walters - */ - -#include "CharacterSet.hpp" -#include "PercentEncodedCharacterDecoder.hpp" - -namespace { - - /** - * This is the character set containing just numbers. - */ - const Uri::CharacterSet DIGIT('0', '9'); - - /** - * This is the character set containing just the upper-case - * letters 'A' through 'F', used in upper-case hexadecimal. - */ - const Uri::CharacterSet HEX_UPPER('A', 'F'); - - /** - * This is the character set containing just the lower-case - * letters 'a' through 'f', used in lower-case hexadecimal. - */ - const Uri::CharacterSet HEX_LOWER('a', 'f'); - -} - -namespace Uri { - - struct PercentEncodedCharacterDecoder::Impl { - // Properties - - /** - * This is the decoded character. - */ - int decodedCharacter = 0; - - /** - * This is the number of digits that we still need to shift in - * to decode the character. - */ - size_t digitsLeft = 2; - - // Methods - - /** - * This method shifts in the given hex digit as part of - * building the decoded character. - * - * @param[in] c - * This is the hex digit to shift into the decoded character. - * - * @return - * An indication of whether or not the given hex digit - * was valid is returned. - */ - bool ShiftInHexDigit(char c) { - decodedCharacter <<= 4; - if (DIGIT.Contains(c)) { - decodedCharacter += (int)(c - '0'); - } else if (HEX_UPPER.Contains(c)) { - decodedCharacter += (int)(c - 'A') + 10; - } else if (HEX_LOWER.Contains(c)) { - decodedCharacter += (int)(c - 'a') + 10; - } else { - return false; - } - return true; - } - }; - - PercentEncodedCharacterDecoder::~PercentEncodedCharacterDecoder() noexcept = default; - PercentEncodedCharacterDecoder::PercentEncodedCharacterDecoder(PercentEncodedCharacterDecoder&&) noexcept = default; - PercentEncodedCharacterDecoder& PercentEncodedCharacterDecoder::operator=(PercentEncodedCharacterDecoder&&) noexcept = default; - - PercentEncodedCharacterDecoder::PercentEncodedCharacterDecoder() - : impl_(new Impl) - { - } - - bool PercentEncodedCharacterDecoder::NextEncodedCharacter(char c) { - if (!impl_->ShiftInHexDigit(c)) { - return false; - } - --impl_->digitsLeft; - return true; - } - - bool PercentEncodedCharacterDecoder::Done() const { - return (impl_->digitsLeft == 0); - } - - char PercentEncodedCharacterDecoder::GetDecodedCharacter() const { - return (char)impl_->decodedCharacter; - } - -} diff --git a/src/PercentEncodedCharacterDecoder.hpp b/src/PercentEncodedCharacterDecoder.hpp deleted file mode 100644 index 04f769c..0000000 --- a/src/PercentEncodedCharacterDecoder.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef URI_PERCENT_ENCODED_CHARACTER_DECODER_HPP -#define URI_PERCENT_ENCODED_CHARACTER_DECODER_HPP - -/** - * @file PercentEncodedCharacterDecoder.hpp - * - * This module declares the Uri::PercentEncodedCharacterDecoder class. - * - * © 2018 by Richard Walters - */ - -#include <memory> -#include <stddef.h> - -namespace Uri { - - /** - * This class can take in a percent-encoded character, - * decode it, and also detect if there are any problems in the encoding. - */ - class PercentEncodedCharacterDecoder { - // Lifecycle management - public: - ~PercentEncodedCharacterDecoder() noexcept; - PercentEncodedCharacterDecoder(const PercentEncodedCharacterDecoder&) = delete; - PercentEncodedCharacterDecoder(PercentEncodedCharacterDecoder&&) noexcept; - PercentEncodedCharacterDecoder& operator=(const PercentEncodedCharacterDecoder&) = delete; - PercentEncodedCharacterDecoder& operator=(PercentEncodedCharacterDecoder&&) noexcept; - - // Methods - public: - /** - * This is the default constructor. - */ - PercentEncodedCharacterDecoder(); - - /** - * This method inputs the next encoded character. - * - * @param[in] c - * This is the next encoded character to give to the decoder. - * - * @return - * An indication of whether or not the encoded character - * was accepted is returned. - */ - bool NextEncodedCharacter(char c); - - /** - * This method checks to see if the decoder is done - * and has decoded the encoded character. - * - * @return - * An indication of whether or not the decoder is done - * and has decoded the encoded character is returned. - */ - bool Done() const; - - /** - * This method returns the decoded character, once - * the decoder is done. - * - * @return - * The decoded character is returned. - */ - char GetDecodedCharacter() const; - - // Properties - private: - /** - * This is the type of structure that contains the private - * properties of the instance. It is defined in the implementation - * and declared here to ensure that it is scoped inside the class. - */ - struct Impl; - - /** - * This contains the private properties of the instance. - */ - std::unique_ptr< Impl > impl_; - }; - -} - -#endif /* URI_PERCENT_ENCODED_CHARACTER_DECODER_HPP */ diff --git a/src/Uri.cpp b/src/Uri.cpp deleted file mode 100644 index ff161f3..0000000 --- a/src/Uri.cpp +++ /dev/null @@ -1,1470 +0,0 @@ -/** - * @file Uri.cpp - * - * This module contains the implementation of the Uri::Uri class. - * - * © 2018 by Richard Walters - */ - -#include "CharacterSet.hpp" -#include "PercentEncodedCharacterDecoder.hpp" - -#include <algorithm> -#include <functional> -#include <inttypes.h> -#include <limits> -#include <memory> -#include <sstream> -#include <string> -#include <StringExtensions/StringExtensions.hpp> -#include <Uri/Uri.hpp> -#include <vector> - -namespace { - - /** - * This is the character set containing just the alphabetic characters - * from the ASCII character set. - */ - const Uri::CharacterSet ALPHA{ - Uri::CharacterSet('a', 'z'), - Uri::CharacterSet('A', 'Z') - }; - - /** - * This is the character set containing just numbers. - */ - const Uri::CharacterSet DIGIT('0', '9'); - - /** - * This is the character set containing just the characters allowed - * in a hexadecimal digit. - */ - const Uri::CharacterSet HEXDIG{ - Uri::CharacterSet('0', '9'), - Uri::CharacterSet('A', 'F'), - Uri::CharacterSet('a', 'f') - }; - - /** - * This is the character set corresponds to the "unreserved" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). - */ - const Uri::CharacterSet UNRESERVED{ - ALPHA, - DIGIT, - '-', '.', '_', '~' - }; - - /** - * This is the character set corresponds to the "sub-delims" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). - */ - const Uri::CharacterSet SUB_DELIMS{ - '!', '$', '&', '\'', '(', ')', - '*', '+', ',', ';', '=' - }; - - /** - * This is the character set corresponds to the second part - * of the "scheme" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). - */ - const Uri::CharacterSet SCHEME_NOT_FIRST{ - ALPHA, - DIGIT, - '+', '-', '.', - }; - - /** - * This is the character set corresponds to the "pchar" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), - * leaving out "pct-encoded". - */ - const Uri::CharacterSet PCHAR_NOT_PCT_ENCODED{ - UNRESERVED, - SUB_DELIMS, - ':', '@' - }; - - /** - * This is the character set corresponds to the "query" syntax - * and the "fragment" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), - * leaving out "pct-encoded". - */ - const Uri::CharacterSet QUERY_OR_FRAGMENT_NOT_PCT_ENCODED{ - PCHAR_NOT_PCT_ENCODED, - '/', '?' - }; - - /** - * This is the character set almost corresponds to the "query" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), - * leaving out "pct-encoded", except that '+' is also excluded, because - * for some web services (e.g. AWS S3) a '+' is treated as - * synonymous with a space (' ') and thus gets misinterpreted. - */ - const Uri::CharacterSet QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS{ - UNRESERVED, - '!', '$', '&', '\'', '(', ')', - '*', ',', ';', '=', - ':', '@', - '/', '?' - }; - - /** - * This is the character set corresponds to the "userinfo" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), - * leaving out "pct-encoded". - */ - const Uri::CharacterSet USER_INFO_NOT_PCT_ENCODED{ - UNRESERVED, - SUB_DELIMS, - ':', - }; - - /** - * This is the character set corresponds to the "reg-name" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), - * leaving out "pct-encoded". - */ - const Uri::CharacterSet REG_NAME_NOT_PCT_ENCODED{ - UNRESERVED, - SUB_DELIMS - }; - - /** - * This is the character set corresponds to the last part of - * the "IPvFuture" syntax - * specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). - */ - const Uri::CharacterSet IPV_FUTURE_LAST_PART{ - UNRESERVED, - SUB_DELIMS, - ':' - }; - - /** - * This function checks to make sure the given string - * is a valid rendering of an octet as a decimal number. - * - * @param[in] octetString - * This is the octet string to validate. - * - * @return - * An indication of whether or not the given astring - * is a valid rendering of an octet as a - * decimal number is returned. - */ - bool ValidateOctet(const std::string& octetString) { - int octet = 0; - for (auto c: octetString) { - if (DIGIT.Contains(c)) { - octet *= 10; - octet += (int)(c - '0'); - } else { - return false; - } - } - return (octet <= 255); - } - - /** - * This function checks to make sure the given address - * is a valid IPv6 address according to the rules in - * RFC 3986 (https://tools.ietf.org/html/rfc3986). - * - * @param[in] address - * This is the IPv6 address to validate. - * - * @return - * An indication of whether or not the given address - * is a valid IPv6 address is returned. - */ - bool ValidateIpv4Address(const std::string& address) { - size_t numGroups = 0; - size_t state = 0; - std::string octetBuffer; - for (auto c: address) { - switch (state) { - case 0: { // not in an octet yet - if (DIGIT.Contains(c)) { - octetBuffer.push_back(c); - state = 1; - } else { - return false; - } - } break; - - case 1: { // expect a digit or dot - if (c == '.') { - if (numGroups++ >= 4) { - return false; - } - if (!ValidateOctet(octetBuffer)) { - return false; - } - octetBuffer.clear(); - state = 0; - } else if (DIGIT.Contains(c)) { - octetBuffer.push_back(c); - } else { - return false; - } - } break; - } - } - if (!octetBuffer.empty()) { - ++numGroups; - if (!ValidateOctet(octetBuffer)) { - return false; - } - } - return (numGroups == 4); - } - - /** - * This function checks to make sure the given address - * is a valid IPv6 address according to the rules in - * RFC 3986 (https://tools.ietf.org/html/rfc3986). - * - * @param[in] address - * This is the IPv6 address to validate. - * - * @return - * An indication of whether or not the given address - * is a valid IPv6 address is returned. - */ - bool ValidateIpv6Address(const std::string& address) { - enum class ValidationState { - NO_GROUPS_YET, - COLON_BUT_NO_GROUPS_YET, - AFTER_DOUBLE_COLON, - IN_GROUP_NOT_IPV4, - IN_GROUP_COULD_BE_IPV4, - COLON_AFTER_GROUP, - } state = ValidationState::NO_GROUPS_YET; - size_t numGroups = 0; - size_t numDigits = 0; - bool doubleColonEncountered = false; - size_t potentialIpv4AddressStart = 0; - size_t position = 0; - bool ipv4AddressEncountered = false; - for (auto c: address) { - switch (state) { - case ValidationState::NO_GROUPS_YET: { - if (c == ':') { - state = ValidationState::COLON_BUT_NO_GROUPS_YET; - } else if (DIGIT.Contains(c)) { - potentialIpv4AddressStart = position; - numDigits = 1; - state = ValidationState::IN_GROUP_COULD_BE_IPV4; - } else if (HEXDIG.Contains(c)) { - numDigits = 1; - state = ValidationState::IN_GROUP_NOT_IPV4; - } else { - return false; - } - } break; - - case ValidationState::COLON_BUT_NO_GROUPS_YET: { - if (c == ':') { - doubleColonEncountered = true; - state = ValidationState::AFTER_DOUBLE_COLON; - } else { - return false; - } - } break; - - case ValidationState::AFTER_DOUBLE_COLON: { - if (DIGIT.Contains(c)) { - potentialIpv4AddressStart = position; - if (++numDigits > 4) { - return false; - } - state = ValidationState::IN_GROUP_COULD_BE_IPV4; - } else if (HEXDIG.Contains(c)) { - if (++numDigits > 4) { - return false; - } - state = ValidationState::IN_GROUP_NOT_IPV4; - } else { - return false; - } - } break; - - case ValidationState::IN_GROUP_NOT_IPV4: { - if (c == ':') { - numDigits = 0; - ++numGroups; - state = ValidationState::COLON_AFTER_GROUP; - } else if (HEXDIG.Contains(c)) { - if (++numDigits > 4) { - return false; - } - } else { - return false; - } - } break; - - case ValidationState::IN_GROUP_COULD_BE_IPV4: { - if (c == ':') { - numDigits = 0; - ++numGroups; - state = ValidationState::COLON_AFTER_GROUP; - } else if (c == '.') { - ipv4AddressEncountered = true; - break; - } else if (DIGIT.Contains(c)) { - if (++numDigits > 4) { - return false; - } - } else if (HEXDIG.Contains(c)) { - if (++numDigits > 4) { - return false; - } - state = ValidationState::IN_GROUP_NOT_IPV4; - } else { - return false; - } - } break; - - case ValidationState::COLON_AFTER_GROUP: { - if (c == ':') { - if (doubleColonEncountered) { - return false; - } else { - doubleColonEncountered = true; - state = ValidationState::AFTER_DOUBLE_COLON; - } - } else if (DIGIT.Contains(c)) { - potentialIpv4AddressStart = position; - ++numDigits; - state = ValidationState::IN_GROUP_COULD_BE_IPV4; - } else if (HEXDIG.Contains(c)) { - ++numDigits; - state = ValidationState::IN_GROUP_NOT_IPV4; - } else { - return false; - } - } break; - } - if (ipv4AddressEncountered) { - break; - } - ++position; - } - if ( - (state == ValidationState::IN_GROUP_NOT_IPV4) - || (state == ValidationState::IN_GROUP_COULD_BE_IPV4) - ) { - // count trailing group - ++numGroups; - } - if ( - (position == address.length()) - && ( - (state == ValidationState::COLON_BUT_NO_GROUPS_YET) - || (state == ValidationState::COLON_AFTER_GROUP) - ) - ) { // trailing single colon - return false; - } - if (ipv4AddressEncountered) { - if (!ValidateIpv4Address(address.substr(potentialIpv4AddressStart))) { - return false; - } - numGroups += 2; - } - if (doubleColonEncountered) { - // A double colon matches one or more groups (of 0). - return (numGroups <= 7); - } else { - return (numGroups == 8); - } - } - - /** - * This function takes a given "stillPassing" strategy - * and invokes it on the sequence of characters in the given - * string, to check if the string passes or not. - * - * @param[in] candidate - * This is the string to test. - * - * @param[in] stillPassing - * This is the strategy to invoke in order to test the string. - * - * @return - * An indication of whether or not the given candidate string - * passes the test is returned. - */ - bool FailsMatch( - const std::string& candidate, - std::function< bool(char, bool) > stillPassing - ) { - for (const auto c: candidate) { - if (!stillPassing(c, false)) { - return true; - } - } - return !stillPassing(' ', true); - } - - /** - * This function returns a strategy function that - * may be used with the FailsMatch function to test a scheme - * to make sure it is legal according to the standard. - * - * @return - * A strategy function that may be used with the - * FailsMatch function to test a scheme to make sure - * it is legal according to the standard is returned. - */ - std::function< bool(char, bool) > LegalSchemeCheckStrategy() { - auto isFirstCharacter = std::make_shared< bool >(true); - return [isFirstCharacter](char c, bool end){ - if (end) { - return !*isFirstCharacter; - } else { - bool check; - if (*isFirstCharacter) { - check = ALPHA.Contains(c); - } else { - check = SCHEME_NOT_FIRST.Contains(c); - } - *isFirstCharacter = false; - return check; - } - }; - } - - /** - * This method checks and decodes the given URI element. - * What we are calling a "URI element" is any part of the URI - * which is a sequence of characters that: - * - may be percent-encoded - * - if not percent-encoded, are in a restricted set of characters - * - * @param[in,out] element - * On input, this is the element to check and decode. - * On output, this is the decoded element. - * - * @param[in] allowedCharacters - * This is the set of characters that do not need to - * be percent-encoded. - * - * @return - * An indication of whether or not the element - * passed all checks and was decoded successfully is returned. - */ - bool DecodeElement( - std::string& element, - const Uri::CharacterSet& allowedCharacters - ) { - const auto originalSegment = std::move(element); - element.clear(); - bool decodingPec = false; - Uri::PercentEncodedCharacterDecoder pecDecoder; - for (const auto c: originalSegment) { - if (decodingPec) { - if (!pecDecoder.NextEncodedCharacter(c)) { - return false; - } - if (pecDecoder.Done()) { - decodingPec = false; - element.push_back((char)pecDecoder.GetDecodedCharacter()); - } - } else if (c == '%') { - decodingPec = true; - pecDecoder = Uri::PercentEncodedCharacterDecoder(); - } else { - if (allowedCharacters.Contains(c)) { - element.push_back(c); - } else { - return false; - } - } - } - return true; - } - - /** - * This function returns the hex digit that corresponds - * to the given value. - * - * @param[in] value - * This is the value to convert to a hex digit. - * - * @return - * The hex digit corresponding to the given value is returned. - */ - char MakeHexDigit(unsigned int value) { - if (value < 10) { - return (char)(value + '0'); - } else { - return (char)(value - 10 + 'A'); - } - } - - /** - * This method encodes the given URI element. - * What we are calling a "URI element" is any part of the URI - * which is a sequence of characters that: - * - may be percent-encoded - * - if not percent-encoded, are in a restricted set of characters - * - * @param[in] element - * This is the element to encode. - * - * @param[in] allowedCharacters - * This is the set of characters that do not need to - * be percent-encoded. - * - * @return - * The encoded element is returned. - */ - std::string EncodeElement( - const std::string& element, - const Uri::CharacterSet& allowedCharacters - ) { - std::string encodedElement; - for (uint8_t c: element) { - if (allowedCharacters.Contains(c)) { - encodedElement.push_back(c); - } else { - encodedElement.push_back('%'); - encodedElement.push_back(MakeHexDigit((unsigned int)c >> 4)); - encodedElement.push_back(MakeHexDigit((unsigned int)c & 0x0F)); - } - } - return encodedElement; - } - - /** - * This method checks and decodes the given query or fragment. - * - * @param[in,out] queryOrFragment - * On input, this is the query or fragment to check and decode. - * On output, this is the decoded query or fragment. - * - * @return - * An indication of whether or not the query or fragment - * passed all checks and was decoded successfully is returned. - */ - bool DecodeQueryOrFragment(std::string& queryOrFragment) { - return DecodeElement( - queryOrFragment, - QUERY_OR_FRAGMENT_NOT_PCT_ENCODED - ); - } - -} - -namespace Uri { - /** - * This contains the private properties of a Uri instance. - */ - struct Uri::Impl { - // Properties - - /** - * This is the "scheme" element of the URI. - */ - std::string scheme; - - /** - * This is the "UserInfo" element of the URI. - */ - std::string userInfo; - - /** - * This is the "host" element of the URI. - */ - std::string host; - - /** - * This flag indicates whether or not the - * URI includes a port number. - */ - bool hasPort = false; - - /** - * This is the port number element of the URI. - */ - uint16_t port = 0; - - /** - * This is the "path" element of the URI, - * as a sequence of segments. - */ - std::vector< std::string > path; - - /** - * This flag indicates whether or not the - * URI includes a query. - */ - bool hasQuery = false; - - /** - * This is the "query" element of the URI, - * if it has one. - */ - std::string query; - - /** - * This flag indicates whether or not the - * URI includes a fragment. - */ - bool hasFragment = false; - - /** - * This is the "fragment" element of the URI, - * if it has one. - */ - std::string fragment; - - // Methods - - /** - * This method returns an indication of whether or not - * the URI includes any element that is part of the - * authority of the URI. - * - * @return - * An indication of whether or not the URI includes - * any element that is part of the authority of the - * URI is returned. - */ - bool HasAuthority() const { - return ( - !host.empty() - || !userInfo.empty() - || hasPort - ); - } - - /** - * This method builds the internal path element sequence - * by parsing it from the given path string. - * - * @param[in] pathString - * This is the string containing the whole path of the URI. - * - * @return - * An indication if the path was parsed correctly or not - * is returned. - */ - bool ParsePath(std::string pathString) { - path.clear(); - if (pathString == "/") { - // Special case of a path that is empty but needs a single - // empty-string element to indicate that it is absolute. - path.push_back(""); - pathString.clear(); - } else if (!pathString.empty()) { - for(;;) { - auto pathDelimiter = pathString.find('/'); - if (pathDelimiter == std::string::npos) { - path.push_back(pathString); - pathString.clear(); - break; - } else { - path.emplace_back( - pathString.begin(), - pathString.begin() + pathDelimiter - ); - pathString = pathString.substr(pathDelimiter + 1); - } - } - } - for (auto& segment: path) { - if (!DecodeElement(segment, PCHAR_NOT_PCT_ENCODED)) { - return false; - } - } - return true; - } - - /** - * This method parses the elements that make up the authority - * composite part of the URI, by parsing it from the given string. - * - * @param[in] authorityString - * This is the string containing the whole authority part - * of the URI. - * - * @return - * An indication if the path was parsed correctly or not - * is returned. - */ - bool ParseAuthority(const std::string& authorityString) { - /** - * These are the various states for the state machine implemented - * below to correctly split up and validate the URI substring - * containing the host and potentially a port number as well. - */ - enum class HostParsingState { - FIRST_CHARACTER, - NOT_IP_LITERAL, - PERCENT_ENCODED_CHARACTER, - IP_LITERAL, - IPV6_ADDRESS, - IPV_FUTURE_NUMBER, - IPV_FUTURE_BODY, - GARBAGE_CHECK, - PORT, - }; - - // Next, check if there is a UserInfo, and if so, extract it. - const auto userInfoDelimiter = authorityString.find('@'); - std::string hostPortString; - userInfo.clear(); - if (userInfoDelimiter == std::string::npos) { - hostPortString = authorityString; - } else { - userInfo = authorityString.substr(0, userInfoDelimiter); - if (!DecodeElement(userInfo, USER_INFO_NOT_PCT_ENCODED)) { - return false; - } - hostPortString = authorityString.substr(userInfoDelimiter + 1); - } - - // Next, parsing host and port from authority and path. - std::string portString; - HostParsingState hostParsingState = HostParsingState::FIRST_CHARACTER; - host.clear(); - PercentEncodedCharacterDecoder pecDecoder; - bool hostIsRegName = false; - for (const auto c: hostPortString) { - switch(hostParsingState) { - case HostParsingState::FIRST_CHARACTER: { - if (c == '[') { - hostParsingState = HostParsingState::IP_LITERAL; - break; - } else { - hostParsingState = HostParsingState::NOT_IP_LITERAL; - hostIsRegName = true; - } - } - - case HostParsingState::NOT_IP_LITERAL: { - if (c == '%') { - pecDecoder = PercentEncodedCharacterDecoder(); - hostParsingState = HostParsingState::PERCENT_ENCODED_CHARACTER; - } else if (c == ':') { - hostParsingState = HostParsingState::PORT; - } else { - if (REG_NAME_NOT_PCT_ENCODED.Contains(c)) { - host.push_back(c); - } else { - return false; - } - } - } break; - - case HostParsingState::PERCENT_ENCODED_CHARACTER: { - if (!pecDecoder.NextEncodedCharacter(c)) { - return false; - } - if (pecDecoder.Done()) { - hostParsingState = HostParsingState::NOT_IP_LITERAL; - host.push_back((char)pecDecoder.GetDecodedCharacter()); - } - } break; - - case HostParsingState::IP_LITERAL: { - if (c == 'v') { - host.push_back(c); - hostParsingState = HostParsingState::IPV_FUTURE_NUMBER; - break; - } else { - hostParsingState = HostParsingState::IPV6_ADDRESS; - } - } - - case HostParsingState::IPV6_ADDRESS: { - if (c == ']') { - if (!ValidateIpv6Address(host)) { - return false; - } - hostParsingState = HostParsingState::GARBAGE_CHECK; - } else { - host.push_back(c); - } - } break; - - case HostParsingState::IPV_FUTURE_NUMBER: { - if (c == '.') { - hostParsingState = HostParsingState::IPV_FUTURE_BODY; - } else if (!HEXDIG.Contains(c)) { - return false; - } - host.push_back(c); - } break; - - case HostParsingState::IPV_FUTURE_BODY: { - if (c == ']') { - hostParsingState = HostParsingState::GARBAGE_CHECK; - } else if (!IPV_FUTURE_LAST_PART.Contains(c)) { - return false; - } else { - host.push_back(c); - } - } break; - - case HostParsingState::GARBAGE_CHECK: { - // illegal to have anything else, unless it's a colon, - // in which case it's a port delimiter - if (c == ':') { - hostParsingState = HostParsingState::PORT; - } else { - return false; - } - } break; - - case HostParsingState::PORT: { - portString.push_back(c); - } break; - } - } - if ( - (hostParsingState != HostParsingState::FIRST_CHARACTER) - && (hostParsingState != HostParsingState::NOT_IP_LITERAL) - && (hostParsingState != HostParsingState::GARBAGE_CHECK) - && (hostParsingState != HostParsingState::PORT) - ) { - // truncated or ended early - return false; - } - if (hostIsRegName) { - host = StringExtensions::ToLower(host); - } - if (portString.empty()) { - hasPort = false; - } else { - intmax_t portAsInt; - if ( - StringExtensions::ToInteger( - portString, - portAsInt - ) != StringExtensions::ToIntegerResult::Success - ) { - return false; - } - if ( - (portAsInt < 0) - || (portAsInt > (decltype(portAsInt))std::numeric_limits< decltype(port) >::max()) - ) { - return false; - } - port = (decltype(port))portAsInt; - hasPort = true; - } - return true; - } - - /** - * This method takes an unparsed URI string and separates out - * the scheme (if any) and parses it, returning the remainder - * of the unparsed URI string. - * - * @param[in] authorityAndPathString - * This is the the part of an unparsed URI consisting - * of the authority (if any) followed by the path. - * - * @param[out] pathString - * This is where to store the the path - * part of the input string. - * - * @return - * An indication of whether or not the given input string - * was successfully parsed is returned. - */ - bool ParseScheme( - const std::string& uriString, - std::string& rest - ) { - // Limit our search so we don't scan into the authority - // or path elements, because these may have the colon - // character as well, which we might misinterpret - // as the scheme delimiter. - auto authorityOrPathDelimiterStart = uriString.find('/'); - if (authorityOrPathDelimiterStart == std::string::npos) { - authorityOrPathDelimiterStart = uriString.length(); - } - const auto schemeEnd = uriString.substr(0, authorityOrPathDelimiterStart).find(':'); - if (schemeEnd == std::string::npos) { - scheme.clear(); - rest = uriString; - } else { - scheme = uriString.substr(0, schemeEnd); - if ( - FailsMatch( - scheme, - LegalSchemeCheckStrategy() - ) - ) { - return false; - } - scheme = StringExtensions::ToLower(scheme); - rest = uriString.substr(schemeEnd + 1); - } - return true; - } - - /** - * This method takes the part of an unparsed URI consisting - * of the authority (if any) followed by the path, and divides - * it into the authority and path parts, storing any authority - * information in the internal state, and returning the path - * part of the input string. - * - * @param[in] authorityAndPathString - * This is the the part of an unparsed URI consisting - * of the authority (if any) followed by the path. - * - * @param[out] pathString - * This is where to store the the path - * part of the input string. - * - * @return - * An indication of whether or not the given input string - * was successfully parsed is returned. - */ - bool SplitAuthorityFromPathAndParseIt( - std::string authorityAndPathString, - std::string& pathString - ) { - // Split authority from path. If there is an authority, parse it. - if (authorityAndPathString.substr(0, 2) == "//") { - // Strip off authority marker. - authorityAndPathString = authorityAndPathString.substr(2); - - // First separate the authority from the path. - auto authorityEnd = authorityAndPathString.find('/'); - if (authorityEnd == std::string::npos) { - authorityEnd = authorityAndPathString.length(); - } - pathString = authorityAndPathString.substr(authorityEnd); - auto authorityString = authorityAndPathString.substr(0, authorityEnd); - - // Parse the elements inside the authority string. - if (!ParseAuthority(authorityString)) { - return false; - } - } else { - userInfo.clear(); - host.clear(); - hasPort = false; - pathString = authorityAndPathString; - } - return true; - } - - /** - * This method handles the special case of the URI having an - * authority but having an empty path. In this case it sets - * the path as "/". - */ - void SetDefaultPathIfAuthorityPresentAndPathEmpty() { - if ( - !host.empty() - && path.empty() - ) { - path.push_back(""); - } - } - - /** - * This method takes the part of a URI string that has just - * the query element with its delimiter, and breaks off - * and decodes the query. - * - * @param[in] queryWithDelimiter - * This is the part of a URI string that has just - * the query element with its delimiter. - * - * @return - * An indication of whether or not the method succeeded - * is returned. - */ - bool ParseQuery(const std::string& queryWithDelimiter) { - hasQuery = !queryWithDelimiter.empty(); - if (hasQuery) { - query = queryWithDelimiter.substr(1); - } else { - query.clear(); - } - return DecodeQueryOrFragment(query); - } - - /** - * This method takes the part of a URI string that has just - * the query and/or fragment elements, and breaks off - * and decodes the fragment part, returning the rest, - * which will be either empty or have the query with the - * query delimiter still attached. - * - * @param[in] queryAndOrFragment - * This is the part of a URI string that has just - * the query and/or fragment elements. - * - * @param[out] rest - * This is where to store the rest of the input string - * after removing any fragment and fragment delimiter. - * - * @return - * An indication of whether or not the method succeeded - * is returned. - */ - bool ParseFragment( - const std::string& queryAndOrFragment, - std::string& rest - ) { - const auto fragmentDelimiter = queryAndOrFragment.find('#'); - if (fragmentDelimiter == std::string::npos) { - hasFragment = false; - fragment.clear(); - rest = queryAndOrFragment; - } else { - hasFragment = true; - fragment = queryAndOrFragment.substr(fragmentDelimiter + 1); - rest = queryAndOrFragment.substr(0, fragmentDelimiter); - } - return DecodeQueryOrFragment(fragment); - } - - /** - * This method determines whether or not it makes sense to - * navigate one level up from the current path - * (in other words, does appending ".." to the path - * actually change the path?) - * - * @return - * An indication of whether or not it makes sense to - * navigate one level up from the current path is returned. - */ - bool CanNavigatePathUpOneLevel() const { - return ( - !IsPathAbsolute() - || (path.size() > 1) - ); - } - - /** - * This method applies the "remove_dot_segments" routine talked about - * in RFC 3986 (https://tools.ietf.org/html/rfc3986) to the path - * segments of the URI, in order to normalize the path - * (apply and remove "." and ".." segments). - */ - void NormalizePath() { - // Rebuild the path one segment - // at a time, removing and applying special - // navigation segments ("." and "..") as we go. - auto oldPath = std::move(path); - path.clear(); - bool atDirectoryLevel = false; - for (const auto segment: oldPath) { - if (segment == ".") { - atDirectoryLevel = true; - } else if (segment == "..") { - // Remove last path element - // if we can navigate up a level. - if (!path.empty()) { - if (CanNavigatePathUpOneLevel()) { - path.pop_back(); - } - } - atDirectoryLevel = true; - } else { - // Non-relative elements can just - // transfer over fine. An empty - // segment marks a transition to - // a directory level context. If we're - // already in that context, we - // want to ignore the transition. - if ( - !atDirectoryLevel - || !segment.empty() - ) { - path.push_back(segment); - } - atDirectoryLevel = segment.empty(); - } - } - - // If at the end of rebuilding the path, - // we're in a directory level context, - // add an empty segment to mark the fact. - if ( - atDirectoryLevel - && ( - !path.empty() - && !path.back().empty() - ) - ) { - path.push_back(""); - } - } - - /** - * This method replaces the URI's scheme with that of - * another URI. - * - * @param[in] other - * This is the other URI from which to copy the scheme. - */ - void CopyScheme(const Uri& other) { - scheme = other.impl_->scheme; - } - - /** - * This method replaces the URI's authority with that of - * another URI. - * - * @param[in] other - * This is the other URI from which to copy the authority. - */ - void CopyAuthority(const Uri& other) { - host = other.impl_->host; - userInfo = other.impl_->userInfo; - hasPort = other.impl_->hasPort; - port = other.impl_->port; - } - - /** - * This method replaces the URI's path with that of - * another URI. - * - * @param[in] other - * This is the other URI from which to copy the path. - */ - void CopyPath(const Uri& other) { - path = other.impl_->path; - } - - /** - * This method replaces the URI's path with that of - * the normalized form of another URI. - * - * @param[in] other - * This is the other URI from which to copy - * the normalized path. - */ - void CopyAndNormalizePath(const Uri& other) { - CopyPath(other); - NormalizePath(); - } - - /** - * This method replaces the URI's query with that of - * another URI. - * - * @param[in] other - * This is the other URI from which to copy the query. - */ - void CopyQuery(const Uri& other) { - hasQuery = other.impl_->hasQuery; - query = other.impl_->query; - } - - /** - * This method replaces the URI's fragment with that of - * another URI. - * - * @param[in] other - * This is the other URI from which to copy the query. - */ - void CopyFragment(const Uri& other) { - hasFragment = other.impl_->hasFragment; - fragment = other.impl_->fragment; - } - - /** - * This method returns an indication of whether or not the - * path of the URI is an absolute path, meaning it begins - * with a forward slash ('/') character. - * - * @return - * An indication of whether or not the path of the URI - * is an absolute path, meaning it begins - * with a forward slash ('/') character is returned. - */ - bool IsPathAbsolute() const { - return ( - !path.empty() - && (path[0] == "") - ); - } - }; - - Uri::~Uri() noexcept = default; - Uri::Uri(const Uri& other) - : impl_(new Impl) - { - *this = other; - } - Uri::Uri(Uri&&) noexcept = default; - Uri& Uri::operator=(const Uri& other) { - if (this != &other) { - *impl_ = *other.impl_; - } - return *this; - } - Uri& Uri::operator=(Uri&&) noexcept = default; - - Uri::Uri() - : impl_(new Impl) - { - } - - bool Uri::operator==(const Uri& other) const { - return ( - (impl_->scheme == other.impl_->scheme) - && (impl_->userInfo == other.impl_->userInfo) - && (impl_->host == other.impl_->host) - && ( - (!impl_->hasPort && !other.impl_->hasPort) - || ( - (impl_->hasPort && other.impl_->hasPort) - && (impl_->port == other.impl_->port) - ) - ) - && (impl_->path == other.impl_->path) - && ( - (!impl_->hasQuery && !other.impl_->hasQuery) - || ( - (impl_->hasQuery && other.impl_->hasQuery) - && (impl_->query == other.impl_->query) - ) - ) - && ( - (!impl_->hasFragment && !other.impl_->hasFragment) - || ( - (impl_->hasFragment && other.impl_->hasFragment) - && (impl_->fragment == other.impl_->fragment) - ) - ) - ); - } - - bool Uri::operator!=(const Uri& other) const { - return !(*this == other); - } - - bool Uri::ParseFromString(const std::string& uriString) { - std::string rest; - if (!impl_->ParseScheme(uriString, rest)) { - return false; - } - const auto pathEnd = rest.find_first_of("?#"); - const auto authorityAndPathString = rest.substr(0, pathEnd); - const auto queryAndOrFragment = rest.substr(authorityAndPathString.length()); - std::string pathString; - if (!impl_->SplitAuthorityFromPathAndParseIt(authorityAndPathString, pathString)) { - return false; - } - if (!impl_->ParsePath(pathString)) { - return false; - } - impl_->SetDefaultPathIfAuthorityPresentAndPathEmpty(); - if (!impl_->ParseFragment(queryAndOrFragment, rest)) { - return false; - } - return impl_->ParseQuery(rest); - } - - std::string Uri::GetScheme() const { - return impl_->scheme; - } - - std::string Uri::GetUserInfo() const { - return impl_->userInfo; - } - - std::string Uri::GetHost() const { - return impl_->host; - } - - std::vector< std::string > Uri::GetPath() const { - return impl_->path; - } - - bool Uri::HasPort() const { - return impl_->hasPort; - } - - uint16_t Uri::GetPort() const { - return impl_->port; - } - - bool Uri::IsRelativeReference() const { - return impl_->scheme.empty(); - } - - bool Uri::ContainsRelativePath() const { - return !impl_->IsPathAbsolute(); - } - - bool Uri::HasQuery() const { - return impl_->hasQuery; - } - - std::string Uri::GetQuery() const { - return impl_->query; - } - - bool Uri::HasFragment() const { - return impl_->hasFragment; - } - - std::string Uri::GetFragment() const { - return impl_->fragment; - } - - void Uri::NormalizePath() { - impl_->NormalizePath(); - } - - Uri Uri::Resolve(const Uri& relativeReference) const { - // Resolve the reference by following the algorithm - // from section 5.2.2 in - // RFC 3986 (https://tools.ietf.org/html/rfc3986). - Uri target; - if (!relativeReference.impl_->scheme.empty()) { - target.impl_->CopyScheme(relativeReference); - target.impl_->CopyAuthority(relativeReference); - target.impl_->CopyAndNormalizePath(relativeReference); - target.impl_->CopyQuery(relativeReference); - } else { - if (!relativeReference.impl_->host.empty()) { - target.impl_->CopyAuthority(relativeReference); - target.impl_->CopyAndNormalizePath(relativeReference); - target.impl_->CopyQuery(relativeReference); - } else { - if (relativeReference.impl_->path.empty()) { - target.impl_->path = impl_->path; - if (!relativeReference.impl_->query.empty()) { - target.impl_->CopyQuery(relativeReference); - } else { - target.impl_->CopyQuery(*this); - } - } else { - // RFC describes this as: - // "if (R.path starts-with "/") then" - if (relativeReference.impl_->IsPathAbsolute()) { - target.impl_->CopyAndNormalizePath(relativeReference); - } else { - // RFC describes this as: - // "T.path = merge(Base.path, R.path);" - target.impl_->CopyPath(*this); - if (target.impl_->path.size() > 1) { - target.impl_->path.pop_back(); - } - std::copy( - relativeReference.impl_->path.begin(), - relativeReference.impl_->path.end(), - std::back_inserter(target.impl_->path) - ); - target.NormalizePath(); - } - target.impl_->CopyQuery(relativeReference); - } - target.impl_->CopyAuthority(*this); - } - target.impl_->CopyScheme(*this); - } - target.impl_->CopyFragment(relativeReference); - return target; - } - - void Uri::SetScheme(const std::string& scheme) { - impl_->scheme = scheme; - } - - void Uri::SetUserInfo(const std::string& userinfo) { - impl_->userInfo = userinfo; - } - - void Uri::SetHost(const std::string& host) { - impl_->host = host; - } - - void Uri::SetPort(uint16_t port) { - impl_->port = port; - impl_->hasPort = true; - } - - void Uri::ClearPort() { - impl_->hasPort = false; - } - - void Uri::SetPath(const std::vector< std::string >& path) { - impl_->path = path; - } - - void Uri::ClearQuery() { - impl_->hasQuery = false; - } - - void Uri::SetQuery(const std::string& query) { - impl_->query = query; - impl_->hasQuery = true; - } - - void Uri::ClearFragment() { - impl_->hasFragment = false; - } - - void Uri::SetFragment(const std::string& fragment) { - impl_->fragment = fragment; - impl_->hasFragment = true; - } - - std::string Uri::GenerateString() const { - std::ostringstream buffer; - if (!impl_->scheme.empty()) { - buffer << impl_->scheme << ':'; - } - if (impl_->HasAuthority()) { - buffer << "//"; - if (!impl_->userInfo.empty()) { - buffer << EncodeElement(impl_->userInfo, USER_INFO_NOT_PCT_ENCODED) << '@'; - } - if (!impl_->host.empty()) { - if (ValidateIpv6Address(impl_->host)) { - buffer << '[' << StringExtensions::ToLower(impl_->host) << ']'; - } else { - buffer << EncodeElement(impl_->host, REG_NAME_NOT_PCT_ENCODED); - } - } - if (impl_->hasPort) { - buffer << ':' << impl_->port; - } - } - // Special case: absolute but otherwise empty path. - if ( - impl_->IsPathAbsolute() - && (impl_->path.size() == 1) - ) { - buffer << '/'; - } - size_t i = 0; - for (const auto& segment: impl_->path) { - buffer << EncodeElement(segment, PCHAR_NOT_PCT_ENCODED); - if (i + 1 < impl_->path.size()) { - buffer << '/'; - } - ++i; - } - if (impl_->hasQuery) { - buffer << '?' << EncodeElement(impl_->query, QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS); - } - if (impl_->hasFragment) { - buffer << '#' << EncodeElement(impl_->fragment, QUERY_OR_FRAGMENT_NOT_PCT_ENCODED); - } - return buffer.str(); - } -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index a7f6320..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -# CMakeLists.txt for UriTests -# -# © 2018 by Richard Walters - -cmake_minimum_required(VERSION 3.8) -set(This UriTests) - -set(Sources - src/UriTests.cpp - src/CharacterSetTests.cpp - src/PercentEncodedCharacterDecoderTests.cpp -) - -add_executable(${This} ${Sources}) -set_target_properties(${This} PROPERTIES - FOLDER Tests -) - -target_include_directories(${This} PRIVATE ..) - -target_link_libraries(${This} PUBLIC - gtest_main - Uri -) - -add_test( - NAME ${This} - COMMAND ${This} -) diff --git a/test/src/CharacterSetTests.cpp b/test/src/CharacterSetTests.cpp deleted file mode 100644 index 3663ee6..0000000 --- a/test/src/CharacterSetTests.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @file CharacterSetTests.cpp - * - * This module contains the unit tests of the Uri::CharacterSet class. - * - * © 2018 by Richard Walters - */ - -#include <gtest/gtest.h> -#include <src/CharacterSet.hpp> -#include <utility> -#include <vector> - -TEST(CharacterSetTests, DefaultConstructor) { - Uri::CharacterSet cs; - for (char c = 0; c < 0x7F; ++c) { - ASSERT_FALSE(cs.Contains(c)); - } -} - -TEST(CharacterSetTests, SingleCharacterConstructor) { - Uri::CharacterSet cs('X'); - for (char c = 0; c < 0x7F; ++c) { - if (c == 'X') { - ASSERT_TRUE(cs.Contains(c)); - } else { - ASSERT_FALSE(cs.Contains(c)); - } - } -} - -TEST(CharacterSetTests, RangeConstructor) { - Uri::CharacterSet cs('A', 'G'); - for (char c = 0; c < 0x7F; ++c) { - if ( - (c >= 'A') - && (c <= 'G') - ) { - ASSERT_TRUE(cs.Contains(c)); - } else { - ASSERT_FALSE(cs.Contains(c)); - } - } -} - -TEST(CharacterSetTests, Range_Constructor_Reversed) { - Uri::CharacterSet cs('G', 'A'); - for (char c = 0; c < 0x7F; ++c) { - if ( - (c >= 'A') - && (c <= 'G') - ) { - ASSERT_TRUE(cs.Contains(c)); - } else { - ASSERT_FALSE(cs.Contains(c)); - } - } -} - -TEST(CharacterSetTests, InitializerListConstructor) { - Uri::CharacterSet cs1{'X'}; - for (char c = 0; c < 0x7F; ++c) { - if (c == 'X') { - ASSERT_TRUE(cs1.Contains(c)); - } else { - ASSERT_FALSE(cs1.Contains(c)); - } - } - Uri::CharacterSet cs2{'A', 'G'}; - for (char c = 0; c < 0x7F; ++c) { - if ( - (c == 'A') - || (c == 'G') - ) { - ASSERT_TRUE(cs2.Contains(c)); - } else { - ASSERT_FALSE(cs2.Contains(c)); - } - } - Uri::CharacterSet cs3{Uri::CharacterSet('f', 'i')}; - for (char c = 0; c < 0x7F; ++c) { - if ( - (c >= 'f') - && (c <= 'i') - ) { - ASSERT_TRUE(cs3.Contains(c)); - } else { - ASSERT_FALSE(cs3.Contains(c)); - } - } - Uri::CharacterSet cs4{Uri::CharacterSet('a', 'c'), Uri::CharacterSet('f', 'i')}; - for (char c = 0; c < 0x7F; ++c) { - if ( - ( - (c >= 'a') - && (c <= 'c') - ) - || ( - (c >= 'f') - && (c <= 'i') - ) - ) { - ASSERT_TRUE(cs4.Contains(c)); - } else { - ASSERT_FALSE(cs4.Contains(c)); - } - } - Uri::CharacterSet cs5{Uri::CharacterSet('a', 'c'), Uri::CharacterSet('x')}; - for (char c = 0; c < 0x7F; ++c) { - if ( - ( - (c >= 'a') - && (c <= 'c') - ) - || (c == 'x') - ) { - ASSERT_TRUE(cs5.Contains(c)); - } else { - ASSERT_FALSE(cs5.Contains(c)); - } - } -} diff --git a/test/src/PercentEncodedCharacterDecoderTests.cpp b/test/src/PercentEncodedCharacterDecoderTests.cpp deleted file mode 100644 index fcc4286..0000000 --- a/test/src/PercentEncodedCharacterDecoderTests.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @file PercentEncodedCharacterDecoderTests.cpp - * - * This module contains the unit tests of the Uri::PercentEncodedCharacterDecoder class. - * - * © 2018 by Richard Walters - */ - -#include <gtest/gtest.h> -#include <src/PercentEncodedCharacterDecoder.hpp> -#include <stddef.h> -#include <vector> - -TEST(PercentEncodedCharacterDecoderTests, GoodSequences) { - Uri::PercentEncodedCharacterDecoder pec; - struct TestVector { - char sequence[2]; - char expectedOutput; - }; - const std::vector< TestVector > testVectors{ - {{'4', '1'}, 'A'}, - {{'5', 'A'}, 'Z'}, - {{'6', 'e'}, 'n'}, - {{'e', '1'}, (char)0xe1}, - {{'C', 'A'}, (char)0xca}, - }; - size_t index = 0; - for (auto testVector: testVectors) { - pec = Uri::PercentEncodedCharacterDecoder(); - ASSERT_FALSE(pec.Done()); - ASSERT_TRUE(pec.NextEncodedCharacter(testVector.sequence[0])); - ASSERT_FALSE(pec.Done()); - ASSERT_TRUE(pec.NextEncodedCharacter(testVector.sequence[1])); - ASSERT_TRUE(pec.Done()); - ASSERT_EQ(testVector.expectedOutput, pec.GetDecodedCharacter()) << index; - ++index; - } -} - -TEST(PercentEncodedCharacterDecoderTests, BadSequences) { - Uri::PercentEncodedCharacterDecoder pec; - std::vector< char > testVectors{ - 'G', 'g', '.', 'z', '-', ' ', 'V', - }; - for (auto testVector: testVectors) { - pec = Uri::PercentEncodedCharacterDecoder(); - ASSERT_FALSE(pec.Done()); - ASSERT_FALSE(pec.NextEncodedCharacter(testVector)); - } -} diff --git a/test/src/UriTests.cpp b/test/src/UriTests.cpp deleted file mode 100644 index 4848724..0000000 --- a/test/src/UriTests.cpp +++ /dev/null @@ -1,964 +0,0 @@ -/** - * @file UriTests.cpp - * - * This module contains the unit tests of the Uri::Uri class. - * - * © 2018 by Richard Walters - */ - -#include <gtest/gtest.h> -#include <stddef.h> -#include <Uri/Uri.hpp> - -TEST(UriTests, ParseFromStringNoScheme) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("foo/bar")); - ASSERT_EQ("", uri.GetScheme()); - ASSERT_EQ( - (std::vector< std::string >{ - "foo", - "bar", - }), - uri.GetPath() - ); -} - -TEST(UriTests, ParseFromStringUrl) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar")); - ASSERT_EQ("http", uri.GetScheme()); - ASSERT_EQ("www.example.com", uri.GetHost()); - ASSERT_EQ( - (std::vector< std::string >{ - "", - "foo", - "bar", - }), - uri.GetPath() - ); -} - -TEST(UriTests, ParseFromStringUrnDefaultPathDelimiter) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("urn:book:fantasy:Hobbit")); - ASSERT_EQ("urn", uri.GetScheme()); - ASSERT_EQ("", uri.GetHost()); - ASSERT_EQ( - (std::vector< std::string >{ - "book:fantasy:Hobbit", - }), - uri.GetPath() - ); -} - -TEST(UriTests, ParseFromStringPathCornerCases) { - struct TestVector { - std::string pathIn; - std::vector< std::string > pathOut; - }; - const std::vector< TestVector > testVectors{ - {"", {}}, - {"/", {""}}, - {"/foo", {"", "foo"} }, - {"foo/", {"foo", ""} }, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.pathIn)) << index; - ASSERT_EQ(testVector.pathOut, uri.GetPath()) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringHasAPortNumber) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com:8080/foo/bar")); - ASSERT_EQ("www.example.com", uri.GetHost()); - ASSERT_TRUE(uri.HasPort()); - ASSERT_EQ(8080, uri.GetPort()); -} - -TEST(UriTests, ParseFromStringDoesNotHaveAPortNumber) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar")); - ASSERT_EQ("www.example.com", uri.GetHost()); - ASSERT_FALSE(uri.HasPort()); -} - -TEST(UriTests, ParseFromStringTwiceFirstWithPortNumberThenWithout) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com:8080/foo/bar")); - ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar")); - ASSERT_FALSE(uri.HasPort()); -} - -TEST(UriTests, ParseFromStringBadPortNumberPurelyAlphabetic) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString("http://www.example.com:spam/foo/bar")); -} - -TEST(UriTests, ParseFromStringBadPortNumberStartsNumericEndsAlphabetic) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString("http://www.example.com:8080spam/foo/bar")); -} - -TEST(UriTests, ParseFromStringLargestValidPortNumber) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com:65535/foo/bar")); - ASSERT_TRUE(uri.HasPort()); - ASSERT_EQ(65535, uri.GetPort()); -} - -TEST(UriTests, ParseFromStringBadPortNumberTooBig) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString("http://www.example.com:65536/foo/bar")); -} - -TEST(UriTests, ParseFromStringBadPortNumberNegative) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString("http://www.example.com:-1234/foo/bar")); -} - -TEST(UriTests, ParseFromStringEndsAfterAuthority) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://www.example.com")); -} - -TEST(UriTests, ParseFromStringRelativeVsNonRelativeReferences) { - struct TestVector { - std::string uriString; - bool isRelativeReference; - }; - const std::vector< TestVector > testVectors{ - {"http://www.example.com/", false}, - {"http://www.example.com", false}, - {"/", true}, - {"foo", true}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.isRelativeReference, uri.IsRelativeReference()) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringRelativeVsNonRelativePaths) { - struct TestVector { - std::string uriString; - bool containsRelativePath; - }; - const std::vector< TestVector > testVectors{ - {"http://www.example.com/", false}, - {"http://www.example.com", false}, - {"/", false}, - {"foo", true}, - - /* - * This is only a valid test vector if we understand - * correctly that an empty string IS a valid - * "relative reference" URI with an empty path. - */ - {"", true}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.containsRelativePath, uri.ContainsRelativePath()) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringQueryAndFragmentElements) { - struct TestVector { - std::string uriString; - std::string host; - std::string query; - std::string fragment; - }; - const std::vector< TestVector > testVectors{ - {"http://www.example.com/", "www.example.com", "", ""}, - {"http://example.com?foo", "example.com", "foo", ""}, - {"http://www.example.com#foo", "www.example.com", "", "foo"}, - {"http://www.example.com?foo#bar", "www.example.com", "foo", "bar"}, - {"http://www.example.com?earth?day#bar", "www.example.com", "earth?day", "bar"}, - {"http://www.example.com/spam?foo#bar", "www.example.com", "foo", "bar"}, - - /* - * NOTE: curiously, but we think this is correct, that - * having a trailing question mark is equivalent to not having - * any question mark, because in both cases, the query element - * is empty string. Perhaps research deeper to see if this is right. - */ - {"http://www.example.com/?", "www.example.com", "", ""}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.host, uri.GetHost()) << index; - ASSERT_EQ(testVector.query, uri.GetQuery()) << index; - ASSERT_EQ(testVector.fragment, uri.GetFragment()) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringUserInfo) { - struct TestVector { - std::string uriString; - std::string userInfo; - }; - const std::vector< TestVector > testVectors{ - {"http://www.example.com/", ""}, - {"http://joe@www.example.com", "joe"}, - {"http://pepe:feelsbadman@www.example.com", "pepe:feelsbadman"}, - {"//www.example.com", ""}, - {"//bob@www.example.com", "bob"}, - {"/", ""}, - {"foo", ""}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.userInfo, uri.GetUserInfo()) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringTwiceFirstUserInfoThenWithout) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://joe@www.example.com/foo/bar")); - ASSERT_TRUE(uri.ParseFromString("/foo/bar")); - ASSERT_TRUE(uri.GetUserInfo().empty()); -} - -TEST(UriTests, ParseFromStringSchemeIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"://www.example.com/"}, - {"0://www.example.com/"}, - {"+://www.example.com/"}, - {"@://www.example.com/"}, - {".://www.example.com/"}, - {"h@://www.example.com/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringSchemeBarelyLegal) { - struct TestVector { - std::string uriString; - std::string scheme; - }; - const std::vector< TestVector > testVectors{ - {"h://www.example.com/", "h"}, - {"x+://www.example.com/", "x+"}, - {"y-://www.example.com/", "y-"}, - {"z.://www.example.com/", "z."}, - {"aa://www.example.com/", "aa"}, - {"a0://www.example.com/", "a0"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.scheme, uri.GetScheme()); - ++index; - } -} - -TEST(UriTests, ParseFromStringSchemeMixedCase) { - const std::vector< std::string > testVectors{ - {"http://www.example.com/"}, - {"hTtp://www.example.com/"}, - {"HTTP://www.example.com/"}, - {"Http://www.example.com/"}, - {"HttP://www.example.com/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector)) << index; - ASSERT_EQ("http", uri.GetScheme()) << ">>> Failed for test vector element " << index << " <<<"; - ++index; - } -} - -TEST(UriTests, ParseFromStringHostEndsInDot) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://example.com./foo")); - ASSERT_EQ("example.com.", uri.GetHost()); -} - -TEST(UriTests, ParseFromStringUserInfoIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"//%X@www.example.com/"}, - {"//{@www.example.com/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringUserInfoBarelyLegal) { - struct TestVector { - std::string uriString; - std::string userInfo; - }; - const std::vector< TestVector > testVectors{ - {"//%41@www.example.com/", "A"}, - {"//@www.example.com/", ""}, - {"//!@www.example.com/", "!"}, - {"//'@www.example.com/", "'"}, - {"//(@www.example.com/", "("}, - {"//;@www.example.com/", ";"}, - {"http://:@www.example.com/", ":"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.userInfo, uri.GetUserInfo()); - ++index; - } -} - -TEST(UriTests, ParseFromStringHostIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"//%X@www.example.com/"}, - {"//@www:example.com/"}, - {"//[vX.:]/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringHostBarelyLegal) { - struct TestVector { - std::string uriString; - std::string host; - }; - const std::vector< TestVector > testVectors{ - {"//%41/", "a"}, - {"///", ""}, - {"//!/", "!"}, - {"//'/", "'"}, - {"//(/", "("}, - {"//;/", ";"}, - {"//1.2.3.4/", "1.2.3.4"}, - {"//[v7.:]/", "v7.:"}, - {"//[v7.aB]/", "v7.aB"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.host, uri.GetHost()); - ++index; - } -} - -TEST(UriTests, ParseFromStringHostMixedCase) { - const std::vector< std::string > testVectors{ - {"http://www.example.com/"}, - {"http://www.EXAMPLE.com/"}, - {"http://www.exAMple.com/"}, - {"http://www.example.cOM/"}, - {"http://wWw.exampLe.Com/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector)) << index; - ASSERT_EQ("www.example.com", uri.GetHost()) << ">>> Failed for test vector element " << index << " <<<"; - ++index; - } -} - -TEST(UriTests, ParseFromStringDontMisinterpretColonInOtherPlacesAsSchemeDelimiter) { - const std::vector< std::string > testVectors{ - {"//foo:bar@www.example.com/"}, - {"//www.example.com/a:b"}, - {"//www.example.com/foo?a:b"}, - {"//www.example.com/foo#a:b"}, - {"//[v7.:]/"}, - {"/:/foo"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector)) << index; - ASSERT_TRUE(uri.GetScheme().empty()); - ++index; - } -} - -TEST(UriTests, ParseFromStringPathIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"http://www.example.com/foo[bar"}, - {"http://www.example.com/]bar"}, - {"http://www.example.com/foo]"}, - {"http://www.example.com/["}, - {"http://www.example.com/abc/foo]"}, - {"http://www.example.com/abc/["}, - {"http://www.example.com/foo]/abc"}, - {"http://www.example.com/[/abc"}, - {"http://www.example.com/foo]/"}, - {"http://www.example.com/[/"}, - {"/foo[bar"}, - {"/]bar"}, - {"/foo]"}, - {"/["}, - {"/abc/foo]"}, - {"/abc/["}, - {"/foo]/abc"}, - {"/[/abc"}, - {"/foo]/"}, - {"/[/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringPathBarelyLegal) { - struct TestVector { - std::string uriString; - std::vector< std::string > path; - }; - const std::vector< TestVector > testVectors{ - {"/:/foo", {"", ":", "foo"}}, - {"bob@/foo", {"bob@", "foo"}}, - {"hello!", {"hello!"}}, - {"urn:hello,%20w%6Frld", {"hello, world"}}, - {"//example.com/foo/(bar)/", {"", "foo", "(bar)", ""}}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.path, uri.GetPath()); - ++index; - } -} - -TEST(UriTests, ParseFromStringQueryIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"http://www.example.com/?foo[bar"}, - {"http://www.example.com/?]bar"}, - {"http://www.example.com/?foo]"}, - {"http://www.example.com/?["}, - {"http://www.example.com/?abc/foo]"}, - {"http://www.example.com/?abc/["}, - {"http://www.example.com/?foo]/abc"}, - {"http://www.example.com/?[/abc"}, - {"http://www.example.com/?foo]/"}, - {"http://www.example.com/?[/"}, - {"?foo[bar"}, - {"?]bar"}, - {"?foo]"}, - {"?["}, - {"?abc/foo]"}, - {"?abc/["}, - {"?foo]/abc"}, - {"?[/abc"}, - {"?foo]/"}, - {"?[/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringQueryBarelyLegal) { - struct TestVector { - std::string uriString; - std::string query; - }; - const std::vector< TestVector > testVectors{ - {"/?:/foo", ":/foo"}, - {"?bob@/foo", "bob@/foo"}, - {"?hello!", "hello!"}, - {"urn:?hello,%20w%6Frld", "hello, world"}, - {"//example.com/foo?(bar)/", "(bar)/"}, - {"http://www.example.com/?foo?bar", "foo?bar" }, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.query, uri.GetQuery()); - ++index; - } -} - -TEST(UriTests, ParseFromStringFragmentIllegalCharacters) { - const std::vector< std::string > testVectors{ - {"http://www.example.com/#foo[bar"}, - {"http://www.example.com/#]bar"}, - {"http://www.example.com/#foo]"}, - {"http://www.example.com/#["}, - {"http://www.example.com/#abc/foo]"}, - {"http://www.example.com/#abc/["}, - {"http://www.example.com/#foo]/abc"}, - {"http://www.example.com/#[/abc"}, - {"http://www.example.com/#foo]/"}, - {"http://www.example.com/#[/"}, - {"#foo[bar"}, - {"#]bar"}, - {"#foo]"}, - {"#["}, - {"#abc/foo]"}, - {"#abc/["}, - {"#foo]/abc"}, - {"#[/abc"}, - {"#foo]/"}, - {"#[/"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_FALSE(uri.ParseFromString(testVector)) << index; - ++index; - } -} - -TEST(UriTests, ParseFromStringFragmentBarelyLegal) { - struct TestVector { - std::string uriString; - std::string fragment; - }; - const std::vector< TestVector > testVectors{ - {"/#:/foo", ":/foo"}, - {"#bob@/foo", "bob@/foo"}, - {"#hello!", "hello!"}, - {"urn:#hello,%20w%6Frld", "hello, world"}, - {"//example.com/foo#(bar)/", "(bar)/"}, - {"http://www.example.com/#foo?bar", "foo?bar" }, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.fragment, uri.GetFragment()); - ++index; - } -} - -TEST(UriTests, ParseFromStringPathsWithPercentEncodedCharacters) { - struct TestVector { - std::string uriString; - std::string pathFirstSegment; - }; - const std::vector< TestVector > testVectors{ - {"%41", "A"}, - {"%4A", "J"}, - {"%4a", "J"}, - {"%bc", "\xbc"}, - {"%Bc", "\xbc"}, - {"%bC", "\xbc"}, - {"%BC", "\xbc"}, - {"%41%42%43", "ABC"}, - {"%41%4A%43%4b", "AJCK"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - ASSERT_EQ(testVector.pathFirstSegment, uri.GetPath()[0]); - ++index; - } -} - -TEST(UriTests, NormalizePath) { - struct TestVector { - std::string uriString; - std::vector< std::string > normalizedPathSegments; - }; - const std::vector< TestVector > testVectors{ - {"/a/b/c/./../../g", {"", "a", "g"}}, - {"mid/content=5/../6", {"mid", "6"}}, - {"http://example.com/a/../b", {"", "b"}}, - {"http://example.com/../b", {"", "b"}}, - {"http://example.com/a/../b/", {"", "b", ""}}, - {"http://example.com/a/../../b", {"", "b"}}, - {"./a/b", {"a", "b"}}, - {"..", {}}, - {"/", {""}}, - {"a/b/..", {"a", ""}}, - {"a/b/.", {"a", "b", ""}}, - {"a/b/./c", {"a", "b", "c"}}, - {"a/b/./c/", {"a", "b", "c", ""}}, - {"/a/b/..", {"", "a", ""}}, - {"/a/b/.", {"", "a", "b", ""}}, - {"/a/b/./c", {"", "a", "b", "c"}}, - {"/a/b/./c/", {"", "a", "b", "c", ""}}, - {"./a/b/..", {"a", ""}}, - {"./a/b/.", {"a", "b", ""}}, - {"./a/b/./c", {"a", "b", "c"}}, - {"./a/b/./c/", {"a", "b", "c", ""}}, - {"../a/b/..", {"a", ""}}, - {"../a/b/.", {"a", "b", ""}}, - {"../a/b/./c", {"a", "b", "c"}}, - {"../a/b/./c/", {"a", "b", "c", ""}}, - {"../a/b/../c", {"a", "c"}}, - {"../a/b/./../c/", {"a", "c", ""}}, - {"../a/b/./../c", {"a", "c"}}, - {"../a/b/./../c/", {"a", "c", ""}}, - {"../a/b/.././c/", {"a", "c", ""}}, - {"../a/b/.././c", {"a", "c"}}, - {"../a/b/.././c/", {"a", "c", ""}}, - {"/./c/d", {"", "c", "d"}}, - {"/../c/d", {"", "c", "d"}}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString(testVector.uriString)) << index; - uri.NormalizePath(); - ASSERT_EQ(testVector.normalizedPathSegments, uri.GetPath()) << index; - ++index; - } -} - -TEST(UriTests, ConstructNormalizeAndCompareEquivalentUris) { - // This was inspired by section 6.2.2 - // of RFC 3986 (https://tools.ietf.org/html/rfc3986). - Uri::Uri uri1, uri2; - ASSERT_TRUE(uri1.ParseFromString("example://a/b/c/%7Bfoo%7D")); - ASSERT_TRUE(uri2.ParseFromString("eXAMPLE://a/./b/../b/%63/%7bfoo%7d")); - ASSERT_NE(uri1, uri2); - uri2.NormalizePath(); - ASSERT_EQ(uri1, uri2); -} - -TEST(UriTests, ReferenceResolution) { - struct TestVector { - std::string baseString; - std::string relativeReferenceString; - std::string targetString; - }; - const std::vector< TestVector > testVectors{ - // These are all taken from section 5.4.1 - // of RFC 3986 (https://tools.ietf.org/html/rfc3986). - {"http://a/b/c/d;p?q", "g:h", "g:h"}, - {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"}, - {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"}, - {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"}, - {"http://a/b/c/d;p?q", "//g", "http://g"}, - {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"}, - {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"}, - {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"}, - {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"}, - {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"}, - {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"}, - {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"}, - {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"}, - {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"}, - {"http://a/b/c/d;p?q", ".", "http://a/b/c/"}, - {"http://a/b/c/d;p?q", "./", "http://a/b/c/"}, - {"http://a/b/c/d;p?q", "..", "http://a/b/"}, - {"http://a/b/c/d;p?q", "../", "http://a/b/"}, - {"http://a/b/c/d;p?q", "../g", "http://a/b/g"}, - {"http://a/b/c/d;p?q", "../..", "http://a"}, - {"http://a/b/c/d;p?q", "../../", "http://a"}, - {"http://a/b/c/d;p?q", "../../g", "http://a/g"}, - - // Here are some examples of our own. - {"http://example.com", "foo", "http://example.com/foo"}, - {"http://example.com/", "foo", "http://example.com/foo"}, - {"http://example.com", "foo/", "http://example.com/foo/"}, - {"http://example.com/", "foo/", "http://example.com/foo/"}, - {"http://example.com", "/foo", "http://example.com/foo"}, - {"http://example.com/", "/foo", "http://example.com/foo"}, - {"http://example.com", "/foo/", "http://example.com/foo/"}, - {"http://example.com/", "/foo/", "http://example.com/foo/"}, - {"http://example.com/", "?foo", "http://example.com/?foo"}, - {"http://example.com/", "#foo", "http://example.com/#foo"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri baseUri, relativeReferenceUri, expectedTargetUri; - ASSERT_TRUE(baseUri.ParseFromString(testVector.baseString)); - ASSERT_TRUE(relativeReferenceUri.ParseFromString(testVector.relativeReferenceString)) << index; - ASSERT_TRUE(expectedTargetUri.ParseFromString(testVector.targetString)) << index; - const auto actualTargetUri = baseUri.Resolve(relativeReferenceUri); - ASSERT_EQ(expectedTargetUri, actualTargetUri) << index; - ++index; - } -} - -TEST(UriTests, EmptyPathInUriWithAuthorityIsEquivalentToSlashOnlyPath) { - Uri::Uri uri1, uri2; - ASSERT_TRUE(uri1.ParseFromString("http://example.com")); - ASSERT_TRUE(uri2.ParseFromString("http://example.com/")); - ASSERT_EQ(uri1, uri2); - ASSERT_TRUE(uri1.ParseFromString("//example.com")); - ASSERT_TRUE(uri2.ParseFromString("//example.com/")); - ASSERT_EQ(uri1, uri2); -} - -TEST(UriTests, IPv6Address) { - struct TestVector { - std::string uriString; - std::string expectedHost; - bool isValid; - }; - const std::vector< TestVector > testVectors{ - // valid - {"http://[::1]/", "::1", true}, - {"http://[::ffff:1.2.3.4]/", "::ffff:1.2.3.4", true}, - {"http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/", "2001:db8:85a3:8d3:1319:8a2e:370:7348", true}, - {"http://[2001:db8:85a3:8d3:1319:8a2e:370::]/", "2001:db8:85a3:8d3:1319:8a2e:370::", true}, - {"http://[2001:db8:85a3:8d3:1319:8a2e::1]/", "2001:db8:85a3:8d3:1319:8a2e::1", true}, - {"http://[fFfF::1]", "fFfF::1", true}, - {"http://[1234::1]", "1234::1", true}, - {"http://[fFfF:1:2:3:4:5:6:a]", "fFfF:1:2:3:4:5:6:a", true}, - {"http://[2001:db8:85a3::8a2e:0]/", "2001:db8:85a3::8a2e:0", true}, - {"http://[2001:db8:85a3:8a2e::]/", "2001:db8:85a3:8a2e::", true}, - - // invalid - {"http://[::fFfF::1]", "", false}, - {"http://[::ffff:1.2.x.4]/", "", false}, - {"http://[::ffff:1.2.3.4.8]/", "", false}, - {"http://[::ffff:1.2.3]/", "", false}, - {"http://[::ffff:1.2.3.]/", "", false}, - {"http://[::ffff:1.2.3.256]/", "", false}, - {"http://[::fxff:1.2.3.4]/", "", false}, - {"http://[::ffff:1.2.3.-4]/", "", false}, - {"http://[::ffff:1.2.3. 4]/", "", false}, - {"http://[::ffff:1.2.3.4 ]/", "", false}, - {"http://[::ffff:1.2.3.4/", "", false}, - {"http://::ffff:1.2.3.4]/", "", false}, - {"http://::ffff:a.2.3.4]/", "", false}, - {"http://::ffff:1.a.3.4]/", "", false}, - {"http://[2001:db8:85a3:8d3:1319:8a2e:370:7348:0000]/", "", false}, - {"http://[2001:db8:85a3:8d3:1319:8a2e:370:7348::1]/", "", false}, - {"http://[2001:db8:85a3:8d3:1319:8a2e:370::1]/", "", false}, - {"http://[2001:db8:85a3::8a2e:0:]/", "", false}, - {"http://[2001:db8:85a3::8a2e::]/", "", false}, - {"http://[]/", "", false}, - {"http://[:]/", "", false}, - {"http://[v]/", "", false}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - const bool parseResult = uri.ParseFromString(testVector.uriString); - ASSERT_EQ(testVector.isValid, parseResult) << index; - if (parseResult) { - ASSERT_EQ(testVector.expectedHost, uri.GetHost()); - } - ++index; - } -} - -TEST(UriTests, IPvFutureAddress) { - struct TestVector { - std::string uriString; - std::string expectedHost; - bool isValid; - }; - const std::vector< TestVector > testVectors{ - // valid - {"http://[v1.x]/", "v1.x", true}, - {"http://[vf.xy]/", "vf.xy", true}, - {"http://[vf.x:y]/", "vf.x:y", true}, - - // invalid - {"http://[vx]/", "", false}, - {"http://[v12]/", "", false}, - {"http://[v1.?]/", "", false}, - {"http://[v1.x?]/", "", false}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - const bool parseResult = uri.ParseFromString(testVector.uriString); - ASSERT_EQ(testVector.isValid, parseResult) << index; - if (parseResult) { - ASSERT_EQ(testVector.expectedHost, uri.GetHost()); - } - ++index; - } -} - -TEST(UriTests, GenerateString) { - struct TestVector { - std::string scheme; - std::string userinfo; - std::string host; - bool hasPort; - uint16_t port; - std::vector< std::string > path; - bool hasQuery; - std::string query; - bool hasFragment; - std::string fragment; - std::string expectedUriString; - }; - const std::vector< TestVector > testVectors{ - // general test vectors - {"http", "bob", "www.example.com", true, 8080, {"", "abc", "def"}, true, "foobar", true, "ch2", "http://bob@www.example.com:8080/abc/def?foobar#ch2"}, - {"http", "bob", "www.example.com", true, 0, {}, true, "foobar", true, "ch2", "http://bob@www.example.com:0?foobar#ch2"}, - {"http", "bob", "www.example.com", true, 0, {}, true, "foobar", true, "", "http://bob@www.example.com:0?foobar#"}, - {"", "", "example.com", false, 0, {}, true, "bar", false, "", "//example.com?bar"}, - {"", "", "example.com", false, 0, {}, true, "" , false, "", "//example.com?"}, - {"", "", "example.com", false, 0, {}, false, "", false, "", "//example.com"}, - {"", "", "example.com", false, 0, {""}, false, "", false, "", "//example.com/"}, - {"", "", "example.com", false, 0, {"", "xyz"}, false, "", false, "", "//example.com/xyz"}, - {"", "", "example.com", false, 0, {"", "xyz", ""}, false, "", false, "", "//example.com/xyz/"}, - {"", "", "", false, 0, {""}, false, "", false, "", "/"}, - {"", "", "", false, 0, {"", "xyz"}, false, "", false, "", "/xyz"}, - {"", "", "", false, 0, {"", "xyz", ""}, false, "", false, "", "/xyz/"}, - {"", "", "", false, 0, {}, false, "", false, "", ""}, - {"", "", "", false, 0, {"xyz"}, false, "", false, "", "xyz"}, - {"", "", "", false, 0, {"xyz", ""}, false, "", false, "", "xyz/"}, - {"", "", "", false, 0, {}, true, "bar", false, "", "?bar"}, - {"http", "", "", false, 0, {}, true, "bar", false, "", "http:?bar"}, - {"http", "", "", false, 0, {}, false, "", false, "", "http:"}, - {"http", "", "::1", false, 0, {}, false, "", false, "", "http://[::1]"}, - {"http", "", "::1.2.3.4", false, 0, {}, false, "", false, "", "http://[::1.2.3.4]"}, - {"http", "", "1.2.3.4", false, 0, {}, false, "", false, "", "http://1.2.3.4"}, - {"", "", "", false, 0, {}, false, "", false, "", ""}, - {"http", "bob", "", false, 0, {}, true, "foobar", false, "", "http://bob@?foobar"}, - {"", "bob", "", false, 0, {}, true, "foobar", false, "", "//bob@?foobar"}, - {"", "bob", "", false, 0, {}, false, "", false, "", "//bob@"}, - - // percent-encoded character test vectors - {"http", "b b", "www.example.com", true, 8080, {"", "abc", "def"}, true, "foobar", true, "ch2", "http://b%20b@www.example.com:8080/abc/def?foobar#ch2"}, - {"http", "bob", "www.e ample.com", true, 8080, {"", "abc", "def"}, true, "foobar", true, "ch2", "http://bob@www.e%20ample.com:8080/abc/def?foobar#ch2"}, - {"http", "bob", "www.example.com", true, 8080, {"", "a c", "def"}, true, "foobar", true, "ch2", "http://bob@www.example.com:8080/a%20c/def?foobar#ch2"}, - {"http", "bob", "www.example.com", true, 8080, {"", "abc", "def"}, true, "foo ar", true, "ch2", "http://bob@www.example.com:8080/abc/def?foo%20ar#ch2"}, - {"http", "bob", "www.example.com", true, 8080, {"", "abc", "def"}, true, "foobar", true, "c 2", "http://bob@www.example.com:8080/abc/def?foobar#c%202"}, - {"http", "bob", "ሴ.example.com", true, 8080, {"", "abc", "def"}, true, "foobar", false, "", "http://bob@%E1%88%B4.example.com:8080/abc/def?foobar"}, - - // normalization of IPv6 address hex digits - {"http", "bob", "fFfF::1", true, 8080, {"", "abc", "def"}, true, "foobar", true, "c 2", "http://bob@[ffff::1]:8080/abc/def?foobar#c%202"}, - }; - size_t index = 0; - for (const auto& testVector : testVectors) { - Uri::Uri uri; - uri.SetScheme(testVector.scheme); - uri.SetUserInfo(testVector.userinfo); - uri.SetHost(testVector.host); - if (testVector.hasPort) { - uri.SetPort(testVector.port); - } else { - uri.ClearPort(); - } - uri.SetPath(testVector.path); - if (testVector.hasQuery) { - uri.SetQuery(testVector.query); - } else { - uri.ClearQuery(); - } - if (testVector.hasFragment) { - uri.SetFragment(testVector.fragment); - } else { - uri.ClearFragment(); - } - const auto actualUriString = uri.GenerateString(); - ASSERT_EQ(testVector.expectedUriString, actualUriString) << index; - ++index; - } -} - -TEST(UriTests, FragmentEmptyButPresent) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://example.com#")); - ASSERT_TRUE(uri.HasFragment()); - ASSERT_EQ("", uri.GetFragment()); - ASSERT_EQ("http://example.com/#", uri.GenerateString()); - uri.ClearFragment(); - ASSERT_EQ("http://example.com/", uri.GenerateString()); - ASSERT_FALSE(uri.HasFragment()); - ASSERT_TRUE(uri.ParseFromString("http://example.com")); - ASSERT_FALSE(uri.HasFragment()); - uri.SetFragment(""); - ASSERT_TRUE(uri.HasFragment()); - ASSERT_EQ("", uri.GetFragment()); - ASSERT_EQ("http://example.com/#", uri.GenerateString()); -} - -TEST(UriTests, QueryEmptyButPresent) { - Uri::Uri uri; - ASSERT_TRUE(uri.ParseFromString("http://example.com?")); - ASSERT_TRUE(uri.HasQuery()); - ASSERT_EQ("", uri.GetQuery()); - ASSERT_EQ("http://example.com/?", uri.GenerateString()); - uri.ClearQuery(); - ASSERT_EQ("http://example.com/", uri.GenerateString()); - ASSERT_FALSE(uri.HasQuery()); - ASSERT_TRUE(uri.ParseFromString("http://example.com")); - ASSERT_FALSE(uri.HasQuery()); - uri.SetQuery(""); - ASSERT_TRUE(uri.HasQuery()); - ASSERT_EQ("", uri.GetQuery()); - ASSERT_EQ("http://example.com/?", uri.GenerateString()); -} - -TEST(UriTests, MakeACopy) { - Uri::Uri uri1; - (void)uri1.ParseFromString("http://www.example.com/foo.txt"); - Uri::Uri uri2(uri1); - uri1.SetQuery("bar"); - uri2.SetFragment("page2"); - uri2.SetHost("example.com"); - EXPECT_EQ("http://www.example.com/foo.txt?bar", uri1.GenerateString()); - EXPECT_EQ("http://example.com/foo.txt#page2", uri2.GenerateString()); -} - -TEST(UriTests, AssignACopy) { - Uri::Uri uri1; - (void)uri1.ParseFromString("http://www.example.com/foo.txt"); - Uri::Uri uri2; - uri2 = uri1; - uri1.SetQuery("bar"); - uri2.SetFragment("page2"); - uri2.SetHost("example.com"); - EXPECT_EQ("http://www.example.com/foo.txt?bar", uri1.GenerateString()); - EXPECT_EQ("http://example.com/foo.txt#page2", uri2.GenerateString()); -} - -TEST(UriTests, ClearQuery) { - Uri::Uri uri; - (void)uri.ParseFromString("http://www.example.com/?foo=bar"); - uri.ClearQuery(); - EXPECT_EQ("http://www.example.com/", uri.GenerateString()); - EXPECT_FALSE(uri.HasQuery()); -} - -TEST(UriTests, PercentEncodePlusInQueries) { - // Although RFC 3986 doesn't say anything about '+', some web services - // treat it the same as ' ' due to how HTML originally defined how - // to encode the query portion of a URL - // (see https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20). - // - // To avoid issues with these web services, make sure '+' is - // percent-encoded in a URI when the URI is encoded. - Uri::Uri uri; - uri.SetQuery("foo+bar"); - EXPECT_EQ("?foo%2Bbar", uri.GenerateString()); -} |