From a973cb15bc401e7ccc76e351414f73dd2bf44f11 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Mon, 4 Jun 2018 13:23:17 -0400 Subject: [PATCH 01/15] Incremented version to 0.8.6 --- CMakeLists.txt | 2 +- doc/conf.py | 2 +- src/show.hpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 63e62e2..f207344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ SET( CMAKE_CXX_STANDARD_REQUIRED ON ) PROJECT( "SHOW Tests & Examples" - VERSION 0.8.5 + VERSION 0.8.6 LANGUAGES CXX ) diff --git a/doc/conf.py b/doc/conf.py index 27f2d9c..0a54d83 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -56,7 +56,7 @@ # The short X.Y version. version = u'0.8' # The full version, including alpha/beta/rc tags. -release = u'0.8.5' +release = u'0.8.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/show.hpp b/src/show.hpp index 12fe232..fb0bb5a 100644 --- a/src/show.hpp +++ b/src/show.hpp @@ -35,8 +35,8 @@ namespace show static const std::string name { "SHOW" }; static const int major { 0 }; static const int minor { 8 }; - static const int revision{ 5 }; - static const std::string string { "0.8.5" }; + static const int revision{ 6 }; + static const std::string string { "0.8.6" }; } From fc5c1c3d5051841ca78aa01c6048c899277b85fd Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Mon, 4 Jun 2018 13:52:14 -0400 Subject: [PATCH 02/15] Split files into foldable major sections --- src/show.hpp | 107 +++++++++++++++++++++-------------------- src/show/multipart.hpp | 47 ++++++++++-------- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/src/show.hpp b/src/show.hpp index fb0bb5a..c815946 100644 --- a/src/show.hpp +++ b/src/show.hpp @@ -25,11 +25,8 @@ #include -namespace show +namespace show // Constants //////////////////////////////////////////////////// { - // Constants /////////////////////////////////////////////////////////////// - - namespace version { static const std::string name { "SHOW" }; @@ -38,21 +35,11 @@ namespace show static const int revision{ 6 }; static const std::string string { "0.8.6" }; } - - - // Forward declarations //////////////////////////////////////////////////// - - - class _socket; - class connection; - class server; - class request; - class response; - - - // Basic types ///////////////////////////////////////////////////////////// - - +} + + +namespace show // Basic types ////////////////////////////////////////////////// +{ using socket_fd = int; // `int` instead of `std::streamsize` because this is a buffer for POSIX // `read()` @@ -132,10 +119,16 @@ namespace show std::vector< std::string >, _less_ignore_case_ASCII >; - - - // Classes ///////////////////////////////////////////////////////////////// - +} + + +namespace show // Main classes ///////////////////////////////////////////////// +{ + class _socket; + class connection; + class server; + class request; + class response; class _socket { @@ -355,7 +348,11 @@ namespace show int timeout() const; int timeout( int ); }; - +} + + +namespace show // Throwables /////////////////////////////////////////////////// +{ // TODO: Add file descriptor to `socket_error` throws class socket_error : public std::runtime_error { using runtime_error::runtime_error; }; class request_parse_error : public std::runtime_error { using runtime_error::runtime_error; }; @@ -370,23 +367,21 @@ namespace show }; class connection_timeout : public connection_interrupted {}; class client_disconnected : public connection_interrupted {}; - - - // Functions /////////////////////////////////////////////////////////////// - - +} + + +namespace show // URL-encoding ///////////////////////////////////////////////// +{ std::string url_encode( const std::string& o, bool use_plus_space = true ); std::string url_decode( const std::string& ); - - - // Implementations ///////////////////////////////////////////////////////// - - - // _socket ----------------------------------------------------------------- - +} + + +namespace show // `show::_socket` implementation /////////////////////////////// +{ inline _socket::_socket( socket_fd fd, const std::string& address, @@ -520,9 +515,11 @@ namespace show else return WRITE; } - - // connection -------------------------------------------------------------- - +} + + +namespace show // `show::connection` implementation //////////////////////////// +{ inline connection::connection( socket_fd fd, const std::string& client_address, @@ -845,9 +842,11 @@ namespace show _timeout = t; return _timeout; } - - // request ----------------------------------------------------------------- - +} + + +namespace show // `show::request` implementation /////////////////////////////// +{ inline request::request( request&& o ) : _connection { o._connection }, read_content { std::move( o.read_content ) }, @@ -1298,9 +1297,11 @@ namespace show return result; } - - // response ---------------------------------------------------------------- - +} + + +namespace show // `show::response` implementation ////////////////////////////// +{ inline response::response( connection & c, http_protocol protocol, @@ -1421,9 +1422,11 @@ namespace show { return _connection -> overflow( ch ); } - - // server ------------------------------------------------------------------ - +} + + +namespace show // `show::server` implementation //////////////////////////////// +{ inline server::server( const std::string& address, unsigned int port, @@ -1617,9 +1620,11 @@ namespace show _timeout = t; return _timeout; } - - // Functions --------------------------------------------------------------- - +} + + +namespace show // URL-encoding implementations ///////////////////////////////// +{ inline std::string url_encode( const std::string& o, bool use_plus_space diff --git a/src/show/multipart.hpp b/src/show/multipart.hpp index 0fd1008..cc41dde 100644 --- a/src/show/multipart.hpp +++ b/src/show/multipart.hpp @@ -11,11 +11,8 @@ #include -namespace show +namespace show // `show::multipart` class ////////////////////////////////////// { - // Classes ///////////////////////////////////////////////////////////////// - - class multipart { protected: @@ -108,7 +105,11 @@ namespace show iterator begin(); iterator end(); }; - +} + + +namespace show // Utilities //////////////////////////////////////////////////// +{ class multipart_parse_error : public request_parse_error { using request_parse_error::request_parse_error; @@ -127,13 +128,11 @@ namespace show bool & segment_boundary_reached, std::function< void() > parent_finished_callback ); - - - // Implementations ///////////////////////////////////////////////////////// - - - // Segment ----------------------------------------------------------------- - +} + + +namespace show // `show::multipart::segment` implementation //////////////////// +{ // Initializes an invalid `segment` for use with `multipart::iterator`'s // copy constructor inline multipart::segment::segment() : @@ -384,9 +383,11 @@ namespace show else return traits_type::eof(); } - - // Iterator ---------------------------------------------------------------- - +} + + +namespace show // `show::multipart::iterator` implementation /////////////////// +{ inline multipart::iterator::iterator( multipart& p, bool end ) : _parent { &p }, _is_end { end }, @@ -479,9 +480,11 @@ namespace show { return !( *this == o ); } - - // Multipart --------------------------------------------------------------- - +} + + +namespace show // `show::multipart` implementation ///////////////////////////// +{ template< class String > multipart::multipart( std::streambuf& b, String&& boundary @@ -557,9 +560,11 @@ namespace show { return iterator{ *this, true }; } - - // Helper functions -------------------------------------------------------- - +} + + +namespace show // Utility functions implementation ///////////////////////////// +{ inline std::streambuf::int_type _read_buffer_until_boundary( bool crlf_start, std::streambuf & buffer, From fe568528b28dd4818582b6f4579484c4032d859d Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Fri, 21 Sep 2018 20:58:10 -0400 Subject: [PATCH 03/15] Added modern CMake configuration & package installation --- CMakeLists.txt | 96 ++++++++++++++++------------------------- examples/CMakeLists.txt | 18 ++++++++ tests/CMakeLists.txt | 38 ++++++++++++++++ 3 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 examples/CMakeLists.txt create mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index f207344..8e0118d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,76 +1,56 @@ CMAKE_MINIMUM_REQUIRED( VERSION 3.6 ) -SET( CMAKE_CXX_STANDARD 11 ) -SET( CMAKE_CXX_STANDARD_REQUIRED ON ) +SET( CMAKE_CXX_STANDARD 11 ) +SET( CMAKE_CXX_STANDARD_REQUIRED ON ) +SET( CMAKE_CXX_EXTENSIONS OFF ) PROJECT( - "SHOW Tests & Examples" + "Simple Header-Only Webserver" VERSION 0.8.6 LANGUAGES CXX ) -FIND_LIBRARY( UNITTEST_LIBRARY UnitTest++ ) - -INCLUDE_DIRECTORIES( - src -) +ENABLE_TESTING() -# Tests ######################################################################## +ADD_LIBRARY( show INTERFACE ) +TARGET_INCLUDE_DIRECTORIES( + show + INTERFACE + "$" + "$" +) +INSTALL( + TARGETS show + EXPORT "show-config" + RUNTIME DESTINATION "bin/" + LIBRARY DESTINATION "lib/" + ARCHIVE DESTINATION "lib/static/" +) +INSTALL( + DIRECTORY "src/" + DESTINATION "include/" +) -if( UNITTEST_LIBRARY ) - FIND_LIBRARY( CURL_LIBRARY curl ) - ADD_EXECUTABLE( tests - ../tests/async_utils.cpp - ../tests/base64_tests.cpp - ../tests/connection_tests.cpp - ../tests/multipart_tests.cpp - ../tests/request_tests.cpp - ../tests/response_tests.cpp - ../tests/server_tests.cpp - ../tests/tests.cpp - ../tests/type_tests.cpp - ../tests/url_encode_tests.cpp - ) - TARGET_LINK_LIBRARIES( tests - ${UNITTEST_LIBRARY} - ${CURL_LIBRARY} - ) -endif( UNITTEST_LIBRARY ) +ADD_SUBDIRECTORY( "tests/" ) +ADD_SUBDIRECTORY( "examples/" ) -# Examples ##################################################################### -ADD_EXECUTABLE( echo - ../examples/echo.cpp -) -ADD_EXECUTABLE( fileserve - ../examples/fileserve.cpp +INCLUDE( CMakePackageConfigHelpers ) +WRITE_BASIC_PACKAGE_VERSION_FILE( + "${CMAKE_BINARY_DIR}/show-config-version.cmake" + COMPATIBILITY SameMinorVersion ) -ADD_EXECUTABLE( hello_world - ../examples/hello_world.cpp -) -ADD_EXECUTABLE( http_1_1 - ../examples/http_1_1.cpp -) -ADD_EXECUTABLE( - multipart_form_handling - ../examples/multipart_form_handling.cpp -) -ADD_EXECUTABLE( multiple_clients - ../examples/multiple_clients.cpp -) -ADD_EXECUTABLE( streaming_echo - ../examples/streaming_echo.cpp +INSTALL( + FILES "${CMAKE_BINARY_DIR}/show-config-version.cmake" + DESTINATION "lib/cmake/show/" ) -ADD_CUSTOM_TARGET( examples ) -ADD_DEPENDENCIES( examples - echo - fileserve - hello_world - http_1_1 - multipart_form_handling - multiple_clients - streaming_echo +INSTALL( + EXPORT "show-config" + NAMESPACE "SHOW::" + DESTINATION "lib/cmake/show/" ) + +EXPORT( EXPORT "show-config" FILE "show-config.cmake" ) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..ae923bf --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,18 @@ +SET( + SHOW_EXAMPLES + echo + fileserve + hello_world + http_1_1 + multipart_form_handling + multiple_clients + streaming_echo +) + +FOREACH( EXAMPLE IN LISTS SHOW_EXAMPLES ) + ADD_EXECUTABLE( ${EXAMPLE} EXCLUDE_FROM_ALL "${EXAMPLE}.cpp" ) + TARGET_LINK_LIBRARIES( ${EXAMPLE} show ) +ENDFOREACH() + +ADD_CUSTOM_TARGET( examples ) +ADD_DEPENDENCIES( examples ${SHOW_EXAMPLES} ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..0c778eb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,38 @@ +# Try to find by package first (UnitTest++ provides one), but fall back to +# FIND_LIBRARY() if it's not installed (Homebrew for example does not) +FIND_PACKAGE( UnitTest++ QUIET ) +IF( NOT UnitTest++ ) + FIND_LIBRARY( UnitTest++ UnitTest++ ) +ENDIF() + + +IF( UnitTest++ ) + # Unfortunately CMake doesn't supply a component-based package for cURL + FIND_PACKAGE( CURL REQUIRED ) + + ADD_EXECUTABLE( + show_unit_tests + "async_utils.cpp" + "base64_tests.cpp" + "connection_tests.cpp" + "multipart_tests.cpp" + "request_tests.cpp" + "response_tests.cpp" + "server_tests.cpp" + "tests.cpp" + "type_tests.cpp" + "url_encode_tests.cpp" + ) + TARGET_INCLUDE_DIRECTORIES( show_unit_tests PRIVATE ${CURL_INCLUDE_DIRS} ) + TARGET_LINK_LIBRARIES( + show_unit_tests + PRIVATE + show + UnitTest++ + ${CURL_LIBRARIES} + ) + + ADD_TEST( NAME unit_tests COMMAND show_unit_tests ) +ELSE() + MESSAGE( WARNING "UnitTest++ not found, not building unit tests" ) +ENDIF() From e3358fa91b17ddc473ca534ac945be58e94e3bbf Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Fri, 21 Sep 2018 21:23:23 -0400 Subject: [PATCH 04/15] Updated documentation to reflect CMake package support --- doc/Tutorial.rst | 21 ++++++++++++--------- examples/README.md | 8 +++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/doc/Tutorial.rst b/doc/Tutorial.rst index c4a98dd..d139822 100644 --- a/doc/Tutorial.rst +++ b/doc/Tutorial.rst @@ -9,20 +9,23 @@ This shows the basic usage of SHOW; see the `examples `_ package. Once installed somewhere CMake can find it, import and use SHOW in your *CMakeLists.txt* with:: - clang++ -I "SHOW/src/" ... - -SHOW is entirely contained in a single header file, you have to do then is include SHOW using ``#include ``. With either compiler you'll also need to specify C++11 support with ``-std=c++11``. + FIND_PACKAGE( SHOW REQUIRED COMPONENTS show ) + ADD_EXECUTABLE( my_server my_server.cpp ) + TARGET_LINK_LIBRARIES( my_server PRIVATE SHOW::show ) -If you use `CMake `_ and don't have SHOW linked to said include path, you'll need to include the following in your *CMakeLists.txt*:: +You should also switch your compiler to C++11 mode with:: - include_directories( "SHOW/src/" ) + SET( CMAKE_CXX_STANDARD 11 ) + SET( CMAKE_CXX_STANDARD_REQUIRED ON ) + SET( CMAKE_CXX_EXTENSIONS OFF ) -replacing ``"SHOW/src/"`` with wherever you've cloned or installed SHOW. Switch to C++11 mode with:: +For GCC and Clang, you can either link `show.hpp` to one of your standard include search paths, or use the ``-I`` flag to tell the compiler where too find the header:: - set( CMAKE_CXX_STANDARD 11 ) - set( CMAKE_CXX_STANDARD_REQUIRED ON ) + clang++ -I "SHOW/src/" ... + +SHOW is entirely contained in a single header file, you have to do then is include SHOW using ``#include ``. With either compiler you'll also need to specify C++11 support with ``-std=c++11``. Creating a Server ================= diff --git a/examples/README.md b/examples/README.md index 27fc742..b27c41e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,10 @@ -There are two easy ways to build the examples in this directory. If you have [`cmake`](https://cmake.org/) installed, there are build instructions in the supplied *CMakeLists.txt*. To use it, first run +There are two easy ways to build the examples in this directory. If you have [CMake](https://cmake.org/) installed, make a build directory somewhere and `cd` to it. Then run ```sh -mkdir -p make -cd make -cmake .. +cmake $SHOW_REPO_DIR ``` -then either run `make examples` to build all examples, or `make $NAME` to build a specific example. The other way to build any of the examples is manually with Clang (`clang++`) or GCC (`g++`). Assuming you're running this in the "make" directory from above: +where `$SHOW_REPO_DIR` is where you cloned SHOW. Then either run `make examples` to build all examples, or `make $NAME` to build a specific example. The other way to build any of the examples is manually with Clang (`clang++`) or GCC (`g++`). Assuming you're running this in the "make" directory from above: ```sh clang++ -std=c++11 -I ../src ../examples/$NAME.cpp -o $NAME From d5376a66800051552531e7c9d5823fdde7222b45 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Fri, 21 Sep 2018 21:27:36 -0400 Subject: [PATCH 05/15] Removed reference to in-source build in examples README --- examples/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index b27c41e..84abfe2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,10 +4,13 @@ There are two easy ways to build the examples in this directory. If you have [C cmake $SHOW_REPO_DIR ``` -where `$SHOW_REPO_DIR` is where you cloned SHOW. Then either run `make examples` to build all examples, or `make $NAME` to build a specific example. The other way to build any of the examples is manually with Clang (`clang++`) or GCC (`g++`). Assuming you're running this in the "make" directory from above: +where `$SHOW_REPO_DIR` is where you cloned SHOW. Then either run `make examples` to build all examples, or `make $NAME` to build a specific example. The other way to build any of the examples is manually with Clang (`clang++`) or GCC (`g++`): ```sh -clang++ -std=c++11 -I ../src ../examples/$NAME.cpp -o $NAME +clang++ -std=c++11 \ + -I $SHOW_REPO_DIR/src \ + $SHOW_REPO_DIR/examples/$NAME.cpp \ + -o $NAME ``` Each of these servers can be tested from a second terminal window. From 51d633387d6a1a9a6e182239ef2a23d4202d1b9b Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 15:33:02 -0400 Subject: [PATCH 06/15] Tweaked some documentation --- doc/{Classes.rst => Types.rst} | 16 ++++++++-------- doc/index.rst | 2 +- examples/echo.cpp | 2 +- src/show.hpp | 4 +++- 4 files changed, 13 insertions(+), 11 deletions(-) rename doc/{Classes.rst => Types.rst} (97%) diff --git a/doc/Classes.rst b/doc/Types.rst similarity index 97% rename from doc/Classes.rst rename to doc/Types.rst index b6129eb..09c897b 100644 --- a/doc/Classes.rst +++ b/doc/Types.rst @@ -1,9 +1,9 @@ -=============== -Classes & Types -=============== +===== +Types +===== -Classes -======= +Main Types +========== The public interfaces to the main SHOW classes are documented on the following pages: @@ -15,14 +15,14 @@ The public interfaces to the main SHOW classes are documented on the following p Request Response -Types -===== +Support Types +============= .. cpp:namespace-push:: show .. cpp:enum:: http_protocol - Symbolizes the possibly HTTP protocols understood by SHOW. The enum members are: + Symbolizes the HTTP protocols understood by SHOW. The enum members are: +--------------+---------------------------------------------------------+ | ``HTTP_1_0`` | HTTP/1.0 | diff --git a/doc/index.rst b/doc/index.rst index 638e307..3a31373 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,7 +15,7 @@ SHOW is released under the `zlib license { &request }, {} }; diff --git a/src/show.hpp b/src/show.hpp index c815946..2b7f7f8 100644 --- a/src/show.hpp +++ b/src/show.hpp @@ -654,7 +654,7 @@ namespace show // `show::connection` implementation //////////////////////////// std::streamsize count ) { - // TODO: copy in available chunks rather than ~i calls to `sbumpc()`? + // FIXME: copy in available chunks rather than ~i calls to `sbumpc()` std::streamsize i{ 0 }; @@ -1267,6 +1267,8 @@ namespace show // `show::request` implementation /////////////////////////////// std::streamsize count ) { + // FIXME: copy in available chunks rather than ~i calls to `sbumpc()` + std::streamsize read; if( _unknown_content_length ) From 6fb9b9ff58f15f1c9d629313c5aad275861707eb Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 17:42:27 -0400 Subject: [PATCH 07/15] Improved base64 decoding & added option to ignore incorrect padding --- doc/Utilities.rst | 4 +- src/show/base64.hpp | 88 +++++++++++++++++++++--------------------- tests/base64_tests.cpp | 22 +++++++++++ 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/doc/Utilities.rst b/doc/Utilities.rst index c4c19ab..4ca1aa1 100644 --- a/doc/Utilities.rst +++ b/doc/Utilities.rst @@ -21,10 +21,12 @@ These are utilities for handling `base64 ` * :cpp:var:`base64_chars_urlsafe` -.. cpp:function:: std::string base64_decode( const std::string& o, const char* chars = base64_chars_standard ) +.. cpp:function:: std::string base64_decode( const std::string& o, const char* chars = base64_chars_standard, show::base64_flags flags = 0x00 ) Decode a base64-encoded string ``o`` using the character set ``chars``, which must point to a ``char`` array of length 64. Throws a :cpp:class:`base64_decode_error` if the input is not encoded against ``chars`` or has incorrect padding. + Incorrect padding can be ignored by passing ``show::base64_ignore_padding`` as the ``flags`` argument. + .. seealso:: * :cpp:var:`base64_chars_standard` diff --git a/src/show/base64.hpp b/src/show/base64.hpp index 0c66324..0e3062f 100644 --- a/src/show/base64.hpp +++ b/src/show/base64.hpp @@ -16,13 +16,18 @@ namespace show }; + using base64_flags = unsigned char; + static const base64_flags base64_ignore_padding = 0x01; + + std::string base64_encode( const std::string& o, const char* chars = base64_chars_standard ); std::string base64_decode( const std::string& o, - const char* chars = base64_chars_standard + const char* chars = base64_chars_standard, + base64_flags flags = 0x00 ); @@ -105,12 +110,13 @@ namespace show return encoded; } - inline std::string base64_decode( const std::string& o, const char* chars ) + inline std::string base64_decode( + const std::string& o, + const char* chars, + base64_flags flags + ) { - /*unsigned*/ char current_octet; - std::string decoded; auto unpadded_len = o.size(); - for( auto r_iter = o.rbegin(); r_iter != o.rend(); @@ -124,37 +130,33 @@ namespace show } auto b64_size = unpadded_len; - if( b64_size % 4 ) b64_size += 4 - ( b64_size % 4 ); - if( b64_size > o.size() ) - // Missing required padding - // TODO: add flag to explicitly ignore? + if( !( flags & base64_ignore_padding ) && b64_size > o.size() ) throw base64_decode_error{ "missing required padding" }; std::map< char, /*unsigned*/ char > reverse_lookup; for( /*unsigned*/ char i{ 0 }; i < 64; ++i ) reverse_lookup[ chars[ i ] ] = i; - reverse_lookup[ '=' ] = 0; - for( std::string::size_type i{ 0 }; i < b64_size; ++i ) - { - if( o[ i ] == '=' && i + 1 < b64_size && o[ i + 1 ] != '=' ) + auto get_hextet = [ &reverse_lookup ]( /*unsigned*/ char c ){ + if( c == '=' ) throw base64_decode_error{ "premature padding" }; - - std::map< char, /*unsigned*/ char >::iterator first, second; - - first = reverse_lookup.find( o[ i ] ); - if( first == reverse_lookup.end() ) + auto found_hextet = reverse_lookup.find( c ); + if( found_hextet == reverse_lookup.end() ) throw base64_decode_error{ "invalid base64 character" }; - - if( i + 1 < o.size() ) - { - second = reverse_lookup.find( o[ i + 1 ] ); - if( second == reverse_lookup.end() ) - throw base64_decode_error{ "invalid base64 character" }; - } + return found_hextet -> second; + }; + + /*unsigned*/ char current_octet; + std::string decoded; + for( std::string::size_type i{ 0 }; i < unpadded_len; ++i ) + { + auto first_hextet = get_hextet( o[ i ] ); + char second_hextet = 0x00; + if( i + 1 < unpadded_len ) + second_hextet = get_hextet( o[ i + 1 ] ); switch( i % 4 ) { @@ -162,43 +164,39 @@ namespace show // i // ****** ****** ****** ****** // ^^^^^^ ^^ - current_octet = first -> second << 2; - if( i + 1 < o.size() ) - current_octet |= ( - second -> second >> 4 - ) & 0x03 /* 00000011 */; + current_octet = ( + ( first_hextet << 2 ) + | ( ( second_hextet >> 4 ) & 0x03 ) /* 00000011 */ + ); break; case 1: // i // ****** ****** ****** ****** // ^^^^ ^^^^ - current_octet = first -> second << 4; - if( i + 1 < o.size() ) - current_octet |= ( - second -> second >> 2 - ) & 0x0F /* 00001111 */; + current_octet = ( + ( first_hextet << 4 ) + | ( ( second_hextet >> 2 ) & 0x0F ) /* 00001111 */ + ); break; case 2: // i // ****** ****** ****** ****** // ^^ ^^^^^^ - current_octet = ( first -> second << 6 ) & 0xC0 /* 11000000 */; - if( i + 1 < o.size() ) - current_octet |= second -> second & 0x3F /* 00111111 */; + current_octet = ( + ( first_hextet << 6 ) + | ( second_hextet & 0x3F ) /* 00111111 */ + ); break; case 3: // i // ****** ****** ****** ****** - // - continue; } - // Skip null bytes added by padding (but continue to next iteration - // to check for premature padding) - if( current_octet == 0x00 && o[ i + 1 ] == '=' ) - continue; - - decoded += current_octet; + if( i + 1 < unpadded_len ) + decoded += current_octet; + else + break; } return decoded; diff --git a/tests/base64_tests.cpp b/tests/base64_tests.cpp index db45137..0586f5e 100644 --- a/tests/base64_tests.cpp +++ b/tests/base64_tests.cpp @@ -281,6 +281,28 @@ SUITE( ShowBase64Tests ) ); } + TEST( DecodeMissingPadding ) + { + auto s = show::base64_decode( + "SGVsbG8gV29ybGQ", + show::base64_chars_standard, + show::base64_ignore_padding + ); + std::string hw{ "Hello World" }; + CHECK_EQUAL( hw, s ); + } + + TEST( DecodeMissingPaddingNulls ) + { + auto s = show::base64_decode( + "AAA", + show::base64_chars_standard, + show::base64_ignore_padding + ); + std::string hw( 2, '\0' ); + CHECK_EQUAL( hw, s ); + } + TEST( DecodeFailMissingPadding ) { try From e1c8faa955e6fcf503fe1694ab7ccddabac2a596 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 17:49:58 -0400 Subject: [PATCH 08/15] Removed .gitignore as out-of-source builds are preferred --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 73b07e8..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -make -local -doc/_doctrees -doc/html \ No newline at end of file From b8e41f8569df295d7f9a7d907d0dc1110d1aced1 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 17:59:01 -0400 Subject: [PATCH 09/15] Fixed UnitTest++ package detection --- tests/CMakeLists.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c778eb..5badc83 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,12 +1,7 @@ -# Try to find by package first (UnitTest++ provides one), but fall back to -# FIND_LIBRARY() if it's not installed (Homebrew for example does not) -FIND_PACKAGE( UnitTest++ QUIET ) -IF( NOT UnitTest++ ) - FIND_LIBRARY( UnitTest++ UnitTest++ ) -ENDIF() +FIND_PACKAGE( UnitTest++ ) -IF( UnitTest++ ) +IF( TARGET UnitTest++ ) # Unfortunately CMake doesn't supply a component-based package for cURL FIND_PACKAGE( CURL REQUIRED ) From 520a433e0d50bedf6c9efd0eaec4af4fb641785b Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 18:17:28 -0400 Subject: [PATCH 10/15] Sphinx doc generation now pulls SHOW version from "show.hpp" --- doc/conf.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 0a54d83..925dc8d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -53,10 +53,21 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = u'0.8' -# The full version, including alpha/beta/rc tags. -release = u'0.8.6' +import os, re +with open( os.path.join( + os.path.dirname( os.path.dirname( __file__ ) ), + "src", + "show.hpp" +) ) as show_hpp: + for line in show_hpp: + if "static const std::string string" in line: + match = re.search( r"\d+\.\d+\.\d+", line ) + if match is not None: + # The full version, including alpha/beta/rc tags. + release = match.group( 0 ) + # The short X.Y version. + version = ".".join( release.split( "." )[ : 2 ] ) + break # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 1906ee9765c56abef9c4ba7494c20ba39517da37 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 18:30:29 -0400 Subject: [PATCH 11/15] Include CTest explicitly and use its `BUILD_TESTING` option --- CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e0118d..104ffd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ PROJECT( LANGUAGES CXX ) -ENABLE_TESTING() +INCLUDE( CTest ) ADD_LIBRARY( show INTERFACE ) @@ -33,7 +33,12 @@ INSTALL( ) -ADD_SUBDIRECTORY( "tests/" ) +# CTest sets `BUILD_TESTING` to "on" by default +IF( BUILD_TESTING ) + ADD_SUBDIRECTORY( "tests/" ) +ENDIF() + +# All examples are excluded from `ALL` but can be built manually ADD_SUBDIRECTORY( "examples/" ) From 17780112427eb572ca264e6afc9d1fa638fca812 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 19:27:38 -0400 Subject: [PATCH 12/15] Use GNUInstallDirs to set up where to install stuff --- CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 104ffd3..8cbd3f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ PROJECT( ) INCLUDE( CTest ) +INCLUDE( GNUInstallDirs ) ADD_LIBRARY( show INTERFACE ) @@ -18,18 +19,18 @@ TARGET_INCLUDE_DIRECTORIES( show INTERFACE "$" - "$" + "$" ) INSTALL( TARGETS show EXPORT "show-config" - RUNTIME DESTINATION "bin/" - LIBRARY DESTINATION "lib/" - ARCHIVE DESTINATION "lib/static/" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) INSTALL( DIRECTORY "src/" - DESTINATION "include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" ) @@ -49,13 +50,13 @@ WRITE_BASIC_PACKAGE_VERSION_FILE( ) INSTALL( FILES "${CMAKE_BINARY_DIR}/show-config-version.cmake" - DESTINATION "lib/cmake/show/" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/show/" ) INSTALL( EXPORT "show-config" NAMESPACE "SHOW::" - DESTINATION "lib/cmake/show/" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/show/" ) EXPORT( EXPORT "show-config" FILE "show-config.cmake" ) From 5eacb2396327c6ecc1c583ebb6a1c0b9056da15c Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 19:28:11 -0400 Subject: [PATCH 13/15] Added documentation build configuration --- CMakeLists.txt | 9 +++++++++ CMakeModules/FindSphinx.cmake | 34 ++++++++++++++++++++++++++++++++++ doc/CMakeLists.txt | 26 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 CMakeModules/FindSphinx.cmake create mode 100644 doc/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cbd3f5..5fbd4fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,16 @@ PROJECT( LANGUAGES CXX ) +LIST( APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules/" ) + INCLUDE( CTest ) INCLUDE( GNUInstallDirs ) +STRING( REPLACE "${PROJECT_NAME}" "show" + CMAKE_INSTALL_DOCDIR + "${CMAKE_INSTALL_DOCDIR}" +) + ADD_LIBRARY( show INTERFACE ) TARGET_INCLUDE_DIRECTORIES( @@ -42,6 +49,8 @@ ENDIF() # All examples are excluded from `ALL` but can be built manually ADD_SUBDIRECTORY( "examples/" ) +ADD_SUBDIRECTORY( "doc/" ) + INCLUDE( CMakePackageConfigHelpers ) WRITE_BASIC_PACKAGE_VERSION_FILE( diff --git a/CMakeModules/FindSphinx.cmake b/CMakeModules/FindSphinx.cmake new file mode 100644 index 0000000..65575be --- /dev/null +++ b/CMakeModules/FindSphinx.cmake @@ -0,0 +1,34 @@ +INCLUDE( FindPackageHandleStandardArgs ) + +FOREACH( PROGRAM + "apidoc" + "autogen" + "build" + "quickstart" +) + FIND_PROGRAM( + SPHINX_${PROGRAM}_LOCATION + NAMES "sphinx-${PROGRAM}" + HINTS "${SPHINX_DIR}" "$ENV{SPHINX_DIR}" + PATH_SUFFIXES "bin/" + DOC "Sphinx executable sphinx-${PROGRAM}" + ) + MARK_AS_ADVANCED( SPHINX_${PROGRAM}_LOCATION ) + IF( SPHINX_${PROGRAM}_LOCATION ) + ADD_EXECUTABLE( Sphinx::${PROGRAM} IMPORTED ) + SET_TARGET_PROPERTIES( + Sphinx::${PROGRAM} + PROPERTIES + IMPORTED_LOCATION "${SPHINX_${PROGRAM}_LOCATION}" + ) + SET(Sphinx_${PROGRAM}_FOUND TRUE) + ENDIF () +ENDFOREACH() + +# FPHSA will set this to false if it wasn't actually found +SET( Sphinx_FOUND TRUE ) +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Sphinx + HANDLE_COMPONENTS + REQUIRED_VARS Sphinx_FOUND +) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..632d5ed --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,26 @@ +FIND_PACKAGE( Sphinx COMPONENTS build ) + +IF( TARGET Sphinx::build ) + SET(SHOW_DOC_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/show/") + ADD_CUSTOM_TARGET( doc ALL ) + + FOREACH( BUILDER "html" ) + FILE(MAKE_DIRECTORY "${SHOW_DOC_OUTPUT_DIR}/${BUILDER}/") + ADD_CUSTOM_TARGET( + doc_${BUILDER} + Sphinx::build + -b "${BUILDER}" + -d "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}" + "${SHOW_DOC_OUTPUT_DIR}/${BUILDER}/" + ) + ADD_DEPENDENCIES( doc doc_${BUILDER} ) + ENDFOREACH() + + INSTALL( + DIRECTORY "${SHOW_DOC_OUTPUT_DIR}" + DESTINATION "${CMAKE_INSTALL_DOCDIR}" + ) +ELSE () + MESSAGE( WARNING "Sphinx not found, not building documentation" ) +ENDIF () From b3845629e40ec09dbf6758fc50a60f30da946b68 Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 19:41:29 -0400 Subject: [PATCH 14/15] Improved unit tests build configuration, splitting them up into individual executables so they work better with CTest --- tests/CMakeLists.txt | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5badc83..6545a64 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,29 +5,47 @@ IF( TARGET UnitTest++ ) # Unfortunately CMake doesn't supply a component-based package for cURL FIND_PACKAGE( CURL REQUIRED ) - ADD_EXECUTABLE( - show_unit_tests - "async_utils.cpp" - "base64_tests.cpp" - "connection_tests.cpp" - "multipart_tests.cpp" - "request_tests.cpp" - "response_tests.cpp" - "server_tests.cpp" - "tests.cpp" - "type_tests.cpp" - "url_encode_tests.cpp" - ) - TARGET_INCLUDE_DIRECTORIES( show_unit_tests PRIVATE ${CURL_INCLUDE_DIRS} ) - TARGET_LINK_LIBRARIES( - show_unit_tests + ADD_CUSTOM_TARGET( tests ALL ) + + ADD_LIBRARY( show_unit_test_utils STATIC ) + TARGET_SOURCES( show_unit_test_utils PRIVATE + "async_utils.cpp" + "tests.cpp" + ) + TARGET_INCLUDE_DIRECTORIES( show_unit_test_utils + PUBLIC ${CURL_INCLUDE_DIRS} + ) + TARGET_LINK_LIBRARIES( show_unit_test_utils + PUBLIC show UnitTest++ ${CURL_LIBRARIES} ) - ADD_TEST( NAME unit_tests COMMAND show_unit_tests ) + FOREACH( SUITE + "base64" + "connection" + "multipart" + "request" + "response" + "server" + "type" + "url_encode" + ) + ADD_EXECUTABLE( show_${SUITE}_unit_tests ) + TARGET_SOURCES( show_${SUITE}_unit_tests + PRIVATE "${SUITE}_tests.cpp" + ) + TARGET_LINK_LIBRARIES( show_${SUITE}_unit_tests + PRIVATE show_unit_test_utils + ) + ADD_DEPENDENCIES( tests show_${SUITE}_unit_tests ) + ADD_TEST( + NAME "${SUITE}_unit_tests" + COMMAND show_${SUITE}_unit_tests + ) + ENDFOREACH() ELSE() MESSAGE( WARNING "UnitTest++ not found, not building unit tests" ) ENDIF() From 1399df0e887533c7962a69e9ecd8bb163f6ad81d Mon Sep 17 00:00:00 2001 From: Joseph Durel Date: Sat, 18 May 2019 20:14:44 -0400 Subject: [PATCH 15/15] Made the `show::connection::xsgetn()` implementation possibly more efficient --- src/show.hpp | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/show.hpp b/src/show.hpp index 2b7f7f8..21b02e8 100644 --- a/src/show.hpp +++ b/src/show.hpp @@ -3,6 +3,7 @@ #define SHOW_HPP +#include // std::copy #include #include #include @@ -654,23 +655,32 @@ namespace show // `show::connection` implementation //////////////////////////// std::streamsize count ) { - // FIXME: copy in available chunks rather than ~i calls to `sbumpc()` - - std::streamsize i{ 0 }; - - while( i < count ) + if( count == 0 ) + return 0; + + auto available = showmanyc(); + + if( available < 1 ) { - int_type gotc = sbumpc(); - - if( gotc == traits_type::not_eof( gotc ) ) - s[ i ] = traits_type::to_char_type( gotc ); + auto c = underflow(); + if( c == traits_type::not_eof( c ) ) + // Try again + return xsgetn( s, count ); else - break; - - ++i; + return 0; } - - return i; + else if( count <= available ) + { + std::copy( gptr(), egptr(), s ); + setg( + eback(), + gptr() + count, + egptr() + ); + return count; + } + else + return xsgetn( s, available ); } inline connection::int_type connection::pbackfail( int_type c ) @@ -1267,8 +1277,6 @@ namespace show // `show::request` implementation /////////////////////////////// std::streamsize count ) { - // FIXME: copy in available chunks rather than ~i calls to `sbumpc()` - std::streamsize read; if( _unknown_content_length )