diff options
-rw-r--r-- | cpp/src/qpid/Address.cpp | 36 | ||||
-rwxr-xr-x | cpp/src/qpid/Address.h | 53 | ||||
-rw-r--r-- | cpp/src/qpid/Url.cpp | 133 | ||||
-rw-r--r-- | cpp/src/qpid/Url.h | 12 | ||||
-rw-r--r-- | cpp/src/tests/Url.cpp | 53 |
5 files changed, 224 insertions, 63 deletions
diff --git a/cpp/src/qpid/Address.cpp b/cpp/src/qpid/Address.cpp index e278c0295c..ac8c7f30b1 100644 --- a/cpp/src/qpid/Address.cpp +++ b/cpp/src/qpid/Address.cpp @@ -18,17 +18,41 @@ #include "Address.h" +#include <ostream> + +using namespace std; + namespace qpid { -std::ostream& operator<<(std::ostream& os, const Address& addr) { - const TcpAddress *t = addr.get<TcpAddress>(); - if (t) - os << *t; - return os; +TcpAddress::TcpAddress(const std::string& h, uint16_t p): host(h), port(p) {} + +struct AddressOstreamVisitor : public boost::static_visitor<ostream&> { + ostream& out; + AddressOstreamVisitor(ostream& o) : out(o) {} + template <class T> ostream& operator()(const T& data) { return out << data; } +}; + +ostream& operator<<(ostream& os, const Address& addr) { + AddressOstreamVisitor visitor(os); + return boost::apply_visitor(visitor, addr.value); +} + +bool operator==(const TcpAddress& x, const TcpAddress& y) { + return y.host==x.host && y.port == x.port; } -std::ostream& operator<<(std::ostream& os, const TcpAddress& a) { +ostream& operator<<(ostream& os, const TcpAddress& a) { return os << "tcp:" << a.host << ":" << a.port; } +ExampleAddress::ExampleAddress(const char c) : data(c) {} + +bool operator==(const ExampleAddress& x, const ExampleAddress& y) { + return x.data == y.data; +} + +ostream& operator<<(ostream& os, const ExampleAddress& ex) { + return os << "example:" << ex.data; +} + } // namespace qpid diff --git a/cpp/src/qpid/Address.h b/cpp/src/qpid/Address.h index 4913e96e9f..9669985165 100755 --- a/cpp/src/qpid/Address.h +++ b/cpp/src/qpid/Address.h @@ -22,7 +22,7 @@ #include "qpid/sys/IntegerTypes.h" #include <boost/variant.hpp> -#include <ostream> +#include <iosfwd> #include <string> #include <vector> @@ -31,39 +31,54 @@ namespace qpid { /** TCP address of a broker - host:port */ struct TcpAddress { static const uint16_t DEFAULT_PORT=5672; - explicit TcpAddress(const std::string& host_=std::string(), - uint16_t port_=DEFAULT_PORT) - : host(host_), port(port_) {} + explicit TcpAddress(const std::string& host_=std::string(),uint16_t port_=DEFAULT_PORT); std::string host; uint16_t port; }; - -inline bool operator==(const TcpAddress& x, const TcpAddress& y) { - return y.host==x.host && y.port == x.port; -} - +bool operator==(const TcpAddress& x, const TcpAddress& y); std::ostream& operator<<(std::ostream& os, const TcpAddress& a); +/**@internal Not a real address type, this is a placeholder to + * demonstrate and validate multi-protocol Urls for unit tests and + * developer education only. An example address holds just a char. + */ +struct ExampleAddress { + explicit ExampleAddress(char data); + char data; +}; +bool operator==(const ExampleAddress& x, const ExampleAddress& y); +std::ostream& operator<<(std::ostream& os, const ExampleAddress& a); + /** - * Address is a variant of all address types, more coming in future. - * - * Address wraps a boost::variant rather than be defined in terms of - * boost::variant to prevent users from having to deal directly with - * boost. + * Contains the address of an AMQP broker. Can any supported type of + * broker address. Currently only TcpAddress is supported. */ struct Address { public: Address(const Address& a) : value(a.value) {} Address(const TcpAddress& tcp) : value(tcp) {} - template <class T> Address& operator=(const T& t) { value=t; return *this; } - template <class T> T* get() { return boost::get<T>(&value); } - template <class T> const T* get() const { return boost::get<T>(&value); } + Address(const ExampleAddress& eg) : value(eg) {} ///<@internal + + template <class AddressType> Address& operator=(const AddressType& t) { value=t; return *this; } + + /** Get the address of type AddressType. + *@return AddressType* pointing to the contained address or 0 if + *contained address is not of type AddressType. + */ + template <class AddressType> AddressType* get() { return boost::get<AddressType>(&value); } + + /** Get the address of type AddressType. + *@return AddressType* pointing to the contained address or 0 if + *contained address is not of type AddressType. + */ + template <class AddressType> const AddressType* get() const { return boost::get<AddressType>(&value); } private: - boost::variant<TcpAddress> value; + boost::variant<TcpAddress,ExampleAddress> value; + friend std::ostream& operator<<(std::ostream& os, const Address& addr); }; -std::ostream& operator<<(std::ostream& os, const Address& addr); + } // namespace qpid diff --git a/cpp/src/qpid/Url.cpp b/cpp/src/qpid/Url.cpp index b49bc6d288..d0aa28ab0f 100644 --- a/cpp/src/qpid/Url.cpp +++ b/cpp/src/qpid/Url.cpp @@ -27,21 +27,28 @@ #include <boost/spirit.hpp> #include <boost/spirit/actor.hpp> +#include <boost/lexical_cast.hpp> #include <sstream> +#include <map> +#include <algorithm> +#include <limits> #include <stdio.h> #include <errno.h> using namespace boost::spirit; using namespace std; +using boost::lexical_cast; namespace qpid { +Url::Invalid::Invalid(const string& s) : Exception(s) {} + Url Url::getHostNameUrl(uint16_t port) { TcpAddress address(std::string(), port); if (!sys::SystemInfo::getLocalHostname(address)) - throw InvalidUrl(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); + throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); return Url(address); } @@ -71,6 +78,120 @@ ostream& operator<<(ostream& os, const Url& url) { return os; } + +/** Simple recursive-descent parser for url grammar in AMQP 0-10 spec: + + amqp_url = "amqp:" prot_addr_list + prot_addr_list = [prot_addr ","]* prot_addr + prot_addr = tcp_prot_addr | tls_prot_addr + + tcp_prot_addr = tcp_id tcp_addr + tcp_id = "tcp:" | "" + tcp_addr = [host [":" port] ] + host = <as per http://www.ietf.org/rfc/rfc3986.txt> + port = number]]> +*/ +class UrlParser { + public: + UrlParser(Url& u, const char* s) : url(u), text(s), end(s+strlen(s)), i(s) {} + bool parse() { return literal("amqp:") && list(&UrlParser::protAddr, &UrlParser::comma) && i == end; } + + private: + typedef bool (UrlParser::*Rule)(); + + bool comma() { return literal(","); } + + // NOTE: tcpAddr must be last since it is allowed to omit it's tcp: tag. + bool protAddr() { return exampleAddr() || tcpAddr(); } + + bool tcpAddr() { + TcpAddress addr; + literal("tcp:"); // Don't check result, allowed to be absent. + return addIf(host(addr.host) && (literal(":") ? port(addr.port) : true), addr); + } + + // Placeholder address type till we have multiple address types. Address is a single char. + bool exampleAddr () { + if (literal("example:") && i < end) { + ExampleAddress ex(*i++); + url.push_back(ex); + return true; + } + return false; + } + + // FIXME aconway 2008-11-20: this does not implement http://www.ietf.org/rfc/rfc3986.txt. + // Works for DNS names and ipv4 literals but won't handle ipv6. + bool host(string& h) { + const char* start=i; + while (unreserved() || pctEncoded()) + ; + if (start == i) h = LOCALHOST; // Default + else h.assign(start, i); + return true; + } + + bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); } + + bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } + + bool hexDigit() { return ::strchr("01234567890abcdefABCDEF", *i) && advance(); } + + bool port(uint16_t& p) { return decimalInt(p); } + + template <class AddrType> bool addIf(bool ok, const AddrType& addr) { if (ok) url.push_back(addr); return ok; } + + template <class IntType> bool decimalInt(IntType& n) { + const char* start = i; + while (::isdigit(*i)) ++i; + try { + n = lexical_cast<IntType>(string(start, i)); + return true; + } catch(...) { return false; } + } + + bool literal(const char* s) { + int n = ::strlen(s); + if (n <= end-i && equal(s, s+n, i)) return advance(n); + return false; + }; + + bool noop() { return true; } + + /** List of item, separated by separator, with min & max bounds. */ + bool list(Rule item, Rule separator, size_t min=0, size_t max=UNLIMITED) { + assert(max > 0); + assert(max >= min); + if (!(this->*item)()) return min == 0; // Empty list. + size_t n = 1; + while (n < max && i < end) { + if (!(this->*separator)()) break; + if (i == end || !(this->*item)()) return false; // Separator with no item. + ++n; + } + return n >= min; + } + + /** List of items with no separator */ + bool list(Rule item, size_t min=0, size_t max=UNLIMITED) { return list(item, &UrlParser::noop, min, max); } + + bool advance(size_t n=1) { + if (i+n > end) return false; + i += n; + return true; + } + + static const size_t UNLIMITED = size_t(~1); + static const std::string LOCALHOST; + + Url& url; + const char* text; + const char* end; + const char* i; +}; + +const string UrlParser::LOCALHOST("127.0.0.1"); + // Addition to boost::spirit parsers: accept any character from a // string. Vastly more compile-time-efficient than long rules of the // form: ch_p('x') | ch_p('y') |... @@ -129,20 +250,20 @@ struct UrlGrammar : public grammar<UrlGrammar> }; void Url::parse(const char* url) { - cache.clear(); - if (!boost::spirit::parse(url, UrlGrammar(*this)).full) - throw InvalidUrl(string("Invalid AMQP url: ")+url); + parseNoThrow(url); + if (empty()) + throw Url::Invalid(QPID_MSG("Invalid URL: " << url)); } void Url::parseNoThrow(const char* url) { cache.clear(); - if (!boost::spirit::parse(url, UrlGrammar(*this)).full) + if (!UrlParser(*this, url).parse()) clear(); } void Url::throwIfEmpty() const { if (empty()) - throw InvalidUrl("URL contains no addresses"); + throw Url::Invalid("URL contains no addresses"); } std::istream& operator>>(std::istream& is, Url& url) { diff --git a/cpp/src/qpid/Url.h b/cpp/src/qpid/Url.h index 0946b97490..07ca46e70c 100644 --- a/cpp/src/qpid/Url.h +++ b/cpp/src/qpid/Url.h @@ -40,9 +40,7 @@ struct Url : public std::vector<Address> { * on a multi-homed host. */ static Url getIpAddressesUrl(uint16_t port); - struct InvalidUrl : public Exception { - InvalidUrl(const std::string& s) : Exception(s) {} - }; + struct Invalid : public Exception { Invalid(const std::string& s); }; /** Convert to string form. */ std::string str() const; @@ -53,22 +51,22 @@ struct Url : public std::vector<Address> { /** URL containing a single address */ explicit Url(const Address& addr) { push_back(addr); } - /** Parse url, throw InvalidUrl if invalid. */ + /** Parse url, throw Invalid if invalid. */ explicit Url(const std::string& url) { parse(url.c_str()); } - /** Parse url, throw InvalidUrl if invalid. */ + /** Parse url, throw Invalid if invalid. */ explicit Url(const char* url) { parse(url); } Url& operator=(const Url& u) { this->std::vector<Address>::operator=(u); cache=u.cache; return *this; } Url& operator=(const char* s) { parse(s); return *this; } Url& operator=(const std::string& s) { parse(s); return *this; } - /** Throw InvalidUrl if the URL does not contain any addresses. */ + /** Throw Invalid if the URL does not contain any addresses. */ void throwIfEmpty() const; /** Replace contents with parsed URL as defined in * https://wiki.108.redhat.com/jira/browse/AMQP-95 - *@exception InvalidUrl if the url is invalid. + *@exception Invalid if the url is invalid. */ void parse(const char* url); void parse(const std::string& url) { parse(url.c_str()); } diff --git a/cpp/src/tests/Url.cpp b/cpp/src/tests/Url.cpp index 4684ab4a7c..f3b42a7208 100644 --- a/cpp/src/tests/Url.cpp +++ b/cpp/src/tests/Url.cpp @@ -28,37 +28,40 @@ using namespace boost::assign; QPID_AUTO_TEST_SUITE(UrlTestSuite) -QPID_AUTO_TEST_CASE(testUrl_str) { - Url url; - url.push_back(TcpAddress("foo.com")); - url.push_back(TcpAddress("bar.com", 6789)); - BOOST_CHECK_EQUAL("amqp:tcp:foo.com:5672,tcp:bar.com:6789", url.str()); - BOOST_CHECK(Url().str().empty()); -} - +#define URL_CHECK_STR(STR) BOOST_CHECK_EQUAL(Url(STR).str(), STR) +#define URL_CHECK_INVALID(STR) BOOST_CHECK_THROW(Url(STR), Url::Invalid) -QPID_AUTO_TEST_CASE(testUrl_parse) { - Url url; - url.parse("amqp:foo.com,tcp:bar.com:1234"); - BOOST_CHECK_EQUAL(2u, url.size()); - BOOST_CHECK_EQUAL("foo.com", url[0].get<TcpAddress>()->host); - BOOST_CHECK_EQUAL("amqp:tcp:foo.com:5672,tcp:bar.com:1234", url.str()); +QPID_AUTO_TEST_CASE(TestParseTcp) { + URL_CHECK_STR("amqp:tcp:host:42"); + URL_CHECK_STR("amqp:tcp:host-._~%ff%23:42"); // unreserved chars and pct encoded hex. - url.parse("amqp:foo/ignorethis"); - BOOST_CHECK_EQUAL("amqp:tcp:foo:5672", url.str()); + // Check defaults + BOOST_CHECK_EQUAL(Url("amqp:host:42").str(), "amqp:tcp:host:42"); + BOOST_CHECK_EQUAL(Url("amqp:tcp:host").str(), "amqp:tcp:host:5672"); + BOOST_CHECK_EQUAL(Url("amqp:tcp:").str(), "amqp:tcp:127.0.0.1:5672"); + BOOST_CHECK_EQUAL(Url("amqp:").str(), "amqp:tcp:127.0.0.1:5672"); + BOOST_CHECK_EQUAL(Url("amqp::42").str(), "amqp:tcp:127.0.0.1:42"); - url.parse("amqp:"); - BOOST_CHECK_EQUAL("amqp:tcp::5672", url.str()); + URL_CHECK_INVALID("amqp::badHost!#$#"); + URL_CHECK_INVALID("amqp::host:badPort"); +} - try { - url.parse("invalid url"); - BOOST_FAIL("Expected InvalidUrl exception"); - } - catch (const Url::InvalidUrl&) {} +QPID_AUTO_TEST_CASE(TestParseExample) { + URL_CHECK_STR("amqp:example:x"); + URL_CHECK_INVALID("amqp:example:badExample"); +} - url.parseNoThrow("invalid url"); - BOOST_CHECK(url.empty()); +QPID_AUTO_TEST_CASE(TestParseMultiAddress) { + URL_CHECK_STR("amqp:tcp:host:0,example:y,tcp:foo:0,example:1"); + URL_CHECK_STR("amqp:example:z,tcp:foo:0"); + URL_CHECK_INVALID("amqp:tcp:h:0,"); + URL_CHECK_INVALID(",amqp:tcp:h"); } +QPID_AUTO_TEST_CASE(TestInvalidAddress) { + URL_CHECK_INVALID("xxxx"); + URL_CHECK_INVALID(""); +} + QPID_AUTO_TEST_SUITE_END() |