Repository: qpid-proton Updated Branches: refs/heads/master 2eac44b53 -> b4cadd1db
PROTON-1351: [C++ binding] remove dependency on Proton-c url code - In preparation for moving this to the proton core library - Improved C++ url tests Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/b4cadd1d Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/b4cadd1d Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/b4cadd1d Branch: refs/heads/master Commit: b4cadd1dbd9121f546909502ff35d58500843fcc Parents: 2eac44b Author: Andrew Stitcher <[email protected]> Authored: Tue Nov 15 10:41:33 2016 -0500 Committer: Andrew Stitcher <[email protected]> Committed: Fri Nov 18 12:51:47 2016 -0500 ---------------------------------------------------------------------- proton-c/bindings/cpp/include/proton/url.hpp | 6 +- proton-c/bindings/cpp/src/url.cpp | 244 ++++++++++++++++++---- proton-c/bindings/cpp/src/url_test.cpp | 40 +++- 3 files changed, 245 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b4cadd1d/proton-c/bindings/cpp/include/proton/url.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/url.hpp b/proton-c/bindings/cpp/include/proton/url.hpp index 57ef586..78271b6 100644 --- a/proton-c/bindings/cpp/include/proton/url.hpp +++ b/proton-c/bindings/cpp/include/proton/url.hpp @@ -22,14 +22,13 @@ * */ +#include "./internal/pn_unique_ptr.hpp" #include "./types_fwd.hpp" #include "./error.hpp" #include <iosfwd> #include <string> -struct pn_url_t; - namespace proton { /// An error encountered during URL parsing. @@ -124,7 +123,8 @@ class url { friend PN_CPP_EXTERN std::string to_string(const url&); private: - pn_url_t* url_; + struct impl; + internal::pn_unique_ptr<impl> impl_; /// @cond INTERNAL http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b4cadd1d/proton-c/bindings/cpp/src/url.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/url.cpp b/proton-c/bindings/cpp/src/url.cpp index 923aaf0..1aa01e1 100644 --- a/proton-c/bindings/cpp/src/url.cpp +++ b/proton-c/bindings/cpp/src/url.cpp @@ -23,73 +23,239 @@ #include "proton/error.hpp" -#include <proton/url.h> - #include "proton_bits.hpp" +#include <cstdlib> +#include <cstring> +#include <iomanip> #include <sstream> -namespace proton { +namespace { -url_error::url_error(const std::string& s) : error(s) {} +/** URL-encode src and append to dst. */ +static std::string pni_urlencode(const std::string &src) { + static const char *bad = "@:/"; -namespace { + std::ostringstream dst; + dst << std::hex << std::uppercase << std::setfill('0'); -pn_url_t* parse_throw(const char* s) { - pn_url_t* u = pn_url_parse(s); - if (!u) throw url_error("invalid URL: " + std::string(s)); - return u; + std::size_t i = 0; + std::size_t j = src.find_first_of(bad); + while (j!=std::string::npos) { + dst << src.substr(i, j-i); + dst << "%" << std::setw(2) << src[j]; + i = j + 1; + j = src.find_first_of(bad); + } + dst << src.substr(i); + return dst.str(); } -pn_url_t* parse_allow_empty(const char* s) { - return s && *s ? parse_throw(s) : pn_url(); +// Low level url parser +static void pni_urldecode(const char *src, char *dst) +{ + const char *in = src; + char *out = dst; + while (*in != '\0') + { + if ('%' == *in) + { + if ((in[1] != '\0') && (in[2] != '\0')) + { + char esc[3]; + esc[0] = in[1]; + esc[1] = in[2]; + esc[2] = '\0'; + unsigned long d = std::strtoul(esc, NULL, 16); + *out = (char)d; + in += 3; + out++; + } + else + { + *out = *in; + in++; + out++; + } + } + else + { + *out = *in; + in++; + out++; + } + } + *out = '\0'; } -void replace(pn_url_t*& var, pn_url_t* val) { - if (var) pn_url_free(var); - var = val; -} +void parse_url(char *url, const char **scheme, const char **user, const char **pass, const char **host, const char **port, const char **path) +{ + if (!url) return; + + char *slash = std::strchr(url, '/'); + + if (slash && slash>url) { + char *scheme_end = std::strstr(slash-1, "://"); + + if (scheme_end && scheme_end<slash) { + *scheme_end = '\0'; + *scheme = url; + url = scheme_end + 3; + slash = std::strchr(url, '/'); + } + } + + if (slash) { + *slash = '\0'; + *path = slash + 1; + } + + char *at = std::strchr(url, '@'); + if (at) { + *at = '\0'; + char *up = url; + url = at + 1; + char *colon = std::strchr(up, ':'); + if (colon) { + *colon = '\0'; + char *p = colon + 1; + pni_urldecode(p, p); + *pass = p; + } + pni_urldecode(up, up); + *user = up; + } + + *host = url; + char *open = (*url == '[') ? url : 0; + if (open) { + char *close = std::strchr(open, ']'); + if (close) { + *host = open + 1; + *close = '\0'; + url = close + 1; + } + } -void defaults(pn_url_t* u) { - const char* scheme = pn_url_get_scheme(u); - const char* port = pn_url_get_port(u); - if (!scheme || *scheme=='\0' ) pn_url_set_scheme(u, url::AMQP.c_str()); - if (!port || *port=='\0' ) pn_url_set_port(u, pn_url_get_scheme(u)); + char *colon = std::strchr(url, ':'); + if (colon) { + *colon = '\0'; + *port = colon + 1; + } } } // namespace -url::url(const std::string &s) : url_(parse_throw(s.c_str())) { defaults(url_); } +namespace proton { + +struct url::impl { + static const char* const default_host; + const char* scheme; + const char* username; + const char* password; + const char* host; + const char* port; + const char* path; + char* cstr; + mutable std::string str; + + impl(const std::string& s) : + scheme(0), username(0), password(0), host(0), port(0), path(0), + cstr(new char[s.size()+1]) + { + std::strncpy(cstr, s.c_str(), s.size()); + cstr[s.size()] = 0; + parse_url(cstr, &scheme, &username, &password, &host, &port, &path); + } + + ~impl() { + delete [] cstr; + } + + void defaults() { + if (!scheme || *scheme=='\0' ) scheme = proton::url::AMQP.c_str(); + if (!host || *host=='\0' ) host = default_host; + if (!port || *port=='\0' ) port = scheme; + } -url::url(const std::string &s, bool d) : url_(parse_throw(s.c_str())) { if (d) defaults(url_); } + operator std::string() const { + if ( str.empty() ) { + if (scheme) { + str += scheme; + str += "://"; + } + if (username) { + str += pni_urlencode(username); + } + if (password) { + str += ":"; + str += pni_urlencode(password); + } + if (username || password) { + str += "@"; + } + if (host) { + if (std::strchr(host, ':')) { + str += '['; + str += host; + str += ']'; + } else { + str += host; + } + } + if (port) { + str += ':'; + str += port; + } + if (path) { + str += '/'; + str += path; + } + } + return str; + } -url::url(const url& u) : url_(parse_allow_empty(pn_url_str(u.url_))) {} +}; -url::~url() { pn_url_free(url_); } +const char* const url::impl::default_host = "localhost"; + + +url_error::url_error(const std::string& s) : error(s) {} + +url::url(const std::string &s) : impl_(new impl(s)) { impl_->defaults(); } + +url::url(const std::string &s, bool d) : impl_(new impl(s)) { if (d) impl_->defaults(); } + +url::url(const url& u) : impl_(new impl(u)) {} + +url::~url() {} url& url::operator=(const url& u) { - if (this != &u) replace(url_, parse_allow_empty(pn_url_str(u.url_))); + if (this != &u) { + impl_.reset(new impl(*u.impl_)); + } return *this; } -url::operator std::string() const { return str(pn_url_str(url_)); } +url::operator std::string() const { return *impl_; } -std::string url::scheme() const { return str(pn_url_get_scheme(url_)); } -std::string url::user() const { return str(pn_url_get_username(url_)); } -std::string url::password() const { return str(pn_url_get_password(url_)); } -std::string url::host() const { return str(pn_url_get_host(url_)); } -std::string url::port() const { return str(pn_url_get_port(url_)); } -std::string url::path() const { return str(pn_url_get_path(url_)); } +std::string url::scheme() const { return str(impl_->scheme); } +std::string url::user() const { return str(impl_->username); } +std::string url::password() const { return str(impl_->password); } +std::string url::host() const { return str(impl_->host); } +std::string url::port() const { return str(impl_->port); } +std::string url::path() const { return str(impl_->path); } std::string url::host_port() const { return host() + ":" + port(); } -bool url::empty() const { return *pn_url_str(url_) == '\0'; } +bool url::empty() const { return impl_->str.empty(); } const std::string url::AMQP("amqp"); const std::string url::AMQPS("amqps"); uint16_t url::port_int() const { // TODO aconway 2015-10-27: full service name lookup + // astitcher 2016-11-17: It is hard to make the full service name lookup platform independent if (port() == AMQP) return 5672; if (port() == AMQPS) return 5671; std::istringstream is(port()); @@ -101,21 +267,21 @@ uint16_t url::port_int() const { } std::ostream& operator<<(std::ostream& o, const url& u) { - return o << pn_url_str(u.url_); + return o << std::string(u); } std::string to_string(const url& u) { - return std::string(pn_url_str(u.url_)); + return u; } std::istream& operator>>(std::istream& i, url& u) { std::string s; i >> s; if (!i.fail() && !i.bad()) { - pn_url_t* p = pn_url_parse(s.c_str()); - if (p) { - replace(u.url_, p); - defaults(u.url_); + if (!s.empty()) { + url::impl* p = new url::impl(s); + p->defaults(); + u.impl_.reset(p); } else { i.clear(std::ios::failbit); } http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b4cadd1d/proton-c/bindings/cpp/src/url_test.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/url_test.cpp b/proton-c/bindings/cpp/src/url_test.cpp index ad9eca4..0727e2c 100644 --- a/proton-c/bindings/cpp/src/url_test.cpp +++ b/proton-c/bindings/cpp/src/url_test.cpp @@ -22,11 +22,45 @@ namespace { -void parse_to_string_test() { - std::string s("amqp://foo:xyz/path"); +void check_url(const std::string& s, + const std::string& scheme, + const std::string& user, + const std::string& pwd, + const std::string& host, + const std::string& port, + const std::string& path + ) +{ proton::url u(s); - ASSERT_EQUAL(to_string(u), s); + ASSERT_EQUAL(scheme, u.scheme()); + ASSERT_EQUAL(user, u.user()); + ASSERT_EQUAL(pwd, u.password()); + ASSERT_EQUAL(host, u.host()); + ASSERT_EQUAL(port, u.port()); + ASSERT_EQUAL(path, u.path()); } + +void parse_to_string_test() { + check_url("amqp://foo:xyz/path", + "amqp", "", "", "foo", "xyz", "path"); + check_url("amqp://username:password@host:1234/path", + "amqp", "username", "password", "host", "1234", "path"); + check_url("host:1234", + "amqp", "", "", "host", "1234", ""); + check_url("host", + "amqp", "", "", "host", "amqp", ""); + check_url("host/path", + "amqp", "", "", "host", "amqp", "path"); + check_url("amqps://host", + "amqps", "", "", "host", "amqps", ""); + check_url("/path", + "amqp", "", "", "localhost", "amqp", "path"); + check_url("", + "amqp", "", "", "localhost", "amqp", ""); + check_url(":1234", + "amqp", "", "", "localhost", "1234", ""); +} + } int main(int, char**) { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
