aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt31
-rw-r--r--Cargo.toml3
-rw-r--r--README.md53
-rw-r--r--include/Uri/Uri.hpp342
-rw-r--r--src/CharacterSet.cpp84
-rw-r--r--src/CharacterSet.hpp102
-rw-r--r--src/PercentEncodedCharacterDecoder.cpp103
-rw-r--r--src/PercentEncodedCharacterDecoder.hpp85
-rw-r--r--src/Uri.cpp1470
-rw-r--r--test/CMakeLists.txt29
-rw-r--r--test/src/CharacterSetTests.cpp122
-rw-r--r--test/src/PercentEncodedCharacterDecoderTests.cpp50
-rw-r--r--test/src/UriTests.cpp964
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)
diff --git a/Cargo.toml b/Cargo.toml
index 23dcf1e..c4df3f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
]
diff --git a/README.md b/README.md
index 1408a9e..b8c38a5 100644
--- a/README.md
+++ b/README.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());
-}