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-ec271b91b98006d7f2bf7bc460811c1da002531e.tar.bz2 libadhocutil-ec271b91b98006d7f2bf7bc460811c1da002531e.tar.xz libadhocutil-ec271b91b98006d7f2bf7bc460811c1da002531e.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 + | 
