Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File upload support in the HttpServer. #1792

Merged
merged 10 commits into from
Aug 2, 2019
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
path = Sming/Components/libyuarel
url = https://github.com/jacketizer/libyuarel.git
ignore = dirty
[submodule "multipart-parser"]
path = Sming/Components/MultipartParser/multipart-parser
url = https://github.com/iafonov/multipart-parser-c.git
ignore = dirty

[submodule "esptool"]
path = Sming/Arch/Esp8266/Components/esptool/esptool
Expand Down
30 changes: 30 additions & 0 deletions Sming/Components/MultipartParser/HttpMultipartResource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* HttpMultipartResource.cpp
*
* @author: 2019 - Slavey Karadzhov <[email protected]>
*
****/

#include "HttpMultipartResource.h"

int HttpMultipartResource::setFileMap(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
String contentType = request.headers[HTTP_HEADER_CONTENT_TYPE];
String mimeType = ContentType::toString(MIME_FORM_MULTIPART);
if(!(request.method == HTTP_POST && contentType.startsWith(mimeType))) {
return 0;
}

mapper(request.files);

return 0;
}

void HttpMultipartResource::shutdown(HttpServerConnection& connection)
{
}
49 changes: 49 additions & 0 deletions Sming/Components/MultipartParser/HttpMultipartResource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* HttpMultipartResource.h
*
* @author: 2019 - Slavey Karadzhov <[email protected]>
*
****/

#pragma once

#include <Network/Http/HttpServerConnection.h>
#include <Network/Http/HttpResource.h>
#include <WString.h>

typedef Delegate<void(HttpFiles&)> HttpFilesMapper;

class HttpMultipartResource : public HttpResource
{
public:
/**
* @brief HttpResource that allows handling of HTTP file upload.
* @param mapper callback that provides information where the desired upload fields will be stored.
* @param complete callback that will be called after the request has completed.
* @note:
* On a normal computer the file uploads are usually using
* temporary space on the hard disk or in memory to store the incoming data.
* On an embedded device that is a luxury that we can hardly afford.
* Therefore we should define a `map` that specifies explicitly
* where every form field will be stored.
* If a field is not specified then its content will be discarded.
*/
HttpMultipartResource(const HttpFilesMapper& mapper, HttpResourceDelegate complete)
{
onHeadersComplete = HttpResourceDelegate(&HttpMultipartResource::setFileMap, this);
onRequestComplete = complete;
this->mapper = mapper;
}

virtual int setFileMap(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response);

void shutdown(HttpServerConnection& connection) override;

private:
HttpFilesMapper mapper;
};
154 changes: 154 additions & 0 deletions Sming/Components/MultipartParser/MultipartParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* MultipartParser.cpp
*
* @author: 2019 - Slavey Karadzhov <[email protected]>
*
****/

#include "MultipartParser.h"
#include <Network/Http/HttpBodyParser.h>

multipart_parser_settings_t MultipartParser::settings = {
.on_header_field = readHeaderName,
.on_header_value = readHeaderValue,
.on_part_data = partData,
.on_part_data_begin = partBegin,
.on_headers_complete = nullptr,
.on_part_data_end = partEnd,
.on_body_end = bodyEnd,
};


void formMultipartParser(HttpRequest& request, const char* at, int length)
{
auto parser = static_cast<MultipartParser*>(request.args);

if(length == PARSE_DATASTART) {
delete parser;

parser = new MultipartParser(&request);
request.args = parser;

return;
}

if(length == PARSE_DATAEND) {
delete parser;
request.args = nullptr;

return;
}

parser->execute(at, length);
}


/** @brief Boilerplate code for multipart_parser callbacks
* @note Obtain parser object and check it
*/
#define GET_PARSER() \
auto parser = static_cast<MultipartParser*>(p->data); \
if(parser == nullptr) { \
return -1; \
}

