summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2016-01-11 23:03:23 +0000
committerDan Goodliffe <dan@randomdan.homeip.net>2016-01-11 23:03:23 +0000
commitec271b91b98006d7f2bf7bc460811c1da002531e (patch)
treef123a5097db740d3fa2a40a1f93f5f5ff7612115
parentAdd a wrapper constructor to Exception to pass parameters to BaseException's ... (diff)
downloadlibadhocutil-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.jam13
-rw-r--r--libadhocutil/unittests/testUriParse.cpp230
-rw-r--r--libadhocutil/uriParse.cpp188
-rw-r--r--libadhocutil/uriParse.h38
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
+