curl-fuzzer C++ reference
Loading...
Searching...
No Matches
request_data.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <algorithm>
13#include <cstddef>
14#include <cstdio>
15#include <cstring>
16#include <limits>
17#include <string>
18
21
22namespace proto_fuzzer {
23
24namespace {
25
32const char* BoundedCString(const std::string& value, std::size_t limit, std::string* truncated) {
33 if (value.size() <= limit) {
34 return value.c_str();
35 }
36 truncated->assign(value.data(), limit);
37 return truncated->c_str();
38}
39
46bool NeedsUploadCallbacks(const curl::fuzzer::proto::Scenario& scenario) {
47 // curl's TELNET implementation polls stdin unless a READFUNCTION was
48 // explicitly installed. Always provide the bounded per-scenario source,
49 // even when there is no upload payload to send.
50 if (scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET || scenario.has_upload()) {
51 return true;
52 }
53 const std::size_t option_count = RuntimeOptionCount(scenario);
54 for (std::size_t index = 0; index < option_count; ++index) {
55 const auto& option = scenario.options(static_cast<int>(index));
56 if (option.option_id() == curl::fuzzer::proto::CURLOPT_UPLOAD ||
57 option.option_id() == curl::fuzzer::proto::CURLOPT_POST) {
58 return true;
59 }
60 }
61 return false;
62}
63
67const char* MimeEncoderName(curl::fuzzer::proto::MimeEncoder encoder) {
68 switch (encoder) {
69 case curl::fuzzer::proto::MIME_ENCODER_BINARY:
70 return "binary";
71 case curl::fuzzer::proto::MIME_ENCODER_8BIT:
72 return "8bit";
73 case curl::fuzzer::proto::MIME_ENCODER_7BIT:
74 return "7bit";
75 case curl::fuzzer::proto::MIME_ENCODER_BASE64:
76 return "base64";
77 case curl::fuzzer::proto::MIME_ENCODER_QUOTED_PRINTABLE:
78 return "quoted-printable";
79 case curl::fuzzer::proto::MIME_ENCODER_UNSPECIFIED:
80 default:
81 return nullptr;
82 }
83}
84
89template <typename RepeatedBytes>
90CurlSlistPtr BuildStringList(const RepeatedBytes& values, std::size_t count_limit, std::size_t value_limit,
91 std::size_t* applied) {
92 CurlSlistPtr list;
93 std::string truncated;
94 const std::size_t count = std::min<std::size_t>(count_limit, values.size());
95 for (std::size_t i = 0; i < count; ++i) {
96 const std::string& value = values.Get(static_cast<int>(i));
97 curl_slist* appended = curl_slist_append(list.get(), BoundedCString(value, value_limit, &truncated));
98 if (appended == nullptr) {
99 break;
100 }
101 // append retains the existing nodes and may return the same head. Release
102 // before resetting so adopting the successful result cannot free them.
103 (void)list.release();
104 list.reset(appended);
105 ++*applied;
106 }
107 return list;
108}
109
113template <typename ProtoPart>
114void ApplyPartMetadata(curl_mimepart* part, const ProtoPart& source, RequestBuildStats* stats) {
115 // Reuse the rare compatibility-path allocation across all three fields;
116 // ordinary postprocessed metadata never writes this scratch string.
117 std::string truncated;
118 if (!source.name().empty()) {
119 (void)curl_mime_name(part, BoundedCString(source.name(), scenario_limits::kMaxMetadataBytes, &truncated));
120 }
121 if (!source.filename().empty()) {
122 (void)curl_mime_filename(part, BoundedCString(source.filename(), scenario_limits::kMaxMetadataBytes, &truncated));
123 }
124 if (!source.content_type().empty()) {
125 (void)curl_mime_type(part, BoundedCString(source.content_type(), scenario_limits::kMaxMetadataBytes, &truncated));
126 }
127 if (const char* encoder = MimeEncoderName(source.encoder())) {
128 (void)curl_mime_encoder(part, encoder);
129 }
130
131 std::size_t header_count = 0;
132 CurlSlistPtr headers = BuildStringList(source.headers(), scenario_limits::kMaxMimeHeadersPerPart,
134 if (headers != nullptr) {
135 // take_ownership=1 is crucial: unlike the strings above, MIME retains the
136 // list pointer. Once attached, the root curl_mime_free call recursively
137 // releases it, including lists on nested parts.
138 const CURLcode result = curl_mime_headers(part, headers.get(), 1);
139 if (result == CURLE_OK) {
140 (void)headers.release();
141 stats->mime_headers += header_count;
142 }
143 }
144}
145
149void ApplyPartData(curl_mimepart* part, const std::string& data) {
150 const std::size_t size = std::min(data.size(), scenario_limits::kMaxMimeDataBytes);
151 const char* bytes = data.empty() ? "" : data.data();
152 (void)curl_mime_data(part, bytes, size);
153}
154
159void ApplyGeneratedPartData(curl_mimepart* part, const curl::fuzzer::proto::GeneratedBytes& source,
160 std::size_t* remaining_generated_bytes, RequestBuildStats* stats) {
161 const std::size_t pattern_size = std::min(source.pattern().size(), scenario_limits::kMaxGeneratedMimePatternBytes);
162 if (pattern_size == 0 || *remaining_generated_bytes == 0) {
163 (void)curl_mime_data(part, "", 0);
164 return;
165 }
166
167 const std::size_t max_repeat_count = *remaining_generated_bytes / pattern_size;
168 const std::size_t repeat_count =
169 std::min<std::size_t>(static_cast<std::size_t>(source.repeat_count()), max_repeat_count);
170 const std::size_t materialized_size = repeat_count * pattern_size;
171
172 std::string materialized(materialized_size, '\0');
173 for (std::size_t offset = 0; offset < materialized_size; offset += pattern_size) {
174 std::copy_n(source.pattern().data(), pattern_size, materialized.data() + offset);
175 }
176 const char* bytes = materialized.empty() ? "" : materialized.data();
177 if (curl_mime_data(part, bytes, materialized.size()) == CURLE_OK) {
178 stats->generated_mime_bytes += materialized.size();
179 }
180 *remaining_generated_bytes -= materialized.size();
181}
182
186void PopulateSubparts(curl_mime* mime, const curl::fuzzer::proto::MimeSubparts& source, std::size_t* remaining_parts,
187 std::size_t* remaining_generated_bytes, RequestBuildStats* stats) {
188 const std::size_t count = std::min<std::size_t>(scenario_limits::kMaxNestedMimeParts, source.parts_size());
189 for (std::size_t i = 0; i < count && *remaining_parts != 0; ++i) {
190 curl_mimepart* part = curl_mime_addpart(mime);
191 if (part == nullptr) {
192 break;
193 }
194 --*remaining_parts;
195 ++stats->mime_parts;
196 const auto& proto_part = source.parts(static_cast<int>(i));
197 ApplyPartMetadata(part, proto_part, stats);
198 if (proto_part.content_case() == curl::fuzzer::proto::MimeDataPart::kGeneratedData) {
199 ApplyGeneratedPartData(part, proto_part.generated_data(), remaining_generated_bytes, stats);
200 } else {
201 // Preserve the historical leaf behavior: an absent source is applied as
202 // empty data just like an explicitly empty field 2.
203 ApplyPartData(part, proto_part.data());
204 }
205 }
206}
207
211CurlMimePtr BuildMimePost(CURL* easy, const curl::fuzzer::proto::MimePost& source, UploadScriptState* upload_state,
212 RequestBuildStats* stats) {
213 CurlMimePtr mime(curl_mime_init(easy));
214 if (mime == nullptr) {
215 return nullptr;
216 }
217
218 std::size_t remaining_parts = scenario_limits::kMaxTotalMimeParts;
219 std::size_t remaining_generated_bytes = scenario_limits::kMaxGeneratedMimeDataBytes;
220 const std::size_t count = std::min<std::size_t>(scenario_limits::kMaxTopLevelMimeParts, source.parts_size());
221 for (std::size_t i = 0; i < count && remaining_parts != 0; ++i) {
222 curl_mimepart* part = curl_mime_addpart(mime.get());
223 if (part == nullptr) {
224 break;
225 }
226 --remaining_parts;
227 ++stats->mime_parts;
228 const auto& proto_part = source.parts(static_cast<int>(i));
229 ApplyPartMetadata(part, proto_part, stats);
230
231 switch (proto_part.content_case()) {
232 case curl::fuzzer::proto::MimePart::kData:
233 ApplyPartData(part, proto_part.data());
234 break;
235 case curl::fuzzer::proto::MimePart::kGeneratedData:
236 ApplyGeneratedPartData(part, proto_part.generated_data(), &remaining_generated_bytes, stats);
237 break;
238 case curl::fuzzer::proto::MimePart::kUploadCallback:
239 if (upload_state != nullptr && upload_state->InstallAsMimePart(part) == CURLE_OK) {
240 ++stats->mime_callback_parts;
241 }
242 break;
243 case curl::fuzzer::proto::MimePart::kSubparts: {
244 CurlMimePtr subparts(curl_mime_init(easy));
245 if (subparts == nullptr) {
246 break;
247 }
248 PopulateSubparts(subparts.get(), proto_part.subparts(), &remaining_parts, &remaining_generated_bytes, stats);
249 if (curl_mime_subparts(part, subparts.get()) == CURLE_OK) {
250 (void)subparts.release();
251 }
252 break;
253 }
254 case curl::fuzzer::proto::MimePart::CONTENT_NOT_SET:
255 default:
256 break;
257 }
258 }
259 return mime;
260}
261
262} // namespace
263
272UploadScriptState::UploadScriptState(const curl::fuzzer::proto::Scenario& scenario)
273 : data_(),
274 read_step_count_(0),
275 total_size_(scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET ? 0 : scenario_limits::kMaxUploadBytes),
276 max_read_size_(scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET ? scenario_limits::kMaxTelnetUploadReadSize
277 : scenario_limits::kMaxUploadReadSize),
278 offset_(0),
279 next_read_size_(0),
280 seek_calls_(0),
281 terminal_(curl::fuzzer::proto::UPLOAD_TERMINAL_EOF),
282 seek_result_(curl::fuzzer::proto::UPLOAD_SEEK_CANTSEEK),
283 before_read_callback_(nullptr),
284 before_read_userdata_(nullptr),
285 scripted_(scenario.has_upload()) {
286 if (!scripted_) {
287 return;
288 }
289
290 const auto& upload = scenario.upload();
291 const std::size_t data_limit = scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET
294 const std::size_t data_size = std::min(upload.data().size(), data_limit);
295 data_ = std::string_view(upload.data().data(), data_size);
296 total_size_ = data_.size();
297 terminal_ = upload.terminal();
298 if (scenario.scheme() != curl::fuzzer::proto::SCHEME_TELNET &&
299 terminal_ == curl::fuzzer::proto::UPLOAD_TERMINAL_PAUSE) {
300 // Event-driven protocols need an external resume source. Interpret this
301 // TELNET-specific outcome as EOF before curl can retain a paused transfer.
302 terminal_ = curl::fuzzer::proto::UPLOAD_TERMINAL_EOF;
303 }
304 seek_result_ = upload.seek_result();
305
306 const std::size_t read_step_limit = scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET
309 read_step_count_ = std::min<std::size_t>(upload.read_sizes_size(), read_step_limit);
310 for (std::size_t i = 0; i < read_step_count_; ++i) {
311 const std::size_t requested = upload.read_sizes(static_cast<int>(i));
312 // Zero-as-one ensures every retained step makes progress; see the schema
313 // comment for why zero is not treated as an early EOF sentinel.
314 read_sizes_[i] = std::max<std::size_t>(1, std::min(requested, max_read_size_));
315 }
316}
317
327std::size_t UploadScriptState::Read(char* buffer, std::size_t capacity) {
328 // TELNET can produce negotiation replies while one curl_multi_perform call
329 // owns the thread. Empty them before returning more callback bytes;
330 // otherwise send_telnet_data() can consume the whole bounded transfer
331 // timeout while the synchronous path waits for socket capacity.
332 if (before_read_callback_ != nullptr) {
333 before_read_callback_(before_read_userdata_);
334 }
335 if (offset_ >= total_size_) {
336 switch (terminal_) {
337 case curl::fuzzer::proto::UPLOAD_TERMINAL_ABORT:
338 return CURL_READFUNC_ABORT;
339 case curl::fuzzer::proto::UPLOAD_TERMINAL_PAUSE:
340 return CURL_READFUNC_PAUSE;
341 case curl::fuzzer::proto::UPLOAD_TERMINAL_EOF:
342 default:
343 return 0;
344 }
345 }
346 if (buffer == nullptr || capacity == 0) {
347 return 0;
348 }
349
350 std::size_t chunk_limit = std::min(capacity, max_read_size_);
351 if (next_read_size_ < read_step_count_) {
352 chunk_limit = std::min(chunk_limit, read_sizes_[next_read_size_++]);
353 }
354 const std::size_t count = std::min(chunk_limit, total_size_ - offset_);
355 if (scripted_) {
356 std::memcpy(buffer, data_.data() + offset_, count);
357 } else {
358 std::memset(buffer, 'U', count);
359 }
360 offset_ += count;
361 return count;
362}
363
372int UploadScriptState::Seek(curl_off_t requested_offset, int origin) {
373 ++seek_calls_;
374 switch (seek_result_) {
375 case curl::fuzzer::proto::UPLOAD_SEEK_CANTSEEK:
376 return CURL_SEEKFUNC_CANTSEEK;
377 case curl::fuzzer::proto::UPLOAD_SEEK_FAIL:
378 return CURL_SEEKFUNC_FAIL;
379 case curl::fuzzer::proto::UPLOAD_SEEK_OK:
380 break;
381 default:
382 return CURL_SEEKFUNC_CANTSEEK;
383 }
384
385 const curl_off_t current = static_cast<curl_off_t>(offset_);
386 const curl_off_t end = static_cast<curl_off_t>(total_size_);
387 curl_off_t base = 0;
388 switch (origin) {
389 case SEEK_SET:
390 base = 0;
391 break;
392 case SEEK_CUR:
393 base = current;
394 break;
395 case SEEK_END:
396 base = end;
397 break;
398 default:
399 return CURL_SEEKFUNC_FAIL;
400 }
401
402 // Both base and end are at most 16 KiB. Comparing the requested delta to
403 // these small bounds before addition handles even CURL_OFF_T_MIN safely.
404 if (requested_offset < -base || requested_offset > end - base) {
405 return CURL_SEEKFUNC_FAIL;
406 }
407 offset_ = static_cast<std::size_t>(base + requested_offset);
408 next_read_size_ = 0;
409 return CURL_SEEKFUNC_OK;
410}
411
417std::size_t UploadScriptState::ReadCallback(char* buffer, std::size_t size, std::size_t nitems, void* userdata) {
418 if (userdata == nullptr || size == 0 || nitems == 0) {
419 return 0;
420 }
421 const std::size_t max = std::numeric_limits<std::size_t>::max();
422 if (nitems > max / size) {
423 return CURL_READFUNC_ABORT;
424 }
425 return static_cast<UploadScriptState*>(userdata)->Read(buffer, size * nitems);
426}
427
431int UploadScriptState::SeekCallback(void* userdata, curl_off_t offset, int origin) {
432 if (userdata == nullptr) {
433 return CURL_SEEKFUNC_FAIL;
434 }
435 return static_cast<UploadScriptState*>(userdata)->Seek(offset, origin);
436}
437
439std::size_t UploadScriptState::data_size() const { return total_size_; }
440
442std::size_t UploadScriptState::read_step_count() const { return read_step_count_; }
443
445std::size_t UploadScriptState::offset() const { return offset_; }
446
448std::size_t UploadScriptState::seek_calls() const { return seek_calls_; }
449
451bool UploadScriptState::scripted() const { return scripted_; }
452
458CURLcode UploadScriptState::InstallAsMimePart(curl_mimepart* part) {
459 if (part == nullptr) {
460 return CURLE_BAD_FUNCTION_ARGUMENT;
461 }
462 return curl_mime_data_cb(part, static_cast<curl_off_t>(total_size_), &UploadScriptState::ReadCallback,
463 &UploadScriptState::SeekCallback, nullptr, this);
464}
465
468void UploadScriptState::SetBeforeReadCallback(BeforeReadCallback callback, void* userdata) {
469 before_read_callback_ = callback;
470 before_read_userdata_ = userdata;
471}
472
483ScenarioRequestData::ScenarioRequestData(CURL* easy, const curl::fuzzer::proto::Scenario& scenario,
484 bool apply_resolve_entries)
485 : easy_(easy),
486 request_headers_(nullptr),
487 resolve_entries_(nullptr),
488 telnet_options_(nullptr),
489 mail_rcpt_(nullptr),
490 upload_state_(scenario),
491 mime_post_(nullptr),
492 upload_callbacks_installed_(false),
493 resolve_entries_ready_(!apply_resolve_entries) {
494 if (easy_ == nullptr) {
495 return;
496 }
497
498 // Complete all allocating work before installing retained pointers. If a
499 // C++ allocation throws, scoped owners unwind without leaving the easy
500 // handle pointing into a partially constructed request-data object.
501 bool resolve_entries_built = false;
502 if (apply_resolve_entries) {
503 resolve_entries_ = BuildStringList(scenario.resolve_entries(), scenario_limits::kMaxResolveEntries,
504 scenario_limits::kMaxResolveEntryBytes, &stats_.resolve_entries);
505 // This mapping is deliberately last: preceding wildcard or removal
506 // mutations may exercise host-cache parsing, but cannot redirect the
507 // canonical resolver-lane origin away from the in-process mock.
508 curl_slist* appended = curl_slist_append(resolve_entries_.get(), "resolve.test:80:127.0.0.1");
509 if (appended != nullptr) {
510 (void)resolve_entries_.release();
511 resolve_entries_.reset(appended);
512 resolve_entries_built = true;
513 }
514 }
515
516 // HTTP headers/MIME and TELNET options are mutually exclusive because only
517 // the selected protocol can observe them. Avoid allocating protocol-inert
518 // lists and trees in compatibility inputs that bypass target policy.
519 if (scenario.scheme() == curl::fuzzer::proto::SCHEME_TELNET) {
520 telnet_options_ = BuildStringList(scenario.telnet_options(), scenario_limits::kMaxTelnetOptions,
521 scenario_limits::kMaxTelnetOptionBytes, &stats_.telnet_options);
522 } else {
523 request_headers_ = BuildStringList(scenario.request_headers(), scenario_limits::kMaxRequestHeaders,
524 scenario_limits::kMaxMetadataBytes, &stats_.request_headers);
525 if (scenario.has_mime_post()) {
526 mime_post_ = BuildMimePost(easy_, scenario.mime_post(), &upload_state_, &stats_);
527 }
528 }
529
530 if (scenario.scheme() == curl::fuzzer::proto::SCHEME_SMTP || scenario.scheme() == curl::fuzzer::proto::SCHEME_SMTPS ||
531 !scenario.mail_rcpt().empty()) {
532 // Recipient list entries borrow protobuf-owned storage only for the
533 // duration of BuildStringList; curl_slist_append copies each address.
534 mail_rcpt_ = BuildStringList(scenario.mail_rcpt(), scenario_limits::kMaxMailRecipients,
535 scenario_limits::kMaxMailRecipientBytes, &stats_.mail_rcpt);
536 }
537
538 if (resolve_entries_built) {
539 resolve_entries_ready_ = curl_easy_setopt(easy_, CURLOPT_RESOLVE, resolve_entries_.get()) == CURLE_OK;
540 }
541
542 if (NeedsUploadCallbacks(scenario)) {
543 // Install a per-run memory source even when Scenario.upload is absent but
544 // CURLOPT_UPLOAD or TELNET may request caller input. Non-TELNET schemes
545 // retain the historical fallback bytes; TELNET returns EOF. Either result
546 // replaces stdin and cannot block OSS-Fuzz. The state remains scoped to
547 // the complete drive so retries cannot share a cursor across iterations.
548 upload_callbacks_installed_ = true;
549 (void)curl_easy_setopt(easy_, CURLOPT_READFUNCTION, &UploadScriptState::ReadCallback);
550 (void)curl_easy_setopt(easy_, CURLOPT_READDATA, &upload_state_);
551 (void)curl_easy_setopt(easy_, CURLOPT_SEEKFUNCTION, &UploadScriptState::SeekCallback);
552 (void)curl_easy_setopt(easy_, CURLOPT_SEEKDATA, &upload_state_);
553 }
554
555 if (telnet_options_ != nullptr) {
556 (void)curl_easy_setopt(easy_, CURLOPT_TELNETOPTIONS, telnet_options_.get());
557 }
558 if (mail_rcpt_ != nullptr) {
559 (void)curl_easy_setopt(easy_, CURLOPT_MAIL_RCPT, mail_rcpt_.get());
560 }
561 if (request_headers_ != nullptr) {
562 (void)curl_easy_setopt(easy_, CURLOPT_HTTPHEADER, request_headers_.get());
563 }
564 if (mime_post_ != nullptr) {
565 (void)curl_easy_setopt(easy_, CURLOPT_MIMEPOST, mime_post_.get());
566 }
567}
568
575 if (easy_ != nullptr) {
576 // Clear callbacks before their userdata member is destroyed. There is no
577 // perform in this destructor, but making the handle non-dangling keeps the
578 // ownership rule robust if cleanup later gains diagnostics or getinfo.
579 if (upload_callbacks_installed_) {
580 (void)curl_easy_setopt(easy_, CURLOPT_SEEKFUNCTION, nullptr);
581 (void)curl_easy_setopt(easy_, CURLOPT_SEEKDATA, nullptr);
582 (void)curl_easy_setopt(easy_, CURLOPT_READFUNCTION, nullptr);
583 (void)curl_easy_setopt(easy_, CURLOPT_READDATA, nullptr);
584 }
585 if (mime_post_ != nullptr) {
586 (void)curl_easy_setopt(easy_, CURLOPT_MIMEPOST, nullptr);
587 }
588 if (request_headers_ != nullptr) {
589 (void)curl_easy_setopt(easy_, CURLOPT_HTTPHEADER, nullptr);
590 }
591 if (resolve_entries_ != nullptr) {
592 (void)curl_easy_setopt(easy_, CURLOPT_RESOLVE, nullptr);
593 }
594 if (telnet_options_ != nullptr) {
595 (void)curl_easy_setopt(easy_, CURLOPT_TELNETOPTIONS, nullptr);
596 }
597 if (mail_rcpt_ != nullptr) {
598 (void)curl_easy_setopt(easy_, CURLOPT_MAIL_RCPT, nullptr);
599 }
600 }
601 // Callback MIME parts retain upload_state_ as userdata. Release the root
602 // explicitly while that state is still live instead of relying only on
603 // reverse member destruction order.
604 mime_post_.reset();
605}
606
610const RequestBuildStats& ScenarioRequestData::stats() const { return stats_; }
611
615const UploadScriptState& ScenarioRequestData::upload_state() const { return upload_state_; }
616
621bool ScenarioRequestData::upload_callbacks_installed() const { return upload_callbacks_installed_; }
622
626bool ScenarioRequestData::resolve_entries_ready() const { return resolve_entries_ready_; }
627
634 upload_state_.SetBeforeReadCallback(callback, userdata);
635}
636
637} // namespace proto_fuzzer
~ScenarioRequestData()
Detach pointer options/callbacks while the easy handle is valid, then release request allocations.
const RequestBuildStats & stats() const
Return resource counts after caps and allocation failures were applied, without exposing or transferr...
const UploadScriptState & upload_state() const
Return the callback state retained for the complete drive.
bool resolve_entries_ready() const
Return whether resolver-only slist construction installed its mandatory final loopback mapping.
void SetBeforeUploadReadCallback(UploadScriptState::BeforeReadCallback callback, void *userdata)
Arrange protocol-specific work immediately before every upload read.
bool upload_callbacks_installed() const
Return whether this scenario needed raw upload callbacks.
ScenarioRequestData(CURL *easy, const curl::fuzzer::proto::Scenario &scenario, bool apply_resolve_entries=false)
Construct and apply the protocol-specific pointer-valued fields in scenario to easy.
Cursor and policy behind the request read/seek callbacks.
std::size_t Read(char *buffer, std::size_t capacity)
Copy the next bounded chunk and advance the cursor, using either the explicit payload or the allocati...
int Seek(curl_off_t requested_offset, int origin)
Apply the configured seek outcome and, for OK, move the bounded cursor.
CURLcode InstallAsMimePart(curl_mimepart *part)
Install the same bounded read/seek cursor behind a public MIME callback part.
std::size_t read_step_count() const
UploadScriptState(const curl::fuzzer::proto::Scenario &scenario)
Borrow and cap a scripted upload source before libcurl receives a userdata pointer.
void(*)(void *) BeforeReadCallback
C-compatible hook invoked immediately before each read result is chosen.
std::unique_ptr< curl_slist, CurlSlistDeleter > CurlSlistPtr
Unique ownership of a caller-owned curl_slist.
Definition curl_raii.h:60
std::unique_ptr< curl_mime, CurlMimeDeleter > CurlMimePtr
Unique ownership of a caller-owned curl_mime tree.
Definition curl_raii.h:62
std::size_t RuntimeOptionCount(const curl::fuzzer::proto::Scenario &scenario)
Return the runtime-visible option prefix length.
Translates Scenario SetOption messages into curl_easy_setopt calls, plus the fixed baseline setopts t...
Owns pointer-valued HTTP headers, TELNET options, MIME state, and upload callback state derived from ...
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxUploadReadSteps
Short-read scripts need several transitions, not mutation-sized sequences.
constexpr std::size_t kMaxMimeDataBytes
MIME payloads remain large enough to cross encoder buffer boundaries.
constexpr std::size_t kMaxTelnetUploadReadSteps
OSS-Fuzz's local AF_UNIX peer normally fills curl's 4 KiB TELNET reads, so the bounded 8 KiB response...
constexpr std::size_t kMaxNestedMimeParts
One bounded child layer is sufficient to enter recursive MIME code.
constexpr std::size_t kMaxTotalMimeParts
Share a total budget so many parents cannot multiply their child caps.
constexpr std::size_t kMaxUploadBytes
Maximum callback-backed body bytes visible in one scenario.
constexpr std::size_t kMaxMimeHeadersPerPart
Per-part headers are bounded separately from the part count.
constexpr std::size_t kMaxResolveEntries
Host-cache mutations need only a compact sequence of add/remove/wildcard entries.
constexpr std::size_t kMaxResolveEntryBytes
CURLOPT_RESOLVE consumes NUL-terminated host:port:address records.
constexpr std::size_t kMaxGeneratedMimeDataBytes
Retain both sides of curl's 64 KiB send-buffer boundary.
constexpr std::size_t kMaxTelnetUploadBytes
TELNET writes upload bytes while its blocking driver owns the thread, so keep total callback work sma...
constexpr std::size_t kMaxTopLevelMimeParts
Bound top-level MIME traversal before curl recursively serializes it.
constexpr std::size_t kMaxMetadataBytes
Metadata reaches NUL-terminated APIs, where giant suffixes add no coverage.
constexpr std::size_t kMaxGeneratedMimePatternBytes
Longer repeated patterns are better expressed as ordinary MIME data and make the compact source less ...
Counts the resources that survived allocation and runtime budgets.