|
@@ -6950,7 +6950,8 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
|
|
template <typename T, typename U>
|
|
template <typename T, typename U>
|
|
|
bool prepare_content_receiver(T &x, int &status,
|
|
bool prepare_content_receiver(T &x, int &status,
|
|
|
ContentReceiverWithProgress receiver,
|
|
ContentReceiverWithProgress receiver,
|
|
|
- bool decompress, U callback) {
|
|
|
|
|
|
|
+ bool decompress, size_t payload_max_length,
|
|
|
|
|
+ bool &exceed_payload_max_length, U callback) {
|
|
|
if (decompress) {
|
|
if (decompress) {
|
|
|
std::string encoding = x.get_header_value("Content-Encoding");
|
|
std::string encoding = x.get_header_value("Content-Encoding");
|
|
|
std::unique_ptr<decompressor> decompressor;
|
|
std::unique_ptr<decompressor> decompressor;
|
|
@@ -6966,12 +6967,22 @@ bool prepare_content_receiver(T &x, int &status,
|
|
|
|
|
|
|
|
if (decompressor) {
|
|
if (decompressor) {
|
|
|
if (decompressor->is_valid()) {
|
|
if (decompressor->is_valid()) {
|
|
|
|
|
+ size_t decompressed_size = 0;
|
|
|
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
|
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
|
|
size_t off, size_t len) {
|
|
size_t off, size_t len) {
|
|
|
- return decompressor->decompress(buf, n,
|
|
|
|
|
- [&](const char *buf2, size_t n2) {
|
|
|
|
|
- return receiver(buf2, n2, off, len);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ return decompressor->decompress(
|
|
|
|
|
+ buf, n, [&](const char *buf2, size_t n2) {
|
|
|
|
|
+ // Guard against zip-bomb: check
|
|
|
|
|
+ // decompressed size against limit.
|
|
|
|
|
+ if (payload_max_length > 0 &&
|
|
|
|
|
+ (decompressed_size >= payload_max_length ||
|
|
|
|
|
+ n2 > payload_max_length - decompressed_size)) {
|
|
|
|
|
+ exceed_payload_max_length = true;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ decompressed_size += n2;
|
|
|
|
|
+ return receiver(buf2, n2, off, len);
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
return callback(std::move(out));
|
|
return callback(std::move(out));
|
|
|
} else {
|
|
} else {
|
|
@@ -6992,11 +7003,14 @@ template <typename T>
|
|
|
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
|
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
|
|
DownloadProgress progress,
|
|
DownloadProgress progress,
|
|
|
ContentReceiverWithProgress receiver, bool decompress) {
|
|
ContentReceiverWithProgress receiver, bool decompress) {
|
|
|
|
|
+ bool exceed_payload_max_length = false;
|
|
|
return prepare_content_receiver(
|
|
return prepare_content_receiver(
|
|
|
- x, status, std::move(receiver), decompress,
|
|
|
|
|
- [&](const ContentReceiverWithProgress &out) {
|
|
|
|
|
|
|
+ x, status, std::move(receiver), decompress, payload_max_length,
|
|
|
|
|
+ exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
|
|
|
auto ret = true;
|
|
auto ret = true;
|
|
|
- auto exceed_payload_max_length = false;
|
|
|
|
|
|
|
+ // Note: exceed_payload_max_length may also be set by the decompressor
|
|
|
|
|
+ // wrapper in prepare_content_receiver when the decompressed payload
|
|
|
|
|
+ // size exceeds the limit.
|
|
|
|
|
|
|
|
if (is_chunked_transfer_encoding(x.headers)) {
|
|
if (is_chunked_transfer_encoding(x.headers)) {
|
|
|
auto result = read_content_chunked(strm, x, payload_max_length, out);
|
|
auto result = read_content_chunked(strm, x, payload_max_length, out);
|
|
@@ -11288,45 +11302,63 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
|
|
if (detail::expect_content(req)) {
|
|
if (detail::expect_content(req)) {
|
|
|
// Content reader handler
|
|
// Content reader handler
|
|
|
{
|
|
{
|
|
|
|
|
+ // Track whether the ContentReader was aborted due to the decompressed
|
|
|
|
|
+ // payload exceeding `payload_max_length_`.
|
|
|
|
|
+ // The user handler runs after the lambda returns, so we must restore the
|
|
|
|
|
+ // 413 status if the handler overwrites it.
|
|
|
|
|
+ bool content_reader_payload_too_large = false;
|
|
|
|
|
+
|
|
|
ContentReader reader(
|
|
ContentReader reader(
|
|
|
[&](ContentReceiver receiver) {
|
|
[&](ContentReceiver receiver) {
|
|
|
auto result = read_content_with_content_receiver(
|
|
auto result = read_content_with_content_receiver(
|
|
|
strm, req, res, std::move(receiver), nullptr, nullptr);
|
|
strm, req, res, std::move(receiver), nullptr, nullptr);
|
|
|
- if (!result) { output_error_log(Error::Read, &req); }
|
|
|
|
|
|
|
+ if (!result) {
|
|
|
|
|
+ output_error_log(Error::Read, &req);
|
|
|
|
|
+ if (res.status == StatusCode::PayloadTooLarge_413) {
|
|
|
|
|
+ content_reader_payload_too_large = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
return result;
|
|
return result;
|
|
|
},
|
|
},
|
|
|
[&](FormDataHeader header, ContentReceiver receiver) {
|
|
[&](FormDataHeader header, ContentReceiver receiver) {
|
|
|
auto result = read_content_with_content_receiver(
|
|
auto result = read_content_with_content_receiver(
|
|
|
strm, req, res, nullptr, std::move(header),
|
|
strm, req, res, nullptr, std::move(header),
|
|
|
std::move(receiver));
|
|
std::move(receiver));
|
|
|
- if (!result) { output_error_log(Error::Read, &req); }
|
|
|
|
|
|
|
+ if (!result) {
|
|
|
|
|
+ output_error_log(Error::Read, &req);
|
|
|
|
|
+ if (res.status == StatusCode::PayloadTooLarge_413) {
|
|
|
|
|
+ content_reader_payload_too_large = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
return result;
|
|
return result;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ bool dispatched = false;
|
|
|
if (req.method == "POST") {
|
|
if (req.method == "POST") {
|
|
|
- if (dispatch_request_for_content_reader(
|
|
|
|
|
- req, res, std::move(reader),
|
|
|
|
|
- post_handlers_for_content_reader_)) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ dispatched = dispatch_request_for_content_reader(
|
|
|
|
|
+ req, res, std::move(reader), post_handlers_for_content_reader_);
|
|
|
} else if (req.method == "PUT") {
|
|
} else if (req.method == "PUT") {
|
|
|
- if (dispatch_request_for_content_reader(
|
|
|
|
|
- req, res, std::move(reader),
|
|
|
|
|
- put_handlers_for_content_reader_)) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ dispatched = dispatch_request_for_content_reader(
|
|
|
|
|
+ req, res, std::move(reader), put_handlers_for_content_reader_);
|
|
|
} else if (req.method == "PATCH") {
|
|
} else if (req.method == "PATCH") {
|
|
|
- if (dispatch_request_for_content_reader(
|
|
|
|
|
- req, res, std::move(reader),
|
|
|
|
|
- patch_handlers_for_content_reader_)) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ dispatched = dispatch_request_for_content_reader(
|
|
|
|
|
+ req, res, std::move(reader), patch_handlers_for_content_reader_);
|
|
|
} else if (req.method == "DELETE") {
|
|
} else if (req.method == "DELETE") {
|
|
|
- if (dispatch_request_for_content_reader(
|
|
|
|
|
- req, res, std::move(reader),
|
|
|
|
|
- delete_handlers_for_content_reader_)) {
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ dispatched = dispatch_request_for_content_reader(
|
|
|
|
|
+ req, res, std::move(reader), delete_handlers_for_content_reader_);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (dispatched) {
|
|
|
|
|
+ if (content_reader_payload_too_large) {
|
|
|
|
|
+ // Enforce the limit: override any status the handler may have set
|
|
|
|
|
+ // and return false so the error path sends a plain 413 response.
|
|
|
|
|
+ res.status = StatusCode::PayloadTooLarge_413;
|
|
|
|
|
+ res.body.clear();
|
|
|
|
|
+ res.content_length_ = 0;
|
|
|
|
|
+ res.content_provider_ = nullptr;
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|