|
| 1 | +/* |
| 2 | + This file is part of libhttpserver |
| 3 | + Copyright (C) 2011-2026 Sebastiano Merlino |
| 4 | +
|
| 5 | + This library is free software; you can redistribute it and/or |
| 6 | + modify it under the terms of the GNU Lesser General Public |
| 7 | + License as published by the Free Software Foundation; either |
| 8 | + version 2.1 of the License, or (at your option) any later version. |
| 9 | +
|
| 10 | + This library is distributed in the hope that it will be useful, |
| 11 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | + Lesser General Public License for more details. |
| 14 | +
|
| 15 | + You should have received a copy of the GNU Lesser General Public |
| 16 | + License along with this library; if not, write to the Free Software |
| 17 | + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 |
| 18 | + USA |
| 19 | +*/ |
| 20 | + |
| 21 | +// TASK-047 -- The two body-pipeline stages |
| 22 | +// (requests_answer_first_step / requests_answer_second_step) extracted |
| 23 | +// from webserver_request.cpp to keep that TU under the 500-LOC ceiling |
| 24 | +// after adding the request_received and body_chunk firing sites |
| 25 | +// (matches the TASK-046 split pattern that carved |
| 26 | +// webserver_callbacks_lifecycle.cpp out of webserver_callbacks.cpp). |
| 27 | +// |
| 28 | +// Also hosts the small anon-ns helper that wraps the body_chunk firing |
| 29 | +// site, both to keep the second-step orchestrator at CCN <= 10 and to |
| 30 | +// make the short-circuit teardown path single-sourced. |
| 31 | + |
| 32 | +#include "httpserver/webserver.hpp" |
| 33 | +#include "httpserver/detail/webserver_impl.hpp" |
| 34 | + |
| 35 | +#include <microhttpd.h> |
| 36 | + |
| 37 | +#include <strings.h> |
| 38 | + |
| 39 | +#include <chrono> |
| 40 | +#include <cstddef> |
| 41 | +#include <cstdint> |
| 42 | +#include <cstring> |
| 43 | +#include <iostream> |
| 44 | +#include <optional> |
| 45 | +#include <span> |
| 46 | +#include <string> |
| 47 | +#include <utility> |
| 48 | + |
| 49 | +#include "httpserver/create_webserver.hpp" |
| 50 | +#include "httpserver/hook_action.hpp" |
| 51 | +#include "httpserver/hook_context.hpp" |
| 52 | +#include "httpserver/hook_phase.hpp" |
| 53 | +#include "httpserver/http_request.hpp" |
| 54 | +#include "httpserver/http_response.hpp" |
| 55 | +#include "httpserver/http_utils.hpp" |
| 56 | +#include "httpserver/detail/modded_request.hpp" |
| 57 | + |
| 58 | +namespace httpserver { |
| 59 | + |
| 60 | +using httpserver::http::http_utils; |
| 61 | + |
| 62 | +namespace detail { |
| 63 | + |
| 64 | +namespace { |
| 65 | + |
| 66 | +// Wrap the body_chunk firing site so requests_answer_second_step stays a |
| 67 | +// flat sequence of small steps. Returns true iff a hook short-circuited |
| 68 | +// and the caller should signal MHD that the chunk was consumed (returns |
| 69 | +// MHD_YES with *upload_data_size = 0). Side effects on short-circuit: |
| 70 | +// - mr->response_ is populated with the hook-supplied response, |
| 71 | +// - mr->skip_handler is set so finalize_answer routes through the |
| 72 | +// skip branch, |
| 73 | +// - any in-flight MHD_PostProcessor is destroyed (32 KB buffer freed). |
| 74 | +bool fire_and_maybe_short_circuit_body_chunk(webserver_impl* impl, |
| 75 | + modded_request* mr, |
| 76 | + const char* upload_data, |
| 77 | + size_t upload_data_size) { |
| 78 | + ::httpserver::body_chunk_ctx ctx{ |
| 79 | + mr->dhr.get(), |
| 80 | + std::span<const std::byte>( |
| 81 | + reinterpret_cast<const std::byte*>(upload_data), |
| 82 | + upload_data_size), |
| 83 | + static_cast<std::uint64_t>(mr->dhr->get_content().size()), |
| 84 | + /*is_final=*/false}; |
| 85 | + auto sc = impl->fire_body_chunk(ctx); |
| 86 | + if (!sc) return false; |
| 87 | + mr->response_.emplace(std::move(*sc)); |
| 88 | + mr->skip_handler = true; |
| 89 | + if (mr->pp != nullptr) { |
| 90 | + MHD_destroy_post_processor(mr->pp); |
| 91 | + mr->pp = nullptr; |
| 92 | + } |
| 93 | + return true; |
| 94 | +} |
| 95 | + |
| 96 | +// Feed @p upload_data through MHD's post processor (when one is |
| 97 | +// attached) and close any open upload-target stream. Pulled out of the |
| 98 | +// second_step orchestrator so the orchestrator stays under the CCN bar. |
| 99 | +void run_post_processor_if_attached(modded_request* mr, |
| 100 | + webserver* parent, |
| 101 | + const char* upload_data, |
| 102 | + size_t upload_data_size) { |
| 103 | + if (mr->pp == nullptr) return; |
| 104 | + mr->ws = parent; |
| 105 | + MHD_post_process(mr->pp, upload_data, upload_data_size); |
| 106 | + if (mr->upload_ostrm != nullptr && mr->upload_ostrm->is_open()) { |
| 107 | + mr->upload_ostrm->close(); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +} // namespace |
| 112 | + |
| 113 | +MHD_Result webserver_impl::requests_answer_first_step(MHD_Connection* connection, struct detail::modded_request* mr) { |
| 114 | + mr->dhr.reset(new http_request(connection, parent->unescaper)); |
| 115 | + mr->dhr->set_file_cleanup_callback(parent->file_cleanup_callback); |
| 116 | + |
| 117 | + // TASK-047 -- request_received hook. Fires after the http_request is |
| 118 | + // populated but before any body bytes are read (and before any |
| 119 | + // post-processor is created). Mutable ref so a hook may adjust |
| 120 | + // per-request state. Short-circuit: stash the response, mark |
| 121 | + // skip-to-finalize, and return MHD_YES. MHD will call back into |
| 122 | + // requests_answer_second_step with *upload_data_size == 0, which |
| 123 | + // routes through complete_request -> finalize_answer, where the |
| 124 | + // skip_handler branch goes straight to materialize_and_queue_response. |
| 125 | + // No post-processor exists at this point, so no teardown is needed. |
| 126 | + if (any_hooks_[static_cast<std::size_t>( |
| 127 | + ::httpserver::hook_phase::request_received)] |
| 128 | + .load(std::memory_order_relaxed)) { |
| 129 | + ::httpserver::request_received_ctx ctx{ |
| 130 | + mr->dhr.get(), |
| 131 | + std::chrono::steady_clock::now()}; |
| 132 | + if (auto sc = fire_request_received(ctx)) { |
| 133 | + mr->response_.emplace(std::move(*sc)); |
| 134 | + mr->skip_handler = true; |
| 135 | + return MHD_YES; |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + if (!mr->has_body) { |
| 140 | + return MHD_YES; |
| 141 | + } |
| 142 | + |
| 143 | + mr->dhr->set_content_size_limit(parent->content_size_limit); |
| 144 | + const char *encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, http_utils::http_header_content_type); |
| 145 | + |
| 146 | + if (parent->post_process_enabled && |
| 147 | + (nullptr != encoding && |
| 148 | + ((0 == strncasecmp(http_utils::http_post_encoding_form_urlencoded, encoding, strlen(http_utils::http_post_encoding_form_urlencoded))) || |
| 149 | + (0 == strncasecmp(http_utils::http_post_encoding_multipart_formdata, encoding, strlen(http_utils::http_post_encoding_multipart_formdata)))))) { |
| 150 | + const size_t post_memory_limit(32 * 1024); // Same as #MHD_POOL_SIZE_DEFAULT |
| 151 | + mr->pp = MHD_create_post_processor(connection, post_memory_limit, &webserver_impl::post_iterator, mr); |
| 152 | + } else { |
| 153 | + mr->pp = nullptr; |
| 154 | + } |
| 155 | + return MHD_YES; |
| 156 | +} |
| 157 | + |
| 158 | +MHD_Result webserver_impl::requests_answer_second_step(MHD_Connection* connection, const char* method, |
| 159 | + const char* version, const char* upload_data, |
| 160 | + size_t* upload_data_size, struct detail::modded_request* mr) { |
| 161 | + if (0 == *upload_data_size) return complete_request(connection, mr, version, method); |
| 162 | + |
| 163 | + if (!mr->has_body) { |
| 164 | + *upload_data_size = 0; |
| 165 | + return MHD_YES; |
| 166 | + } |
| 167 | + |
| 168 | + // TASK-047 -- a prior pre-handler short-circuit (request_received in |
| 169 | + // first_step, or body_chunk on an earlier chunk) already populated |
| 170 | + // mr->response_. Consume the chunk so MHD advances; the next |
| 171 | + // *upload_data_size == 0 callback will route to finalize_answer's |
| 172 | + // skip_handler branch. |
| 173 | + if (mr->skip_handler) { |
| 174 | + *upload_data_size = 0; |
| 175 | + return MHD_YES; |
| 176 | + } |
| 177 | + |
| 178 | + // TASK-047 -- body_chunk hook fires per chunk BEFORE the bytes are |
| 179 | + // appended to mr->dhr / fed to MHD_post_process. |
| 180 | + if (any_hooks_[static_cast<std::size_t>( |
| 181 | + ::httpserver::hook_phase::body_chunk)] |
| 182 | + .load(std::memory_order_relaxed)) { |
| 183 | + if (fire_and_maybe_short_circuit_body_chunk( |
| 184 | + this, mr, upload_data, *upload_data_size)) { |
| 185 | + *upload_data_size = 0; |
| 186 | + return MHD_YES; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | +#ifdef DEBUG |
| 191 | + std::cout << "Writing content: " << std::string(upload_data, *upload_data_size) << std::endl; |
| 192 | +#endif // DEBUG |
| 193 | + // The post iterator is only created from the libmicrohttpd for content of type |
| 194 | + // multipart/form-data and application/x-www-form-urlencoded |
| 195 | + // all other content (which is indicated by mr-pp == nullptr) |
| 196 | + // has to be put to the content even if put_processed_data_to_content is set to false |
| 197 | + if (mr->pp == nullptr || parent->put_processed_data_to_content) { |
| 198 | + mr->dhr->grow_content(upload_data, *upload_data_size); |
| 199 | + } |
| 200 | + run_post_processor_if_attached(mr, parent, upload_data, *upload_data_size); |
| 201 | + |
| 202 | + *upload_data_size = 0; |
| 203 | + return MHD_YES; |
| 204 | +} |
| 205 | + |
| 206 | +} // namespace detail |
| 207 | +} // namespace httpserver |
0 commit comments