From 56470cb02817ba73f810e42c7984deaca5924046 Mon Sep 17 00:00:00 2001 From: Arik Yavilevich Date: Fri, 23 May 2025 12:17:43 +0300 Subject: [PATCH 1/6] fix https://github.com/ESP32Async/ESPAsyncWebServer/issues/183 --- src/AsyncJson.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index b8d014b2..d612afd1 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -125,12 +125,12 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) if (json.success()) { #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); if (!error) { JsonVariant json = jsonBuffer.as(); #else JsonDocument jsonBuffer; - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); if (!error) { JsonVariant json = jsonBuffer.as(); #endif From 2070e49c0b68da41099131fadc98a1f181d9c26a Mon Sep 17 00:00:00 2001 From: Arik Yavilevich Date: Sat, 24 May 2025 20:01:35 +0300 Subject: [PATCH 2/6] make edge case handing in AsyncCallbackJsonWebHandler more robust --- src/AsyncJson.cpp | 50 ++++++++++++++++++++++++++++++++--------- src/AsyncJson.h | 1 - src/ESPAsyncWebServer.h | 1 + src/WebRequest.cpp | 2 +- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index d612afd1..3bdc3f8b 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -117,20 +117,35 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) JsonVariant json; _onRequest(request, json); return; - } else if (request->_tempObject != NULL) { + } + // this is not a GET + // check if body is too large, if it is, don't parse + if (request->contentLength() > _maxContentLength) + { + request->send(413); + return; + } + // try to parse body as JSON + if (request->_tempObject != NULL) + { + size_t dataSize = min(request->contentLength(), request->_tempSize); // smaller value of contentLength or the size of the buffer. normally those should match. #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer jsonBuffer; - JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject)); + uint8_t * p = (uint8_t *)(request->_tempObject); + p[dataSize] = '\0'; // null terminate, assume we allocated one extra char + // parse can only get null terminated strings as parameters + JsonVariant json = jsonBuffer.parse(p); if (json.success()) { #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), dataSize); if (!error) { JsonVariant json = jsonBuffer.as(); #else JsonDocument jsonBuffer; - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); + // deserializeJson expects a null terminated string or a pointer plus length + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), dataSize); if (!error) { JsonVariant json = jsonBuffer.as(); #endif @@ -139,27 +154,40 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) return; } } - request->send(_contentLength > _maxContentLength ? 413 : 400); - } else { + // there is no body, no buffer or we had an error parsing the body + request->send(400); + } else { // if no _onRequest request->send(500); } } void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { if (_onRequest) { - _contentLength = total; - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { - request->_tempObject = malloc(total); - if (request->_tempObject == NULL) { + if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { // if request content length is valid size and we have no content buffer yet + request->_tempObject = malloc(total + 1); // allocate one additional byte so we can null terminate this buffer (needed for ArduinoJson 5) + if (request->_tempObject == NULL) { // if allocation failed #ifdef ESP32 log_e("Failed to allocate"); #endif request->abort(); return; } + request->_tempSize = total; // store the size of allocation we made into _tempSize } if (request->_tempObject != NULL) { - memcpy((uint8_t *)(request->_tempObject) + index, data, len); + // check if the buffer is the right size so we don't write out of bounds + if (request->_tempSize >= total) + { + memcpy((uint8_t *)(request->_tempObject) + index, data, len); + } + else + { +#ifdef ESP32 + log_e("Bad size of temp buffer"); +#endif + request->abort(); + return; + } } } } diff --git a/src/AsyncJson.h b/src/AsyncJson.h index b5777d63..2194069d 100644 --- a/src/AsyncJson.h +++ b/src/AsyncJson.h @@ -79,7 +79,6 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler { String _uri; WebRequestMethodComposite _method; ArJsonRequestHandlerFunction _onRequest; - size_t _contentLength; #if ARDUINOJSON_VERSION_MAJOR == 6 size_t maxJsonBufferSize; #endif diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 24233cd3..ca4217fe 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -261,6 +261,7 @@ class AsyncWebServerRequest { public: File _tempFile; void *_tempObject; + size_t _tempSize; AsyncWebServerRequest(AsyncWebServer *, AsyncClient *); ~AsyncWebServerRequest(); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 8b735af3..730bb30a 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -25,7 +25,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c) : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), - _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) { + _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL), _tempSize(0) { c->onError( [](void *r, AsyncClient *c, int8_t error) { (void)c; From 172a88da800e3202fc9d5fbff457f3da3878ec6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 19:15:02 +0000 Subject: [PATCH 3/6] ci(pre-commit): Apply automatic fixes --- src/AsyncJson.cpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index 3bdc3f8b..eebb2486 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -120,20 +120,19 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) } // this is not a GET // check if body is too large, if it is, don't parse - if (request->contentLength() > _maxContentLength) - { + if (request->contentLength() > _maxContentLength) { request->send(413); return; } // try to parse body as JSON - if (request->_tempObject != NULL) - { - size_t dataSize = min(request->contentLength(), request->_tempSize); // smaller value of contentLength or the size of the buffer. normally those should match. + if (request->_tempObject != NULL) { + size_t dataSize = + min(request->contentLength(), request->_tempSize); // smaller value of contentLength or the size of the buffer. normally those should match. #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer jsonBuffer; - uint8_t * p = (uint8_t *)(request->_tempObject); - p[dataSize] = '\0'; // null terminate, assume we allocated one extra char + uint8_t *p = (uint8_t *)(request->_tempObject); + p[dataSize] = '\0'; // null terminate, assume we allocated one extra char // parse can only get null terminated strings as parameters JsonVariant json = jsonBuffer.parse(p); if (json.success()) { @@ -156,32 +155,29 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) } // there is no body, no buffer or we had an error parsing the body request->send(400); - } else { // if no _onRequest + } else { // if no _onRequest request->send(500); } } void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { if (_onRequest) { - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { // if request content length is valid size and we have no content buffer yet - request->_tempObject = malloc(total + 1); // allocate one additional byte so we can null terminate this buffer (needed for ArduinoJson 5) - if (request->_tempObject == NULL) { // if allocation failed + if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { // if request content length is valid size and we have no content buffer yet + request->_tempObject = malloc(total + 1); // allocate one additional byte so we can null terminate this buffer (needed for ArduinoJson 5) + if (request->_tempObject == NULL) { // if allocation failed #ifdef ESP32 log_e("Failed to allocate"); #endif request->abort(); return; } - request->_tempSize = total; // store the size of allocation we made into _tempSize + request->_tempSize = total; // store the size of allocation we made into _tempSize } if (request->_tempObject != NULL) { // check if the buffer is the right size so we don't write out of bounds - if (request->_tempSize >= total) - { + if (request->_tempSize >= total) { memcpy((uint8_t *)(request->_tempObject) + index, data, len); - } - else - { + } else { #ifdef ESP32 log_e("Bad size of temp buffer"); #endif From 1db83fdd80cdec4daf27436310be1fda74d89817 Mon Sep 17 00:00:00 2001 From: Arik Yavilevich Date: Wed, 28 May 2025 17:41:30 +0300 Subject: [PATCH 4/6] refactor fix to be more robust and encapsulated --- src/AsyncJson.cpp | 59 ++++++++++++++++++++++++++++------------- src/ESPAsyncWebServer.h | 1 - src/WebRequest.cpp | 2 +- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index 3bdc3f8b..43266a08 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -5,6 +5,11 @@ #if ASYNC_JSON_SUPPORT == 1 +typedef struct { + size_t length; // the size we can write into "content", not including null termination + uint8_t content[1]; // this will be of size "content-length" + 1 byte to guarantee that the content is null terminated. null termination is needed for ArduinoJson 5 +} AsyncJsonResponseBuffer; + #if ARDUINOJSON_VERSION_MAJOR == 5 AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { _code = 200; @@ -119,7 +124,7 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) return; } // this is not a GET - // check if body is too large, if it is, don't parse + // check if json body is too large, if it is, don't deserialize if (request->contentLength() > _maxContentLength) { request->send(413); @@ -127,25 +132,24 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) } // try to parse body as JSON - if (request->_tempObject != NULL) + if (request->_tempObject != NULL) // see if we succeeded allocating a buffer earlier { - size_t dataSize = min(request->contentLength(), request->_tempSize); // smaller value of contentLength or the size of the buffer. normally those should match. + AsyncJsonResponseBuffer * buffer = (AsyncJsonResponseBuffer*)request->_tempObject; #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer jsonBuffer; - uint8_t * p = (uint8_t *)(request->_tempObject); - p[dataSize] = '\0'; // null terminate, assume we allocated one extra char + buffer->content[buffer->length] = '\0'; // null terminate, assume we allocated one extra char // parse can only get null terminated strings as parameters - JsonVariant json = jsonBuffer.parse(p); + JsonVariant json = jsonBuffer.parse(buffer->content); if (json.success()) { #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), dataSize); + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); // content with length > this->maxJsonBufferSize might not get deserialized + DeserializationError error = deserializeJson(jsonBuffer, buffer->content, buffer->length); if (!error) { JsonVariant json = jsonBuffer.as(); #else JsonDocument jsonBuffer; // deserializeJson expects a null terminated string or a pointer plus length - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), dataSize); + DeserializationError error = deserializeJson(jsonBuffer, buffer->content, buffer->length); if (!error) { JsonVariant json = jsonBuffer.as(); #endif @@ -153,6 +157,9 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) _onRequest(request, json); return; } + // free buffer, we are done with it, so release memory ASAP + free(request->_tempObject); + request->_tempObject = NULL; } // there is no body, no buffer or we had an error parsing the body request->send(400); @@ -163,30 +170,46 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { if (_onRequest) { - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { // if request content length is valid size and we have no content buffer yet - request->_tempObject = malloc(total + 1); // allocate one additional byte so we can null terminate this buffer (needed for ArduinoJson 5) + if (index == 0) // on first piece + { + // check nobody has already allocated the buffer + if (request->_tempObject != NULL) + { +#ifdef ESP32 + log_e("Temp object already in use"); +#endif + return; // do nothing else here, handleRequest will return a HTTP error + } + // check total size is valid + if (total >= _maxContentLength) + { + return; // do nothing else here, handleRequest will return a HTTP error + } + // allocate buffer + request->_tempObject = calloc(1, sizeof(AsyncJsonResponseBuffer) + total); // normally _tempObject will be "free"ed by the destructor of the request, but can release earlier if desired. if (request->_tempObject == NULL) { // if allocation failed #ifdef ESP32 log_e("Failed to allocate"); #endif - request->abort(); - return; + return; // do nothing else here, handleRequest will return a HTTP error } - request->_tempSize = total; // store the size of allocation we made into _tempSize + ((AsyncJsonResponseBuffer*)request->_tempObject)->length = total; // store the size of allocation we made into _tempObject } + + // add data to the buffer if the buffer exists if (request->_tempObject != NULL) { + AsyncJsonResponseBuffer * buffer = (AsyncJsonResponseBuffer*)request->_tempObject; // check if the buffer is the right size so we don't write out of bounds - if (request->_tempSize >= total) + if (buffer->length >= total && buffer->length >= index + len) { - memcpy((uint8_t *)(request->_tempObject) + index, data, len); + memcpy(buffer->content + index, data, len); } else { #ifdef ESP32 log_e("Bad size of temp buffer"); #endif - request->abort(); - return; + return; // do nothing else here } } } diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index ca4217fe..24233cd3 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -261,7 +261,6 @@ class AsyncWebServerRequest { public: File _tempFile; void *_tempObject; - size_t _tempSize; AsyncWebServerRequest(AsyncWebServer *, AsyncClient *); ~AsyncWebServerRequest(); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 730bb30a..8b735af3 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -25,7 +25,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c) : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), - _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL), _tempSize(0) { + _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) { c->onError( [](void *r, AsyncClient *c, int8_t error) { (void)c; From 50d51125dfe787bdf262059505feaea666a31c32 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 22:06:40 +0000 Subject: [PATCH 5/6] ci(pre-commit): Apply automatic fixes --- src/AsyncJson.cpp | 49 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index 7c0d53f6..f3d73ddd 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -6,8 +6,9 @@ #if ASYNC_JSON_SUPPORT == 1 typedef struct { - size_t length; // the size we can write into "content", not including null termination - uint8_t content[1]; // this will be of size "content-length" + 1 byte to guarantee that the content is null terminated. null termination is needed for ArduinoJson 5 + size_t length; // the size we can write into "content", not including null termination + uint8_t content + [1]; // this will be of size "content-length" + 1 byte to guarantee that the content is null terminated. null termination is needed for ArduinoJson 5 } AsyncJsonResponseBuffer; #if ARDUINOJSON_VERSION_MAJOR == 5 @@ -125,24 +126,23 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) } // this is not a GET // check if json body is too large, if it is, don't deserialize - if (request->contentLength() > _maxContentLength) - { + if (request->contentLength() > _maxContentLength) { request->send(413); return; } // try to parse body as JSON - if (request->_tempObject != NULL) // see if we succeeded allocating a buffer earlier + if (request->_tempObject != NULL) // see if we succeeded allocating a buffer earlier { - AsyncJsonResponseBuffer * buffer = (AsyncJsonResponseBuffer*)request->_tempObject; + AsyncJsonResponseBuffer *buffer = (AsyncJsonResponseBuffer *)request->_tempObject; #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer jsonBuffer; - buffer->content[buffer->length] = '\0'; // null terminate, assume we allocated one extra char + buffer->content[buffer->length] = '\0'; // null terminate, assume we allocated one extra char // parse can only get null terminated strings as parameters JsonVariant json = jsonBuffer.parse(buffer->content); if (json.success()) { #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); // content with length > this->maxJsonBufferSize might not get deserialized + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); // content with length > this->maxJsonBufferSize might not get deserialized DeserializationError error = deserializeJson(jsonBuffer, buffer->content, buffer->length); if (!error) { JsonVariant json = jsonBuffer.as(); @@ -170,46 +170,43 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { if (_onRequest) { - if (index == 0) // on first piece + if (index == 0) // on first piece { // check nobody has already allocated the buffer - if (request->_tempObject != NULL) - { + if (request->_tempObject != NULL) { #ifdef ESP32 log_e("Temp object already in use"); #endif - return; // do nothing else here, handleRequest will return a HTTP error + return; // do nothing else here, handleRequest will return a HTTP error } // check total size is valid - if (total >= _maxContentLength) - { - return; // do nothing else here, handleRequest will return a HTTP error + if (total >= _maxContentLength) { + return; // do nothing else here, handleRequest will return a HTTP error } // allocate buffer - request->_tempObject = calloc(1, sizeof(AsyncJsonResponseBuffer) + total); // normally _tempObject will be "free"ed by the destructor of the request, but can release earlier if desired. - if (request->_tempObject == NULL) { // if allocation failed + request->_tempObject = calloc( + 1, sizeof(AsyncJsonResponseBuffer) + total + ); // normally _tempObject will be "free"ed by the destructor of the request, but can release earlier if desired. + if (request->_tempObject == NULL) { // if allocation failed #ifdef ESP32 log_e("Failed to allocate"); #endif - return; // do nothing else here, handleRequest will return a HTTP error + return; // do nothing else here, handleRequest will return a HTTP error } - ((AsyncJsonResponseBuffer*)request->_tempObject)->length = total; // store the size of allocation we made into _tempObject + ((AsyncJsonResponseBuffer *)request->_tempObject)->length = total; // store the size of allocation we made into _tempObject } // add data to the buffer if the buffer exists if (request->_tempObject != NULL) { - AsyncJsonResponseBuffer * buffer = (AsyncJsonResponseBuffer*)request->_tempObject; + AsyncJsonResponseBuffer *buffer = (AsyncJsonResponseBuffer *)request->_tempObject; // check if the buffer is the right size so we don't write out of bounds - if (buffer->length >= total && buffer->length >= index + len) - { + if (buffer->length >= total && buffer->length >= index + len) { memcpy(buffer->content + index, data, len); - } - else - { + } else { #ifdef ESP32 log_e("Bad size of temp buffer"); #endif - return; // do nothing else here + return; // do nothing else here } } } From 426aea173923afab5e436a64f3979ff3672746d1 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Sun, 1 Jun 2025 22:05:21 +0200 Subject: [PATCH 6/6] Cleanup proposal fix for #183 --- examples/Json/Json.ino | 13 +++++ src/AsyncJson.cpp | 112 ++++++++++++++++------------------------- 2 files changed, 57 insertions(+), 68 deletions(-) diff --git a/examples/Json/Json.ino b/examples/Json/Json.ino index 0ea88928..9290a82e 100644 --- a/examples/Json/Json.ino +++ b/examples/Json/Json.ino @@ -63,14 +63,27 @@ void setup() { JsonObject root = doc.to(); root["foo"] = "bar"; serializeJson(root, *response); + Serial.println(); request->send(response); }); // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2 // curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2 + // + // edge cases: + // + // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 5" http://192.168.4.1/json2 => rx timeout + // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 2" http://192.168.4.1/json2 => 12 + // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 4" http://192.168.4.1/json2 => 1234 + // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 10" http://192.168.4.1/json2 => rx timeout + // curl -v -X POST -H "Content-Type: application/json" -d "12345678" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678 + // curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678 + // curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 9" http://192.168.4.1/json2 => 413: Content length exceeds maximum allowed + handler->setMaxContentLength(8); handler->setMethod(HTTP_POST | HTTP_PUT); handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { serializeJson(json, Serial); + Serial.println(); AsyncJsonResponse *response = new AsyncJsonResponse(); JsonObject root = response->getRoot().to(); root["hello"] = json.as()["name"]; diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index f3d73ddd..8381f75e 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -5,12 +5,6 @@ #if ASYNC_JSON_SUPPORT == 1 -typedef struct { - size_t length; // the size we can write into "content", not including null termination - uint8_t content - [1]; // this will be of size "content-length" + 1 byte to guarantee that the content is null terminated. null termination is needed for ArduinoJson 5 -} AsyncJsonResponseBuffer; - #if ARDUINOJSON_VERSION_MAJOR == 5 AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { _code = 200; @@ -119,95 +113,77 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) { if (_onRequest) { + // GET request: if (request->method() == HTTP_GET) { JsonVariant json; _onRequest(request, json); return; } - // this is not a GET - // check if json body is too large, if it is, don't deserialize + + // POST / PUT / ... requests: + // check if JSON body is too large, if it is, don't deserialize if (request->contentLength() > _maxContentLength) { +#ifdef ESP32 + log_e("Content length exceeds maximum allowed"); +#endif request->send(413); return; } - // try to parse body as JSON - if (request->_tempObject != NULL) // see if we succeeded allocating a buffer earlier - { - AsyncJsonResponseBuffer *buffer = (AsyncJsonResponseBuffer *)request->_tempObject; + if (request->_tempObject == NULL) { + // there is no body + request->send(400); + return; + } + #if ARDUINOJSON_VERSION_MAJOR == 5 - DynamicJsonBuffer jsonBuffer; - buffer->content[buffer->length] = '\0'; // null terminate, assume we allocated one extra char - // parse can only get null terminated strings as parameters - JsonVariant json = jsonBuffer.parse(buffer->content); - if (json.success()) { + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject); + if (json.success()) { #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); // content with length > this->maxJsonBufferSize might not get deserialized - DeserializationError error = deserializeJson(jsonBuffer, buffer->content, buffer->length); - if (!error) { - JsonVariant json = jsonBuffer.as(); + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject); + if (!error) { + JsonVariant json = jsonBuffer.as(); #else - JsonDocument jsonBuffer; - // deserializeJson expects a null terminated string or a pointer plus length - DeserializationError error = deserializeJson(jsonBuffer, buffer->content, buffer->length); - if (!error) { - JsonVariant json = jsonBuffer.as(); + JsonDocument jsonBuffer; + DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject); + if (!error) { + JsonVariant json = jsonBuffer.as(); #endif - _onRequest(request, json); - return; - } - // free buffer, we are done with it, so release memory ASAP - free(request->_tempObject); - request->_tempObject = NULL; + _onRequest(request, json); + } else { + // error parsing the body + request->send(400); } - // there is no body, no buffer or we had an error parsing the body - request->send(400); - } else { // if no _onRequest - request->send(500); } } void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { if (_onRequest) { - if (index == 0) // on first piece - { - // check nobody has already allocated the buffer - if (request->_tempObject != NULL) { -#ifdef ESP32 - log_e("Temp object already in use"); -#endif - return; // do nothing else here, handleRequest will return a HTTP error - } - // check total size is valid - if (total >= _maxContentLength) { - return; // do nothing else here, handleRequest will return a HTTP error - } - // allocate buffer - request->_tempObject = calloc( - 1, sizeof(AsyncJsonResponseBuffer) + total - ); // normally _tempObject will be "free"ed by the destructor of the request, but can release earlier if desired. - if (request->_tempObject == NULL) { // if allocation failed + // ignore callback if size is larger than maxContentLength + if (total > _maxContentLength) { + return; + } + + if (index == 0) { + // this check allows request->_tempObject to be initialized from a middleware + if (request->_tempObject == NULL) { + request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string + if (request->_tempObject == NULL) { #ifdef ESP32 - log_e("Failed to allocate"); + log_e("Failed to allocate"); #endif - return; // do nothing else here, handleRequest will return a HTTP error + request->abort(); + return; + } } - ((AsyncJsonResponseBuffer *)request->_tempObject)->length = total; // store the size of allocation we made into _tempObject } - // add data to the buffer if the buffer exists if (request->_tempObject != NULL) { - AsyncJsonResponseBuffer *buffer = (AsyncJsonResponseBuffer *)request->_tempObject; - // check if the buffer is the right size so we don't write out of bounds - if (buffer->length >= total && buffer->length >= index + len) { - memcpy(buffer->content + index, data, len); - } else { -#ifdef ESP32 - log_e("Bad size of temp buffer"); -#endif - return; // do nothing else here - } + uint8_t *buffer = (uint8_t *)request->_tempObject; + memcpy(buffer + index, data, len); } } }