diff options
author | Dan Goodliffe <dan@randomdan.homeip.net> | 2016-01-11 23:03:23 +0000 |
---|---|---|
committer | Dan Goodliffe <dan@randomdan.homeip.net> | 2016-01-11 23:03:23 +0000 |
commit | ec271b91b98006d7f2bf7bc460811c1da002531e (patch) | |
tree | f123a5097db740d3fa2a40a1f93f5f5ff7612115 | |
parent | Add a wrapper constructor to Exception to pass parameters to BaseException's ... (diff) | |
download | libadhocutil-0.3.5.tar.bz2 libadhocutil-0.3.5.tar.xz libadhocutil-0.3.5.zip |
Add code parsing URI structureslibadhocutil-0.3.5
-rw-r--r-- | libadhocutil/unittests/Jamfile.jam | 13 | ||||
-rw-r--r-- | libadhocutil/unittests/testUriParse.cpp | 230 | ||||
-rw-r--r-- | libadhocutil/uriParse.cpp | 188 | ||||
-rw-r--r-- | libadhocutil/uriParse.h | 38 |
4 files changed, 469 insertions, 0 deletions
diff --git a/libadhocutil/unittests/Jamfile.jam b/libadhocutil/unittests/Jamfile.jam index 5ea24a8..9a180a7 100644 --- a/libadhocutil/unittests/Jamfile.jam +++ b/libadhocutil/unittests/Jamfile.jam @@ -9,6 +9,19 @@ lib boost_thread ; lib dl ; run + testUriParse.cpp + : : : + <define>ROOT=\"$(me)\" + <define>BOOST_TEST_DYN_LINK + <library>..//adhocutil + <library>boost_system + <library>boost_filesystem + <library>boost_utf + : + testUriParse + ; + +run testContext.cpp : : : <define>ROOT=\"$(me)\" diff --git a/libadhocutil/unittests/testUriParse.cpp b/libadhocutil/unittests/testUriParse.cpp new file mode 100644 index 0000000..600f0f0 --- /dev/null +++ b/libadhocutil/unittests/testUriParse.cpp @@ -0,0 +1,230 @@ +#define BOOST_TEST_MODULE UriParse +#include <boost/test/unit_test.hpp> + +#include "uriParse.h" + +BOOST_AUTO_TEST_CASE( simple ) +{ + AdHoc::Uri u("http://localhost"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(!u.port); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(!u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( lowerScheme ) +{ + AdHoc::Uri u("HtTP://localhost"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(!u.port); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(!u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( simpleTrailingSlash ) +{ + AdHoc::Uri u("ssh+git://localhost/"); + BOOST_REQUIRE_EQUAL("ssh+git", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(!u.port); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(u.path); + BOOST_REQUIRE_EQUAL("", *u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( simpleWithPort ) +{ + AdHoc::Uri u("http://localhost:80"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(u.port); + BOOST_REQUIRE_EQUAL(80, *u.port); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(!u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( simpleTrailingSlashWithPort ) +{ + AdHoc::Uri u("http://localhost:80/"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(u.port); + BOOST_REQUIRE_EQUAL(80, *u.port); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(u.path); + BOOST_REQUIRE_EQUAL("", *u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( username ) +{ + AdHoc::Uri u("http://user@localhost"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(u.username); + BOOST_REQUIRE_EQUAL("user", *u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(!u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( usernameAndPassword ) +{ + AdHoc::Uri u("http://user:pass@localhost"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(u.username); + BOOST_REQUIRE_EQUAL("user", *u.username); + BOOST_REQUIRE(u.password); + BOOST_REQUIRE_EQUAL("pass", *u.password); + BOOST_REQUIRE(!u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( path ) +{ + AdHoc::Uri u("http://localhost/path/to/resource"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(u.path); + BOOST_REQUIRE_EQUAL("path/to/resource", *u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(!u.fragment); +} + +BOOST_AUTO_TEST_CASE( query0 ) +{ + AdHoc::Uri u("http://localhost/?"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(0, u.query.size()); +} + +BOOST_AUTO_TEST_CASE( query1 ) +{ + AdHoc::Uri u("http://localhost/?var=val"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(1, u.query.size()); + BOOST_REQUIRE_EQUAL("var", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("val", u.query.begin()->second); +} + +BOOST_AUTO_TEST_CASE( query2 ) +{ + AdHoc::Uri u("http://localhost/?var=val&name=value"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(2, u.query.size()); + BOOST_REQUIRE_EQUAL("name", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("value", u.query.begin()->second); + BOOST_REQUIRE_EQUAL("var", u.query.rbegin()->first); + BOOST_REQUIRE_EQUAL("val", u.query.rbegin()->second); +} + +BOOST_AUTO_TEST_CASE( queryMany ) +{ + AdHoc::Uri u("http://localhost/?name=val&name=value"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(2, u.query.size()); + BOOST_REQUIRE_EQUAL("name", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("val", u.query.begin()->second); + BOOST_REQUIRE_EQUAL("name", u.query.rbegin()->first); + BOOST_REQUIRE_EQUAL("value", u.query.rbegin()->second); +} + +BOOST_AUTO_TEST_CASE( queryNoValue1 ) +{ + AdHoc::Uri u("http://localhost/?n1"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(1, u.query.size()); + BOOST_REQUIRE_EQUAL("n1", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("", u.query.begin()->second); +} + +BOOST_AUTO_TEST_CASE( queryNoValue1eq ) +{ + AdHoc::Uri u("http://localhost/?n1="); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(1, u.query.size()); + BOOST_REQUIRE_EQUAL("n1", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("", u.query.begin()->second); +} + +BOOST_AUTO_TEST_CASE( queryNoValue2 ) +{ + AdHoc::Uri u("http://localhost/?n1=&n2"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE_EQUAL(2, u.query.size()); + BOOST_REQUIRE_EQUAL("n1", u.query.begin()->first); + BOOST_REQUIRE_EQUAL("", u.query.begin()->second); + BOOST_REQUIRE_EQUAL("n2", u.query.rbegin()->first); + BOOST_REQUIRE_EQUAL("", u.query.rbegin()->second); +} + +BOOST_AUTO_TEST_CASE( fragment ) +{ + AdHoc::Uri u("http://localhost/path/to/resource#someFrag"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("localhost", u.host); + BOOST_REQUIRE(!u.username); + BOOST_REQUIRE(!u.password); + BOOST_REQUIRE(u.path); + BOOST_REQUIRE_EQUAL("path/to/resource", *u.path); + BOOST_REQUIRE(u.query.empty()); + BOOST_REQUIRE(u.fragment); + BOOST_REQUIRE_EQUAL("someFrag", *u.fragment); +} + +BOOST_AUTO_TEST_CASE( ipv6 ) +{ + AdHoc::Uri u("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html"); + BOOST_REQUIRE_EQUAL("http", u.scheme); + BOOST_REQUIRE_EQUAL("[fedc:ba98:7654:3210:fedc:ba98:7654:3210]", u.host); + BOOST_REQUIRE(u.port); + BOOST_REQUIRE_EQUAL(80, *u.port); + BOOST_REQUIRE(u.path); + BOOST_REQUIRE_EQUAL("index.html", *u.path); +} + +BOOST_AUTO_TEST_CASE( bad ) +{ + BOOST_REQUIRE_THROW(AdHoc::Uri(""), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("localhost"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("t00+p://foo"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("tcp:"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("http:/"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("tcp://"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("ftp/local"), AdHoc::InvalidUri); + BOOST_REQUIRE_THROW(AdHoc::Uri("tcp://local:"), std::bad_cast); + BOOST_REQUIRE_THROW(AdHoc::Uri("tcp://local:foo"), std::bad_cast); + BOOST_REQUIRE_THROW(AdHoc::Uri("tcp://user:pass@"), AdHoc::InvalidUri); + + AdHoc::InvalidUri ui("message", "http://localhost"); + BOOST_REQUIRE_EQUAL("InvalidUri (message) parsing [http://localhost]", ui.what()); +} + diff --git a/libadhocutil/uriParse.cpp b/libadhocutil/uriParse.cpp new file mode 100644 index 0000000..4a07bdb --- /dev/null +++ b/libadhocutil/uriParse.cpp @@ -0,0 +1,188 @@ +#include "uriParse.h" +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/lexical_cast.hpp> +#include "buffer.h" + +namespace AdHoc { + static inline int _is_scheme_char(int c) + { + return (!isalpha(c) && '+' != c && '-' != c && '.' != c) ? 0 : 1; + } + + Uri::Uri(const std::string & uri) + { + auto * puri = this; + const char *curstr; + int i; + int userpass_flag; + int bracket_flag; + + curstr = uri.c_str(); + + const char * tmpstr = strchr(curstr, ':'); + if (!tmpstr) { + throw InvalidUri("Schema marker not found", uri); + } + int len = tmpstr - curstr; + for (i = 0; i < len; i++) { + if (!_is_scheme_char(curstr[i])) { + throw InvalidUri("Invalid format", uri); + } + } + puri->scheme = std::string(curstr, len); + boost::algorithm::to_lower(puri->scheme); + tmpstr++; + curstr = tmpstr; + + for (int i = 0; i < 2; i++) { + if ('/' != *curstr) { + throw InvalidUri("Invalid format", uri); + } + curstr++; + } + + userpass_flag = 0; + tmpstr = curstr; + while ('\0' != *tmpstr) { + if ('@' == *tmpstr) { + userpass_flag = 1; + break; + } else if ('/' == *tmpstr) { + userpass_flag = 0; + break; + } + tmpstr++; + } + + tmpstr = curstr; + if (userpass_flag) { + while ('\0' != *tmpstr && ':' != *tmpstr && '@' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + puri->username = std::string(curstr, len); + curstr = tmpstr; + if (':' == *curstr) { + curstr++; + tmpstr = curstr; + while ('\0' != *tmpstr && '@' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + puri->password = std::string(curstr, len); + curstr = tmpstr; + } + if ('@' != *curstr) { + throw InvalidUri("Invalid format", uri); + } + curstr++; + } + + if ('[' == *curstr) { + bracket_flag = 1; + } else { + bracket_flag = 0; + } + + tmpstr = curstr; + while ('\0' != *tmpstr) { + if (bracket_flag && ']' == *tmpstr) { + tmpstr++; + break; + } else if (!bracket_flag && (':' == *tmpstr || '/' == *tmpstr)) { + break; + } + tmpstr++; + } + if (tmpstr == curstr) { + throw InvalidUri("Host cannot be blank", uri); + } + len = tmpstr - curstr; + puri->host = std::string(curstr, len); + boost::algorithm::to_lower(puri->host); + curstr = tmpstr; + + if (':' == *curstr) { + curstr++; + tmpstr = curstr; + while ('\0' != *tmpstr && '/' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + puri->port = boost::lexical_cast<uint16_t>(std::string(curstr, len)); + curstr = tmpstr; + } + + if ('\0' == *curstr) { + return; + } + + if ('/' != *curstr) { + throw InvalidUri("Invalid format", uri); + } + curstr++; + + tmpstr = curstr; + while ('\0' != *tmpstr && '#' != *tmpstr && '?' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + puri->path = std::string(curstr, len); + curstr = tmpstr; + + if ('?' == *curstr) { + curstr++; + tmpstr = curstr; + while ('\0' != *tmpstr && '#' != *tmpstr) { + while ('\0' != *tmpstr && '#' != *tmpstr && '=' != *tmpstr && '&' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + auto q = puri->query.insert({ std::string(curstr, len), std::string() }); + curstr = tmpstr; + if ('=' == *curstr) { + curstr++; + while ('\0' != *tmpstr && '#' != *tmpstr && '&' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + q->second = std::string(curstr, len); + curstr = tmpstr; + } + if ('&' == *tmpstr) { + tmpstr++; + curstr = tmpstr; + } + else if ('\0' != *tmpstr && '#' != *tmpstr) { + throw InvalidUri("Parse error in query params", uri); + } + } + curstr = tmpstr; + } + + if ('#' == *curstr) { + curstr++; + tmpstr = curstr; + while ('\0' != *tmpstr) { + tmpstr++; + } + len = tmpstr - curstr; + puri->fragment = std::string(curstr, len); + curstr = tmpstr; + } + } + + InvalidUri::InvalidUri(const std::string & e, const std::string & u) : + Exception<std::invalid_argument>(std::string()), + err(e), + uri(u) + { + } + + std::string + InvalidUri::message() const throw() + { + return stringbf("InvalidUri (%s) parsing [%s]", err, uri); + } +} + diff --git a/libadhocutil/uriParse.h b/libadhocutil/uriParse.h new file mode 100644 index 0000000..dea44a2 --- /dev/null +++ b/libadhocutil/uriParse.h @@ -0,0 +1,38 @@ +#ifndef ADHOCUTIL_URIPARSE_H +#define ADHOCUTIL_URIPARSE_H + +#include "visibility.h" +#include "exception.h" +#include <boost/optional.hpp> +#include <boost/filesystem/path.hpp> +#include <string> +#include <map> + +namespace AdHoc { + class DLL_PUBLIC Uri { + public: + Uri(const std::string &); + + std::string scheme; + boost::optional<std::string> username; + boost::optional<std::string> password; + std::string host; + boost::optional<uint16_t> port; + boost::optional<boost::filesystem::path> path; + std::multimap<std::string, std::string> query; + boost::optional<std::string> fragment; + }; + + class DLL_PUBLIC InvalidUri : public Exception<std::invalid_argument> { + public: + InvalidUri(const std::string & err, const std::string & uri); + + std::string message() const throw() override; + + const std::string err; + const std::string uri; + }; +} + +#endif + |