Files
boost_beast/include/beast/http/impl/basic_parser_v1.ipp
Vinnie Falco 589e18c199 rfc7230 compliance, limits, and tests for basic_parser_v1:
New parser set_option function for controlling independent size limits
on headers and body. By default request and response parsers are set up
with reasonable limits to prevent resource exhaustion attacks.

* Parser adheres strictly to rfc7230
* Increased test coverage
* Headers and body maximum size limit options
2017-07-20 08:12:07 -07:00

1201 lines
36 KiB
C++

//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
*
* Additional changes are licensed under the same terms as NGINX and
* copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/*
This code is a modified version of nodejs/http-parser, copyright above:
https://github.com/nodejs/http-parser
*/
#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP
#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP
#include <beast/http/detail/rfc7230.hpp>
#include <beast/core/buffer_concepts.hpp>
#include <cassert>
namespace beast {
namespace http {
template<bool isRequest, class Derived>
basic_parser_v1<isRequest, Derived>::
basic_parser_v1()
{
init();
}
template<bool isRequest, class Derived>
bool
basic_parser_v1<isRequest, Derived>::
keep_alive() const
{
if(http_major_ >= 1 && http_minor_ >= 1)
{
if(flags_ & parse_flag::connection_close)
return false;
}
else
{
if(! (flags_ & parse_flag::connection_keep_alive))
return false;
}
return ! needs_eof();
}
template<bool isRequest, class Derived>
template<class ConstBufferSequence>
typename std::enable_if<
! std::is_convertible<ConstBufferSequence,
boost::asio::const_buffer>::value,
std::size_t>::type
basic_parser_v1<isRequest, Derived>::
write(ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(is_ConstBufferSequence<ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
std::size_t used = 0;
for(auto const& buffer : buffers)
{
used += write(buffer, ec);
if(ec)
break;
}
return used;
}
template<bool isRequest, class Derived>
std::size_t
basic_parser_v1<isRequest, Derived>::
write(boost::asio::const_buffer const& buffer, error_code& ec)
{
using beast::http::detail::is_digit;
using beast::http::detail::is_tchar;
using beast::http::detail::is_text;
using beast::http::detail::to_field_char;
using beast::http::detail::to_value_char;
using beast::http::detail::unhex;
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto const data = buffer_cast<void const*>(buffer);
auto const size = buffer_size(buffer);
if(size == 0 && s_ != s_dead)
return 0;
auto begin =
reinterpret_cast<char const*>(data);
auto const end = begin + size;
auto p = begin;
auto used = [&]
{
return p - reinterpret_cast<char const*>(data);
};
auto err = [&](parse_error ev)
{
ec = ev;
s_ = s_dead;
return used();
};
auto errc = [&]
{
s_ = s_dead;
return used();
};
auto piece = [&]
{
return boost::string_ref{
begin, static_cast<std::size_t>(p - begin)};
};
auto cb = [&](pmf_t next)
{
if(cb_ && p != begin)
{
(this->*cb_)(ec, piece());
if(ec)
return true; // error
}
cb_ = next;
if(cb_)
begin = p;
return false;
};
for(;p != end; ++p)
{
unsigned char ch = *p;
redo:
switch(s_)
{
case s_dead:
case s_closed_complete:
return err(parse_error::connection_closed);
break;
case s_req_start:
flags_ = 0;
cb_ = nullptr;
content_length_ = no_content_length;
s_ = s_req_method0;
goto redo;
case s_req_method0:
if(! is_tchar(ch))
return err(parse_error::bad_method);
call_on_start(ec);
if(ec)
return errc();
assert(! cb_);
cb(&self::call_on_method);
s_ = s_req_method;
break;
case s_req_method:
if(ch == ' ')
{
if(cb(nullptr))
return errc();
s_ = s_req_url0;
break;
}
if(! is_tchar(ch))
return err(parse_error::bad_method);
break;
case s_req_url0:
{
if(ch == ' ')
return err(parse_error::bad_uri);
// VFALCO TODO Better checking for valid URL characters
if(! is_text(ch))
return err(parse_error::bad_uri);
assert(! cb_);
cb(&self::call_on_uri);
s_ = s_req_url;
break;
}
case s_req_url:
if(ch == ' ')
{
if(cb(nullptr))
return errc();
s_ = s_req_http;
break;
}
// VFALCO TODO Better checking for valid URL characters
if(! is_text(ch))
return err(parse_error::bad_uri);
break;
case s_req_http:
if(ch != 'H')
return err(parse_error::bad_version);
s_ = s_req_http_H;
break;
case s_req_http_H:
if(ch != 'T')
return err(parse_error::bad_version);
s_ = s_req_http_HT;
break;
case s_req_http_HT:
if(ch != 'T')
return err(parse_error::bad_version);
s_ = s_req_http_HTT;
break;
case s_req_http_HTT:
if(ch != 'P')
return err(parse_error::bad_version);
s_ = s_req_http_HTTP;
break;
case s_req_http_HTTP:
if(ch != '/')
return err(parse_error::bad_version);
s_ = s_req_major;
break;
case s_req_major:
if(! is_digit(ch))
return err(parse_error::bad_version);
http_major_ = ch - '0';
s_ = s_req_dot;
break;
case s_req_dot:
if(ch != '.')
return err(parse_error::bad_version);
s_ = s_req_minor;
break;
case s_req_minor:
if(! is_digit(ch))
return err(parse_error::bad_version);
http_minor_ = ch - '0';
s_ = s_req_cr;
break;
case s_req_cr:
if(ch != '\r')
return err(parse_error::bad_version);
s_ = s_req_lf;
break;
case s_req_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
call_on_request(ec);
if(ec)
return errc();
s_ = s_header_name0;
break;
//----------------------------------------------------------------------
case s_res_start:
flags_ = 0;
cb_ = nullptr;
content_length_ = no_content_length;
if(ch != 'H')
return err(parse_error::bad_version);
call_on_start(ec);
if(ec)
return errc();
s_ = s_res_H;
break;
case s_res_H:
if(ch != 'T')
return err(parse_error::bad_version);
s_ = s_res_HT;
break;
case s_res_HT:
if(ch != 'T')
return err(parse_error::bad_version);
s_ = s_res_HTT;
break;
case s_res_HTT:
if(ch != 'P')
return err(parse_error::bad_version);
s_ = s_res_HTTP;
break;
case s_res_HTTP:
if(ch != '/')
return err(parse_error::bad_version);
s_ = s_res_major;
break;
case s_res_major:
if(! is_digit(ch))
return err(parse_error::bad_version);
http_major_ = ch - '0';
s_ = s_res_dot;
break;
case s_res_dot:
if(ch != '.')
return err(parse_error::bad_version);
s_ = s_res_minor;
break;
case s_res_minor:
if(! is_digit(ch))
return err(parse_error::bad_version);
http_minor_ = ch - '0';
s_ = s_res_space_1;
break;
case s_res_space_1:
if(ch != ' ')
return err(parse_error::bad_version);
s_ = s_res_status0;
break;
case s_res_status0:
if(! is_digit(ch))
return err(parse_error::bad_status);
status_code_ = ch - '0';
s_ = s_res_status1;
break;
case s_res_status1:
if(! is_digit(ch))
return err(parse_error::bad_status);
status_code_ = status_code_ * 10 + ch - '0';
s_ = s_res_status2;
break;
case s_res_status2:
if(! is_digit(ch))
return err(parse_error::bad_status);
status_code_ = status_code_ * 10 + ch - '0';
s_ = s_res_space_2;
break;
case s_res_space_2:
if(ch != ' ')
return err(parse_error::bad_status);
s_ = s_res_reason0;
break;
case s_res_reason0:
if(ch == '\r')
{
s_ = s_res_line_lf;
break;
}
if(! is_text(ch))
return err(parse_error::bad_reason);
assert(! cb_);
cb(&self::call_on_reason);
s_ = s_res_reason;
break;
case s_res_reason:
if(ch == '\r')
{
if(cb(nullptr))
return errc();
s_ = s_res_line_lf;
break;
}
if(! is_text(ch))
return err(parse_error::bad_reason);
break;
case s_res_line_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
s_ = s_res_line_done;
break;
case s_res_line_done:
call_on_response(ec);
if(ec)
return errc();
s_ = s_header_name0;
goto redo;
//----------------------------------------------------------------------
case s_header_name0:
{
if(ch == '\r')
{
s_ = s_headers_almost_done;
break;
}
auto c = to_field_char(ch);
if(! c)
return err(parse_error::bad_field);
switch(c)
{
case 'c': pos_ = 0; fs_ = h_C; break;
case 'p': pos_ = 0; fs_ = h_matching_proxy_connection; break;
case 't': pos_ = 0; fs_ = h_matching_transfer_encoding; break;
case 'u': pos_ = 0; fs_ = h_matching_upgrade; break;
default:
fs_ = h_general;
break;
}
assert(! cb_);
cb(&self::call_on_field);
s_ = s_header_name;
break;
}
case s_header_name:
{
for(; p != end; ++p)
{
ch = *p;
auto c = to_field_char(ch);
if(! c)
break;
switch(fs_)
{
default:
case h_general:
break;
case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break;
case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break;
case h_CON:
++pos_;
switch(c)
{
case 'n': fs_ = h_matching_connection; break;
case 't': fs_ = h_matching_content_length; break;
default:
fs_ = h_general;
}
break;
case h_matching_connection:
++pos_;
if(c != detail::parser_str::connection[pos_])
fs_ = h_general;
else if(pos_ == sizeof(detail::parser_str::connection)-2)
fs_ = h_connection;
break;
case h_matching_proxy_connection:
++pos_;
if(c != detail::parser_str::proxy_connection[pos_])
fs_ = h_general;
else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2)
fs_ = h_connection;
break;
case h_matching_content_length:
++pos_;
if(c != detail::parser_str::content_length[pos_])
fs_ = h_general;
else if(pos_ == sizeof(detail::parser_str::content_length)-2)
{
if(flags_ & parse_flag::contentlength)
return err(parse_error::bad_content_length);
fs_ = h_content_length0;
}
break;
case h_matching_transfer_encoding:
++pos_;
if(c != detail::parser_str::transfer_encoding[pos_])
fs_ = h_general;
else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2)
fs_ = h_transfer_encoding;
break;
case h_matching_upgrade:
++pos_;
if(c != detail::parser_str::upgrade[pos_])
fs_ = h_general;
else if(pos_ == sizeof(detail::parser_str::upgrade)-2)
fs_ = h_upgrade;
break;
case h_connection:
case h_content_length0:
case h_transfer_encoding:
case h_upgrade:
fs_ = h_general;
break;
}
}
if(p == end)
{
--p;
break;
}
if(ch == ':')
{
if(cb(nullptr))
return errc();
s_ = s_header_value0;
break;
}
return err(parse_error::bad_field);
}
/*
header-field = field-name ":" OWS field-value OWS
field-name = token
field-value = *( field-content / obs-fold )
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
field-vchar = VCHAR / obs-text
obs-fold = CRLF 1*( SP / HTAB )
; obsolete line folding
*/
case s_header_value0:
if(ch == ' ' || ch == '\t')
break;
if(ch == '\r')
{
s_ = s_header_value0_lf;
break;
}
if(fs_ == h_content_length0)
{
content_length_ = 0;
flags_ |= parse_flag::contentlength;
}
assert(! cb_);
cb(&self::call_on_value);
s_ = s_header_value;
// fall through
case s_header_value:
{
for(; p != end; ++p)
{
ch = *p;
if(ch == '\r')
{
if(cb(nullptr))
return errc();
s_ = s_header_value_lf;
break;
}
auto const c = to_value_char(ch);
if(! c)
return err(parse_error::bad_value);
switch(fs_)
{
case h_general:
default:
break;
case h_connection:
switch(c)
{
case 'k':
pos_ = 0;
fs_ = h_matching_connection_keep_alive;
break;
case 'c':
pos_ = 0;
fs_ = h_matching_connection_close;
break;
case 'u':
pos_ = 0;
fs_ = h_matching_connection_upgrade;
break;
default:
if(ch == ' ' || ch == '\t' || ch == ',')
break;
if(! is_tchar(ch))
return err(parse_error::bad_value);
fs_ = h_connection_token;
break;
}
break;
case h_matching_connection_keep_alive:
++pos_;
if(c != detail::parser_str::keep_alive[pos_])
fs_ = h_connection_token;
else if(pos_ == sizeof(detail::parser_str::keep_alive)- 2)
fs_ = h_connection_keep_alive;
break;
case h_matching_connection_close:
++pos_;
if(c != detail::parser_str::close[pos_])
fs_ = h_connection_token;
else if(pos_ == sizeof(detail::parser_str::close)-2)
fs_ = h_connection_close;
break;
case h_matching_connection_upgrade:
++pos_;
if(c != detail::parser_str::upgrade[pos_])
fs_ = h_connection_token;
else if(pos_ == sizeof(detail::parser_str::upgrade)-2)
fs_ = h_connection_upgrade;
break;
case h_connection_close:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_close;
}
else if(ch == ' ' || ch == '\t')
fs_ = h_connection_close_ows;
else if(is_tchar(ch))
fs_ = h_connection_token;
else
return err(parse_error::bad_value);
break;
case h_connection_close_ows:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_close;
break;
}
if(ch == ' ' || ch == '\t')
break;
return err(parse_error::bad_value);
case h_connection_keep_alive:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_keep_alive;
}
else if(ch == ' ' || ch == '\t')
fs_ = h_connection_keep_alive_ows;
else if(is_tchar(ch))
fs_ = h_connection_token;
else
return err(parse_error::bad_value);
break;
case h_connection_keep_alive_ows:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_keep_alive;
break;
}
if(ch == ' ' || ch == '\t')
break;
return err(parse_error::bad_value);
case h_connection_upgrade:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_upgrade;
}
else if(ch == ' ' || ch == '\t')
fs_ = h_connection_upgrade_ows;
else if(is_tchar(ch))
fs_ = h_connection_token;
else
return err(parse_error::bad_value);
break;
case h_connection_upgrade_ows:
if(ch == ',')
{
fs_ = h_connection;
flags_ |= parse_flag::connection_upgrade;
break;
}
if(ch == ' ' || ch == '\t')
break;
return err(parse_error::bad_value);
case h_connection_token:
if(ch == ',')
fs_ = h_connection;
else if(ch == ' ' || ch == '\t')
fs_ = h_connection_token_ows;
else if(! is_tchar(ch))
return err(parse_error::bad_value);
break;
case h_connection_token_ows:
if(ch == ',')
{
fs_ = h_connection;
break;
}
if(ch == ' ' || ch == '\t')
break;
return err(parse_error::bad_value);
case h_content_length0:
if(! is_digit(ch))
return err(parse_error::bad_content_length);
content_length_ = ch - '0';
fs_ = h_content_length;
break;
case h_content_length:
if(ch == ' ' || ch == '\t')
{
fs_ = h_content_length_ows;
break;
}
if(! is_digit(ch))
return err(parse_error::bad_content_length);
if(content_length_ > (no_content_length - 10) / 10)
return err(parse_error::bad_content_length);
content_length_ =
content_length_ * 10 + ch - '0';
break;
case h_content_length_ows:
if(ch != ' ' && ch != '\t')
return err(parse_error::bad_content_length);
break;
case h_transfer_encoding:
if(c == 'c')
{
pos_ = 0;
fs_ = h_matching_transfer_encoding_chunked;
}
else if(c != ' ' && c != '\t' && c != ',')
{
fs_ = h_matching_transfer_encoding_general;
}
break;
case h_matching_transfer_encoding_chunked:
++pos_;
if(c != detail::parser_str::chunked[pos_])
fs_ = h_matching_transfer_encoding_general;
else if(pos_ == sizeof(detail::parser_str::chunked)-2)
fs_ = h_transfer_encoding_chunked;
break;
case h_matching_transfer_encoding_general:
if(c == ',')
fs_ = h_transfer_encoding;
break;
case h_transfer_encoding_chunked:
if(c != ' ' && c != '\t' && c != ',')
fs_ = h_transfer_encoding;
break;
case h_upgrade:
flags_ |= parse_flag::upgrade;
fs_ = h_general;
break;
}
}
if(p == end)
--p;
break;
}
case s_header_value0_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
s_ = s_header_value0_almost_done;
break;
case s_header_value0_almost_done:
if(ch == ' ' || ch == '\t')
{
s_ = s_header_value0;
break;
}
if(fs_ == h_content_length0)
return err(parse_error::bad_content_length);
if(fs_ == h_upgrade)
flags_ |= parse_flag::upgrade;
assert(! cb_);
call_on_value(ec, boost::string_ref{"", 0});
if(ec)
return errc();
s_ = s_header_name0;
goto redo;
case s_header_value_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
s_ = s_header_value_almost_done;
break;
case s_header_value_almost_done:
if(ch == ' ' || ch == '\t')
{
switch(fs_)
{
case h_matching_connection_keep_alive:
case h_matching_connection_close:
case h_matching_connection_upgrade:
fs_ = h_connection_token_ows;
break;
case h_connection_close:
fs_ = h_connection_close_ows;
break;
case h_connection_keep_alive:
fs_ = h_connection_keep_alive_ows;
break;
case h_connection_upgrade:
fs_ = h_connection_upgrade_ows;
break;
case h_content_length:
fs_ = h_content_length_ows;
break;
case h_matching_transfer_encoding_chunked:
fs_ = h_matching_transfer_encoding_general;
break;
default:
break;
}
call_on_value(ec, boost::string_ref(" ", 1));
s_ = s_header_value_unfold;
break;
}
switch(fs_)
{
case h_connection_keep_alive:
case h_connection_keep_alive_ows:
flags_ |= parse_flag::connection_keep_alive;
break;
case h_connection_close:
case h_connection_close_ows:
flags_ |= parse_flag::connection_close;
break;
case h_connection_upgrade:
case h_connection_upgrade_ows:
flags_ |= parse_flag::connection_upgrade;
break;
case h_transfer_encoding_chunked:
case h_transfer_encoding_chunked_ows:
flags_ |= parse_flag::chunked;
break;
default:
break;
}
s_ = s_header_name0;
goto redo;
case s_header_value_unfold:
assert(! cb_);
cb(&self::call_on_value);
s_ = s_header_value;
goto redo;
case s_headers_almost_done:
{
if(ch != '\n')
return err(parse_error::bad_crlf);
if(flags_ & parse_flag::trailing)
{
//if(cb(&self::call_on_chunk_complete)) return errc();
s_ = s_complete;
goto redo;
}
if((flags_ & parse_flag::chunked) && (flags_ & parse_flag::contentlength))
return err(parse_error::illegal_content_length);
upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) ==
(parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/;
auto const maybe_skip = call_on_headers(ec);
if(ec)
return errc();
switch(maybe_skip)
{
case 0:
break;
case 2:
upgrade_ = true;
// fall through
case 1:
flags_ |= parse_flag::skipbody;
break;
default:
return err(parse_error::bad_on_headers_rv);
}
s_ = s_headers_done;
goto redo;
}
case s_headers_done:
{
assert(! cb_);
call_on_headers(ec);
if(ec)
return errc();
bool const hasBody =
(flags_ & parse_flag::chunked) || (content_length_ > 0 &&
content_length_ != no_content_length);
if(upgrade_ && (/*method == "connect" ||*/ (flags_ & parse_flag::skipbody) || ! hasBody))
{
s_ = s_complete;
}
else if((flags_ & parse_flag::skipbody) || content_length_ == 0)
{
s_ = s_complete;
}
else if(flags_ & parse_flag::chunked)
{
s_ = s_chunk_size0;
break;
}
else if(content_length_ != no_content_length)
{
s_ = s_body_identity0;
break;
}
else if(! needs_eof())
{
s_ = s_complete;
}
else
{
s_ = s_body_identity_eof0;
break;
}
goto redo;
}
case s_body_identity0:
assert(! cb_);
cb(&self::call_on_body);
s_ = s_body_identity;
// fall through
case s_body_identity:
{
std::size_t n;
if(static_cast<std::size_t>((end - p)) < content_length_)
n = end - p;
else
n = static_cast<std::size_t>(content_length_);
assert(content_length_ != 0 && content_length_ != no_content_length);
content_length_ -= n;
if(content_length_ == 0)
{
p += n - 1;
s_ = s_complete;
goto redo; // ????
}
p += n - 1;
break;
}
case s_body_identity_eof0:
assert(! cb_);
cb(&self::call_on_body);
s_ = s_body_identity_eof;
// fall through
case s_body_identity_eof:
p = end - 1;
break;
case s_chunk_size0:
{
auto v = unhex(ch);
if(v == -1)
return err(parse_error::invalid_chunk_size);
content_length_ = v;
s_ = s_chunk_size;
break;
}
case s_chunk_size:
{
if(ch == '\r')
{
s_ = s_chunk_size_lf;
break;
}
if(ch == ';')
{
s_ = s_chunk_ext_name0;
break;
}
auto v = unhex(ch);
if(v == -1)
return err(parse_error::invalid_chunk_size);
if(content_length_ > (no_content_length - 16) / 16)
return err(parse_error::bad_content_length);
content_length_ =
content_length_ * 16 + v;
break;
}
case s_chunk_ext_name0:
if(! is_tchar(ch))
return err(parse_error::invalid_ext_name);
s_ = s_chunk_ext_name;
break;
case s_chunk_ext_name:
if(ch == '\r')
{
s_ = s_chunk_size_lf;
break;
}
if(ch == '=')
{
s_ = s_chunk_ext_val;
break;
}
if(ch == ';')
{
s_ = s_chunk_ext_name0;
break;
}
if(! is_tchar(ch))
return err(parse_error::invalid_ext_name);
break;
case s_chunk_ext_val:
if(ch == '\r')
{
s_ = s_chunk_size_lf;
break;
}
break;
case s_chunk_size_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
if(content_length_ == 0)
{
flags_ |= parse_flag::trailing;
s_ = s_header_name0;
break;
}
//call_chunk_header(ec); if(ec) return errc();
s_ = s_chunk_data0;
break;
case s_chunk_data0:
assert(! cb_);
cb(&self::call_on_body);
s_ = s_chunk_data;
goto redo; // VFALCO fall through?
case s_chunk_data:
{
std::size_t n;
if(static_cast<std::size_t>((end - p)) < content_length_)
n = end - p;
else
n = static_cast<std::size_t>(content_length_);
content_length_ -= n;
p += n - 1;
if(content_length_ == 0)
s_ = s_chunk_data_cr;
break;
}
case s_chunk_data_cr:
if(ch != '\r')
return err(parse_error::bad_crlf);
if(cb(nullptr))
return errc();
s_ = s_chunk_data_lf;
break;
case s_chunk_data_lf:
if(ch != '\n')
return err(parse_error::bad_crlf);
s_ = s_chunk_size0;
break;
case s_complete:
++p;
if(cb(nullptr))
return errc();
call_on_complete(ec);
if(ec)
return errc();
s_ = s_restart;
return used();
case s_restart:
if(keep_alive())
reset();
else
s_ = s_dead;
goto redo;
}
}
if(cb_)
{
(this->*cb_)(ec, piece());
if(ec)
return errc();
}
return used();
}
template<bool isRequest, class Derived>
void
basic_parser_v1<isRequest, Derived>::
write_eof(error_code& ec)
{
switch(s_)
{
case s_restart:
s_ = s_closed_complete;
break;
case s_dead:
case s_closed_complete:
break;
case s_body_identity_eof0:
case s_body_identity_eof:
cb_ = nullptr;
call_on_complete(ec);
if(ec)
{
s_ = s_dead;
break;
}
s_ = s_closed_complete;
break;
default:
s_ = s_dead;
ec = parse_error::short_read;
break;
}
}
template<bool isRequest, class Derived>
bool
basic_parser_v1<isRequest, Derived>::
needs_eof(std::true_type) const
{
return false;
}
template<bool isRequest, class Derived>
bool
basic_parser_v1<isRequest, Derived>::
needs_eof(std::false_type) const
{
// See RFC 2616 section 4.4
if( status_code_ / 100 == 1 || // 1xx e.g. Continue
status_code_ == 204 || // No Content
status_code_ == 304 || // Not Modified
flags_ & parse_flag::skipbody) // response to a HEAD request
return false;
if((flags_ & parse_flag::chunked) ||
content_length_ != no_content_length)
return false;
return true;
}
} // http
} // beast
#endif