From 933a98b97c1b40bb30fbe2607b5fcc43c9995191 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 12 Nov 2014 16:15:33 -0800 Subject: [PATCH] Add http::chunk_encode: This transforms a ConstBufferSequence into a new ConstBufferSequence whose data is encoded according to the Content transfer encoding rules of RFC2616. The implementation does not copy any memory. --- beast/http/HTTP.unity.cpp | 1 + beast/http/chunk_encode.h | 284 ++++++++++++++++++++++ beast/http/tests/chunked_encoder.test.cpp | 151 ++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 beast/http/chunk_encode.h create mode 100644 beast/http/tests/chunked_encoder.test.cpp diff --git a/beast/http/HTTP.unity.cpp b/beast/http/HTTP.unity.cpp index 750ba7fcdd..c1de909440 100644 --- a/beast/http/HTTP.unity.cpp +++ b/beast/http/HTTP.unity.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include diff --git a/beast/http/chunk_encode.h b/beast/http/chunk_encode.h new file mode 100644 index 0000000000..0f74cf9001 --- /dev/null +++ b/beast/http/chunk_encode.h @@ -0,0 +1,284 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_HTTP_CHUNK_ENCODE_H_INCLUDED +#define BEAST_HTTP_CHUNK_ENCODE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include // + +namespace beast { +namespace http { + +namespace detail { + +template +class chunk_encoded_buffers +{ +private: + using const_buffer = boost::asio::const_buffer; + + Buffers buffers_; + const_buffer head_; + const_buffer tail_; + + // Storage for the longest hex string we might need, plus delimiters. + std::array data_; + +public: + using value_type = boost::asio::const_buffer; + + class const_iterator; + + chunk_encoded_buffers() = delete; + chunk_encoded_buffers (chunk_encoded_buffers const&) = default; + chunk_encoded_buffers& operator= (chunk_encoded_buffers const&) = default; + + chunk_encoded_buffers (Buffers const& buffers, bool final_chunk); + + const_iterator + begin() const + { + return const_iterator(*this, false); + } + + const_iterator + end() const + { + return const_iterator(*this, true); + } + +private: + // Unchecked conversion of unsigned to hex string + template + std::enable_if_t::value, OutIter> + to_hex(OutIter const first, OutIter const last, Unsigned n); +}; + +template +class chunk_encoded_buffers::const_iterator + : public std::iterator +{ +private: + using iterator = typename Buffers::const_iterator; + enum class Where { head, input, end }; + chunk_encoded_buffers const* buffers_; + Where where_; + iterator iter_; + +public: + const_iterator(); + const_iterator (const_iterator const&) = default; + const_iterator& operator= (const_iterator const&) = default; + bool operator== (const_iterator const& other) const; + bool operator!= (const_iterator const& other) const; + const_iterator& operator++(); + const_iterator& operator--(); + const_iterator operator++(int) const; + const_iterator operator--(int) const; + const_buffer operator*() const; + +private: + friend class chunk_encoded_buffers; + const_iterator(chunk_encoded_buffers const& buffers, bool past_the_end); +}; + +//------------------------------------------------------------------------------ + +template +chunk_encoded_buffers::chunk_encoded_buffers ( + Buffers const& buffers, bool final_chunk) + : buffers_(buffers) +{ + auto const size = boost::asio::buffer_size(buffers); + data_[data_.size() - 2] = '\r'; + data_[data_.size() - 1] = '\n'; + auto pos = to_hex(data_.begin(), data_.end() - 2, size); + head_ = const_buffer(&*pos, + std::distance(pos, data_.end())); + if (size > 0 && final_chunk) + tail_ = const_buffer("\r\n0\r\n\r\n", 7); + else + tail_ = const_buffer("\r\n", 2); +} + +template +template +std::enable_if_t::value, OutIter> +chunk_encoded_buffers::to_hex( + OutIter const first, OutIter const last, Unsigned n) +{ + assert(first != last); + OutIter iter = last; + if(n == 0) + { + *--iter = '0'; + return iter; + } + while(n) + { + assert(iter != first); + *--iter = "0123456789abcdef"[n&0xf]; + n>>=4; + } + return iter; +} + +template +chunk_encoded_buffers::const_iterator::const_iterator() + : where_(Where::end) + , buffers_(nullptr) +{ +} + +template +bool +chunk_encoded_buffers::const_iterator::operator==( + const_iterator const& other) const +{ + return buffers_ == other.buffers_ && + where_ == other.where_ && iter_ == other.iter_; +} + +template +bool +chunk_encoded_buffers::const_iterator::operator!=( + const_iterator const& other) const +{ + return buffers_ != other.buffers_ || + where_ != other.where_ || iter_ != other.iter_; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator++() -> + const_iterator& +{ + assert(buffers_); + assert(where_ != Where::end); + if (where_ == Where::head) + where_ = Where::input; + else if (iter_ != buffers_->buffers_.end()) + ++iter_; + else + where_ = Where::end; + return *this; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator--() -> + const_iterator& +{ + assert(buffers_); + assert(where_ != Where::begin); + if (where_ == Where::end) + where_ = Where::input; + else if (iter_ != buffers_->buffers_.begin()) + --iter_; + else + where_ = Where::head; + return *this; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator++(int) const -> + const_iterator +{ + auto iter = *this; + ++iter; + return iter; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator--(int) const -> + const_iterator +{ + auto iter = *this; + --iter; + return iter; +} + +template +auto +chunk_encoded_buffers::const_iterator::operator*() const -> + const_buffer +{ + assert(buffers_); + assert(where_ != Where::end); + if (where_ == Where::head) + return buffers_->head_; + if (iter_ != buffers_->buffers_.end()) + return *iter_; + return buffers_->tail_; +} + +template +chunk_encoded_buffers::const_iterator::const_iterator( + chunk_encoded_buffers const& buffers, bool past_the_end) + : buffers_(&buffers) + , where_(past_the_end ? Where::end : Where::head) + , iter_(past_the_end ? buffers_->buffers_.end() : + buffers_->buffers_.begin()) +{ +} + +} + +/** Returns a chunk-encoded BufferSequence. + + See: + http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 + + @tparam Buffers A type meeting the requirements of BufferSequence. + @param buffers The input buffer sequence. + @param final_chunk `true` If this should include a final-chunk. + @return A chunk-encoded ConstBufferSeqeunce representing the input. +*/ +/** @{ */ +template +detail::chunk_encoded_buffers +chunk_encode (Buffers const& buffers, + bool final_chunk = false) +{ + return detail::chunk_encoded_buffers< + Buffers>(buffers, final_chunk); +} + +// Returns a chunked encoding final chunk. +inline +boost::asio::const_buffers_1 +chunk_encode_final() +{ + return boost::asio::const_buffers_1( + "0\r\n\r\n", 5); +} +/** @} */ + +} // http +} // beast + +#endif diff --git a/beast/http/tests/chunked_encoder.test.cpp b/beast/http/tests/chunked_encoder.test.cpp new file mode 100644 index 0000000000..81ea77d8db --- /dev/null +++ b/beast/http/tests/chunked_encoder.test.cpp @@ -0,0 +1,151 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace beast { +namespace http { + +class chunk_encode_test : public unit_test::suite +{ +public: + // Convert CR LF to printables for display + static + std::string + encode (std::string const& s) + { + std::string result; + for(auto const c : s) + { + if (c == '\r') + result += "\\r"; + else if (c== '\n') + result += "\\n"; + else + result += c; + } + return result; + } + + // Print the contents of a ConstBufferSequence to the log + template + static + void + print (ConstBufferSequence const& buffers, Log log) + { + for(auto const& buf : buffers) + log << encode (std::string( + boost::asio::buffer_cast(buf), + boost::asio::buffer_size(buf))); + } + + // Convert a ConstBufferSequence to a string + template + static + std::string + buffer_to_string (ConstBufferSequence const& b) + { + std::string s; + auto const n = boost::asio::buffer_size(b); + s.resize(n); + boost::asio::buffer_copy( + boost::asio::buffer(&s[0], n), b); + return s; + } + + // Append a ConstBufferSequence to an existing string + template + static + void + buffer_append (std::string& s, ConstBufferSequence const& b) + { + s += buffer_to_string(b); + } + + // Convert the input sequence of the stream to a + // chunked-encoded string. The input sequence is consumed. + template + static + std::string + streambuf_to_string (Streambuf& sb, + bool final_chunk = false) + { + std::string s; + buffer_append(s, chunk_encode(sb.data(), final_chunk)); + return s; + } + + // Check an input against the correct chunk encoded version + void + check (std::string const& in, std::string const& answer, + bool final_chunk = true) + { + asio::streambuf sb(3); + sb << in; + auto const out = streambuf_to_string (sb, final_chunk); + if (! expect (out == answer)) + log << "expected\n" << encode(answer) << + "\ngot\n" << encode(out); + } + + void testStreambuf() + { + asio::streambuf sb(3); + std::string const s = + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789"; + sb << s; + expect(buffer_to_string(sb.data()) == s); + } + + void + testEncoder() + { + check("", "0\r\n\r\n"); + check("x", "1\r\nx\r\n0\r\n\r\n"); + check("abcd", "4\r\nabcd\r\n0\r\n\r\n"); + check("x", "1\r\nx\r\n", false); + check( + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + , + "d2\r\n" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "\r\n" + "0\r\n\r\n"); + } + + void + run() + { + testStreambuf(); + testEncoder(); + } +}; + +BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast); + +} +}