|
1 | 1 | /* Copyright (C) 2026 Sebastiano Merlino; SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | 2 | #include <algorithm> |
| 3 | +#include <limits> |
3 | 4 | #include <memory> |
4 | 5 | #include <string> |
5 | 6 | #include <utility> |
@@ -142,19 +143,30 @@ auto http1_response_outbox::reserve_buffer_locked(http1_response_sink& sink, std |
142 | 143 | sink.storage_units_ = sink.bytes_.capacity(); |
143 | 144 | return true; |
144 | 145 | } |
145 | | - const auto bound = std::max(size, 2 * sink.bytes_.capacity()); |
146 | | - if (!storage_charge_.grow(bound)) return false; |
| 146 | + const auto maximum = sink.bytes_.max_size(); |
| 147 | + if (size > maximum) return false; |
| 148 | + const auto capacity = sink.bytes_.capacity(); |
| 149 | + const auto bound = std::max(size, capacity > maximum / 2 ? maximum : 2 * capacity); |
| 150 | + if (bound > std::numeric_limits<std::size_t>::max() - storage_charge_.units() || |
| 151 | + !storage_charge_.grow(bound)) return false; |
| 152 | + // Destroy the private candidate before rolling back its admission. |
| 153 | + struct provisional_charge { |
| 154 | + resource_charge& charge; |
| 155 | + std::size_t units; |
| 156 | + ~provisional_charge() { charge.shrink(units); } |
| 157 | + } pending{storage_charge_, bound}; |
147 | 158 | std::string replacement; |
148 | 159 | replacement.reserve(bound); |
149 | | - if (replacement.capacity() > bound) { |
150 | | - std::string().swap(replacement); |
151 | | - storage_charge_.shrink(bound); |
152 | | - return false; |
153 | | - } |
| 160 | + const auto actual = replacement.capacity(); |
| 161 | + const auto excess = actual - bound; |
| 162 | + if (excess > std::numeric_limits<std::size_t>::max() - storage_charge_.units() || |
| 163 | + !storage_charge_.grow(excess)) return false; |
| 164 | + pending.units = actual; |
154 | 165 | replacement.append(sink.bytes_); |
155 | 166 | sink.bytes_.swap(replacement); |
156 | 167 | std::string().swap(replacement); |
157 | | - storage_charge_.shrink(std::exchange(sink.storage_units_, bound)); |
| 168 | + storage_charge_.shrink(std::exchange(sink.storage_units_, actual)); |
| 169 | + pending.units = 0; |
158 | 170 | return true; |
159 | 171 | } |
160 | 172 |
|
@@ -319,23 +331,33 @@ http::outcome http1_response_sink::start(const http::request_head& request, cons |
319 | 331 | // candidate allocation before returning any of its shared capacity. |
320 | 332 | std::string previous = std::move(bytes_); |
321 | 333 | const auto previous_units = std::exchange(storage_units_, 0); |
322 | | - const auto rollback = [this, &previous, previous_units](http::outcome failure) { |
| 334 | + bool restored = false; |
| 335 | + const auto restore = [this, &previous, previous_units, &restored] { |
| 336 | + if (std::exchange(restored, true)) return; |
323 | 337 | owner_->release_buffer_locked(*this); |
324 | 338 | bytes_ = std::move(previous); |
325 | 339 | storage_units_ = previous_units; |
| 340 | + }; |
| 341 | + const auto rollback = [this, &restore](http::outcome failure) { |
| 342 | + restore(); |
326 | 343 | return fail_locked(failure); |
327 | 344 | }; |
328 | | - std::size_t bound = 0; |
329 | | - if (!owner_->field_bound(f, 256, bound) || !owner_->reserve_buffer_locked(*this, before_head + bound)) { |
330 | | - return rollback({http::outcome_code::limit_exceeded, "shared response head capacity exhausted"}); |
331 | | - } |
332 | | - bytes_.append(previous); |
333 | | - const http::outcome head = framer_->start_head(bytes_, request, s, f); |
334 | | - if (!head.ok()) return rollback(head); |
335 | | - if (bytes_.size() > owner_->budget_.max_head_bytes) { |
336 | | - return rollback(http::outcome(http::outcome_code::limit_exceeded, |
337 | | - "http1_response_sink: serialized response head exceeds the" |
338 | | - " head budget")); |
| 345 | + try { |
| 346 | + std::size_t bound = 0; |
| 347 | + if (!owner_->field_bound(f, 256, bound) || !owner_->reserve_buffer_locked(*this, before_head + bound)) { |
| 348 | + return rollback({http::outcome_code::limit_exceeded, "shared response head capacity exhausted"}); |
| 349 | + } |
| 350 | + bytes_.append(previous); |
| 351 | + const http::outcome head = framer_->start_head(bytes_, request, s, f); |
| 352 | + if (!head.ok()) return rollback(head); |
| 353 | + if (bytes_.size() > owner_->budget_.max_head_bytes) { |
| 354 | + return rollback(http::outcome(http::outcome_code::limit_exceeded, |
| 355 | + "http1_response_sink: serialized response head exceeds the" |
| 356 | + " head budget")); |
| 357 | + } |
| 358 | + } catch (...) { |
| 359 | + restore(); |
| 360 | + throw; |
339 | 361 | } |
340 | 362 | std::string().swap(previous); |
341 | 363 | owner_->storage_charge_.shrink(previous_units); |
|
0 commit comments