From 7d7e8e853acb3dde9de34f6ce77acf46b21cd3ed Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Mon, 28 Nov 2011 08:39:11 -0600 Subject: [PATCH] implements new URI processing interface and corrects associated unit tests --- doc/uri.txt | 0 src/uri.cpp | 236 +++++++++++++++++++++++++++++++++++++++++ src/uri.hpp | 125 ++++++++++------------ test/basic/parsing.cpp | 225 +++++++++++++++++++++++++++------------ 4 files changed, 446 insertions(+), 140 deletions(-) create mode 100644 doc/uri.txt create mode 100644 src/uri.cpp diff --git a/doc/uri.txt b/doc/uri.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/uri.cpp b/src/uri.cpp new file mode 100644 index 0000000000..0a79bb4436 --- /dev/null +++ b/src/uri.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2011, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "uri.hpp" + +#include + +#include + +using websocketpp::uri; + +uri::uri(const std::string& uri) { + boost::cmatch matches; + static const boost::regex expression("(ws|wss)://([^/:\\[]+|\\[[0-9:]+\\])(:\\d{1,5})?(/[^#]*)?"); + + // TODO: should this split resource into path/query? + + if (boost::regex_match(uri.c_str(), matches, expression)) { + m_secure = (matches[1] == "wss"); + m_host = matches[2]; + + std::string port(matches[3]); + + if (port != "") { + // strip off the : + // this could probably be done with a better regex. + port = port.substr(1); + } + + m_port = get_port_from_string(port); + + m_resource = matches[4]; + + if (m_resource == "") { + m_resource = "/"; + } + + return; + } + + throw websocketpp::uri_exception("Error parsing WebSocket URI"); +} + +uri::uri(bool secure, const std::string& host, uint16_t port, const std::string& resource) + : m_secure(secure), + m_host(host), + m_port(port), + m_resource(resource == "" ? "/" : resource) {} + +uri::uri(bool secure, + const std::string& host, + const std::string& port, + const std::string& resource) + : m_secure(secure), + m_host(host), + m_port(get_port_from_string(port)), + m_resource(resource == "" ? "/" : resource) {} + +/* Slightly cleaner C++11 delegated constructor method + +uri::uri(bool secure, + const std::string& host, + uint16_t port, + const std::string& resource) + : m_secure(secure), + m_host(host), + m_port(port), + m_resource(resource == "" ? "/" : resource) +{ + if (m_port > 65535) { + throw websocketpp::uri_exception("Port must be less than 65535"); + } +} + +uri::uri(bool secure, + const std::string& host, + const std::string& port, + const std::string& resource) + : uri(secure, host, get_port_from_string(port), resource) {} + +*/ + +bool uri::get_secure() const { + return m_secure; +} + +std::string uri::get_host() const { + return m_host; +} + +uint16_t uri::get_port() const { + return m_port; +} + +std::string uri::get_resource() const { + return m_resource; +} + +std::string uri::str() const { + std::stringstream s; + + s << "ws" << (m_secure ? "s" : "") << "://" << m_host; + + /*if (m_port != (m_secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT)) { + s << ":" << m_port; + }*/ + + s << m_resource; + return s.str(); +} + +uint16_t uri::get_port_from_string(const std::string& port) const { + if (port == "") { + return (m_secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT); + } + + unsigned int t_port = atoi(port.c_str()); + + if (t_port > 65535) { + throw websocketpp::uri_exception("Port must be less than 65535"); + } + + if (t_port == 0) { + throw websocketpp::uri_exception("Error parsing port string"); + } + + return t_port; +} + +/*std::string uri::base() const { + std::stringstream s; + + s << "ws" << (secure ? "s" : "") << "://" << host; + + if (port != (secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT)) { + s << ":" << port; + } + + s << "/"; + return s.str(); +} + +void uri::set_secure(bool secure) { + m_secure = secure; +} + +void uri::set_host(const std::string& host) { + // TODO: additional validation? + m_host = host; +} +void set_port(uint16_t port) { + if (port > 65535) { + throw uri_exception("Port must be less than 65535"); + } +} +void set_port(const std::string& port); +void set_resource(const std::string& resource);*/ + + + + + + + + + + + + + +/*bool uri::parse(const std::string& uri) { + boost::cmatch matches; + static const boost::regex expression("(ws|wss)://([^/:\\[]+|\\[[0-9:]+\\])(:\\d{1,5})?(/[^#]*)?"); + + // TODO: should this split resource into path/query? + + if (boost::regex_match(uri.c_str(), matches, expression)) { + secure = (matches[1] == "wss"); + host = matches[2]; + + std::string port(matches[3]); + + if (port != "") { + // strip off the : + // this could probably be done with a better regex. + port = port.substr(1); + } + + m_port = get_port_from_string(port); + + if (matches[3] == "") { + port = (secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT); + } else { + unsigned int t_port = atoi(std::string(matches[3]).substr(1).c_str()); + + if (t_port > 65535) { + return false; + } + + port = atoi(std::string(matches[3]).substr(1).c_str()); + } + + resource = (matches[4] == "" ? "/" : matches[4]); + + return true; + } + + return false; +}*/ + + + diff --git a/src/uri.hpp b/src/uri.hpp index 194347467f..0b3f0ebdb5 100644 --- a/src/uri.hpp +++ b/src/uri.hpp @@ -28,83 +28,66 @@ #ifndef WEBSOCKETPP_URI_HPP #define WEBSOCKETPP_URI_HPP -#include "common.hpp" - +#include #include #include -#include namespace websocketpp { -struct uri { - bool parse(const std::string& uri) { - boost::cmatch what; - static const boost::regex expression("(ws|wss)://([^/:\\[]+|\\[[0-9:]+\\])(:\\d{1,5})?(/[^#]*)?"); - - // TODO: should this split resource into path/query? - - if (boost::regex_match(uri.c_str(), what, expression)) { - if (what[1] == "wss") { - secure = true; - } else { - secure = false; - } - - host = what[2]; - - if (what[3] == "") { - port = (secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT); - } else { - unsigned int t_port = atoi(std::string(what[3]).substr(1).c_str()); - - if (t_port > 65535) { - return false; - } - - port = atoi(std::string(what[3]).substr(1).c_str()); - } - - if (what[4] == "") { - resource = "/"; - } else { - resource = what[4]; - } - - return true; - } else { - return false; - } - } - std::string base() { - std::stringstream s; - - s << "ws" << (secure ? "s" : "") << "://" << host; - - if (port != (secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT)) { - s << ":" << port; - } - - s << "/"; - return s.str(); +// WebSocket URI only (not http/etc) - } - std::string str() { - std::stringstream s; - - s << "ws" << (secure ? "s" : "") << "://" << host; - - if (port != (secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT)) { - s << ":" << port; - } - - s << resource; - return s.str(); - } - - bool secure; - std::string host; - uint16_t port; - std::string resource; +class uri_exception : public std::exception { +public: + uri_exception(const std::string& msg) : m_msg(msg) {} + ~uri_exception() throw() {} + + virtual const char* what() const throw() { + return m_msg.c_str(); + } +private: + std::string m_msg; +}; + +class uri { +public: + static const int DEFAULT_PORT = 80; + static const int DEFAULT_SECURE_PORT = 443; + + uri(const std::string& uri); + uri(bool secure, const std::string& host, uint16_t port, const std::string& resource); + uri(bool secure, const std::string& host, const std::string& port, const std::string& resource); + + bool get_secure() const; + std::string get_host() const; + uint16_t get_port() const; + std::string get_resource() const; + std::string str() const; + + // get query? + // get fragment + + // hi <3 + + // get the string representation of this URI + + //std::string base() const; // is this still needed? + + // setter methods set some or all (in the case of parse) based on the input. + // These functions throw a uri_exception on failure. + /*void set_uri(const std::string& uri); + + void set_secure(bool secure); + void set_host(const std::string& host); + void set_port(uint16_t port); + void set_port(const std::string& port); + void set_resource(const std::string& resource);*/ +private: + uint16_t get_port_from_string(const std::string& port) const; + + bool m_secure; + std::string m_host; + uint16_t m_port; + std::string m_resource; }; } // namespace websocketpp diff --git a/test/basic/parsing.cpp b/test/basic/parsing.cpp index 6c74c2d7a9..5563cc24f4 100644 --- a/test/basic/parsing.cpp +++ b/test/basic/parsing.cpp @@ -24,143 +24,230 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MODULE parsing +//#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE uri #include -#include "../../src/websocketpp.hpp" +#include + +#include "../../src/uri.hpp" // Test a regular valid ws URI BOOST_AUTO_TEST_CASE( uri_valid ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("ws://localhost:9000/chat"); + + BOOST_CHECK( uri.get_secure() == false ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("ws://localhost:9000/chat") == true); - BOOST_CHECK( uri.secure == false ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/chat" ); + BOOST_CHECK( exception == false); } -// Valid URI with no port specified (unsecure) +// Test a regular valid ws URI BOOST_AUTO_TEST_CASE( uri_valid_no_port_unsecure ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("ws://localhost/chat"); + + BOOST_CHECK( uri.get_secure() == false ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 80 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("ws://localhost/chat") == true); - BOOST_CHECK( uri.secure == false ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 80 ); - BOOST_CHECK( uri.resource == "/chat" ); + BOOST_CHECK( exception == false); } + + // Valid URI with no port (secure) BOOST_AUTO_TEST_CASE( uri_valid_no_port_secure ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://localhost/chat"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 443 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://localhost/chat") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 443 ); - BOOST_CHECK( uri.resource == "/chat" ); + BOOST_CHECK( exception == false); } // Valid URI with no resource BOOST_AUTO_TEST_CASE( uri_valid_no_resource ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://localhost:9000"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("ws://localhost:9000") == true); - BOOST_CHECK( uri.secure == false ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/" ); + BOOST_CHECK( exception == false); } // Valid URI IPv6 Literal BOOST_AUTO_TEST_CASE( uri_valid_ipv6_literal ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://[::1]:9000/chat"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "[::1]"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://[::1]:9000/chat") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "[::1]"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/chat" ); + BOOST_CHECK( exception == false); } + + // Valid URI with more complicated host BOOST_AUTO_TEST_CASE( uri_valid_2 ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://thor-websocket.zaphoyd.net:88/"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "thor-websocket.zaphoyd.net"); + BOOST_CHECK( uri.get_port() == 88 ); + BOOST_CHECK( uri.get_resource() == "/" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://thor-websocket.zaphoyd.net:88/") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "thor-websocket.zaphoyd.net"); - BOOST_CHECK( uri.port == 88 ); - BOOST_CHECK( uri.resource == "/" ); + BOOST_CHECK( exception == false); } + // Invalid URI (port too long) BOOST_AUTO_TEST_CASE( uri_invalid_long_port ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://localhost:900000/chat"); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://localhost:900000/chat") == false); + BOOST_CHECK( exception == true); } -// INvalid URI (http method) +// Invalid URI (http method) BOOST_AUTO_TEST_CASE( uri_invalid_http ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("http://localhost:9000/chat"); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("http://localhost:9000/chat") == false); + BOOST_CHECK( exception == true); } // Valid URI IPv4 literal BOOST_AUTO_TEST_CASE( uri_valid_ipv4_literal ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://127.0.0.1:9000/chat"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "127.0.0.1"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://127.0.0.1:9000/chat") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "127.0.0.1"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/chat" ); + BOOST_CHECK( exception == false); } // Valid URI complicated resource path BOOST_AUTO_TEST_CASE( uri_valid_3 ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://localhost:9000/chat/foo/bar"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat/foo/bar" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://localhost:9000/chat/foo/bar") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/chat/foo/bar" ); + BOOST_CHECK( exception == false); } // Invalid URI broken method separator BOOST_AUTO_TEST_CASE( uri_invalid_method_separator ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss:/localhost:9000/chat"); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss:/localhost:9000/chat") == false); + BOOST_CHECK( exception == true); } // Invalid URI port > 65535 BOOST_AUTO_TEST_CASE( uri_invalid_gt_16_bit_port ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss:/localhost:70000/chat"); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss:/localhost:70000/chat") == false); + BOOST_CHECK( exception == true); } // Invalid URI includes uri fragment BOOST_AUTO_TEST_CASE( uri_invalid_fragment ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss:/localhost:70000/chat#foo"); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss:/localhost:70000/chat#foo") == false); + BOOST_CHECK( exception == true); } // Valid URI complicated resource path with query BOOST_AUTO_TEST_CASE( uri_valid_4 ) { - websocketpp::ws_uri uri; + bool exception = false; + try { + websocketpp::uri uri("wss://localhost:9000/chat/foo/bar?foo=bar"); + + BOOST_CHECK( uri.get_secure() == true ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat/foo/bar?foo=bar" ); + } catch (websocketpp::uri_exception& e) { + exception = true; + } - BOOST_CHECK( uri.parse("wss://localhost:9000/chat/foo/bar?foo=bar") == true); - BOOST_CHECK( uri.secure == true ); - BOOST_CHECK( uri.host == "localhost"); - BOOST_CHECK( uri.port == 9000 ); - BOOST_CHECK( uri.resource == "/chat/foo/bar?foo=bar" ); + BOOST_CHECK( exception == false); } +// TODO: tests for the other two constructors \ No newline at end of file