diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 2f72cb06..ab9e7e44 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -132,6 +132,7 @@ OPTIONS = [ "no-kqueue", "frontend-http2-settings-timeout", "backend-http2-settings-timeout", + "api-max-request-body", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index fcc45638..044e272b 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1183,6 +1183,9 @@ void fill_default_config() { downstreamconf.response_buffer_size = 128_k; downstreamconf.family = AF_UNSPEC; } + + auto &apiconf = mod_config()->api; + apiconf.max_request_body = 16_k; } } // namespace @@ -1914,6 +1917,12 @@ HTTP: HTTP status code. If error status code comes from backend server, the custom error pages are not used. +API: + --api-max-request-body= + Set the maximum size of request body for API request. + Default: )" << util::utos_unit(get_config()->api.max_request_body) + << R"( + Debug: --frontend-http2-dump-request-header= Dumps request headers received by HTTP/2 frontend to the @@ -2447,6 +2456,7 @@ int main(int argc, char **argv) { &flag, 124}, {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, &flag, 125}, + {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3036,6 +3046,10 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT, StringRef{optarg}); break; + case 126: + // --api-max-request-body + cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_api_downstream_connection.cc b/src/shrpx_api_downstream_connection.cc index 2501d9da..fb4867a8 100644 --- a/src/shrpx_api_downstream_connection.cc +++ b/src/shrpx_api_downstream_connection.cc @@ -87,6 +87,15 @@ int APIDownstreamConnection::push_request_headers() { return 0; } + // This works with req.fs.content_length == -1 + if (req.fs.content_length > + static_cast(get_config()->api.max_request_body)) { + send_reply( + 413, http2::get_status_string(downstream_->get_block_allocator(), 413)); + + return 0; + } + return 0; } @@ -98,7 +107,15 @@ int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data, auto output = downstream_->get_request_buf(); - // TODO limit the maximum payload size + auto &apiconf = get_config()->api; + + if (output->rleft() + datalen > apiconf.max_request_body) { + send_reply( + 413, http2::get_status_string(downstream_->get_block_allocator(), 413)); + + return 0; + } + output->append(data, datalen); // We don't have to call Upstream::resume_read() here, because @@ -116,7 +133,7 @@ int APIDownstreamConnection::end_upload_data() { auto output = downstream_->get_request_buf(); struct iovec iov; - auto iovcnt = output->riovec(&iov, 1); + auto iovcnt = output->riovec(&iov, 2); constexpr auto body = StringRef::from_lit("200 OK"); constexpr auto error_body = StringRef::from_lit("400 Bad Request"); @@ -127,6 +144,19 @@ int APIDownstreamConnection::end_upload_data() { return 0; } + std::unique_ptr large_buf; + + // If data spans in multiple chunks, pull them up into one + // contiguous buffer. + if (iovcnt > 1) { + large_buf = make_unique(output->rleft()); + auto len = output->rleft(); + output->remove(large_buf.get(), len); + + iov.iov_base = large_buf.get(); + iov.iov_len = len; + } + Config config{}; config.conn.downstream = std::make_shared(); const auto &downstreamconf = config.conn.downstream; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 5586e934..92d63e21 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -915,6 +915,7 @@ enum { SHRPX_OPTID_ADD_RESPONSE_HEADER, SHRPX_OPTID_ADD_X_FORWARDED_FOR, SHRPX_OPTID_ALTSVC, + SHRPX_OPTID_API_MAX_REQUEST_BODY, SHRPX_OPTID_BACKEND, SHRPX_OPTID_BACKEND_ADDRESS_FAMILY, SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND, @@ -1429,6 +1430,11 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_VERIFY_CLIENT_CACERT; } break; + case 'y': + if (util::strieq_l("api-max-request-bod", name, 19)) { + return SHRPX_OPTID_API_MAX_REQUEST_BODY; + } + break; } break; case 21: @@ -2712,6 +2718,8 @@ int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, case SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT: return parse_duration(&config->http2.downstream.timeout.settings, opt, optarg); + case SHRPX_OPTID_API_MAX_REQUEST_BODY: + return parse_uint_with_unit(&config->api.max_request_body, opt, optarg); case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index c1d71c8c..40b626fd 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -280,6 +280,8 @@ constexpr auto SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT = StringRef::from_lit("frontend-http2-settings-timeout"); constexpr auto SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT = StringRef::from_lit("backend-http2-settings-timeout"); +constexpr auto SHRPX_OPT_API_MAX_REQUEST_BODY = + StringRef::from_lit("api-max-request-body"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -649,6 +651,11 @@ struct ConnectionConfig { std::shared_ptr downstream; }; +struct APIConfig { + // Maximum request body size for one API request + size_t max_request_body; +}; + struct Config { HttpProxy downstream_http_proxy; HttpConfig http; @@ -656,6 +663,7 @@ struct Config { TLSConfig tls; LoggingConfig logging; ConnectionConfig conn; + APIConfig api; ImmutableString pid_file; ImmutableString conf_path; ImmutableString user;