file body reports short_read on eof

fixes #1818
fixes #1815
close #1821
This commit is contained in:
Richard Hodges
2020-01-24 11:28:24 +01:00
parent 9068787df1
commit 3d168c0336
9 changed files with 145 additions and 7 deletions

View File

@ -1,3 +1,11 @@
Version XXX:
* clarify end-of-file behaviour in File::read docs
* file_body returns short_read on eof during read
* fix bug in win32 file_body
--------------------------------------------------------------------------------
Version 283: Version 283:
* ostream_buffer satisfies preconditions of DynamicBuffer_v1::commit * ostream_buffer satisfies preconditions of DynamicBuffer_v1::commit

View File

@ -141,7 +141,10 @@ In this table:
set `ec` to `errc::invalid_argument` and return immediately. set `ec` to `errc::invalid_argument` and return immediately.
The function will ensure that `!ec` is `true` if there was The function will ensure that `!ec` is `true` if there was
no error or set to the appropriate error code if an error no error or set to the appropriate error code if an error
occurred. occurred.
If an end-of-file condition is detected prior to reading any
bytes, the function will ensure that `!ec` is `true` and the
return value shall be 0.
] ]
] ]
[ [

View File

@ -363,6 +363,12 @@ get(error_code& ec) ->
if(ec) if(ec)
return boost::none; return boost::none;
if (nread == 0)
{
ec = error::short_read;
return boost::none;
}
// Make sure there is forward progress // Make sure there is forward progress
BOOST_ASSERT(nread != 0); BOOST_ASSERT(nread != 0);
BOOST_ASSERT(nread <= remain_); BOOST_ASSERT(nread <= remain_);

View File

@ -153,7 +153,15 @@ enum class error
a new parser for each message. This can be easily done by a new parser for each message. This can be easily done by
storing the parser in an boost or std::optional container. storing the parser in an boost or std::optional container.
*/ */
stale_parser stale_parser,
/** The message body is shorter than expected.
This error is returned by @ref file_body when an unexpected
unexpected end-of-file condition is encountered while trying
to read from the file.
*/
short_read
}; };
} // http } // http

View File

@ -56,6 +56,7 @@ public:
case error::bad_chunk_extension: return "bad chunk extension"; case error::bad_chunk_extension: return "bad chunk extension";
case error::bad_obs_fold: return "bad obs-fold"; case error::bad_obs_fold: return "bad obs-fold";
case error::stale_parser: return "stale parser"; case error::stale_parser: return "stale parser";
case error::short_read: return "unexpected eof in body";
default: default:
return "beast.http error"; return "beast.http error";

View File

@ -17,6 +17,7 @@
#include <boost/beast/core/buffers_range.hpp> #include <boost/beast/core/buffers_range.hpp>
#include <boost/beast/core/detail/clamp.hpp> #include <boost/beast/core/detail/clamp.hpp>
#include <boost/beast/core/detail/is_invocable.hpp> #include <boost/beast/core/detail/is_invocable.hpp>
#include <boost/beast/http/error.hpp>
#include <boost/beast/http/write.hpp> #include <boost/beast/http/write.hpp>
#include <boost/beast/http/serializer.hpp> #include <boost/beast/http/serializer.hpp>
#include <boost/asio/async_result.hpp> #include <boost/asio/async_result.hpp>
@ -133,14 +134,16 @@ struct basic_file_body<file_win32>
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
writer(header<isRequest, Fields>&, value_type& b) writer(header<isRequest, Fields>&, value_type& b)
: body_(b) : body_(b)
, pos_(body_.first_)
{ {
BOOST_ASSERT(body_.file_.is_open());
} }
void void
init(error_code&) init(error_code& ec)
{ {
BOOST_ASSERT(body_.file_.is_open()); BOOST_ASSERT(body_.file_.is_open());
pos_ = body_.first_; ec.clear();
} }
boost::optional<std::pair<const_buffers_type, bool>> boost::optional<std::pair<const_buffers_type, bool>>
@ -156,6 +159,11 @@ struct basic_file_body<file_win32>
auto const nread = body_.file_.read(buf_, n, ec); auto const nread = body_.file_.read(buf_, n, ec);
if(ec) if(ec)
return boost::none; return boost::none;
if (nread == 0)
{
ec = error::short_read;
return boost::none;
}
BOOST_ASSERT(nread != 0); BOOST_ASSERT(nread != 0);
pos_ += nread; pos_ += nread;
ec = {}; ec = {};

