Files
nuki_hub/lib/PsychicHttp/src/MultipartProcessor.cpp
2024-12-30 14:37:09 +01:00

426 lines
12 KiB
C++

#include "MultipartProcessor.h"
#include "PsychicRequest.h"
enum
{
EXPECT_BOUNDARY,
PARSE_HEADERS,
WAIT_FOR_RETURN1,
EXPECT_FEED1,
EXPECT_DASH1,
EXPECT_DASH2,
BOUNDARY_OR_DATA,
DASH3_OR_RETURN2,
EXPECT_FEED2,
PARSING_FINISHED,
PARSE_ERROR
};
MultipartProcessor::MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback) : _request(request),
_uploadCallback(uploadCallback),
_temp(),
_parsedLength(0),
_multiParseState(EXPECT_BOUNDARY),
_boundaryPosition(0),
_itemStartIndex(0),
_itemSize(0),
_itemName(),
_itemFilename(),
_itemType(),
_itemValue(),
_itemBuffer(0),
_itemBufferIndex(0),
_itemIsFile(false)
{
}
MultipartProcessor::~MultipartProcessor() {}
esp_err_t MultipartProcessor::process()
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
String value = _request->header("Content-Type");
if (value.startsWith("multipart/"))
{
_boundary = value.substring(value.indexOf('=') + 1);
_boundary.replace("\"", "");
}
else
{
ESP_LOGE(PH_TAG, "No multipart boundary found.");
return ESP_ERR_HTTPD_INVALID_REQ;
}
char* buf = (char*)malloc(FILE_CHUNK_SIZE);
int received;
unsigned long index = 0;
/* Content length of the request gives the size of the file being uploaded */
int remaining = _request->contentLength();
while (remaining > 0)
{
#ifdef ENABLE_ASYNC
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
#endif
/* Receive the file part by part into a buffer */
if ((received = httpd_req_recv(_request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
{
/* Retry if timeout occurred */
if (received == HTTPD_SOCK_ERR_TIMEOUT)
continue;
// bail if we got an error
else if (received == HTTPD_SOCK_ERR_FAIL)
{
ESP_LOGE(PH_TAG, "Socket error");
err = ESP_FAIL;
break;
}
}
// parse it 1 byte at a time.
for (int i = 0; i < received; i++)
{
/* Keep track of remaining size of the file left to be uploaded */
remaining--;
index++;
// send it to our parser
_parseMultipartPostByte(buf[i], !remaining);
_parsedLength++;
}
}
// dont forget to free our buffer
free(buf);
return err;
}
esp_err_t MultipartProcessor::process(const char* body)
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
String value = _request->header("Content-Type");
if (value.startsWith("multipart/"))
{
_boundary = value.substring(value.indexOf('=') + 1);
_boundary.replace("\"", "");
}
else
{
ESP_LOGE(PH_TAG, "No multipart boundary found.");
return ESP_ERR_HTTPD_INVALID_REQ;
}
// loop over the whole string
unsigned int size = strlen(body);
for (unsigned i = 0; i < size; i++)
{
// send it to our parser
_parseMultipartPostByte(body[i], i == size - 1);
_parsedLength++;
}
return err;
}
void MultipartProcessor::_handleUploadByte(uint8_t data, bool last)
{
_itemBuffer[_itemBufferIndex++] = data;
if (last || _itemBufferIndex == FILE_CHUNK_SIZE)
{
if (_uploadCallback)
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
_itemBufferIndex = 0;
}
}
#define itemWriteByte(b) \
do \
{ \
_itemSize++; \
if (_itemIsFile) \
_handleUploadByte(b, last); \
else \
_itemValue += (char)(b); \
} while (0)
void MultipartProcessor::_parseMultipartPostByte(uint8_t data, bool last)
{
if (_multiParseState == PARSE_ERROR)
{
// not sure we can end up with an error during buffer fill, but jsut to be safe
if (_itemBuffer != NULL)
{
free(_itemBuffer);
_itemBuffer = NULL;
}
return;
}
if (!_parsedLength)
{
_multiParseState = EXPECT_BOUNDARY;
_temp = String();
_itemName = String();
_itemFilename = String();
_itemType = String();
}
if (_multiParseState == WAIT_FOR_RETURN1)
{
if (data != '\r')
{
itemWriteByte(data);
}
else
{
_multiParseState = EXPECT_FEED1;
}
}
else if (_multiParseState == EXPECT_BOUNDARY)
{
if (_parsedLength < 2 && data != '-')
{
ESP_LOGE(PH_TAG, "Multipart: No boundary");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data)
{
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 2 == _boundary.length() && data != '\r')
{
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 3 == _boundary.length())
{
if (data != '\n')
{
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
_multiParseState = PARSE_ERROR;
return;
}
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
}
else if (_multiParseState == PARSE_HEADERS)
{
if ((char)data != '\r' && (char)data != '\n')
_temp += (char)data;
if ((char)data == '\n')
{
if (_temp.length())
{
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type"))
{
_itemType = _temp.substring(14);
_itemIsFile = true;
}
else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition"))
{
_temp = _temp.substring(_temp.indexOf(';') + 2);
while (_temp.indexOf(';') > 0)
{
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
if (name == "name")
{
_itemName = nameVal;
}
else if (name == "filename")
{
_itemFilename = nameVal;
_itemIsFile = true;
}
_temp = _temp.substring(_temp.indexOf(';') + 2);
}
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
if (name == "name")
{
_itemName = nameVal;
}
else if (name == "filename")
{
_itemFilename = nameVal;
_itemIsFile = true;
}
}
_temp = String();
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
// value starts from here
_itemSize = 0;
_itemStartIndex = _parsedLength;
_itemValue = String();
if (_itemIsFile)
{
if (_itemBuffer)
free(_itemBuffer);
_itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE);
if (_itemBuffer == NULL)
{
ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer");
_multiParseState = PARSE_ERROR;
return;
}
_itemBufferIndex = 0;
}
}
}
}
else if (_multiParseState == EXPECT_FEED1)
{
if (data != '\n')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = EXPECT_DASH1;
}
}
else if (_multiParseState == EXPECT_DASH1)
{
if (data != '-')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = EXPECT_DASH2;
}
}
else if (_multiParseState == EXPECT_DASH2)
{
if (data != '-')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = BOUNDARY_OR_DATA;
_boundaryPosition = 0;
}
}
else if (_multiParseState == BOUNDARY_OR_DATA)
{
if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data)
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundaryPosition; i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
else if (_boundaryPosition == _boundary.length() - 1)
{
_multiParseState = DASH3_OR_RETURN2;
if (!_itemIsFile)
{
// External - Add parameter!
_request->addParam(_itemName, _itemValue);
}
else
{
if (_itemSize)
{
if (_uploadCallback)
{
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0;
// External - Add parameter!
_request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize));
}
free(_itemBuffer);
_itemBuffer = NULL;
}
}
else
{
_boundaryPosition++;
}
}
else if (_multiParseState == DASH3_OR_RETURN2)
{
if (data == '-' && (_request->contentLength() - _parsedLength - 4) != 0)
{
ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!");
_multiParseState = PARSE_ERROR;
return;
}
if (data == '\r')
{
_multiParseState = EXPECT_FEED2;
}
else if (data == '-' && _request->contentLength() == (_parsedLength + 4))
{
_multiParseState = PARSING_FINISHED;
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
}
else if (_multiParseState == EXPECT_FEED2)
{
if (data == '\n')
{
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
itemWriteByte(_boundary.c_str()[i]);
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
}
}