diff --git a/changelog.md b/changelog.md index 0aa8505..2c95dff 100644 --- a/changelog.md +++ b/changelog.md @@ -1,13 +1,22 @@ HEAD - Feature: Basic support for the permessage-deflate extension. #344 - Improvement: Better automatic std::chrono feature detection for Visual Studio -- Improvement: Major refactoring to bundled CMake build system. CMake can now be used to - build all of the examples and the test suite. Thank you Thijs Wenker for a significant - portion of this code. #378, #435, #449 -- Bug: Fix memory leak when init_asio produces an error. #454 Thank you Mark Grimes for - reporting and fixing. -- Bug: Fix crash when processing a specially crafted HTTP header. Thank you Eli Fidler for - reporting, test cases, and a patch. #456 +- Improvement: Major refactoring to bundled CMake build system. CMake can now be + used to build all of the examples and the test suite. Thank you Thijs Wenker + for a significant portion of this code. #378, #435, #449 +- Improvement: In build environments where `lib::error_code` and + `lib::asio::error_code` match (such as using `boost::asio` with + `boost::system_error` or standalone asio with `std::system_error`, transport + errors are passed through natively rather than being reported as a translated + `pass_through` error type. +- Improvement: Add a `get_transport_error` method to Asio transport connections + to allow retrieving a machine readable native transport error. +- Bug: Fix memory leak when init_asio produces an error. #454 Thank you Mark + Grimes for reporting and fixing. +- Bug: Fix crash when processing a specially crafted HTTP header. Thank you Eli + Fidler for reporting, test cases, and a patch. #456 +- Bug: Fix an issue where standalone Asio builds that use TLS would not compile + due to lingering boost code. #448 Thank you mjsp for reporting 0.6.0 - MINOR BREAKING TRANSPORT POLICY CHANGE: Custom transport policies will now be diff --git a/test/transport/CMakeLists.txt b/test/transport/CMakeLists.txt index d1365cf..8e95648 100644 --- a/test/transport/CMakeLists.txt +++ b/test/transport/CMakeLists.txt @@ -20,6 +20,16 @@ link_openssl() final_target () set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "test") +# Test transport asio security +file (GLOB SOURCE asio/security.cpp) + +init_target (test_transport_asio_security) +build_executable (${TARGET_NAME} ${SOURCE}) +link_boost () +link_openssl() +final_target () +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "test") + endif() # Test transport iostream base diff --git a/test/transport/asio/SConscript b/test/transport/asio/SConscript index ea0526b..f8185e5 100644 --- a/test/transport/asio/SConscript +++ b/test/transport/asio/SConscript @@ -15,14 +15,18 @@ BOOST_LIBS = boostlibs(['unit_test_framework','system','thread','chrono'],env) + objs = env.Object('base_boost.o', ["base.cpp"], LIBS = BOOST_LIBS) objs += env.Object('timers_boost.o', ["timers.cpp"], LIBS = BOOST_LIBS) +objs += env.Object('security_boost.o', ["security.cpp"], LIBS = BOOST_LIBS) prgs = env.Program('test_base_boost', ["base_boost.o"], LIBS = BOOST_LIBS) prgs += env.Program('test_timers_boost', ["timers_boost.o"], LIBS = BOOST_LIBS) +prgs += env.Program('test_security_boost', ["security_boost.o"], LIBS = BOOST_LIBS) if env_cpp11.has_key('WSPP_CPP11_ENABLED'): BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework','system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] objs += env_cpp11.Object('base_stl.o', ["base.cpp"], LIBS = BOOST_LIBS_CPP11) objs += env_cpp11.Object('timers_stl.o', ["timers.cpp"], LIBS = BOOST_LIBS_CPP11) + objs += env_cpp11.Object('security_stl.o', ["security.cpp"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_base_stl', ["base_stl.o"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_timers_stl', ["timers_stl.o"], LIBS = BOOST_LIBS_CPP11) + prgs += env_cpp11.Program('test_security_stl', ["security_stl.o"], LIBS = BOOST_LIBS_CPP11) Return('prgs') diff --git a/test/transport/asio/security.cpp b/test/transport/asio/security.cpp new file mode 100644 index 0000000..a75e2a8 --- /dev/null +++ b/test/transport/asio/security.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015, 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. + * + */ +//#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE transport_asio_base +#include + +#include + +#include + +#include +#include + +template +struct dummy_con : public base { + websocketpp::lib::error_code test() { + return this->translate_ec(websocketpp::lib::asio::error_code()); + } +}; + +BOOST_AUTO_TEST_CASE( translated_ec_none ) { + dummy_con tscon; + + // If the current configuration settings result in the library error type and the asio + // error type being the same, then the code should pass through natively. Otherwise + // we should get a generic pass through error. + if(websocketpp::lib::is_same::value) { + BOOST_CHECK_EQUAL( tscon.test(), websocketpp::lib::error_code() ); + } else { + BOOST_CHECK_EQUAL( tscon.test(), websocketpp::transport::error::make_error_code(websocketpp::transport::error::pass_through) ); + } +} + +BOOST_AUTO_TEST_CASE( translated_ec_tls ) { + dummy_con tscon; + + // If the current configuration settings result in the library error type and the asio + // error type being the same, then the code should pass through natively. Otherwise + // we should get a generic pass through error. + if(websocketpp::lib::is_same::value) { + BOOST_CHECK_EQUAL( tscon.test(), websocketpp::lib::error_code() ); + } else { + BOOST_CHECK_EQUAL( tscon.test(), websocketpp::transport::error::make_error_code(websocketpp::transport::error::pass_through) ); + } +} diff --git a/websocketpp/common/type_traits.hpp b/websocketpp/common/type_traits.hpp index 72cc2e1..c89b82b 100644 --- a/websocketpp/common/type_traits.hpp +++ b/websocketpp/common/type_traits.hpp @@ -53,8 +53,10 @@ namespace lib { #ifdef _WEBSOCKETPP_CPP11_TYPE_TRAITS_ using std::aligned_storage; + using std::is_same; #else using boost::aligned_storage; + using boost::is_same; #endif } // namespace lib diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp index 6ee881a..b945fe1 100644 --- a/websocketpp/transport/asio/base.hpp +++ b/websocketpp/transport/asio/base.hpp @@ -142,9 +142,6 @@ inline custom_alloc_handler make_custom_alloc_handler( template class endpoint; -typedef lib::function - socket_shutdown_handler; - typedef lib::function async_read_handler; diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index 635a0f3..7e97c0c 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -366,6 +366,29 @@ public: return m_strand; } + /// Get the internal transport error code for a closed/failed connection + /** + * Retrieves a machine readable detailed error code indicating the reason + * that the connection was closed or failed. Valid only after the close or + * fail handler is called. + * + * Primarily used if you are using mismatched asio / system_error + * implementations such as `boost::asio` with `std::system_error`. In these + * cases the transport error type is different than the library error type + * and some WebSocket++ functions that return transport errors via the + * library error code type will be coerced into a catch all `pass_through` + * or `tls_error` error. This method will return the original machine + * readable transport error in the native type. + * + * @since 0.7.0 + * + * @return Error code indicating the reason the connection was closed or + * failed + */ + lib::asio::error_code get_transport_ec() const { + return m_tec; + } + /// Initialize transport for reading /** * init_asio is called once immediately after construction to initialize @@ -877,6 +900,7 @@ protected: // We don't know much more about the error at this point. As our // socket/security policy if it knows more: tec = socket_con_type::translate_ec(ec); + m_tec = ec; if (tec == transport::error::tls_error || tec == transport::error::pass_through) @@ -1100,6 +1124,7 @@ protected: // We don't know anything more about this error, give our // socket/security policy a crack at it. tec = socket_con_type::translate_ec(ec); + m_tec = ec; if (tec == transport::error::tls_short_read) { // TLS short read at this point is somewhat expected if both @@ -1156,6 +1181,9 @@ private: std::vector m_bufs; + /// Detailed internal error code + lib::asio::error_code m_tec; + // Handlers tcp_init_handler m_tcp_pre_init_handler; tcp_init_handler m_tcp_post_init_handler; diff --git a/websocketpp/transport/asio/security/base.hpp b/websocketpp/transport/asio/security/base.hpp index dff1450..0f08f40 100644 --- a/websocketpp/transport/asio/security/base.hpp +++ b/websocketpp/transport/asio/security/base.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Peter Thorson. All rights reserved. + * Copyright (c) 2015, 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: @@ -28,6 +28,7 @@ #ifndef WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP #define WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP +#include #include #include #include @@ -63,6 +64,8 @@ namespace transport { namespace asio { namespace socket { +typedef lib::function shutdown_handler; + /** * The transport::asio::socket::* classes are a set of security/socket related * policies and support code for the ASIO transport types. diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp index 138ea40..41634b0 100644 --- a/websocketpp/transport/asio/security/none.hpp +++ b/websocketpp/transport/asio/security/none.hpp @@ -30,6 +30,7 @@ #include +#include #include #include @@ -240,7 +241,7 @@ protected: m_socket->cancel(); } - void async_shutdown(socket_shutdown_handler h) { + void async_shutdown(socket::shutdown_handler h) { lib::asio::error_code ec; m_socket->shutdown(lib::asio::ip::tcp::socket::shutdown_both, ec); h(ec); @@ -252,20 +253,36 @@ protected: /// Translate any security policy specific information about an error code /** - * Translate_ec takes a boost error code and attempts to convert its value - * to an appropriate websocketpp error code. The plain socket policy does - * not presently provide any additional information so all errors will be - * reported as the generic transport pass_through error. + * Translate_ec takes an Asio error code and attempts to convert its value + * to an appropriate websocketpp error code. In the case that the Asio and + * Websocketpp error types are the same (such as using boost::asio and + * boost::system_error or using standalone asio and std::system_error the + * code will be passed through natively. + * + * In the case of a mismatch (boost::asio with std::system_error) a + * translated code will be returned. The plain socket policy does not have + * any additional information so all such errors will be reported as the + * generic transport pass_through error. * * @since 0.3.0 * * @param ec The error code to translate_ec * @return The translated error code */ - lib::error_code translate_ec(lib::asio::error_code) { + template + lib::error_code translate_ec(ErrorCodeType) { // We don't know any more information about this error so pass through return make_error_code(transport::error::pass_through); } + + /// Overload of translate_ec to catch cases where lib::error_code is the + /// same type as lib::asio::error_code + lib::error_code translate_ec(lib::error_code ec) { + // We don't know any more information about this error, but the error is + // the same type as the one we are translating to, so pass through + // untranslated. + return ec; + } private: enum state { UNINITIALIZED = 0, diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp index 350a49f..6be3afe 100644 --- a/websocketpp/transport/asio/security/tls.hpp +++ b/websocketpp/transport/asio/security/tls.hpp @@ -55,7 +55,7 @@ typedef lib::function(connection_hdl)> tls_init_handler; -/// TLS enabled Boost ASIO connection socket component +/// TLS enabled Asio connection socket component /** * transport::asio::tls_socket::connection implements a secure connection socket * component that uses Asio's ssl::stream to wrap an ip::tcp::socket. @@ -315,26 +315,31 @@ protected: get_raw_socket().cancel(); } - void async_shutdown(socket_shutdown_handler callback) { + void async_shutdown(socket::shutdown_handler callback) { m_socket->async_shutdown(callback); } /// Translate any security policy specific information about an error code /** - * Translate_ec takes a boost error code and attempts to convert its value - * to an appropriate websocketpp error code. Any error that is determined to - * be related to TLS but does not have a more specific websocketpp error - * code is returned under the catch all error "tls_error". + * Translate_ec takes an Asio error code and attempts to convert its value + * to an appropriate websocketpp error code. In the case that the Asio and + * Websocketpp error types are the same (such as using boost::asio and + * boost::system_error or using standalone asio and std::system_error the + * code will be passed through natively. * - * Non-TLS related errors are returned as the transport generic pass_through - * error. + * In the case of a mismatch (boost::asio with std::system_error) a + * translated code will be returned. Any error that is determined to be + * related to TLS but does not have a more specific websocketpp error code + * is returned under the catch all error `tls_error`. Non-TLS related errors + * are returned as the transport generic error `pass_through` * * @since 0.3.0 * * @param ec The error code to translate_ec * @return The translated error code */ - lib::error_code translate_ec(boost::system::error_code ec) { + template + lib::error_code translate_ec(ErrorCodeType ec) { if (ec.category() == lib::asio::error::get_ssl_category()) { if (ERR_GET_REASON(ec.value()) == SSL_R_SHORT_READ) { return make_error_code(transport::error::tls_short_read); @@ -349,6 +354,20 @@ protected: return make_error_code(transport::error::pass_through); } } + + /// Overload of translate_ec to catch cases where lib::error_code is the + /// same type as lib::asio::error_code + lib::error_code translate_ec(lib::error_code ec) { + // Normalize the tls_short_read error as it is used by the library and + // needs a consistent value. All other errors pass through natively. + // TODO: how to get the SSL category from std::error? + /*if (ec.category() == lib::asio::error::get_ssl_category()) { + if (ERR_GET_REASON(ec.value()) == SSL_R_SHORT_READ) { + return make_error_code(transport::error::tls_short_read); + } + }*/ + return ec; + } private: socket_type::handshake_type get_handshake_type() { if (m_is_server) {