diff options
Diffstat (limited to 'cpp/src/qpid/Url.cpp')
-rw-r--r-- | cpp/src/qpid/Url.cpp | 133 |
1 files changed, 127 insertions, 6 deletions
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) { |