2017-07-20 08:01:46 -07:00
|
|
|
//
|
2017-07-24 09:42:36 -07:00
|
|
|
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
2017-07-20 08:01:46 -07:00
|
|
|
//
|
|
|
|
|
// 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)
|
|
|
|
|
//
|
2017-07-20 13:40:34 -07:00
|
|
|
// Official repository: https://github.com/boostorg/beast
|
|
|
|
|
//
|
2017-07-20 08:01:46 -07:00
|
|
|
|
2017-07-20 13:40:34 -07:00
|
|
|
#ifndef BOOST_BEAST_HTTP_IMPL_FIELDS_IPP
|
|
|
|
|
#define BOOST_BEAST_HTTP_IMPL_FIELDS_IPP
|
|
|
|
|
|
|
|
|
|
#include <boost/beast/core/buffer_cat.hpp>
|
|
|
|
|
#include <boost/beast/core/string.hpp>
|
|
|
|
|
#include <boost/beast/core/static_string.hpp>
|
|
|
|
|
#include <boost/beast/core/detail/buffers_ref.hpp>
|
|
|
|
|
#include <boost/beast/http/verb.hpp>
|
|
|
|
|
#include <boost/beast/http/rfc7230.hpp>
|
|
|
|
|
#include <boost/beast/http/status.hpp>
|
|
|
|
|
#include <boost/beast/http/chunk_encode.hpp>
|
2017-06-05 09:58:55 -07:00
|
|
|
#include <boost/throw_exception.hpp>
|
2017-06-10 15:05:32 -07:00
|
|
|
#include <stdexcept>
|
2017-06-15 10:03:50 -07:00
|
|
|
#include <string>
|
2016-05-24 06:17:04 -04:00
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000
|
|
|
|
|
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
|
2017-07-20 13:40:34 -07:00
|
|
|
#ifndef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
|
|
|
|
|
#define BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
|
2017-07-09 05:26:27 -07:00
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-07-20 13:40:34 -07:00
|
|
|
namespace boost {
|
2017-07-20 08:01:46 -07:00
|
|
|
namespace beast {
|
|
|
|
|
namespace http {
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
class basic_fields<Allocator>::reader
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
using iter_type = typename list_t::const_iterator;
|
|
|
|
|
|
|
|
|
|
struct field_iterator
|
|
|
|
|
{
|
|
|
|
|
iter_type it_;
|
|
|
|
|
|
|
|
|
|
using value_type = boost::asio::const_buffer;
|
|
|
|
|
using pointer = value_type const*;
|
|
|
|
|
using reference = value_type const;
|
|
|
|
|
using difference_type = std::ptrdiff_t;
|
|
|
|
|
using iterator_category =
|
|
|
|
|
std::bidirectional_iterator_tag;
|
|
|
|
|
|
|
|
|
|
field_iterator() = default;
|
|
|
|
|
field_iterator(field_iterator&& other) = default;
|
|
|
|
|
field_iterator(field_iterator const& other) = default;
|
|
|
|
|
field_iterator& operator=(field_iterator&& other) = default;
|
|
|
|
|
field_iterator& operator=(field_iterator const& other) = default;
|
|
|
|
|
|
|
|
|
|
explicit
|
|
|
|
|
field_iterator(iter_type it)
|
|
|
|
|
: it_(it)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
operator==(field_iterator const& other) const
|
|
|
|
|
{
|
|
|
|
|
return it_ == other.it_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
operator!=(field_iterator const& other) const
|
|
|
|
|
{
|
|
|
|
|
return !(*this == other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reference
|
|
|
|
|
operator*() const
|
|
|
|
|
{
|
|
|
|
|
return it_->buffer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
field_iterator&
|
|
|
|
|
operator++()
|
|
|
|
|
{
|
|
|
|
|
++it_;
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
field_iterator
|
|
|
|
|
operator++(int)
|
|
|
|
|
{
|
|
|
|
|
auto temp = *this;
|
|
|
|
|
++(*this);
|
|
|
|
|
return temp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
field_iterator&
|
|
|
|
|
operator--()
|
|
|
|
|
{
|
|
|
|
|
--it_;
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
field_iterator
|
|
|
|
|
operator--(int)
|
|
|
|
|
{
|
|
|
|
|
auto temp = *this;
|
|
|
|
|
--(*this);
|
|
|
|
|
return temp;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class field_range
|
|
|
|
|
{
|
|
|
|
|
field_iterator first_;
|
|
|
|
|
field_iterator last_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
using const_iterator =
|
|
|
|
|
field_iterator;
|
|
|
|
|
|
|
|
|
|
using value_type =
|
|
|
|
|
typename const_iterator::value_type;
|
|
|
|
|
|
|
|
|
|
field_range(field_range const&) = default;
|
|
|
|
|
|
|
|
|
|
field_range(iter_type first, iter_type last)
|
|
|
|
|
: first_(first)
|
|
|
|
|
, last_(last)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const_iterator
|
|
|
|
|
begin() const
|
|
|
|
|
{
|
|
|
|
|
return first_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const_iterator
|
|
|
|
|
end() const
|
|
|
|
|
{
|
|
|
|
|
return last_;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
using view_type = buffer_cat_view<
|
|
|
|
|
boost::asio::const_buffers_1,
|
|
|
|
|
boost::asio::const_buffers_1,
|
|
|
|
|
boost::asio::const_buffers_1,
|
|
|
|
|
field_range,
|
|
|
|
|
chunk_crlf>;
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
basic_fields const& f_;
|
2017-07-09 20:09:30 -07:00
|
|
|
boost::optional<view_type> view_;
|
2017-06-23 09:33:28 -07:00
|
|
|
char buf_[13];
|
2017-06-06 17:26:11 -07:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
using const_buffers_type =
|
2017-07-09 20:09:30 -07:00
|
|
|
beast::detail::buffers_ref<view_type>;
|
2017-06-06 17:26:11 -07:00
|
|
|
|
2017-06-17 17:33:44 -07:00
|
|
|
reader(basic_fields const& f,
|
|
|
|
|
unsigned version, verb v);
|
2017-06-15 10:03:50 -07:00
|
|
|
|
2017-06-17 17:33:44 -07:00
|
|
|
reader(basic_fields const& f,
|
2017-06-17 17:38:24 -07:00
|
|
|
unsigned version, unsigned code);
|
2017-06-06 17:26:11 -07:00
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
reader(basic_fields const& f);
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
const_buffers_type
|
|
|
|
|
get() const
|
|
|
|
|
{
|
2017-07-09 20:09:30 -07:00
|
|
|
return const_buffers_type(*view_);
|
2017-06-06 17:26:11 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
basic_fields<Allocator>::reader::
|
|
|
|
|
reader(basic_fields const& f)
|
|
|
|
|
: f_(f)
|
|
|
|
|
{
|
|
|
|
|
view_.emplace(
|
|
|
|
|
boost::asio::const_buffers_1{nullptr, 0},
|
|
|
|
|
boost::asio::const_buffers_1{nullptr, 0},
|
|
|
|
|
boost::asio::const_buffers_1{nullptr, 0},
|
|
|
|
|
field_range(f_.list_.begin(), f_.list_.end()),
|
|
|
|
|
chunk_crlf());
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 10:03:50 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
basic_fields<Allocator>::reader::
|
2017-06-17 17:33:44 -07:00
|
|
|
reader(basic_fields const& f,
|
|
|
|
|
unsigned version, verb v)
|
2017-06-15 10:03:50 -07:00
|
|
|
: f_(f)
|
|
|
|
|
{
|
2017-06-23 09:33:28 -07:00
|
|
|
/*
|
|
|
|
|
request
|
|
|
|
|
"<method>"
|
|
|
|
|
" <target>"
|
|
|
|
|
" HTTP/X.Y\r\n" (11 chars)
|
|
|
|
|
*/
|
|
|
|
|
string_view sv;
|
|
|
|
|
if(v == verb::unknown)
|
|
|
|
|
sv = f_.get_method_impl();
|
|
|
|
|
else
|
|
|
|
|
sv = to_string(v);
|
|
|
|
|
|
|
|
|
|
// target_or_reason_ has a leading SP
|
|
|
|
|
|
|
|
|
|
buf_[0] = ' ';
|
|
|
|
|
buf_[1] = 'H';
|
|
|
|
|
buf_[2] = 'T';
|
|
|
|
|
buf_[3] = 'T';
|
|
|
|
|
buf_[4] = 'P';
|
|
|
|
|
buf_[5] = '/';
|
|
|
|
|
buf_[6] = '0' + static_cast<char>(version / 10);
|
|
|
|
|
buf_[7] = '.';
|
|
|
|
|
buf_[8] = '0' + static_cast<char>(version % 10);
|
|
|
|
|
buf_[9] = '\r';
|
|
|
|
|
buf_[10]= '\n';
|
2017-07-09 20:09:30 -07:00
|
|
|
|
|
|
|
|
view_.emplace(
|
|
|
|
|
boost::asio::const_buffers_1{sv.data(), sv.size()},
|
|
|
|
|
boost::asio::const_buffers_1{
|
|
|
|
|
f_.target_or_reason_.data(),
|
|
|
|
|
f_.target_or_reason_.size()},
|
|
|
|
|
boost::asio::const_buffers_1{buf_, 11},
|
|
|
|
|
field_range(f_.list_.begin(), f_.list_.end()),
|
|
|
|
|
chunk_crlf());
|
2017-06-15 10:03:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
basic_fields<Allocator>::reader::
|
2017-06-17 17:33:44 -07:00
|
|
|
reader(basic_fields const& f,
|
2017-06-17 17:38:24 -07:00
|
|
|
unsigned version, unsigned code)
|
2017-06-15 10:03:50 -07:00
|
|
|
: f_(f)
|
|
|
|
|
{
|
2017-06-23 09:33:28 -07:00
|
|
|
/*
|
|
|
|
|
response
|
|
|
|
|
"HTTP/X.Y ### " (13 chars)
|
|
|
|
|
"<reason>"
|
|
|
|
|
"\r\n"
|
|
|
|
|
*/
|
|
|
|
|
buf_[0] = 'H';
|
|
|
|
|
buf_[1] = 'T';
|
|
|
|
|
buf_[2] = 'T';
|
|
|
|
|
buf_[3] = 'P';
|
|
|
|
|
buf_[4] = '/';
|
|
|
|
|
buf_[5] = '0' + static_cast<char>(version / 10);
|
|
|
|
|
buf_[6] = '.';
|
|
|
|
|
buf_[7] = '0' + static_cast<char>(version % 10);
|
|
|
|
|
buf_[8] = ' ';
|
|
|
|
|
buf_[9] = '0' + static_cast<char>(code / 100);
|
|
|
|
|
buf_[10]= '0' + static_cast<char>((code / 10) % 10);
|
|
|
|
|
buf_[11]= '0' + static_cast<char>(code % 10);
|
|
|
|
|
buf_[12]= ' ';
|
|
|
|
|
|
|
|
|
|
string_view sv;
|
|
|
|
|
if(! f_.target_or_reason_.empty())
|
|
|
|
|
sv = f_.target_or_reason_;
|
|
|
|
|
else
|
|
|
|
|
sv = obsolete_reason(static_cast<status>(code));
|
|
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
view_.emplace(
|
|
|
|
|
boost::asio::const_buffers_1{buf_, 13},
|
|
|
|
|
boost::asio::const_buffers_1{sv.data(), sv.size()},
|
|
|
|
|
boost::asio::const_buffers_1{"\r\n", 2},
|
|
|
|
|
field_range(f_.list_.begin(), f_.list_.end()),
|
|
|
|
|
chunk_crlf{});
|
2017-06-15 10:03:50 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
value_type::
|
|
|
|
|
value_type(field name,
|
|
|
|
|
string_view sname, string_view value)
|
|
|
|
|
: off_(static_cast<off_t>(sname.size() + 2))
|
2017-06-06 11:56:06 -07:00
|
|
|
, len_(static_cast<off_t>(value.size()))
|
2017-06-10 15:05:32 -07:00
|
|
|
, f_(name)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
//BOOST_ASSERT(name == field::unknown ||
|
2017-06-16 09:38:53 -07:00
|
|
|
// iequals(sname, to_string(name)));
|
2017-06-06 11:56:06 -07:00
|
|
|
char* p = reinterpret_cast<char*>(this + 1);
|
|
|
|
|
p[off_-2] = ':';
|
|
|
|
|
p[off_-1] = ' ';
|
|
|
|
|
p[off_ + len_] = '\r';
|
|
|
|
|
p[off_ + len_ + 1] = '\n';
|
2017-06-10 15:05:32 -07:00
|
|
|
std::memcpy(p, sname.data(), sname.size());
|
2017-06-06 11:56:06 -07:00
|
|
|
std::memcpy(p + off_, value.data(), value.size());
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
2017-06-10 15:05:32 -07:00
|
|
|
field
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
value_type::
|
2017-06-06 11:56:06 -07:00
|
|
|
name() const
|
2017-06-10 15:05:32 -07:00
|
|
|
{
|
|
|
|
|
return f_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
string_view
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
value_type::
|
|
|
|
|
name_string() const
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
return {reinterpret_cast<
|
|
|
|
|
char const*>(this + 1),
|
|
|
|
|
static_cast<std::size_t>(off_ - 2)};
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
2017-06-06 11:56:06 -07:00
|
|
|
string_view
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
value_type::
|
2017-06-06 11:56:06 -07:00
|
|
|
value() const
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
return {reinterpret_cast<
|
|
|
|
|
char const*>(this + 1) + off_,
|
|
|
|
|
static_cast<std::size_t>(len_)};
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
boost::asio::const_buffer
|
|
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
value_type::
|
2017-06-06 17:26:11 -07:00
|
|
|
buffer() const
|
|
|
|
|
{
|
|
|
|
|
return boost::asio::const_buffer{
|
|
|
|
|
reinterpret_cast<char const*>(this + 1),
|
|
|
|
|
static_cast<std::size_t>(off_) + len_ + 2};
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 11:56:06 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
~basic_fields()
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
delete_list();
|
|
|
|
|
realloc_string(method_, {});
|
2017-06-23 09:33:28 -07:00
|
|
|
realloc_string(
|
|
|
|
|
target_or_reason_, {});
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields(Allocator const& alloc)
|
|
|
|
|
: alloc_(alloc)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-05 09:58:55 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields(basic_fields&& other)
|
2017-06-10 14:28:15 -07:00
|
|
|
: alloc_(std::move(other.alloc_))
|
|
|
|
|
, set_(std::move(other.set_))
|
2017-06-06 11:56:06 -07:00
|
|
|
, list_(std::move(other.list_))
|
|
|
|
|
, method_(other.method_)
|
|
|
|
|
, target_or_reason_(other.target_or_reason_)
|
2017-06-05 09:58:55 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
other.method_.clear();
|
|
|
|
|
other.target_or_reason_.clear();
|
2017-06-05 09:58:55 -07:00
|
|
|
}
|
2017-07-20 08:01:46 -07:00
|
|
|
|
2017-06-10 14:28:15 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
basic_fields(basic_fields&& other, Allocator const& alloc)
|
|
|
|
|
: alloc_(alloc)
|
|
|
|
|
{
|
|
|
|
|
if(alloc_ != other.alloc_)
|
|
|
|
|
{
|
|
|
|
|
copy_all(other);
|
|
|
|
|
other.clear_all();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
set_ = std::move(other.set_);
|
|
|
|
|
list_ = std::move(other.list_);
|
|
|
|
|
method_ = other.method_;
|
|
|
|
|
target_or_reason_ = other.target_or_reason_;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields(basic_fields const& other)
|
2017-06-10 14:28:15 -07:00
|
|
|
: alloc_(alloc_traits::
|
2017-06-06 11:56:06 -07:00
|
|
|
select_on_container_copy_construction(other.alloc_))
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
copy_all(other);
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields(basic_fields const& other,
|
|
|
|
|
Allocator const& alloc)
|
2017-06-05 09:58:55 -07:00
|
|
|
: alloc_(alloc)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
copy_all(other);
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
2017-06-10 14:28:15 -07:00
|
|
|
template<class OtherAlloc>
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields(basic_fields<OtherAlloc> const& other)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
copy_all(other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
2017-06-10 14:28:15 -07:00
|
|
|
template<class OtherAlloc>
|
2017-06-06 11:56:06 -07:00
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
basic_fields(basic_fields<OtherAlloc> const& other,
|
|
|
|
|
Allocator const& alloc)
|
|
|
|
|
: alloc_(alloc)
|
|
|
|
|
{
|
|
|
|
|
copy_all(other);
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
operator=(basic_fields&& other) ->
|
|
|
|
|
basic_fields&
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
|
|
|
|
if(this == &other)
|
|
|
|
|
return *this;
|
2017-06-06 11:56:06 -07:00
|
|
|
move_assign(other, typename alloc_traits::
|
|
|
|
|
propagate_on_container_move_assignment{});
|
2017-07-20 08:01:46 -07:00
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
operator=(basic_fields const& other) ->
|
|
|
|
|
basic_fields&
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
copy_assign(other, typename alloc_traits::
|
|
|
|
|
propagate_on_container_copy_assignment{});
|
2017-07-20 08:01:46 -07:00
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
2017-06-10 14:28:15 -07:00
|
|
|
template<class OtherAlloc>
|
2017-07-20 08:01:46 -07:00
|
|
|
auto
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
operator=(basic_fields<OtherAlloc> const& other) ->
|
|
|
|
|
basic_fields&
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
clear_all();
|
|
|
|
|
copy_all(other);
|
2017-07-20 08:01:46 -07:00
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-10 15:05:32 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Element access
|
|
|
|
|
//
|
2017-06-06 11:56:06 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2016-07-04 13:57:59 -04:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
string_view
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
at(field name) const
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
auto const it = find(name);
|
|
|
|
|
if(it == end())
|
|
|
|
|
BOOST_THROW_EXCEPTION(std::out_of_range{
|
|
|
|
|
"field not found"});
|
|
|
|
|
return it->value();
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-08 22:03:58 -07:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
string_view
|
2017-06-08 22:03:58 -07:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
at(string_view name) const
|
2017-06-08 22:03:58 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
auto const it = find(name);
|
|
|
|
|
if(it == end())
|
|
|
|
|
BOOST_THROW_EXCEPTION(std::out_of_range{
|
|
|
|
|
"field not found"});
|
|
|
|
|
return it->value();
|
2017-06-08 22:03:58 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
string_view
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
operator[](field name) const
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
2017-07-20 08:01:46 -07:00
|
|
|
auto const it = find(name);
|
|
|
|
|
if(it == end())
|
2016-07-04 13:57:59 -04:00
|
|
|
return {};
|
2017-06-05 09:58:55 -07:00
|
|
|
return it->value();
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-08 22:03:58 -07:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
string_view
|
2017-06-08 22:03:58 -07:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
operator[](string_view name) const
|
2017-06-08 22:03:58 -07:00
|
|
|
{
|
|
|
|
|
auto const it = find(name);
|
|
|
|
|
if(it == end())
|
|
|
|
|
return {};
|
|
|
|
|
return it->value();
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-10 15:05:32 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Modifiers
|
|
|
|
|
//
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
clear()
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-06 11:56:06 -07:00
|
|
|
delete_list();
|
2017-07-20 08:01:46 -07:00
|
|
|
set_.clear();
|
2017-06-06 11:56:06 -07:00
|
|
|
list_.clear();
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-06 12:49:03 -07:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
inline
|
|
|
|
|
void
|
2017-06-06 12:49:03 -07:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
insert(field name, string_param const& value)
|
2017-06-06 12:49:03 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
insert(name, to_string(name), value);
|
2017-06-06 12:49:03 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
2017-06-10 15:05:32 -07:00
|
|
|
void
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
insert(string_view sname, string_param const& value)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
auto const name =
|
|
|
|
|
string_to_field(sname);
|
|
|
|
|
insert(name, sname, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
insert(field name,
|
|
|
|
|
string_view sname, string_param const& value)
|
|
|
|
|
{
|
2017-06-22 16:49:46 -07:00
|
|
|
auto& e = new_element(name, sname,
|
|
|
|
|
static_cast<string_view>(value));
|
2017-06-10 15:05:32 -07:00
|
|
|
auto const before =
|
|
|
|
|
set_.upper_bound(sname, key_compare{});
|
|
|
|
|
if(before == set_.begin())
|
|
|
|
|
{
|
2017-06-15 08:31:47 -07:00
|
|
|
BOOST_ASSERT(count(sname) == 0);
|
|
|
|
|
set_.insert_before(before, e);
|
2017-06-10 15:05:32 -07:00
|
|
|
list_.push_back(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto const last = std::prev(before);
|
2017-06-15 08:31:47 -07:00
|
|
|
// VFALCO is it worth comparing `field name` first?
|
2017-06-16 09:38:53 -07:00
|
|
|
if(! iequals(sname, last->name_string()))
|
2017-06-10 15:05:32 -07:00
|
|
|
{
|
2017-06-15 08:31:47 -07:00
|
|
|
BOOST_ASSERT(count(sname) == 0);
|
2017-06-10 15:05:32 -07:00
|
|
|
set_.insert_before(before, e);
|
|
|
|
|
list_.push_back(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-06-15 08:31:47 -07:00
|
|
|
// keep duplicate fields together in the list
|
2017-06-10 15:05:32 -07:00
|
|
|
set_.insert_before(before, e);
|
2017-06-15 08:31:47 -07:00
|
|
|
list_.insert(++list_.iterator_to(*last), e);
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-06 12:49:03 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
2017-06-15 06:55:43 -07:00
|
|
|
set(field name, string_param const& value)
|
2017-06-06 12:49:03 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
2017-06-22 16:49:46 -07:00
|
|
|
set_element(new_element(name, to_string(name),
|
|
|
|
|
static_cast<string_view>(value)));
|
2017-06-06 12:49:03 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-15 06:55:43 -07:00
|
|
|
set(string_view sname, string_param const& value)
|
2017-06-10 15:05:32 -07:00
|
|
|
{
|
2017-06-15 09:34:24 -07:00
|
|
|
set_element(new_element(
|
2017-06-22 16:49:46 -07:00
|
|
|
string_to_field(sname), sname,
|
|
|
|
|
static_cast<string_view>(value)));
|
2017-06-10 15:05:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
erase(const_iterator pos) ->
|
|
|
|
|
const_iterator
|
|
|
|
|
{
|
|
|
|
|
auto next = pos.iter();
|
|
|
|
|
auto& e = *next++;
|
|
|
|
|
set_.erase(e);
|
|
|
|
|
list_.erase(e);
|
|
|
|
|
delete_element(e);
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
std::size_t
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
erase(field name)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
return erase(to_string(name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
std::size_t
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
erase(string_view name)
|
|
|
|
|
{
|
|
|
|
|
std::size_t n =0;
|
|
|
|
|
set_.erase_and_dispose(name, key_compare{},
|
|
|
|
|
[&](value_type* e)
|
|
|
|
|
{
|
|
|
|
|
++n;
|
|
|
|
|
list_.erase(list_.iterator_to(*e));
|
|
|
|
|
delete_element(*e);
|
|
|
|
|
});
|
|
|
|
|
return n;
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
2016-11-10 05:34:49 -05:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
swap(basic_fields<Allocator>& other)
|
2017-07-20 08:01:46 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
swap(other, typename alloc_traits::
|
|
|
|
|
propagate_on_container_swap{});
|
2017-07-20 08:01:46 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-08 22:03:58 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
2017-06-10 15:05:32 -07:00
|
|
|
swap(
|
|
|
|
|
basic_fields<Allocator>& lhs,
|
|
|
|
|
basic_fields<Allocator>& rhs)
|
|
|
|
|
{
|
|
|
|
|
lhs.swap(rhs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Lookup
|
|
|
|
|
//
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
std::size_t
|
2017-06-08 22:03:58 -07:00
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
count(field name) const
|
2017-06-08 22:03:58 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
return count(to_string(name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
std::size_t
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
count(string_view name) const
|
|
|
|
|
{
|
|
|
|
|
return set_.count(name, key_compare{});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
find(field name) const ->
|
|
|
|
|
const_iterator
|
|
|
|
|
{
|
|
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
return find(to_string(name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
find(string_view name) const ->
|
|
|
|
|
const_iterator
|
|
|
|
|
{
|
|
|
|
|
auto const it = set_.find(
|
|
|
|
|
name, key_compare{});
|
|
|
|
|
if(it == set_.end())
|
|
|
|
|
return list_.end();
|
|
|
|
|
return list_.iterator_to(*it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
equal_range(field name) const ->
|
|
|
|
|
std::pair<const_iterator, const_iterator>
|
|
|
|
|
{
|
|
|
|
|
BOOST_ASSERT(name != field::unknown);
|
|
|
|
|
return equal_range(to_string(name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
equal_range(string_view name) const ->
|
|
|
|
|
std::pair<const_iterator, const_iterator>
|
|
|
|
|
{
|
|
|
|
|
auto const result =
|
|
|
|
|
set_.equal_range(name, key_compare{});
|
|
|
|
|
return {
|
|
|
|
|
list_.iterator_to(result->first),
|
|
|
|
|
list_.iterator_to(result->second)};
|
2017-06-08 22:03:58 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-06 11:56:06 -07:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
namespace detail {
|
2017-06-06 17:26:11 -07:00
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
// Filter a token list
|
|
|
|
|
//
|
|
|
|
|
template<class String, class Pred>
|
2017-06-06 11:56:06 -07:00
|
|
|
void
|
2017-07-09 05:26:27 -07:00
|
|
|
filter_token_list(
|
|
|
|
|
String& s,
|
2017-07-12 16:35:52 -07:00
|
|
|
string_view value,
|
2017-07-09 05:26:27 -07:00
|
|
|
Pred&& pred)
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
token_list te{value};
|
|
|
|
|
auto it = te.begin();
|
|
|
|
|
auto last = te.end();
|
|
|
|
|
if(it == last)
|
|
|
|
|
return;
|
|
|
|
|
while(pred(*it))
|
|
|
|
|
if(++it == last)
|
|
|
|
|
return;
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
while(++it != last)
|
|
|
|
|
{
|
|
|
|
|
if(! pred(*it))
|
|
|
|
|
{
|
|
|
|
|
s.append(", ");
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-06 11:56:06 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
// Filter the last item in a token list
|
|
|
|
|
template<class String, class Pred>
|
2017-06-06 11:56:06 -07:00
|
|
|
void
|
2017-07-09 05:26:27 -07:00
|
|
|
filter_token_list_last(
|
|
|
|
|
String& s,
|
2017-07-12 16:35:52 -07:00
|
|
|
string_view value,
|
2017-07-09 05:26:27 -07:00
|
|
|
Pred&& pred)
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
token_list te{value};
|
|
|
|
|
if(te.begin() != te.end())
|
|
|
|
|
{
|
|
|
|
|
auto it = te.begin();
|
|
|
|
|
auto next = std::next(it);
|
|
|
|
|
if(next == te.end())
|
|
|
|
|
{
|
|
|
|
|
if(! pred(*it))
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
for(;;)
|
|
|
|
|
{
|
|
|
|
|
it = next;
|
|
|
|
|
next = std::next(it);
|
|
|
|
|
if(next == te.end())
|
|
|
|
|
{
|
|
|
|
|
if(! pred(*it))
|
|
|
|
|
{
|
|
|
|
|
s.append(", ");
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
s.append(", ");
|
|
|
|
|
s.append(it->data(), it->size());
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-06 11:56:06 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
template<class String>
|
2017-06-06 11:56:06 -07:00
|
|
|
void
|
2017-07-09 05:26:27 -07:00
|
|
|
keep_alive_impl(
|
2017-07-12 16:35:52 -07:00
|
|
|
String& s, string_view value,
|
2017-07-09 05:26:27 -07:00
|
|
|
unsigned version, bool keep_alive)
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
if(version < 11)
|
|
|
|
|
{
|
|
|
|
|
if(keep_alive)
|
|
|
|
|
{
|
|
|
|
|
// remove close
|
|
|
|
|
filter_token_list(s, value,
|
|
|
|
|
[](string_view s)
|
|
|
|
|
{
|
|
|
|
|
return iequals(s, "close");
|
|
|
|
|
});
|
|
|
|
|
// add keep-alive
|
|
|
|
|
if(s.empty())
|
|
|
|
|
s.append("keep-alive");
|
|
|
|
|
else if(! token_list{value}.exists("keep-alive"))
|
|
|
|
|
s.append(", keep-alive");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// remove close and keep-alive
|
|
|
|
|
filter_token_list(s, value,
|
|
|
|
|
[](string_view s)
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
iequals(s, "close") ||
|
|
|
|
|
iequals(s, "keep-alive");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if(keep_alive)
|
|
|
|
|
{
|
|
|
|
|
// remove close and keep-alive
|
|
|
|
|
filter_token_list(s, value,
|
|
|
|
|
[](string_view s)
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
iequals(s, "close") ||
|
|
|
|
|
iequals(s, "keep-alive");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// remove keep-alive
|
|
|
|
|
filter_token_list(s, value,
|
|
|
|
|
[](string_view s)
|
|
|
|
|
{
|
|
|
|
|
return iequals(s, "keep-alive");
|
|
|
|
|
});
|
|
|
|
|
// add close
|
|
|
|
|
if(s.empty())
|
|
|
|
|
s.append("close");
|
|
|
|
|
else if(! token_list{value}.exists("close"))
|
|
|
|
|
s.append(", close");
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-06 11:56:06 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
} // detail
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// Fields
|
|
|
|
|
|
2017-06-06 17:26:11 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
string_view
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
get_method_impl() const
|
|
|
|
|
{
|
|
|
|
|
return method_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
string_view
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
get_target_impl() const
|
|
|
|
|
{
|
2017-06-23 09:33:28 -07:00
|
|
|
if(target_or_reason_.empty())
|
|
|
|
|
return target_or_reason_;
|
|
|
|
|
return {
|
|
|
|
|
target_or_reason_.data() + 1,
|
|
|
|
|
target_or_reason_.size() - 1};
|
2017-06-06 17:26:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
string_view
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
get_reason_impl() const
|
|
|
|
|
{
|
|
|
|
|
return target_or_reason_;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
bool
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
get_chunked_impl() const
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
auto const te = token_list{
|
|
|
|
|
(*this)[field::transfer_encoding]};
|
|
|
|
|
for(auto it = te.begin(); it != te.end();)
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
auto const next = std::next(it);
|
2017-06-19 15:37:39 -07:00
|
|
|
if(next == te.end())
|
2017-07-09 05:26:27 -07:00
|
|
|
return iequals(*it, "chunked");
|
|
|
|
|
it = next;
|
2017-06-19 15:37:39 -07:00
|
|
|
}
|
2017-07-09 05:26:27 -07:00
|
|
|
return false;
|
2017-06-06 11:56:06 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-09 05:26:27 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
bool
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
get_keep_alive_impl(unsigned version) const
|
|
|
|
|
{
|
|
|
|
|
auto const it = find(field::connection);
|
|
|
|
|
if(version < 11)
|
|
|
|
|
{
|
|
|
|
|
if(it == end())
|
|
|
|
|
return false;
|
|
|
|
|
return token_list{
|
|
|
|
|
it->value()}.exists("keep-alive");
|
|
|
|
|
}
|
|
|
|
|
if(it == end())
|
|
|
|
|
return true;
|
|
|
|
|
return ! token_list{
|
|
|
|
|
it->value()}.exists("close");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_method_impl(string_view s)
|
|
|
|
|
{
|
|
|
|
|
realloc_string(method_, s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_target_impl(string_view s)
|
|
|
|
|
{
|
|
|
|
|
realloc_target(
|
|
|
|
|
target_or_reason_, s);
|
|
|
|
|
}
|
2017-06-19 15:37:39 -07:00
|
|
|
|
2017-06-06 11:56:06 -07:00
|
|
|
template<class Allocator>
|
2017-07-09 05:26:27 -07:00
|
|
|
inline
|
2017-06-06 11:56:06 -07:00
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
2017-07-09 05:26:27 -07:00
|
|
|
set_reason_impl(string_view s)
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
realloc_string(
|
|
|
|
|
target_or_reason_, s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_chunked_impl(bool value)
|
|
|
|
|
{
|
|
|
|
|
auto it = find(field::transfer_encoding);
|
|
|
|
|
if(value)
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
// append "chunked"
|
2017-06-19 15:37:39 -07:00
|
|
|
if(it == end())
|
|
|
|
|
{
|
|
|
|
|
set(field::transfer_encoding, "chunked");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-07-09 05:26:27 -07:00
|
|
|
auto const te = token_list{it->value()};
|
|
|
|
|
for(auto itt = te.begin();;)
|
|
|
|
|
{
|
|
|
|
|
auto const next = std::next(itt);
|
|
|
|
|
if(next == te.end())
|
|
|
|
|
{
|
|
|
|
|
if(iequals(*itt, "chunked"))
|
|
|
|
|
return; // already set
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
itt = next;
|
|
|
|
|
}
|
|
|
|
|
static_string<max_static_buffer> buf;
|
|
|
|
|
if(it->value().size() <= buf.size() + 9)
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
buf.append(it->value().data(), it->value().size());
|
|
|
|
|
buf.append(", chunked", 9);
|
|
|
|
|
set(field::transfer_encoding, buf);
|
2017-06-19 15:37:39 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-07-20 13:40:34 -07:00
|
|
|
#ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
|
2017-07-09 05:26:27 -07:00
|
|
|
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
|
2017-06-19 15:37:39 -07:00
|
|
|
std::string s;
|
2017-07-09 05:26:27 -07:00
|
|
|
#else
|
|
|
|
|
using rebind_type =
|
|
|
|
|
typename std::allocator_traits<
|
|
|
|
|
Allocator>::template rebind_alloc<char>;
|
|
|
|
|
std::basic_string<
|
|
|
|
|
char,
|
|
|
|
|
std::char_traits<char>,
|
|
|
|
|
rebind_type> s{rebind_type{alloc_}};
|
|
|
|
|
#endif
|
2017-06-19 15:37:39 -07:00
|
|
|
s.reserve(it->value().size() + 9);
|
|
|
|
|
s.append(it->value().data(), it->value().size());
|
|
|
|
|
s.append(", chunked", 9);
|
|
|
|
|
set(field::transfer_encoding, s);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-07-09 05:26:27 -07:00
|
|
|
// filter "chunked"
|
|
|
|
|
if(it == end())
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
static_string<max_static_buffer> buf;
|
|
|
|
|
detail::filter_token_list_last(buf, it->value(),
|
2017-07-12 16:35:52 -07:00
|
|
|
[](string_view s)
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
return iequals(s, "chunked");
|
|
|
|
|
});
|
|
|
|
|
if(! buf.empty())
|
|
|
|
|
set(field::transfer_encoding, buf);
|
|
|
|
|
else
|
|
|
|
|
erase(field::transfer_encoding);
|
|
|
|
|
}
|
|
|
|
|
catch(std::length_error const&)
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-20 13:40:34 -07:00
|
|
|
#ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
|
2017-07-09 05:26:27 -07:00
|
|
|
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
|
|
|
|
|
std::string s;
|
|
|
|
|
#else
|
|
|
|
|
using rebind_type =
|
|
|
|
|
typename std::allocator_traits<
|
|
|
|
|
Allocator>::template rebind_alloc<char>;
|
|
|
|
|
std::basic_string<
|
|
|
|
|
char,
|
|
|
|
|
std::char_traits<char>,
|
|
|
|
|
rebind_type> s{rebind_type{alloc_}};
|
|
|
|
|
#endif
|
|
|
|
|
s.reserve(it->value().size());
|
|
|
|
|
detail::filter_token_list_last(s, it->value(),
|
2017-07-12 16:35:52 -07:00
|
|
|
[](string_view s)
|
2017-07-09 05:26:27 -07:00
|
|
|
{
|
|
|
|
|
return iequals(s, "chunked");
|
|
|
|
|
});
|
|
|
|
|
if(! s.empty())
|
|
|
|
|
set(field::transfer_encoding, s);
|
|
|
|
|
else
|
|
|
|
|
erase(field::transfer_encoding);
|
2017-06-19 15:37:39 -07:00
|
|
|
}
|
2017-07-09 05:26:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_content_length_impl(
|
|
|
|
|
boost::optional<std::uint64_t> const& value)
|
|
|
|
|
{
|
|
|
|
|
if(! value)
|
|
|
|
|
erase(field::content_length);
|
2017-06-06 11:56:06 -07:00
|
|
|
else
|
2017-07-09 05:26:27 -07:00
|
|
|
set(field::content_length, *value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_keep_alive_impl(
|
|
|
|
|
unsigned version, bool keep_alive)
|
|
|
|
|
{
|
|
|
|
|
// VFALCO What about Proxy-Connection ?
|
|
|
|
|
auto const value = (*this)[field::connection];
|
|
|
|
|
try
|
2017-06-19 15:37:39 -07:00
|
|
|
{
|
2017-07-09 05:26:27 -07:00
|
|
|
static_string<max_static_buffer> buf;
|
|
|
|
|
detail::keep_alive_impl(
|
|
|
|
|
buf, value, version, keep_alive);
|
|
|
|
|
if(buf.empty())
|
|
|
|
|
erase(field::connection);
|
|
|
|
|
else
|
|
|
|
|
set(field::connection, buf);
|
|
|
|
|
}
|
|
|
|
|
catch(std::length_error const&)
|
|
|
|
|
{
|
2017-07-20 13:40:34 -07:00
|
|
|
#ifdef BOOST_BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
|
2017-07-09 05:26:27 -07:00
|
|
|
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
|
|
|
|
|
std::string s;
|
|
|
|
|
#else
|
|
|
|
|
using rebind_type =
|
|
|
|
|
typename std::allocator_traits<
|
|
|
|
|
Allocator>::template rebind_alloc<char>;
|
|
|
|
|
std::basic_string<
|
|
|
|
|
char,
|
|
|
|
|
std::char_traits<char>,
|
|
|
|
|
rebind_type> s{rebind_type{alloc_}};
|
|
|
|
|
#endif
|
|
|
|
|
s.reserve(value.size());
|
|
|
|
|
detail::keep_alive_impl(
|
|
|
|
|
s, value, version, keep_alive);
|
|
|
|
|
if(s.empty())
|
|
|
|
|
erase(field::connection);
|
|
|
|
|
else
|
|
|
|
|
set(field::connection, s);
|
2017-06-19 15:37:39 -07:00
|
|
|
}
|
2017-06-06 11:56:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
auto
|
|
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
new_element(field name,
|
|
|
|
|
string_view sname, string_view value) ->
|
|
|
|
|
value_type&
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-06-10 15:05:32 -07:00
|
|
|
if(sname.size() + 2 >
|
2017-06-06 11:56:06 -07:00
|
|
|
(std::numeric_limits<off_t>::max)())
|
|
|
|
|
BOOST_THROW_EXCEPTION(std::length_error{
|
|
|
|
|
"field name too large"});
|
|
|
|
|
if(value.size() + 2 >
|
|
|
|
|
(std::numeric_limits<off_t>::max)())
|
|
|
|
|
BOOST_THROW_EXCEPTION(std::length_error{
|
|
|
|
|
"field value too large"});
|
|
|
|
|
value = detail::trim(value);
|
|
|
|
|
std::uint16_t const off =
|
2017-06-10 15:05:32 -07:00
|
|
|
static_cast<off_t>(sname.size() + 2);
|
2017-06-06 11:56:06 -07:00
|
|
|
std::uint16_t const len =
|
|
|
|
|
static_cast<off_t>(value.size());
|
|
|
|
|
auto const p = alloc_traits::allocate(alloc_,
|
2017-06-10 15:05:32 -07:00
|
|
|
1 + (off + len + 2 + sizeof(value_type) - 1) /
|
|
|
|
|
sizeof(value_type));
|
|
|
|
|
// VFALCO allocator can't call the constructor because its private
|
|
|
|
|
//alloc_traits::construct(alloc_, p, name, sname, value);
|
|
|
|
|
new(p) value_type{name, sname, value};
|
2017-06-06 11:56:06 -07:00
|
|
|
return *p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
2017-06-10 15:05:32 -07:00
|
|
|
delete_element(value_type& e)
|
2017-06-06 11:56:06 -07:00
|
|
|
{
|
2017-06-15 09:34:24 -07:00
|
|
|
auto const n = 1 + (e.off_ + e.len_ + 2 +
|
|
|
|
|
sizeof(value_type) - 1) / sizeof(value_type);
|
2017-06-06 11:56:06 -07:00
|
|
|
alloc_traits::destroy(alloc_, &e);
|
|
|
|
|
alloc_traits::deallocate(alloc_, &e, n);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 09:34:24 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
set_element(value_type& e)
|
|
|
|
|
{
|
|
|
|
|
auto it = set_.lower_bound(
|
|
|
|
|
e.name_string(), key_compare{});
|
2017-06-16 09:38:53 -07:00
|
|
|
if(it == set_.end() || ! iequals(
|
2017-06-15 09:34:24 -07:00
|
|
|
e.name_string(), it->name_string()))
|
|
|
|
|
{
|
|
|
|
|
set_.insert_before(it, e);
|
|
|
|
|
list_.push_back(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for(;;)
|
|
|
|
|
{
|
|
|
|
|
auto next = it;
|
|
|
|
|
++next;
|
|
|
|
|
set_.erase(it);
|
|
|
|
|
list_.erase(list_.iterator_to(*it));
|
|
|
|
|
delete_element(*it);
|
|
|
|
|
it = next;
|
|
|
|
|
if(it == set_.end() ||
|
2017-06-16 09:38:53 -07:00
|
|
|
! iequals(e.name_string(), it->name_string()))
|
2017-06-15 09:34:24 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
set_.insert_before(it, e);
|
|
|
|
|
list_.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 11:56:06 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
realloc_string(string_view& dest, string_view s)
|
|
|
|
|
{
|
|
|
|
|
if(dest.empty() && s.empty())
|
|
|
|
|
return;
|
|
|
|
|
auto a = typename std::allocator_traits<
|
|
|
|
|
Allocator>::template rebind_alloc<
|
|
|
|
|
char>(alloc_);
|
|
|
|
|
if(! dest.empty())
|
|
|
|
|
{
|
|
|
|
|
a.deallocate(const_cast<char*>(
|
|
|
|
|
dest.data()), dest.size());
|
|
|
|
|
dest.clear();
|
|
|
|
|
}
|
|
|
|
|
if(! s.empty())
|
|
|
|
|
{
|
|
|
|
|
auto const p = a.allocate(s.size());
|
|
|
|
|
std::memcpy(p, s.data(), s.size());
|
|
|
|
|
dest = {p, s.size()};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-23 09:33:28 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
realloc_target(
|
|
|
|
|
string_view& dest, string_view s)
|
|
|
|
|
{
|
|
|
|
|
// The target string are stored with an
|
|
|
|
|
// extra space at the beginning to help
|
|
|
|
|
// the reader class.
|
|
|
|
|
if(dest.empty() && s.empty())
|
|
|
|
|
return;
|
|
|
|
|
auto a = typename std::allocator_traits<
|
|
|
|
|
Allocator>::template rebind_alloc<
|
|
|
|
|
char>(alloc_);
|
|
|
|
|
if(! dest.empty())
|
|
|
|
|
{
|
|
|
|
|
a.deallocate(const_cast<char*>(
|
|
|
|
|
dest.data()), dest.size());
|
|
|
|
|
dest.clear();
|
|
|
|
|
}
|
|
|
|
|
if(! s.empty())
|
|
|
|
|
{
|
|
|
|
|
auto const p = a.allocate(1 + s.size());
|
|
|
|
|
p[0] = ' ';
|
|
|
|
|
std::memcpy(p + 1, s.data(), s.size());
|
|
|
|
|
dest = {p, 1 + s.size()};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 11:56:06 -07:00
|
|
|
template<class Allocator>
|
|
|
|
|
template<class OtherAlloc>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
copy_all(basic_fields<OtherAlloc> const& other)
|
|
|
|
|
{
|
|
|
|
|
for(auto const& e : other.list_)
|
2017-06-10 15:05:32 -07:00
|
|
|
insert(e.name(), e.name_string(), e.value());
|
2017-06-06 11:56:06 -07:00
|
|
|
realloc_string(method_, other.method_);
|
|
|
|
|
realloc_string(target_or_reason_,
|
|
|
|
|
other.target_or_reason_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
clear_all()
|
|
|
|
|
{
|
|
|
|
|
clear();
|
|
|
|
|
realloc_string(method_, {});
|
|
|
|
|
realloc_string(target_or_reason_, {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
delete_list()
|
|
|
|
|
{
|
|
|
|
|
for(auto it = list_.begin(); it != list_.end();)
|
|
|
|
|
delete_element(*it++);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
move_assign(basic_fields& other, std::true_type)
|
|
|
|
|
{
|
|
|
|
|
clear_all();
|
|
|
|
|
set_ = std::move(other.set_);
|
|
|
|
|
list_ = std::move(other.list_);
|
|
|
|
|
method_ = other.method_;
|
|
|
|
|
target_or_reason_ = other.target_or_reason_;
|
|
|
|
|
other.method_.clear();
|
|
|
|
|
other.target_or_reason_.clear();
|
|
|
|
|
alloc_ = other.alloc_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
move_assign(basic_fields& other, std::false_type)
|
|
|
|
|
{
|
|
|
|
|
clear_all();
|
|
|
|
|
if(alloc_ != other.alloc_)
|
|
|
|
|
{
|
|
|
|
|
copy_all(other);
|
|
|
|
|
other.clear_all();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
set_ = std::move(other.set_);
|
|
|
|
|
list_ = std::move(other.list_);
|
|
|
|
|
method_ = other.method_;
|
|
|
|
|
target_or_reason_ = other.target_or_reason_;
|
|
|
|
|
other.method_.clear();
|
|
|
|
|
other.target_or_reason_.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
copy_assign(basic_fields const& other, std::true_type)
|
|
|
|
|
{
|
|
|
|
|
clear_all();
|
|
|
|
|
alloc_ = other.alloc_;
|
|
|
|
|
copy_all(other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
copy_assign(basic_fields const& other, std::false_type)
|
|
|
|
|
{
|
|
|
|
|
clear_all();
|
|
|
|
|
copy_all(other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
swap(basic_fields& other, std::true_type)
|
|
|
|
|
{
|
|
|
|
|
using std::swap;
|
|
|
|
|
swap(alloc_, other.alloc_);
|
|
|
|
|
swap(set_, other.set_);
|
|
|
|
|
swap(list_, other.list_);
|
|
|
|
|
swap(method_, other.method_);
|
|
|
|
|
swap(target_or_reason_, other.target_or_reason_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class Allocator>
|
|
|
|
|
inline
|
|
|
|
|
void
|
|
|
|
|
basic_fields<Allocator>::
|
|
|
|
|
swap(basic_fields& other, std::false_type)
|
|
|
|
|
{
|
|
|
|
|
BOOST_ASSERT(alloc_ == other.alloc_);
|
|
|
|
|
using std::swap;
|
|
|
|
|
swap(set_, other.set_);
|
|
|
|
|
swap(list_, other.list_);
|
|
|
|
|
swap(method_, other.method_);
|
|
|
|
|
swap(target_or_reason_, other.target_or_reason_);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-20 08:01:46 -07:00
|
|
|
} // http
|
|
|
|
|
} // beast
|
2017-07-20 13:40:34 -07:00
|
|
|
} // boost
|
2017-07-20 08:01:46 -07:00
|
|
|
|
|
|
|
|
#endif
|