diff --git a/.gitmodules b/.gitmodules index d4788148aa..6ff528045d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/Sming/Components/MultipartParser/HttpMultipartResource.cpp b/Sming/Components/MultipartParser/HttpMultipartResource.cpp new file mode 100644 index 0000000000..56a32f307e --- /dev/null +++ b/Sming/Components/MultipartParser/HttpMultipartResource.cpp @@ -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 + * + ****/ + +#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) +{ +} diff --git a/Sming/Components/MultipartParser/HttpMultipartResource.h b/Sming/Components/MultipartParser/HttpMultipartResource.h new file mode 100644 index 0000000000..f148b739f7 --- /dev/null +++ b/Sming/Components/MultipartParser/HttpMultipartResource.h @@ -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 + * + ****/ + +#pragma once + +#include +#include +#include + +typedef Delegate 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; +}; diff --git a/Sming/Components/MultipartParser/MultipartParser.cpp b/Sming/Components/MultipartParser/MultipartParser.cpp new file mode 100644 index 0000000000..998acdf29e --- /dev/null +++ b/Sming/Components/MultipartParser/MultipartParser.cpp @@ -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 + * + ****/ + +#include "MultipartParser.h" +#include + +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(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(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; +} diff --git a/Sming/Components/MultipartParser/MultipartParser.h b/Sming/Components/MultipartParser/MultipartParser.h new file mode 100644 index 0000000000..77b84ff509 --- /dev/null +++ b/Sming/Components/MultipartParser/MultipartParser.h @@ -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 + * + ****/ + +#pragma once + +#include +#include + +#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); diff --git a/Sming/Components/MultipartParser/README.rst b/Sming/Components/MultipartParser/README.rst new file mode 100644 index 0000000000..21bf29aaf3 --- /dev/null +++ b/Sming/Components/MultipartParser/README.rst @@ -0,0 +1,4 @@ +Multipart Parser +================ + +Component to manage processing of multipart form data. diff --git a/Sming/Components/MultipartParser/component.mk b/Sming/Components/MultipartParser/component.mk new file mode 100644 index 0000000000..d8e419f093 --- /dev/null +++ b/Sming/Components/MultipartParser/component.mk @@ -0,0 +1,2 @@ +COMPONENT_SUBMODULES := multipart-parser +COMPONENT_SRCFILES := multipart-parser/multipart_parser.c diff --git a/Sming/Components/MultipartParser/multipart-parser b/Sming/Components/MultipartParser/multipart-parser new file mode 160000 index 0000000000..772639cf10 --- /dev/null +++ b/Sming/Components/MultipartParser/multipart-parser @@ -0,0 +1 @@ +Subproject commit 772639cf10db6d9f5a655ee9b7eb20b815fab396 diff --git a/Sming/Components/MultipartParser/multipart-parser.patch b/Sming/Components/MultipartParser/multipart-parser.patch new file mode 100644 index 0000000000..09e87d7baa --- /dev/null +++ b/Sming/Components/MultipartParser/multipart-parser.patch @@ -0,0 +1,139 @@ +diff --git a/multipart_parser.c b/multipart_parser.c +index 981dabb..52e0200 100644 +--- a/multipart_parser.c ++++ b/multipart_parser.c +@@ -5,9 +5,10 @@ + + #include "multipart_parser.h" + +-#include +-#include + #include ++#ifndef tolower ++ int tolower (int __c); ++#endif + + static void multipart_log(const char * format, ...) + { +@@ -43,20 +44,6 @@ do { \ + #define LF 10 + #define CR 13 + +-struct multipart_parser { +- void * data; +- +- size_t index; +- size_t boundary_length; +- +- unsigned char state; +- +- const multipart_parser_settings* settings; +- +- char* lookbehind; +- char multipart_boundary[1]; +-}; +- + enum state { + s_uninitialized = 1, + s_start, +@@ -77,10 +64,10 @@ enum state { + s_end + }; + +-multipart_parser* multipart_parser_init +- (const char *boundary, const multipart_parser_settings* settings) { ++multipart_parser_t* multipart_parser_init ++ (const char *boundary, const multipart_parser_settings_t * settings) { + +- multipart_parser* p = malloc(sizeof(multipart_parser) + ++ multipart_parser_t* p = malloc(sizeof(multipart_parser_t) + + strlen(boundary) + + strlen(boundary) + 9); + +@@ -96,22 +83,22 @@ multipart_parser* multipart_parser_init + return p; + } + +-void multipart_parser_free(multipart_parser* p) { ++void multipart_parser_free(multipart_parser_t* p) { + free(p); + } + +-void multipart_parser_set_data(multipart_parser *p, void *data) { ++void multipart_parser_set_data(multipart_parser_t *p, void *data) { + p->data = data; + } + +-void *multipart_parser_get_data(multipart_parser *p) { ++void *multipart_parser_get_data(multipart_parser_t *p) { + return p->data; + } + +-size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) { ++size_t multipart_parser_execute(multipart_parser_t* p, const char *buf, size_t len) { + size_t i = 0; + size_t mark = 0; +- char c, cl; ++ unsigned char c, cl; + int is_last = 0; + + while(i < len) { +diff --git a/multipart_parser.h b/multipart_parser.h +index 05429d7..5fe695e 100644 +--- a/multipart_parser.h ++++ b/multipart_parser.h +@@ -11,14 +11,13 @@ extern "C" + #endif + + #include +-#include + +-typedef struct multipart_parser multipart_parser; +-typedef struct multipart_parser_settings multipart_parser_settings; ++typedef struct multipart_parser multipart_parser_t; ++typedef struct multipart_parser_settings multipart_parser_settings_t; + typedef struct multipart_parser_state multipart_parser_state; + +-typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length); +-typedef int (*multipart_notify_cb) (multipart_parser*); ++typedef int (*multipart_data_cb) (multipart_parser_t*, const char *at, size_t length); ++typedef int (*multipart_notify_cb) (multipart_parser_t*); + + struct multipart_parser_settings { + multipart_data_cb on_header_field; +@@ -31,15 +30,28 @@ struct multipart_parser_settings { + multipart_notify_cb on_body_end; + }; + +-multipart_parser* multipart_parser_init +- (const char *boundary, const multipart_parser_settings* settings); ++struct multipart_parser { ++ void* data; + +-void multipart_parser_free(multipart_parser* p); ++ size_t index; ++ size_t boundary_length; + +-size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len); ++ unsigned char state; + +-void multipart_parser_set_data(multipart_parser* p, void* data); +-void * multipart_parser_get_data(multipart_parser* p); ++ const multipart_parser_settings_t* settings; ++ ++ char* lookbehind; ++ char multipart_boundary[1]; ++}; ++ ++multipart_parser_t* multipart_parser_init(const char* boundary, const multipart_parser_settings_t* settings); ++ ++void multipart_parser_free(multipart_parser_t* p); ++ ++size_t multipart_parser_execute(multipart_parser_t* p, const char* buf, size_t len); ++ ++void multipart_parser_set_data(multipart_parser_t* p, void* data); ++void * multipart_parser_get_data(multipart_parser_t* p); + + #ifdef __cplusplus + } /* extern "C" */ + \ No newline at end of file diff --git a/Sming/Core/Data/ObjectMap.h b/Sming/Core/Data/ObjectMap.h index 719b63ae23..f3f6839575 100644 --- a/Sming/Core/Data/ObjectMap.h +++ b/Sming/Core/Data/ObjectMap.h @@ -37,7 +37,7 @@ * value = nullptr; // Free object, "key1" -> nullptr (but still in map) * value.remove(); // Free object1 and remove from map * - * // As soon as `map` goes out of scope, all contained objects are detroyed + * // As soon as `map` goes out of scope, all contained objects are destroyed * map["key1"] = new MyType(); * map["key2"] = new MyType(); * } diff --git a/Sming/Core/Network/Http/HttpCommon.h b/Sming/Core/Network/Http/HttpCommon.h index c811dc61a1..be9d13246f 100644 --- a/Sming/Core/Network/Http/HttpCommon.h +++ b/Sming/Core/Network/Http/HttpCommon.h @@ -17,6 +17,8 @@ #include "WString.h" #include "../WebConstants.h" #include "../Url.h" +#include "Data/Stream/ReadWriteStream.h" +#include "Data/ObjectMap.h" #ifndef HTTP_MAX_HEADER_SIZE #define HTTP_MAX_HEADER_SIZE (8 * 1024) @@ -40,6 +42,8 @@ enum HttpConnectionState { eHCS_Sent }; +typedef ObjectMap HttpFiles; + /** * @brief Return a string name of the given error * @param err diff --git a/Sming/Core/Network/Http/HttpRequest.h b/Sming/Core/Network/Http/HttpRequest.h index c17ba60b74..de0001e761 100644 --- a/Sming/Core/Network/Http/HttpRequest.h +++ b/Sming/Core/Network/Http/HttpRequest.h @@ -119,7 +119,7 @@ class HttpRequest * * @retval HttpRequest* */ - HttpRequest* setFile(const String& formElementName, IDataSourceStream* stream) + HttpRequest* setFile(const String& formElementName, ReadWriteStream* stream) { if(stream != nullptr) { files[formElementName] = stream; @@ -276,6 +276,7 @@ class HttpRequest HttpMethod method = HTTP_GET; HttpHeaders headers; HttpParams postParams; + HttpFiles files; int retries = 0; // how many times the request should be send again... @@ -300,7 +301,5 @@ class HttpRequest #endif private: - ObjectMap files; - HttpParams* queryParams = nullptr; // << @todo deprecate }; diff --git a/Sming/Core/Network/HttpServer.cpp b/Sming/Core/Network/HttpServer.cpp index a8d878f399..81f5c8d19d 100644 --- a/Sming/Core/Network/HttpServer.cpp +++ b/Sming/Core/Network/HttpServer.cpp @@ -14,6 +14,10 @@ #include "TcpClient.h" #include "WString.h" +#ifdef ENABLE_HTTP_SERVER_MULTIPART +#include +#endif + void HttpServer::configure(const HttpServerSettings& settings) { this->settings = settings; @@ -23,6 +27,9 @@ void HttpServer::configure(const HttpServerSettings& settings) if(settings.useDefaultBodyParsers) { setBodyParser(ContentType::toString(MIME_FORM_URL_ENCODED), formUrlParser); +#ifdef ENABLE_HTTP_SERVER_MULTIPART + setBodyParser(ContentType::toString(MIME_FORM_MULTIPART), formMultipartParser); +#endif } setKeepAlive(settings.keepAliveSeconds); diff --git a/Sming/Core/Network/rBootHttpUpdate.h b/Sming/Core/Network/rBootHttpUpdate.h index 5226b5e8a9..c37cd874af 100644 --- a/Sming/Core/Network/rBootHttpUpdate.h +++ b/Sming/Core/Network/rBootHttpUpdate.h @@ -37,6 +37,8 @@ class rBootItemOutputStream : public ReadWriteStream virtual ~rBootItemOutputStream() { close(); + delete item; + item = nullptr; } void setItem(rBootHttpUpdateItem* item) diff --git a/Sming/component.mk b/Sming/component.mk index b70ba37bad..811ddf684a 100644 --- a/Sming/component.mk +++ b/Sming/component.mk @@ -72,6 +72,14 @@ ifdef LOCALE GLOBAL_CFLAGS += -DLOCALE=$(LOCALE) endif +# => Multipart Parser +COMPONENT_VARS += ENABLE_HTTP_SERVER_MULTIPART +ifeq ($(ENABLE_HTTP_SERVER_MULTIPART),1) + SMING_FEATURES += HTTP_SERVER_MULTIPART + GLOBAL_CFLAGS += -DENABLE_HTTP_SERVER_MULTIPART=1 + COMPONENT_DEPENDS += MultipartParser +endif + ### Debug output parameters # By default `debugf` does not print file name and line number. If you want this enabled set the directive below to 1 diff --git a/samples/HttpServer_FirmwareUpload/.cproject b/samples/HttpServer_FirmwareUpload/.cproject new file mode 100644 index 0000000000..cdd4acfa57 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/.cproject @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + -f ${ProjDirPath}/Makefile + all + true + true + true + + + make + -f ${ProjDirPath}/Makefile + clean + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flash + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashonefile + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashinit + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashboot + true + true + true + + + make + -f ${ProjDirPath}/Makefile + rebuild + true + true + true + + + + + + + + + + + + + + + + + + + + diff --git a/samples/HttpServer_FirmwareUpload/.gitignore b/samples/HttpServer_FirmwareUpload/.gitignore new file mode 100644 index 0000000000..ff6ba517db --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/.gitignore @@ -0,0 +1 @@ +web/.lastModified diff --git a/samples/HttpServer_FirmwareUpload/.project b/samples/HttpServer_FirmwareUpload/.project new file mode 100644 index 0000000000..aadd728366 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/.project @@ -0,0 +1,28 @@ + + + HttpServer_FirmwareUpload + + + SmingFramework + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/samples/HttpServer_FirmwareUpload/Makefile b/samples/HttpServer_FirmwareUpload/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/samples/HttpServer_FirmwareUpload/README.rst b/samples/HttpServer_FirmwareUpload/README.rst new file mode 100644 index 0000000000..eb71d4ab25 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/README.rst @@ -0,0 +1,18 @@ +HttpServer Firmware Upload +========================= + +Introduction +------------ + +The HTTP server coming with Sming is quite powerful but it is limited +from the available resources of the underlining hardware (your favorite +ESP8266 microcontroller). + +This sample demonstrates how to enable file upload of the HTTP server. +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. +In this sample we demonstrate how to define which file upload fields +should be stored and what (file) streams are responsible for storing the data. +If a field is not specified then its content will be discarded. \ No newline at end of file diff --git a/samples/HttpServer_FirmwareUpload/app/application.cpp b/samples/HttpServer_FirmwareUpload/app/application.cpp new file mode 100644 index 0000000000..7963fed1bd --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/app/application.cpp @@ -0,0 +1,137 @@ +#include + +#include +#include +#include + +HttpServer server; +String lastModified; + +void onIndex(HttpRequest& request, HttpResponse& response) +{ + TemplateFileStream* tmpl = new TemplateFileStream("index.html"); + auto& vars = tmpl->variables(); + response.sendTemplate(tmpl); +} + +void onFile(HttpRequest& request, HttpResponse& response) +{ + if(lastModified.length() > 0 && request.headers[HTTP_HEADER_IF_MODIFIED_SINCE].equals(lastModified)) { + response.code = HTTP_STATUS_NOT_MODIFIED; + return; + } + + String file = request.uri.getRelativePath(); + + if(file[0] == '.') + response.code = HTTP_STATUS_FORBIDDEN; + else { + if(lastModified.length() > 0) { + response.headers[HTTP_HEADER_LAST_MODIFIED] = lastModified; + } + + response.setCache(86400, true); // It's important to use cache for better performance. + response.sendFile(file); + } +} + +int onUpload(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) +{ + ReadWriteStream* file = request.files["firmware"]; + if(file == nullptr) { + debug_e("Something went wrong with the file upload"); + return 1; + } + + debugf("Uploaded length: %d", file->available()); + + rboot_config bootConfig = rboot_get_config(); + uint8_t slot = bootConfig.current_rom; + slot = (slot == 0 ? 1 : 0); + Serial.printf("Firmware updated, rebooting to rom %d...\r\n", slot); + rboot_set_current_rom(slot); + System.restart(5); // defer the restart with 5 seconds to give time to the web server to return the response + + response.sendFile("restart.html"); + + return 0; +} + +void simpleUploadMapper(HttpFiles& files) +{ + /* + * 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 a specific form field will be stored. + * + * If a field is not specified then its content will be discarded. + */ + + // Below we instruct the server to store max 1024 bytes + // from the incoming data for the "firmware" form fields. + files["firmware"] = new LimitedMemoryStream(1024); +} + +void fileUploadMapper(HttpFiles& files) +{ + /* + * 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 a specific form field will be stored. + * + * If a field is not specified then its content will be discarded. + */ + + // Get the address where the next firmware should be stored. + rboot_config bootConfig = rboot_get_config(); + uint8_t slot = bootConfig.current_rom; + slot = (slot == 0 ? 1 : 0); + int romStartAddress = bootConfig.roms[slot]; + + // We create a rBoot item to be stored + auto item = new rBootHttpUpdateItem(); + item->size = 0; + item->targetOffset = romStartAddress; // the start location on flash memory + item->url = "http://localhost"; // will be discarded + + auto rBootStream = new rBootItemOutputStream(); + rBootStream->setItem(item); + + files["firmware"] = rBootStream; +} + +void startWebServer() +{ + server.listen(80); + server.paths.set("/", onIndex); + + HttpMultipartResource* uploadResouce = new HttpMultipartResource(fileUploadMapper, onUpload); + server.paths.set("/upload", uploadResouce); + + server.paths.setDefault(onFile); +} + +void init() +{ + spiffs_mount(); // Mount file system, in order to work with files + + if(fileExist(".lastModified")) { + // The last modification + lastModified = fileGetContent(".lastModified"); + lastModified.trim(); + } + + Serial.begin(SERIAL_BAUD_RATE); // 115200 by default + Serial.systemDebugOutput(true); // Enable debug output to serial + + WifiStation.enable(true); + + // Run WEB server on system ready + System.onReady(startWebServer); +} diff --git a/samples/HttpServer_FirmwareUpload/component.mk b/samples/HttpServer_FirmwareUpload/component.mk new file mode 100644 index 0000000000..87a96d5aa8 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/component.mk @@ -0,0 +1,11 @@ +SPIFF_FILES = web/ +ARDUINO_LIBRARIES := ArduinoJson6 + +# The line below enables the form upload support on the server +ENABLE_HTTP_SERVER_MULTIPART = 1 + +web-pack: + $(Q) date +'%a, %d %b %Y %H:%M:%S GMT' -u > web/.lastModified + +web-upload: web-pack spiff_update + $(call WriteFlash,$(SPIFF_START_OFFSET)=$(SPIFF_BIN_OUT)) diff --git a/samples/HttpServer_FirmwareUpload/web/index.html b/samples/HttpServer_FirmwareUpload/web/index.html new file mode 100644 index 0000000000..416830ad22 --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/web/index.html @@ -0,0 +1,20 @@ + + + + Sming Firmware Updater + + + +

Update Firmware

+ +

+ This sample demonstrates how to update the current firmware by uploading a new firmware directly on the device. + Behind the scenes the newly uploaded firmware will be stored on the FLASH memory into the next available slot. + + +

+ +
+

+ + diff --git a/samples/HttpServer_FirmwareUpload/web/restart.html b/samples/HttpServer_FirmwareUpload/web/restart.html new file mode 100644 index 0000000000..2f27f1073d --- /dev/null +++ b/samples/HttpServer_FirmwareUpload/web/restart.html @@ -0,0 +1,15 @@ + + + + Sming Firmware Updater + + + +

Firmware update: Success

+ +

+ The new firmware was updated and the device will be restarted in a few seconds. + Take a look at the serial output for details. +

+ + \ No newline at end of file