mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-28 15:05:53 +00:00
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.
This commit is contained in:
committed by
Nik Bougalis
parent
b14751aad9
commit
933a98b97c
@@ -27,6 +27,7 @@
|
||||
#include <beast/http/impl/raw_parser.cpp>
|
||||
#include <beast/http/impl/URL.cpp>
|
||||
|
||||
#include <beast/http/tests/chunked_encoder.test.cpp>
|
||||
#include <beast/http/tests/parser.test.cpp>
|
||||
#include <beast/http/tests/rfc2616.test.cpp>
|
||||
#include <beast/http/tests/URL.test.cpp>
|
||||
|
||||
284
beast/http/chunk_encode.h
Normal file
284
beast/http/chunk_encode.h
Normal file
@@ -0,0 +1,284 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
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 <boost/asio/buffer.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <beast/cxx14/type_traits.h> // <type_traits>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Buffers>
|
||||
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<char, 2 * sizeof(std::size_t) + 2> 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<class OutIter, class Unsigned>
|
||||
std::enable_if_t<std::is_unsigned<Unsigned>::value, OutIter>
|
||||
to_hex(OutIter const first, OutIter const last, Unsigned n);
|
||||
};
|
||||
|
||||
template <class Buffers>
|
||||
class chunk_encoded_buffers<Buffers>::const_iterator
|
||||
: public std::iterator<std::bidirectional_iterator_tag, const_buffer>
|
||||
{
|
||||
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 <class Buffers>
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
template <class OutIter, class Unsigned>
|
||||
std::enable_if_t<std::is_unsigned<Unsigned>::value, OutIter>
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
chunk_encoded_buffers<Buffers>::const_iterator::const_iterator()
|
||||
: where_(Where::end)
|
||||
, buffers_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Buffers>
|
||||
bool
|
||||
chunk_encoded_buffers<Buffers>::const_iterator::operator==(
|
||||
const_iterator const& other) const
|
||||
{
|
||||
return buffers_ == other.buffers_ &&
|
||||
where_ == other.where_ && iter_ == other.iter_;
|
||||
}
|
||||
|
||||
template <class Buffers>
|
||||
bool
|
||||
chunk_encoded_buffers<Buffers>::const_iterator::operator!=(
|
||||
const_iterator const& other) const
|
||||
{
|
||||
return buffers_ != other.buffers_ ||
|
||||
where_ != other.where_ || iter_ != other.iter_;
|
||||
}
|
||||
|
||||
template <class Buffers>
|
||||
auto
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
auto
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
auto
|
||||
chunk_encoded_buffers<Buffers>::const_iterator::operator++(int) const ->
|
||||
const_iterator
|
||||
{
|
||||
auto iter = *this;
|
||||
++iter;
|
||||
return iter;
|
||||
}
|
||||
|
||||
template <class Buffers>
|
||||
auto
|
||||
chunk_encoded_buffers<Buffers>::const_iterator::operator--(int) const ->
|
||||
const_iterator
|
||||
{
|
||||
auto iter = *this;
|
||||
--iter;
|
||||
return iter;
|
||||
}
|
||||
|
||||
template <class Buffers>
|
||||
auto
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
chunk_encoded_buffers<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 <class Buffers>
|
||||
detail::chunk_encoded_buffers<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
|
||||
151
beast/http/tests/chunked_encoder.test.cpp
Normal file
151
beast/http/tests/chunked_encoder.test.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
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 <beast/asio/streambuf.h>
|
||||
#include <beast/http/chunk_encode.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
|
||||
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 <class ConstBufferSequence, class Log>
|
||||
static
|
||||
void
|
||||
print (ConstBufferSequence const& buffers, Log log)
|
||||
{
|
||||
for(auto const& buf : buffers)
|
||||
log << encode (std::string(
|
||||
boost::asio::buffer_cast<char const*>(buf),
|
||||
boost::asio::buffer_size(buf)));
|
||||
}
|
||||
|
||||
// Convert a ConstBufferSequence to a string
|
||||
template <class ConstBufferSequence>
|
||||
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 <class ConstBufferSequence>
|
||||
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 <class Streambuf>
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user