diff options
Diffstat (limited to 'cpp/src/qpid/Url.cpp')
-rw-r--r-- | cpp/src/qpid/Url.cpp | 215 |
1 files changed, 125 insertions, 90 deletions
diff --git a/cpp/src/qpid/Url.cpp b/cpp/src/qpid/Url.cpp index 95d6a34136..f831167dd8 100644 --- a/cpp/src/qpid/Url.cpp +++ b/cpp/src/qpid/Url.cpp @@ -19,57 +19,32 @@ #include "qpid/Url.h" #include "qpid/Exception.h" #include "qpid/Msg.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/StrError.h" -#include <limits.h> // NB: must be before boost/spirit headers. -#include <boost/spirit.hpp> -#include <boost/spirit/actor.hpp> +#include <boost/lexical_cast.hpp> -#include <sstream> +#include <algorithm> -#include <sys/ioctl.h> -#include <net/if.h> -#include <unistd.h> -#include <arpa/inet.h> -#include <stdio.h> -#include <errno.h> +#include <string.h> -using namespace boost::spirit; using namespace std; +using boost::lexical_cast; namespace qpid { -std::ostream& operator<<(std::ostream& os, const TcpAddress& a) { - return os << "tcp:" << a.host << ":" << a.port; -} - -std::istream& operator>>(std::istream&, const TcpAddress&); +Url::Invalid::Invalid(const string& s) : Exception(s) {} Url Url::getHostNameUrl(uint16_t port) { - char name[HOST_NAME_MAX]; - if (::gethostname(name, sizeof(name)) != 0) - throw InvalidUrl(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); - return Url(TcpAddress(name, port)); + TcpAddress address(std::string(), port); + if (!sys::SystemInfo::getLocalHostname(address)) + throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); + return Url(address); } -static const string LOCALHOST("127.0.0.1"); - Url Url::getIpAddressesUrl(uint16_t port) { Url url; - int s = socket (PF_INET, SOCK_STREAM, 0); - for (int i=1;;i++) { - struct ifreq ifr; - ifr.ifr_ifindex = i; - if (::ioctl (s, SIOCGIFNAME, &ifr) < 0) - break; - /* now ifr.ifr_name is set */ - if (::ioctl (s, SIOCGIFADDR, &ifr) < 0) - continue; - struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr; - string addr(inet_ntoa(sin->sin_addr)); - if (addr != LOCALHOST) - url.push_back(TcpAddress(addr, port)); - } - close (s); + sys::SystemInfo::getLocalIpAddresses(port, url); return url; } @@ -93,78 +68,138 @@ ostream& operator<<(ostream& os, const Url& url) { return os; } -// 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') |... -// -struct ch_in : public char_parser<ch_in> { - ch_in(const string& chars_) : chars(chars_) {} - bool test(char ch_) const { - return chars.find(ch_) != string::npos; - } - string chars; -}; -inline ch_in ch_in_p(const string& chars) { - return ch_in(chars); -} +/** 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; } -/** Grammar for AMQP URLs. */ -struct UrlGrammar : public grammar<UrlGrammar> -{ - Url& addr; + 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); + } - UrlGrammar(Url& addr_) : addr(addr_) {} - - template <class ScannerT> - struct definition { - TcpAddress tcp; - - definition(const UrlGrammar& self) - { - first = eps_p[clear_a(self.addr)] >> amqp_url; - amqp_url = str_p("amqp:") >> prot_addr_list >> - !(str_p("/") >> !parameters); - prot_addr_list = prot_addr % ','; - prot_addr = tcp_prot_addr; // Extend for TLS etc. - - // TCP addresses - tcp_prot_addr = tcp_id >> tcp_addr[push_back_a(self.addr, tcp)]; - tcp_id = !str_p("tcp:"); - tcp_addr = !(host[assign_a(tcp.host)] >> !(':' >> port)); - - // See http://www.apps.ietf.org/rfc/rfc3986.html#sec-A - // for real host grammar. Shortcut: - port = uint_parser<uint16_t>()[assign_a(tcp.port)]; - host = *( unreserved | pct_encoded ); - unreserved = alnum_p | ch_in_p("-._~"); - pct_encoded = "%" >> xdigit_p >> xdigit_p; - parameters = *anychar_p >> end_p; // Ignore, not used yet. + // 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(); } - const rule<ScannerT>& start() const { return first; } + bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } - rule<ScannerT> first, amqp_url, prot_addr_list, prot_addr, - tcp_prot_addr, tcp_id, tcp_addr, host, port, - unreserved, pct_encoded, parameters; + bool hexDigit() { return i < end && ::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 (decDigit()) + ; + try { + n = lexical_cast<IntType>(string(start, i)); + return true; + } catch(...) { return false; } + } + + bool decDigit() { return i < end && ::isdigit(*i) && advance(); } + + 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"); + 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) { |