diff options
Diffstat (limited to 'boost/beast/websocket/detail')
-rw-r--r-- | boost/beast/websocket/detail/frame.hpp | 304 | ||||
-rw-r--r-- | boost/beast/websocket/detail/hybi13.hpp | 75 | ||||
-rw-r--r-- | boost/beast/websocket/detail/mask.hpp | 267 | ||||
-rw-r--r-- | boost/beast/websocket/detail/pausation.hpp | 229 | ||||
-rw-r--r-- | boost/beast/websocket/detail/pmd_extension.hpp | 442 | ||||
-rw-r--r-- | boost/beast/websocket/detail/type_traits.hpp | 36 | ||||
-rw-r--r-- | boost/beast/websocket/detail/utf8_checker.hpp | 344 |
7 files changed, 1697 insertions, 0 deletions
diff --git a/boost/beast/websocket/detail/frame.hpp b/boost/beast/websocket/detail/frame.hpp new file mode 100644 index 0000000000..95080aeefc --- /dev/null +++ b/boost/beast/websocket/detail/frame.hpp @@ -0,0 +1,304 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_FRAME_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_FRAME_HPP + +#include <boost/beast/websocket/rfc6455.hpp> +#include <boost/beast/websocket/detail/utf8_checker.hpp> +#include <boost/beast/core/buffers_suffix.hpp> +#include <boost/beast/core/flat_static_buffer.hpp> +#include <boost/beast/core/static_string.hpp> +#include <boost/asio/buffer.hpp> +#include <boost/assert.hpp> +#include <boost/endian/buffers.hpp> +#include <cstdint> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +inline +std::uint16_t +big_uint16_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return (p[0]<<8) + p[1]; +} + +inline +std::uint64_t +big_uint64_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + (static_cast<std::uint64_t>(p[0])<<56) + + (static_cast<std::uint64_t>(p[1])<<48) + + (static_cast<std::uint64_t>(p[2])<<40) + + (static_cast<std::uint64_t>(p[3])<<32) + + (static_cast<std::uint64_t>(p[4])<<24) + + (static_cast<std::uint64_t>(p[5])<<16) + + (static_cast<std::uint64_t>(p[6])<< 8) + + p[7]; +} + +inline +std::uint32_t +little_uint32_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + p[0] + + (static_cast<std::uint32_t>(p[1])<< 8) + + (static_cast<std::uint32_t>(p[2])<<16) + + (static_cast<std::uint32_t>(p[3])<<24); +} + +inline +void +native_to_little_uint32(std::uint32_t v, void* buf) +{ + auto p = reinterpret_cast<std::uint8_t*>(buf); + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; +} + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + +// Contents of a WebSocket frame header +struct frame_header +{ + std::uint64_t len; + std::uint32_t key; + opcode op; + bool fin : 1; + bool mask : 1; + bool rsv1 : 1; + bool rsv2 : 1; + bool rsv3 : 1; +}; + +// holds the largest possible frame header +using fh_buffer = + flat_static_buffer<14>; + +// holds the largest possible control frame +using frame_buffer = + flat_static_buffer< 2 + 8 + 4 + 125 >; + +inline +bool constexpr +is_reserved(opcode op) +{ + return + (op >= opcode::rsv3 && op <= opcode::rsv7) || + (op >= opcode::crsvb && op <= opcode::crsvf); +} + +inline +bool constexpr +is_valid(opcode op) +{ + return op <= opcode::crsvf; +} + +inline +bool constexpr +is_control(opcode op) +{ + return op >= opcode::close; +} + +inline +bool +is_valid_close_code(std::uint16_t v) +{ + switch(v) + { + case close_code::normal: // 1000 + case close_code::going_away: // 1001 + case close_code::protocol_error: // 1002 + case close_code::unknown_data: // 1003 + case close_code::bad_payload: // 1007 + case close_code::policy_error: // 1008 + case close_code::too_big: // 1009 + case close_code::needs_extension: // 1010 + case close_code::internal_error: // 1011 + case close_code::service_restart: // 1012 + case close_code::try_again_later: // 1013 + return true; + + // explicitly reserved + case close_code::reserved1: // 1004 + case close_code::no_status: // 1005 + case close_code::abnormal: // 1006 + case close_code::reserved2: // 1014 + case close_code::reserved3: // 1015 + return false; + } + // reserved + if(v >= 1016 && v <= 2999) + return false; + // not used + if(v <= 999) + return false; + return true; +} + +//------------------------------------------------------------------------------ + +// Write frame header to dynamic buffer +// +template<class DynamicBuffer> +void +write(DynamicBuffer& db, frame_header const& fh) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using namespace boost::endian; + std::size_t n; + std::uint8_t b[14]; + b[0] = (fh.fin ? 0x80 : 0x00) | static_cast<std::uint8_t>(fh.op); + if(fh.rsv1) + b[0] |= 0x40; + if(fh.rsv2) + b[0] |= 0x20; + if(fh.rsv3) + b[0] |= 0x10; + b[1] = fh.mask ? 0x80 : 0x00; + if(fh.len <= 125) + { + b[1] |= fh.len; + n = 2; + } + else if(fh.len <= 65535) + { + b[1] |= 126; + ::new(&b[2]) big_uint16_buf_t{ + (std::uint16_t)fh.len}; + n = 4; + } + else + { + b[1] |= 127; + ::new(&b[2]) big_uint64_buf_t{fh.len}; + n = 10; + } + if(fh.mask) + { + native_to_little_uint32(fh.key, &b[n]); + n += 4; + } + db.commit(buffer_copy( + db.prepare(n), buffer(b))); +} + +// Read data from buffers +// This is for ping and pong payloads +// +template<class Buffers> +void +read_ping(ping_data& data, Buffers const& bs) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffer; + BOOST_ASSERT(buffer_size(bs) <= data.max_size()); + data.resize(buffer_size(bs)); + buffer_copy(mutable_buffer{ + data.data(), data.size()}, bs); +} + +// Read close_reason, return true on success +// This is for the close payload +// +template<class Buffers> +void +read_close(close_reason& cr, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + auto n = buffer_size(bs); + BOOST_ASSERT(n <= 125); + if(n == 0) + { + cr = close_reason{}; + code = close_code::none; + return; + } + if(n == 1) + { + code = close_code::protocol_error; + return; + } + buffers_suffix<Buffers> cb(bs); + { + std::uint8_t b[2]; + buffer_copy(buffer(b), cb); + cr.code = big_uint16_to_native(&b[0]); + cb.consume(2); + n -= 2; + if(! is_valid_close_code(cr.code)) + { + code = close_code::protocol_error; + return; + } + } + if(n > 0) + { + cr.reason.resize(n); + buffer_copy(buffer(&cr.reason[0], n), cb); + if(! check_utf8( + cr.reason.data(), cr.reason.size())) + { + code = close_code::protocol_error; + return; + } + } + else + { + cr.reason = ""; + } + code = close_code::none; +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/hybi13.hpp b/boost/beast/websocket/detail/hybi13.hpp new file mode 100644 index 0000000000..b9c67b8200 --- /dev/null +++ b/boost/beast/websocket/detail/hybi13.hpp @@ -0,0 +1,75 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_HPP + +#include <boost/beast/core/static_string.hpp> +#include <boost/beast/core/string.hpp> +#include <boost/beast/core/detail/base64.hpp> +#include <boost/beast/core/detail/sha1.hpp> +#include <boost/assert.hpp> +#include <array> +#include <cstdint> +#include <string> +#include <type_traits> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +using sec_ws_key_type = static_string< + beast::detail::base64::encoded_size(16)>; + +using sec_ws_accept_type = static_string< + beast::detail::base64::encoded_size(20)>; + +template<class Gen> +void +make_sec_ws_key(sec_ws_key_type& key, Gen& g) +{ + char a[16]; + for(int i = 0; i < 16; i += 4) + { + auto const v = g(); + a[i ] = v & 0xff; + a[i+1] = (v >> 8) & 0xff; + a[i+2] = (v >> 16) & 0xff; + a[i+3] = (v >> 24) & 0xff; + } + key.resize(key.max_size()); + key.resize(beast::detail::base64::encode( + key.data(), &a[0], 16)); +} + +template<class = void> +void +make_sec_ws_accept(sec_ws_accept_type& accept, + string_view key) +{ + BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n); + static_string<sec_ws_key_type::max_size_n + 36> m(key); + m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + beast::detail::sha1_context ctx; + beast::detail::init(ctx); + beast::detail::update(ctx, m.data(), m.size()); + char digest[beast::detail::sha1_context::digest_size]; + beast::detail::finish(ctx, &digest[0]); + accept.resize(accept.max_size()); + accept.resize(beast::detail::base64::encode( + accept.data(), &digest[0], sizeof(digest))); +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/mask.hpp b/boost/beast/websocket/detail/mask.hpp new file mode 100644 index 0000000000..92ea0a4238 --- /dev/null +++ b/boost/beast/websocket/detail/mask.hpp @@ -0,0 +1,267 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_MASK_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_MASK_HPP + +#include <boost/beast/core/detail/config.hpp> +#include <boost/asio/buffer.hpp> +#include <array> +#include <climits> +#include <cstdint> +#include <random> +#include <type_traits> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +// Pseudo-random source of mask keys +// +template<class Generator> +class maskgen_t +{ + Generator g_; + +public: + using result_type = + typename Generator::result_type; + + maskgen_t(); + + result_type + operator()() noexcept; + + void + rekey(); +}; + +template<class Generator> +maskgen_t<Generator>::maskgen_t() +{ + rekey(); +} + +template<class Generator> +auto +maskgen_t<Generator>::operator()() noexcept -> + result_type +{ + for(;;) + if(auto key = g_()) + return key; +} + +template<class _> +void +maskgen_t<_>::rekey() +{ + std::random_device rng; +#if 0 + std::array<std::uint32_t, 32> e; + for(auto& i : e) + i = rng(); + // VFALCO This constructor causes + // address sanitizer to fail, no idea why. + std::seed_seq ss(e.begin(), e.end()); + g_.seed(ss); +#else + g_.seed(rng()); +#endif +} + +// VFALCO NOTE This generator has 5KB of state! +//using maskgen = maskgen_t<std::mt19937>; +using maskgen = maskgen_t<std::minstd_rand>; + +//------------------------------------------------------------------------------ + +using prepared_key = + std::conditional<sizeof(void*) == 8, + std::uint64_t, std::uint32_t>::type; + +inline +void +prepare_key(std::uint32_t& prepared, std::uint32_t key) +{ + prepared = key; +} + +inline +void +prepare_key(std::uint64_t& prepared, std::uint32_t key) +{ + prepared = + (static_cast<std::uint64_t>(key) << 32) | key; +} + +template<class T> +inline +typename std::enable_if<std::is_integral<T>::value, T>::type +ror(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast<unsigned>( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast<T>((t << (bits - n)) | ( + static_cast<typename std::make_unsigned<T>::type>(t) >> n)); +} + +// 32-bit optimized +// +template<class = void> +void +mask_inplace_fast( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + auto n = b.size(); + auto p = reinterpret_cast<std::uint8_t*>(b.data()); + if(n >= sizeof(key)) + { + // Bring p to 4-byte alignment + auto const i = reinterpret_cast< + std::uintptr_t>(p) & (sizeof(key)-1); + switch(i) + { + case 1: p[2] ^= static_cast<std::uint8_t>(key >> 16); BOOST_BEAST_FALLTHROUGH; + case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8); BOOST_BEAST_FALLTHROUGH; + case 3: p[0] ^= static_cast<std::uint8_t>(key); + { + auto const d = static_cast<unsigned>(sizeof(key) - i); + key = ror(key, 8*d); + n -= d; + p += d; + BOOST_BEAST_FALLTHROUGH; + } + default: + break; + } + } + + // Mask 4 bytes at a time + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint32_t*>(p) ^= key; + p += sizeof(key); + } + + // Leftovers + n &= sizeof(key)-1; + switch(n) + { + case 3: p[2] ^= static_cast<std::uint8_t>(key >> 16); BOOST_BEAST_FALLTHROUGH; + case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8); BOOST_BEAST_FALLTHROUGH; + case 1: p[0] ^= static_cast<std::uint8_t>(key); + key = ror(key, static_cast<unsigned>(8*n)); + BOOST_BEAST_FALLTHROUGH; + default: + break; + } +} + +// 64-bit optimized +// +template<class = void> +void +mask_inplace_fast( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + auto n = b.size(); + auto p = reinterpret_cast<std::uint8_t*>(b.data()); + if(n >= sizeof(key)) + { + // Bring p to 8-byte alignment + auto const i = reinterpret_cast< + std::uintptr_t>(p) & (sizeof(key)-1); + switch(i) + { + case 1: p[6] ^= static_cast<std::uint8_t>(key >> 48); + case 2: p[5] ^= static_cast<std::uint8_t>(key >> 40); + case 3: p[4] ^= static_cast<std::uint8_t>(key >> 32); + case 4: p[3] ^= static_cast<std::uint8_t>(key >> 24); + case 5: p[2] ^= static_cast<std::uint8_t>(key >> 16); + case 6: p[1] ^= static_cast<std::uint8_t>(key >> 8); + case 7: p[0] ^= static_cast<std::uint8_t>(key); + { + auto const d = static_cast< + unsigned>(sizeof(key) - i); + key = ror(key, 8*d); + n -= d; + p += d; + } + default: + break; + } + } + + // Mask 8 bytes at a time + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint64_t*>(p) ^= key; + p += sizeof(key); + } + + // Leftovers + n &= sizeof(key)-1; + switch(n) + { + case 7: p[6] ^= static_cast<std::uint8_t>(key >> 48); + case 6: p[5] ^= static_cast<std::uint8_t>(key >> 40); + case 5: p[4] ^= static_cast<std::uint8_t>(key >> 32); + case 4: p[3] ^= static_cast<std::uint8_t>(key >> 24); + case 3: p[2] ^= static_cast<std::uint8_t>(key >> 16); + case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8); + case 1: p[0] ^= static_cast<std::uint8_t>(key); + key = ror(key, static_cast<unsigned>(8*n)); + default: + break; + } +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + mask_inplace_fast(b, key); +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + mask_inplace_fast(b, key); +} + +// Apply mask in place +// +template<class MutableBuffers, class KeyType> +void +mask_inplace( + MutableBuffers const& bs, KeyType& key) +{ + for(boost::asio::mutable_buffer b : bs) + mask_inplace(b, key); +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/pausation.hpp b/boost/beast/websocket/detail/pausation.hpp new file mode 100644 index 0000000000..f51ee10327 --- /dev/null +++ b/boost/beast/websocket/detail/pausation.hpp @@ -0,0 +1,229 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP + +#include <boost/beast/core/handler_ptr.hpp> +#include <boost/asio/associated_allocator.hpp> +#include <boost/asio/coroutine.hpp> +#include <boost/assert.hpp> +#include <array> +#include <memory> +#include <new> +#include <utility> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +// A container that holds a suspended, asynchronous composed +// operation. The contained object may be invoked later to +// resume the operation, or the container may be destroyed. +// +class pausation +{ + struct base + { + base() = default; + base(base &&) = delete; + base(base const&) = delete; + virtual ~base() = default; + virtual void operator()() = 0; + }; + + template<class F> + struct holder : base + { + F f; + + holder(holder&&) = default; + + template<class U> + explicit + holder(U&& u) + : f(std::forward<U>(u)) + { + } + + void + operator()() override + { + F f_(std::move(f)); + this->~holder(); + // invocation of f_() can + // assign a new object to *this. + f_(); + } + }; + + struct exemplar : boost::asio::coroutine + { + struct H + { + void operator()(); + }; + + struct T + { + using handler_type = H; + }; + + handler_ptr<T, H> hp; + + void operator()(); + }; + + template<class Op> + class saved_op + { + Op* op_ = nullptr; + + public: + ~saved_op() + { + if(op_) + { + Op op(std::move(*op_)); + op_->~Op(); + typename std::allocator_traits< + boost::asio::associated_allocator_t<Op>>:: + template rebind_alloc<Op> alloc{ + boost::asio::get_associated_allocator(op)}; + std::allocator_traits< + decltype(alloc)>::deallocate(alloc, op_, 1); + } + } + + saved_op(saved_op&& other) + : op_(other.op_) + { + other.op_ = nullptr; + } + + saved_op& operator=(saved_op&& other) + { + BOOST_ASSERT(! op_); + op_ = other.op_; + other.op_ = 0; + return *this; + } + + explicit + saved_op(Op&& op) + { + typename std::allocator_traits< + boost::asio::associated_allocator_t<Op>>:: + template rebind_alloc<Op> alloc{ + boost::asio::get_associated_allocator(op)}; + auto const p = std::allocator_traits< + decltype(alloc)>::allocate(alloc, 1); + op_ = new(p) Op{std::move(op)}; + } + + void + operator()() + { + BOOST_ASSERT(op_); + Op op{std::move(*op_)}; + typename std::allocator_traits< + boost::asio::associated_allocator_t<Op>>:: + template rebind_alloc<Op> alloc{ + boost::asio::get_associated_allocator(op)}; + std::allocator_traits< + decltype(alloc)>::deallocate(alloc, op_, 1); + op_ = nullptr; + op(); + } + }; + + using buf_type = char[sizeof(holder<exemplar>)]; + + base* base_ = nullptr; + alignas(holder<exemplar>) buf_type buf_; + +public: + pausation() = default; + pausation(pausation const&) = delete; + pausation& operator=(pausation const&) = delete; + + ~pausation() + { + if(base_) + base_->~base(); + } + + pausation(pausation&& other) + { + boost::ignore_unused(other); + BOOST_ASSERT(! other.base_); + } + + pausation& + operator=(pausation&& other) + { + boost::ignore_unused(other); + BOOST_ASSERT(! base_); + BOOST_ASSERT(! other.base_); + return *this; + } + + template<class F> + void + emplace(F&& f); + + template<class F> + void + save(F&& f); + + explicit + operator bool() const + { + return base_ != nullptr; + } + + bool + maybe_invoke() + { + if(base_) + { + auto const basep = base_; + base_ = nullptr; + (*basep)(); + return true; + } + return false; + } +}; + +template<class F> +void +pausation::emplace(F&& f) +{ + using type = holder<typename std::decay<F>::type>; + static_assert(sizeof(buf_type) >= sizeof(type), + "buffer too small"); + BOOST_ASSERT(! base_); + base_ = ::new(buf_) type{std::forward<F>(f)}; +} + +template<class F> +void +pausation::save(F&& f) +{ + emplace(saved_op<F>{std::move(f)}); +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/pmd_extension.hpp b/boost/beast/websocket/detail/pmd_extension.hpp new file mode 100644 index 0000000000..a28b844cd7 --- /dev/null +++ b/boost/beast/websocket/detail/pmd_extension.hpp @@ -0,0 +1,442 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP + +#include <boost/beast/core/error.hpp> +#include <boost/beast/core/buffers_suffix.hpp> +#include <boost/beast/core/read_size.hpp> +#include <boost/beast/zlib/deflate_stream.hpp> +#include <boost/beast/zlib/inflate_stream.hpp> +#include <boost/beast/websocket/option.hpp> +#include <boost/beast/http/rfc7230.hpp> +#include <boost/asio/buffer.hpp> +#include <utility> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +// permessage-deflate offer parameters +// +// "context takeover" means: +// preserve sliding window across messages +// +struct pmd_offer +{ + bool accept; + + // 0 = absent, or 8..15 + int server_max_window_bits; + + // -1 = present, 0 = absent, or 8..15 + int client_max_window_bits; + + // `true` if server_no_context_takeover offered + bool server_no_context_takeover; + + // `true` if client_no_context_takeover offered + bool client_no_context_takeover; +}; + +template<class = void> +int +parse_bits(string_view s) +{ + if(s.size() == 0) + return -1; + if(s.size() > 2) + return -1; + if(s[0] < '1' || s[0] > '9') + return -1; + unsigned i = 0; + for(auto c : s) + { + if(c < '0' || c > '9') + return -1; + auto const i0 = i; + i = 10 * i + (c - '0'); + if(i < i0) + return -1; + } + return static_cast<int>(i); +} + +// Parse permessage-deflate request fields +// +template<class Allocator> +void +pmd_read(pmd_offer& offer, + http::basic_fields<Allocator> const& fields) +{ + offer.accept = false; + offer.server_max_window_bits= 0; + offer.client_max_window_bits = 0; + offer.server_no_context_takeover = false; + offer.client_no_context_takeover = false; + + http::ext_list list{ + fields["Sec-WebSocket-Extensions"]}; + for(auto const& ext : list) + { + if(iequals(ext.first, "permessage-deflate")) + { + for(auto const& param : ext.second) + { + if(iequals(param.first, + "server_max_window_bits")) + { + if(offer.server_max_window_bits != 0) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(param.second.empty()) + { + // The negotiation offer extension + // parameter is missing the value. + // + return; // MUST decline + } + offer.server_max_window_bits = + parse_bits(param.second); + if( offer.server_max_window_bits < 8 || + offer.server_max_window_bits > 15) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + } + else if(iequals(param.first, + "client_max_window_bits")) + { + if(offer.client_max_window_bits != 0) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + offer.client_max_window_bits = + parse_bits(param.second); + if( offer.client_max_window_bits < 8 || + offer.client_max_window_bits > 15) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + } + else + { + offer.client_max_window_bits = -1; + } + } + else if(iequals(param.first, + "server_no_context_takeover")) + { + if(offer.server_no_context_takeover) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + offer.server_no_context_takeover = true; + } + else if(iequals(param.first, + "client_no_context_takeover")) + { + if(offer.client_no_context_takeover) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + offer.client_no_context_takeover = true; + } + else + { + // The negotiation offer contains an extension + // parameter not defined for use in an offer. + // + return; // MUST decline + } + } + offer.accept = true; + return; + } + } +} + +// Set permessage-deflate fields for a client offer +// +template<class Allocator> +void +pmd_write(http::basic_fields<Allocator>& fields, + pmd_offer const& offer) +{ + static_string<512> s; + s = "permessage-deflate"; + if(offer.server_max_window_bits != 0) + { + if(offer.server_max_window_bits != -1) + { + s += "; server_max_window_bits="; + s += to_static_string( + offer.server_max_window_bits); + } + else + { + s += "; server_max_window_bits"; + } + } + if(offer.client_max_window_bits != 0) + { + if(offer.client_max_window_bits != -1) + { + s += "; client_max_window_bits="; + s += to_static_string( + offer.client_max_window_bits); + } + else + { + s += "; client_max_window_bits"; + } + } + if(offer.server_no_context_takeover) + { + s += "; server_no_context_takeover"; + } + if(offer.client_no_context_takeover) + { + s += "; client_no_context_takeover"; + } + fields.set(http::field::sec_websocket_extensions, s); +} + +// Negotiate a permessage-deflate client offer +// +template<class Allocator> +void +pmd_negotiate( + http::basic_fields<Allocator>& fields, + pmd_offer& config, + pmd_offer const& offer, + permessage_deflate const& o) +{ + if(! (offer.accept && o.server_enable)) + { + config.accept = false; + return; + } + config.accept = true; + + static_string<512> s = "permessage-deflate"; + + config.server_no_context_takeover = + offer.server_no_context_takeover || + o.server_no_context_takeover; + if(config.server_no_context_takeover) + s += "; server_no_context_takeover"; + + config.client_no_context_takeover = + o.client_no_context_takeover || + offer.client_no_context_takeover; + if(config.client_no_context_takeover) + s += "; client_no_context_takeover"; + + if(offer.server_max_window_bits != 0) + config.server_max_window_bits = (std::min)( + offer.server_max_window_bits, + o.server_max_window_bits); + else + config.server_max_window_bits = + o.server_max_window_bits; + if(config.server_max_window_bits < 15) + { + // ZLib's deflateInit silently treats 8 as + // 9 due to a bug, so prevent 8 from being used. + // + if(config.server_max_window_bits < 9) + config.server_max_window_bits = 9; + + s += "; server_max_window_bits="; + s += to_static_string( + config.server_max_window_bits); + } + + switch(offer.client_max_window_bits) + { + case -1: + // extension parameter is present with no value + config.client_max_window_bits = + o.client_max_window_bits; + if(config.client_max_window_bits < 15) + { + s += "; client_max_window_bits="; + s += to_static_string( + config.client_max_window_bits); + } + break; + + case 0: + /* extension parameter is absent. + + If a received extension negotiation offer doesn't have the + "client_max_window_bits" extension parameter, the corresponding + extension negotiation response to the offer MUST NOT include the + "client_max_window_bits" extension parameter. + */ + if(o.client_max_window_bits == 15) + config.client_max_window_bits = 15; + else + config.accept = false; + break; + + default: + // extension parameter has value in [8..15] + config.client_max_window_bits = (std::min)( + o.client_max_window_bits, + offer.client_max_window_bits); + s += "; client_max_window_bits="; + s += to_static_string( + config.client_max_window_bits); + break; + } + if(config.accept) + fields.set(http::field::sec_websocket_extensions, s); +} + +// Normalize the server's response +// +inline +void +pmd_normalize(pmd_offer& offer) +{ + if(offer.accept) + { + if( offer.server_max_window_bits == 0) + offer.server_max_window_bits = 15; + + if( offer.client_max_window_bits == 0 || + offer.client_max_window_bits == -1) + offer.client_max_window_bits = 15; + } +} + +//-------------------------------------------------------------------- + +// Compress a buffer sequence +// Returns: `true` if more calls are needed +// +template<class DeflateStream, class ConstBufferSequence> +bool +deflate( + DeflateStream& zo, + boost::asio::mutable_buffer& out, + buffers_suffix<ConstBufferSequence>& cb, + bool fin, + std::size_t& total_in, + error_code& ec) +{ + using boost::asio::buffer; + BOOST_ASSERT(out.size() >= 6); + zlib::z_params zs; + zs.avail_in = 0; + zs.next_in = nullptr; + zs.avail_out = out.size(); + zs.next_out = out.data(); + for(auto in : beast::detail::buffers_range(cb)) + { + zs.avail_in = in.size(); + if(zs.avail_in == 0) + continue; + zs.next_in = in.data(); + zo.write(zs, zlib::Flush::none, ec); + if(ec) + { + if(ec != zlib::error::need_buffers) + return false; + BOOST_ASSERT(zs.avail_out == 0); + BOOST_ASSERT(zs.total_out == out.size()); + ec.assign(0, ec.category()); + break; + } + if(zs.avail_out == 0) + { + BOOST_ASSERT(zs.total_out == out.size()); + break; + } + BOOST_ASSERT(zs.avail_in == 0); + } + total_in = zs.total_in; + cb.consume(zs.total_in); + if(zs.avail_out > 0 && fin) + { + auto const remain = boost::asio::buffer_size(cb); + if(remain == 0) + { + // Inspired by Mark Adler + // https://github.com/madler/zlib/issues/149 + // + // VFALCO We could do this flush twice depending + // on how much space is in the output. + zo.write(zs, zlib::Flush::block, ec); + BOOST_ASSERT(! ec || ec == zlib::error::need_buffers); + if(ec == zlib::error::need_buffers) + ec.assign(0, ec.category()); + if(ec) + return false; + if(zs.avail_out >= 6) + { + zo.write(zs, zlib::Flush::full, ec); + BOOST_ASSERT(! ec); + // remove flush marker + zs.total_out -= 4; + out = buffer(out.data(), zs.total_out); + return false; + } + } + } + ec.assign(0, ec.category()); + out = buffer(out.data(), zs.total_out); + return true; +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/type_traits.hpp b/boost/beast/websocket/detail/type_traits.hpp new file mode 100644 index 0000000000..6c2806146c --- /dev/null +++ b/boost/beast/websocket/detail/type_traits.hpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP + +#include <boost/beast/websocket/rfc6455.hpp> +#include <boost/beast/core/detail/type_traits.hpp> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +template<class F> +using is_RequestDecorator = + typename beast::detail::is_invocable<F, + void(request_type&)>::type; + +template<class F> +using is_ResponseDecorator = + typename beast::detail::is_invocable<F, + void(response_type&)>::type; + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/boost/beast/websocket/detail/utf8_checker.hpp b/boost/beast/websocket/detail/utf8_checker.hpp new file mode 100644 index 0000000000..e86310f051 --- /dev/null +++ b/boost/beast/websocket/detail/utf8_checker.hpp @@ -0,0 +1,344 @@ +// +// Copyright (c) 2016-2017 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP + +#include <boost/beast/core/type_traits.hpp> +#include <boost/asio/buffer.hpp> +#include <boost/assert.hpp> +#include <algorithm> +#include <cstdint> + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +/** A UTF8 validator. + + This validator can be used to check if a buffer containing UTF8 text is + valid. The write function may be called incrementally with segmented UTF8 + sequences. The finish function determines if all processed text is valid. +*/ +template<class = void> +class utf8_checker_t +{ + std::size_t need_ = 0; // chars we need to finish the code point + std::uint8_t* p_ = cp_; // current position in temp buffer + std::uint8_t cp_[4]; // a temp buffer for the code point + +public: + /** Prepare to process text as valid utf8 + */ + void + reset(); + + /** Check that all processed text is valid utf8 + */ + bool + finish(); + + /** Check if text is valid UTF8 + + @return `true` if the text is valid utf8 or false otherwise. + */ + bool + write(std::uint8_t const* in, std::size_t size); + + /** Check if text is valid UTF8 + + @return `true` if the text is valid utf8 or false otherwise. + */ + template<class ConstBufferSequence> + bool + write(ConstBufferSequence const& bs); +}; + +template<class _> +void +utf8_checker_t<_>:: +reset() +{ + need_ = 0; + p_ = cp_; +} + +template<class _> +bool +utf8_checker_t<_>:: +finish() +{ + auto const success = need_ == 0; + reset(); + return success; +} + +template<class _> +template<class ConstBufferSequence> +bool +utf8_checker_t<_>:: +write(ConstBufferSequence const& bs) +{ + static_assert(boost::asio::is_const_buffer_sequence<ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + for(auto b : beast::detail::buffers_range(bs)) + if(! write(reinterpret_cast< + std::uint8_t const*>(b.data()), + b.size())) + return false; + return true; +} + +template<class _> +bool +utf8_checker_t<_>:: +write(std::uint8_t const* in, std::size_t size) +{ + auto const valid = + [](std::uint8_t const*& p) + { + if(p[0] < 128) + { + ++p; + return true; + } + if((p[0] & 0xe0) == 0xc0) + { + if( (p[1] & 0xc0) != 0x80 || + (p[0] & 0xfe) == 0xc0) // overlong + return false; + p += 2; + return true; + } + if((p[0] & 0xf0) == 0xe0) + { + if( (p[1] & 0xc0) != 0x80 + || (p[2] & 0xc0) != 0x80 + || (p[0] == 0xe0 && (p[1] & 0xe0) == 0x80) // overlong + || (p[0] == 0xed && (p[1] & 0xe0) == 0xa0) // surrogate + //|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF + ) + return false; + p += 3; + return true; + } + if((p[0] & 0xf8) == 0xf0) + { + if( (p[1] & 0xc0) != 0x80 + || (p[2] & 0xc0) != 0x80 + || (p[3] & 0xc0) != 0x80 + || (p[0] == 0xf0 && (p[1] & 0xf0) == 0x80) // overlong + || (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4 // > U+10FFFF + ) + return false; + p += 4; + return true; + } + return false; + }; + auto const fail_fast = + [&]() + { + auto const n = p_ - cp_; + switch(n) + { + default: + BOOST_ASSERT(false); + BOOST_BEAST_FALLTHROUGH; + case 1: + cp_[1] = 0x81; + BOOST_BEAST_FALLTHROUGH; + case 2: + cp_[2] = 0x81; + BOOST_BEAST_FALLTHROUGH; + case 3: + cp_[3] = 0x81; + break; + } + std::uint8_t const* p = cp_; + return ! valid(p); + }; + auto const needed = + [](std::uint8_t const v) + { + if(v < 128) + return 1; + if(v < 192) + return 0; + if(v < 224) + return 2; + if(v < 240) + return 3; + if(v < 248) + return 4; + return 0; + }; + + auto const end = in + size; + + // Finish up any incomplete code point + if(need_ > 0) + { + // Calculate what we have + auto n = (std::min)(size, need_); + size -= n; + need_ -= n; + + // Add characters to the code point + while(n--) + *p_++ = *in++; + BOOST_ASSERT(p_ <= cp_ + 5); + + // Still incomplete? + if(need_ > 0) + { + // Incomplete code point + BOOST_ASSERT(in == end); + + // Do partial validation on the incomplete + // code point, this is called "Fail fast" + // in Autobahn|Testsuite parlance. + return ! fail_fast(); + } + + // Complete code point, validate it + std::uint8_t const* p = &cp_[0]; + if(! valid(p)) + return false; + p_ = cp_; + } + + if(size <= sizeof(std::size_t)) + goto slow; + + // Align `in` to sizeof(std::size_t) boundary + { + auto const in0 = in; + auto last = reinterpret_cast<std::uint8_t const*>( + ((reinterpret_cast<std::uintptr_t>(in) + sizeof(std::size_t) - 1) / + sizeof(std::size_t)) * sizeof(std::size_t)); + + // Check one character at a time for low-ASCII + while(in < last) + { + if(*in & 0x80) + { + // Not low-ASCII so switch to slow loop + size = size - (in - in0); + goto slow; + } + ++in; + } + size = size - (in - in0); + } + + // Fast loop: Process 4 or 8 low-ASCII characters at a time + { + auto const in0 = in; + auto last = in + size - 7; + auto constexpr mask = static_cast< + std::size_t>(0x8080808080808080 & ~std::size_t{0}); + while(in < last) + { +#if 0 + std::size_t temp; + std::memcpy(&temp, in, sizeof(temp)); + if((temp & mask) != 0) +#else + // Technically UB but works on all known platforms + if((*reinterpret_cast<std::size_t const*>(in) & mask) != 0) +#endif + { + size = size - (in - in0); + goto slow; + } + in += sizeof(std::size_t); + } + // There's at least one more full code point left + last += 4; + while(in < last) + if(! valid(in)) + return false; + goto tail; + } + +slow: + // Slow loop: Full validation on one code point at a time + { + auto last = in + size - 3; + while(in < last) + if(! valid(in)) + return false; + } + +tail: + // Handle the remaining bytes. The last + // characters could split a code point so + // we save the partial code point for later. + // + // On entry to the loop, `in` points to the + // beginning of a code point. + // + for(;;) + { + // Number of chars left + auto n = end - in; + if(! n) + break; + + // Chars we need to finish this code point + auto const need = needed(*in); + if(need == 0) + return false; + if(need <= n) + { + // Check a whole code point + if(! valid(in)) + return false; + } + else + { + // Calculate how many chars we need + // to finish this partial code point + need_ = need - n; + + // Save the partial code point + while(n--) + *p_++ = *in++; + BOOST_ASSERT(in == end); + BOOST_ASSERT(p_ <= cp_ + 5); + + // Do partial validation on the incomplete + // code point, this is called "Fail fast" + // in Autobahn|Testsuite parlance. + return ! fail_fast(); + } + } + return true; +} + +using utf8_checker = utf8_checker_t<>; + +template<class = void> +bool +check_utf8(char const* p, std::size_t n) +{ + utf8_checker c; + if(! c.write(reinterpret_cast<const uint8_t*>(p), n)) + return false; + return c.finish(); +} + +} // detail +} // websocket +} // beast +} // boost + +#endif |