From 48a99fd71b11321b85c385245b5f2d36fed22df1 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Thu, 10 Sep 2026 23:10:57 +0200 Subject: Fun with (raw) deflate --- server/src/srv.cpp | 141 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 10 deletions(-) (limited to 'server/src/srv.cpp') diff --git a/server/src/srv.cpp b/server/src/srv.cpp index adfef18..a901f8e 100644 --- a/server/src/srv.cpp +++ b/server/src/srv.cpp @@ -6,6 +6,7 @@ module; #include #include #include +#include #include #include #include @@ -156,10 +157,8 @@ struct readable_gpx_body auto total = 0uz; try { - for (auto it = net::buffer_sequence_begin(b); - it != net::buffer_sequence_end(b); it++) + for (auto cur_in_buf : beast::buffers_range(b)) { - auto cur_in_buf = net::const_buffer{*it}; while (cur_in_buf.size() > 0) { auto to_read = @@ -217,6 +216,120 @@ struct readable_gpx_body static_assert(bhttp::concepts::body); static_assert(bhttp::concepts::body_reader); +template +struct raw_deflate_body +{ + using value_type = Inner::value_type; + + class reader + { + static constexpr auto const chunk_size = 16384; + + Inner::reader r_; + bool stream_ended_ = false; + beast::zlib::z_params zs_ = {}; + beast::zlib::inflate_stream inflate_strm_ = {}; + std::unique_ptr> in_buf_ = nullptr; + std::unique_ptr> out_buf_ = nullptr; + + auto inflate_chunk(beast::error_code& ec) -> void + { + assert(zs_.avail_in > 0); + zs_.next_in = in_buf_->data(); + + do + { + zs_.avail_out = out_buf_->size(); + zs_.next_out = out_buf_->data(); + + inflate_strm_.write(zs_, beast::zlib::Flush::none, ec); + assert(ec != beast::zlib::error::stream_error); + if (ec == beast::zlib::error::need_buffers) + ec = {}; + if (ec == beast::zlib::error::end_of_stream) + { + ec = {}; + stream_ended_ = true; + } + if (ec) + break; + + auto have = chunk_size - zs_.avail_out; + auto cbuf = + net::buffer(static_cast(out_buf_->data()), have); + + auto inner_ec = beast::error_code{}; + do + cbuf += r_.put(cbuf, inner_ec); + while (cbuf.size() > 0 && !inner_ec); + if (inner_ec) + { + ec = inner_ec; + break; + } + } + while (zs_.avail_out == 0); + + // In the last case (stream_ended_), the user has sent too much data + assert(zs_.avail_in == 0 || ec || stream_ended_); + } + + public: + template + explicit reader(bhttp::header& hdr, value_type& v) + : r_{hdr, v} + { + } + + auto + init(boost::optional /* n */, beast::error_code& ec) noexcept + -> void + { + in_buf_.reset(new std::array{}); + out_buf_.reset(new std::array{}); + ec = {}; + } + + auto + put(beast::concepts::const_buffer_sequence auto b, + beast::error_code& ec) noexcept -> std::size_t + { + auto in_buf = net::buffer(in_buf_->data(), in_buf_->size()); + in_buf += zs_.avail_in; + + auto consumed_from_b = net::buffer_copy(in_buf, b); + zs_.avail_in += consumed_from_b; + + if (zs_.avail_in == in_buf_->size()) + inflate_chunk(ec); + else + ec = {}; + + return consumed_from_b; + } + + auto finish(beast::error_code& ec) noexcept + { + ec = {}; + if (zs_.avail_in > 0) + inflate_chunk(ec); + if (!ec && !stream_ended_) + ec = beast::zlib::error::stream_error; + if (stream_ended_ && zs_.avail_in > 0) + // client sent data past deflate stream end + ec = beast::zlib::error::stream_error; + + in_buf_.reset(); + out_buf_.reset(); + inflate_strm_.reset(); + if (!ec) + { + r_.finish(ec); + } + } + }; +}; + auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) -> net::awaitable { @@ -228,23 +341,31 @@ auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) { l.debug("Reading GPX request body"); auto const before_read_gpx = chrono::steady_clock::now(); - auto req = - co_await http::read_request(ctx, std::move(r)); + auto parse_res = + r.p->get()[bhttp::field::content_encoding] == "x-deflate-raw" + ? std::move( + (co_await http::read_request>( + ctx, std::move(r))) + .body()) + : std::move((co_await http::read_request( + ctx, std::move(r))) + .body()); l.debug( "Read GPX request body in {}; init took {}, actual parsing {}, " "finishing {}", chrono::duration{ chrono::steady_clock::now() - before_read_gpx }, - chrono::duration{req->body().init_parse_duration()}, - chrono::duration{req->body().put_parse_duration()}, + chrono::duration{parse_res.init_parse_duration()}, + chrono::duration{parse_res.put_parse_duration()}, chrono::duration{ - req->body().finish_parse_duration() + parse_res.finish_parse_duration() }); - gpx_file = std::move(req->body().unwrap()); + gpx_file = std::move(parse_res.unwrap()); } catch (std::exception& ex) { + l_.warn("Failed to parse GPX file: {}", ex.what()); // TODO: more detailed problem reporting auto tpl = problem::tpl{ .status = bhttp::status::bad_request, @@ -280,7 +401,7 @@ auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) auto handler::handle_sysinfo(l0_ctx ctx, http::readable_request r) -> net::awaitable { - auto req = co_await http::read_request(ctx, std::move(r)); + auto req = co_await http::read_empty_request(ctx, std::move(r)); auto info = inner_.sysinfo(); auto rsp = http::make_rsp( -- cgit v1.3