esp32-fm-pirate/src/http/HTTPMultipartBodyParser.cpp
2023-04-22 21:55:43 +03:00

289 lines
7.4 KiB
C++

#include "HTTPMultipartBodyParser.hpp"
#include <sstream>
const size_t MAXLINESIZE = 256;
namespace httpsserver {
HTTPMultipartBodyParser::HTTPMultipartBodyParser(HTTPRequest * req):
HTTPBodyParser(req),
peekBuffer(NULL),
peekBufferSize(0),
boundary(""),
lastBoundary(""),
fieldName(""),
fieldMimeType(""),
fieldFilename("")
{
auto contentType = _request->getHeader("Content-Type");
#ifdef DEBUG_MULTIPART_PARSER
Serial.print("Content type: ");
Serial.println(contentType.c_str());
#endif
auto boundaryIndex = contentType.find("boundary=");
if(boundaryIndex == std::string::npos) {
HTTPS_LOGE("Multipart: missing boundary=");
discardBody();
return;
}
boundary = contentType.substr(boundaryIndex + 9); // "boundary="
auto commaIndex = boundary.find(';');
boundary = "--" + boundary.substr(0, commaIndex);
if(boundary.size() > 72) {
HTTPS_LOGE("Multipart: boundary string too long");
discardBody();
}
lastBoundary = boundary + "--";
}
HTTPMultipartBodyParser::~HTTPMultipartBodyParser() {
if (peekBuffer) {
free(peekBuffer);
peekBuffer = NULL;
}
}
void HTTPMultipartBodyParser::discardBody() {
if (peekBuffer) {
free(peekBuffer);
}
peekBuffer = NULL;
peekBufferSize = 0;
_request->discardRequestBody();
}
bool HTTPMultipartBodyParser::endOfBody() {
return peekBufferSize == 0 && _request->requestComplete();
}
void HTTPMultipartBodyParser::fillBuffer(size_t maxLen) {
// Fill the buffer with up to maxLen bytes (total length, including
// what was already in the buffer), but stop reading ahead once
// we have a CR in the buffer (because the upper layers will
// stop consuming there anyway, to forestall overrunning
// a boundary)
char *bufPtr;
if (peekBuffer == NULL) {
// Nothing in the buffer. Allocate one of the wanted size
peekBuffer = (char *)malloc(maxLen);
if (peekBuffer == NULL) {
HTTPS_LOGE("Multipart: out of memory");
discardBody();
return;
}
bufPtr = peekBuffer;
peekBufferSize = 0;
} else if (peekBufferSize < maxLen) {
// Something in the buffer, but not enough
char *newPeekBuffer = (char *)realloc(peekBuffer, maxLen);
if (newPeekBuffer == NULL) {
HTTPS_LOGE("Multipart: out of memory");
discardBody();
return;
}
peekBuffer = newPeekBuffer;
bufPtr = peekBuffer + peekBufferSize;
} else {
// We already have enough data in the buffer.
return;
}
while(bufPtr < peekBuffer+maxLen) {
size_t didRead = _request->readChars(bufPtr, peekBuffer+maxLen-bufPtr);
if (didRead == 0) {
break;
}
bufPtr += didRead;
// We stop buffering once we have a CR in the buffer
if (memchr(peekBuffer, '\r', bufPtr-peekBuffer) != NULL) {
break;
}
}
peekBufferSize = bufPtr - peekBuffer;
if (peekBufferSize == 0) {
HTTPS_LOGE("Multipart incomplete");
}
}
void HTTPMultipartBodyParser::consumedBuffer(size_t consumed) {
if (consumed == 0) {
return;
}
if (consumed == peekBufferSize) {
free(peekBuffer);
peekBuffer = NULL;
peekBufferSize = 0;
} else {
memmove(peekBuffer, peekBuffer+consumed, peekBufferSize-consumed);
peekBufferSize -= consumed;
}
}
bool HTTPMultipartBodyParser::skipCRLF() {
if (peekBufferSize < 2) {
fillBuffer(2);
}
if (peekBufferSize < 2) {
return false;
}
if (peekBuffer[0] != '\r') {
return false;
}
if (peekBuffer[1] != '\n') {
HTTPS_LOGE("Multipart incorrect line terminator");
discardBody();
return false;
}
consumedBuffer(2);
return true;
}
std::string HTTPMultipartBodyParser::readLine() {
fillBuffer(MAXLINESIZE);
if (peekBufferSize == 0) {
return "";
}
char *crPtr = (char *)memchr(peekBuffer, '\r', peekBufferSize);
if (crPtr == NULL) {
HTTPS_LOGE("Multipart line too long");
discardBody();
return "";
}
size_t lineLength = crPtr-peekBuffer;
std::string rv(peekBuffer, lineLength);
consumedBuffer(lineLength);
skipCRLF();
return rv;
}
// Returns true if the buffer contains a boundary (or possibly lastBoundary)
bool HTTPMultipartBodyParser::peekBoundary() {
if (peekBuffer == NULL || peekBufferSize < boundary.size()) {
return false;
}
char *ptr = peekBuffer;
if (*ptr == '\r') {
ptr++;
}
if (*ptr == '\n') {
ptr++;
}
return memcmp(ptr, boundary.c_str(), boundary.size()) == 0;
}
bool HTTPMultipartBodyParser::nextField() {
fillBuffer(MAXLINESIZE);
while(!peekBoundary()) {
std::string dummy = readLine();
if (endOfBody()) {
HTTPS_LOGE("Multipart missing last boundary");
return false;
}
fillBuffer(MAXLINESIZE);
}
skipCRLF();
std::string line = readLine();
if (line == lastBoundary) {
discardBody();
return false;
}
if (line != boundary) {
HTTPS_LOGE("Multipart incorrect boundary");
return false;
}
// Read header lines up to and including blank line
fieldName = "";
fieldMimeType = "text/plain";
fieldFilename = "";
while (true) {
line = readLine();
if (line == "") {
break;
}
if (line.substr(0, 14) == "Content-Type: ") {
fieldMimeType = line.substr(14);
}
if (line.substr(0, 31) == "Content-Disposition: form-data;") {
// Parse name=value; or name="value"; fields.
std::string field;
line = line.substr(31);
while(true) {
size_t pos = line.find_first_not_of(' ');
if (pos != std::string::npos) {
line = line.substr(pos);
}
if (line == "") break;
pos = line.find(';');
if (pos == std::string::npos) {
field = line;
line = "";
} else {
field = line.substr(0, pos);
line = line.substr(pos+1);
}
pos = field.find('=');
if (pos == std::string::npos) {
HTTPS_LOGE("Multipart ill-formed form-data header");
return false;
}
std::string headerName = field.substr(0, pos);
std::string headerValue = field.substr(pos+1);
if (headerValue.substr(0,1) == "\"") {
headerValue = headerValue.substr(1, headerValue.size()-2);
}
if (headerName == "name") {
fieldName = headerValue;
}
if (headerName == "filename") {
fieldFilename = headerValue;
}
}
}
}
if (fieldName == "") {
HTTPS_LOGE("Multipart missing name");
return false;
}
return true;
}
std::string HTTPMultipartBodyParser::getFieldName() {
return fieldName;
}
std::string HTTPMultipartBodyParser::getFieldFilename() {
return fieldFilename;
}
std::string HTTPMultipartBodyParser::getFieldMimeType() {
return fieldMimeType;
}
bool HTTPMultipartBodyParser::endOfField() {
return peekBoundary();
}
size_t HTTPMultipartBodyParser::read(byte* buffer, size_t bufferSize) {
if (peekBoundary()) {
return 0;
}
size_t readSize = std::min(bufferSize, MAXLINESIZE);
fillBuffer(readSize);
if (peekBoundary()) {
return 0;
}
// We read at most up to a CR (so we don't miss a boundary that has been partially buffered)
// but we always read at least one byte so if the first byte in the buffer is a CR we do read it.
if (peekBufferSize > 1) {
char *crPtr = (char *)memchr(peekBuffer+1, '\r', peekBufferSize-1);
if (crPtr != NULL && crPtr - peekBuffer < bufferSize) {
bufferSize = crPtr - peekBuffer;
}
}
size_t copySize = std::min(bufferSize, peekBufferSize);
memcpy(buffer, peekBuffer, copySize);
consumedBuffer(copySize);
return copySize;
}
} /* namespace httpsserver */