@@ -29,11 +29,20 @@ enum {
2929 PARSE_REQ_FAIL = 4
3030};
3131
32+ enum {
33+ CHUNK_NONE = 0 , // Body transfer encoding is not chunked
34+ CHUNK_LENGTH , // Getting chunk length - HHHH[;...] CR LF
35+ CHUNK_EXTENSION , // Getting chunk length - HHHH[;...] CR LF
36+ CHUNK_DATA , // Handling chunk data
37+ CHUNK_END , // Getting chunk end marker - CR LF
38+ };
39+
3240AsyncWebServerRequest::AsyncWebServerRequest (AsyncWebServer *s, AsyncClient *c)
3341 : _client(c), _server(s), _handler(NULL ), _response(NULL ), _onDisconnectfn(NULL ), _temp(), _parseState(PARSE_REQ_START ), _version(0 ), _method(HTTP_ANY ),
3442 _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP ), _authMethod(AsyncAuthType::AUTH_NONE ), _isMultipart(false ),
3543 _isPlainPost(false ), _expectingContinue(false ), _contentLength(0 ), _parsedLength(0 ), _multiParseState(0 ), _boundaryPosition(0 ), _itemStartIndex(0 ),
36- _itemSize(0 ), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0 ), _itemBufferIndex(0 ), _itemIsFile(false ), _tempObject(NULL ) {
44+ _itemSize(0 ), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0 ), _itemBufferIndex(0 ), _itemIsFile(false ),
45+ _chunkedParseState(CHUNK_NONE ), _tempObject(NULL ) {
3746 c->onError (
3847 [](void *r, AsyncClient *c, int8_t error) {
3948 (void )c;
@@ -164,6 +173,14 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
164173 }
165174 }
166175 } else if (_parseState == PARSE_REQ_BODY ) {
176+ if (_chunkedParseState != CHUNK_NONE ) {
177+ if (_parseChunkedBytes ((uint8_t *)buf, len)) {
178+ _parseState = PARSE_REQ_END ;
179+ _runMiddlewareChain ();
180+ _send ();
181+ }
182+ break ;
183+ }
167184 // A handler should be already attached at this point in _parseLine function.
168185 // If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
169186 const bool needParse = _handler && !_handler->isRequestHandlerTrivial ();
@@ -334,6 +351,78 @@ bool AsyncWebServerRequest::_parseReqHead() {
334351 return true ;
335352}
336353
354+ // Returns true when done
355+ bool AsyncWebServerRequest::_parseChunkedBytes (uint8_t *buf, size_t len) {
356+ for (size_t i = 0 ; i < len;) {
357+ if (_chunkedParseState == CHUNK_DATA ) {
358+ // In DATA state, we pass the bytes off to handleBody as a group
359+
360+ // In order to avoid allocating an extra buffer, the data
361+ // blocks that we pass on do not necessarily correspond to
362+ // whole chunks. We just send however much we already have,
363+ // anticipating that more will arrive later. handleBody()
364+ // cannot assume that it receives entire chunks at once.
365+ // That should not be a problem because we do not attach
366+ // any semantic meaning to chunks. That might change if
367+ // we were to support chunk extensions, but that seems
368+ // unlikely since RFC9112 suggests that they are only
369+ // useful for very specialized purposes.
370+ size_t curLen = std::min (_chunkSize - _chunkOffset, len - i);
371+
372+ // On the final zero-length chunk, _chunkSize - _chunkOffset
373+ // will be zero, so we will call handleBody with a zero size,
374+ // marking the end of the data stream.
375+
376+ if (_handler) {
377+ _handler->handleBody (this , buf + i, curLen, _chunkStartIndex, _contentLength);
378+ }
379+ _chunkOffset += curLen;
380+ _chunkStartIndex += curLen;
381+ i += curLen;
382+ if (_chunkOffset == _chunkSize) {
383+ _chunkedParseState = CHUNK_END ;
384+ }
385+ } else {
386+ // In other states we process the bytes one by one
387+ uint8_t data = buf[i++];
388+
389+ if (_chunkedParseState == CHUNK_LENGTH ) {
390+ // Incrementally decode a hex number
391+ if (data >= ' 0' && data <= ' 9' ) {
392+ _chunkSize = (_chunkSize * 16 ) + (data - ' 0' );
393+ } else if (data >= ' A' && data <= ' F' ) {
394+ _chunkSize = (_chunkSize * 16 ) + (data - ' A' + 10 );
395+ } else if (data >= ' a' && data <= ' f' ) {
396+ _chunkSize = (_chunkSize * 16 ) + (data - ' a' + 10 );
397+ } else if (data == ' ;' ) {
398+ _chunkedParseState = CHUNK_EXTENSION ;
399+ } else if (data == ' \n ' ) {
400+ _chunkOffset = 0 ;
401+ _chunkedParseState = CHUNK_DATA ;
402+ }
403+ } else if (_chunkedParseState == CHUNK_EXTENSION ) {
404+ if (data == ' \n ' ) {
405+ // A zero length chunk marks the end of the chunk stream
406+ _chunkOffset = 0 ;
407+ _chunkedParseState = CHUNK_DATA ;
408+ }
409+ } else if (_chunkedParseState == CHUNK_END ) {
410+ if (data == ' \n ' ) {
411+ if (_chunkSize == 0 ) {
412+ // If we needed to support trailers, we would switch to
413+ // TRAILER state, but since we have no use case for them,
414+ // we just stop processing the body.
415+ return true ;
416+ }
417+ _chunkSize = 0 ;
418+ _chunkedParseState = CHUNK_LENGTH ;
419+ }
420+ }
421+ }
422+ }
423+ return false ;
424+ }
425+
337426bool AsyncWebServerRequest::_parseReqHeader () {
338427 AsyncWebHeader header = AsyncWebHeader::parse (_temp);
339428 if (header) {
@@ -348,7 +437,10 @@ bool AsyncWebServerRequest::_parseReqHeader() {
348437 _boundary.replace (String (' "' ), String ());
349438 _isMultipart = true ;
350439 }
351- } else if (name.equalsIgnoreCase (T_Content_Length)) {
440+ } else if (name.equalsIgnoreCase (T_Content_Length) || name.equalsIgnoreCase (T_X_Expected_Entity_Length)) {
441+ // MacOS WebDAVFS uses X-Expected-Entity-Length to indicate the
442+ // total length of a chunked request body. It is useful to
443+ // determine if a PUT can possibly fit in the available space.
352444 _contentLength = atoi (value.c_str ());
353445 } else if (name.equalsIgnoreCase (T_EXPECT ) && value.equalsIgnoreCase (T_100_CONTINUE )) {
354446 _expectingContinue = true ;
@@ -385,6 +477,17 @@ bool AsyncWebServerRequest::_parseReqHeader() {
385477 // WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
386478 _reqconntype = RCT_EVENT ;
387479 }
480+ } else if (name.equalsIgnoreCase (T_Transfer_Encoding)) {
481+ String lowcase (value);
482+ lowcase.toLowerCase ();
483+
484+ if (lowcase.indexOf (" chunked" ) != -1 ) {
485+ _chunkSize = 0 ;
486+ _chunkStartIndex = 0 ;
487+ _chunkedParseState = CHUNK_LENGTH ;
488+ _itemIsFile = true ;
489+ _itemFilename = _url;
490+ }
388491 }
389492 _headers.emplace_back (std::move (header));
390493 }
@@ -680,7 +783,7 @@ void AsyncWebServerRequest::_parseLine() {
680783 String response (T_HTTP_100_CONT );
681784 _client->write (response.c_str (), response.length ());
682785 }
683- if (_contentLength) {
786+ if (_contentLength || _chunkedParseState != CHUNK_NONE ) {
684787 _parseState = PARSE_REQ_BODY ;
685788 } else {
686789 _parseState = PARSE_REQ_END ;
0 commit comments