From 8303266430e567b25b7ea914279e0f5f408d8190 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Sat, 28 May 2016 12:21:40 +0700 Subject: [PATCH 01/11] Travis CI updates: * Run autobahn/valgrind tests when target branch in {master, develop} * Add coveralls * Show full stacktrace for usan (RIPD-1150) * Manual launch of coverage (RIPD-1152) * Use lldb on Darwin (RIPD-1152) * Set defaults if not CI (RIPD-1152) * Add autobahn result parser (RIPD-1147) --- .gitignore | 4 + .travis.yml | 55 +++---------- README.md | 3 +- scripts/blacklist.supp | 39 ++++++++++ scripts/build-and-test.sh | 134 ++++++++++++++++++++------------ scripts/install-boost.sh | 4 +- scripts/install-dependencies.sh | 12 ++- scripts/install-valgrind.sh | 4 +- scripts/parseautobahn.py | 43 ++++++++++ scripts/run-with-debugger.sh | 22 ++++++ 10 files changed, 222 insertions(+), 98 deletions(-) create mode 100644 scripts/blacklist.supp mode change 100644 => 100755 scripts/install-valgrind.sh create mode 100644 scripts/parseautobahn.py create mode 100755 scripts/run-with-debugger.sh diff --git a/.gitignore b/.gitignore index cc8f6cb39..e51312d94 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ contents.xcworkspacedata .svn profile bin/ +node_modules/ +cov-int/ +nohup.out +venv/ diff --git a/.travis.yml b/.travis.yml index 04f04d4df..380cb741c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,13 +14,6 @@ env: packages: &gcc5_pkgs - gcc-5 - g++-5 - # - gcc-5-multilib - # - g++-5-multilib - # - gcc-multilib - # - g++-multilib - # - libgd2-xpm - # - ia32-libs - # - ia32-libs-multiarch - python-software-properties - libssl-dev - libffi-dev @@ -52,20 +45,7 @@ packages: &clang38_pkgs matrix: include: - # GCC/Debug - # - compiler: gcc - # env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 - # addons: &ao_gcc5 - # apt: - # sources: ['ubuntu-toolchain-r-test'] - # packages: *gcc5_pkgs - - # # GCC/Release - # - compiler: gcc - # env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=64 - # addons: *ao_gcc5 - - # Coverage + # GCC/Coverage - compiler: gcc env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64 addons: &ao_gcc5 @@ -73,36 +53,25 @@ matrix: sources: ['ubuntu-toolchain-r-test'] packages: *gcc5_pkgs - # # Clang/Debug - # - compiler: clang - # env: GCC_VER=5 VARIANT=debug CLANG_VER=3.8 ADDRESS_MODEL=64 - # addons: &ao_clang38 - # apt: - # sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] - # packages: *clang38_pkgs + # # GCC/Debug + # - compiler: gcc + # env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 + # addons: *ao_gcc5 + # branches: # NOTE: this does NOT work, though it SHOULD + # - master + # - develop - # # Clang/Release - # - compiler: clang - # env: GCC_VER=5 VARIANT=release CLANG_VER=3.8 ADDRESS_MODEL=64 - # addons: *ao_clang38 - - # Clang/AddressSanitizer + # Clang/UndefinedBehaviourSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=asan CLANG_VER=3.8 ADDRESS_MODEL=64 + env: GCC_VER=5 VARIANT=usan CLANG_VER=3.8 ADDRESS_MODEL=64 UBSAN_OPTIONS='print_stacktrace=1' addons: &ao_clang38 apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] packages: *clang38_pkgs - # Clang/MemorySanitizer - # VFALCO Generates false positives unless libc++ is compiled with msan turned on - #- compiler: clang - # env: GCC_VER=5 VARIANT=msan CLANG_VER=3.8 ADDRESS_MODEL=64 MSAN_OPTIONS=poison_in_dtor=1,sanitize-memory-track-origins=2 - # addons: *ao_clang38 - - # Clang/UndefinedBehaviourSanitizer + # Clang/AddressSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=usan CLANG_VER=3.8 ADDRESS_MODEL=64 + env: GCC_VER=5 VARIANT=asan CLANG_VER=3.8 ADDRESS_MODEL=64 addons: *ao_clang38 cache: diff --git a/README.md b/README.md index 87612d41a..a9c8a923e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Beast [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov] -(https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![Documentation] +(https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![coveralls] +(https://coveralls.io/repos/github/vinniefalco/Beast/badge.svg?branch=master)](https://coveralls.io/github/vinniefalco/Beast?branch=master) [![Documentation] (https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License] (https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) diff --git a/scripts/blacklist.supp b/scripts/blacklist.supp new file mode 100644 index 000000000..08968f04d --- /dev/null +++ b/scripts/blacklist.supp @@ -0,0 +1,39 @@ +# Remember that this blacklist file is GLOBAL to all sanitizers +# Be therefore extremely careful when considering to add a sanitizer +# filter here instead of using a runtime suppression +# +# Remember also that filters here quite literally completely +# remove instrumentation altogether, so filtering here means +# that sanitizers such as tsan will false positive on problems +# introduced by code filtered here. +# +# The main use for this file is ubsan, as it's the only sanitizer +# without a runtime suppression facility. +# +# Be ESPECIALLY careful when filtering out entire source files! +# Try if at all possible to filter only functions using fun:regex +# Remember you must use mangled symbol names with fun:regex + + +#### Compile time filters for ubsan #### + +## The well known ubsan failure in libstdc++ extant for years :) +# Line 96:24: runtime error: load of value 4294967221, which is not a valid value for type 'std::_Ios_Fmtflags' +fun:*_Ios_Fmtflags* + +# boost/any.hpp:259:16: runtime error: downcast of address 0x000004392e70 which does not point to an object of type 'any::holder' +fun:*any_cast* + +# boost/lexical_cast.hpp:1625:43: runtime error: downcast of address 0x7fbb4fffbce8 which does not point to an object of type 'buffer_t' (aka 'parser_buf >, char>') +fun:*shl_input_streamable* + + + + +#### Compile time filters for asan #### + + +#### Compile time filters for msan #### + + +#### Compile time filters for tsan #### diff --git a/scripts/build-and-test.sh b/scripts/build-and-test.sh index d71b6e23c..0aa410406 100755 --- a/scripts/build-and-test.sh +++ b/scripts/build-and-test.sh @@ -1,8 +1,11 @@ -#!/bin/bash -u -# We use set -e and bash with -u to bail on first non zero exit code of any -# processes launched or upon any unbound variable +#!/usr/bin/env bash +# We use set -e to bail on first non zero exit code of any processes launched +# and -x to exit upon any unbound variable. -x will output command lines used +# (with variable expansion) +set -eux + +# brew install bash (4) to get this working on OSX! shopt -s globstar -set -ex ################################## ENVIRONMENT ################################# @@ -17,36 +20,90 @@ else export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH fi +MAIN_BRANCH="0" +# For builds not triggered by a pull request TRAVIS_BRANCH is the name of the +# branch currently being built; whereas for builds triggered by a pull request +# it is the name of the branch targeted by the pull request (in many cases this +# will be master). +if [[ $TRAVIS_BRANCH == "master" || $TRAVIS_BRANCH == "develop" ]]; then + MAIN_BRANCH="1" +fi + +num_jobs="1" +if [[ $(uname) == "Darwin" ]]; then + num_jobs=$(sysctl -n hw.physicalcpu) +elif [[ $(uname -s) == "Linux" ]]; then + # CircleCI returns 32 phys procs, but 2 virt proc + num_proc_units=$(nproc) + # Physical cores + num_jobs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) + if (("$num_proc_units" < "$num_jobs")); then + num_jobs=$num_proc_units + fi +fi + echo "using toolset: $CC" echo "using variant: $VARIANT" echo "using address-model: $ADDRESS_MODEL" echo "using PATH: $PATH" +echo "using MAIN_BRANCH: $MAIN_BRANCH" +echo "using BOOST_ROOT: $BOOST_ROOT" #################################### HELPERS ################################### -function run_tests_with_gdb { - for x in bin/**/*-tests; do scripts/run-with-gdb.sh "$x"; done +function run_tests_with_debugger { + for x in bin/**/$VARIANT/**/*-tests; do + scripts/run-with-debugger.sh "$x" + done } function run_tests { - for x in bin/**/*-tests; do "$x"; done + for x in bin/**/$VARIANT/**/*-tests; do + $x + done } -num_procs=1 -if [[ $(uname) == "Darwin" ]]; then - num_procs=$(sysctl -n hw.ncpu) -elif [[ $(uname -s) == "Linux" ]]; then - num_procs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) # physical cores - virt_num_procs=$(nproc) # CircleCI returns 32 phys procs, but 1 virt proc - if (("$virt_num_procs" < "$num_procs")); then - num_procs=$virt_num_procs +function run_tests_with_valgrind { + for x in bin/**/$VARIANT/**/*-tests; do + if [[ $(basename $x) == "bench-tests" ]]; then + $x + else + # TODO --max-stackframe=8388608 + # see: https://travis-ci.org/vinniefalco/Beast/jobs/132486245 + valgrind --error-exitcode=1 "$x" fi -fi + done +} function build_beast { $BOOST_ROOT/bjam toolset=$CC \ variant=$VARIANT \ - address-model=$ADDRESS_MODEL -j${num_procs} + address-model=$ADDRESS_MODEL \ + -j${num_jobs} +} + +function run_autobahn_test_suite { + # Run autobahn tests + wsecho=$(find bin -name "websocket-echo" | grep /$VARIANT/) + nohup $wsecho& + + # We need to wait a while so wstest can connect! + sleep 5 + cd scripts && wstest -m fuzzingclient + cd .. + # Show the output + cat nohup.out + rm nohup.out + # Show what jobs are running + jobs + # Wait a while for things to wind down before issuing a kill + sleep 5 + # Kill it gracefully + kill -INT %1 + # Wait for all the jobs to finish + wait + # Parse the test results, with python>=2.5<3 script + python scripts/parseautobahn.py scripts/autoresults/index.json } ##################################### BUILD #################################### @@ -62,24 +119,12 @@ if [[ $VARIANT == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info > /dev/null # Perform test - run_tests - - # Run autobahn tests - export SERVER=$(find . -name "websocket-echo") - nohup $SERVER& - - # We need to wait a while so wstest can connect! - sleep 5 - cd scripts && wstest -m fuzzingclient - cd .. - # Show the output - cat nohup.out - rm nohup.out - jobs - sleep 5 - # Kill it gracefully - kill -INT %1 - wait + if [[ $MAIN_BRANCH == "1" ]]; then + run_tests_with_valgrind + run_autobahn_test_suite + else + run_tests + fi # Create test coverage data file lcov --no-external -c -d . -o testrun.info > /dev/null @@ -88,20 +133,13 @@ if [[ $VARIANT == "coverage" ]]; then lcov -a baseline.info -a testrun.info -o lcov-all.info > /dev/null # Extract only include/beast, and don\'t report on examples/test - lcov -e "lcov-all.info" "*/include/beast/*" -o lcov.info > /dev/null + lcov -e "lcov-all.info" "$PWD/include/beast/*" -o lcov.info > /dev/null ~/.local/bin/codecov -X gcov -else - # TODO: make a function - run_tests_with_gdb + cat lcov.info | node_modules/.bin/coveralls - if [[ $VARIANT == "debug" ]]; then - for x in bin/**/*-tests; do - # if [[ $x != "bench-tests" ]]; then - valgrind --error-exitcode=1 "$x" - ## declare -i RESULT=$RESULT + $? - # fi - done - echo - fi + # Clean up these stragglers so BOOST_ROOT cache is clean + find $BOOST_ROOT/bin.v2 -name "*.gcda" | xargs rm -f +else + run_tests_with_debugger fi diff --git a/scripts/install-boost.sh b/scripts/install-boost.sh index 9dd3a4f6a..8365c2d54 100755 --- a/scripts/install-boost.sh +++ b/scripts/install-boost.sh @@ -1,4 +1,4 @@ -#!/bin/bash -u +#!/usr/bin/env bash # Assumptions: # 1) BOOST_ROOT and BOOST_URL are already defined, # and contain valid values. @@ -6,7 +6,7 @@ # folder name internal to boost's .tar.gz # When testing you can force a boost build by clearing travis caches: # https://travis-ci.org/ripple/rippled/caches -set -e +set -eu if [ ! -d "$BOOST_ROOT/lib" ] then wget $BOOST_URL -O /tmp/boost.tar.gz diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 34eb86048..4130e2b47 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -1,6 +1,9 @@ -#!/bin/bash -u +#!/usr/bin/env bash # Exit if anything fails. -set -e +set -eux + +HERE=$PWD + # Override gcc version to $GCC_VER. # Put an appropriate symlink at the front of the path. mkdir -v $HOME/bin @@ -44,3 +47,8 @@ tar xfvz lcov-1.12.tar.gz -C $HOME # Set install path mkdir -p $LCOV_ROOT cd $HOME/lcov-1.12 && make install PREFIX=$LCOV_ROOT + +# Install coveralls reporter +cd $HERE +mkdir -p node_modules +npm install coveralls diff --git a/scripts/install-valgrind.sh b/scripts/install-valgrind.sh old mode 100644 new mode 100755 index f1779a2ba..943eb8673 --- a/scripts/install-valgrind.sh +++ b/scripts/install-valgrind.sh @@ -1,7 +1,7 @@ -#!/bin/bash -u +#!/usr/bin/env bash # Assumptions: # 1) VALGRIND_ROOT is already defined, and contains a valid values -set -e +set -eu if [ ! -d "$VALGRIND_ROOT/bin" ] then # These are specified in the addons/apt section of .travis.yml diff --git a/scripts/parseautobahn.py b/scripts/parseautobahn.py new file mode 100644 index 000000000..6b91e8c63 --- /dev/null +++ b/scripts/parseautobahn.py @@ -0,0 +1,43 @@ +import os +import json +import sys + +VARIANT = os.environ.get('VARIANT', 'release') +EXPECTED_BEHAVIOR = ('OK', 'UNIMPLEMENTED', 'INFORMATIONAL') +EXPECTED_BEHAVIOR_CLOSE = ('OK', 'INFORMATIONAL') +WARNINGS = ("peer did not respond (in time) in closing handshake", ) + +args = sys.argv[1:] +fn = os.path.abspath(args[0]) +indexPath = os.path.dirname(fn) +relativeToIndex = lambda f: os.path.join(indexPath, f) +print "index", fn + + +failures = [] +warnings = [] + +with open(fn, 'r') as fh: + index = json.load(fh) + for servername, serverResults in index.items(): + for test in serverResults: + result = serverResults[test] + if ((result['behavior'] not in EXPECTED_BEHAVIOR) or + result['behaviorClose'] not in EXPECTED_BEHAVIOR_CLOSE): + with open(relativeToIndex(result['reportfile'])) as rh: + report = json.load(rh) + if (report.get('wasNotCleanReason', '') in WARNINGS and + VARIANT != 'release'): + warnings.append(report) + else: + failures.append(report) + + +if warnings: + print >> sys.stderr, json.dumps(warnings, indent=2) + print >> sys.stderr, 'there was %s warnings' % len(warnings) + +if failures: + print >> sys.stderr, json.dumps(failures, indent=2) + print >> sys.stderr, 'there was %s failures' % len(failures) + sys.exit(1) diff --git a/scripts/run-with-debugger.sh b/scripts/run-with-debugger.sh new file mode 100755 index 000000000..6a8b55d85 --- /dev/null +++ b/scripts/run-with-debugger.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -eu + +if [[ $(uname) == "Darwin" ]]; then + # -o runs after loading the binary + # -k runs after any crash + # We use a ghetto appromixation of --return-child-result, exiting with + # 1 on a crash + lldb --batch \ + -o 'run' \ + -k 'thread backtrace all' \ + -k 'script import os; os._exit(1)' \ + $@ +else + gdb --silent \ + --batch \ + --return-child-result \ + -ex="set print thread-events off" \ + -ex=run \ + -ex="thread apply all bt full" \ + --args $@ +fi From 803d145a5e8398b727e5edfd2eeddf1dbd94ee97 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 28 May 2016 07:57:49 -0400 Subject: [PATCH 02/11] Add CHANGELOG --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 000000000..ef388f196 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,3 @@ +1.0.0-b6 + +-------------------------------------------------------------------------------- From cd41a0decd7ad08f8db90cfaaf0c04bd3822f590 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 27 May 2016 11:10:45 -0400 Subject: [PATCH 03/11] Use SFINAE on return values --- CHANGELOG | 3 +++ TODO.txt | 4 ---- include/beast/core/to_string.hpp | 15 ++++++++------- include/beast/http/basic_headers.hpp | 18 ++++++++---------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ef388f196..b223e8a3b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ 1.0.0-b6 +* Use SFINAE on return values +* Use beast::error_code instead of nested types + -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index c84855163..00cb10540 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,9 +1,5 @@ * Add writer::prepare(msg&) interface to set Content-Type -General: -* Use SFINAE on return values (search for "class =") -* Remove http,websocket error_code types and use the one in - Boost.Http * Use enum instead of bool in isRequest * move version to a subclass of message diff --git a/include/beast/core/to_string.hpp b/include/beast/core/to_string.hpp index 41ba8d0fa..c415fd5cb 100644 --- a/include/beast/core/to_string.hpp +++ b/include/beast/core/to_string.hpp @@ -25,15 +25,16 @@ namespace beast { @return A string representing the contents of the input area. @note This function participates in overload resolution only if - the streambuf parameter meets the requirements of @b `Streambuf`. + the buffers parameter meets the requirements of @b `ConstBufferSequence`. */ -template::value> -#endif -> +template +#if GENERATING_DOCS std::string +#else +typename std::enable_if< + is_ConstBufferSequence::value, + std::string>::type +#endif to_string(ConstBufferSequence const& buffers) { using boost::asio::buffer_cast; diff --git a/include/beast/http/basic_headers.hpp b/include/beast/http/basic_headers.hpp index 6e034e277..38b3d3ee2 100644 --- a/include/beast/http/basic_headers.hpp +++ b/include/beast/http/basic_headers.hpp @@ -401,10 +401,9 @@ public: If a field value already exists the new value will be extended as per RFC2616 Section 4.2. */ - template::value>::type> - void + template + typename std::enable_if< + ! std::is_constructible::value>::type insert(boost::string_ref name, T const& value) { insert(name, @@ -414,7 +413,7 @@ public: /** Replace a field value. The current field value, if any, is removed. Then the - specified value is inserted as if by insert(field, value). + specified value is inserted as if by `insert(field, value)`. */ void replace(boost::string_ref const& name, @@ -423,12 +422,11 @@ public: /** Replace a field value. The current field value, if any, is removed. Then the - specified value is inserted as if by insert(field, value). + specified value is inserted as if by `insert(field, value)`. */ - template::value>::type> - void + template + typename std::enable_if< + ! std::is_constructible::value>::type replace(boost::string_ref const& name, T const& value) { replace(name, From bc2642f42344a3b9ecb74f2580c18bfae719bdca Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 27 May 2016 11:10:55 -0400 Subject: [PATCH 04/11] Use beast::error_code instead of nested types --- include/beast/core/streambuf_readstream.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/beast/core/streambuf_readstream.hpp b/include/beast/core/streambuf_readstream.hpp index 383f95119..e014f6d21 100644 --- a/include/beast/core/streambuf_readstream.hpp +++ b/include/beast/core/streambuf_readstream.hpp @@ -93,8 +93,6 @@ class streambuf_readstream static_assert(is_Streambuf::value, "Streambuf requirements not met"); - using error_code = boost::system::error_code; - template class read_some_op; From 5a0a47cbae2e38fc69368e825d3b71257ed420a3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 27 May 2016 11:17:53 -0400 Subject: [PATCH 05/11] Tidy up use of GENERATING_DOCS --- CHANGELOG | 2 + TODO.txt | 14 +----- examples/http_stream.hpp | 12 ++--- include/beast/core/static_string.hpp | 4 -- include/beast/http/basic_parser_v1.hpp | 6 +-- include/beast/http/message.hpp | 4 -- include/beast/http/message_v1.hpp | 4 -- include/beast/websocket/stream.hpp | 66 +++++++++++++------------- 8 files changed, 46 insertions(+), 66 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b223e8a3b..18cf19dfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ 1.0.0-b6 +* Add HTTP field value parsers * Use SFINAE on return values * Use beast::error_code instead of nested types +* Tidy up use of GENERATING_DOCS -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index 00cb10540..31043032b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -7,11 +7,7 @@ Boost.Http Docs: * Include Example program listings in the docs * Fix index in docs -* Figure out why namespace rfc2616 is included in docs - (currently disabled via GENERATING_DOCS macro) * melpon sandbox? -* Check DOXYGEN, GENERATIC_DOCS directives in source - - See if we can include them now that xsl is fixed * Implement cleanup-param to remove spaces around template arguments e.g. in basic_streambuf move constructor members * Don't put using namespace at file scope in examples, @@ -23,15 +19,12 @@ Core: * Complete allocator testing in basic_streambuf WebSocket: +* more invokable unit test coverage +* More control over the HTTP request and response during handshakes * optimized versions of key/masking, choose prepared_key size -* invokable unit test -* Don't try to read requests into empty_body * Give callers control over the http request/response used during handshake * Investigate poor autobahn results in Debug builds * Fall through composed operation switch cases -* Replace stream::error_ with stream::state_, example states: ok, error, abort_io - Need a cancel state so waking up a ping stored in invokable knows to call the - final handler with operation_aborted * Use close_code::no_code instead of close_code::none * Make request_type, response_type public APIs, use in stream member function signatures @@ -56,8 +49,5 @@ HTTP: * Complete allocator testing in basic_streambuf, basic_headers * Add tests for writer using the resume function / coros * Custom HTTP error codes for various situations -* Make empty_body write-only, remove reader nested type -* Add concepts WritableBody ReadableBody with type checks, - check them in read and write functions * Branch prediction hints in parser * Check basic_parser_v1 against rfc7230 for leading message whitespace diff --git a/examples/http_stream.hpp b/examples/http_stream.hpp index 4bf275d29..eb07f6378 100644 --- a/examples/http_stream.hpp +++ b/examples/http_stream.hpp @@ -321,12 +321,12 @@ public: */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< ReadHandler, void(error_code)>::result_type - #endif +#endif async_read(message_v1& msg, ReadHandler&& handler); @@ -416,12 +416,12 @@ public: */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< WriteHandler, void(error_code)>::result_type - #endif +#endif async_write(message_v1 const& msg, WriteHandler&& handler); diff --git a/include/beast/core/static_string.hpp b/include/beast/core/static_string.hpp index 911abcce3..a3ec8919d 100644 --- a/include/beast/core/static_string.hpp +++ b/include/beast/core/static_string.hpp @@ -506,8 +506,6 @@ compare( } // detail -#if ! GENERATING_DOCS - template bool operator==( @@ -672,8 +670,6 @@ operator>=( return detail::compare(lhs, s) >= 0; } -#endif - } // beast #endif diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp index cdfd5026f..c0acbf398 100644 --- a/include/beast/http/basic_parser_v1.hpp +++ b/include/beast/http/basic_parser_v1.hpp @@ -373,14 +373,14 @@ public: @return The number of bytes consumed in the input sequence. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS std::size_t - #else +#else typename std::enable_if< ! std::is_convertible::value, std::size_t>::type - #endif +#endif write(ConstBufferSequence const& buffers, error_code& ec); /** Write a single buffer of data to the parser. diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 1be051c0b..04c77037c 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -145,8 +145,6 @@ private: } }; -#if ! GENERATING_DOCS - /// A typical HTTP request template>> @@ -157,8 +155,6 @@ template>> using response = message; -#endif - } // http } // beast diff --git a/include/beast/http/message_v1.hpp b/include/beast/http/message_v1.hpp index cb25ac2fe..fb700b819 100644 --- a/include/beast/http/message_v1.hpp +++ b/include/beast/http/message_v1.hpp @@ -50,8 +50,6 @@ struct message_v1 : message } }; -#if ! GENERATING_DOCS - /// A typical HTTP/1 request template>> @@ -62,8 +60,6 @@ template>> using response_v1 = message_v1; -#endif - /// Returns `true` if a HTTP/1 message indicates a keep alive template bool diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 7ee2b1602..83699916a 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -95,12 +95,12 @@ public: /// The type of the lowest layer. using lowest_layer_type = -#if GENERATING_DOCS + #if GENERATING_DOCS implementation_defined; -#else + #else typename beast::detail::get_lowest_layer< next_layer_type>::type; -#endif + #endif /** Move-construct a stream. @@ -404,12 +404,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< AcceptHandler, void(error_code)>::result_type - #endif +#endif async_accept(AcceptHandler&& handler); /** Read and respond to a WebSocket HTTP Upgrade request. @@ -530,12 +530,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< AcceptHandler, void(error_code)>::result_type - #endif +#endif async_accept(ConstBufferSequence const& buffers, AcceptHandler&& handler); @@ -647,12 +647,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< AcceptHandler, void(error_code)>::result_type - #endif +#endif async_accept(http::request_v1 const& request, AcceptHandler&& handler); @@ -784,12 +784,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< HandshakeHandler, void(error_code)>::result_type - #endif +#endif async_handshake(boost::string_ref const& host, boost::string_ref const& resource, HandshakeHandler&& h); @@ -895,12 +895,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< CloseHandler, void(error_code)>::result_type - #endif +#endif async_close(close_reason const& cr, CloseHandler&& handler); /** Send a WebSocket ping frame. @@ -973,12 +973,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< PingHandler, void(error_code)>::result_type - #endif +#endif async_ping(ping_data const& payload, PingHandler&& handler); /** Read a message from the stream. @@ -1103,12 +1103,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< ReadHandler, void(error_code)>::result_type - #endif +#endif async_read(opcode& op, Streambuf& streambuf, ReadHandler&& handler); @@ -1243,12 +1243,12 @@ public: manner equivalent to using boost::asio::io_service::post(). */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< ReadHandler, void(error_code)>::result_type - #endif +#endif async_read_frame(frame_info& fi, Streambuf& streambuf, ReadHandler&& handler); @@ -1369,12 +1369,12 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< WriteHandler, void(error_code)>::result_type - #endif +#endif async_write(ConstBufferSequence const& buffers, WriteHandler&& handler); @@ -1478,12 +1478,12 @@ public: ); @endcode */ template - #if GENERATING_DOCS +#if GENERATING_DOCS void_or_deduced - #else +#else typename async_completion< WriteHandler, void(error_code)>::result_type - #endif +#endif async_write_frame(bool fin, ConstBufferSequence const& buffers, WriteHandler&& handler); From 7e8f5401b2ca276c2b64a6d37ccb15ad8db2ea01 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 24 May 2016 06:17:04 -0400 Subject: [PATCH 06/11] Add HTTP field value parsers: ext_list: Iterable container of comma separated extensions, where each extension is a token followed an optional list of semicolon delimited parameters, with each parameter consisting of a name / value pair. The value can be a token or quoted-string. param_list: Iterable container of semicolon delimited parameters, where each parameter is a name / value pair. The value can be a token or quoted-string. token_list Iterable container of comma delimited tokens. * Remove obsolete rfc2616 functions * Refactor and consolidate case-insensitive string helpers --- CHANGELOG | 10 +- TODO.txt | 7 - include/beast/core/detail/ci_char_traits.hpp | 114 ++-- include/beast/http.hpp | 2 +- include/beast/http/basic_headers.hpp | 9 +- include/beast/http/detail/rfc7230.hpp | 261 +++++++++ include/beast/http/impl/basic_headers.ipp | 10 +- include/beast/http/impl/message_v1.ipp | 18 +- include/beast/http/impl/rfc7230.ipp | 548 +++++++++++++++++++ include/beast/http/impl/write.ipp | 8 +- include/beast/http/parser_v1.hpp | 2 - include/beast/http/rfc2616.hpp | 464 ---------------- include/beast/http/rfc7230.hpp | 230 +++++++- include/beast/websocket/impl/stream.ipp | 8 +- include/beast/websocket/option.hpp | 2 +- test/Jamfile | 1 - test/http/CMakeLists.txt | 1 - test/http/basic_parser_v1.cpp | 12 +- test/http/nodejs_parser.hpp | 4 +- test/http/rfc2616.cpp | 115 ---- test/http/rfc7230.cpp | 237 ++++++++ 21 files changed, 1385 insertions(+), 678 deletions(-) create mode 100644 include/beast/http/detail/rfc7230.hpp create mode 100644 include/beast/http/impl/rfc7230.ipp delete mode 100644 include/beast/http/rfc2616.hpp delete mode 100644 test/http/rfc2616.cpp diff --git a/CHANGELOG b/CHANGELOG index 18cf19dfa..a467786be 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,16 @@ 1.0.0-b6 -* Add HTTP field value parsers * Use SFINAE on return values * Use beast::error_code instead of nested types * Tidy up use of GENERATING_DOCS +* Remove obsolete RFC2616 functions +* Add HTTP field value parser containers: + - ext_list + - param_list + - token_list + +API Changes: + +* ci_equal is moved to beast::http namespace, in rfc7230.hpp -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index 31043032b..2efbbb77f 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,7 +2,6 @@ Boost.Http * Use enum instead of bool in isRequest -* move version to a subclass of message Docs: * Include Example program listings in the docs @@ -33,21 +32,15 @@ HTTP: * Define Parser concept in HTTP - Need parse version of read() so caller can set parser options like maximum size of headers, maximum body size, etc -* trim public interface of rfc2616.h to essentials only * add bool should_close(message_v1 const&) to replace the use of eof return value from write and async_write -* http type_check, e.g. is_WritableBody * More fine grained parser errors * HTTP parser size limit with test (configurable?) * HTTP parser trailers with test * Decode chunk encoding parameters * URL parser, strong URL character checking in HTTP parser -* Update for rfc7230 -* Consider rename to MessageBody concept * Fix prepare() calling content_length() without init() -* Use construct,destroy allocator routines in basic_headers * Complete allocator testing in basic_streambuf, basic_headers -* Add tests for writer using the resume function / coros * Custom HTTP error codes for various situations * Branch prediction hints in parser * Check basic_parser_v1 against rfc7230 for leading message whitespace diff --git a/include/beast/core/detail/ci_char_traits.hpp b/include/beast/core/detail/ci_char_traits.hpp index 8484f273f..0a388b7aa 100644 --- a/include/beast/core/detail/ci_char_traits.hpp +++ b/include/beast/core/detail/ci_char_traits.hpp @@ -8,76 +8,96 @@ #ifndef BEAST_DETAIL_CI_CHAR_TRAITS_HPP #define BEAST_DETAIL_CI_CHAR_TRAITS_HPP +#include #include -#include -#include -#include -#include -#include -#include +#include +#include namespace beast { namespace detail { -/** Case-insensitive function object for performing less than comparisons. */ +inline +char +tolower(char c) +{ + static std::array constexpr tab = {{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + }}; + return static_cast(tab[static_cast(c)]); +} + +template +inline +boost::string_ref +string_helper(const char (&s)[N]) +{ + return boost::string_ref{s, N-1}; +} + +template +inline +T const& +string_helper(T const& t) +{ + return t; +} + +// Case-insensitive less struct ci_less { static bool const is_transparent = true; + template bool - operator()(boost::string_ref const& lhs, - boost::string_ref const& rhs) const noexcept + operator()(S1 const& lhs, S2 const& rhs) const noexcept { using std::begin; using std::end; + auto const s1 = string_helper(lhs); + auto const s2 = string_helper(rhs); return std::lexicographical_compare( - begin(lhs), end(lhs), begin(rhs), end(rhs), + begin(s1), end(s1), begin(s2), end(s2), [](char lhs, char rhs) { - return std::tolower(lhs) < std::tolower(rhs); + return tolower(lhs) < tolower(rhs); } ); } }; -inline +// Case-insensitive equal +struct ci_equal_pred +{ + bool + operator()(char c1, char c2) const noexcept + { + return tolower(c1) == tolower(c2); + } +}; + +// Case-insensitive equal +template bool -ci_equal(std::pair lhs, - std::pair rhs) +ci_equal(S1 const& lhs, S2 const& rhs) { - if(lhs.second != rhs.second) - return false; - return std::equal (lhs.first, lhs.first + lhs.second, - rhs.first, - [] (char lhs, char rhs) - { - return std::tolower(lhs) == std::tolower(rhs); - } - ); -} - -template -inline -std::pair -view(const char (&s)[N]) -{ - return {s, N-1}; -} - -inline -std::pair -view(std::string const& s) -{ - return {s.data(), s.size()}; -} - -/** Returns `true` if strings are case-insensitive equal. */ -template -inline -bool -ci_equal(String1 const& lhs, String2 const& rhs) -{ - return ci_equal(view(lhs), view(rhs)); + return boost::range::equal( + string_helper(lhs), string_helper(rhs), + ci_equal_pred{}); } } // detail diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 1d0a2678e..f3ffefa55 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/beast/http/basic_headers.hpp b/include/beast/http/basic_headers.hpp index 38b3d3ee2..ee6b3b0f8 100644 --- a/include/beast/http/basic_headers.hpp +++ b/include/beast/http/basic_headers.hpp @@ -246,8 +246,7 @@ public: Field names are stored as-is, but comparison are case-insensitive. The container preserves the order of insertion of fields with different names. For fields with the same name, the implementation - concatenates values inserted with duplicate names as per the - rules in rfc2616 section 4.2. + concatenates values inserted with duplicate names as per rfc7230. @note Meets the requirements of @b `FieldSequence`. */ @@ -393,8 +392,7 @@ public: */ // VFALCO TODO Consider allowing rvalue references for std::move? void - insert(boost::string_ref const& name, - boost::string_ref const& value); + insert(boost::string_ref const& name, boost::string_ref value); /** Insert a field value. @@ -416,8 +414,7 @@ public: specified value is inserted as if by `insert(field, value)`. */ void - replace(boost::string_ref const& name, - boost::string_ref const& value); + replace(boost::string_ref const& name, boost::string_ref value); /** Replace a field value. diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp new file mode 100644 index 000000000..64272fdbb --- /dev/null +++ b/include/beast/http/detail/rfc7230.hpp @@ -0,0 +1,261 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_RFC7230_HPP +#define BEAST_HTTP_DETAIL_RFC7230_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +inline +bool +is_tchar(char c) +{ + /* + tchar = "!" | "#" | "$" | "%" | "&" | + "'" | "*" | "+" | "-" | "." | + "^" | "_" | "`" | "|" | "~" | + DIGIT | ALPHA + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 + }}; + return tab[static_cast(c)]; +} + +inline +bool +is_qdchar(char c) +{ + /* + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)]; +} + +inline +bool +is_qpchar(char c) +{ + /* + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)]; +} + +template +void +skip_ows(FwdIt& it, FwdIt const& end) +{ + while(it != end) + { + auto const c = *it; + if(c != ' ' && c != '\t') + break; + ++it; + } +} + +inline +boost::string_ref +trim(boost::string_ref const& s) +{ + auto first = s.begin(); + auto last = s.end(); + skip_ows(first, last); + while(first != last) + { + auto const c = *std::prev(last); + if(c != ' ' && c != '\t') + break; + --last; + } + if(first == last) + return {}; + return {&*first, + static_cast(last - first)}; +} + +struct param_iter +{ + using iter_type = boost::string_ref::const_iterator; + + iter_type it; + iter_type begin; + iter_type end; + std::pair v; + + bool + empty() const + { + return begin == it; + } + + template + void + increment(); +}; + +template +void +param_iter:: +increment() +{ +/* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + + quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF + + Example: + chunked;a=b;i=j,gzip;windowBits=12 + x,y +*/ + auto const err = + [&] + { + it = begin; + }; + v.first.clear(); + v.second.clear(); + detail::skip_ows(it, end); + begin = it; + if(it == end) + return err(); + if(*it != ';') + return err(); + ++it; + detail::skip_ows(it, end); + if(it == end) + return err(); + // param + if(! detail::is_tchar(*it)) + return err(); + auto const p0 = it; + for(;;) + { + ++it; + if(it == end) + return err(); + if(! detail::is_tchar(*it)) + break; + } + auto const p1 = it; + detail::skip_ows(it, end); + if(it == end) + return err(); + if(*it != '=') + return err(); + ++it; + detail::skip_ows(it, end); + if(it == end) + return err(); + if(*it == '"') + { + // quoted-string + auto const p2 = it; + ++it; + for(;;) + { + if(it == end) + return err(); + auto c = *it++; + if(c == '"') + break; + if(detail::is_qdchar(c)) + continue; + if(c != '\\') + return err(); + if(it == end) + return err(); + c = *it++; + if(! detail::is_qpchar(c)) + return err(); + } + v.first = { &*p0, static_cast(p1 - p0) }; + v.second = { &*p2, static_cast(it - p2) }; + } + else + { + // token + if(! detail::is_tchar(*it)) + return err(); + auto const p2 = it; + for(;;) + { + it++; + if(it == end) + break; + if(! detail::is_tchar(*it)) + break; + } + v.first = { &*p0, static_cast(p1 - p0) }; + v.second = { &*p2, static_cast(it - p2) }; + } +} + +} // detail +} // http +} // beast + +#endif + diff --git a/include/beast/http/impl/basic_headers.ipp b/include/beast/http/impl/basic_headers.ipp index 31f9913a0..27b9d4160 100644 --- a/include/beast/http/impl/basic_headers.ipp +++ b/include/beast/http/impl/basic_headers.ipp @@ -8,6 +8,8 @@ #ifndef BEAST_HTTP_IMPL_BASIC_HEADERS_IPP #define BEAST_HTTP_IMPL_BASIC_HEADERS_IPP +#include + namespace beast { namespace http { @@ -257,12 +259,13 @@ template void basic_headers:: insert(boost::string_ref const& name, - boost::string_ref const& value) + boost::string_ref value) { + value = detail::trim(value); typename set_t::insert_commit_data d; auto const result = set_.insert_check(name, less{}, d); - if (result.second) + if(result.second) { auto const p = alloc_traits::allocate( this->member(), 1); @@ -284,8 +287,9 @@ template void basic_headers:: replace(boost::string_ref const& name, - boost::string_ref const& value) + boost::string_ref value) { + value = detail::trim(value); erase(name); insert(name, value); } diff --git a/include/beast/http/impl/message_v1.ipp b/include/beast/http/impl/message_v1.ipp index 16a85a58b..d0b69e4ef 100644 --- a/include/beast/http/impl/message_v1.ipp +++ b/include/beast/http/impl/message_v1.ipp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_IMPL_MESSAGE_V1_IPP #define BEAST_HTTP_IMPL_MESSAGE_V1_IPP -#include +#include #include #include #include @@ -22,13 +22,11 @@ is_keep_alive(message_v1 const& msg) { if(msg.version >= 11) { - if(rfc2616::token_in_list( - msg.headers["Connection"], "close")) + if(token_list{msg.headers["Connection"]}.exists("close")) return false; return true; } - if(rfc2616::token_in_list( - msg.headers["Connection"], "keep-alive")) + if(token_list{msg.headers["Connection"]}.exists("keep-alive")) return true; return false; } @@ -39,8 +37,7 @@ is_upgrade(message_v1 const& msg) { if(msg.version < 11) return false; - if(rfc2616::token_in_list( - msg.headers["Connection"], "upgrade")) + if(token_list{msg.headers["Connection"]}.exists("upgrade")) return true; return false; } @@ -129,8 +126,7 @@ prepare(message_v1& msg, throw std::invalid_argument( "prepare called with Content-Length field set"); - if(rfc2616::token_in_list( - msg.headers["Transfer-Encoding"], "chunked")) + if(token_list{msg.headers["Transfer-Encoding"]}.exists("chunked")) throw std::invalid_argument( "prepare called with Transfer-Encoding: chunked set"); @@ -175,8 +171,8 @@ prepare(message_v1& msg, } // rfc7230 6.7. - if(msg.version < 11 && rfc2616::token_in_list( - msg.headers["Connection"], "upgrade")) + if(msg.version < 11 && token_list{ + msg.headers["Connection"]}.exists("upgrade")) throw std::invalid_argument( "invalid version for Connection: upgrade"); } diff --git a/include/beast/http/impl/rfc7230.ipp b/include/beast/http/impl/rfc7230.ipp new file mode 100644 index 000000000..feffa23ee --- /dev/null +++ b/include/beast/http/impl/rfc7230.ipp @@ -0,0 +1,548 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_RFC7230_IPP +#define BEAST_HTTP_IMPL_RFC7230_IPP + +#include +#include +#include + +namespace beast { +namespace http { + +class param_list::const_iterator +{ + using iter_type = boost::string_ref::const_iterator; + + std::string s_; + detail::param_iter pi_; + +public: + using value_type = param_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.pi_.it == pi_.it && + other.pi_.end == pi_.end && + other.pi_.begin == pi_.begin; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return pi_.v; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class param_list; + + const_iterator(iter_type begin, iter_type end) + { + pi_.it = begin; + pi_.begin = begin; + pi_.end = end; + increment(); + } + + template + static + std::string + unquote(boost::string_ref const& sr); + + template + void + increment(); +}; + +inline +auto +param_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +param_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +std::string +param_list::const_iterator:: +unquote(boost::string_ref const& sr) +{ + std::string s; + s.reserve(sr.size()); + auto it = sr.begin() + 1; + auto end = sr.end() - 1; + while(it != end) + { + if(*it == '\\') + ++it; + s.push_back(*it); + ++it; + } + return s; +} + +template +void +param_list::const_iterator:: +increment() +{ + s_.clear(); + pi_.increment(); + if(pi_.empty()) + { + pi_.it = pi_.end; + pi_.begin = pi_.end; + } + else if(pi_.v.second.front() == '"') + { + s_ = unquote(pi_.v.second); + pi_.v.second = boost::string_ref{ + s_.data(), s_.size()}; + } +} + +//------------------------------------------------------------------------------ + +class ext_list::const_iterator +{ + ext_list::value_type v_; + iter_type it_; + iter_type begin_; + iter_type end_; + +public: + using value_type = ext_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.it_ == it_ && + other.begin_ == begin_ && + other.end_ == end_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class ext_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + begin_ = begin; + end_ = end; + increment(); + } + + template + void + increment(); +}; + +inline +auto +ext_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +ext_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +auto +ext_list:: +find(T const& s) -> + const_iterator +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return beast::detail::ci_equal(s, v.first); + }); +} + +template +bool +ext_list:: +exists(T const& s) +{ + return find(s) != end(); +} + +template +void +ext_list::const_iterator:: +increment() +{ + /* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + + chunked;a=b;i=j,gzip;windowBits=12 + x,y + ,,,,,chameleon + */ + auto const err = + [&] + { + it_ = end_; + begin_ = end_; + }; + auto need_comma = it_ != begin_; + v_.first = {}; + begin_ = it_; + for(;;) + { + detail::skip_ows(it_, end_); + if(it_ == end_) + return err(); + auto const c = *it_; + if(detail::is_tchar(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == end_) + break; + if(! detail::is_tchar(*it_)) + break; + } + v_.first = boost::string_ref{&*p0, + static_cast(it_ - p0)}; + detail::param_iter pi; + pi.it = it_; + pi.begin = it_; + pi.end = end_; + for(;;) + { + pi.increment(); + if(pi.empty()) + break; + } + v_.second = param_list{boost::string_ref{&*it_, + static_cast(pi.it - it_)}}; + it_ = pi.it; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +//------------------------------------------------------------------------------ + +class token_list::const_iterator +{ + token_list::value_type v_; + iter_type it_; + iter_type begin_; + iter_type end_; + +public: + using value_type = token_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.it_ == it_ && + other.begin_ == begin_ && + other.end_ == end_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class token_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + begin_ = begin; + end_ = end; + increment(); + } + + template + void + increment(); +}; + +inline +auto +token_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +token_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +void +token_list::const_iterator:: +increment() +{ + /* + token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] ) + */ + auto const err = + [&] + { + it_ = end_; + begin_ = end_; + }; + auto need_comma = it_ != begin_; + v_ = {}; + begin_ = it_; + for(;;) + { + detail::skip_ows(it_, end_); + if(it_ == end_) + return err(); + auto const c = *it_; + if(detail::is_tchar(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == end_) + break; + if(! detail::is_tchar(*it_)) + break; + } + v_ = boost::string_ref{&*p0, + static_cast(it_ - p0)}; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +template +bool +token_list:: +exists(T const& s) +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return beast::detail::ci_equal(s, v); + } + ) != end(); +} + +} // http +} // beast + +#endif + diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index bb08c0381..05f2a908c 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -97,10 +97,10 @@ struct write_preparation message_v1 const& msg_) : msg(msg_) , w(msg) - , chunked(rfc2616::token_in_list( - msg.headers["Transfer-Encoding"], "chunked")) - , close(rfc2616::token_in_list( - msg.headers["Connection"], "close") || + , chunked(token_list{ + msg.headers["Transfer-Encoding"]}.exists("chunked")) + , close(token_list{ + msg.headers["Connection"]}.exists("close") || (msg.version < 11 && ! msg.headers.exists( "Content-Length"))) { diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index 46f8dee88..c99c2156d 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -124,8 +124,6 @@ private: { if(! value_.empty()) { - rfc2616::trim_right_in_place(value_); - // VFALCO could std::move m_.headers.insert(field_, value_); field_.clear(); value_.clear(); diff --git a/include/beast/http/rfc2616.hpp b/include/beast/http/rfc2616.hpp deleted file mode 100644 index e3c4b8446..000000000 --- a/include/beast/http/rfc2616.hpp +++ /dev/null @@ -1,464 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_RFC2616_HPP -#define BEAST_HTTP_RFC2616_HPP - -#include -#include -#include -#include -#include -#include -#include -#include // for std::tie, remove ASAP -#include -#include - -namespace beast { - -#if ! GENERATING_DOCS - -/** Routines for performing RFC2616 compliance. - RFC2616: - Hypertext Transfer Protocol -- HTTP/1.1 - http://www.w3.org/Protocols/rfc2616/rfc2616 -*/ -namespace rfc2616 { - -namespace detail { - -struct ci_equal_pred -{ - bool operator()(char c1, char c2) - { - // VFALCO TODO Use a table lookup here - return std::tolower(c1) == std::tolower(c2); - } -}; - -} // detail - -/** Returns `true` if `c` is linear white space. - - This excludes the CRLF sequence allowed for line continuations. -*/ -inline -bool -is_lws(char c) -{ - return c == ' ' || c == '\t'; -} - -/** Returns `true` if `c` is any whitespace character. */ -inline -bool -is_white(char c) -{ - switch (c) - { - case ' ': case '\f': case '\n': - case '\r': case '\t': case '\v': - return true; - }; - return false; -} - -/** Returns `true` if `c` is a control character. */ -inline -bool -is_control(char c) -{ - return c <= 31 || c >= 127; -} - -/** Returns `true` if `c` is a separator. */ -inline -bool -is_separator(char c) -{ - // VFALCO Could use a static table - switch (c) - { - case '(': case ')': case '<': case '>': case '@': - case ',': case ';': case ':': case '\\': case '"': - case '{': case '}': case ' ': case '\t': - return true; - }; - return false; -} - -/** Returns `true` if `c` is a character. */ -inline -bool -is_char(char c) -{ - return c >= 0 && c <= 127; -} - -template -FwdIter -trim_left (FwdIter first, FwdIter last) -{ - return std::find_if_not (first, last, - is_white); -} - -template -FwdIter -trim_right (FwdIter first, FwdIter last) -{ - if (first == last) - return last; - do - { - --last; - if (! is_white (*last)) - return ++last; - } - while (last != first); - return first; -} - -template -void -trim_right_in_place (std::basic_string < - CharT, Traits, Allocator>& s) -{ - s.resize (std::distance (s.begin(), - trim_right (s.begin(), s.end()))); -} - -template -std::pair -trim (FwdIter first, FwdIter last) -{ - first = trim_left (first, last); - last = trim_right (first, last); - return std::make_pair (first, last); -} - -template -String -trim (String const& s) -{ - using std::begin; - using std::end; - auto first = begin(s); - auto last = end(s); - std::tie (first, last) = trim (first, last); - return { first, last }; -} - -template -String -trim_right (String const& s) -{ - using std::begin; - using std::end; - auto first (begin(s)); - auto last (end(s)); - last = trim_right (first, last); - return { first, last }; -} - -inline -std::string -trim (std::string const& s) -{ - return trim (s); -} - -/** Parse a character sequence of values separated by commas. - Double quotes and escape sequences will be converted. Excess white - space, commas, double quotes, and empty elements are not copied. - Format: - #(token|quoted-string) - Reference: - http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 -*/ -template ::value_type>>, - class Char> -Result -split(FwdIt first, FwdIt last, Char delim) -{ - Result result; - using string = typename Result::value_type; - FwdIt iter = first; - string e; - while (iter != last) - { - if (*iter == '"') - { - // quoted-string - ++iter; - while (iter != last) - { - if (*iter == '"') - { - ++iter; - break; - } - - if (*iter == '\\') - { - // quoted-pair - ++iter; - if (iter != last) - e.append (1, *iter++); - } - else - { - // qdtext - e.append (1, *iter++); - } - } - if (! e.empty()) - { - result.emplace_back(std::move(e)); - e.clear(); - } - } - else if (*iter == delim) - { - e = trim_right (e); - if (! e.empty()) - { - result.emplace_back(std::move(e)); - e.clear(); - } - ++iter; - } - else if (is_lws (*iter)) - { - ++iter; - } - else - { - e.append (1, *iter++); - } - } - - if (! e.empty()) - { - e = trim_right (e); - if (! e.empty()) - result.emplace_back(std::move(e)); - } - return result; -} - -template ::value_type>>> -Result -split_commas(FwdIt first, FwdIt last) -{ - return split(first, last, ','); -} - -template > -Result -split_commas(boost::string_ref const& s) -{ - return split_commas(s.begin(), s.end()); -} - -//------------------------------------------------------------------------------ - -/** Iterates through a comma separated list. - - Meets the requirements of ForwardIterator. - - List defined in rfc2616 2.1. - - @note Values returned may contain backslash escapes. -*/ -class list_iterator -{ - using iter_type = boost::string_ref::const_iterator; - - iter_type it_; - iter_type end_; - boost::string_ref value_; - -public: - using value_type = boost::string_ref; - using pointer = value_type const*; - using reference = value_type const&; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::forward_iterator_tag; - - list_iterator(iter_type begin, iter_type end) - : it_(begin) - , end_(end) - { - if(it_ != end_) - increment(); - } - - bool - operator==(list_iterator const& other) const - { - return other.it_ == it_ && other.end_ == end_ - && other.value_.size() == value_.size(); - } - - bool - operator!=(list_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_; - } - - pointer - operator->() const - { - return &*(*this); - } - - list_iterator& - operator++() - { - increment(); - return *this; - } - - list_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - -private: - template - void - increment(); -}; - -template -void -list_iterator::increment() -{ - value_.clear(); - while(it_ != end_) - { - if(*it_ == '"') - { - // quoted-string - ++it_; - if(it_ == end_) - return; - if(*it_ != '"') - { - auto start = it_; - for(;;) - { - ++it_; - if(it_ == end_) - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - return; - } - if(*it_ == '"') - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - ++it_; - return; - } - } - } - ++it_; - } - else if(*it_ == ',') - { - it_++; - continue; - } - else if(is_lws(*it_)) - { - ++it_; - continue; - } - else - { - auto start = it_; - for(;;) - { - ++it_; - if(it_ == end_ || - *it_ == ',' || - is_lws(*it_)) - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - return; - } - } - } - } -} - -/** Returns true if two strings are equal. - - A case-insensitive comparison is used. -*/ -inline -bool -ci_equal(boost::string_ref s1, boost::string_ref s2) -{ - return boost::range::equal(s1, s2, - detail::ci_equal_pred{}); -} - -/** Returns a range representing the list. */ -inline -boost::iterator_range -make_list(boost::string_ref const& field) -{ - return boost::iterator_range{ - list_iterator{field.begin(), field.end()}, - list_iterator{field.end(), field.end()}}; -} - -/** Returns true if the specified token exists in the list. - - A case-insensitive comparison is used. -*/ -template -bool -token_in_list(boost::string_ref const& value, - boost::string_ref const& token) -{ - for(auto const& item : make_list(value)) - if(ci_equal(item, token)) - return true; - return false; -} - -} // rfc2616 - -#endif - -} // beast - -#endif - diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index 3f69354af..e429efba2 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -8,16 +8,238 @@ #ifndef BEAST_HTTP_RFC7230_HPP #define BEAST_HTTP_RFC7230_HPP -#include -#include +#include namespace beast { -namespace rfc7230 { +namespace http { +/** A list of parameters in a HTTP extension field value. + This container allows iteration of the parameter list + in a HTTP extension. The parameter list is a series + of "name = value" pairs with each pair starting with + a semicolon. -} // rfc7230 + BNF: + @code + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + @endcode + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. +*/ +class param_list +{ + boost::string_ref s_; + +public: + /** The type of each element in the list. + + The first string in the pair is the name of the + parameter, and the second string in the pair is its value. + */ + using value_type = + std::pair; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /// Default constructor. + param_list() = default; + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + param_list(boost::string_ref const& s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; +}; + +//------------------------------------------------------------------------------ + +/** A list of extensions in a comma separated HTTP field value. + + This container allows iteration of the extensions in a HTTP + field value. The extension list is a comma separated list of + token parameter list pairs. + + BNF: + @code + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + @endcode + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. +*/ +class ext_list +{ + using iter_type = boost::string_ref::const_iterator; + + boost::string_ref s_; + +public: + /** The type of each element in the list. + + The first element of the pair is the extension token, and the + second element of the pair is an iterable container holding the + extension's name/value parameters. + */ + using value_type = std::pair; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + ext_list(boost::string_ref const& s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; + + /** Find a token in the list. + + @param s The token to find. A case-insensitive comparison is used. + + @return An iterator to the matching token, or `end()` if no + token exists. + */ + template + const_iterator + find(T const& s); + + /** Return `true` if a token is present in the list. + + @param s The token to find. A case-insensitive comparison is used. + */ + template + bool + exists(T const& s); +}; + +//------------------------------------------------------------------------------ + +/** A list of tokens in a comma separated HTTP field value. + + This container allows iteration of the extensions in a HTTP + field value. The extension list is a comma separated list of + token parameter list pairs. + + BNF: + @code + token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] ) + @endcode + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. +*/ +class token_list +{ + using iter_type = boost::string_ref::const_iterator; + + boost::string_ref s_; + +public: + /** The type of each element in the token list. + + The first element of the pair is the extension token, and the + second element of the pair is an iterable container holding the + extension's name/value parameters. + */ + using value_type = boost::string_ref; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + token_list(boost::string_ref const& s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; + + /** Return `true` if a token is present in the list. + + @param s The token to find. A case-insensitive comparison is used. + */ + template + bool + exists(T const& s); +}; + +} // http } // beast +#include + #endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 569f43b6e..c331cdd31 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -951,8 +951,7 @@ build_response(http::request_v1 const& req) return err("Missing Host"); if(! req.headers.exists("Sec-WebSocket-Key")) return err("Missing Sec-WebSocket-Key"); - if(! rfc2616::token_in_list( - req.headers["Upgrade"], "websocket")) + if(! http::token_list{req.headers["Upgrade"]}.exists("websocket")) return err("Missing websocket Upgrade token"); { auto const version = @@ -1005,8 +1004,7 @@ do_response(http::response_v1 const& res, return fail(); if(! is_upgrade(res)) return fail(); - if(! rfc2616::ci_equal( - res.headers["Upgrade"], "websocket")) + if(! http::token_list{res.headers["Upgrade"]}.exists("websocket")) return fail(); if(! res.headers.exists("Sec-WebSocket-Accept")) return fail(); diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index a26927a25..abe22409e 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -120,7 +120,7 @@ decorate(Decorator&& d) This setting only affects the behavior of HTTP requests that implicitly or explicitly ask for a keepalive. For HTTP requests that indicate the connection should be closed, the connection is - closed as per rfc2616. + closed as per rfc7230. The default setting is to close connections after a failed upgrade request. diff --git a/test/Jamfile b/test/Jamfile index 02fb70a1b..9cd423b4e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -54,7 +54,6 @@ unit-test http-tests : http/read.cpp http/reason.cpp http/resume_context.cpp - http/rfc2616.cpp http/rfc7230.cpp http/status.cpp http/streambuf_body.cpp diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 6b2b8c374..af46ab20c 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -20,7 +20,6 @@ add_executable (http-tests read.cpp reason.cpp resume_context.cpp - rfc2616.cpp rfc7230.cpp status.cpp streambuf_body.cpp diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index bef3d4cdb..b5ef62fde 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -135,6 +135,13 @@ public: { }; + static + std::string + str(boost::string_ref const& s) + { + return std::string{s.data(), s.size()}; + } + template class test_parser : public basic_parser_v1> @@ -146,8 +153,7 @@ public: { if(! value_.empty()) { - rfc2616::trim_right_in_place(value_); - fields.emplace(field_, value_); + fields.emplace(field_, str(detail::trim(value_))); field_.clear(); value_.clear(); } diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp index d197958f9..4cbf2b573 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/http/nodejs_parser.hpp @@ -11,7 +11,7 @@ #include "nodejs-parser/http_parser.h" #include -#include +#include #include #include #include @@ -611,7 +611,7 @@ nodejs_basic_parser::check_header() { if (! value_.empty()) { - rfc2616::trim_right_in_place(value_); + //detail::trim(value_); call_on_field(field_, value_, has_on_field{}); field_.clear(); diff --git a/test/http/rfc2616.cpp b/test/http/rfc2616.cpp deleted file mode 100644 index 346e838dd..000000000 --- a/test/http/rfc2616.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include - -namespace beast { -namespace rfc2616 { -namespace test { - -class rfc2616_test : public beast::unit_test::suite -{ -public: - void - checkSplit(std::string const& s, - std::vector const& expected) - { - auto const parsed = split_commas(s.begin(), s.end()); - expect (parsed == expected); - } - - void testSplit() - { - checkSplit("", {}); - checkSplit(" ", {}); - checkSplit(" ", {}); - checkSplit("\t", {}); - checkSplit(" \t ", {}); - checkSplit(",", {}); - checkSplit(",,", {}); - checkSplit(" ,", {}); - checkSplit(" , ,", {}); - checkSplit("x", {"x"}); - checkSplit(" x", {"x"}); - checkSplit(" \t x", {"x"}); - checkSplit("x ", {"x"}); - checkSplit("x \t", {"x"}); - checkSplit(" \t x \t ", {"x"}); - checkSplit("\"\"", {}); - checkSplit(" \"\"", {}); - checkSplit("\"\" ", {}); - checkSplit("\"x\"", {"x"}); - checkSplit("\" \"", {" "}); - checkSplit("\" x\"", {" x"}); - checkSplit("\"x \"", {"x "}); - checkSplit("\" x \"", {" x "}); - checkSplit("\"\tx \"", {"\tx "}); - checkSplit("x,y", {"x", "y"}); - checkSplit("x ,\ty ", {"x", "y"}); - checkSplit("x, y, z", {"x","y","z"}); - checkSplit("x, \"y\", z", {"x","y","z"}); - checkSplit(",,x,,\"y\",,", {"x","y"}); - } - - void - checkIter(std::string const& s, - std::vector const& expected) - { - std::vector got; - for(auto const& v : make_list(s)) - got.emplace_back(v); - expect(got == expected); - } - - void - testIter() - { - checkIter("x", {"x"}); - checkIter(" x", {"x"}); - checkIter("x\t", {"x"}); - checkIter("\tx ", {"x"}); - checkIter(",x", {"x"}); - checkIter("x,", {"x"}); - checkIter(",x,", {"x"}); - checkIter(" , x\t,\t", {"x"}); - checkIter("x,y", {"x", "y"}); - checkIter("x, ,y ", {"x", "y"}); - checkIter("\"x\"", {"x"}); - } - - void - testList() - { - expect(token_in_list("x", "x")); - expect(token_in_list("x,y", "x")); - expect(token_in_list("x,y", "y")); - expect(token_in_list("x, y ", "y")); - expect(token_in_list("x", "X")); - expect(token_in_list("Y", "y")); - expect(token_in_list("close, keepalive", "close")); - expect(token_in_list("close, keepalive", "keepalive")); - } - - void - run() - { - testSplit(); - testIter(); - testList(); - } -}; - -BEAST_DEFINE_TESTSUITE(rfc2616,http,beast); - -} // test -} // rfc2616 -} // beast diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp index 93a1a81a0..c63fd8ae9 100644 --- a/test/http/rfc7230.cpp +++ b/test/http/rfc7230.cpp @@ -7,3 +7,240 @@ // Test that header file is self-contained. #include + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace test { + +class rfc7230_test : public beast::unit_test::suite +{ +public: + static + std::string + fmt(std::string const& s) + { + return '\'' + s + '\''; + } + + static + std::string + str(boost::string_ref const& s) + { + return std::string(s.data(), s.size()); + } + + static + std::string + str(param_list const& c) + { + std::string s; + for(auto const& p : c) + { + s.push_back(';'); + s.append(str(p.first)); + s.push_back('='); + s.append(str(p.second)); + } + return s; + } + + void + testParamList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(param_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(param_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + auto const cq = + [&](std::string const& s, std::string const& good) + { + auto const got = str(param_list{s}); + expect(got == good, fmt(got)); + }; + + ce(""); + cs(" ;\t i =\t 1 \t", ";i=1"); + cq("\t; \t xyz=1 ; ijk=\"q\\\"t\"", ";xyz=1;ijk=q\"t"); + + // invalid strings + cs(";", ""); + cs(";,", ""); + cs(";xy", ""); + cs(";xy", ""); + cs(";xy ", ""); + cs(";xy,", ""); + + cq(";x=,", ""); + cq(";xy=\"", ""); + cq(";xy=\"\x7f", ""); + cq(";xy=\"\\", ""); + cq(";xy=\"\\\x01\"", ""); + } + + static + std::string + str(ext_list const& ex) + { + std::string s; + for(auto const& e : ex) + { + if(! s.empty()) + s += ','; + s.append(str(e.first)); + s += str(e.second); + } + return s; + } + + void + testExtList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(ext_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(ext_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + auto const cq = + [&](std::string const& s, std::string const& good) + { + auto const got = str(ext_list{s}); + expect(got == good, fmt(got)); + }; + /* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + */ + ce(""); + cs(",", ""); + cs(", ", ""); + cs(",\t", ""); + cs(", \t", ""); + cs(" ", ""); + cs(" ,", ""); + cs("\t,", ""); + cs("\t , \t", ""); + cs(",,", ""); + cs(" , \t,, \t,", ""); + + ce("a"); + ce("ab"); + ce("a,b"); + cs(" a ", "a"); + cs("\t a, b\t , c\t", "a,b,c"); + + cs("a; \t i\t=\t \t1\t ", "a;i=1"); + ce("a;i=1;j=2;k=3"); + ce("a;i=1;j=2;k=3,b;i=4;j=5;k=6"); + + cq("ab;x=\" \"", "ab;x= "); + cq("ab;x=\"\\\"\"", "ab;x=\""); + + expect(ext_list{"a,b;i=1,c;j=2;k=3"}.exists("A")); + expect(ext_list{"a,b;i=1,c;j=2;k=3"}.exists("b")); + expect(! ext_list{"a,b;i=1,c;j=2;k=3"}.exists("d")); + + // invalid strings + cs("i j", "i"); + cs(";", ""); + } + + static + std::string + str(token_list const& c) + { + bool first = true; + std::string s; + for(auto const& p : c) + { + if(! first) + s.push_back(','); + s.append(str(p)); + first = false; + } + return s; + } + + void + testTokenList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(token_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(token_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + + cs("", ""); + cs(" ", ""); + cs(" ", ""); + cs("\t", ""); + cs(" \t ", ""); + cs(",", ""); + cs(",,", ""); + cs(" ,", ""); + cs(" , ,", ""); + cs(" x", "x"); + cs(" \t x", "x"); + cs("x ", "x"); + cs("x \t", "x"); + cs(" \t x \t ", "x"); + ce("x,y"); + cs("x ,\ty ", "x,y"); + cs("x, y, z", "x,y,z"); + + expect(token_list{"a,b,c"}.exists("A")); + expect(token_list{"a,b,c"}.exists("b")); + expect(! token_list{"a,b,c"}.exists("d")); + + // invalid + cs("x y", "x"); + } + + void + run() + { + testParamList(); + testExtList(); + testTokenList(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc7230,http,beast); + +} // test +} // http +} // beast From 8afdcb9e9fde277032d6ce096ee25703eba836f2 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 28 May 2016 07:56:38 -0400 Subject: [PATCH 07/11] Add message swap members and free functions --- CHANGELOG | 6 ++-- TODO.txt | 2 ++ doc/quickref.xml | 1 + include/beast/http/message.hpp | 46 +++++++++++++++++++++++++++++++ include/beast/http/message_v1.hpp | 24 ++++++++++++++++ test/http/message.cpp | 22 +++++++++++++++ test/http/message_v1.cpp | 28 +++++++++++++++++++ 7 files changed, 125 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a467786be..48e293889 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,10 +4,8 @@ * Use beast::error_code instead of nested types * Tidy up use of GENERATING_DOCS * Remove obsolete RFC2616 functions -* Add HTTP field value parser containers: - - ext_list - - param_list - - token_list +* Add message swap members and free functions +* Add HTTP field value parser containers: ext_list, param_list, token_list API Changes: diff --git a/TODO.txt b/TODO.txt index 2efbbb77f..3b66fc113 100644 --- a/TODO.txt +++ b/TODO.txt @@ -44,3 +44,5 @@ HTTP: * Custom HTTP error codes for various situations * Branch prediction hints in parser * Check basic_parser_v1 against rfc7230 for leading message whitespace +* Fix the order of message constructor parameters: + body first then headers (since body is constructed with arguments more often) diff --git a/doc/quickref.xml b/doc/quickref.xml index 8aec05241..929876ff0 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -56,6 +56,7 @@ parse prepare read + swap write Constants diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 04c77037c..6c1a57c93 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -24,12 +24,30 @@ struct request_fields { std::string method; std::string url; + +protected: + void + swap(request_fields& other) + { + using std::swap; + swap(method, other.method); + swap(url, other.url); + } }; struct response_fields { int status; std::string reason; + +protected: + void + swap(response_fields& other) + { + using std::swap; + swap(status, other.status); + swap(reason, other.reason); + } }; } // detail @@ -125,7 +143,14 @@ struct message { } + /// Swap this message for another message. + void + swap(message& other); + private: + using base = typename std::conditional::type; + template message(std::piecewise_construct_t, std::tuple& tu, beast::detail::index_sequence) @@ -145,6 +170,27 @@ private: } }; +template +void +message:: +swap(message& other) +{ + using std::swap; + base::swap(other); + swap(headers, other.headers); + swap(body, other.body); +} + +/// Swap one message for another message. +template +inline +void +swap(message& lhs, + message& rhs) +{ + lhs.swap(rhs); +} + /// A typical HTTP request template>> diff --git a/include/beast/http/message_v1.hpp b/include/beast/http/message_v1.hpp index fb700b819..68abc68c1 100644 --- a/include/beast/http/message_v1.hpp +++ b/include/beast/http/message_v1.hpp @@ -48,8 +48,32 @@ struct message_v1 : message std::forward(argn)...) { } + + /// Swap this message for another message. + void + swap(message_v1& other); }; +template +void +message_v1:: +swap(message_v1& other) +{ + using std::swap; + message::swap(other); + swap(version, other.version); +} + +/// Swap one message for another message. +template +inline +void +swap(message_v1& lhs, + message_v1& rhs) +{ + lhs.swap(rhs); +} + /// A typical HTTP/1 request template>> diff --git a/test/http/message.cpp b/test/http/message.cpp index 6d46ed472..3c549248e 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -126,9 +127,30 @@ public: } } + void testSwap() + { + message m1; + message m2; + m1.url = "u"; + m1.body = "1"; + m1.headers.insert("h", "v"); + m2.method = "G"; + m2.body = "2"; + swap(m1, m2); + expect(m1.method == "G"); + expect(m2.method.empty()); + expect(m1.url.empty()); + expect(m2.url == "u"); + expect(m1.body == "2"); + expect(m2.body == "1"); + expect(! m1.headers.exists("h")); + expect(m2.headers.exists("h")); + } + void run() override { testConstruction(); + testSwap(); } }; diff --git a/test/http/message_v1.cpp b/test/http/message_v1.cpp index d86097ddd..a8954c681 100644 --- a/test/http/message_v1.cpp +++ b/test/http/message_v1.cpp @@ -8,6 +8,8 @@ // Test that header file is self-contained. #include +#include +#include #include #include @@ -78,10 +80,36 @@ public: expect(! is_keep_alive(m)); } + void testSwap() + { + message_v1 m1; + message_v1 m2; + m1.status = 200; + m1.version = 10; + m1.body = "1"; + m1.headers.insert("h", "v"); + m2.status = 404; + m2.reason = "OK"; + m2.body = "2"; + m2.version = 11; + swap(m1, m2); + expect(m1.status == 404); + expect(m2.status == 200); + expect(m1.reason == "OK"); + expect(m2.reason.empty()); + expect(m1.version == 11); + expect(m2.version == 10); + expect(m1.body == "2"); + expect(m2.body == "1"); + expect(! m1.headers.exists("h")); + expect(m2.headers.exists("h")); + } + void run() override { testFreeFunctions(); testPrepare(); + testSwap(); } }; From 50b5dab5df941382d150f7bb0f89cbc98b34b51f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 28 May 2016 09:23:54 -0400 Subject: [PATCH 08/11] Rename concept to DynamicBuffer (API change): Conform to the Networking TS by renaming the Streambuf concept to DynamicBuffer in all places. Values of types meeting the requirements of DynamicBuffer are renamed to dynabuf. See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4478.html#requirements.dynamic_buffers * Headers renamed * Formal parameter names renamed * Template argument types renamed * Documentation updated --- CHANGELOG | 3 + README.md | 5 + doc/beast.qbk | 2 +- doc/design.qbk | 14 +- doc/http.qbk | 24 +--- doc/quickref.xml | 10 +- doc/reference.xsl | 8 +- doc/types/DynamicBuffer.qbk | 125 ++++++++++++++++++ doc/types/Streambuf.qbk | 96 -------------- doc/websocket.qbk | 2 +- include/beast/core.hpp | 4 +- include/beast/core/basic_streambuf.hpp | 56 ++++---- include/beast/core/buffer_concepts.hpp | 20 +-- include/beast/core/detail/buffer_concepts.hpp | 2 +- ...{write_streambuf.hpp => write_dynabuf.hpp} | 60 ++++----- ..._readstream.hpp => dynabuf_readstream.hpp} | 38 +++--- include/beast/core/impl/basic_streambuf.ipp | 54 ++++---- ..._readstream.ipp => dynabuf_readstream.ipp} | 42 +++--- include/beast/core/static_streambuf.hpp | 2 +- ...{write_streambuf.hpp => write_dynabuf.hpp} | 24 ++-- include/beast/http/basic_dynabuf_body.hpp | 95 +++++++++++++ include/beast/http/empty_body.hpp | 1 - include/beast/http/impl/read.ipp | 112 ++++++++-------- include/beast/http/impl/write.ipp | 71 +++++----- include/beast/http/read.hpp | 37 +++--- include/beast/http/streambuf_body.hpp | 80 +---------- include/beast/http/string_body.hpp | 3 +- include/beast/websocket/detail/frame.hpp | 34 ++--- .../beast/websocket/detail/stream_base.hpp | 11 +- .../beast/websocket/impl/read_frame_op.ipp | 30 ++--- include/beast/websocket/impl/read_op.ipp | 16 +-- include/beast/websocket/impl/stream.ipp | 95 +++++++------ include/beast/websocket/stream.hpp | 59 +++++---- test/Jamfile | 5 +- test/core/CMakeLists.txt | 4 +- ..._readstream.cpp => dynabuf_readstream.cpp} | 24 ++-- ...{write_streambuf.cpp => write_dynabuf.cpp} | 6 +- test/http/CMakeLists.txt | 1 + test/http/basic_dynabuf_body.cpp | 9 ++ test/http/basic_parser_v1.cpp | 2 +- test/http/message_fuzz.hpp | 2 +- 41 files changed, 676 insertions(+), 612 deletions(-) create mode 100644 doc/types/DynamicBuffer.qbk delete mode 100644 doc/types/Streambuf.qbk rename include/beast/core/detail/{write_streambuf.hpp => write_dynabuf.hpp} (69%) rename include/beast/core/{streambuf_readstream.hpp => dynabuf_readstream.hpp} (90%) rename include/beast/core/impl/{streambuf_readstream.ipp => dynabuf_readstream.ipp} (88%) rename include/beast/core/{write_streambuf.hpp => write_dynabuf.hpp} (67%) create mode 100644 include/beast/http/basic_dynabuf_body.hpp rename test/core/{streambuf_readstream.cpp => dynabuf_readstream.cpp} (86%) rename test/core/{write_streambuf.cpp => write_dynabuf.cpp} (84%) create mode 100644 test/http/basic_dynabuf_body.cpp diff --git a/CHANGELOG b/CHANGELOG index 48e293889..ffce41c66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,4 +11,7 @@ API Changes: * ci_equal is moved to beast::http namespace, in rfc7230.hpp +* "DynamicBuffer","dynabuf" renamed from "Streambuf", "streambuf". See: + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4478.html#requirements.dynamic_buffers + -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index a9c8a923e..af2f595af 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ Requirements: * C++11 or greater * OpenSSL (optional) +This software is currently in beta: interfaces are subject to change. For +recent changes see [CHANGELOG](CHANGELOG). +The library has been submitted to the +[Boost Library Incubator](http://rrsd.com/blincubator.com/bi_library/beast-2/?gform_post_id=1579) + Example WebSocket program: ```C++ #include diff --git a/doc/beast.qbk b/doc/beast.qbk index 1b8cd3524..f9d8b89ad 100644 --- a/doc/beast.qbk +++ b/doc/beast.qbk @@ -192,11 +192,11 @@ supporting its development. [section:types Type Requirements] [include types/Body.qbk] [include types/BufferSequence.qbk] +[include types/DynamicBuffer.qbk] [include types/Field.qbk] [include types/FieldSequence.qbk] [include types/Parser.qbk] [include types/Reader.qbk] -[include types/Streambuf.qbk] [include types/Streams.qbk] [include types/Writer.qbk] [endsect] diff --git a/doc/design.qbk b/doc/design.qbk index 5e90dac80..ff109d762 100644 --- a/doc/design.qbk +++ b/doc/design.qbk @@ -25,13 +25,13 @@ libraries: * Let library users make the important decisions such as how to allocate memory or how to leverage flow control. -Beast formalizes the [link beast.types.Streambuf [*`Streambuf`]] concept, -and relies heavily on the Boost.Asio [*`ConstBufferSequence`] and -[*`MutableBufferSequence`] concepts for passing buffers to functions. -The authors have found the `Streambuf` and buffer sequence interfaces -to be optimal for interacting with Asio, and for other tasks such as -incremental parsing of data in buffers (for example, parsing websocket -frames stored in a [link beast.ref.static_streambuf `static_streambuf`]). +Beast uses the [link beast.types.DynamicBuffer [*`DynamicBuffer`]] concept +presented in the Netwoking TS, and relies heavily on the Boost.Asio +[*`ConstBufferSequence`] and [*`MutableBufferSequence`] concepts for passing +buffers to functions. The authors have found the dynamic buffer and buffer +sequence interfaces to be optimal for interacting with Asio, and for other +tasks such as incremental parsing of data in buffers (for example, parsing +websocket frames stored in a [link beast.ref.static_streambuf `static_streambuf`]). During the development of Beast the authors have studied other software packages and in particular the comments left during the Boost Review process diff --git a/doc/http.qbk b/doc/http.qbk index f1b533d9c..6089102ac 100644 --- a/doc/http.qbk +++ b/doc/http.qbk @@ -182,21 +182,9 @@ used in the examples: resp.body = "Here is the data you requested"; ``` -* [link beast.ref.http__basic_streambuf_body [*`basic_streambuf_body`:]] A body with a -`value_type` of `streambuf`. A streambuf is an efficient storage object which -uses multiple octet arrays of varying lengths to represent data. Here is -a body that uses a `boost::asio::streambuf` as its container: -``` - template - http::response> - make_response(ConstBufferSequence const& buffers) - { - http::response> resp; - resp.body.commit(boost::asio::buffer_copy(resp.body.prepare( - boost::asio::buffer_size(buffers)), buffers)); - return resp; - } -``` +* [link beast.ref.http__streambuf_body [*`streambuf_body`:]] A body with a +`value_type` of [link beast.ref.streambuf `streambuf`]: an efficient storage +object which uses multiple octet arrays of varying lengths to represent data. [heading Sockets] @@ -234,7 +222,7 @@ When the implementation reads messages from a socket, it can read bytes lying after the end of the message if they are present (the alternative is to read a single byte at a time which is unsuitable for performance reasons). To store and re-use these extra bytes on subsequent messages, the read interface -requires an additional paramter: a [link beast.types.Streambuf [*`Streambuf`]] +requires an additional paramter: a [link beast.types.DynamicBuffer [*`DynamicBuffer`]] object. This example reads a message from the socket, with the extra bytes stored in the streambuf parameter for use in a subsequent call to read: ``` @@ -264,7 +252,7 @@ called: An alternative to using a `boost::asio::streambuf` is to use a [link beast.ref.streambuf `beast::streambuf`], which meets the requirements of -[*`Streambuf`] and is optimized for performance: +[*`DynamicBuffer`] and is optimized for performance: ``` void handle_read(boost::system::error_code); ... @@ -274,7 +262,7 @@ An alternative to using a `boost::asio::streambuf` is to use a ``` The `read` implementation can use any object meeting the requirements of -[link beast.types.Streambuf [*`Streambuf`]], allowing callers to define custom +[link beast.types.DynamicBuffer [*`DynamicBuffer`]], allowing callers to define custom memory management strategies used by the implementation. [endsect] diff --git a/doc/quickref.xml b/doc/quickref.xml index 929876ff0..3368eedc1 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -29,9 +29,9 @@ Classes + basic_dynabuf_body basic_headers basic_parser_v1 - basic_streambuf_body empty_body headers message @@ -130,6 +130,7 @@ basic_streambuf buffers_adapter consuming_buffers + dynabuf_readstream error_code handler_alloc prepared_buffers @@ -137,7 +138,6 @@ static_streambuf_n static_string streambuf - streambuf_readstream system_error @@ -164,8 +164,8 @@ is_BufferSequence is_CompletionHandler is_ConstBufferSequence + is_DynamicBuffer is_MutableBufferSequence - is_Streambuf is_SyncReadStream is_SyncStream is_SyncWriteStream @@ -174,10 +174,10 @@ Concepts - BufferSequence AsyncStream + BufferSequence + DynamicBuffer Stream - Streambuf SyncStream diff --git a/doc/reference.xsl b/doc/reference.xsl index 0824f0774..3a82f1e58 100644 --- a/doc/reference.xsl +++ b/doc/reference.xsl @@ -1552,15 +1552,15 @@ class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*ConstBufferSequence]]`` + + class ``[link beast.types.DynamicBuffer [*DynamicBuffer]]`` + class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]`` - + class ``[link beast.types.streams.Stream [*Stream]]`` - - class ``[link beast.types.Streambuf [*Streambuf]]`` - class ``[link beast.types.streams.SyncStream [*SyncStream]]`` diff --git a/doc/types/DynamicBuffer.qbk b/doc/types/DynamicBuffer.qbk new file mode 100644 index 000000000..dd0243bf5 --- /dev/null +++ b/doc/types/DynamicBuffer.qbk @@ -0,0 +1,125 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:DynamicBuffer DynamicBuffer] + +A dynamic buffer encapsulates memory storage that may be automatically resized +as required, where the memory is divided into an input sequence followed by an +output sequence. These memory regions are internal to the dynamic buffer, but +direct access to the elements is provided to permit them to be efficiently used +with I/O operations, such as the send or receive operations of a socket. Data +written to the output sequence of a dynamic buffer object is appended to the +input sequence of the same object. + +The interface to this concept is intended to permit the following +implementation strategies: + +* A single contiguous octet array, which is reallocated as necessary to + accommodate changes in the size of the octet sequence. + +* A sequence of one or more octet arrays, where each array is of the same + size. Additional octet array objects are appended to the sequence to + accommodate changes in the size of the octet sequence. + +* A sequence of one or more octet arrays of varying sizes. Additional octet + array objects are appended to the sequence to accommodate changes in the + size of the character sequence. This is the implementation approached + currently offered by [link beast.ref.basic_streambuf `basic_streambuf`]. + +In the table below: + +* `X` denotes a dynamic buffer class. +* `a` denotes a value of type `X`. +* `c` denotes a (possibly const) value of type `X`. +* `n` denotes a value of type `std::size_t`. +* `T` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`]. +* `U` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`]. + +[table DynamicBuffer requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [`T`] + [ + This type represents the memory associated with the input sequence. + ] +] +[ + [`X::mutable_buffers_type`] + [`U`] + [ + This type represents the memory associated with the output sequence. + ] +] +[ + [`c.size()`] + [`std::size_t`] + [ + Returns the size, in bytes, of the input sequence. + ] +] +[ + [`c.max_size()`] + [`std::size_t`] + [ + Returns the permitted maximum of the sum of the sizes of the input + sequence and output sequence. + ] +] +[ + [`c.capacity()`] + [`std::size_t`] + [ + Returns the maximum sum of the sizes of the input sequence and output + sequence that the dynamic buffer can hold without requiring reallocation. + ] +] +[ + [`c.data()`] + [`X::const_buffers_type`] + [ + Returns a constant buffer sequence u that represents the memory + associated with the input sequence, and where `buffer_size(u) == size()`. + ] +] +[ + [`a.prepare(n)`] + [`X:mutable_buffers_type`] + [ + Returns a mutable buffer sequence u representing the output sequence, + and where `buffer_size(u) == n`. The dynamic buffer reallocates memory + as required. All constant or mutable buffer sequences previously + obtained using `data()` or `prepare()` are invalidated. + + Throws: `length_error` if `size() + n` exceeds `max_size()`. + ] +] +[ + [`a.commit(n)`] + [ ] + [ + Appends `n` bytes from the start of the output sequence to the end of + the input sequence. The remainder of the output sequence is discarded. + If `n` is greater than the size of the output sequence, the entire + output sequence is appended to the input sequence. All constant or + mutable buffer sequences previously obtained using `data()` or + `prepare()` are invalidated. + ] +] +[ + [`a.consume(n)`] + [ ] + [ + Removes `n` bytes from beginning of the input sequence. If `n` is + greater than the size of the input sequence, the entire input sequence + is removed. All constant or mutable buffer sequences previously + obtained using `data()` or `prepare()` are invalidated. + ] +] +] + +[endsect] diff --git a/doc/types/Streambuf.qbk b/doc/types/Streambuf.qbk deleted file mode 100644 index 60bf88ed2..000000000 --- a/doc/types/Streambuf.qbk +++ /dev/null @@ -1,96 +0,0 @@ -[/ - Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Streambuf Streambuf] - -A [*`Streambuf`] represents a logical octet sequence divided in two sections, -the input sequence and the output sequence. Octets are written to the output -sequence, then moved to the input sequence where they are available for -reading. When some or all of the input sequence is no longer needed, it may -be consumed. - -The interface to this concept is intended to permit the following -implementation strategies: - -* A single contiguous octet array, which is reallocated as necessary to - accommodate changes in the size of the octet sequence. - -* A sequence of one or more octet arrays, where each array is of the same - size. Additional octet array objects are appended to the sequence to - accommodate changes in the size of the octet sequence. - -* A sequence of one or more octet arrays of varying sizes. Additional octet - array objects are appended to the sequence to accommodate changes in the - size of the character sequence. This is the implementation approached - currently offered by [link beast.ref.basic_streambuf `basic_streambuf`]. - -In the table below: - -* `X` denotes a class meeting the requirements of [*`Streambuf`] -* `a` denotes a value of type `X` -* `n` denotes a value convertible to `std::size_t` -* `U`, `T` denote unspecified types. - -[table Streambuf requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X::const_buffers_type`] - [`T`] - [`T` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`].] -] -[ - [`X::mutable_buffers_type`] - [`U`] - [`U` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`].] -] -[ - [`a.commit(n)`] - [`void`] - [Moves bytes from the output sequence to the input sequence.] -] -[ - [`a.consume(n)`] - [`void`] - [Removes bytes from the input sequence.] -] -[ - [`a.data()`] - [`T`] - [Returns a list of buffers that represents the input sequence.] -] -[ - [`a.prepare(n)`] - [`U`] - [Returns a list of buffers that represents the output sequence, with - the given size.] -] -[ - [`a.size()`] - [`std::size_t`] - [Returns the size of the input sequence.] -] -[ - [`a.max_size()`] - [`std::size_t`] - [Returns the maximum size of the stream buffer.] -] -[ - [`read_size_helper(a, n)`] - [`std::size_t`] - [ - Returns the suggested number of bytes to read into the output - sequence where `n` is an upper limit on this value. One possible - implementation is to return the number of bytes that may be prepared - without causing a dynamic allocation or `n`, whichever is smaller. - Calls to `read_size_helper` will be made without namespace - qualification, to allow the rules for argument dependent lookup to - take effect. - ] -] -] - -[endsect] diff --git a/doc/websocket.qbk b/doc/websocket.qbk index 617deb2a5..da2808cc0 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -358,7 +358,7 @@ handler of the corresponding read function.] Because calls to read data may return a variable amount of bytes, the interface to calls that read data require an object that meets the requirements -of [link beast.types.Streambuf [*`Streambuf`]]. This concept is modeled on +of [link beast.types.DynamicBuffer [*`DynamicBuffer`]]. This concept is modeled on [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`]. The implementation does not perform queueing or buffering of messages. If diff --git a/include/beast/core.hpp b/include/beast/core.hpp index 26eb9cd7b..dc750eea5 100644 --- a/include/beast/core.hpp +++ b/include/beast/core.hpp @@ -24,8 +24,8 @@ #include #include #include -#include +#include #include -#include +#include #endif diff --git a/include/beast/core/basic_streambuf.hpp b/include/beast/core/basic_streambuf.hpp index b48a6d0f3..6aee96034 100644 --- a/include/beast/core/basic_streambuf.hpp +++ b/include/beast/core/basic_streambuf.hpp @@ -18,14 +18,14 @@ namespace beast { -/** A @b `Streambuf` that uses multiple buffers internally. +/** A @b `DynamicBuffer` that uses multiple buffers internally. The implementation uses a sequence of one or more character arrays of varying sizes. Additional character array objects are appended to the sequence to accommodate changes in the size of the character sequence. - @note Meets the requirements of @b Streambuf. + @note Meets the requirements of @b `DynamicBuffer`. @tparam Allocator The allocator to use for managing memory. */ @@ -202,26 +202,37 @@ public: basic_streambuf(std::size_t alloc_size = 1024, Allocator const& alloc = allocator_type{}); - /// Get the associated allocator + /// Returns a copy of the associated allocator. allocator_type get_allocator() const { return this->member(); } - /// Get the maximum size of the basic_streambuf. + /// Returns the size of the input sequence. + size_type + size() const + { + return in_size_; + } + + /// Returns the permitted maximum sum of the sizes of the input and output sequence. size_type max_size() const { return std::numeric_limits::max(); } - /// Get the size of the input sequence. - size_type - size() const - { - return in_size_; - } + /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + std::size_t + capacity() const; + + /** Get a list of buffers that represents the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const; /** Get a list of buffers that represents the output sequence, with the given size. @@ -239,22 +250,11 @@ public: void commit(size_type n); - /** Get a list of buffers that represents the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ - const_buffers_type - data() const; - /// Remove bytes from the input sequence. void consume(size_type n); - /// Clear everything. - void - clear(); - - // Helper for read_until + // Helper for boost::asio::read_until template friend std::size_t @@ -262,6 +262,9 @@ public: OtherAllocator> const& streambuf, std::size_t max_size); private: + void + clear(); + void move_assign(basic_streambuf& other, std::false_type); @@ -277,20 +280,17 @@ private: void delete_list(); - std::size_t - prepare_size() const; - void debug_check() const; }; -/** Format output to a stream buffer. +/** Format output to a @ref basic_streambuf. - @param streambuf The streambuf to write to. + @param streambuf The @ref basic_streambuf to write to. @param t The object to write. - @return The stream buffer. + @return A reference to the @ref basic_streambuf. */ template basic_streambuf& diff --git a/include/beast/core/buffer_concepts.hpp b/include/beast/core/buffer_concepts.hpp index f99d716bd..addc3cdf3 100644 --- a/include/beast/core/buffer_concepts.hpp +++ b/include/beast/core/buffer_concepts.hpp @@ -35,6 +35,16 @@ struct is_ConstBufferSequence : { }; +/// Determine if `T` meets the requirements of @b `DynamicBuffer`. +template +#if GENERATING_DOCS +struct is_DynamicBuffer : std::integral_constant +#else +struct is_DynamicBuffer : detail::is_DynamicBuffer::type +#endif +{ +}; + /// Determine if `T` meets the requirements of @b `MutableBufferSequence`. template #if GENERATING_DOCS @@ -46,16 +56,6 @@ struct is_MutableBufferSequence : { }; -/// Determine if `T` meets the requirements of @b `Streambuf`. -template -#if GENERATING_DOCS -struct is_Streambuf : std::integral_constant -#else -struct is_Streambuf : detail::is_Streambuf::type -#endif -{ -}; - } // beast #endif diff --git a/include/beast/core/detail/buffer_concepts.hpp b/include/beast/core/detail/buffer_concepts.hpp index ce1451fbb..2361e678a 100644 --- a/include/beast/core/detail/buffer_concepts.hpp +++ b/include/beast/core/detail/buffer_concepts.hpp @@ -86,7 +86,7 @@ public: }; template -class is_Streambuf +class is_DynamicBuffer { template #include @@ -42,73 +42,73 @@ public: ! is_string_literal::value; }; -template +template void -write_streambuf(Streambuf& streambuf, +write_dynabuf(DynamicBuffer& dynabuf, boost::asio::const_buffer const& buffer) { using boost::asio::buffer_copy; using boost::asio::buffer_size; - streambuf.commit(buffer_copy( - streambuf.prepare(buffer_size(buffer)), + dynabuf.commit(buffer_copy( + dynabuf.prepare(buffer_size(buffer)), buffer)); } -template +template void -write_streambuf(Streambuf& streambuf, +write_dynabuf(DynamicBuffer& dynabuf, boost::asio::mutable_buffer const& buffer) { using boost::asio::buffer_copy; using boost::asio::buffer_size; - streambuf.commit(buffer_copy( - streambuf.prepare(buffer_size(buffer)), + dynabuf.commit(buffer_copy( + dynabuf.prepare(buffer_size(buffer)), buffer)); } -template +template typename std::enable_if< is_BufferConvertible::value && ! std::is_convertible::value && ! std::is_convertible::value >::type -write_streambuf(Streambuf& streambuf, T const& t) +write_dynabuf(DynamicBuffer& dynabuf, T const& t) { using boost::asio::buffer_copy; using boost::asio::buffer_size; auto const buffers = boost::asio::buffer(t); - streambuf.commit(buffer_copy( - streambuf.prepare(buffer_size(buffers)), + dynabuf.commit(buffer_copy( + dynabuf.prepare(buffer_size(buffers)), buffers)); } -template +template typename std::enable_if< is_ConstBufferSequence::value && ! is_BufferConvertible::value && ! std::is_convertible::value && ! std::is_convertible::value >::type -write_streambuf(Streambuf& streambuf, Buffers const& buffers) +write_dynabuf(DynamicBuffer& dynabuf, Buffers const& buffers) { using boost::asio::buffer_copy; using boost::asio::buffer_size; - streambuf.commit(buffer_copy( - streambuf.prepare(buffer_size(buffers)), + dynabuf.commit(buffer_copy( + dynabuf.prepare(buffer_size(buffers)), buffers)); } -template +template void -write_streambuf(Streambuf& streambuf, const char (&s)[N]) +write_dynabuf(DynamicBuffer& dynabuf, const char (&s)[N]) { using boost::asio::buffer_copy; - streambuf.commit(buffer_copy( - streambuf.prepare(N - 1), + dynabuf.commit(buffer_copy( + dynabuf.prepare(N - 1), boost::asio::buffer(s, N - 1))); } -template +template typename std::enable_if< ! is_string_literal::value && ! is_ConstBufferSequence::value && @@ -116,22 +116,22 @@ typename std::enable_if< ! std::is_convertible::value && ! std::is_convertible::value >::type -write_streambuf(Streambuf& streambuf, T const& t) +write_dynabuf(DynamicBuffer& dynabuf, T const& t) { using boost::asio::buffer; using boost::asio::buffer_copy; auto const s = boost::lexical_cast(t); - streambuf.commit(buffer_copy( - streambuf.prepare(s.size()), buffer(s))); + dynabuf.commit(buffer_copy( + dynabuf.prepare(s.size()), buffer(s))); } -template +template void -write_streambuf(Streambuf& streambuf, +write_dynabuf(DynamicBuffer& dynabuf, T0 const& t0, T1 const& t1, TN const&... tn) { - write_streambuf(streambuf, t0); - write_streambuf(streambuf, t1, tn...); + write_dynabuf(dynabuf, t0); + write_dynabuf(dynabuf, t1, tn...); } } // detail diff --git a/include/beast/core/streambuf_readstream.hpp b/include/beast/core/dynabuf_readstream.hpp similarity index 90% rename from include/beast/core/streambuf_readstream.hpp rename to include/beast/core/dynabuf_readstream.hpp index e014f6d21..9e8199db8 100644 --- a/include/beast/core/streambuf_readstream.hpp +++ b/include/beast/core/dynabuf_readstream.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_STREAMBUF_READSTREAM_HPP -#define BEAST_STREAMBUF_READSTREAM_HPP +#ifndef BEAST_DYNABUF_READSTREAM_HPP +#define BEAST_DYNABUF_READSTREAM_HPP #include #include @@ -22,11 +22,11 @@ namespace beast { -/** A @b `Stream` with attached @b `Streambuf` to buffer reads. +/** A @b `Stream` with attached @b `DynamicBuffer` to buffer reads. This wraps a @b `Stream` implementation so that calls to write are passed through to the underlying stream, while calls to read will - first consume the input sequence stored in a @b `Streambuf` which + first consume the input sequence stored in a @b `DynamicBuffer` which is part of the object. The use-case for this class is different than that of the @@ -50,9 +50,9 @@ namespace beast { // Process the next HTTP headers on the stream, // leaving excess bytes behind for the next call. // - template + template void process_http_message( - streambuf_readstream& stream) + dynabuf_readstream& stream) { // Read up to and including the end of the HTTP // headers, leaving the sequence in the stream's @@ -85,24 +85,24 @@ namespace beast { @tparam Stream The type of stream to wrap. - @tparam Streambuf The type of stream buffer to use. + @tparam DynamicBuffer The type of stream buffer to use. */ -template -class streambuf_readstream +template +class dynabuf_readstream { - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); template class read_some_op; - Streambuf sb_; + DynamicBuffer sb_; std::size_t capacity_ = 0; Stream next_layer_; public: /// The type of the internal buffer - using streambuf_type = Streambuf; + using dynabuf_type = DynamicBuffer; /// The type of the next layer. using next_layer_type = @@ -122,14 +122,14 @@ public: @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - streambuf_readstream(streambuf_readstream&&) = default; + dynabuf_readstream(dynabuf_readstream&&) = default; /** Move assignment. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - streambuf_readstream& operator=(streambuf_readstream&&) = default; + dynabuf_readstream& operator=(dynabuf_readstream&&) = default; /** Construct the wrapping stream. @@ -137,7 +137,7 @@ public: */ template explicit - streambuf_readstream(Args&&... args); + dynabuf_readstream(Args&&... args); /// Get a reference to the next layer. next_layer_type& @@ -174,7 +174,7 @@ public: by causing the internal buffer size to increase beyond the caller defined maximum. */ - Streambuf& + DynamicBuffer& buffer() { return sb_; @@ -187,7 +187,7 @@ public: by causing the internal buffer size to increase beyond the caller defined maximum. */ - Streambuf const& + DynamicBuffer const& buffer() const { return sb_; @@ -275,6 +275,6 @@ public: } // beast -#include +#include #endif diff --git a/include/beast/core/impl/basic_streambuf.ipp b/include/beast/core/impl/basic_streambuf.ipp index 230548711..50bb43e02 100644 --- a/include/beast/core/impl/basic_streambuf.ipp +++ b/include/beast/core/impl/basic_streambuf.ipp @@ -8,7 +8,7 @@ #ifndef BEAST_IMPL_BASIC_STREAMBUF_IPP #define BEAST_IMPL_BASIC_STREAMBUF_IPP -#include +#include #include #include #include @@ -527,6 +527,28 @@ basic_streambuf::basic_streambuf( "basic_streambuf: invalid alloc_size"); } +template +std::size_t +basic_streambuf::capacity() const +{ + auto pos = out_; + if(pos == list_.end()) + return 0; + auto n = pos->size() - out_pos_; + while(++pos != list_.end()) + n += pos->size(); + return in_size_ + n; +} + +template +auto +basic_streambuf:: +data() const -> + const_buffers_type +{ + return const_buffers_type(*this); +} + template auto basic_streambuf::prepare(size_type n) -> @@ -646,14 +668,6 @@ basic_streambuf::commit(size_type n) debug_check(); } -template -auto -basic_streambuf::data() const -> - const_buffers_type -{ - return const_buffers_type(*this); -} - template void basic_streambuf::consume(size_type n) @@ -717,7 +731,8 @@ basic_streambuf::consume(size_type n) template void -basic_streambuf::clear() +basic_streambuf:: +clear() { delete_list(); list_.clear(); @@ -795,21 +810,6 @@ basic_streambuf::delete_list() } } -// Returns the number of bytes which can be -// prepared without causing a memory allocation. -template -std::size_t -basic_streambuf::prepare_size() const -{ - auto pos = out_; - if(pos == list_.end()) - return 0; - auto n = pos->size() - out_pos_; - while(++pos != list_.end()) - n += pos->size(); - return n; -} - template void basic_streambuf::debug_check() const @@ -855,7 +855,7 @@ std::size_t read_size_helper(basic_streambuf< Allocator> const& streambuf, std::size_t max_size) { - auto const avail = streambuf.prepare_size(); + auto const avail = streambuf.capacity() - streambuf.size(); if(avail == 0) return std::min(max_size, std::max(512, streambuf.alloc_size_)); @@ -866,7 +866,7 @@ template basic_streambuf& operator<<(basic_streambuf& streambuf, T const& t) { - detail::write_streambuf(streambuf, t); + detail::write_dynabuf(streambuf, t); return streambuf; } diff --git a/include/beast/core/impl/streambuf_readstream.ipp b/include/beast/core/impl/dynabuf_readstream.ipp similarity index 88% rename from include/beast/core/impl/streambuf_readstream.ipp rename to include/beast/core/impl/dynabuf_readstream.ipp index 8a0506d62..a0fbd9b16 100644 --- a/include/beast/core/impl/streambuf_readstream.ipp +++ b/include/beast/core/impl/dynabuf_readstream.ipp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_IMPL_STREAMBUF_READSTREAM_IPP -#define BEAST_IMPL_STREAMBUF_READSTREAM_IPP +#ifndef BEAST_IMPL_DYNABUF_READSTREAM_HPP +#define BEAST_IMPL_DYNABUF_READSTREAM_HPP #include #include @@ -16,24 +16,24 @@ namespace beast { -template +template template -class streambuf_readstream< - Stream, Streambuf>::read_some_op +class dynabuf_readstream< + Stream, DynamicBuffer>::read_some_op { using alloc_type = handler_alloc; struct data { - streambuf_readstream& srs; + dynabuf_readstream& srs; MutableBufferSequence bs; Handler h; int state = 0; template data(DeducedHandler&& h_, - streambuf_readstream& srs_, + dynabuf_readstream& srs_, MutableBufferSequence const& bs_) : srs(srs_) , bs(bs_) @@ -50,7 +50,7 @@ public: template read_some_op(DeducedHandler&& h, - streambuf_readstream& srs, Args&&... args) + dynabuf_readstream& srs, Args&&... args) : d_(std::allocate_shared(alloc_type{h}, std::forward(h), srs, std::forward(args)...)) @@ -94,10 +94,10 @@ public: } }; -template +template template void -streambuf_readstream:: +dynabuf_readstream:: read_some_op::operator()( error_code const& ec, std::size_t bytes_transferred) { @@ -155,18 +155,18 @@ read_some_op::operator()( //------------------------------------------------------------------------------ -template +template template -streambuf_readstream:: -streambuf_readstream(Args&&... args) +dynabuf_readstream:: +dynabuf_readstream(Args&&... args) : next_layer_(std::forward(args)...) { } -template +template template auto -streambuf_readstream:: +dynabuf_readstream:: async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) -> typename async_completion< @@ -184,10 +184,10 @@ async_write_some(ConstBufferSequence const& buffers, std::forward(handler)); } -template +template template std::size_t -streambuf_readstream:: +dynabuf_readstream:: read_some( MutableBufferSequence const& buffers) { @@ -203,10 +203,10 @@ read_some( return n; } -template +template template std::size_t -streambuf_readstream:: +dynabuf_readstream:: read_some(MutableBufferSequence const& buffers, error_code& ec) { @@ -232,10 +232,10 @@ read_some(MutableBufferSequence const& buffers, return bytes_transferred; } -template +template template auto -streambuf_readstream:: +dynabuf_readstream:: async_read_some( MutableBufferSequence const& buffers, ReadHandler&& handler) -> diff --git a/include/beast/core/static_streambuf.hpp b/include/beast/core/static_streambuf.hpp index 07fa4fc1f..2166ba200 100644 --- a/include/beast/core/static_streambuf.hpp +++ b/include/beast/core/static_streambuf.hpp @@ -15,7 +15,7 @@ namespace beast { -/** A @b `Streambuf` with a fixed size internal buffer. +/** A @b `DynamicBuffer` with a fixed size internal buffer. Ownership of the underlying storage belongs to the derived class. diff --git a/include/beast/core/write_streambuf.hpp b/include/beast/core/write_dynabuf.hpp similarity index 67% rename from include/beast/core/write_streambuf.hpp rename to include/beast/core/write_dynabuf.hpp index 07e7d8095..e99c56f0e 100644 --- a/include/beast/core/write_streambuf.hpp +++ b/include/beast/core/write_dynabuf.hpp @@ -5,20 +5,20 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WRITE_STREAMBUF_HPP -#define BEAST_WRITE_STREAMBUF_HPP +#ifndef BEAST_WRITE_DYNABUF_HPP +#define BEAST_WRITE_DYNABUF_HPP #include -#include +#include #include #include namespace beast { -/** Write to a Streambuf. +/** Write to a @b `DynamicBuffer`. This function appends the serialized representation of each provided - argument into the stream buffer. It is capable of converting the + argument into the dynamic buffer. It is capable of converting the following types of arguments: @li `boost::asio::const_buffer` @@ -33,29 +33,29 @@ namespace beast { For all types not listed above, the function will invoke `boost::lexical_cast` on the argument in an attempt to convert to - a string, which is then appended to the stream buffer. + a string, which is then appended to the dynamic buffer. When this function serializes numbers, it converts them to their text representation as if by a call to `std::to_string`. - @param streambuf The stream buffer to write to. + @param dynabuf The dynamic buffer to write to. @param args A list of one or more arguments to write. @throws unspecified Any exceptions thrown by `boost::lexical_cast`. @note This function participates in overload resolution only if - the `streambuf` parameter meets the requirements of @b `Streambuf`. + the `dynabuf` parameter meets the requirements of @b `DynamicBuffer`. */ -template +template #if GENERATING_DOCS void #else -typename std::enable_if::value>::type +typename std::enable_if::value>::type #endif -write(Streambuf& streambuf, Args const&... args) +write(DynamicBuffer& dynabuf, Args const&... args) { - detail::write_streambuf(streambuf, args...); + detail::write_dynabuf(dynabuf, args...); } } // beast diff --git a/include/beast/http/basic_dynabuf_body.hpp b/include/beast/http/basic_dynabuf_body.hpp new file mode 100644 index 000000000..ee84ca538 --- /dev/null +++ b/include/beast/http/basic_dynabuf_body.hpp @@ -0,0 +1,95 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BASIC_DYNABUF_BODY_HPP +#define BEAST_HTTP_BASIC_DYNABUF_BODY_HPP + +#include +#include + +namespace beast { +namespace http { + +/** A message body represented by a @b `DynamicBuffer` + + Meets the requirements of @b `Body`. +*/ +template +struct basic_dynabuf_body +{ + /// The type of the `message::body` member + using value_type = DynamicBuffer; + +#if GENERATING_DOCS +private: +#endif + + class reader + { + value_type& sb_; + + public: + template + explicit + reader(message& m) noexcept + : sb_(m.body) + { + } + + void + write(void const* data, + std::size_t size, error_code&) noexcept + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + sb_.commit(buffer_copy( + sb_.prepare(size), buffer(data, size))); + } + }; + + class writer + { + DynamicBuffer const& body_; + + public: + writer(writer const&) = delete; + writer& operator=(writer const&) = delete; + + template + explicit + writer(message< + isRequest, basic_dynabuf_body, Headers> const& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + } + + std::uint64_t + content_length() const + { + return body_.size(); + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(body_.data()); + return true; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp index 8eb44064d..152a71fe3 100644 --- a/include/beast/http/empty_body.hpp +++ b/include/beast/http/empty_body.hpp @@ -9,7 +9,6 @@ #define BEAST_HTTP_EMPTY_BODY_HPP #include -#include #include #include #include diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 0d22a4b5c..38c09eb56 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -21,7 +21,7 @@ namespace http { namespace detail { template + class DynamicBuffer, class Parser, class Handler> class parse_op { using alloc_type = @@ -30,7 +30,7 @@ class parse_op struct data { Stream& s; - Streambuf& sb; + DynamicBuffer& db; Parser& p; Handler h; bool started = false; @@ -39,9 +39,9 @@ class parse_op template data(DeducedHandler&& h_, Stream& s_, - Streambuf& sb_, Parser& p_) + DynamicBuffer& sb_, Parser& p_) : s(s_) - , sb(sb_) + , db(sb_) , p(p_) , h(std::forward(h_)) , cont(boost_asio_handler_cont_helpers:: @@ -101,9 +101,9 @@ public: }; template + class DynamicBuffer, class Parser, class Handler> void -parse_op:: +parse_op:: operator()(error_code ec, std::size_t bytes_transferred, bool again) { auto& d = *d_; @@ -115,7 +115,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) case 0: { auto const used = - d.p.write(d.sb.data(), ec); + d.p.write(d.db.data(), ec); if(ec) { // call handler @@ -126,7 +126,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) } if(used > 0) d.started = true; - d.sb.consume(used); + d.db.consume(used); if(d.p.complete()) { // call handler @@ -142,8 +142,8 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) case 1: // read d.state = 2; - d.s.async_read_some(d.sb.prepare( - read_size_helper(d.sb, 65536)), + d.s.async_read_some(d.db.prepare( + read_size_helper(d.db, 65536)), std::move(*this)); return; @@ -172,8 +172,8 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) d.state = 99; break; } - d.sb.commit(bytes_transferred); - auto const used = d.p.write(d.sb.data(), ec); + d.db.commit(bytes_transferred); + auto const used = d.p.write(d.db.data(), ec); if(ec) { // call handler @@ -182,7 +182,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) } if(used > 0) d.started = true; - d.sb.consume(used); + d.db.consume(used); if(d.p.complete()) { // call handler @@ -199,7 +199,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) //------------------------------------------------------------------------------ -template class read_op @@ -216,7 +216,7 @@ class read_op struct data { Stream& s; - Streambuf& sb; + DynamicBuffer& db; message_type& m; parser_type p; Handler h; @@ -226,9 +226,9 @@ class read_op template data(DeducedHandler&& h_, Stream& s_, - Streambuf& sb_, message_type& m_) + DynamicBuffer& sb_, message_type& m_) : s(s_) - , sb(sb_) + , db(sb_) , m(m_) , h(std::forward(h_)) , cont(boost_asio_handler_cont_helpers:: @@ -286,11 +286,11 @@ public: } }; -template void -read_op:: +read_op:: operator()(error_code ec, bool again) { auto& d = *d_; @@ -301,7 +301,7 @@ operator()(error_code ec, bool again) { case 0: d.state = 1; - async_parse(d.s, d.sb, d.p, std::move(*this)); + async_parse(d.s, d.db, d.p, std::move(*this)); return; case 1: @@ -318,49 +318,49 @@ operator()(error_code ec, bool again) //------------------------------------------------------------------------------ -template +template void parse(SyncReadStream& stream, - Streambuf& streambuf, Parser& parser) + DynamicBuffer& dynabuf, Parser& parser) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_Parser::value, "Parser requirements not met"); error_code ec; - parse(stream, streambuf, parser, ec); + parse(stream, dynabuf, parser, ec); if(ec) throw boost::system::system_error{ec}; } -template +template void -parse(SyncReadStream& stream, Streambuf& streambuf, +parse(SyncReadStream& stream, DynamicBuffer& dynabuf, Parser& parser, error_code& ec) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_Parser::value, "Parser requirements not met"); bool started = false; for(;;) { auto used = - parser.write(streambuf.data(), ec); + parser.write(dynabuf.data(), ec); if(ec) return; - streambuf.consume(used); + dynabuf.consume(used); if(used > 0) started = true; if(parser.complete()) break; - streambuf.commit(stream.read_some( - streambuf.prepare(read_size_helper( - streambuf, 65536)), ec)); + dynabuf.commit(stream.read_some( + dynabuf.prepare(read_size_helper( + dynabuf, 65536)), ec)); if(ec && ec != boost::asio::error::eof) return; if(ec == boost::asio::error::eof) @@ -379,86 +379,86 @@ parse(SyncReadStream& stream, Streambuf& streambuf, } template + class DynamicBuffer, class Parser, class ReadHandler> typename async_completion< ReadHandler, void(error_code)>::result_type async_parse(AsyncReadStream& stream, - Streambuf& streambuf, Parser& parser, ReadHandler&& handler) + DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler) { static_assert(is_AsyncReadStream::value, "AsyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_Parser::value, "Parser requirements not met"); beast::async_completion completion(handler); - detail::parse_op{ - completion.handler, stream, streambuf, parser}; + completion.handler, stream, dynabuf, parser}; return completion.result.get(); } -template void -read(SyncReadStream& stream, Streambuf& streambuf, +read(SyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& msg) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_ReadableBody::value, "ReadableBody requirements not met"); error_code ec; - read(stream, streambuf, msg, ec); + read(stream, dynabuf, msg, ec); if(ec) throw system_error{ec}; } -template void -read(SyncReadStream& stream, Streambuf& streambuf, +read(SyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& m, error_code& ec) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_ReadableBody::value, "ReadableBody requirements not met"); parser_v1 p; - parse(stream, streambuf, p, ec); + parse(stream, dynabuf, p, ec); if(ec) return; assert(p.complete()); m = p.release(); } -template typename async_completion< ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, Streambuf& streambuf, +async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& m, ReadHandler&& handler) { static_assert(is_AsyncReadStream::value, "AsyncReadStream requirements not met"); - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); static_assert(is_ReadableBody::value, "ReadableBody requirements not met"); beast::async_completion completion(handler); - detail::read_op{completion.handler, - stream, streambuf, m}; + stream, dynabuf, m}; return completion.result.get(); } diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index 05f2a908c..e06d4ab1e 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -32,51 +32,51 @@ namespace http { namespace detail { -template +template void -write_firstline(Streambuf& streambuf, +write_firstline(DynamicBuffer& dynabuf, message_v1 const& msg) { - write(streambuf, msg.method); - write(streambuf, " "); - write(streambuf, msg.url); - write(streambuf, " HTTP/"); - write(streambuf, msg.version / 10); - write(streambuf, "."); - write(streambuf, msg.version % 10); - write(streambuf, "\r\n"); + write(dynabuf, msg.method); + write(dynabuf, " "); + write(dynabuf, msg.url); + write(dynabuf, " HTTP/"); + write(dynabuf, msg.version / 10); + write(dynabuf, "."); + write(dynabuf, msg.version % 10); + write(dynabuf, "\r\n"); } -template +template void -write_firstline(Streambuf& streambuf, +write_firstline(DynamicBuffer& dynabuf, message_v1 const& msg) { - write(streambuf, "HTTP/"); - write(streambuf, msg.version / 10); - write(streambuf, "."); - write(streambuf, msg.version % 10); - write(streambuf, " "); - write(streambuf, msg.status); - write(streambuf, " "); - write(streambuf, msg.reason); - write(streambuf, "\r\n"); + write(dynabuf, "HTTP/"); + write(dynabuf, msg.version / 10); + write(dynabuf, "."); + write(dynabuf, msg.version % 10); + write(dynabuf, " "); + write(dynabuf, msg.status); + write(dynabuf, " "); + write(dynabuf, msg.reason); + write(dynabuf, "\r\n"); } -template +template void -write_fields(Streambuf& streambuf, FieldSequence const& fields) +write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) { - static_assert(is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); //static_assert(is_FieldSequence::value, // "FieldSequence requirements not met"); for(auto const& field : fields) { - write(streambuf, field.name()); - write(streambuf, ": "); - write(streambuf, field.value()); - write(streambuf, "\r\n"); + write(dynabuf, field.name()); + write(dynabuf, ": "); + write(dynabuf, field.value()); + write(dynabuf, "\r\n"); } } @@ -378,17 +378,17 @@ operator()(error_code ec, std::size_t, bool again) d.copy = {}; } -template +template class writef0_lambda { - Streambuf const& sb_; + DynamicBuffer const& sb_; SyncWriteStream& stream_; bool chunked_; error_code& ec_; public: writef0_lambda(SyncWriteStream& stream, - Streambuf const& sb, bool chunked, error_code& ec) + DynamicBuffer const& sb, bool chunked, error_code& ec) : sb_(sb) , stream_(stream) , chunked_(chunked) @@ -548,9 +548,8 @@ async_write(AsyncWriteStream& stream, message_v1 const& msg, WriteHandler&& handler) { - static_assert( - is_AsyncWriteStream::value, - "AsyncWriteStream requirements not met"); + static_assert(is_AsyncWriteStream::value, + "AsyncWriteStream requirements not met"); static_assert(is_WritableBody::value, "WritableBody requirements not met"); beast::async_completion #include #include -#include namespace beast { namespace http { @@ -36,7 +35,7 @@ namespace http { @param stream The stream from which the data is to be read. The type must support the @b `SyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -47,10 +46,10 @@ namespace http { @throws boost::system::system_error on failure. */ -template +template void parse(SyncReadStream& stream, - Streambuf& streambuf, Parser& parser); + DynamicBuffer& dynabuf, Parser& parser); /** Parse a HTTP/1 message from a stream. @@ -71,7 +70,7 @@ parse(SyncReadStream& stream, @param stream The stream from which the data is to be read. The type must support the @b `SyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -82,10 +81,10 @@ parse(SyncReadStream& stream, @param ec Set to the error, if any occurred. */ -template +template void parse(SyncReadStream& stream, - Streambuf& streambuf, Parser& parser, error_code& ec); + DynamicBuffer& dynabuf, Parser& parser, error_code& ec); /** Start an asynchronous operation to parse a HTTP/1 message from a stream. @@ -106,7 +105,7 @@ parse(SyncReadStream& stream, @param stream The stream from which the data is to be read. The type must support the @b `AsyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -128,14 +127,14 @@ parse(SyncReadStream& stream, manner equivalent to using `boost::asio::io_service::post`. */ template + class DynamicBuffer, class Parser, class ReadHandler> #if GENERATING_DOCS void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_parse(AsyncReadStream& stream, Streambuf& streambuf, +async_parse(AsyncReadStream& stream, DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler); /** Read a HTTP/1 message from a stream. @@ -157,7 +156,7 @@ async_parse(AsyncReadStream& stream, Streambuf& streambuf, @param stream The stream from which the data is to be read. The type must support the @b `SyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -168,10 +167,10 @@ async_parse(AsyncReadStream& stream, Streambuf& streambuf, @throws boost::system::system_error Thrown on failure. */ -template void -read(SyncReadStream& stream, Streambuf& streambuf, +read(SyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& msg); /** Read a HTTP/1 message from a stream. @@ -193,7 +192,7 @@ read(SyncReadStream& stream, Streambuf& streambuf, @param stream The stream from which the data is to be read. The type must support the @b `SyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -204,10 +203,10 @@ read(SyncReadStream& stream, Streambuf& streambuf, @param ec Set to the error, if any occurred. */ -template void -read(SyncReadStream& stream, Streambuf& streambuf, +read(SyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& msg, error_code& ec); @@ -229,7 +228,7 @@ read(SyncReadStream& stream, Streambuf& streambuf, @param stream The stream to read the message from. The type must support the @b `AsyncReadStream` concept. - @param streambuf A `Streambuf` holding additional bytes + @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the stream buffer's input sequence will be given to the parser @@ -249,7 +248,7 @@ read(SyncReadStream& stream, Streambuf& streambuf, this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ -template #if GENERATING_DOCS @@ -258,7 +257,7 @@ void_or_deduced typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_read(AsyncReadStream& stream, Streambuf& streambuf, +async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, message_v1& msg, ReadHandler&& handler); diff --git a/include/beast/http/streambuf_body.hpp b/include/beast/http/streambuf_body.hpp index faadf1c12..68a4a4ec6 100644 --- a/include/beast/http/streambuf_body.hpp +++ b/include/beast/http/streambuf_body.hpp @@ -8,91 +8,17 @@ #ifndef BEAST_HTTP_STREAMBUF_BODY_HPP #define BEAST_HTTP_STREAMBUF_BODY_HPP -#include -#include +#include #include -#include -#include namespace beast { namespace http { -/** A message body represented by a Streambuf +/** A message body represented by a @ref streambuf Meets the requirements of @b `Body`. */ -template -struct basic_streambuf_body -{ - /// The type of the `message::body` member - using value_type = Streambuf; - -#if GENERATING_DOCS -private: -#endif - - class reader - { - value_type& sb_; - - public: - template - explicit - reader(message& m) noexcept - : sb_(m.body) - { - } - - void - write(void const* data, - std::size_t size, error_code&) noexcept - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - sb_.commit(buffer_copy( - sb_.prepare(size), buffer(data, size))); - } - }; - - class writer - { - Streambuf const& body_; - - public: - writer(writer const&) = delete; - writer& operator=(writer const&) = delete; - - template - explicit - writer(message< - isRequest, basic_streambuf_body, Headers> const& m) - : body_(m.body) - { - } - - void - init(error_code& ec) - { - } - - std::uint64_t - content_length() const - { - return body_.size(); - } - - template - boost::tribool - operator()(resume_context&&, error_code&, Write&& write) - { - write(body_.data()); - return true; - } - }; -}; - -using streambuf_body = basic_streambuf_body; +using streambuf_body = basic_dynabuf_body; } // http } // beast diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp index c69a626d6..76e46a05b 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -9,8 +9,7 @@ #define BEAST_HTTP_STRING_BODY_HPP #include -#include -#include +#include #include #include diff --git a/include/beast/websocket/detail/frame.hpp b/include/beast/websocket/detail/frame.hpp index 05955631c..0de62d77f 100644 --- a/include/beast/websocket/detail/frame.hpp +++ b/include/beast/websocket/detail/frame.hpp @@ -117,11 +117,11 @@ is_valid(close_code::value code) //------------------------------------------------------------------------------ -// Write frame header to streambuf +// Write frame header to dynamic buffer // -template +template void -write(Streambuf& sb, frame_header const& fh) +write(DynamicBuffer& db, frame_header const& fh) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -159,24 +159,24 @@ write(Streambuf& sb, frame_header const& fh) native_to_little_uint32(fh.key, &b[n]); n += 4; } - sb.commit(buffer_copy( - sb.prepare(n), buffer(b))); + db.commit(buffer_copy( + db.prepare(n), buffer(b))); } // Read fixed frame header // Requires at least 2 bytes // -template +template std::size_t -read_fh1(frame_header& fh, Streambuf& sb, +read_fh1(frame_header& fh, DynamicBuffer& db, role_type role, close_code::value& code) { using boost::asio::buffer; using boost::asio::buffer_copy; using boost::asio::buffer_size; std::uint8_t b[2]; - assert(buffer_size(sb.data()) >= sizeof(b)); - sb.consume(buffer_copy(buffer(b), sb.data())); + assert(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); std::size_t need; fh.len = b[1] & 0x7f; switch(fh.len) @@ -236,9 +236,9 @@ read_fh1(frame_header& fh, Streambuf& sb, // Decode variable frame header from stream // -template +template void -read_fh2(frame_header& fh, Streambuf& sb, +read_fh2(frame_header& fh, DynamicBuffer& db, role_type role, close_code::value& code) { using boost::asio::buffer; @@ -250,8 +250,8 @@ read_fh2(frame_header& fh, Streambuf& sb, case 126: { std::uint8_t b[2]; - assert(buffer_size(sb.data()) >= sizeof(b)); - sb.consume(buffer_copy(buffer(b), sb.data())); + assert(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); fh.len = big_uint16_to_native(&b[0]); // length not canonical if(fh.len < 126) @@ -264,8 +264,8 @@ read_fh2(frame_header& fh, Streambuf& sb, case 127: { std::uint8_t b[8]; - assert(buffer_size(sb.data()) >= sizeof(b)); - sb.consume(buffer_copy(buffer(b), sb.data())); + assert(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); fh.len = big_uint64_to_native(&b[0]); // length not canonical if(fh.len < 65536) @@ -279,8 +279,8 @@ read_fh2(frame_header& fh, Streambuf& sb, if(fh.mask) { std::uint8_t b[4]; - assert(buffer_size(sb.data()) >= sizeof(b)); - sb.consume(buffer_copy(buffer(b), sb.data())); + assert(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); fh.key = little_uint32_to_native(&b[0]); } else diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index daecf145d..5d66747fb 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -107,15 +106,13 @@ protected: void prepare_fh(close_code::value& code); - template + template void - write_close(Streambuf& sb, - close_reason const& rc); + write_close(DynamicBuffer& db, close_reason const& rc); - template + template void - write_ping(Streambuf& sb, opcode op, - ping_data const& data); + write_ping(DynamicBuffer& db, opcode op, ping_data const& data); }; } // detail diff --git a/include/beast/websocket/impl/read_frame_op.ipp b/include/beast/websocket/impl/read_frame_op.ipp index 776d88ca9..6102e1145 100644 --- a/include/beast/websocket/impl/read_frame_op.ipp +++ b/include/beast/websocket/impl/read_frame_op.ipp @@ -23,7 +23,7 @@ namespace websocket { // processes any received control frames. // template -template +template class stream::read_frame_op { using alloc_type = @@ -35,27 +35,27 @@ class stream::read_frame_op using fmb_type = typename fb_type::mutable_buffers_type; - using smb_type = - typename Streambuf::mutable_buffers_type; + using dmb_type = + typename DynamicBuffer::mutable_buffers_type; struct data : op { stream& ws; frame_info& fi; - Streambuf& sb; + DynamicBuffer& db; Handler h; fb_type fb; - boost::optional smb; + boost::optional dmb; boost::optional fmb; bool cont; int state = 0; template data(DeducedHandler&& h_, stream& ws_, - frame_info& fi_, Streambuf& sb_) + frame_info& fi_, DynamicBuffer& sb_) : ws(ws_) , fi(fi_) - , sb(sb_) + , db(sb_) , h(std::forward(h_)) , cont(boost_asio_handler_cont_helpers:: is_continuation(h)) @@ -127,9 +127,9 @@ public: }; template -template +template void -stream::read_frame_op:: +stream::read_frame_op:: operator()(error_code ec, std::size_t bytes_transferred) { auto& d = *d_; @@ -139,9 +139,9 @@ operator()(error_code ec, std::size_t bytes_transferred) } template -template +template void -stream::read_frame_op:: +stream::read_frame_op:: operator()(error_code ec,std::size_t bytes_transferred, bool again) { enum @@ -187,18 +187,18 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) case do_read_payload: d.state = do_read_payload + 1; - d.smb = d.sb.prepare( + d.dmb = d.db.prepare( detail::clamp(d.ws.rd_need_)); // receive payload data d.ws.stream_.async_read_some( - *d.smb, std::move(*this)); + *d.dmb, std::move(*this)); return; case do_read_payload + 1: { d.ws.rd_need_ -= bytes_transferred; auto const pb = prepare_buffers( - bytes_transferred, *d.smb); + bytes_transferred, *d.dmb); if(d.ws.rd_fh_.mask) detail::mask_inplace(pb, d.ws.rd_key_); if(d.ws.rd_opcode_ == opcode::text) @@ -213,7 +213,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) break; } } - d.sb.commit(bytes_transferred); + d.db.commit(bytes_transferred); if(d.ws.rd_need_ > 0) { d.state = do_read_payload; diff --git a/include/beast/websocket/impl/read_op.ipp b/include/beast/websocket/impl/read_op.ipp index 89f840e22..e00d19d3e 100644 --- a/include/beast/websocket/impl/read_op.ipp +++ b/include/beast/websocket/impl/read_op.ipp @@ -17,7 +17,7 @@ namespace websocket { // read an entire message // template -template +template class stream::read_op { using alloc_type = @@ -27,7 +27,7 @@ class stream::read_op { stream& ws; opcode& op; - Streambuf& sb; + DynamicBuffer& db; Handler h; frame_info fi; bool cont; @@ -36,10 +36,10 @@ class stream::read_op template data(DeducedHandler&& h_, stream& ws_, opcode& op_, - Streambuf& sb_) + DynamicBuffer& sb_) : ws(ws_) , op(op_) - , sb(sb_) + , db(sb_) , h(std::forward(h_)) , cont(boost_asio_handler_cont_helpers:: is_continuation(h)) @@ -98,9 +98,9 @@ public: }; template -template +template void -stream::read_op:: +stream::read_op:: operator()(error_code const& ec, bool again) { auto& d = *d_; @@ -117,9 +117,9 @@ operator()(error_code const& ec, bool again) // the handler is moved from the data block // before asio_handler_deallocate is called. d.ws.async_read_frame( - d.fi, d.sb, std::move(*this)); + d.fi, d.db, std::move(*this)); #else - d.ws.async_read_frame(d.fi, d.sb, *this); + d.ws.async_read_frame(d.fi, d.db, *this); #endif return; diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index c331cdd31..874081df3 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -94,10 +93,10 @@ stream_base::prepare_fh(close_code::value& code) } } -template +template void stream_base::write_close( - Streambuf& sb, close_reason const& cr) + DynamicBuffer& db, close_reason const& cr) { using namespace boost::endian; frame_header fh; @@ -111,7 +110,7 @@ stream_base::write_close( fh.mask = role_ == detail::role_type::client; if(fh.mask) fh.key = maskgen_(); - detail::write(sb, fh); + detail::write(db, fh); if(cr.code != close_code::none) { detail::prepared_key_type key; @@ -121,29 +120,29 @@ stream_base::write_close( std::uint8_t b[2]; ::new(&b[0]) big_uint16_buf_t{ (std::uint16_t)cr.code}; - auto d = sb.prepare(2); + auto d = db.prepare(2); boost::asio::buffer_copy(d, boost::asio::buffer(b)); if(fh.mask) detail::mask_inplace(d, key); - sb.commit(2); + db.commit(2); } if(! cr.reason.empty()) { - auto d = sb.prepare(cr.reason.size()); + auto d = db.prepare(cr.reason.size()); boost::asio::buffer_copy(d, boost::asio::const_buffer( cr.reason.data(), cr.reason.size())); if(fh.mask) detail::mask_inplace(d, key); - sb.commit(cr.reason.size()); + db.commit(cr.reason.size()); } } } -template +template void -stream_base::write_ping(Streambuf& sb, +stream_base::write_ping(DynamicBuffer& db, opcode op, ping_data const& data) { frame_header fh; @@ -156,19 +155,19 @@ stream_base::write_ping(Streambuf& sb, fh.mask = role_ == role_type::client; if(fh.mask) fh.key = maskgen_(); - detail::write(sb, fh); + detail::write(db, fh); if(data.empty()) return; detail::prepared_key_type key; if(fh.mask) detail::prepare_key(key, fh.key); - auto d = sb.prepare(data.size()); + auto d = db.prepare(data.size()); boost::asio::buffer_copy(d, boost::asio::const_buffers_1( data.data(), data.size())); if(fh.mask) detail::mask_inplace(d, key); - sb.commit(data.size()); + db.commit(data.size()); } } // detail @@ -453,10 +452,10 @@ void stream:: ping(ping_data const& payload, error_code& ec) { - detail::frame_streambuf sb; + detail::frame_streambuf db; write_ping( - sb, opcode::ping, payload); - boost::asio::write(stream_, sb.data(), ec); + db, opcode::ping, payload); + boost::asio::write(stream_, db.data(), ec); } template @@ -477,31 +476,35 @@ async_ping(ping_data const& payload, PingHandler&& handler) } template -template +template void stream:: -read(opcode& op, Streambuf& streambuf) +read(opcode& op, DynamicBuffer& dynabuf) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); error_code ec; - read(op, streambuf, ec); + read(op, dynabuf, ec); if(ec) throw system_error{ec}; } template -template +template void stream:: -read(opcode& op, Streambuf& streambuf, error_code& ec) +read(opcode& op, DynamicBuffer& dynabuf, error_code& ec) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); frame_info fi; for(;;) { - read_frame(fi, streambuf, ec); + read_frame(fi, dynabuf, ec); if(ec) break; op = fi.op; @@ -511,47 +514,51 @@ read(opcode& op, Streambuf& streambuf, error_code& ec) } template -template +template typename async_completion< ReadHandler, void(error_code)>::result_type stream:: async_read(opcode& op, - Streambuf& streambuf, ReadHandler&& handler) + DynamicBuffer& dynabuf, ReadHandler&& handler) { static_assert(is_AsyncStream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); beast::async_completion< ReadHandler, void(error_code) > completion(handler); - read_op{ - completion.handler, *this, op, streambuf}; + read_op{ + completion.handler, *this, op, dynabuf}; return completion.result.get(); } template -template +template void stream:: -read_frame(frame_info& fi, Streambuf& streambuf) +read_frame(frame_info& fi, DynamicBuffer& dynabuf) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); error_code ec; - read_frame(fi, streambuf, ec); + read_frame(fi, dynabuf, ec); if(ec) throw system_error{ec}; } template -template +template void stream:: -read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) +read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); close_code::value code{}; for(;;) { @@ -630,7 +637,7 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) } } // read payload - auto smb = streambuf.prepare( + auto smb = dynabuf.prepare( detail::clamp(rd_need_)); auto const bytes_transferred = stream_.read_some(smb, ec); @@ -652,7 +659,7 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) break; } } - streambuf.commit(bytes_transferred); + dynabuf.commit(bytes_transferred); fi.op = rd_opcode_; fi.fin = rd_fh_.fin && rd_need_ == 0; return; @@ -686,21 +693,21 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) } template -template +template typename async_completion< ReadHandler, void(error_code)>::result_type stream:: async_read_frame(frame_info& fi, - Streambuf& streambuf, ReadHandler&& handler) + DynamicBuffer& dynabuf, ReadHandler&& handler) { static_assert(is_AsyncStream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_Streambuf::value, - "Streambuf requirements not met"); + static_assert(beast::is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); beast::async_completion< ReadHandler, void(error_code)> completion(handler); - read_frame_op{ - completion.handler, *this, fi, streambuf}; + read_frame_op{ + completion.handler, *this, fi, dynabuf}; return completion.result.get(); } @@ -712,6 +719,9 @@ write(ConstBufferSequence const& buffers) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); error_code ec; write(buffers, ec); if(ec) @@ -774,6 +784,9 @@ write_frame(bool fin, ConstBufferSequence const& buffers) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); error_code ec; write_frame(fin, buffers, ec); if(ec) diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 83699916a..dcc5f75d0 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -78,7 +78,7 @@ struct frame_info @par Concepts @b `AsyncStream`, @b `Decorator`, - @b `Streambuf`, + @b `DynamicBuffer`, @b `SyncStream` */ template @@ -86,7 +86,7 @@ class stream : public detail::stream_base { friend class stream_test; - streambuf_readstream stream_; + dynabuf_readstream stream_; public: /// The type of the next layer. @@ -567,7 +567,7 @@ public: @throws boost::system::system_error Thrown on failure. */ - // VFALCO TODO This should also take a streambuf with any leftover bytes. + // VFALCO TODO This should also take a DynamicBuffer with any leftover bytes. template void accept(http::request_v1 const& request); @@ -1007,14 +1007,14 @@ public: @param op A value to receive the message type. This object must remain valid until the handler is called. - @param streambuf A stream buffer to hold the message data. - This object must remain valid until the handler is called. + @param dynabuf A dynamic buffer to hold the message data after + any masking or decompression has been applied. @throws boost::system::system_error Thrown on failure. */ - template + template void - read(opcode& op, Streambuf& streambuf); + read(opcode& op, DynamicBuffer& dynabuf); /** Read a message from the stream. @@ -1042,15 +1042,14 @@ public: @param op A value to receive the message type. This object must remain valid until the handler is called. - @param streambuf A stream buffer to hold the message data. - This object must remain valid until the handler is called. + @param dynabuf A dynamic buffer to hold the message data after + any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. */ - template + template void - read(opcode& op, - Streambuf& streambuf, error_code& ec); + read(opcode& op, DynamicBuffer& dynabuf, error_code& ec); /** Start an asynchronous operation to read a message from the stream. @@ -1086,8 +1085,9 @@ public: @param op A value to receive the message type. This object must remain valid until the handler is called. - @param streambuf A stream buffer to hold the message data. - This object must remain valid until the handler is called. + @param dynabuf A dynamic buffer to hold the message data after + any masking or decompression has been applied. This object must + remain valid until the handler is called. @param handler The handler to be called when the read operation completes. Copies will be made of the handler as required. The @@ -1102,15 +1102,14 @@ public: this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ - template + template #if GENERATING_DOCS void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif - async_read(opcode& op, - Streambuf& streambuf, ReadHandler&& handler); + async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); /** Read a message frame from the stream. @@ -1141,13 +1140,14 @@ public: @param fi An object to store metadata about the message. - @param streambuf A stream buffer to hold the message data. + @param dynabuf A dynamic buffer to hold the message data after + any masking or decompression has been applied. @throws boost::system::system_error Thrown on failure. */ - template + template void - read_frame(frame_info& fi, Streambuf& streambuf); + read_frame(frame_info& fi, DynamicBuffer& dynabuf); /** Read a message frame from the stream. @@ -1178,13 +1178,14 @@ public: @param fi An object to store metadata about the message. - @param streambuf A stream buffer to hold the message data. + @param dynabuf A dynamic buffer to hold the message data after + any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. */ - template + template void - read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec); + read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec); /** Start an asynchronous operation to read a message frame from the stream. @@ -1225,7 +1226,7 @@ public: @param fi An object to store metadata about the message. This object must remain valid until the handler is called. - @param streambuf A stream buffer to hold the message data after + @param dynabuf A dynamic buffer to hold the message data after any masking or decompression has been applied. This object must remain valid until the handler is called. @@ -1242,7 +1243,7 @@ public: this function. Invocation of the handler will be performed in a manner equivalent to using boost::asio::io_service::post(). */ - template + template #if GENERATING_DOCS void_or_deduced #else @@ -1250,7 +1251,7 @@ public: ReadHandler, void(error_code)>::result_type #endif async_read_frame(frame_info& fi, - Streambuf& streambuf, ReadHandler&& handler); + DynamicBuffer& dynabuf, ReadHandler&& handler); /** Write a message to the stream. @@ -1495,8 +1496,8 @@ private: template class response_op; template class write_op; template class write_frame_op; - template class read_op; - template class read_frame_op; + template class read_op; + template class read_frame_op; void reset(); diff --git a/test/Jamfile b/test/Jamfile index 9cd423b4e..508b6e06e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -21,6 +21,7 @@ unit-test core-tests : core/buffer_concepts.cpp core/buffers_adapter.cpp core/consuming_buffers.cpp + core/dynabuf_readstream.cpp core/error.cpp core/handler_alloc.cpp core/handler_concepts.cpp @@ -30,9 +31,8 @@ unit-test core-tests : core/static_string.cpp core/stream_concepts.cpp core/streambuf.cpp - core/streambuf_readstream.cpp core/to_string.cpp - core/write_streambuf.cpp + core/write_dynabuf.cpp core/detail/base64.cpp core/detail/empty_base_optimization.cpp core/detail/get_lowest_layer.cpp @@ -41,6 +41,7 @@ unit-test core-tests : unit-test http-tests : ../extras/beast/unit_test/main.cpp + http/basic_dynabuf_body.cpp http/basic_headers.cpp http/basic_parser_v1.cpp http/body_type.cpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 530a60e86..3cc71b05d 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable (core-tests buffer_concepts.cpp buffers_adapter.cpp consuming_buffers.cpp + dynabuf_readstream.cpp error.cpp handler_alloc.cpp handler_concepts.cpp @@ -24,9 +25,8 @@ add_executable (core-tests static_string.cpp stream_concepts.cpp streambuf.cpp - streambuf_readstream.cpp to_string.cpp - write_streambuf.cpp + write_dynabuf.cpp detail/base64.cpp detail/empty_base_optimization.cpp detail/get_lowest_layer.cpp diff --git a/test/core/streambuf_readstream.cpp b/test/core/dynabuf_readstream.cpp similarity index 86% rename from test/core/streambuf_readstream.cpp rename to test/core/dynabuf_readstream.cpp index 9481cb51e..97af7455e 100644 --- a/test/core/streambuf_readstream.cpp +++ b/test/core/dynabuf_readstream.cpp @@ -6,7 +6,7 @@ // // Test that header file is self-contained. -#include +#include #include #include @@ -17,11 +17,11 @@ namespace beast { -class streambuf_readstream_test +class dynabuf_readstream_test : public unit_test::suite , public test::enable_yield_to { - using self = streambuf_readstream_test; + using self = dynabuf_readstream_test; public: void testSpecialMembers() @@ -29,16 +29,16 @@ public: using socket_type = boost::asio::ip::tcp::socket; boost::asio::io_service ios; { - streambuf_readstream srs(ios); - streambuf_readstream srs2(std::move(srs)); + dynabuf_readstream srs(ios); + dynabuf_readstream srs2(std::move(srs)); srs = std::move(srs2); expect(&srs.get_io_service() == &ios); expect(&srs.get_io_service() == &srs2.get_io_service()); } { socket_type sock(ios); - streambuf_readstream srs(sock); - streambuf_readstream srs2(std::move(srs)); + dynabuf_readstream srs(sock); + dynabuf_readstream srs2(std::move(srs)); } } @@ -55,7 +55,7 @@ public: { test::fail_stream< test::string_stream> fs(n, ios_, ", world!"); - streambuf_readstream< + dynabuf_readstream< decltype(fs)&, streambuf> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); @@ -73,7 +73,7 @@ public: { test::fail_stream< test::string_stream> fs(n, ios_, ", world!"); - streambuf_readstream< + dynabuf_readstream< decltype(fs)&, streambuf> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( @@ -92,7 +92,7 @@ public: { test::fail_stream< test::string_stream> fs(n, ios_, ", world!"); - streambuf_readstream< + dynabuf_readstream< decltype(fs)&, streambuf> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); @@ -111,7 +111,7 @@ public: { test::fail_stream< test::string_stream> fs(n, ios_, ", world!"); - streambuf_readstream< + dynabuf_readstream< decltype(fs)&, streambuf> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( @@ -137,7 +137,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(streambuf_readstream,core,beast); +BEAST_DEFINE_TESTSUITE(dynabuf_readstream,core,beast); } // beast diff --git a/test/core/write_streambuf.cpp b/test/core/write_dynabuf.cpp similarity index 84% rename from test/core/write_streambuf.cpp rename to test/core/write_dynabuf.cpp index 15417e313..137ea350d 100644 --- a/test/core/write_streambuf.cpp +++ b/test/core/write_dynabuf.cpp @@ -6,14 +6,14 @@ // // Test that header file is self-contained. -#include +#include #include #include namespace beast { -class write_streambuf_test : public beast::unit_test::suite +class write_dynabuf_test : public beast::unit_test::suite { public: void run() override @@ -31,6 +31,6 @@ public: } }; -BEAST_DEFINE_TESTSUITE(write_streambuf,core,beast); +BEAST_DEFINE_TESTSUITE(write_dynabuf,core,beast); } // beast diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index af46ab20c..df4b5ed3d 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -7,6 +7,7 @@ GroupSources(test/http "/") add_executable (http-tests ${BEAST_INCLUDES} ../../extras/beast/unit_test/main.cpp + basic_dynabuf_body.cpp basic_headers.cpp basic_parser_v1.cpp body_type.cpp diff --git a/test/http/basic_dynabuf_body.cpp b/test/http/basic_dynabuf_body.cpp new file mode 100644 index 000000000..31fbd7736 --- /dev/null +++ b/test/http/basic_dynabuf_body.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index b5ef62fde..b1bd23763 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -11,7 +11,7 @@ #include "message_fuzz.hpp" #include -#include +#include #include #include #include diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp index ef130187a..fd29c3931 100644 --- a/test/http/message_fuzz.hpp +++ b/test/http/message_fuzz.hpp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP -#include +#include #include #include #include From 27ca1b26985e9d8e3c359fb63e2f93d56b7e0f2d Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Fri, 3 Jun 2016 15:27:18 +0000 Subject: [PATCH 09/11] Add Gitter badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af2f595af..a94495cd9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Beast -[![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov] +[![Join the chat at https://gitter.im/vinniefalco/Beast](https://badges.gitter.im/vinniefalco/Beast.svg)](https://gitter.im/vinniefalco/Beast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status] +(https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov] (https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![coveralls] (https://coveralls.io/repos/github/vinniefalco/Beast/badge.svg?branch=master)](https://coveralls.io/github/vinniefalco/Beast?branch=master) [![Documentation] (https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License] From c060d0876751ceaea9afab87c5d7e11739892306 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 3 Jun 2016 11:40:55 -0400 Subject: [PATCH 10/11] rfc7230 compliance, limits, and tests for basic_parser_v1: New parser set_option function for controlling independent size limits on headers and body. By default request and response parsers are set up with reasonable limits to prevent resource exhaustion attacks. * Parser adheres strictly to rfc7230 * Increased test coverage * Headers and body maximum size limit options --- CHANGELOG | 4 + TODO.txt | 8 + doc/quickref.xml | 5 + extras/beast/test/fail_counter.hpp | 86 +- extras/beast/test/fail_stream.hpp | 7 +- include/beast/http/basic_parser_v1.hpp | 314 ++-- include/beast/http/detail/basic_parser_v1.hpp | 207 +-- include/beast/http/detail/rfc7230.hpp | 127 +- include/beast/http/impl/basic_parser_v1.ipp | 887 +++++----- include/beast/http/parse_error.hpp | 30 +- include/beast/http/parser_v1.hpp | 2 +- test/http/CMakeLists.txt | 3 + test/http/basic_parser_v1.cpp | 1546 ++++++++++------- test/http/fail_parser.hpp | 112 ++ test/http/parse_error.cpp | 2 +- test/http/read.cpp | 73 +- 16 files changed, 2093 insertions(+), 1320 deletions(-) create mode 100644 test/http/fail_parser.hpp diff --git a/CHANGELOG b/CHANGELOG index ffce41c66..0eacc6fef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ * Remove obsolete RFC2616 functions * Add message swap members and free functions * Add HTTP field value parser containers: ext_list, param_list, token_list +* Fixes for some corner cases in basic_parser_v1 +* Configurable limits on headers and body sizes in basic_parser_v1 API Changes: @@ -14,4 +16,6 @@ API Changes: * "DynamicBuffer","dynabuf" renamed from "Streambuf", "streambuf". See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4478.html#requirements.dynamic_buffers +* basic_parser_v1 adheres to rfc7230 as strictly as possible + -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index 3b66fc113..0282538b2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -46,3 +46,11 @@ HTTP: * Check basic_parser_v1 against rfc7230 for leading message whitespace * Fix the order of message constructor parameters: body first then headers (since body is constructed with arguments more often) +* Unit tests for char tables +* Remove status_code() from API when isRequest==true, et. al. +* Permit sending trailers and parameters in chunk-encoding chunks + +Future: + +* SOCKS proxy client and server implementations + diff --git a/doc/quickref.xml b/doc/quickref.xml index 3368eedc1..d025b3567 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -39,6 +39,11 @@ streambuf_body string_body + Options + + body_max_size + headers_max_size + Type Traits is_Body diff --git a/extras/beast/test/fail_counter.hpp b/extras/beast/test/fail_counter.hpp index 06500461c..28e1c90c9 100644 --- a/extras/beast/test/fail_counter.hpp +++ b/extras/beast/test/fail_counter.hpp @@ -13,10 +13,78 @@ namespace beast { namespace test { +enum error +{ + fail_error = 1 +}; + +namespace detail { + +class fail_error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "test"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + default: + case error::fail_error: + return "test error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition{ev, *this}; + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_error_category() +{ + static fail_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{static_cast(ev), + detail::get_error_category()}; +} + /** A countdown to simulated failure. On the Nth operation, the class will fail with the specified - error code, or the default error code of invalid_argument. + error code, or the default error code of @ref fail_error. */ class fail_counter { @@ -31,10 +99,10 @@ public: @param The 0-based index of the operation to fail on or after. */ explicit - fail_counter(std::size_t n = 0) + fail_counter(std::size_t n, + error_code ev = make_error_code(fail_error)) : n_(n) - , ec_(boost::system::errc::make_error_code( - boost::system::errc::errc_t::invalid_argument)) + , ec_(ev) { } @@ -66,4 +134,14 @@ public: } // test } // beast +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + #endif diff --git a/extras/beast/test/fail_stream.hpp b/extras/beast/test/fail_stream.hpp index 2bbfaf1e1..1c3a34e26 100644 --- a/extras/beast/test/fail_stream.hpp +++ b/extras/beast/test/fail_stream.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace beast { namespace test { @@ -26,8 +27,8 @@ namespace test { template class fail_stream { + boost::optional fc_; fail_counter* pfc_; - fail_counter fc_; NextLayer next_layer_; public: @@ -46,8 +47,8 @@ public: template explicit fail_stream(std::size_t n, Args&&... args) - : pfc_(&fc_) - , fc_(n) + : fc_(n) + , pfc_(&*fc_) , next_layer_(std::forward(args)...) { } diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp index c0acbf398..871f04cda 100644 --- a/include/beast/http/basic_parser_v1.hpp +++ b/include/beast/http/basic_parser_v1.hpp @@ -25,17 +25,65 @@ namespace http { namespace parse_flag { enum values { - chunked = 1 << 0, - connection_keep_alive = 1 << 1, - connection_close = 1 << 2, - connection_upgrade = 1 << 3, - trailing = 1 << 4, - upgrade = 1 << 5, - skipbody = 1 << 6, - contentlength = 1 << 7 + chunked = 1, + connection_keep_alive = 2, + connection_close = 4, + connection_upgrade = 8, + trailing = 16, + upgrade = 32, + skipbody = 64, + contentlength = 128 }; } // parse_flag +/** Headers maximum size option. + + Sets the maximum number of cumulative bytes allowed + including all header octets. A value of zero indicates + no limit on the number of header octets + + The default headers maximum size is 16KB (16,384 bytes). + + @note Objects of this type are passed to @ref set_option. +*/ +struct headers_max_size +{ + std::size_t value; + + explicit + headers_max_size(std::size_t v) + : value(v) + { + } +}; + +/** Body maximum size option. + + Sets the maximum number of cumulative bytes allowed including + all body octets. Octets in chunk-encoded bodies are counted + after decoding. A value of zero indicates no limit on + the number of body octets. + + The default body maximum size for requests is 4MB (four + megabytes or 4,194,304 bytes) and unlimited for responses. + + @note Objects of this type are passed to @ref set_option. +*/ +struct body_max_size +{ + std::size_t value; + + explicit + body_max_size(std::size_t v) + : value(v) + { + } +}; + +/// The value returned when no content length is known or applicable. +static std::uint64_t constexpr no_content_length = + std::numeric_limits::max(); + /** A parser for decoding HTTP/1 wire format messages. This parser is designed to efficiently parse messages in the @@ -85,7 +133,7 @@ enum values Called for each piece of the current header value. - @li `int on_headers(error_code&)` + @li `int on_headers(std::uint64_t content_length, error_code&)` Called when all the headers have been parsed successfully. @@ -126,90 +174,12 @@ enum values presented with request or response message. */ template -class basic_parser_v1 +class basic_parser_v1 : public detail::parser_base { private: using self = basic_parser_v1; typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); - static std::uint64_t constexpr no_content_length = - std::numeric_limits::max(); - - enum state : std::uint8_t - { - s_closed = 1, - - s_req_start, - s_req_method_start, - s_req_method, - s_req_space_before_url, - s_req_url_start, - s_req_url, - s_req_http_start, - s_req_http_H, - s_req_http_HT, - s_req_http_HTT, - s_req_http_HTTP, - s_req_major_start, - s_req_major, - s_req_minor_start, - s_req_minor, - s_req_line_end, - - s_res_start, - s_res_H, - s_res_HT, - s_res_HTT, - s_res_HTTP, - s_res_major_start, - s_res_major, - s_res_minor_start, - s_res_minor, - s_res_status_code_start, - s_res_status_code, - s_res_status_start, - s_res_status, - s_res_line_almost_done, - s_res_line_done, - - s_header_field_start, - s_header_field, - s_header_value_start, - s_header_value_discard_lWs0, - s_header_value_discard_ws0, - s_header_value_almost_done0, - s_header_value_text_start, - s_header_value_discard_lWs, - s_header_value_discard_ws, - s_header_value_text, - s_header_value_almost_done, - - s_headers_almost_done, - s_headers_done, - - s_chunk_size_start, - s_chunk_size, - s_chunk_parameters, - s_chunk_size_almost_done, - - // states below do not count towards - // the limit on the size of the message - - s_body_identity0, - s_body_identity, - s_body_identity_eof0, - s_body_identity_eof, - - s_chunk_data_start, - s_chunk_data, - s_chunk_data_almost_done, - s_chunk_data_done, - - s_complete, - s_restart, - s_closed_complete - }; - enum field_state : std::uint8_t { h_general = 0, @@ -224,25 +194,36 @@ private: h_matching_upgrade, h_connection, + h_content_length0, h_content_length, + h_content_length_ows, h_transfer_encoding, h_upgrade, h_matching_transfer_encoding_chunked, - h_matching_connection_token_start, + h_matching_transfer_encoding_general, h_matching_connection_keep_alive, h_matching_connection_close, h_matching_connection_upgrade, - h_matching_connection_token, h_transfer_encoding_chunked, + h_transfer_encoding_chunked_ows, + h_connection_keep_alive, + h_connection_keep_alive_ows, h_connection_close, + h_connection_close_ows, h_connection_upgrade, + h_connection_upgrade_ows, + h_connection_token, + h_connection_token_ows }; + std::size_t h_max_; + std::size_t h_left_; + std::size_t b_max_; + std::size_t b_left_; std::uint64_t content_length_; - std::uint64_t nread_; pmf_t cb_; state s_ : 8; unsigned flags_ : 8; @@ -260,10 +241,42 @@ public: /// Copy assignment. basic_parser_v1& operator=(basic_parser_v1 const&) = default; - /// Constructor - basic_parser_v1() + /// Default constructor + basic_parser_v1(); + + /** Set options on the parser. + + @param args One or more parser options to set. + */ +#if GENERATING_DOCS + template + void + set_option(Args&&... args) +#else + template + void + set_option(A1&& a1, A2&& a2, An&&... an) +#endif { - init(std::integral_constant{}); + set_option(std::forward(a1)); + set_option(std::forward(a2), + std::forward(an)...); + } + + /// Set the headers maximum size option + void + set_option(headers_max_size const& o) + { + h_max_ = o.value; + h_left_ = h_max_; + } + + /// Set the body maximum size option + void + set_option(body_max_size const& o) + { + b_max_ = o.value; + b_left_ = b_max_; } /// Returns internal flags associated with the parser. @@ -413,17 +426,48 @@ private: } void - init(std::true_type) + reset(std::true_type) { s_ = s_req_start; } void - init(std::false_type) + reset(std::false_type) { s_ = s_res_start; } + void + reset() + { + h_left_ = h_max_; + b_left_ = b_max_; + reset(std::integral_constant{}); + } + + void + init(std::true_type) + { + // 16KB max headers, 4MB max body + h_max_ = 16 * 1024; + b_max_ = 4 * 1024 * 1024; + } + + void + init(std::false_type) + { + // 16KB max headers, unlimited body + h_max_ = 16 * 1024; + b_max_ = 0; + } + + void + init() + { + init(std::integral_constant{}); + reset(); + } + bool needs_eof(std::true_type) const; @@ -584,7 +628,7 @@ private: { template().on_headers( - std::declval()))>> + std::declval(), std::declval()))>> static R check(int); template static std::false_type check(...); @@ -661,8 +705,16 @@ private: void call_on_method(error_code& ec, boost::string_ref const& s) { - call_on_method(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_method(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_uri(error_code& ec, @@ -678,8 +730,16 @@ private: void call_on_uri(error_code& ec, boost::string_ref const& s) { - call_on_uri(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_uri(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_reason(error_code& ec, @@ -695,8 +755,16 @@ private: void call_on_reason(error_code& ec, boost::string_ref const& s) { - call_on_reason(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_reason(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_request(error_code& ec, std::true_type) @@ -742,7 +810,15 @@ private: void call_on_field(error_code& ec, boost::string_ref const& s) { - call_on_field(ec, s, has_on_field{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_field(ec, s, has_on_field{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_value(error_code& ec, @@ -758,22 +834,32 @@ private: void call_on_value(error_code& ec, boost::string_ref const& s) { - call_on_value(ec, s, has_on_value{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_value(ec, s, has_on_value{}); + } + else + { + ec = parse_error::headers_too_big; + } } - int call_on_headers(error_code& ec, std::true_type) + int call_on_headers(error_code& ec, + std::uint64_t content_length, std::true_type) { - return impl().on_headers(ec); + return impl().on_headers(content_length, ec); } - int call_on_headers(error_code& ec, std::false_type) + int call_on_headers(error_code& ec, std::uint64_t, std::false_type) { return 0; } int call_on_headers(error_code& ec) { - return call_on_headers(ec, has_on_headers{}); + return call_on_headers(ec, content_length_, + has_on_headers{}); } void call_on_body(error_code& ec, @@ -789,7 +875,15 @@ private: void call_on_body(error_code& ec, boost::string_ref const& s) { - call_on_body(ec, s, has_on_body{}); + if(! b_max_ || s.size() <= b_left_) + { + b_left_ -= s.size(); + call_on_body(ec, s, has_on_body{}); + } + else + { + ec = parse_error::body_too_big; + } } void call_on_complete(error_code& ec, std::true_type) diff --git a/include/beast/http/detail/basic_parser_v1.hpp b/include/beast/http/detail/basic_parser_v1.hpp index 7568296d6..196089bca 100644 --- a/include/beast/http/detail/basic_parser_v1.hpp +++ b/include/beast/http/detail/basic_parser_v1.hpp @@ -17,137 +17,6 @@ namespace beast { namespace http { namespace detail { -// '0'...'9' -inline -bool -is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -inline -bool -is_token(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 - }}; - return tab[static_cast(c)] != 0; -} - -inline -bool -is_text(char c) -{ - // TEXT = - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 - }}; - return tab[static_cast(c)] != 0; -} - -// converts to lower case, -// returns 0 if not a valid token char -// -inline -char -to_field_char(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 - }}; - return tab[static_cast(c)]; -} - -// converts to lower case, -// returns 0 if not a valid text char -// -inline -char -to_value_char(char c) -{ - // TEXT = - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 - }}; - return static_cast(tab[static_cast(c)]); -} - -inline -std::int8_t -unhex(char c) -{ - static std::array constexpr tab = {{ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 - }}; - return tab[static_cast(c)]; -} - template struct parser_str_t { @@ -196,6 +65,82 @@ parser_str_t<_>::transfer_encoding[18]; using parser_str = parser_str_t<>; +class parser_base +{ +protected: + enum state : std::uint8_t + { + s_dead = 1, + + s_req_start, + s_req_method0, + s_req_method, + s_req_url0, + s_req_url, + s_req_http, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_major, + s_req_dot, + s_req_minor, + s_req_cr, + s_req_lf, + + s_res_start, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_major, + s_res_dot, + s_res_minor, + s_res_space_1, + s_res_status0, + s_res_status1, + s_res_status2, + s_res_space_2, + s_res_reason0, + s_res_reason, + s_res_line_lf, + s_res_line_done, + + s_header_name0, + s_header_name, + s_header_value0_lf, + s_header_value0_almost_done, + s_header_value0, + s_header_value, + s_header_value_lf, + s_header_value_almost_done, + s_header_value_unfold, + + s_headers_almost_done, + s_headers_done, + + s_chunk_size0, + s_chunk_size, + s_chunk_ext_name0, + s_chunk_ext_name, + s_chunk_ext_val, + s_chunk_size_lf, + s_chunk_data0, + s_chunk_data, + s_chunk_data_cr, + s_chunk_data_lf, + + s_body_identity0, + s_body_identity, + s_body_identity_eof0, + s_body_identity_eof, + + s_complete, + s_restart, + s_closed_complete + }; +}; + } // detail } // http } // beast diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index 64272fdbb..dc9285a5f 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -17,6 +17,56 @@ namespace beast { namespace http { namespace detail { +inline +bool +is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +inline +bool +is_alpha(char c) +{ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 80 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 112 + }}; + return tab[static_cast(c)] != 0; +} + +inline +bool +is_text(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)] != 0; +} + inline bool is_tchar(char c) @@ -27,7 +77,7 @@ is_tchar(char c) "^" | "_" | "`" | "|" | "~" | DIGIT | ALPHA */ - static std::array constexpr tab = {{ + static std::array constexpr tab = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 @@ -37,7 +87,7 @@ is_tchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 }}; - return tab[static_cast(c)]; + return tab[static_cast(c)] != 0; } inline @@ -97,6 +147,79 @@ is_qpchar(char c) return tab[static_cast(c)]; } +// converts to lower case, +// returns 0 if not a valid token char +// +inline +char +to_field_char(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 + }}; + return tab[static_cast(c)]; +} + +// converts to lower case, +// returns 0 if not a valid text char +// +inline +char +to_value_char(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 + }}; + return static_cast(tab[static_cast(c)]); +} + +inline +std::int8_t +unhex(char c) +{ + static std::array constexpr tab = {{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 + }}; + return tab[static_cast(c)]; +} + template void skip_ows(FwdIt& it, FwdIt const& end) diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp index 4e6a67761..918c52cfe 100644 --- a/include/beast/http/impl/basic_parser_v1.ipp +++ b/include/beast/http/impl/basic_parser_v1.ipp @@ -5,21 +5,57 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +/* + This code is a modified version of nodejs/http-parser, copyright above: + https://github.com/nodejs/http-parser +*/ + #ifndef BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP #define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP +#include #include #include namespace beast { namespace http { +template +basic_parser_v1:: +basic_parser_v1() +{ + init(); +} + template bool basic_parser_v1:: keep_alive() const { - if(http_major_ > 0 && http_minor_ > 0) + if(http_major_ >= 1 && http_minor_ >= 1) { if(flags_ & parse_flag::connection_close) return false; @@ -32,8 +68,6 @@ keep_alive() const return ! needs_eof(); } -// Implementation inspired by nodejs/http-parser - template template typename std::enable_if< @@ -61,18 +95,18 @@ basic_parser_v1:: write(boost::asio::const_buffer const& buffer, error_code& ec) { using beast::http::detail::is_digit; - using beast::http::detail::is_token; + using beast::http::detail::is_tchar; using beast::http::detail::is_text; using beast::http::detail::to_field_char; using beast::http::detail::to_value_char; using beast::http::detail::unhex; using boost::asio::buffer_cast; using boost::asio::buffer_size; - + auto const data = buffer_cast(buffer); auto const size = buffer_size(buffer); - if(size == 0 && s_ != s_closed) + if(size == 0 && s_ != s_dead) return 0; auto begin = @@ -86,12 +120,12 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) auto err = [&](parse_error ev) { ec = ev; - s_ = s_closed; + s_ = s_dead; return used(); }; auto errc = [&] { - s_ = s_closed; + s_ = s_dead; return used(); }; auto piece = [&] @@ -118,7 +152,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) redo: switch(s_) { - case s_closed: + case s_dead: case s_closed_complete: return err(parse_error::connection_closed); break; @@ -127,36 +161,33 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) flags_ = 0; cb_ = nullptr; content_length_ = no_content_length; - s_ = s_req_method_start; + s_ = s_req_method0; goto redo; - case s_req_method_start: - if(! is_token(ch)) + case s_req_method0: + if(! is_tchar(ch)) return err(parse_error::bad_method); call_on_start(ec); if(ec) return errc(); - cb_ = &self::call_on_method; + assert(! cb_); + cb(&self::call_on_method); s_ = s_req_method; break; case s_req_method: - if(! is_token(ch)) + if(ch == ' ') { if(cb(nullptr)) return errc(); - s_ = s_req_space_before_url; - goto redo; + s_ = s_req_url0; + break; } + if(! is_tchar(ch)) + return err(parse_error::bad_method); break; - case s_req_space_before_url: - if(ch != ' ') - return err(parse_error::bad_request); - s_ = s_req_url_start; - break; - - case s_req_url_start: + case s_req_url0: { if(ch == ' ') return err(parse_error::bad_uri); @@ -174,7 +205,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_req_http_start; + s_ = s_req_http; break; } // VFALCO TODO Better checking for valid URL characters @@ -182,7 +213,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) return err(parse_error::bad_uri); break; - case s_req_http_start: + case s_req_http: if(ch != 'H') return err(parse_error::bad_version); s_ = s_req_http_H; @@ -209,80 +240,56 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_req_http_HTTP: if(ch != '/') return err(parse_error::bad_version); - s_ = s_req_major_start; - break; - - case s_req_major_start: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; s_ = s_req_major; break; case s_req_major: - if(ch == '.') - { - s_ = s_req_minor_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_major_ = 10 * http_major_ + ch - '0'; - if(http_major_ > 999) - return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_req_dot; break; - case s_req_minor_start: - if(! is_digit(ch)) + case s_req_dot: + if(ch != '.') return err(parse_error::bad_version); - http_minor_ = ch - '0'; s_ = s_req_minor; break; case s_req_minor: - if(ch == '\r') - { - s_ = s_req_line_end; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_minor_ = 10 * http_minor_ + ch - '0'; - if(http_minor_ > 999) - return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_req_cr; break; - case s_req_line_end: + case s_req_cr: + if(ch != '\r') + return err(parse_error::bad_version); + s_ = s_req_lf; + break; + + case s_req_lf: if(ch != '\n') return err(parse_error::bad_crlf); call_on_request(ec); if(ec) return errc(); - s_ = s_header_field_start; + s_ = s_header_name0; break; - //-------------------------------------------- + //---------------------------------------------------------------------- case s_res_start: flags_ = 0; cb_ = nullptr; content_length_ = no_content_length; - switch(ch) - { - case 'H': - call_on_start(ec); - if(ec) - return errc(); - s_ = s_res_H; - break; - // VFALCO NOTE this allows whitespace at the beginning, - // need to check rfc7230 - case '\r': - case '\n': - break; - default: + if(ch != 'H') return err(parse_error::bad_version); - } + call_on_start(ec); + if(ec) + return errc(); + s_ = s_res_H; break; case s_res_H: @@ -306,118 +313,88 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_res_HTTP: if(ch != '/') return err(parse_error::bad_version); - s_ = s_res_major_start; - break; - - case s_res_major_start: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; s_ = s_res_major; break; case s_res_major: - if(ch == '.') - { - s_ = s_res_minor_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_major_ = 10 * http_major_ + ch - '0'; - if(http_major_ > 999) - return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_res_dot; break; - case s_res_minor_start: - if(! is_digit(ch)) + case s_res_dot: + if(ch != '.') return err(parse_error::bad_version); - http_minor_ = ch - '0'; s_ = s_res_minor; break; case s_res_minor: - if(ch == ' ') - { - s_ = s_res_status_code_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_minor_ = 10 * http_minor_ + ch - '0'; - if(http_minor_ > 999) - return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_res_space_1; break; - case s_res_status_code_start: + case s_res_space_1: + if(ch != ' ') + return err(parse_error::bad_version); + s_ = s_res_status0; + break; + + case s_res_status0: if(! is_digit(ch)) - { - if(ch == ' ') - break; - return err(parse_error::bad_status_code); - } + return err(parse_error::bad_status); status_code_ = ch - '0'; - s_ = s_res_status_code; + s_ = s_res_status1; break; - case s_res_status_code: + case s_res_status1: if(! is_digit(ch)) - { - switch(ch) - { - case ' ': s_ = s_res_status_start; break; - case '\r': s_ = s_res_line_almost_done; break; - case '\n': s_ = s_header_field_start; break; - default: - return err(parse_error::bad_status_code); - } - break; - } + return err(parse_error::bad_status); status_code_ = status_code_ * 10 + ch - '0'; - if(status_code_ > 999) - return err(parse_error::bad_status_code); + s_ = s_res_status2; break; - case s_res_status_start: + case s_res_status2: + if(! is_digit(ch)) + return err(parse_error::bad_status); + status_code_ = status_code_ * 10 + ch - '0'; + s_ = s_res_space_2; + break; + + case s_res_space_2: + if(ch != ' ') + return err(parse_error::bad_status); + s_ = s_res_reason0; + break; + + case s_res_reason0: if(ch == '\r') { - s_ = s_res_line_almost_done; - break; - } - // VFALCO Is this up to spec? - if(ch == '\n') - { - s_ = s_header_field_start; + s_ = s_res_line_lf; break; } if(! is_text(ch)) - return err(parse_error::bad_status); - if(cb(&self::call_on_reason)) - return errc(); - pos_ = 0; - s_ = s_res_status; + return err(parse_error::bad_reason); + assert(! cb_); + cb(&self::call_on_reason); + s_ = s_res_reason; break; - case s_res_status: + case s_res_reason: if(ch == '\r') { if(cb(nullptr)) return errc(); - s_ = s_res_line_almost_done; - break; - } - if(ch == '\n') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_field_start; + s_ = s_res_line_lf; break; } if(! is_text(ch)) - return err(parse_error::bad_status); + return err(parse_error::bad_reason); break; - case s_res_line_almost_done: + case s_res_line_lf: if(ch != '\n') return err(parse_error::bad_crlf); s_ = s_res_line_done; @@ -427,15 +404,12 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) call_on_response(ec); if(ec) return errc(); - s_ = s_header_field_start; + s_ = s_header_name0; goto redo; - //-------------------------------------------- + //---------------------------------------------------------------------- - // message-header = field-name ":" [ field-value ] - // field-name = token - - case s_header_field_start: + case s_header_name0: { if(ch == '\r') { @@ -457,21 +431,23 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) } assert(! cb_); cb(&self::call_on_field); - s_ = s_header_field; + s_ = s_header_name; break; } - case s_header_field: + case s_header_name: { for(; p != end; ++p) { ch = *p; auto c = to_field_char(ch); - if(! c) - break; + if(! c) + break; switch(fs_) { - case h_general: break; + default: + case h_general: + break; case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; case h_CON: @@ -487,8 +463,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_connection: ++pos_; - if(pos_ >= sizeof(detail::parser_str::connection)-1 || - c != detail::parser_str::connection[pos_]) + if(c != detail::parser_str::connection[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::connection)-2) fs_ = h_connection; @@ -496,8 +471,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_proxy_connection: ++pos_; - if(pos_ >= sizeof(detail::parser_str::proxy_connection)-1 || - c != detail::parser_str::proxy_connection[pos_]) + if(c != detail::parser_str::proxy_connection[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) fs_ = h_connection; @@ -505,22 +479,19 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_content_length: ++pos_; - if(pos_ >= sizeof(detail::parser_str::content_length)-1 || - c != detail::parser_str::content_length[pos_]) + if(c != detail::parser_str::content_length[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::content_length)-2) { if(flags_ & parse_flag::contentlength) return err(parse_error::bad_content_length); - fs_ = h_content_length; - flags_ |= parse_flag::contentlength; + fs_ = h_content_length0; } break; case h_matching_transfer_encoding: ++pos_; - if(pos_ >= sizeof(detail::parser_str::transfer_encoding)-1 || - c != detail::parser_str::transfer_encoding[pos_]) + if(c != detail::parser_str::transfer_encoding[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) fs_ = h_transfer_encoding; @@ -528,22 +499,18 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_upgrade: ++pos_; - if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || - c != detail::parser_str::upgrade[pos_]) + if(c != detail::parser_str::upgrade[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::upgrade)-2) fs_ = h_upgrade; break; case h_connection: - case h_content_length: + case h_content_length0: case h_transfer_encoding: case h_upgrade: - // VFALCO Do we allow a space here? fs_ = h_general; break; - default: - break; } } if(p == end) @@ -555,111 +522,39 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_header_value_start; + s_ = s_header_value0; break; } return err(parse_error::bad_field); } - - // field-value = *( field-content | LWS ) - // field-content = *TEXT - // LWS = [CRLF] 1*( SP | HT ) - - case s_header_value_start: - if(ch == '\r') - { - s_ = s_header_value_discard_lWs0; - break; - } - if(ch == ' ' || ch == '\t') - { - s_ = s_header_value_discard_ws0; - break; - } - s_ = s_header_value_text_start; - goto redo; - - case s_header_value_discard_ws0: + /* + header-field = field-name ":" OWS field-value OWS + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + */ + case s_header_value0: if(ch == ' ' || ch == '\t') break; if(ch == '\r') { - s_ = s_header_value_discard_lWs0; + s_ = s_header_value0_lf; break; } - s_ = s_header_value_text_start; - goto redo; - - case s_header_value_discard_lWs0: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value_almost_done0; - break; - - case s_header_value_almost_done0: - if(ch == ' ' || ch == '\t') + if(fs_ == h_content_length0) { - s_ = s_header_value_discard_ws0; - break; + content_length_ = 0; + flags_ |= parse_flag::contentlength; } - call_on_value(ec, boost::string_ref{"", 0}); - if(ec) - return errc(); - s_ = s_header_field_start; - goto redo; + assert(! cb_); + cb(&self::call_on_value); + s_ = s_header_value; + // fall through - case s_header_value_text_start: - { - auto const c = to_value_char(ch); - if(! c) - return err(parse_error::bad_value); - switch(fs_) - { - case h_upgrade: - flags_ |= parse_flag::upgrade; - fs_ = h_general; - break; - - case h_transfer_encoding: - if(c == 'c') - fs_ = h_matching_transfer_encoding_chunked; - else - fs_ = h_general; - break; - - case h_content_length: - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - content_length_ = ch - '0'; - break; - - case h_connection: - switch(c) - { - case 'k': fs_ = h_matching_connection_keep_alive; break; - case 'c': fs_ = h_matching_connection_close; break; - case 'u': fs_ = h_matching_connection_upgrade; break; - default: - fs_ = h_matching_connection_token; - break; - } - break; - - case h_matching_connection_token_start: - break; - - default: - fs_ = h_general; - break; - } - pos_ = 0; - if(cb(&self::call_on_value)) - return errc(); - s_ = s_header_value_text; - break; - } - - case s_header_value_text: + case s_header_value: { for(; p != end; ++p) { @@ -668,7 +563,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_header_value_discard_lWs; + s_ = s_header_value_lf; break; } auto const c = to_value_char(ch); @@ -677,16 +572,165 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) switch(fs_) { case h_general: + default: break; case h_connection: - case h_transfer_encoding: - assert(0); + switch(c) + { + case 'k': + pos_ = 0; + fs_ = h_matching_connection_keep_alive; + break; + case 'c': + pos_ = 0; + fs_ = h_matching_connection_close; + break; + case 'u': + pos_ = 0; + fs_ = h_matching_connection_upgrade; + break; + default: + if(ch == ' ' || ch == '\t' || ch == ',') + break; + if(! is_tchar(ch)) + return err(parse_error::bad_value); + fs_ = h_connection_token; + break; + } + break; + + case h_matching_connection_keep_alive: + ++pos_; + if(c != detail::parser_str::keep_alive[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::keep_alive)- 2) + fs_ = h_connection_keep_alive; + break; + + case h_matching_connection_close: + ++pos_; + if(c != detail::parser_str::close[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::close)-2) + fs_ = h_connection_close; + break; + + case h_matching_connection_upgrade: + ++pos_; + if(c != detail::parser_str::upgrade[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_connection_upgrade; + break; + + case h_connection_close: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_close; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_close_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_close_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_close; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_keep_alive: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_keep_alive; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_keep_alive_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_keep_alive_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_keep_alive; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_upgrade: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_upgrade; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_upgrade_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_upgrade_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_upgrade; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_token: + if(ch == ',') + fs_ = h_connection; + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_token_ows; + else if(! is_tchar(ch)) + return err(parse_error::bad_value); + break; + + case h_connection_token_ows: + if(ch == ',') + { + fs_ = h_connection; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_content_length0: + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + content_length_ = ch - '0'; + fs_ = h_content_length; break; case h_content_length: if(ch == ' ' || ch == '\t') + { + fs_ = h_content_length_ows; break; + } if(! is_digit(ch)) return err(parse_error::bad_content_length); if(content_length_ > (no_content_length - 10) / 10) @@ -695,92 +739,44 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) content_length_ * 10 + ch - '0'; break; + case h_content_length_ows: + if(ch != ' ' && ch != '\t') + return err(parse_error::bad_content_length); + break; + + case h_transfer_encoding: + if(c == 'c') + { + pos_ = 0; + fs_ = h_matching_transfer_encoding_chunked; + } + else if(c != ' ' && c != '\t' && c != ',') + { + fs_ = h_matching_transfer_encoding_general; + } + break; + case h_matching_transfer_encoding_chunked: ++pos_; - if(pos_ >= sizeof(detail::parser_str::chunked)-1 || - c != detail::parser_str::chunked[pos_]) - fs_ = h_general; + if(c != detail::parser_str::chunked[pos_]) + fs_ = h_matching_transfer_encoding_general; else if(pos_ == sizeof(detail::parser_str::chunked)-2) fs_ = h_transfer_encoding_chunked; break; - case h_matching_connection_token_start: - switch(c) - { - case 'k': fs_ = h_matching_connection_keep_alive; break; - case 'c': fs_ = h_matching_connection_close; break; - case 'u': fs_ = h_matching_connection_upgrade; break; - default: - if(is_token(c)) - fs_ = h_matching_connection_token; - else if(ch == ' ' || ch == '\t') - { } - else - fs_ = h_general; - break; - } - break; - - case h_matching_connection_keep_alive: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::keep_alive)-1 || - c != detail::parser_str::keep_alive[pos_]) - fs_ = h_matching_connection_token; - else if (pos_ == sizeof(detail::parser_str::keep_alive)- 2) - fs_ = h_connection_keep_alive; - break; - - case h_matching_connection_close: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::close)-1 || - c != detail::parser_str::close[pos_]) - fs_ = h_matching_connection_token; - else if(pos_ == sizeof(detail::parser_str::close)-2) - fs_ = h_connection_close; - break; - - case h_matching_connection_upgrade: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || - c != detail::parser_str::upgrade[pos_]) - fs_ = h_matching_connection_token; - else if (pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_connection_upgrade; - break; - - case h_matching_connection_token: - if(ch == ',') - { - fs_ = h_matching_connection_token_start; - pos_ = 0; - } + case h_matching_transfer_encoding_general: + if(c == ',') + fs_ = h_transfer_encoding; break; case h_transfer_encoding_chunked: - if(ch != ' ' && ch != '\t') - fs_ = h_general; + if(c != ' ' && c != '\t' && c != ',') + fs_ = h_transfer_encoding; break; - case h_connection_keep_alive: - case h_connection_close: - case h_connection_upgrade: - if(ch ==',') - { - if(fs_ == h_connection_keep_alive) - flags_ |= parse_flag::connection_keep_alive; - else if(fs_ == h_connection_close) - flags_ |= parse_flag::connection_close; - else if(fs_ == h_connection_upgrade) - flags_ |= parse_flag::connection_upgrade; - fs_ = h_matching_connection_token_start; - pos_ = 0; - } - else if(ch != ' ' && ch != '\t') - { - fs_ = h_matching_connection_token; - } - break; - default: + case h_upgrade: + flags_ |= parse_flag::upgrade; + fs_ = h_general; break; } } @@ -789,25 +785,30 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) break; } - case s_header_value_discard_ws: - if(ch == ' ' || ch == '\t') - break; - if(ch == '\r') - { - s_ = s_header_value_discard_lWs; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_value); - call_on_value(ec, boost::string_ref(" ", 1)); - if(ec) - return errc(); - if(cb(&self::call_on_value)) - return errc(); - s_ = s_header_value_text; + case s_header_value0_lf: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value0_almost_done; break; - case s_header_value_discard_lWs: + case s_header_value0_almost_done: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value0; + break; + } + if(fs_ == h_content_length0) + return err(parse_error::bad_content_length); + if(fs_ == h_upgrade) + flags_ |= parse_flag::upgrade; + assert(! cb_); + call_on_value(ec, boost::string_ref{"", 0}); + if(ec) + return errc(); + s_ = s_header_name0; + goto redo; + + case s_header_value_lf: if(ch != '\n') return err(parse_error::bad_crlf); s_ = s_header_value_almost_done; @@ -816,19 +817,72 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_header_value_almost_done: if(ch == ' ' || ch == '\t') { - s_ = s_header_value_discard_ws; + switch(fs_) + { + case h_matching_connection_keep_alive: + case h_matching_connection_close: + case h_matching_connection_upgrade: + fs_ = h_connection_token_ows; + break; + + case h_connection_close: + fs_ = h_connection_close_ows; + break; + + case h_connection_keep_alive: + fs_ = h_connection_keep_alive_ows; + break; + + case h_connection_upgrade: + fs_ = h_connection_upgrade_ows; + break; + + case h_content_length: + fs_ = h_content_length_ows; + break; + + case h_matching_transfer_encoding_chunked: + fs_ = h_matching_transfer_encoding_general; + break; + + default: + break; + } + call_on_value(ec, boost::string_ref(" ", 1)); + s_ = s_header_value_unfold; break; } switch(fs_) { - case h_connection_keep_alive: flags_ |= parse_flag::connection_keep_alive; break; - case h_connection_close: flags_ |= parse_flag::connection_close; break; - case h_transfer_encoding_chunked: flags_ |= parse_flag::chunked; break; - case h_connection_upgrade: flags_ |= parse_flag::connection_upgrade; break; + case h_connection_keep_alive: + case h_connection_keep_alive_ows: + flags_ |= parse_flag::connection_keep_alive; + break; + case h_connection_close: + case h_connection_close_ows: + flags_ |= parse_flag::connection_close; + break; + + case h_connection_upgrade: + case h_connection_upgrade_ows: + flags_ |= parse_flag::connection_upgrade; + break; + + case h_transfer_encoding_chunked: + case h_transfer_encoding_chunked_ows: + flags_ |= parse_flag::chunked; + break; + default: break; } - s_ = s_header_field_start; + s_ = s_header_name0; + goto redo; + + case s_header_value_unfold: + assert(! cb_); + cb(&self::call_on_value); + s_ = s_header_value; goto redo; case s_headers_almost_done: @@ -850,9 +904,14 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) return errc(); switch(maybe_skip) { - case 0: break; - case 2: upgrade_ = true; // fall through - case 1: flags_ |= parse_flag::skipbody; break; + case 0: + break; + case 2: + upgrade_ = true; + // fall through + case 1: + flags_ |= parse_flag::skipbody; + break; default: return err(parse_error::bad_on_headers_rv); } @@ -879,13 +938,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) } else if(flags_ & parse_flag::chunked) { - s_ = s_chunk_size_start; + s_ = s_chunk_size0; break; } - else if(content_length_ == 0) - { - s_ = s_complete; - } else if(content_length_ != no_content_length) { s_ = s_body_identity0; @@ -907,7 +962,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) assert(! cb_); cb(&self::call_on_body); s_ = s_body_identity; - goto redo; // VFALCO fall through? + // fall through case s_body_identity: { @@ -932,13 +987,13 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) assert(! cb_); cb(&self::call_on_body); s_ = s_body_identity_eof; - goto redo; // VFALCO fall through? + // fall through case s_body_identity_eof: p = end - 1; break; - case s_chunk_size_start: + case s_chunk_size0: { auto v = unhex(ch); if(v == -1) @@ -947,23 +1002,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) s_ = s_chunk_size; break; } + case s_chunk_size: { if(ch == '\r') { - s_ = s_chunk_size_almost_done; + s_ = s_chunk_size_lf; + break; + } + if(ch == ';') + { + s_ = s_chunk_ext_name0; break; } auto v = unhex(ch); if(v == -1) - { - if(ch == ';' || ch == ' ') - { - s_ = s_chunk_parameters; - break; - } return err(parse_error::invalid_chunk_size); - } if(content_length_ > (no_content_length - 16) / 16) return err(parse_error::bad_content_length); content_length_ = @@ -971,29 +1025,54 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) break; } - case s_chunk_parameters: + case s_chunk_ext_name0: + if(! is_tchar(ch)) + return err(parse_error::invalid_ext_name); + s_ = s_chunk_ext_name; + break; + + case s_chunk_ext_name: if(ch == '\r') { - s_ = s_chunk_size_almost_done; + s_ = s_chunk_size_lf; + break; + } + if(ch == '=') + { + s_ = s_chunk_ext_val; + break; + } + if(ch == ';') + { + s_ = s_chunk_ext_name0; + break; + } + if(! is_tchar(ch)) + return err(parse_error::invalid_ext_name); + break; + + case s_chunk_ext_val: + if(ch == '\r') + { + s_ = s_chunk_size_lf; break; } break; - case s_chunk_size_almost_done: + case s_chunk_size_lf: if(ch != '\n') return err(parse_error::bad_crlf); - nread_ = 0; if(content_length_ == 0) { flags_ |= parse_flag::trailing; - s_ = s_header_field_start; + s_ = s_header_name0; break; } //call_chunk_header(ec); if(ec) return errc(); - s_ = s_chunk_data_start; + s_ = s_chunk_data0; break; - case s_chunk_data_start: + case s_chunk_data0: assert(! cb_); cb(&self::call_on_body); s_ = s_chunk_data; @@ -1009,23 +1088,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) content_length_ -= n; p += n - 1; if(content_length_ == 0) - s_ = s_chunk_data_almost_done; + s_ = s_chunk_data_cr; break; } - case s_chunk_data_almost_done: + case s_chunk_data_cr: if(ch != '\r') return err(parse_error::bad_crlf); if(cb(nullptr)) return errc(); - s_ = s_chunk_data_done; + s_ = s_chunk_data_lf; break; - case s_chunk_data_done: + case s_chunk_data_lf: if(ch != '\n') return err(parse_error::bad_crlf); - nread_ = 0; - s_ = s_chunk_size_start; + s_ = s_chunk_size0; break; case s_complete: @@ -1040,9 +1118,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_restart: if(keep_alive()) - init(std::integral_constant{}); + reset(); else - s_ = s_closed; + s_ = s_dead; goto redo; } } @@ -1066,7 +1144,7 @@ write_eof(error_code& ec) s_ = s_closed_complete; break; - case s_closed: + case s_dead: case s_closed_complete: break; @@ -1076,14 +1154,14 @@ write_eof(error_code& ec) call_on_complete(ec); if(ec) { - s_ = s_closed; + s_ = s_dead; break; } s_ = s_closed_complete; break; default: - s_ = s_closed; + s_ = s_dead; ec = parse_error::short_read; break; } @@ -1103,18 +1181,15 @@ basic_parser_v1:: needs_eof(std::false_type) const { // See RFC 2616 section 4.4 - if (status_code_ / 100 == 1 || // 1xx e.g. Continue - status_code_ == 204 || // No Content - status_code_ == 304 || // Not Modified - flags_ & parse_flag::skipbody) // response to a HEAD request - { + if( status_code_ / 100 == 1 || // 1xx e.g. Continue + status_code_ == 204 || // No Content + status_code_ == 304 || // Not Modified + flags_ & parse_flag::skipbody) // response to a HEAD request return false; - } - if((flags_ & parse_flag::chunked) || content_length_ != no_content_length) - { + if((flags_ & parse_flag::chunked) || + content_length_ != no_content_length) return false; - } return true; } diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp index c60d3a7cf..c98934dc8 100644 --- a/include/beast/http/parse_error.hpp +++ b/include/beast/http/parse_error.hpp @@ -23,8 +23,8 @@ enum class parse_error bad_crlf, bad_request, - bad_status_code, bad_status, + bad_reason, bad_field, bad_value, @@ -33,7 +33,11 @@ enum class parse_error bad_on_headers_rv, invalid_chunk_size, + invalid_ext_name, + invalid_ext_val, + headers_too_big, + body_too_big, short_read, general @@ -60,7 +64,7 @@ public: return "bad method"; case parse_error::bad_uri: - return "bad Request-URI"; + return "bad request-target"; case parse_error::bad_version: return "bad HTTP-Version"; @@ -69,13 +73,13 @@ public: return "missing CRLF"; case parse_error::bad_request: - return "bad Request-Line"; - - case parse_error::bad_status_code: - return "bad Status-Code"; + return "bad reason-phrase"; case parse_error::bad_status: - return "bad Status-Line"; + return "bad status-code"; + + case parse_error::bad_reason: + return "bad reason-phrase"; case parse_error::bad_field: return "bad field token"; @@ -95,6 +99,18 @@ public: case parse_error::invalid_chunk_size: return "invalid chunk size"; + case parse_error::invalid_ext_name: + return "invalid ext name"; + + case parse_error::invalid_ext_val: + return "invalid ext val"; + + case parse_error::headers_too_big: + return "headers size limit exceeded"; + + case parse_error::body_too_big: + return "body size limit exceeded"; + case parse_error::short_read: return "unexpected end of data"; diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index c99c2156d..0909c298c 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -172,7 +172,7 @@ private: m_.reason = std::move(this->reason_); } - int on_headers(error_code&) + int on_headers(std::uint64_t, error_code&) { flush(); m_.version = 10 * this->http_major() + this->http_minor(); diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index df4b5ed3d..0d9f9da2e 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -6,6 +6,8 @@ GroupSources(test/http "/") add_executable (http-tests ${BEAST_INCLUDES} + message_fuzz.hpp + fail_parser.hpp ../../extras/beast/unit_test/main.cpp basic_dynabuf_body.cpp basic_headers.cpp @@ -35,6 +37,7 @@ endif() add_executable (bench-tests ${BEAST_INCLUDES} + nodejs_parser.hpp ../../extras/beast/unit_test/main.cpp nodejs_parser.cpp parser_bench.cpp diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index b1bd23763..b8b207b60 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -8,10 +8,9 @@ // Test that header file is self-contained. #include -#include "message_fuzz.hpp" +#include "fail_parser.hpp" -#include -#include +#include #include #include #include @@ -91,7 +90,7 @@ public: { value = true; } - int on_headers(error_code&) + int on_headers(std::uint64_t, error_code&) { headers = true; return 0; @@ -106,201 +105,7 @@ public: } }; - //-------------------------------------------------------------------------- - - static - std::string - escaped_string(boost::string_ref const& s) - { - std::string out; - out.reserve(s.size()); - char const* p = s.data(); - while(p != s.end()) - { - if(*p == '\r') - out.append("\\r"); - else if(*p == '\n') - out.append("\\n"); - else if (*p == '\t') - out.append("\\t"); - else - out.append(p, 1); - ++p; - } - return out; - } - - template - struct null_parser : basic_parser_v1> - { - }; - - static - std::string - str(boost::string_ref const& s) - { - return std::string{s.data(), s.size()}; - } - - template - class test_parser : - public basic_parser_v1> - { - std::string field_; - std::string value_; - - void check() - { - if(! value_.empty()) - { - fields.emplace(field_, str(detail::trim(value_))); - field_.clear(); - value_.clear(); - } - } - - public: - std::map fields; - std::string body; - - void on_field(boost::string_ref const& s, error_code&) - { - check(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - } - - int on_headers(error_code&) - { - check(); - return 0; - } - - void on_body(boost::string_ref const& s, error_code&) - { - body.append(s.data(), s.size()); - } - }; - - // Parse the entire input buffer as a valid message, - // then parse in two pieces of all possible lengths. - // - template - void - parse(boost::string_ref const& m, F&& f) - { - using boost::asio::buffer; - for(;;) - { - error_code ec; - Parser p; - p.write(buffer(m.data(), m.size()), ec); - if(! expect(! ec, ec.message())) - break; - if(p.needs_eof()) - { - p.write_eof(ec); - if(! expect(! ec, ec.message())) - break; - } - if(expect(p.complete())) - f(p); - break; - } - for(std::size_t i = 1; i < m.size() - 1; ++i) - { - error_code ec; - Parser p; - p.write(buffer(&m[0], i), ec); - if(! expect(! ec, ec.message())) - continue; - if(! p.complete()) - { - p.write(buffer(&m[i], m.size() - i), ec); - if(! expect(! ec, ec.message())) - continue; - } - if(! p.complete() && p.needs_eof()) - { - p.write_eof(ec); - if(! expect(! ec, ec.message())) - continue; - } - if(! expect(p.complete())) - continue; - f(p); - } - } - - // Parse with an expected error code - // - template - void - parse_ev(boost::string_ref const& m, parse_error ev) - { - using boost::asio::buffer; - { - error_code ec; - null_parser p; - p.write(buffer(m.data(), m.size()), ec); - if(expect(! p.complete())) - expect(ec == ev, ec.message()); - } - for(std::size_t i = 1; i < m.size() - 1; ++i) - { - error_code ec; - null_parser p; - p.write(buffer(&m[0], i), ec); - if(! expect(! p.complete())) - continue; - if(ec) - { - expect(ec == ev, ec.message()); - continue; - } - p.write(buffer(&m[i], m.size() - i), ec); - if(! expect(! p.complete())) - continue; - if(! expect(ec == ev, ec.message())) - continue; - } - } - - // Parse a valid message with expected version - // - template - void - version(boost::string_ref const& m, - unsigned major, unsigned minor) - { - parse>(m, - [&](null_parser const& p) - { - expect(p.http_major() == major); - expect(p.http_minor() == minor); - }); - } - - // Parse a valid message with expected flags mask - // - void - checkf(boost::string_ref const& m, std::uint8_t mask) - { - parse>(m, - [&](null_parser const& p) - { - expect(p.flags() & mask); - }); - } - - //-------------------------------------------------------------------------- - - // Check all callbacks invoked + // Check that all callbacks are invoked void testCallbacks() { @@ -352,439 +157,1012 @@ public: } } + //-------------------------------------------------------------------------- + + template + static void - testVersion() - { - version ("GET / HTTP/0.0\r\n\r\n", 0, 0); - version ("GET / HTTP/0.1\r\n\r\n", 0, 1); - version ("GET / HTTP/0.9\r\n\r\n", 0, 9); - version ("GET / HTTP/1.0\r\n\r\n", 1, 0); - version ("GET / HTTP/1.1\r\n\r\n", 1, 1); - version ("GET / HTTP/9.9\r\n\r\n", 9, 9); - version ("GET / HTTP/999.999\r\n\r\n", 999, 999); - parse_ev("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version); - - version ("HTTP/0.0 200 OK\r\n\r\n", 0, 0); - version ("HTTP/0.1 200 OK\r\n\r\n", 0, 1); - version ("HTTP/0.9 200 OK\r\n\r\n", 0, 9); - version ("HTTP/1.0 200 OK\r\n\r\n", 1, 0); - version ("HTTP/1.1 200 OK\r\n\r\n", 1, 1); - version ("HTTP/9.9 200 OK\r\n\r\n", 9, 9); - version ("HTTP/999.999 200 OK\r\n\r\n", 999, 999); - parse_ev("HTTP/1000.0 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/0.1000 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/99999999999999999999.0 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/0.99999999999999999999 200 OK\r\n\r\n", parse_error::bad_version); - } - - void testConnection(std::string const& token, - std::uint8_t flag) - { - checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + " \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + " \t\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \r\n" " " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" " " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \r\n" "\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" "\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X, " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X,\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X,\t " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + " \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag); - } - - void testContentLength() - { - std::size_t const length = 0; - std::string const length_s = - std::to_string(length); - - checkf("GET / HTTP/1.1\r\nContent-Length:"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \t\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t \r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - } - - void testTransferEncoding() - { - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \t\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t \r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked ); - } - - void testFlags() - { - testConnection("keep-alive", parse_flag::connection_keep_alive); - testConnection("close", parse_flag::connection_close); - testConnection("upgrade", parse_flag::connection_upgrade); - - checkf("GET / HTTP/1.1\r\nConnection: close, win\r\n\r\n", parse_flag::connection_close); - checkf("GET / HTTP/1.1\r\nConnection: keep-alive, win\r\n\r\n", parse_flag::connection_keep_alive); - checkf("GET / HTTP/1.1\r\nConnection: upgrade, win\r\n\r\n", parse_flag::connection_upgrade); - - testContentLength(); - - testTransferEncoding(); - - checkf( - "GET / HTTP/1.1\r\n" - "Upgrade: x\r\n" - "\r\n", - parse_flag::upgrade - ); - - parse_ev( - "GET / HTTP/1.1\r\n" - "Transfer-Encoding:chunked\r\n" - "Content-Length: 0\r\n" - "Proxy-Connection: close\r\n" - "\r\n", parse_error::illegal_content_length); - } - - void testHeaders() - { - parse>( - "GET / HTTP/1.0\r\n" - "Conniving: yes\r\n" - "Content-Lengthening: yes\r\n" - "Transfer-Encoding: deflate\r\n" - "Connection: sweep\r\n" - "\r\n", - [](null_parser const&) - { - }); - - parse_ev( - "GET / HTTP/1.0\r\n" - "Content-Length: 1\r\n" - "Content-Length: 2\r\n" - "\r\n", - parse_error::bad_content_length); - - parse_ev( - "GET / HTTP/1.0\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "fffffffffffffffff\r\n" - "0\r\n\r\n", - parse_error::bad_content_length); - - parse_ev("GET / HTTP/1.0\r\nContent-Length: 1e9\r\n\r\n", - parse_error::bad_content_length); - - parse_ev("GET / HTTP/1.0\r\nContent-Length: 99999999999999999999999\r\n\r\n", - parse_error::bad_content_length); - } - - void testUpgrade() - { - using boost::asio::buffer; - null_parser p; - boost::string_ref s = - "GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n"; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - if(! expect(! ec, ec.message())) - return; - expect(p.complete()); - expect(p.upgrade()); - } - - void testBad() - { - parse_ev(" ", parse_error::bad_method); - parse_ev(" G", parse_error::bad_method); - parse_ev("G:", parse_error::bad_request); - parse_ev("GET /", parse_error::bad_uri); - parse_ev("GET / X", parse_error::bad_version); - parse_ev("GET / HX", parse_error::bad_version); - parse_ev("GET / HTTX", parse_error::bad_version); - parse_ev("GET / HTTPX", parse_error::bad_version); - parse_ev("GET / HTTP/.", parse_error::bad_version); - parse_ev("GET / HTTP/1000", parse_error::bad_version); - parse_ev("GET / HTTP/1. ", parse_error::bad_version); - parse_ev("GET / HTTP/1.1000", parse_error::bad_version); - parse_ev("GET / HTTP/1.1\r ", parse_error::bad_crlf); - parse_ev("GET / HTTP/1.1\r\nf :", parse_error::bad_field); - } - - void testInvalidMatrix() + for_split(boost::string_ref const& s, F const& f) { using boost::asio::buffer; using boost::asio::buffer_copy; - std::string s; - - for(std::size_t n = 0;; ++n) + for(std::size_t i = 0; i < s.size(); ++i) { - // Create a request and set one octet to an invalid char - s = - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 00\r\n" - "\r\n"; - auto const len = s.size(); - if(n < len) - { - s[n] = 0; - for(std::size_t m = 1; m < len - 1; ++m) - { - // Use separately allocated buffers so - // address sanitizer has something to chew on. - // - std::unique_ptr p1(new char[m]); - std::unique_ptr p2(new char[len - m]); - auto const b1 = buffer(p1.get(), m); - auto const b2 = buffer(p2.get(), len - m); - buffer_copy(b1, buffer(s.data(), m)); - buffer_copy(b2, buffer(s.data() + m, len - m)); - null_parser p; - error_code ec; - p.write(b1, ec); - if(ec) - { - pass(); - continue; - } - p.write(b2, ec); - expect(ec); - } - } - else - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - expect(! ec, ec.message()); - break; - } - } - - for(std::size_t n = 0;; ++n) - { - // Create a response and set one octet to an invalid char - s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "0\r\n\r\n"; - auto const len = s.size(); - if(n < len) - { - s[n] = 0; - for(std::size_t m = 1; m < len - 1; ++m) - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), m), ec); - if(ec) - { - pass(); - continue; - } - p.write(buffer(s.data() + m, len - m), ec); - expect(ec); - } - } - else - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - expect(! ec, ec.message()); - break; - } + // Use separately allocated buffers so + // address sanitizer has something to chew on. + // + auto const n1 = s.size() - i; + auto const n2 = i; + std::unique_ptr p1(new char[n1]); + std::unique_ptr p2(new char[n2]); + buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); + buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); + f( + boost::string_ref{p1.get(), n1}, + boost::string_ref{p2.get(), n2}); } } + struct none + { + template + void + operator()(Parser const&) const + { + } + }; + + template void - testRandomReq(std::size_t N) + good(int onBodyRv, std::string const& s, F const& f) { using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - message_fuzz mg; - for(std::size_t i = 0; i < N; ++i) - { - std::string s; + for_split(s, + [&](boost::string_ref const& s1, boost::string_ref const& s2) { - streambuf sb; - mg.request(sb); - s.reserve(buffer_size(sb.data())); - for(auto const& b : sb.data()) - s.append(buffer_cast(b), - buffer_size(b)); - } - null_parser p; - for(std::size_t j = 1; j < s.size() - 1; ++j) - { - error_code ec; - p.write(buffer(&s[0], j), ec); - if(! expect(! ec, ec.message())) + static std::size_t constexpr Limit = 200; + std::size_t n; + for(n = 0; n < Limit; ++n) { - log << escaped_string(s); + test::fail_counter fc(n); + fail_parser p(fc); + p.on_body_rv(onBodyRv); + error_code ec; + p.write(buffer(s1.data(), s1.size()), ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + if(! expect(s2.empty() || ! p.complete())) + break; + p.write(buffer(s2.data(), s2.size()), ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + p.write_eof(ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + expect(p.complete()); + f(p); break; } - if(! p.complete()) + expect(n < Limit); + }); + } + + template + void + good(std::string const& s, F const& f = {}) + { + return good(0, s, f); + } + + template + void + bad(int onBodyRv, std::string const& s, error_code ev) + { + using boost::asio::buffer; + for_split(s, + [&](boost::string_ref const& s1, boost::string_ref const& s2) + { + static std::size_t constexpr Limit = 200; + std::size_t n; + for(n = 0; n < Limit; ++n) { - p.write(buffer(&s[j], s.size() - j), ec); - if(! expect(! ec, ec.message())) + test::fail_counter fc(n); + fail_parser p(fc); + p.on_body_rv(onBodyRv); + error_code ec; + p.write(buffer(s1.data(), s1.size()), ec); + if(ec == test::fail_error) + continue; + if(ec) { - log << escaped_string(s); + expect((ec && ! ev) || ec == ev); break; } - } - if(! expect(p.complete())) + if(! expect(! p.complete())) + break; + if(! s2.empty()) + { + p.write(buffer(s2.data(), s2.size()), ec); + if(ec == test::fail_error) + continue; + if(ec) + { + expect((ec && ! ev) || ec == ev); + break; + } + if(! expect(! p.complete())) + break; + } + p.write_eof(ec); + if(ec == test::fail_error) + continue; + expect(! p.complete()); + expect((ec && ! ev) || ec == ev); break; - if(! p.keep_alive()) - { - p.~null_parser(); - new(&p) null_parser{}; } - } + expect(n < Limit); + }); + } + + template + void + bad(std::string const& s, error_code ev = {}) + { + return bad(0, s, ev); + } + + //-------------------------------------------------------------------------- + + class version + { + suite& s_; + unsigned major_; + unsigned minor_; + + public: + version(suite& s, unsigned major, unsigned minor) + : s_(s) + , major_(major) + , minor_(minor) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.http_major() == major_); + s_.expect(p.http_minor() == minor_); + } + }; + + class status + { + suite& s_; + unsigned code_; + public: + status(suite& s, int code) + : s_(s) + , code_(code) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.status_code() == code_); + } + }; + + void testRequestLine() + { + /* + request-line = method SP request-target SP HTTP-version CRLF + method = token + request-target = origin-form / absolute-form / authority-form / asterisk-form + HTTP-version = "HTTP/" DIGIT "." DIGIT + */ + good("GET /x HTTP/1.0\r\n\r\n"); + good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); + good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); + // VFALCO TODO various forms of good request-target (uri) + good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); + good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); + good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); + good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); + good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); + + bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); + bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); + bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + // VFALCO TODO various forms of bad request-target (uri) + bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); + + // write a bad request line in 2 pieces + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buffer_cat( + buf("GET / "), buf("_TTP/1.1\r\n"), + buf("\r\n") + ), ec); + expect(ec == parse_error::bad_version); } } - void - testRandomResp(std::size_t N) + void testStatusLine() { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - message_fuzz mg; - for(std::size_t i = 0; i < N; ++i) + /* + status-line = HTTP-version SP status-code SP reason-phrase CRLF + HTTP-version = "HTTP/" DIGIT "." DIGIT + status-code = 3DIGIT + reason-phrase = *( HTAB / SP / VCHAR / obs-text ) + */ + good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); + good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); + good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); + good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); + good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); + good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); + good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); + good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); + good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); + good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); + good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); + good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); + good("HTTP/1.0 200 \r\n" "\r\n"); + good("HTTP/1.1 200 X \r\n" "\r\n"); + good("HTTP/1.1 200 X\t\r\n" "\r\n"); + good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); + } + + //-------------------------------------------------------------------------- + + void testHeaders() + { + /* + header-field = field-name ":" OWS field-value OWS + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + */ + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("f:\r\n")); + good(m("f: \r\n")); + good(m("f:\t\r\n")); + good(m("f: \t\r\n")); + good(m("f: v\r\n")); + good(m("f:\tv\r\n")); + good(m("f:\tv \r\n")); + good(m("f:\tv\t\r\n")); + good(m("f:\tv\t \r\n")); + good(m("f:\r\n \r\n")); + good(m("f:v\r\n")); + good(m("f: v\r\n u\r\n")); + good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + bad(m(" f: v\r\n"), parse_error::bad_field); + bad(m("\tf: v\r\n"), parse_error::bad_field); + bad(m("f : v\r\n"), parse_error::bad_field); + bad(m("f\t: v\r\n"), parse_error::bad_field); + bad(m("f: \n\r\n"), parse_error::bad_value); + bad(m("f: v\r \r\n"), parse_error::bad_crlf); + bad(m("f: \r v\r\n"), parse_error::bad_crlf); + bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); + } + + //-------------------------------------------------------------------------- + + class flags + { + suite& s_; + std::size_t value_; + + public: + flags(suite& s, std::size_t value) + : s_(s) + , value_(value) { - std::string s; - { - streambuf sb; - mg.response(sb); - s.reserve(buffer_size(sb.data())); - for(auto const& b : sb.data()) - s.append(buffer_cast(b), - buffer_size(b)); - } - null_parser p; - for(std::size_t j = 1; j < s.size() - 1; ++j) - { - error_code ec; - p.write(buffer(&s[0], j), ec); - if(! expect(! ec, ec.message())) - { - log << escaped_string(s); - break; - } - if(! p.complete()) - { - p.write(buffer(&s[j], s.size() - j), ec); - if(! expect(! ec, ec.message())) - { - log << escaped_string(s); - break; - } - } - if(! expect(p.complete())) - break; - if(! p.keep_alive()) - { - p.~null_parser(); - new(&p) null_parser{}; - } - } } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.flags() == value_); + } + }; + + class keepalive_f + { + suite& s_; + bool value_; + + public: + keepalive_f(suite& s, bool value) + : s_(s) + , value_(value) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.keep_alive() == value_); + } + }; + + void testConnectionHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const cn = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; + }; + auto const keepalive = + [&](bool v) + { + return keepalive_f{*this, v}; + }; + + good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); + good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed + + good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); + + good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); + + good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); + good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); + good("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", keepalive(true)); + good("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", keepalive(false)); + + good(cn("x\r\n"), flags{*this, 0}); + good(cn("x,y\r\n"), flags{*this, 0}); + good(cn("x ,y\r\n"), flags{*this, 0}); + good(cn("x\t,y\r\n"), flags{*this, 0}); + good(cn("keep\r\n"), flags{*this, 0}); + good(cn(",keep\r\n"), flags{*this, 0}); + good(cn(" keep\r\n"), flags{*this, 0}); + good(cn("\tnone\r\n"), flags{*this, 0}); + good(cn("keep,\r\n"), flags{*this, 0}); + good(cn("keep\t\r\n"), flags{*this, 0}); + good(cn("keep\r\n"), flags{*this, 0}); + good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); + good(cn("\r\n keep\r\n"), flags{*this, 0}); + good(cn("keep\r\n \r\n"), flags{*this, 0}); + good(cn("closet\r\n"), flags{*this, 0}); + good(cn(",closet\r\n"), flags{*this, 0}); + good(cn(" closet\r\n"), flags{*this, 0}); + good(cn("\tcloset\r\n"), flags{*this, 0}); + good(cn("closet,\r\n"), flags{*this, 0}); + good(cn("closet\t\r\n"), flags{*this, 0}); + good(cn("closet\r\n"), flags{*this, 0}); + good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); + good(cn("\r\n closet\r\n"), flags{*this, 0}); + good(cn("closet\r\n \r\n"), flags{*this, 0}); + good(cn("clog\r\n"), flags{*this, 0}); + good(cn("key\r\n"), flags{*this, 0}); + good(cn("uptown\r\n"), flags{*this, 0}); + good(cn("keeper\r\n \r\n"), flags{*this, 0}); + good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); + good(cn("up\r\n \r\n"), flags{*this, 0}); + good(cn("upgrader\r\n \r\n"), flags{*this, 0}); + good(cn("none\r\n"), flags{*this, 0}); + good(cn("\r\n none\r\n"), flags{*this, 0}); + + good(m("ConnectioX: close\r\n"), flags{*this, 0}); + good(m("Condor: close\r\n"), flags{*this, 0}); + good(m("Connect: close\r\n"), flags{*this, 0}); + good(m("Connections: close\r\n"), flags{*this, 0}); + + good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); + good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); + good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); + good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); + + bad(cn("["), parse_error::bad_value); + bad(cn("\"\r\n"), parse_error::bad_value); + bad(cn("close[\r\n"), parse_error::bad_value); + bad(cn("close [\r\n"), parse_error::bad_value); + bad(cn("close, upgrade [\r\n"), parse_error::bad_value); + bad(cn("upgrade[]\r\n"), parse_error::bad_value); + bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); + bad(cn("keep-alive[\r\n"), parse_error::bad_value); + bad(cn("keep-alive []\r\n"), parse_error::bad_value); + bad(cn("no[ne]\r\n"), parse_error::bad_value); + } + + void testContentLengthHeader() + { + auto const length = + [&](std::string const& s, std::uint64_t v) + { + good(1, s, + [&](fail_parser const& p) + { + expect(p.content_length() == v); + if(v != no_content_length) + expect(p.flags() & parse_flag::contentlength); + }); + }; + auto const c = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; + }; + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + + length(c("0\r\n"), 0); + length(c("00\r\n"), 0); + length(c("1\r\n"), 1); + length(c("01\r\n"), 1); + length(c("9\r\n"), 9); + length(c("123456789\r\n"), 123456789); + length(c("42 \r\n"), 42); + length(c("42\t\r\n"), 42); + length(c("42 \t \r\n"), 42); + length(c("42\r\n \t \r\n"), 42); + + good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); + good(m("Content-Lengths: many\r\n"), flags{*this, 0}); + good(m("Content: full\r\n"), flags{*this, 0}); + + bad(c("\r\n"), parse_error::bad_content_length); + bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); + bad(c("0 0\r\n"), parse_error::bad_content_length); + bad(c("0 1\r\n"), parse_error::bad_content_length); + bad(c(",\r\n"), parse_error::bad_content_length); + bad(c("0,\r\n"), parse_error::bad_content_length); + bad(m( + "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); + } + + void testTransferEncodingHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const ce = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; + }; + auto const te = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; + }; + good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + + // Technically invalid but beyond the parser's scope to detect + good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + + good(te("gzip\r\n"), flags{*this, 0}); + good(te("chunked, gzip\r\n"), flags{*this, 0}); + good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); + good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); + good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); + good(te("bigchunked\r\n"), flags{*this, 0}); + good(te("chunk\r\n ked\r\n"), flags{*this, 0}); + good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); + good(te("barley\r\n chunked\r\n"), flags{*this, 0}); + + good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); + good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); + good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); + + bad(1, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", parse_error::illegal_content_length); + } + + void testUpgradeHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); + good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); + good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); + + good(m("Up: yes\r\n"), flags{*this, 0}); + good(m("UpgradX: none\r\n"), flags{*this, 0}); + good(m("Upgrades: 2\r\n"), flags{*this, 0}); + good(m("Upsample: 4x\r\n"), flags{*this, 0}); + + good( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](fail_parser const& p) + { + expect(p.upgrade()); + }); + } + + //-------------------------------------------------------------------------- + + class body_f + { + suite& s_; + std::string const& body_; + + public: + body_f(body_f&&) = default; + + body_f(suite& s, std::string const& v) + : s_(s) + , body_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.body == body_); + } + }; + + template + static + boost::asio::const_buffers_1 + buf(char const (&s)[N]) + { + return { s, N-1 }; } void testBody() { - auto match = - [&](std::string const& body) + using boost::asio::buffer; + auto const body = + [&](std::string const& s) + { + return body_f{*this, s}; + }; + good( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + body("1")); + + good( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + body("hello")); + + // on_body returns 2, meaning upgrade + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.on_body_rv(2); + p.write(buf( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + } + + // write the body in 3 pieces + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890")), ec); + expect(! ec); + expect(p.complete()); + expect(! p.needs_eof()); + p.write_eof(ec); + expect(! ec); + p.write_eof(ec); + expect(! ec); + p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); + expect(ec == parse_error::connection_closed); + } + + // request without Content-Length or + // Transfer-Encoding: chunked has no body. + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // response without Content-Length or + // Transfer-Encoding: chunked requires eof. + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.0 200 OK\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.complete()); + expect(p.needs_eof()); + p.write(buf( + "hello" + ), ec); + expect(! ec); + expect(! p.complete()); + expect(p.needs_eof()); + p.write_eof(ec); + expect(! ec); + expect(p.complete()); + p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); + expect(ec == parse_error::connection_closed); + } + + // 304 "Not Modified" response does not require eof + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.0 304 Not Modified\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // Chunked response does not require eof + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(! p.complete()); + p.write(buf( + "0\r\n\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // restart: 1.0 assumes Connection: close + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(ec == parse_error::connection_closed); + } + + // restart: 1.1 assumes Connection: keep-alive + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + } + + bad(3, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "\r\n*", parse_error::bad_on_headers_rv); + + bad(0, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + parse_error::short_read); + } + + void testChunkedBody() + { + auto const body = + [&](std::string const& s) + { + return body_f{*this, s}; + }; + auto const ce = + [](std::string const& s) { return - [&](test_parser const& p) - { - expect(p.body == body); - }; + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + s; }; - parse>( - "GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1")); + /* + chunked-body = *chunk + last-chunk + trailer-part + CRLF + chunk = chunk-size [ chunk-ext ] CRLF + chunk-data CRLF + chunk-size = 1*HEXDIG + last-chunk = 1*("0") [ chunk-ext ] CRLF + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string + trailer-part = *( header-field CRLF ) + */ + good(ce( + "1;xy\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123")); + good(ce( + "1;x\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match("")); + good(ce( + "1;x;y\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "1\r\n" - "a\r\n" - "0\r\n" - "\r\n", match("a")); + good(ce( + "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "2\r\n" - "ab\r\n" - "0\r\n" - "\r\n", match("ab")); + good(ce( + "1\r\n" "a\r\n" "0\r\n" "\r\n" + ), body("a")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "2\r\n" - "ab\r\n" - "1\r\n" - "c\r\n" - "0\r\n" - "\r\n", match("abc")); + good(ce( + "2\r\n" "ab\r\n" "0\r\n" "\r\n" + ), body("ab")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "10\r\n" - "1234567890123456\r\n" - "0\r\n" - "\r\n", match("1234567890123456")); + good(ce( + "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" + ), body("abc")); + + good(ce( + "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" + ), body("1234567890123456")); + + bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); + bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); + bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); + bad(ce("0\r_\n"), parse_error::bad_crlf); + bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); + bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); + bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); + bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); + } + + void testLimits() + { + std::size_t n; + static std::size_t constexpr Limit = 100; + { + for(n = 1; n < Limit; ++n) + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(headers_max_size{n}); + error_code ec; + p.write(buf( + "GET / HTTP/1.1\r\n" + "User-Agent: beast\r\n" + "\r\n" + ), ec); + if(! ec) + break; + expect(ec == parse_error::headers_too_big); + } + expect(n < Limit); + } + { + for(n = 1; n < Limit; ++n) + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(headers_max_size{n}); + error_code ec; + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Content-Length: 4\r\n" + "\r\n" + "****" + ), ec); + if(! ec) + break; + expect(ec == parse_error::headers_too_big); + } + expect(n < Limit); + } + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(body_max_size{2}); + error_code ec; + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Content-Length: 4\r\n" + "\r\n" + "****" + ), ec); + expect(ec == parse_error::body_too_big); + } } void run() override { testCallbacks(); - testVersion(); - testFlags(); + testRequestLine(); + testStatusLine(); testHeaders(); - testUpgrade(); - testBad(); - testInvalidMatrix(); - testRandomReq(100); - testRandomResp(100); + testConnectionHeader(); + testContentLengthHeader(); + testTransferEncodingHeader(); + testUpgradeHeader(); testBody(); + testChunkedBody(); + testLimits(); } }; diff --git a/test/http/fail_parser.hpp b/test/http/fail_parser.hpp new file mode 100644 index 000000000..003992a29 --- /dev/null +++ b/test/http/fail_parser.hpp @@ -0,0 +1,112 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_TEST_FAIL_PARSER_HPP +#define BEAST_HTTP_TEST_FAIL_PARSER_HPP + +#include +#include + +namespace beast { +namespace http { + +template +class fail_parser + : public basic_parser_v1> +{ + test::fail_counter& fc_; + std::uint64_t content_length_ = no_content_length; + int body_rv_ = 0; + +public: + std::string body; + + template + explicit + fail_parser(test::fail_counter& fc, Args&&... args) + : fc_(fc) + { + } + + void + on_body_rv(int rv) + { + body_rv_ = rv; + } + + // valid on successful parse + std::uint64_t + content_length() const + { + return content_length_; + } + + void on_start(error_code& ec) + { + fc_.fail(ec); + } + + void on_method(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_uri(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_reason(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_request(error_code& ec) + { + fc_.fail(ec); + } + + void on_response(error_code& ec) + { + fc_.fail(ec); + } + + void on_field(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_value(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + int on_headers(std::uint64_t content_length, error_code& ec) + { + if(fc_.fail(ec)) + return 0; + content_length_ = content_length; + return body_rv_; + } + + void on_body(boost::string_ref const& s, error_code& ec) + { + if(fc_.fail(ec)) + return; + body.append(s.data(), s.size()); + } + + void on_complete(error_code& ec) + { + fc_.fail(ec); + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp index 69a46dd7e..ad7f47290 100644 --- a/test/http/parse_error.cpp +++ b/test/http/parse_error.cpp @@ -38,8 +38,8 @@ public: check("http", parse_error::bad_version); check("http", parse_error::bad_crlf); check("http", parse_error::bad_request); - check("http", parse_error::bad_status_code); check("http", parse_error::bad_status); + check("http", parse_error::bad_reason); check("http", parse_error::bad_field); check("http", parse_error::bad_value); check("http", parse_error::bad_content_length); diff --git a/test/http/read.cpp b/test/http/read.cpp index 5b8280ce6..d1e6c0540 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -8,6 +8,8 @@ // Test that header file is self-contained. #include +#include "fail_parser.hpp" + #include #include #include @@ -24,77 +26,6 @@ class read_test , public test::enable_yield_to { public: - template - class fail_parser - : public basic_parser_v1> - { - test::fail_counter& fc_; - - public: - template - explicit - fail_parser(test::fail_counter& fc, Args&&... args) - : fc_(fc) - { - } - - void on_start(error_code& ec) - { - fc_.fail(ec); - } - - void on_method(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_uri(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_reason(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_request(error_code& ec) - { - fc_.fail(ec); - } - - void on_response(error_code& ec) - { - fc_.fail(ec); - } - - void on_field(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_value(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - int on_headers(error_code& ec) - { - fc_.fail(ec); - return 0; - } - - void on_body(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_complete(error_code& ec) - { - fc_.fail(ec); - } - }; - template void failMatrix(const char* s, yield_context do_yield) { From 999e2fa0318b5982736d3ea01a418770ea802671 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 3 Jun 2016 11:49:43 -0400 Subject: [PATCH 11/11] Set Beast version to 1.0.0-b6 --- include/beast/version.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/beast/version.hpp b/include/beast/version.hpp index aa098487a..e85e4107d 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -14,8 +14,8 @@ // BEAST_VERSION / 100 % 1000 is the minor version // BEAST_VERSION / 100000 is the major version // -#define BEAST_VERSION 100000 +#define BEAST_VERSION 100006 -#define BEAST_VERSION_STRING "1.0.0-b5" +#define BEAST_VERSION_STRING "1.0.0-b6" #endif