View File

@ -44,6 +44,7 @@ public:
read_header(ts, fb, p, ec); read_header(ts, fb, p, ec);
auto const bytes_transferred = auto const bytes_transferred =
read(ts, fb, p, ec); read(ts, fb, p, ec);
boost::ignore_unused(bytes_transferred);
BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECTS(! ec, ec.message());
// VFALCO What should the read algorithms return? // VFALCO What should the read algorithms return?

View File

@ -67,6 +67,7 @@ public:
check("beast.http", error::bad_obs_fold); check("beast.http", error::bad_obs_fold);
check("beast.http", error::stale_parser); check("beast.http", error::stale_parser);
check("beast.http", error::short_read);
} }
}; };

View File

@ -18,6 +18,7 @@
#include <boost/beast/http/serializer.hpp> #include <boost/beast/http/serializer.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp> #include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/asio/error.hpp>
namespace boost { namespace boost {
namespace beast { namespace beast {
@ -40,6 +41,43 @@ public:
} }
}; };
struct temp_file
{
temp_file(std::ostream& logger)
: path_(boost::filesystem::unique_path())
, log_(logger)
{}
~temp_file()
{
if (!path_.empty())
{
auto ec = error_code();
boost::filesystem::remove(path_, ec);
if (ec)
{
log_ << "warning: failed to remove temporary file: " << path_ << "\n";
}
}
}
temp_file(temp_file&&) = default;
temp_file(temp_file const&) = delete;
temp_file& operator=(temp_file&&) = default;
temp_file& operator=(temp_file const&) = delete;
boost::filesystem::path const& path() const
{
return path_;
}
private:
boost::filesystem::path path_;
std::ostream& log_;
};
template<class File> template<class File>
void void
doTestFileBody() doTestFileBody()
@ -98,16 +136,80 @@ public:
boost::filesystem::remove(temp, ec); boost::filesystem::remove(temp, ec);
BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECTS(! ec, ec.message());
} }
template<class File>
void
fileBodyUnexpectedEofOnGet()
{
auto temp = temp_file(log);
error_code ec;
string_view const ten =
"0123456789"; // 40
// create the temporary file
{
std::ofstream fstemp(temp.path().native());
std::size_t written = 0;
std::size_t to_write = 4097;
while (written < to_write)
{
auto size = std::min(ten.size(), to_write - written);
fstemp << ten.substr(0, size);
BEAST_EXPECT(fstemp.good());
written += size;
}
fstemp.close();
}
// open the file and read the header
{
using file_body_type = basic_file_body<File>;
typename file_body_type::value_type value;
// opened in write mode so we can truncate later
value.open(temp.path().string<std::string>().c_str(), file_mode::read, ec);
BEAST_EXPECTS(! ec, ec.message());
response_header<> header;
header.version(11);
header.result(status::accepted);
header.set(field::server, "test");
header.set(field::content_length, 4097);
typename file_body_type::writer w(header, value);
auto maybe_range = w.get(ec);
BEAST_EXPECTS(!ec, ec.message());
BEAST_EXPECTS(maybe_range.has_value(), "no range returned");
BEAST_EXPECT(maybe_range.value().second);
value.file().seek(4097, ec);
BEAST_EXPECTS(!ec, ec.message());
maybe_range = w.get(ec);
BEAST_EXPECTS(ec == error::short_read, ec.message());
BEAST_EXPECTS(!maybe_range.has_value(), "range returned on error");
}
}
void void
run() override run() override
{ {
doTestFileBody<file_stdio>(); doTestFileBody<file_stdio>();
#if BOOST_BEAST_USE_WIN32_FILE #if BOOST_BEAST_USE_WIN32_FILE
doTestFileBody<file_win32>(); doTestFileBody<file_win32>();
#endif #endif
#if BOOST_BEAST_USE_POSIX_FILE #if BOOST_BEAST_USE_POSIX_FILE
doTestFileBody<file_posix>(); doTestFileBody<file_posix>();
#endif
fileBodyUnexpectedEofOnGet<file_stdio>();
#if BOOST_BEAST_USE_POSIX_FILE
fileBodyUnexpectedEofOnGet<file_posix>();
#endif #endif
#if BOOST_BEAST_USE_WIN32_FILE
fileBodyUnexpectedEofOnGet<file_win32>();
#endif
} }
}; };