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.
+
+
+