MultipartParser::MultipartParser(HttpRequest* request)
{
if(request->headers.contains(HTTP_HEADER_CONTENT_TYPE)) {
// Content-Type: multipart/form-data; boundary=------------------------a48863c0572edce6
int startPost = request->headers[HTTP_HEADER_CONTENT_TYPE].indexOf("boundary=");
if(startPost == -1) {
return;
}

startPost += 9;
String boundary = "--" + request->headers[HTTP_HEADER_CONTENT_TYPE].substring(startPost);
parser = multipart_parser_init(boundary.c_str(), &settings);
}

this->request = request;
parser->data = this;
}

MultipartParser::~MultipartParser()
{
multipart_parser_free(parser);
parser = nullptr;
}

void MultipartParser::execute(const char* at, size_t length)
{
multipart_parser_execute(parser, at, length);
}

int MultipartParser::partBegin(multipart_parser_t* p)
{
GET_PARSER();

return 0;
}

int MultipartParser::readHeaderName(multipart_parser_t* p, const char* at, size_t length)
{
GET_PARSER();

parser->headerName.setString(at, length);

return 0;
}

int MultipartParser::readHeaderValue(multipart_parser_t* p, const char* at, size_t length)
{
GET_PARSER();

if(parser->headerName == _F("Content-Disposition")) {
// Content-Disposition: form-data; name="image"; filename=".gitignore"
// Content-Disposition: form-data; name="data"
String value = String(at, length);
int startPos = value.indexOf(_F("name="));
if(startPos < 0) {
debug_e("Invalid header content");
return -1; // Invalid header content
}
startPos += 6; // name="
int endPos = value.indexOf(';', startPos);
if(endPos < 0) {
parser->name = value.substring(startPos, value.length() - 1);
} else {
parser->name = value.substring(startPos, endPos - 1);
}
}

return 0;
}

int MultipartParser::partData(multipart_parser_t* p, const char* at, size_t length)
{
GET_PARSER();

ReadWriteStream* stream = parser->request->files[parser->name];
if(stream != nullptr) {
stream->write((uint8_t*)at, length);
}

return 0;
}

int MultipartParser::partEnd(multipart_parser_t* p)
{
GET_PARSER();

return 0;
}

int MultipartParser::bodyEnd(multipart_parser_t* p)
{
GET_PARSER();

return 0;
}
47 changes: 47 additions & 0 deletions Sming/Components/MultipartParser/MultipartParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* MultipartParser.h
*
* @author: 2019 - Slavey Karadzhov <[email protected]>
*
****/

#pragma once

#include <Network/Http/HttpCommon.h>
#include <Network/Http/HttpRequest.h>

#include "multipart-parser/multipart_parser.h"

class MultipartParser
{
public:
explicit MultipartParser(HttpRequest* request);
~MultipartParser();

void execute(const char* at, size_t length);

static int readHeaderName(multipart_parser_t* p, const char* at, size_t length);
static int readHeaderValue(multipart_parser_t* p, const char* at, size_t length);
static int partBegin(multipart_parser_t* p);
static int partData(multipart_parser_t* p, const char* at, size_t length);
static int partEnd(multipart_parser_t* p);
static int bodyEnd(multipart_parser_t* p);

private:
static multipart_parser_settings_t settings;

String headerName; ///< Current header field name

HttpRequest* request = nullptr;

multipart_parser_t* parser = nullptr;
String name; // current parameter name
};


void formMultipartParser(HttpRequest& request, const char* at, int length);
4 changes: 4 additions & 0 deletions Sming/Components/MultipartParser/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Multipart Parser
================

Component to manage processing of multipart form data.
2 changes: 2 additions & 0 deletions Sming/Components/MultipartParser/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
COMPONENT_SUBMODULES := multipart-parser
COMPONENT_SRCFILES := multipart-parser/multipart_parser.c
1 change: 1 addition & 0 deletions Sming/Components/MultipartParser/multipart-parser
Submodule multipart-parser added at 772639
Loading