Skip to content

Commit

Permalink
File upload support in the HttpServer. (#1792)
Browse files Browse the repository at this point in the history
  • Loading branch information
slaff authored Aug 2, 2019
1 parent 99c8325 commit 0be2b33
Show file tree
Hide file tree
Showing 24 changed files with 844 additions and 4 deletions.
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

0 comments on commit 0be2b33

Please sign in to comment.