diff options
Diffstat (limited to 'qpid/cpp/src/qpid/Url.cpp')
-rw-r--r-- | qpid/cpp/src/qpid/Url.cpp | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/qpid/cpp/src/qpid/Url.cpp b/qpid/cpp/src/qpid/Url.cpp new file mode 100644 index 0000000000..ab796f4642 --- /dev/null +++ b/qpid/cpp/src/qpid/Url.cpp @@ -0,0 +1,265 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "qpid/Url.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/StrError.h" +#include "qpid/client/Connector.h" +#include "qpid/sys/Mutex.h" +#include <boost/lexical_cast.hpp> + +#include <algorithm> +#include <vector> +#include <string> + +#include <string.h> + +using namespace std; +using boost::lexical_cast; + +namespace qpid { + +class ProtocolTags { + public: + bool find(const string& tag) { + sys::Mutex::ScopedLock l(lock); + return std::find(tags.begin(), tags.end(), tag) != tags.end(); + } + + void add(const string& tag) { + sys::Mutex::ScopedLock l(lock); + if (std::find(tags.begin(), tags.end(), tag) == tags.end()) + tags.push_back(tag); + } + + static ProtocolTags& instance() { + /** First call must be made while program is still single threaded. + * This will be the case since tags are registered in static initializers. + */ + static ProtocolTags tags; + return tags; + } + + private: + sys::Mutex lock; + vector<string> tags; +}; + +Url::Invalid::Invalid(const string& s) : Exception(s) {} + +Url Url::getHostNameUrl(uint16_t port) { + Address address("tcp", std::string(), port); + if (!sys::SystemInfo::getLocalHostname(address)) + throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); + return Url(address); +} + +Url Url::getIpAddressesUrl(uint16_t port) { + Url url; + sys::SystemInfo::getLocalIpAddresses(port, url); + return url; +} + +string Url::str() const { + if (cache.empty() && !this->empty()) { + ostringstream os; + os << *this; + cache = os.str(); + } + return cache; +} + +ostream& operator<<(ostream& os, const Url& url) { + os << "amqp:"; + if (!url.getUser().empty()) os << url.getUser(); + if (!url.getPass().empty()) os << "/" << url.getPass(); + if (!(url.getUser().empty() && url.getPass().empty())) os << "@"; + Url::const_iterator i = url.begin(); + if (i!=url.end()) { + os << *i++; + while (i != url.end()) + os << "," << *i++; + } + return os; +} + +static const std::string TCP = "tcp"; + +/** Simple recursive-descent parser for this grammar: +url = ["amqp:"][ user ["/" password] "@" ] protocol_addr *("," protocol_addr) +protocol_addr = tcp_addr / rmda_addr / ssl_addr / .. others plug-in +tcp_addr = ["tcp:"] host [":" port] +rdma_addr = "rdma:" host [":" port] +ssl_addr = "ssl:" host [":" port] +*/ +class UrlParser { + public: + UrlParser(Url& u, const char* s) : url(u), text(s), end(s+strlen(s)), i(s) {} + bool parse() { + literal("amqp:"); // Optional + userPass(); // Optional + return list(&UrlParser::protocolAddr, &UrlParser::comma) && i == end; + } + + private: + typedef bool (UrlParser::*Rule)(); + + bool userPass() { + const char* at = std::find(i, end, '@'); + if (at == end) return false; + const char* slash = std::find(i, at, '/'); + url.setUser(string(i, slash)); + const char* pass = (slash == at) ? slash : slash+1; + url.setPass(string(pass, at)); + i = at+1; + return true; + } + + bool comma() { return literal(","); } + + bool protocolAddr() { + Address addr(Address::TCP, "", Address::AMQP_PORT); // Set up defaults + protocolTag(addr.protocol); // Optional + bool ok = (host(addr.host) && + (literal(":") ? port(addr.port) : true)); + if (ok) url.push_back(addr); + return ok; + } + + bool protocolTag(string& result) { + const char* j = std::find(i,end,':'); + if (j != end) { + string tag(i,j); + if (ProtocolTags::instance().find(tag)) { + i = j+1; + result = tag; + return true; + } + } + return false; + } + + // TODO aconway 2008-11-20: this does not fully 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) return false;//host is required + else h.assign(start, i); + return true; + } + + bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); } + + bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } + + bool hexDigit() { return i < end && ::strchr("01234567890abcdefABCDEF", *i) && advance(); } + + bool port(uint16_t& p) { return decimalInt(p); } + + 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) { + parseNoThrow(url); + if (empty()) + throw Url::Invalid(QPID_MSG("Invalid URL: " << url)); +} + +void Url::parseNoThrow(const char* url) { + cache.clear(); + if (!UrlParser(*this, url).parse()) + clear(); +} + +void Url::throwIfEmpty() const { + if (empty()) + throw Url::Invalid("URL contains no addresses"); +} + +std::string Url::getUser() const { return user; } +std::string Url::getPass() const { return pass; } +void Url::setUser(const std::string& s) { user = s; } +void Url::setPass(const std::string& s) { pass = s; } + +std::istream& operator>>(std::istream& is, Url& url) { + std::string s; + is >> s; + url.parse(s); + return is; +} + +void Url::addProtocol(const std::string& tag) { ProtocolTags::instance().add(tag); } + +} // namespace qpid |