/src/connection_helper.cpp
Line | Count | Source |
1 | | // Copyright 2025 Google LLC |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | //////////////////////////////////////////////////////////////////////////////// |
16 | | #include "connection_helper.h" |
17 | | #include <cstring> |
18 | | #include <unordered_set> |
19 | | |
20 | | #include "mhd_action.h" |
21 | | #include "http_post_enc.h" |
22 | | #include "mempool_funcs.h" |
23 | | #include "daemon_funcs.h" |
24 | | #include "post_parser_funcs.h" |
25 | | #include "response_funcs.h" |
26 | | #include "stream_process_request.h" |
27 | | #include "stream_funcs.h" |
28 | | |
29 | | |
30 | | // MHD memory pool |
31 | | struct mhd_MemoryPool *g_pool = nullptr; |
32 | | const size_t g_pool_size = 14 * 1024; |
33 | | std::string g_mpart_boundary; |
34 | | |
35 | | // Body status |
36 | | static std::unordered_set<const MHD_Connection*> g_post_parse_ready; |
37 | | |
38 | | // Helper to clear memory pool |
39 | 2 | void destroy_global_pool() { |
40 | 2 | if (g_pool) { mhd_pool_destroy(g_pool); g_pool = nullptr; } |
41 | 2 | } |
42 | | |
43 | | |
44 | | // Helper to set body parsing ready |
45 | 2.57k | void mark_post_parse_ready(MHD_Connection& c) { |
46 | 2.57k | g_post_parse_ready.insert(&c); |
47 | 2.57k | } |
48 | | |
49 | | // Helper to check parse body status |
50 | 0 | bool is_post_parse_ready(const MHD_Connection& c) { |
51 | 0 | return g_post_parse_ready.find(&c) != g_post_parse_ready.end(); |
52 | 0 | } |
53 | | |
54 | | // Helper to clear parse body status |
55 | 2.57k | void clear_post_parse_ready(const MHD_Connection& c) { |
56 | 2.57k | g_post_parse_ready.erase(&c); |
57 | 2.57k | } |
58 | | |
59 | | // Helper to destroy error response |
60 | 5.40k | static bool destroy_error_response(MHD_Connection c) { |
61 | 5.40k | if (c.stage == mhd_HTTP_STAGE_START_REPLY) { |
62 | 563 | MHD_response_destroy(c.rp.response); |
63 | 563 | c.rp.response = nullptr; |
64 | 563 | return true; |
65 | 563 | } |
66 | | |
67 | 4.84k | return false; |
68 | 5.40k | } |
69 | | |
70 | | // Helper to randomly choose HTTP methods |
71 | 1.33k | static std::string pick_method(FuzzedDataProvider& fdp) { |
72 | 1.33k | static const char* kMethods[] = { |
73 | 1.33k | "GET","POST","PUT","HEAD","DELETE","CONNECT","OPTIONS","TRACE","*" |
74 | 1.33k | }; |
75 | 1.33k | return std::string(fdp.PickValueInArray(kMethods)); |
76 | 1.33k | } |
77 | | |
78 | | // Helper to randomly choose http versions |
79 | 1.33k | static std::string pick_http_version(FuzzedDataProvider& fdp) { |
80 | | // Common + a chance to be malformed to trigger version errors. |
81 | 1.33k | switch (fdp.ConsumeIntegralInRange<int>(0, 5)) { |
82 | 680 | case 0: return "HTTP/1.1"; |
83 | 196 | case 1: return "HTTP/1.0"; |
84 | 131 | case 2: return "HTTP/2.0"; |
85 | 147 | case 3: return "HTTP/0.9"; |
86 | 125 | case 4: return "HTTX/1.1"; |
87 | 51 | default: { |
88 | 51 | std::string s = "HTTP/"; |
89 | 51 | s.push_back(char('0' + fdp.ConsumeIntegralInRange<int>(0,9))); |
90 | 51 | s.push_back('.'); |
91 | 51 | s.push_back(char('0' + fdp.ConsumeIntegralInRange<int>(0,9))); |
92 | 51 | return s; |
93 | 0 | } |
94 | 1.33k | } |
95 | 1.33k | } |
96 | | |
97 | | // Helper to check and expand buffer capcaity |
98 | 1.03k | bool ensure_lbuf_capacity(MHD_Connection& c, size_t min_needed) { |
99 | 1.03k | if (!c.daemon) { |
100 | 0 | return false; |
101 | 0 | } |
102 | | |
103 | 1.03k | if (c.rq.cntn.lbuf.data && c.rq.cntn.lbuf.size >= min_needed) { |
104 | 0 | return true; |
105 | 0 | } |
106 | 1.03k | size_t have = c.rq.cntn.lbuf.size; |
107 | 1.03k | size_t need = (min_needed > have) ? (min_needed - have) : 0; |
108 | 1.03k | if (need) { |
109 | 1.03k | mhd_daemon_extend_lbuf_up_to(c.daemon, need, &c.rq.cntn.lbuf); |
110 | 1.03k | } |
111 | 1.03k | return (c.rq.cntn.lbuf.data != nullptr) && (c.rq.cntn.lbuf.size >= min_needed); |
112 | 1.03k | } |
113 | | |
114 | | // Dummy upload actions |
115 | | const struct MHD_UploadAction kContinueAction = { |
116 | | mhd_UPLOAD_ACTION_CONTINUE, { nullptr } |
117 | | }; |
118 | | const struct MHD_UploadAction kSuspend = { |
119 | | mhd_UPLOAD_ACTION_SUSPEND, { nullptr } |
120 | | }; |
121 | | const struct MHD_UploadAction kAbort = { |
122 | | mhd_UPLOAD_ACTION_ABORT, { nullptr } |
123 | | }; |
124 | | |
125 | | // Dummy reader function |
126 | | const struct MHD_UploadAction * |
127 | | dummy_reader(struct MHD_Request*, void*, const struct MHD_String*, |
128 | | const struct MHD_StringNullable*, const struct MHD_StringNullable*, |
129 | | const struct MHD_StringNullable*, size_t, const void*, |
130 | 461 | uint_fast64_t, enum MHD_Bool) { |
131 | 461 | return &kContinueAction; |
132 | 461 | } |
133 | | |
134 | | // Dummy connection request ending function |
135 | | const struct MHD_UploadAction * |
136 | 2.57k | dummy_done(struct MHD_Request*, void*, enum MHD_PostParseResult) { |
137 | 2.57k | return &kContinueAction; |
138 | 2.57k | } |
139 | | |
140 | | // Dummy request callback function |
141 | | static const struct MHD_Action* |
142 | | dummy_request_cb(void* cls, |
143 | | struct MHD_Request* request, |
144 | | const struct MHD_String* path, |
145 | | enum MHD_HTTP_Method method, |
146 | 0 | uint_fast64_t upload_size) { |
147 | 0 | return nullptr; |
148 | 0 | } |
149 | | |
150 | | void init_daemon_connection(FuzzedDataProvider& fdp, |
151 | 2.85k | MHD_Daemon& d, MHD_Connection& c) { |
152 | | // Basic initialisation |
153 | 2.85k | d = {}; |
154 | 2.85k | c = {}; |
155 | 2.85k | c.daemon = &d; |
156 | 2.85k | c.pool = g_pool; |
157 | | |
158 | | // Configure daemon memory pool |
159 | 2.85k | d.conns.cfg.mem_pool_size = g_pool_size; |
160 | 2.85k | d.conns.cfg.mem_pool_zeroing = MHD_MEMPOOL_ZEROING_ON_RESET; |
161 | | |
162 | | // Confiugre daemon request |
163 | 2.85k | d.req_cfg.large_buf.space_left = fdp.ConsumeIntegralInRange<size_t>(256, 65536); |
164 | 2.85k | d.req_cfg.strictness = static_cast<enum MHD_ProtocolStrictLevel>( |
165 | 2.85k | fdp.ConsumeIntegralInRange<int>(0, 2)); |
166 | | |
167 | | // Safe guard for buffer space |
168 | 2.85k | if (fdp.ConsumeBool()) { |
169 | 176 | const size_t clamp = fdp.ConsumeIntegralInRange<size_t>(64, 512); |
170 | 176 | if (d.req_cfg.large_buf.space_left > clamp) |
171 | 169 | d.req_cfg.large_buf.space_left = clamp; |
172 | 176 | } |
173 | | |
174 | | // Configure connection request and general settings |
175 | 2.85k | c.discard_request = false; |
176 | 2.85k | c.suspended = false; |
177 | 2.85k | c.timeout.milsec = fdp.ConsumeIntegralInRange<uint32_t>(0, 4096); |
178 | 2.85k | c.timeout.last_act = 0; |
179 | | |
180 | | // Add dummy callback function |
181 | 2.85k | d.req_cfg.cb = dummy_request_cb; |
182 | 2.85k | d.req_cfg.cb_cls = nullptr; |
183 | 2.85k | } |
184 | | |
185 | 2.85k | void init_connection_buffer(FuzzedDataProvider& fdp, MHD_Connection& c) { |
186 | | // Prepare connection buffer in memory pool |
187 | 2.85k | size_t required = 0; |
188 | 2.85k | const size_t capacity = fdp.ConsumeIntegralInRange<size_t>(512, 16384); |
189 | 2.85k | char* buf = static_cast<char*>(mhd_pool_try_alloc(c.pool, capacity, &required)); |
190 | 2.85k | if (!buf) { |
191 | 1.52k | c.read_buffer = nullptr; |
192 | 1.52k | c.read_buffer_size = 0; |
193 | 1.52k | c.read_buffer_offset = 0; |
194 | 1.52k | return; |
195 | 1.52k | } |
196 | | |
197 | | // Randomly choose configuration |
198 | 1.33k | std::string hdrs; |
199 | 1.33k | const std::string method = pick_method(fdp); |
200 | 1.33k | const std::string version = pick_http_version(fdp); |
201 | 1.33k | const std::string target = (method == "*") ? "*" : (fdp.ConsumeBool() ? "/upload" : "/x?y=z"); |
202 | | |
203 | | // Randomly break line endings in request line |
204 | 1.33k | const bool bare_lf = fdp.ConsumeBool(); |
205 | 1.33k | const bool bare_cr = (!bare_lf) && fdp.ConsumeBool(); |
206 | 8.64k | auto EOL = [&](bool final=false) { |
207 | 8.64k | if (bare_lf) { |
208 | 5.74k | return std::string("\n"); |
209 | 5.74k | } |
210 | 2.90k | if (bare_cr) { |
211 | 707 | return std::string("\r"); |
212 | 707 | } |
213 | | |
214 | 2.19k | return std::string("\r\n"); |
215 | 2.90k | }; |
216 | 1.33k | std::string req = method + " " + target + " " + version + EOL(); |
217 | | |
218 | | // Host headers |
219 | 1.33k | int host_count = 0; |
220 | 1.33k | if (version == "HTTP/1.1") { |
221 | 681 | host_count = fdp.ConsumeIntegralInRange<int>(0,2); |
222 | 681 | } else { |
223 | 649 | host_count = fdp.ConsumeIntegralInRange<int>(0,1); |
224 | 649 | } |
225 | | |
226 | 1.55k | for (int i = 0; i < host_count; ++i) { |
227 | 220 | if (fdp.ConsumeBool()) { |
228 | 95 | hdrs += " Host: fuzz" + std::to_string(i) + EOL(); |
229 | 125 | } else if (fdp.ConsumeBool()) { |
230 | 70 | hdrs += "Host : fuzz" + std::to_string(i) + EOL(); |
231 | 70 | } else { |
232 | 55 | hdrs += "Host: fuzz" + std::to_string(i) + EOL(); |
233 | 55 | } |
234 | 220 | } |
235 | | |
236 | | // Transfer-Encoding and Content-Length headers |
237 | 1.33k | const bool add_te = fdp.ConsumeBool(); |
238 | 1.33k | const bool add_cl = fdp.ConsumeBool(); |
239 | 1.33k | std::string te_val = fdp.PickValueInArray({"chunked","gzip","br","compress"}); |
240 | 1.33k | if (add_te) { |
241 | 415 | hdrs += "Transfer-Encoding: " + te_val + EOL(); |
242 | 415 | } |
243 | 1.33k | if (add_cl) { |
244 | 179 | std::string cl; |
245 | 179 | switch (fdp.ConsumeIntegralInRange<int>(0,3)) { |
246 | 39 | case 0: cl = "0"; break; |
247 | 72 | case 1: cl = std::to_string(fdp.ConsumeIntegralInRange<uint32_t>(0, 1<<20)); |
248 | 72 | break; |
249 | 29 | case 2: cl = "18446744073709551616"; break; |
250 | 39 | default: cl = "xyz"; break; |
251 | 179 | } |
252 | 179 | hdrs += "Content-Length: " + cl + EOL(); |
253 | 179 | } |
254 | | |
255 | | // Random minimum headers |
256 | 1.33k | if (fdp.ConsumeBool()) { |
257 | 186 | hdrs += (fdp.ConsumeBool() ? "Expect: 100-continue" : "Expect: something-weird") + EOL(); |
258 | 186 | } |
259 | | |
260 | 1.33k | bool detect_mpart = false; |
261 | 1.33k | switch (c.rq.app_act.head_act.data.post_parse.enc) { |
262 | 570 | case MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA: |
263 | 570 | g_mpart_boundary = "fuzz" + std::to_string(fdp.ConsumeIntegral<uint32_t>()); |
264 | 570 | hdrs += "Content-Type: multipart/form-data; boundary=" + g_mpart_boundary + EOL(); |
265 | 570 | break; |
266 | 49 | case MHD_HTTP_POST_ENCODING_FORM_URLENCODED: |
267 | 49 | hdrs += "Content-Type: application/x-www-form-urlencoded" + EOL(); |
268 | 49 | break; |
269 | 560 | case MHD_HTTP_POST_ENCODING_TEXT_PLAIN: |
270 | 560 | hdrs += "Content-Type: text/plain" + EOL(); |
271 | 560 | break; |
272 | 151 | default: case MHD_HTTP_POST_ENCODING_OTHER: |
273 | 151 | detect_mpart = fdp.ConsumeBool(); |
274 | 151 | if (detect_mpart) { |
275 | 58 | g_mpart_boundary = "fuzz" + std::to_string(fdp.ConsumeIntegral<uint32_t>()); |
276 | 58 | hdrs += "Content-Type: multipart/form-data; boundary=" + g_mpart_boundary + EOL(); |
277 | 93 | } else { |
278 | 93 | hdrs += std::string("Content-Type: ") |
279 | 93 | + (fdp.ConsumeBool() ? "application/x-www-form-urlencoded" : "text/plain") + EOL(); |
280 | 93 | } |
281 | 151 | break; |
282 | 1.33k | } |
283 | | |
284 | | // Randomly add some chunked headers |
285 | 1.33k | const bool add_te_chunked = fdp.ConsumeBool(); |
286 | 1.33k | if (add_te_chunked) { |
287 | 302 | hdrs += "Transfer-Encoding: chunked" + EOL(); |
288 | 302 | } |
289 | 1.33k | if (fdp.ConsumeBool()) { |
290 | 243 | const uint32_t cl = fdp.ConsumeIntegralInRange<uint32_t>(0, 256); |
291 | 243 | hdrs += "Content-Length: " + std::to_string(cl) + EOL(); |
292 | 243 | } |
293 | 1.33k | if (add_te_chunked && fdp.ConsumeBool()) { |
294 | 198 | if (fdp.ConsumeBool()) |
295 | 105 | hdrs += "Trailer: X-Fuzz-Trace" + EOL(); |
296 | 93 | else |
297 | 93 | hdrs += "Trailer: X-One, X-Two" + EOL(); |
298 | 198 | } |
299 | 1.33k | if (fdp.ConsumeBool()) { |
300 | 255 | hdrs += (fdp.ConsumeBool() ? "Expect: 100-continue" : "Expect: something-weird") + EOL(); |
301 | 255 | } |
302 | | |
303 | | // Connection ending line |
304 | 1.33k | hdrs += "Connection: close" + EOL(); |
305 | 1.33k | std::string end = EOL() + EOL(); |
306 | | |
307 | | // Write into the read buffer |
308 | 1.33k | std::string full = req + hdrs + end; |
309 | 1.33k | const size_t n = (full.size() <= capacity) ? full.size() : capacity; |
310 | 1.33k | memcpy(buf, full.data(), n); |
311 | | |
312 | 1.33k | c.read_buffer = buf; |
313 | 1.33k | c.read_buffer_size = capacity; |
314 | 1.33k | c.read_buffer_offset = n; |
315 | 1.33k | } |
316 | | |
317 | 2.85k | void init_parsing_configuration(FuzzedDataProvider& fdp, MHD_Connection& c) { |
318 | 2.85k | MHD_HTTP_PostEncoding enc; |
319 | | |
320 | | // Configure connection encoding abd methods |
321 | 2.85k | if (fdp.ConsumeBool()) { |
322 | 1.78k | c.rq.app_act.head_act.act = mhd_ACTION_POST_PARSE; |
323 | 1.78k | } else { |
324 | 1.07k | c.rq.app_act.head_act.act = mhd_ACTION_NO_ACTION; |
325 | 1.07k | } |
326 | 2.85k | if (fdp.ConsumeBool()) { |
327 | 1.10k | enc = MHD_HTTP_POST_ENCODING_TEXT_PLAIN; |
328 | 1.75k | } else if (fdp.ConsumeBool()) { |
329 | 305 | enc = MHD_HTTP_POST_ENCODING_FORM_URLENCODED; |
330 | 1.45k | } else if (fdp.ConsumeBool()) { |
331 | 1.28k | enc = MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; |
332 | 1.28k | } else { |
333 | 167 | enc = MHD_HTTP_POST_ENCODING_OTHER; |
334 | 167 | } |
335 | | |
336 | 2.85k | c.rq.app_act.head_act.data.post_parse.buffer_size = |
337 | 2.85k | fdp.ConsumeIntegralInRange<size_t>(1, 4096); |
338 | 2.85k | c.rq.app_act.head_act.data.post_parse.max_nonstream_size = |
339 | 2.85k | fdp.ConsumeIntegralInRange<size_t>(1, 4096); |
340 | | |
341 | | // Confiugre head action for connection post parsing process |
342 | 2.85k | c.rq.app_act.head_act.data.post_parse.enc = enc; |
343 | 2.85k | c.rq.app_act.head_act.data.post_parse.stream_reader = dummy_reader; |
344 | 2.85k | c.rq.app_act.head_act.data.post_parse.reader_cls = nullptr; |
345 | 2.85k | c.rq.app_act.head_act.data.post_parse.done_cb = dummy_done; |
346 | 2.85k | c.rq.app_act.head_act.data.post_parse.done_cb_cls = nullptr; |
347 | 2.85k | } |
348 | | |
349 | 2.85k | void prepare_headers_and_parse(MHD_Connection& connection, size_t size) { |
350 | | // Manually add a parameter for parsing |
351 | 5.32k | auto add_hdr = [&](const char* name_c, const std::string& val_s) { |
352 | 5.32k | const size_t nlen = strlen(name_c); |
353 | 5.32k | const size_t vlen = val_s.size(); |
354 | 5.32k | char* nbuf = static_cast<char*>(mhd_stream_alloc_memory(&connection, nlen + 1)); |
355 | 5.32k | char* vbuf = static_cast<char*>(mhd_stream_alloc_memory(&connection, vlen + 1)); |
356 | 5.32k | if (!nbuf || !vbuf) { |
357 | 53 | return; |
358 | 53 | } |
359 | 5.27k | memcpy(nbuf, name_c, nlen); nbuf[nlen] = '\0'; |
360 | 5.27k | memcpy(vbuf, val_s.data(), vlen); vbuf[vlen] = '\0'; |
361 | 5.27k | struct MHD_String name; |
362 | 5.27k | name.len = nlen; |
363 | 5.27k | name.cstr = nbuf; |
364 | 5.27k | struct MHD_String value; |
365 | 5.27k | value.len = vlen; |
366 | 5.27k | value.cstr = vbuf; |
367 | 5.27k | mhd_stream_add_field(&connection.h1_stream, MHD_VK_HEADER, &name, &value); |
368 | 5.27k | }; |
369 | 2.85k | add_hdr("Host", "fuzz"); |
370 | 2.85k | if ((size & 3u) == 0u) { |
371 | 799 | add_hdr("Transfer-Encoding", "chunked"); |
372 | 799 | } |
373 | 2.85k | if ((size & 7u) == 0u) { |
374 | 333 | add_hdr("Content-Length", "0"); |
375 | 333 | } |
376 | | |
377 | 2.85k | bool force_mpart = (connection.rq.app_act.head_act.data.post_parse.enc |
378 | 2.85k | == MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA); |
379 | 2.85k | if (!force_mpart) { |
380 | 1.57k | force_mpart = ((size & 0x3Fu) == 0u); |
381 | 1.57k | } |
382 | 2.85k | if (force_mpart) { |
383 | 1.33k | if (g_mpart_boundary.empty()) { |
384 | 0 | g_mpart_boundary = "fuzz" + std::to_string(size ^ 0x9e3779b97f4a7c15ULL); |
385 | 0 | } |
386 | 1.33k | std::string ct = "multipart/form-data; boundary=" + g_mpart_boundary; |
387 | 1.33k | add_hdr("Content-Type", ct); |
388 | 1.33k | } |
389 | | |
390 | | // If we wrote a real HTTP header for multipart, parse it so Content-Type is visible |
391 | 2.85k | connection.stage = mhd_HTTP_STAGE_INIT; |
392 | 2.85k | bool got_line = mhd_stream_get_request_line(&connection); |
393 | 2.85k | if (destroy_error_response(connection)) { |
394 | 314 | return; |
395 | 314 | } |
396 | 2.54k | if (got_line && connection.stage == mhd_HTTP_STAGE_REQ_LINE_RECEIVED) { |
397 | 531 | mhd_stream_switch_to_rq_headers_proc(&connection); |
398 | 531 | } |
399 | 2.54k | mhd_stream_parse_request_headers(&connection); |
400 | | |
401 | | // Only proceed to post-parse if header parsing did not bail out |
402 | 2.54k | destroy_error_response(connection); |
403 | 2.54k | } |
404 | | |
405 | 2.57k | void prepare_body_and_process(MHD_Connection& connection, std::string& body, size_t body_size, bool use_stream_body) { |
406 | | // Use streaming if boundary is not empty |
407 | 2.57k | if (!g_mpart_boundary.empty()) { |
408 | 2.57k | std::string wrapped; |
409 | 2.57k | wrapped.reserve(body.size() + g_mpart_boundary.size() * 2 + 128); |
410 | 2.57k | wrapped += "\r\n--"; wrapped += g_mpart_boundary; wrapped += "\r\n"; |
411 | 2.57k | wrapped += "Content-Disposition: form-data; name=\"x\"; filename=\"f\"\r\n"; |
412 | 2.57k | wrapped += "Content-Type: application/octet-stream\r\n\r\n"; |
413 | 2.57k | wrapped += body; wrapped += "\r\n"; |
414 | 2.57k | wrapped += "--"; wrapped += g_mpart_boundary; wrapped += "--\r\n"; |
415 | 2.57k | body.swap(wrapped); |
416 | 2.57k | body_size = body.size(); |
417 | 2.57k | } |
418 | | |
419 | 2.57k | if (!use_stream_body) { |
420 | | // Fuzz mhd_stream_prepare_for_post_parse once and mhd_stream_post_parse |
421 | 1.35k | mhd_stream_prepare_for_post_parse(&connection); |
422 | 1.35k | mhd_stream_post_parse(&connection, &body_size, &body[0]); |
423 | 1.35k | mark_post_parse_ready(connection); |
424 | 1.35k | } else { |
425 | | // Try prepare the body by streaming connection buffer |
426 | 1.22k | const bool want_chunked = (connection.rq.have_chunked_upload == MHD_YES); |
427 | | |
428 | 1.22k | std::string to_feed; |
429 | 1.22k | if (want_chunked) { |
430 | 730 | auto append_chunk = [&](const char* data, size_t len) { |
431 | 730 | char hex[32]; |
432 | 730 | const int n = snprintf(hex, sizeof(hex), "%zx", len); |
433 | 730 | to_feed.append(hex, (size_t)n); |
434 | 730 | if ((len & 3u) == 0u) { |
435 | 230 | to_feed += ";ext=1"; |
436 | 230 | } |
437 | 730 | to_feed += "\r\n"; |
438 | 730 | to_feed.append(data, len); |
439 | 730 | to_feed += "\r\n"; |
440 | 730 | }; |
441 | 365 | if (body_size <= 32) { |
442 | 0 | append_chunk(body.data(), body_size); |
443 | 365 | } else { |
444 | 365 | const size_t mid = body_size / 2; |
445 | 365 | append_chunk(body.data(), mid); |
446 | 365 | append_chunk(body.data() + mid, body_size - mid); |
447 | 365 | } |
448 | 365 | to_feed += "0\r\n"; |
449 | 365 | if (body_size & 1) { |
450 | 141 | to_feed += "X-Fuzz-Trace: 1\r\n\r\n"; |
451 | 224 | } else { |
452 | 224 | to_feed += "\r\n"; |
453 | 224 | } |
454 | 857 | } else { |
455 | | // Non-chunked body is handled as is |
456 | 857 | to_feed.assign(body.data(), body_size); |
457 | 857 | } |
458 | | |
459 | | // Stage into the connection read buffer (allocate if needed). |
460 | 1.22k | size_t feed_sz = to_feed.size(); |
461 | 1.22k | bool staged = false; |
462 | 1.22k | if (connection.read_buffer && connection.read_buffer_size >= feed_sz) { |
463 | 170 | memcpy(connection.read_buffer, to_feed.data(), feed_sz); |
464 | 170 | connection.read_buffer_offset = feed_sz; |
465 | 170 | staged = true; |
466 | 1.05k | } else { |
467 | 1.05k | size_t need = 0; |
468 | 1.05k | char *nb = (char*) mhd_pool_try_alloc(connection.pool, feed_sz, &need); |
469 | 1.05k | if (nb) { |
470 | 862 | memcpy(nb, to_feed.data(), feed_sz); |
471 | 862 | connection.read_buffer = nb; |
472 | 862 | connection.read_buffer_size = feed_sz; |
473 | 862 | connection.read_buffer_offset = feed_sz; |
474 | 862 | staged = true; |
475 | 862 | } |
476 | 1.05k | } |
477 | | |
478 | 1.22k | if (staged) { |
479 | | // Use stream body approach if success |
480 | 1.03k | const size_t min_needed = body_size + 1; |
481 | 1.03k | if (ensure_lbuf_capacity(connection, min_needed)) { |
482 | | // Only post parse the request if buffer is enough |
483 | 907 | connection.stage = mhd_HTTP_STAGE_BODY_RECEIVING; |
484 | 907 | connection.rq.have_chunked_upload = MHD_NO; |
485 | 907 | connection.rq.cntn.cntn_size = (uint64_t) body_size; |
486 | 907 | mhd_stream_prepare_for_post_parse(&connection); |
487 | 907 | mhd_stream_process_request_body(&connection); |
488 | 907 | mark_post_parse_ready(connection); |
489 | 907 | } else { |
490 | | // Fall back if not enough buffer |
491 | 125 | size_t tmp = body_size; |
492 | 125 | mhd_stream_prepare_for_post_parse(&connection); |
493 | 125 | mhd_stream_post_parse(&connection, &tmp, body.data()); |
494 | 125 | mark_post_parse_ready(connection); |
495 | 125 | } |
496 | 1.03k | } else { |
497 | | // Use post prase approach if stream body failed |
498 | 190 | mhd_stream_prepare_for_post_parse(&connection); |
499 | 190 | mhd_stream_post_parse(&connection, &body_size, &body[0]); |
500 | 190 | mark_post_parse_ready(connection); |
501 | 190 | } |
502 | 1.22k | } |
503 | 2.57k | } |
504 | | |
505 | 2.57k | void final_cleanup(MHD_Connection& connection, MHD_Daemon& daemon) { |
506 | | // Post process response |
507 | 2.57k | mhd_stream_switch_from_recv_to_send(&connection); |
508 | 2.57k | mhd_stream_process_req_recv_finished(&connection); |
509 | 2.57k | mhd_stream_release_write_buffer(&connection); |
510 | | |
511 | | // Release buffers in daemon to avoid memory leakage |
512 | 2.57k | if (connection.rq.cntn.lbuf.data != nullptr || connection.rq.cntn.lbuf.size != 0) { |
513 | 0 | mhd_daemon_free_lbuf(&daemon, &connection.rq.cntn.lbuf); |
514 | 0 | } |
515 | | |
516 | | // Clean post parse body status |
517 | 2.57k | clear_post_parse_ready(connection); |
518 | 2.57k | } |