Fix websocket read of zero length message

fix #686
This commit is contained in:
Vinnie Falco
2017-07-27 17:36:06 -07:00
parent c7b830f37f
commit cbb47a0ffd
3 changed files with 190 additions and 165 deletions

View File

@@ -1,3 +1,9 @@
Version 90:
* Fix websocket read of zero length message
--------------------------------------------------------------------------------
Version 89: Version 89:
* Fix CONTRIBUTING.md links * Fix CONTRIBUTING.md links

View File

@@ -233,7 +233,8 @@ operator()(
// Empty non-final frame // Empty non-final frame
goto go_loop; goto go_loop;
} }
ws_.rd_.done = false; ws_.rd_.done =
ws_.rd_.remain == 0 && ws_.rd_.fh.fin;
break; break;
} }
@@ -436,6 +437,8 @@ operator()(
case do_maybe_fill: case do_maybe_fill:
if(ec) if(ec)
break; break;
if(ws_.rd_.done)
break;
dispatched_ = true; dispatched_ = true;
go_maybe_fill: go_maybe_fill:
@@ -495,6 +498,8 @@ operator()(
} }
// Read into caller's buffer // Read into caller's buffer
step_ = do_read; step_ = do_read;
BOOST_ASSERT(ws_.rd_.remain > 0);
BOOST_ASSERT(buffer_size(cb_) > 0);
return ws_.stream_.async_read_some(buffer_prefix( return ws_.stream_.async_read_some(buffer_prefix(
clamp(ws_.rd_.remain), cb_), std::move(*this)); clamp(ws_.rd_.remain), cb_), std::move(*this));
@@ -526,8 +531,8 @@ operator()(
} }
go_done: go_done:
if(ws_.rd_.remain == 0 && ws_.rd_.fh.fin) ws_.rd_.done =
ws_.rd_.done = true; ws_.rd_.remain == 0 && ws_.rd_.fh.fin;
break; break;
case do_inflate: case do_inflate:
@@ -1087,203 +1092,207 @@ loop:
// Empty non-final frame // Empty non-final frame
goto loop; goto loop;
} }
rd_.done = false; rd_.done = rd_.remain == 0 && rd_.fh.fin;
} }
else else
{ {
ec.assign(0, ec.category()); ec.assign(0, ec.category());
} }
if(! pmd_ || ! pmd_->rd_set) if( ! rd_.done)
{ {
if(rd_.buf.size() == 0 && rd_.buf.max_size() > if(! pmd_ || ! pmd_->rd_set)
(std::min)(clamp(rd_.remain),
buffer_size(buffers)))
{ {
// Fill the read buffer first, otherwise we if(rd_.buf.size() == 0 && rd_.buf.max_size() >
// get fewer bytes at the cost of one I/O. (std::min)(clamp(rd_.remain),
auto const mb = rd_.buf.prepare( buffer_size(buffers)))
read_size(rd_.buf, rd_.buf.max_size()));
auto const bytes_transferred =
stream_.read_some(mb, ec);
failed_ = !!ec;
if(failed_)
return bytes_written;
if(rd_.fh.mask)
detail::mask_inplace(buffer_prefix(
clamp(rd_.remain), mb), rd_.key);
rd_.buf.commit(bytes_transferred);
}
if(rd_.buf.size() > 0)
{
// Copy from the read buffer.
// The mask was already applied.
auto const bytes_transferred =
buffer_copy(buffers, rd_.buf.data(),
clamp(rd_.remain));
auto const mb = buffer_prefix(
bytes_transferred, buffers);
rd_.remain -= bytes_transferred;
if(rd_.op == detail::opcode::text)
{ {
if(! rd_.utf8.write(mb) || // Fill the read buffer first, otherwise we
(rd_.remain == 0 && rd_.fh.fin && // get fewer bytes at the cost of one I/O.
! rd_.utf8.finish())) auto const mb = rd_.buf.prepare(
{ read_size(rd_.buf, rd_.buf.max_size()));
code = close_code::bad_payload; auto const bytes_transferred =
goto do_close; stream_.read_some(mb, ec);
} failed_ = !!ec;
if(failed_)
return bytes_written;
if(rd_.fh.mask)
detail::mask_inplace(buffer_prefix(
clamp(rd_.remain), mb), rd_.key);
rd_.buf.commit(bytes_transferred);
} }
bytes_written += bytes_transferred; if(rd_.buf.size() > 0)
rd_.size += bytes_transferred; {
rd_.buf.consume(bytes_transferred); // Copy from the read buffer.
// The mask was already applied.
auto const bytes_transferred =
buffer_copy(buffers, rd_.buf.data(),
clamp(rd_.remain));
auto const mb = buffer_prefix(
bytes_transferred, buffers);
rd_.remain -= bytes_transferred;
if(rd_.op == detail::opcode::text)
{
if(! rd_.utf8.write(mb) ||
(rd_.remain == 0 && rd_.fh.fin &&
! rd_.utf8.finish()))
{
code = close_code::bad_payload;
goto do_close;
}
}
bytes_written += bytes_transferred;
rd_.size += bytes_transferred;
rd_.buf.consume(bytes_transferred);
}
else
{
// Read into caller's buffer
BOOST_ASSERT(rd_.remain > 0);
BOOST_ASSERT(buffer_size(buffers) > 0);
auto const bytes_transferred =
stream_.read_some(buffer_prefix(
clamp(rd_.remain), buffers), ec);
failed_ = !!ec;
if(failed_)
return bytes_written;
BOOST_ASSERT(bytes_transferred > 0);
auto const mb = buffer_prefix(
bytes_transferred, buffers);
rd_.remain -= bytes_transferred;
if(rd_.fh.mask)
detail::mask_inplace(mb, rd_.key);
if(rd_.op == detail::opcode::text)
{
if(! rd_.utf8.write(mb) ||
(rd_.remain == 0 && rd_.fh.fin &&
! rd_.utf8.finish()))
{
code = close_code::bad_payload;
goto do_close;
}
}
bytes_written += bytes_transferred;
rd_.size += bytes_transferred;
}
rd_.done = rd_.remain == 0 && rd_.fh.fin;
} }
else else
{ {
// Read into caller's buffer // Read compressed message frame payload:
auto const bytes_transferred = // inflate even if rd_.fh.len == 0, otherwise we
stream_.read_some(buffer_prefix( // never emit the end-of-stream deflate block.
clamp(rd_.remain), buffers), ec); //
failed_ = !!ec; bool did_read = false;
if(failed_) consuming_buffers<MutableBufferSequence> cb{buffers};
return bytes_written; while(buffer_size(cb) > 0)
BOOST_ASSERT(bytes_transferred > 0);
auto const mb = buffer_prefix(
bytes_transferred, buffers);
rd_.remain -= bytes_transferred;
if(rd_.fh.mask)
detail::mask_inplace(mb, rd_.key);
if(rd_.op == detail::opcode::text)
{ {
if(! rd_.utf8.write(mb) || zlib::z_params zs;
(rd_.remain == 0 && rd_.fh.fin &&
! rd_.utf8.finish()))
{ {
code = close_code::bad_payload; auto const out = buffer_front(cb);
goto do_close; zs.next_out = buffer_cast<void*>(out);
zs.avail_out = buffer_size(out);
BOOST_ASSERT(zs.avail_out > 0);
} }
} if(rd_.remain > 0)
bytes_written += bytes_transferred;
rd_.size += bytes_transferred;
}
if(rd_.remain == 0 && rd_.fh.fin)
rd_.done = true;
}
else
{
// Read compressed message frame payload:
// inflate even if rd_.fh.len == 0, otherwise we
// never emit the end-of-stream deflate block.
//
bool did_read = false;
consuming_buffers<MutableBufferSequence> cb{buffers};
while(buffer_size(cb) > 0)
{
zlib::z_params zs;
{
auto const out = buffer_front(cb);
zs.next_out = buffer_cast<void*>(out);
zs.avail_out = buffer_size(out);
BOOST_ASSERT(zs.avail_out > 0);
}
if(rd_.remain > 0)
{
if(rd_.buf.size() > 0)
{ {
// use what's there if(rd_.buf.size() > 0)
auto const in = buffer_prefix( {
clamp(rd_.remain), buffer_front( // use what's there
rd_.buf.data())); auto const in = buffer_prefix(
zs.avail_in = buffer_size(in); clamp(rd_.remain), buffer_front(
zs.next_in = buffer_cast<void const*>(in); rd_.buf.data()));
zs.avail_in = buffer_size(in);
zs.next_in = buffer_cast<void const*>(in);
}
else if(! did_read)
{
// read new
auto const bytes_transferred =
stream_.read_some(
rd_.buf.prepare(read_size(
rd_.buf, rd_.buf.max_size())),
ec);
failed_ = !!ec;
if(failed_)
return bytes_written;
BOOST_ASSERT(bytes_transferred > 0);
rd_.buf.commit(bytes_transferred);
if(rd_.fh.mask)
detail::mask_inplace(
buffer_prefix(clamp(rd_.remain),
rd_.buf.mutable_data()), rd_.key);
auto const in = buffer_prefix(
clamp(rd_.remain), buffer_front(
rd_.buf.data()));
zs.avail_in = buffer_size(in);
zs.next_in = buffer_cast<void const*>(in);
did_read = true;
}
else
{
break;
}
} }
else if(! did_read) else if(rd_.fh.fin)
{ {
// read new // append the empty block codes
auto const bytes_transferred = static std::uint8_t constexpr
stream_.read_some( empty_block[4] = {
rd_.buf.prepare(read_size( 0x00, 0x00, 0xff, 0xff };
rd_.buf, rd_.buf.max_size())), zs.next_in = empty_block;
ec); zs.avail_in = sizeof(empty_block);
pmd_->zi.write(zs, zlib::Flush::sync, ec);
BOOST_ASSERT(! ec);
failed_ = !!ec; failed_ = !!ec;
if(failed_) if(failed_)
return bytes_written; return bytes_written;
BOOST_ASSERT(bytes_transferred > 0); // VFALCO See:
rd_.buf.commit(bytes_transferred); // https://github.com/madler/zlib/issues/280
if(rd_.fh.mask) BOOST_ASSERT(zs.total_out == 0);
detail::mask_inplace( cb.consume(zs.total_out);
buffer_prefix(clamp(rd_.remain), rd_.size += zs.total_out;
rd_.buf.mutable_data()), rd_.key); bytes_written += zs.total_out;
auto const in = buffer_prefix( if(
clamp(rd_.remain), buffer_front( (role_ == role_type::client &&
rd_.buf.data())); pmd_config_.server_no_context_takeover) ||
zs.avail_in = buffer_size(in); (role_ == role_type::server &&
zs.next_in = buffer_cast<void const*>(in); pmd_config_.client_no_context_takeover))
did_read = true; pmd_->zi.reset();
rd_.done = true;
break;
} }
else else
{ {
break; break;
} }
}
else if(rd_.fh.fin)
{
// append the empty block codes
static std::uint8_t constexpr
empty_block[4] = {
0x00, 0x00, 0xff, 0xff };
zs.next_in = empty_block;
zs.avail_in = sizeof(empty_block);
pmd_->zi.write(zs, zlib::Flush::sync, ec); pmd_->zi.write(zs, zlib::Flush::sync, ec);
BOOST_ASSERT(! ec); BOOST_ASSERT(ec != zlib::error::end_of_stream);
failed_ = !!ec; failed_ = !!ec;
if(failed_) if(failed_)
return bytes_written; return bytes_written;
// VFALCO See: if(rd_msg_max_ && beast::detail::sum_exceeds(
// https://github.com/madler/zlib/issues/280 rd_.size, zs.total_out, rd_msg_max_))
BOOST_ASSERT(zs.total_out == 0); {
code = close_code::too_big;
goto do_close;
}
cb.consume(zs.total_out); cb.consume(zs.total_out);
rd_.size += zs.total_out; rd_.size += zs.total_out;
rd_.remain -= zs.total_in;
rd_.buf.consume(zs.total_in);
bytes_written += zs.total_out; bytes_written += zs.total_out;
if(
(role_ == role_type::client &&
pmd_config_.server_no_context_takeover) ||
(role_ == role_type::server &&
pmd_config_.client_no_context_takeover))
pmd_->zi.reset();
rd_.done = true;
break;
} }
else if(rd_.op == detail::opcode::text)
{ {
break; // check utf8
} if(! rd_.utf8.write(
pmd_->zi.write(zs, zlib::Flush::sync, ec); buffer_prefix(bytes_written, buffers)) || (
BOOST_ASSERT(ec != zlib::error::end_of_stream); rd_.remain == 0 && rd_.fh.fin &&
failed_ = !!ec; ! rd_.utf8.finish()))
if(failed_) {
return bytes_written; code = close_code::bad_payload;
if(rd_msg_max_ && beast::detail::sum_exceeds( goto do_close;
rd_.size, zs.total_out, rd_msg_max_)) }
{
code = close_code::too_big;
goto do_close;
}
cb.consume(zs.total_out);
rd_.size += zs.total_out;
rd_.remain -= zs.total_in;
rd_.buf.consume(zs.total_in);
bytes_written += zs.total_out;
}
if(rd_.op == detail::opcode::text)
{
// check utf8
if(! rd_.utf8.write(
buffer_prefix(bytes_written, buffers)) || (
rd_.remain == 0 && rd_.fh.fin &&
! rd_.utf8.finish()))
{
code = close_code::bad_payload;
goto do_close;
} }
} }
} }

View File

@@ -1638,6 +1638,16 @@ public:
} }
c.handshake(ws, "localhost", "/"); c.handshake(ws, "localhost", "/");
// send empty message
ws.text(true);
c.write(ws, boost::asio::null_buffers{});
{
// receive echoed message
multi_buffer db;
c.read(ws, db);
BEAST_EXPECT(ws.got_text());
BEAST_EXPECT(db.size() == 0);
}
// send message // send message
ws.auto_fragment(false); ws.auto_fragment(false);
ws.binary(false); ws.binary(false);