From 9b30d588a9e24da43d8e5c1370a711a250a13dd9 Mon Sep 17 00:00:00 2001 From: slaff Date: Tue, 2 May 2017 09:52:16 +0200 Subject: [PATCH 01/35] Multiple improvements to the TCP handling. (#1110) TcpClient::onReceive - The data is processed without intermediate buffers. - Errors are reported as expected. TcpConnection - Added support for SSL session resumption - Added convenience methods to set SSL session id and SSL fingerprints TcpServer - Added support for using SSL on the server side !Notice: Due to memory limitations of the device it might handle only one SSL connection at a time! - Added setting for minimumHeapSize needed before the server starts rejecting new client connections --- Sming/SmingCore/Network/TcpClient.cpp | 31 ++++---- Sming/SmingCore/Network/TcpConnection.cpp | 88 +++++++++++++++++++---- Sming/SmingCore/Network/TcpConnection.h | 47 +++++++++++- Sming/SmingCore/Network/TcpServer.cpp | 60 +++++++++++++++- Sming/SmingCore/Network/TcpServer.h | 16 ++++- 5 files changed, 208 insertions(+), 34 deletions(-) diff --git a/Sming/SmingCore/Network/TcpClient.cpp b/Sming/SmingCore/Network/TcpClient.cpp index faaf39aa71..df09b847b1 100644 --- a/Sming/SmingCore/Network/TcpClient.cpp +++ b/Sming/SmingCore/Network/TcpClient.cpp @@ -113,29 +113,28 @@ err_t TcpClient::onReceive(pbuf *buf) if (buf == NULL) { // Disconnected, close it - TcpConnection::onReceive(buf); + return TcpConnection::onReceive(buf); } - else + + if (receive) { - if (receive) - { - char* data = new char[buf->tot_len + 1]; - pbuf_copy_partial(buf, data, buf->tot_len, 0); - data[buf->tot_len] = '\0'; - - if (!receive(*this, data, buf->tot_len)) - { - delete[] data; - return ERR_MEM; + pbuf *cur = buf; + while (cur != NULL && cur->len > 0) { + bool success = !receive(*this, (char*)cur->payload, cur->len); + if(!success) { + debugf("TcpClient::onReceive: Aborted from receive callback"); + + TcpConnection::onReceive(NULL); + return ERR_ABRT; // abort the connection } - delete[] data; + cur = cur->next; } - - // Fire ReadyToSend callback - TcpConnection::onReceive(buf); } + // Fire ReadyToSend callback + TcpConnection::onReceive(buf); + return ERR_OK; } diff --git a/Sming/SmingCore/Network/TcpConnection.cpp b/Sming/SmingCore/Network/TcpConnection.cpp index 3a79e5b8e6..0e0f304292 100644 --- a/Sming/SmingCore/Network/TcpConnection.cpp +++ b/Sming/SmingCore/Network/TcpConnection.cpp @@ -13,10 +13,6 @@ #include "../Wiring/WString.h" #include "../Wiring/IPAddress.h" -#ifdef ENABLE_SSL -#include "../Clock.h" -#endif - TcpConnection::TcpConnection(bool autoDestruct) : autoSelfDestruct(autoDestruct), sleep(0), canSend(true), timeOut(70) { @@ -428,7 +424,21 @@ err_t TcpConnection::staticOnConnected(void *arg, tcp_pcb *tcp, err_t err) } } - con->ssl = ssl_client_new(con->sslContext, clientfd, NULL, 0, con->ssl_ext); + debugf("SSL: Session Id Length: %d", (con->sslSessionId != NULL ? con->sslSessionId->length: 0)); + if(con->sslSessionId != NULL && con->sslSessionId->length > 0) { + debugf("-----BEGIN SSL SESSION PARAMETERS-----"); + for (int i = 0; i < con->sslSessionId->length; i++) { + m_printf("%02x", con->sslSessionId->value[i]); + } + + debugf("\n-----END SSL SESSION PARAMETERS-----"); + } + + con->ssl = ssl_client_new(con->sslContext, clientfd, + (con->sslSessionId != NULL ? con->sslSessionId->value : NULL), + (con->sslSessionId != NULL ? con->sslSessionId->length: 0), + con->ssl_ext + ); if(ssl_handshake_status(con->ssl)!=SSL_OK) { debugf("SSL: handshake is in progress..."); return SSL_OK; @@ -438,6 +448,14 @@ err_t TcpConnection::staticOnConnected(void *arg, tcp_pcb *tcp, err_t err) debugf("SSL: Switching back 80 MHz"); System.setCpuFrequency(eCF_80MHz); #endif + if(con->sslSessionId) { + if(con->sslSessionId->value == NULL) { + con->sslSessionId->value = new uint8_t[SSL_SESSION_ID_SIZE]; + } + memcpy((void *)con->sslSessionId->value, (void *)con->ssl->session_id, con->ssl->sess_id_size); + con->sslSessionId->length = con->ssl->sess_id_size; + } + } } #endif @@ -525,22 +543,41 @@ err_t TcpConnection::staticOnReceive(void *arg, tcp_pcb *tcp, pbuf *p, err_t err debugf("SSL: Switching back to 80 MHz"); System.setCpuFrequency(eCF_80MHz); // Preserve some CPU cycles #endif - if(con->sslFingerprint.certSha1 && ssl_match_fingerprint(con->ssl, con->sslFingerprint.certSha1) != SSL_OK) { - debugf("SSL: Certificate fingerprint does not match!"); - con->close(); - closeTcpConnection(tcp); - return ERR_ABRT; + bool hasError = false; + do { + if(con->sslFingerprint.certSha1 && ssl_match_fingerprint(con->ssl, con->sslFingerprint.certSha1) != SSL_OK) { + debugf("SSL: Certificate fingerprint does not match!"); + hasError = true; + break; + } + + if(con->sslFingerprint.pkSha256 && ssl_match_spki_sha256(con->ssl, con->sslFingerprint.pkSha256) != SSL_OK) { + debugf("SSL: Certificate PK fingerprint does not match!"); + hasError = true; + break; + } + } while(0); + + if(con->freeFingerprints) { + con->freeSslFingerprints(); } - if(con->sslFingerprint.pkSha256 && ssl_match_spki_sha256(con->ssl, con->sslFingerprint.pkSha256) != SSL_OK) { - debugf("SSL: Certificate PK fingerprint does not match!"); + if(hasError) { con->close(); closeTcpConnection(tcp); return ERR_ABRT; } + if(con->sslSessionId) { + if(con->sslSessionId->value == NULL) { + con->sslSessionId->value = new uint8_t[SSL_SESSION_ID_SIZE]; + } + memcpy((void *)con->sslSessionId->value, (void *)con->ssl->session_id, con->ssl->sess_id_size); + con->sslSessionId->length = con->ssl->sess_id_size; + } + err_t res = con->onConnected(err); con->checkSelfFree(); @@ -652,7 +689,7 @@ void TcpConnection::addSslOptions(uint32_t sslOptions) { this->sslOptions |= sslOptions; } -bool TcpConnection::pinCertificate(const uint8_t *fingerprint, SslFingerprintType type) { +bool TcpConnection::pinCertificate(const uint8_t *fingerprint, SslFingerprintType type, bool freeAfterHandshake /* = false */) { int length = 0; uint8_t *localStore; @@ -673,6 +710,7 @@ bool TcpConnection::pinCertificate(const uint8_t *fingerprint, SslFingerprintTyp return false; } + freeFingerprints = freeAfterHandshake; if(localStore) { delete[] localStore; @@ -697,6 +735,12 @@ bool TcpConnection::pinCertificate(const uint8_t *fingerprint, SslFingerprintTyp return true; } +bool TcpConnection::pinCertificate(SSLFingerprints fingerprints, bool freeAfterHandshake /* = false */) { + sslFingerprint = fingerprints; + freeFingerprints = freeAfterHandshake; + return true; +} + bool TcpConnection::setSslClientKeyCert(const uint8_t *key, int keyLength, const uint8_t *certificate, int certificateLength, const char *keyPassword /* = NULL */, bool freeAfterHandshake /* = false */) { @@ -727,6 +771,13 @@ bool TcpConnection::setSslClientKeyCert(const uint8_t *key, int keyLength, return true; } +bool TcpConnection::setSslClientKeyCert(SSLKeyCertPair clientKeyCert, bool freeAfterHandshake /* = false */) { + this->clientKeyCert = clientKeyCert; + freeClientKeyCert = freeAfterHandshake; + + return true; +} + void TcpConnection::freeSslClientKeyCert() { if(clientKeyCert.key) { delete[] clientKeyCert.key; @@ -747,6 +798,17 @@ void TcpConnection::freeSslClientKeyCert() { clientKeyCert.certificateLength = 0; } +void TcpConnection::freeSslFingerprints() { + if(sslFingerprint.certSha1) { + delete[] sslFingerprint.certSha1; + sslFingerprint.certSha1 = NULL; + } + if(sslFingerprint.pkSha256) { + delete[] sslFingerprint.pkSha256; + sslFingerprint.pkSha256 = NULL; + } +} + SSL* TcpConnection::getSsl() { return ssl; } diff --git a/Sming/SmingCore/Network/TcpConnection.h b/Sming/SmingCore/Network/TcpConnection.h index 437961ce2e..b1879e8ecc 100644 --- a/Sming/SmingCore/Network/TcpConnection.h +++ b/Sming/SmingCore/Network/TcpConnection.h @@ -10,6 +10,7 @@ #ifdef ENABLE_SSL #include "../../axtls-8266/compat/lwipr_compat.h" +#include "../Clock.h" #endif #include "../Wiring/WiringFrameworkDependencies.h" @@ -40,7 +41,6 @@ enum SslFingerprintType { // Only when the private key used to generate the certificate is used then that fingerprint }; - typedef struct { uint8_t* certSha1 = NULL; // << certificate SHA1 fingerprint uint8_t* pkSha256 = NULL; // << public key SHA256 fingerprint @@ -54,15 +54,23 @@ typedef struct { int certificateLength = 0; } SSLKeyCertPair; +typedef struct { + uint8_t *value = NULL; + int length = 0; +} SSLSessionId; + #endif struct pbuf; class String; class IDataSourceStream; class IPAddress; +class TcpServer; class TcpConnection { + friend class TcpServer; + public: TcpConnection(bool autoDestruct); TcpConnection(tcp_pcb* connection, bool autoDestruct); @@ -92,7 +100,9 @@ class TcpConnection /** * @brief Sets the SHA1 certificate finger print. * The latter will be used after successful handshake to check against the fingerprint of the other side. + * * @deprecated This method will be removed in future releases. Use pinCertificate instead. + * * @param const uint8_t *data * @param int length * @return bool true of success, false or failure @@ -122,10 +132,24 @@ class TcpConnection * Disadvantages: The hash needs to be updated every time the remote server updates its certificate * @return bool true of success, false or failure */ - bool pinCertificate(const uint8_t *fingerprint, SslFingerprintType type); + bool pinCertificate(const uint8_t *fingerprint, SslFingerprintType type, bool freeAfterHandshake = false); + + /** + * @brief Requires(pins) the remote SSL certificate to match certain fingerprints + * + * @note The data inside the fingerprints parameter is passed by reference + * + * @param SSLFingerprints - passes the certificate fingerprints by reference. + * + * @return bool true of success, false or failure + */ + bool pinCertificate(SSLFingerprints fingerprints, bool freeAfterHandshake = false); /** * @brief Sets client private key, certificate and password from memory + * + * @note This method makes copy of the data. + * * @param const uint8_t *keyData * @param int keyLength * @param const uint8_t *certificateData @@ -139,11 +163,28 @@ class TcpConnection const uint8_t *certificate, int certificateLength, const char *keyPassword = NULL, bool freeAfterHandshake = false); + /** + * @brief Sets client private key, certificate and password from memory + * + * @note This method passes the certificate key chain by reference + * + * @param SSLKeyCertPair + * @param bool freeAfterHandshake + * + * @return bool true of success, false or failure + */ + bool setSslClientKeyCert(SSLKeyCertPair clientKeyCert, bool freeAfterHandshake = false); + /** * @brief Frees the memory used for the client key and certificate pair */ void freeSslClientKeyCert(); + /** + * @brief Frees the memory used for SSL fingerprinting + */ + void freeSslFingerprints(); + SSL* getSsl(); #endif @@ -184,6 +225,8 @@ class TcpConnection uint32_t sslOptions=0; SSLKeyCertPair clientKeyCert; bool freeClientKeyCert = false; + bool freeFingerprints = false; + SSLSessionId* sslSessionId = NULL; #endif bool useSsl = false; }; diff --git a/Sming/SmingCore/Network/TcpServer.cpp b/Sming/SmingCore/Network/TcpServer.cpp index 72c8529e79..3566455811 100644 --- a/Sming/SmingCore/Network/TcpServer.cpp +++ b/Sming/SmingCore/Network/TcpServer.cpp @@ -82,7 +82,13 @@ void TcpServer::setTimeOut(uint16_t waitTimeOut) timeOut = waitTimeOut; } -bool TcpServer::listen(int port) +#ifdef ENABLE_SSL +void TcpServer::setServerKeyCert(SSLKeyCertPair serverKeyCert) { + clientKeyCert = serverKeyCert; +} +#endif + +bool TcpServer::listen(int port, bool useSsl /*= false */) { if (tcp == NULL) initialize(tcp_new()); @@ -90,6 +96,41 @@ bool TcpServer::listen(int port) err_t res = tcp_bind(tcp, IP_ADDR_ANY, port); if (res != ERR_OK) return res; +#ifdef ENABLE_SSL + this->useSsl = useSsl; + + if(useSsl) { + +#ifdef SSL_DEBUG + sslOptions |= SSL_DISPLAY_STATES | SSL_DISPLAY_BYTES | SSL_DISPLAY_CERTS; +#endif + + sslContext = ssl_ctx_new(sslOptions, sslSessionCacheSize); + + if (!(clientKeyCert.keyLength && clientKeyCert.certificateLength)) { + debugf("SSL: server certificate and key are not provided!"); + return false; + } + + if (ssl_obj_memory_load(sslContext, SSL_OBJ_RSA_KEY, + clientKeyCert.key, clientKeyCert.keyLength, + clientKeyCert.keyPassword) != SSL_OK) { + debugf("SSL: Unable to load server private key"); + return false; + } + + if (ssl_obj_memory_load(sslContext, SSL_OBJ_X509_CERT, + clientKeyCert.certificate, + clientKeyCert.certificateLength, NULL) != SSL_OK) { + debugf("SSL: Unable to load server certificate"); + return false; + } + + // TODO: test: free the certificate data on server destroy... + freeClientKeyCert = true; + } +#endif + tcp = tcp_listen(tcp); tcp_accept(tcp, staticAccept); @@ -100,7 +141,7 @@ bool TcpServer::listen(int port) err_t TcpServer::onAccept(tcp_pcb *clientTcp, err_t err) { // Anti DDoS :-) - if (system_get_free_heap_size() < 6500) + if (system_get_free_heap_size() < minHeapSize) { debugf("\r\n\r\nCONNECTION DROPPED\r\n\t(%d)\r\n\r\n", system_get_free_heap_size()); return ERR_MEM; @@ -120,6 +161,21 @@ err_t TcpServer::onAccept(tcp_pcb *clientTcp, err_t err) TcpConnection* client = createClient(clientTcp); if (client == NULL) return ERR_MEM; client->setTimeOut(timeOut); + +#ifdef ENABLE_SSL + if(useSsl) { + int clientfd = axl_append(clientTcp); + if(clientfd == -1) { + debugf("SSL: Unable to initiate tcp "); + return ERR_ABRT; + } + + debugf("SSL: handshake start (%d ms)", millis()); + client->ssl = ssl_server_new(sslContext, clientfd); + client->useSsl = true; + } +#endif + onClient((TcpClient*)client); return ERR_OK; diff --git a/Sming/SmingCore/Network/TcpServer.h b/Sming/SmingCore/Network/TcpServer.h index 930af72062..dfd7c392a9 100644 --- a/Sming/SmingCore/Network/TcpServer.h +++ b/Sming/SmingCore/Network/TcpServer.h @@ -22,9 +22,16 @@ class TcpServer: public TcpConnection { virtual ~TcpServer(); public: - virtual bool listen(int port); + virtual bool listen(int port, bool useSsl = false); void setTimeOut(uint16_t waitTimeOut); +#ifdef ENABLE_SSL + /** + * @brief Adds SSL support and specifies the server certificate and private key. + */ + void setServerKeyCert(SSLKeyCertPair serverKeyCert); +#endif + protected: // Overload this method in your derived class! virtual TcpConnection* createClient(tcp_pcb *clientTcp); @@ -40,6 +47,13 @@ class TcpServer: public TcpConnection { static int16_t totalConnections; uint16_t activeClients = 0; +protected: + int minHeapSize = 6500; + +#ifdef ENABLE_SSL + int sslSessionCacheSize = 50; +#endif + private: uint16_t timeOut; TcpClientDataDelegate clientReceiveDelegate = NULL; From 5cb4ce3b2536c157e56a0a80056709ce537e80da Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 4 May 2017 11:40:14 +0200 Subject: [PATCH 02/35] Refactored HttpClient, HttpServer and WebsocketConnection. (#1112) - Better Code Design - Faster and powerful HttpParser used in both HttpClient and Server - Support for pipelinging in both HttpClient and HttpServer - Suppot for connection reusage for both HttpClient and HttpServer - SSL session resumption support for HttpClient and HttpServer - Added ResourceTree to the HttpServer to allow more flexible definition of resources - Added streaming support to both Http and WebSocket processing. - rBootHttpUpdate should fail now as early as possible. - Changed the ContentType code to allow easier definition of new mime types. ## Backwards-Incompatible changes - WebSocket is renamed to WebSocketConnection to reflect better its meaning and intended use. - Removed the tightly coupled websocket methods inside the HttpServer. You can use WebSocketResource and add it to the HttpServer resource tree to achieve the same results. - Moved writeInit(), writeFlash(const u8 *data, u16 size) and writeEnd() methods from rBootHttpUpdate to rBootItemOutputStream. - TemplateFileStream::setVarsFromRequest needs to be refactored completely. The current code couples TemplateFileStream to HttpRequest, which is too restrictive. TemplateFileStream::setVars method should be introduced that accepts HashMap - HttpRequest::getRequestMethod() is removed. Use HttpRequest::method instead. - CommandExecutor with WebsocketConnection won't work. --- .gitmodules | 8 + Sming/Makefile | 41 +- .../CommandProcessing/CommandExecutor.cpp | 2 +- .../CommandProcessing/CommandExecutor.h | 2 +- .../CommandProcessing/CommandOutput.cpp | 2 +- .../CommandProcessing/CommandOutput.h | 6 +- Sming/SmingCore/DataSourceStream.cpp | 27 +- Sming/SmingCore/DataSourceStream.h | 28 +- Sming/SmingCore/Network/Http/HttpCommon.h | 68 + .../SmingCore/Network/Http/HttpConnection.cpp | 565 +++ Sming/SmingCore/Network/Http/HttpConnection.h | 118 + Sming/SmingCore/Network/Http/HttpRequest.cpp | 266 + Sming/SmingCore/Network/Http/HttpRequest.h | 161 + .../Network/Http/HttpRequestAuth.cpp | 77 + .../SmingCore/Network/Http/HttpRequestAuth.h | 56 + Sming/SmingCore/Network/Http/HttpResource.cpp | 24 + Sming/SmingCore/Network/Http/HttpResource.h | 55 + Sming/SmingCore/Network/Http/HttpResponse.cpp | 182 + Sming/SmingCore/Network/Http/HttpResponse.h | 90 + .../Network/Http/HttpServerConnection.cpp | 411 ++ .../Network/Http/HttpServerConnection.h | 96 + .../Http/Websocket/WebSocketConnection.cpp | 232 + .../Http/Websocket/WebSocketConnection.h | 97 + .../Http/Websocket/WebsocketResource.cpp | 66 + .../Http/Websocket/WebsocketResource.h | 35 + .../Http/Websocket/WsCommandHandlerResource.h | 52 + Sming/SmingCore/Network/HttpClient.cpp | 314 +- Sming/SmingCore/Network/HttpClient.h | 118 +- Sming/SmingCore/Network/HttpRequest.cpp | 267 - Sming/SmingCore/Network/HttpRequest.h | 69 - Sming/SmingCore/Network/HttpResponse.cpp | 257 - Sming/SmingCore/Network/HttpResponse.h | 76 - Sming/SmingCore/Network/HttpServer.cpp | 241 +- Sming/SmingCore/Network/HttpServer.h | 90 +- .../Network/HttpServerConnection.cpp | 177 - .../SmingCore/Network/HttpServerConnection.h | 59 - Sming/SmingCore/Network/WebConstants.h | 104 +- Sming/SmingCore/Network/WebSocket.cpp | 91 - Sming/SmingCore/Network/WebSocket.h | 91 - Sming/SmingCore/Network/rBootHttpUpdate.cpp | 205 +- Sming/SmingCore/Network/rBootHttpUpdate.h | 58 +- Sming/SmingCore/OutputStream.cpp | 35 + Sming/SmingCore/OutputStream.h | 41 + Sming/SmingCore/SmingCore.h | 4 +- Sming/Wiring/FIFO.h | 2 +- Sming/third-party/.patches/http-parser.patch | 4406 +++++++++++++++++ Sming/third-party/.patches/ws_parser.patch | 14 + Sming/third-party/http-parser | 1 + Sming/third-party/ws_parser | 1 + samples/Arducam/app/application.cpp | 8 +- samples/Basic_Ssl/app/application.cpp | 34 +- samples/Basic_WebClient/.cproject | 44 + samples/Basic_WebClient/.project | 28 + samples/Basic_WebClient/Makefile | 24 + samples/Basic_WebClient/Makefile-user.mk | 51 + samples/Basic_WebClient/README.md | 43 + samples/Basic_WebClient/app/application.cpp | 183 + samples/Basic_WebClient/include/ssl/cert.h | 43 + .../Basic_WebClient/include/ssl/private_key.h | 54 + samples/Basic_WebClient/include/user_config.h | 50 + .../Basic_WebSkeletonApp/app/webserver.cpp | 10 +- .../app/application.cpp | 28 +- samples/DNSCaptivePortal/app/application.cpp | 2 +- .../HttpClient_Instapush/app/application.cpp | 20 +- .../HttpClient_ThingSpeak/app/application.cpp | 6 +- samples/HttpServer_AJAX/app/application.cpp | 4 +- .../HttpServer_Bootstrap/app/application.cpp | 29 +- .../app/application.cpp | 6 +- .../HttpServer_WebSockets/app/CUserData.cpp | 6 +- .../HttpServer_WebSockets/app/application.cpp | 32 +- .../HttpServer_WebSockets/include/CUserData.h | 8 +- samples/MeteoControl/app/application.cpp | 8 +- samples/MeteoControl/app/webserver.cpp | 44 +- 73 files changed, 8321 insertions(+), 1932 deletions(-) create mode 100644 Sming/SmingCore/Network/Http/HttpCommon.h create mode 100644 Sming/SmingCore/Network/Http/HttpConnection.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpConnection.h create mode 100644 Sming/SmingCore/Network/Http/HttpRequest.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpRequest.h create mode 100644 Sming/SmingCore/Network/Http/HttpRequestAuth.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpRequestAuth.h create mode 100644 Sming/SmingCore/Network/Http/HttpResource.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpResource.h create mode 100644 Sming/SmingCore/Network/Http/HttpResponse.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpResponse.h create mode 100644 Sming/SmingCore/Network/Http/HttpServerConnection.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpServerConnection.h create mode 100644 Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp create mode 100644 Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h create mode 100644 Sming/SmingCore/Network/Http/Websocket/WebsocketResource.cpp create mode 100644 Sming/SmingCore/Network/Http/Websocket/WebsocketResource.h create mode 100644 Sming/SmingCore/Network/Http/Websocket/WsCommandHandlerResource.h delete mode 100644 Sming/SmingCore/Network/HttpRequest.cpp delete mode 100644 Sming/SmingCore/Network/HttpRequest.h delete mode 100644 Sming/SmingCore/Network/HttpResponse.cpp delete mode 100644 Sming/SmingCore/Network/HttpResponse.h delete mode 100644 Sming/SmingCore/Network/HttpServerConnection.cpp delete mode 100644 Sming/SmingCore/Network/HttpServerConnection.h delete mode 100644 Sming/SmingCore/Network/WebSocket.cpp delete mode 100644 Sming/SmingCore/Network/WebSocket.h create mode 100644 Sming/SmingCore/OutputStream.cpp create mode 100644 Sming/SmingCore/OutputStream.h create mode 100644 Sming/third-party/.patches/http-parser.patch create mode 100644 Sming/third-party/.patches/ws_parser.patch create mode 160000 Sming/third-party/http-parser create mode 160000 Sming/third-party/ws_parser create mode 100644 samples/Basic_WebClient/.cproject create mode 100644 samples/Basic_WebClient/.project create mode 100644 samples/Basic_WebClient/Makefile create mode 100644 samples/Basic_WebClient/Makefile-user.mk create mode 100644 samples/Basic_WebClient/README.md create mode 100644 samples/Basic_WebClient/app/application.cpp create mode 100644 samples/Basic_WebClient/include/ssl/cert.h create mode 100644 samples/Basic_WebClient/include/ssl/private_key.h create mode 100644 samples/Basic_WebClient/include/user_config.h diff --git a/.gitmodules b/.gitmodules index 890a61d9b1..f5a9300542 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,11 @@ path = Sming/third-party/esp-open-lwip url = https://github.com/pfalcon/esp-open-lwip.git ignore = dirty +[submodule "Sming/third-party/http-parser"] + path = Sming/third-party/http-parser + url = https://github.com/nodejs/http-parser.git + ignore = dirty +[submodule "Sming/third-party/ws_parser"] + path = Sming/third-party/ws_parser + url = https://github.com/charliesome/ws_parser.git + ignore = dirty diff --git a/Sming/Makefile b/Sming/Makefile index dd08eff8fb..4fbf170496 100644 --- a/Sming/Makefile +++ b/Sming/Makefile @@ -99,6 +99,15 @@ export COMPILE := gcc export PATH := $(ESP_HOME)/xtensa-lx106-elf/bin:$(PATH) XTENSA_TOOLS_ROOT := $(ESP_HOME)/xtensa-lx106-elf/bin +# select which tools to use as compiler, librarian and linker +AS := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc +CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc +CXX := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-g++ +AR := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-ar +LD := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc +OBJCOPY := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objcopy +OBJDUMP := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objdump + ## COM port parameters # Default COM port speed (generic) COM_SPEED ?= 115200 @@ -143,7 +152,7 @@ TARGET = app CUSTOM_TARGETS ?= # which modules (subdirectories) of the project to include in compiling -MODULES = system system/helpers Wiring SmingCore appinit $(filter %/, $(wildcard SmingCore/*/)) $(filter %/, $(wildcard Services/*/)) $(filter %/, $(wildcard Libraries/*/)) +MODULES = system system/helpers Wiring appinit $(shell find SmingCore -type d) $(filter %/, $(wildcard Services/*/)) $(filter %/, $(wildcard Libraries/*/)) EXTRA_INCDIR = include system/include Wiring Libraries SmingCore $(SDK_BASE)/../include # Place a file that should exist in a submodule that is fetched separately @@ -156,6 +165,16 @@ THIRD_PARTY_DATA += third-party/spiffs/makefile MODULES += third-party/spiffs/src EXTRA_INCDIR += third-party/spiffs/src +# => http-parser +THIRD_PARTY_DATA += third-party/http-parser/Makefile +MODULES += third-party/http-parser/ +EXTRA_INCDIR += third-party/http-parser/ + +# => webscoket-parser +THIRD_PARTY_DATA += third-party/ws_parser/Makefile +MODULES += third-party/ws_parser/ +EXTRA_INCDIR += third-party/ws_parser/ + # => esp-gdbstub ifeq ($(ENABLE_GDB), 1) THIRD_PARTY_DATA += third-party/esp-gdbstub/Makefile @@ -201,6 +220,8 @@ ifeq ($(ENABLE_CUSTOM_PWM), 1) THIRD_PARTY_DATA += third-party/pwm/pwm.c endif +MFORCE32 := $(shell $(CC) --help=target | grep mforce-l32) + # compiler flags using during compilation of source files. Add '-pg' for debugging CFLAGS = -Wpointer-arith -Wundef -Werror -Wl,-EL -nostdlib -mlongcalls -mtext-section-literals -finline-functions -fdata-sections -ffunction-sections -D__ets__ -DICACHE_FLASH -DARDUINO=106 -DCOM_SPEED_SERIAL=$(COM_SPEED_SERIAL) -DENABLE_CMD_EXECUTOR=$(ENABLE_CMD_EXECUTOR) ifeq ($(SMING_RELEASE),1) @@ -212,7 +233,14 @@ else ifeq ($(ENABLE_GDB), 1) else CFLAGS += -Os -g endif - +ifneq ($(MFORCE32),) + # Your compiler supports the -mforce-l32 flag which means that + # constants can be stored in flash (program) memory instead of SRAM. + # See: https://www.arduino.cc/en/Reference/PROGMEM + CFLAGS += -DPROGMEM_L32="__attribute__((aligned(4))) __attribute__((section(\".irom.text\")))" -mforce-l32 +else + CFLAGS += -DPROGMEM_L32="" +endif #Append debug options CFLAGS += -DCUST_FILE_BASE=$$* -DDEBUG_VERBOSE_LEVEL=$(DEBUG_VERBOSE_LEVEL) -DDEBUG_PRINT_FILENAME_AND_LINE=$(DEBUG_PRINT_FILENAME_AND_LINE) @@ -246,15 +274,6 @@ ifeq ($(ENABLE_SSL),1) CXXFLAGS += $(AXTLS_FLAGS) endif -# select which tools to use as compiler, librarian and linker -AS := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc -CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc -CXX := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-g++ -AR := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-ar -LD := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc -OBJCOPY := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objcopy -OBJDUMP := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objdump - SRC_DIR := $(MODULES) BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) diff --git a/Sming/Services/CommandProcessing/CommandExecutor.cpp b/Sming/Services/CommandProcessing/CommandExecutor.cpp index d52ae09dd9..4b7271830e 100644 --- a/Sming/Services/CommandProcessing/CommandExecutor.cpp +++ b/Sming/Services/CommandProcessing/CommandExecutor.cpp @@ -31,7 +31,7 @@ CommandExecutor::CommandExecutor(Stream* reqStream) : CommandExecutor() } } -CommandExecutor::CommandExecutor(WebSocket* reqSocket) +CommandExecutor::CommandExecutor(WebSocketConnection* reqSocket) { commandOutput = new CommandOutput(reqSocket); if (commandHandler.getVerboseMode() != SILENT) diff --git a/Sming/Services/CommandProcessing/CommandExecutor.h b/Sming/Services/CommandProcessing/CommandExecutor.h index 067ba373c1..b7dcd568f9 100644 --- a/Sming/Services/CommandProcessing/CommandExecutor.h +++ b/Sming/Services/CommandProcessing/CommandExecutor.h @@ -20,7 +20,7 @@ class CommandExecutor public: CommandExecutor(TcpClient* cmdClient); CommandExecutor(Stream* reqStream); - CommandExecutor(WebSocket* reqSocket); + CommandExecutor(WebSocketConnection* reqSocket); ~CommandExecutor(); int executorReceive(char *recvData, int recvSize); diff --git a/Sming/Services/CommandProcessing/CommandOutput.cpp b/Sming/Services/CommandProcessing/CommandOutput.cpp index 9c341131b4..77a5dd244b 100644 --- a/Sming/Services/CommandProcessing/CommandOutput.cpp +++ b/Sming/Services/CommandProcessing/CommandOutput.cpp @@ -17,7 +17,7 @@ CommandOutput::CommandOutput(Stream* reqStream) { } -CommandOutput::CommandOutput(WebSocket* reqSocket) +CommandOutput::CommandOutput(WebSocketConnection* reqSocket) : outputSocket(reqSocket) { } diff --git a/Sming/Services/CommandProcessing/CommandOutput.h b/Sming/Services/CommandProcessing/CommandOutput.h index 864d79f6be..c080362e1c 100644 --- a/Sming/Services/CommandProcessing/CommandOutput.h +++ b/Sming/Services/CommandProcessing/CommandOutput.h @@ -12,21 +12,21 @@ #include "Stream.h" #include "Print.h" #include "WiringFrameworkDependencies.h" -#include "Network/WebSocket.h" +#include "Network/Http/Websocket/WebSocketConnection.h" class CommandOutput: public Print { public: CommandOutput(TcpClient* reqClient); CommandOutput(Stream* reqStream); - CommandOutput(WebSocket* reqSocket); + CommandOutput(WebSocketConnection* reqSocket); virtual ~CommandOutput(); size_t write(uint8_t outChar); TcpClient* outputTcpClient = nullptr; Stream* outputStream = nullptr; - WebSocket* outputSocket = nullptr; + WebSocketConnection* outputSocket = nullptr; String tempSocket = ""; }; diff --git a/Sming/SmingCore/DataSourceStream.cpp b/Sming/SmingCore/DataSourceStream.cpp index 05f6164404..0d2eefc807 100644 --- a/Sming/SmingCore/DataSourceStream.cpp +++ b/Sming/SmingCore/DataSourceStream.cpp @@ -6,9 +6,6 @@ ****/ #include "../SmingCore/DataSourceStream.h" -#include "../SmingCore/Network/TcpConnection.h" -#include "../SmingCore/Network/HttpRequest.h" -#include "WiringFrameworkDependencies.h" MemoryDataStream::MemoryDataStream() { @@ -306,13 +303,14 @@ void TemplateFileStream::setVar(String name, String value) templateData[name] = value; } -void TemplateFileStream::setVarsFromRequest(const HttpRequest& request) -{ - if (request.requestGetParameters != NULL) - templateData.setMultiple(*request.requestGetParameters); - if (request.requestPostParameters != NULL) - templateData.setMultiple(*request.requestPostParameters); -} +// TODO: Remove that dependency from here ... +//void TemplateFileStream::setVarsFromRequest(const HttpRequest& request) +//{ +// if (request.requestGetParameters != NULL) +// templateData.setMultiple(*request.requestGetParameters); +// if (request.requestPostParameters != NULL) +// templateData.setMultiple(*request.requestPostParameters); +//} /////////////////////////////////////////////////////////////////////////// @@ -340,3 +338,12 @@ uint16_t JsonObjectStream::readMemoryBlock(char* data, int bufSize) return MemoryDataStream::readMemoryBlock(data, bufSize); } + +int JsonObjectStream::length() +{ + if (rootNode != JsonObject::invalid()) { + return -1; + } + + return rootNode.measureLength(); +} diff --git a/Sming/SmingCore/DataSourceStream.h b/Sming/SmingCore/DataSourceStream.h index 4616b064b7..862b36f51f 100644 --- a/Sming/SmingCore/DataSourceStream.h +++ b/Sming/SmingCore/DataSourceStream.h @@ -75,6 +75,12 @@ class IDataSourceStream * @retval bool True on success. */ virtual bool isFinished() = 0; + + /** + * @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + virtual int length() { return -1; } }; /// Memory data stream class @@ -96,9 +102,17 @@ class MemoryDataStream : public Print, public IDataSourceStream /** @brief Get size of stream * @retval int Quantity of chars in stream + * + * @deprecated Use getLength instead */ int getStreamLength() { return size; } + /** + * @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + int length() { return size; } + /** @brief Write a single char to stream * @param charToWrite Char to write to the stream * @retval size_t Quantity of chars written to stream (always 1) @@ -136,7 +150,7 @@ class FileStream : public IDataSourceStream /** @brief Create a file stream * @param fileName Name of file to open */ - FileStream(); + FileStream(); FileStream(String fileName); virtual ~FileStream(); @@ -164,6 +178,12 @@ class FileStream : public IDataSourceStream */ inline int getPos() { return pos; } + /** + * @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + int length() { return size; } + private: file_t handle; int pos; @@ -253,6 +273,12 @@ class JsonObjectStream : public MemoryDataStream //Use base class documentation virtual uint16_t readMemoryBlock(char* data, int bufSize); + /** + * @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + int length(); + private: DynamicJsonBuffer buffer; JsonObject &rootNode; diff --git a/Sming/SmingCore/Network/Http/HttpCommon.h b/Sming/SmingCore/Network/Http/HttpCommon.h new file mode 100644 index 0000000000..96bfc2a8d5 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpCommon.h @@ -0,0 +1,68 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpServerResource + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_COMMON_H_ +#define _SMING_CORE_HTTP_COMMON_H_ + +#define ENABLE_HTTP_REQUEST_AUTH 1 + +#include "../../Wiring/WString.h" +#include "../../Wiring/WHashMap.h" +#include "../../Delegate.h" +#include "../../Wiring/FILO.h" +#include "../WebConstants.h" +#include "../URL.h" + +#ifndef HTTP_MAX_HEADER_SIZE +#define HTTP_MAX_HEADER_SIZE (8*1024) +#endif + +/* Number of maximum tcp connections to be kept in the pool */ +#ifndef HTTP_REQUEST_POOL_SIZE +#define HTTP_REQUEST_POOL_SIZE 20 +#endif + +#include "../http-parser/http_parser.h" + +/** + * WARNING: For the moment the name "SimpleConcurrentQueue" is very misleading. + */ +template +class SimpleConcurrentQueue: public FIFO { +public: + virtual const T& operator[](unsigned int) const { } + virtual T& operator[](unsigned int) { } + + T peek() const + { + if(!FIFO::numberOfElements) { + return NULL; + } + + return FIFO::peek(); + } + + T dequeue() + { + if(!FIFO::numberOfElements) { + return NULL; + } + + return FIFO::dequeue(); + } +}; + +typedef HashMap HttpParams; +typedef HashMap HttpHeaders; +typedef enum http_method HttpMethod; + +#endif /* _SMING_CORE_HTTP_COMMON_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpConnection.cpp b/Sming/SmingCore/Network/Http/HttpConnection.cpp new file mode 100644 index 0000000000..8ef0b7463f --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpConnection.cpp @@ -0,0 +1,565 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpConnection + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpConnection.h" + +#include "../../Services/WebHelpers/escape.h" + +#ifdef __linux__ +#include "lwip/priv/tcp_priv.h" +#else +#include "lwip/tcp_impl.h" +#endif + +HttpConnection::HttpConnection(RequestQueue* queue): TcpClient(false), mode(eHCM_String) { + this->waitingQueue = queue; +} + +bool HttpConnection::connect(const String& host, int port, bool useSsl /* = false */, uint32_t sslOptions /* = 0 */) { + + debugf("HttpConnection::connect: TCP state: %d, isStarted: %d, isActive: %d", (tcp != NULL? tcp->state : -1), (int)(getConnectionState() != eTCS_Ready), (int)isActive()); + + if(isProcessing()) { + return true; + } + + if(getConnectionState() != eTCS_Ready && isActive()) { + debugf("HttpConnection::reusing TCP connection "); + + // we might have still alive connection + onConnected(ERR_OK); + return true; + } + + debugf("HttpConnection::connecting ..."); + + return TcpClient::connect(host, port, useSsl, sslOptions); +} + +bool HttpConnection::isActive() { + if(tcp == NULL) { + return false; + } + + struct tcp_pcb *pcb; + for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { + if(tcp == pcb) { + return true; + } + } + + return false; +} + +// @deprecated +HashMap &HttpConnection::getResponseHeaders() +{ + return responseHeaders; +} + +String HttpConnection::getResponseHeader(String headerName, String defaultValue /* = "" */) +{ + if (responseHeaders.contains(headerName)) + return responseHeaders[headerName]; + + return defaultValue; +} + +DateTime HttpConnection::getLastModifiedDate() +{ + DateTime res; + String strLM = getResponseHeader("Last-Modified"); + if (res.parseHttpDate(strLM)) + return res; + else + return DateTime(); +} + +DateTime HttpConnection::getServerDate() +{ + DateTime res; + String strSD = getResponseHeader("Date"); + if (res.parseHttpDate(strSD)) + return res; + else + return DateTime(); +} + +String HttpConnection::getResponseString() +{ + if (mode == eHCM_String) + return responseStringData; + else + return ""; +} + +// @enddeprecated + +void HttpConnection::reset() +{ + if(currentRequest != NULL) { + delete currentRequest; + currentRequest = NULL; + } + + code = 0; + responseStringData = ""; + responseHeaders.clear(); + + lastWasValue = true; + lastData = ""; + currentField = ""; +} + + +err_t HttpConnection::onProtocolUpgrade(http_parser* parser) +{ + debugf("onProtocolUpgrade: Protocol upgrade is not supported"); + return ERR_ABRT; +} + +int HttpConnection::staticOnMessageBegin(http_parser* parser) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + connection->reset(); + + connection->currentRequest = connection->executionQueue.dequeue(); + if(connection->currentRequest == NULL) { + return 1; // there are no requests in the queue + } + + if(connection->currentRequest->responseStream != NULL) { + connection->mode = eHCM_Stream; + } + else { + connection->mode = eHCM_String; + } + + return 0; +} + +int HttpConnection::staticOnMessageComplete(http_parser* parser) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + if(!connection->currentRequest) { + return -2; // no current request... + } + + debugf("staticOnMessageComplete: Execution queue: %d, %s", + connection->executionQueue.count(), + connection->currentRequest->uri.toString().c_str() + ); + + // we are finished with this request + int hasError = 0; + if(connection->currentRequest->requestCompletedDelegate) { + bool success = (HTTP_PARSER_ERRNO(parser) == HPE_OK) && // false when the parsing has failed + (connection->code >= 200 && connection->code <= 399); // false when the HTTP status code is not ok + hasError = connection->currentRequest->requestCompletedDelegate(*connection, success); + } + + if(connection->currentRequest->auth != NULL) { + connection->currentRequest->auth->setResponse(connection->getResponse()); + } + + if(connection->currentRequest->retries > 0) { + connection->currentRequest->retries--; + return (connection->executionQueue.enqueue(connection->currentRequest)? 0: -1); + } + + if(connection->currentRequest->responseStream != NULL) { + connection->currentRequest->responseStream->close(); + delete connection->currentRequest->responseStream; + } + + delete connection->currentRequest; + connection->currentRequest = NULL; + + if(!connection->executionQueue.count()) { + connection->onConnected(ERR_OK); + } + + return hasError; +} + +int HttpConnection::staticOnHeadersComplete(http_parser* parser) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + debugf("The headers are complete"); + + /* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + */ + + connection->code = parser->status_code; + if(connection->currentRequest == NULL) { + // nothing to process right now... + return 1; + } + + int error = 0; + if(connection->currentRequest->headersCompletedDelegate) { + error = connection->currentRequest->headersCompletedDelegate(*connection, connection->responseHeaders); + } + + if(!error && connection->currentRequest->method == HTTP_HEAD) { + error = 1; + } + + return error; +} + +int HttpConnection::staticOnStatus(http_parser *parser, const char *at, size_t length) { + return 0; +} + +int HttpConnection::staticOnHeaderField(http_parser *parser, const char *at, size_t length) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + if(connection->lastWasValue) { + // we are starting to process new header + connection->lastData = ""; + connection->lastWasValue = false; + } + connection->lastData += String(at, length); + + return 0; +} + +int HttpConnection::staticOnHeaderValue(http_parser *parser, const char *at, size_t length) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if (connection == NULL) { + // something went wrong + return -1; + } + + if(!connection->lastWasValue) { + connection->currentField = connection->lastData; + connection->responseHeaders[connection->currentField] = ""; + connection->lastWasValue = true; + } + connection->responseHeaders[connection->currentField] += String(at, length); + + return 0; +} + +int HttpConnection::staticOnBody(http_parser *parser, const char *at, size_t length) +{ + HttpConnection *connection = (HttpConnection*)parser->data; + if (connection == NULL) { + // something went wrong + return -1; + } + + if(connection->currentRequest->requestBodyDelegate) { + return connection->currentRequest->requestBodyDelegate(*connection, at, length); + } + + if (connection->mode == eHCM_String) { + connection->responseStringData += String(at, length); + return 0; + } + + if(connection->currentRequest->responseStream != NULL) { + int res = connection->currentRequest->responseStream->write((const uint8_t *)at, length); + if (res != length) { + connection->currentRequest->responseStream->close(); + return 1; + } + } + + return 0; +} + +int HttpConnection::staticOnChunkHeader(http_parser* parser) { + debugf("On chunk header"); + return 0; +} + +int HttpConnection::staticOnChunkComplete(http_parser* parser) { + debugf("On chunk complete"); + return 0; +} + +err_t HttpConnection::onConnected(err_t err) { + if (err == ERR_OK) { + // create parser ... + if(parser == NULL) { + parser = new http_parser; + http_parser_init(parser, HTTP_RESPONSE); + parser->data = (void*)this; + + memset(&parserSettings, 0, sizeof(parserSettings)); + // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. + parserSettings.on_message_begin = staticOnMessageBegin; + parserSettings.on_headers_complete = staticOnHeadersComplete; + parserSettings.on_message_complete = staticOnMessageComplete; + + parserSettings.on_chunk_header = staticOnChunkHeader; + parserSettings.on_chunk_complete = staticOnChunkComplete; + + + // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; + parserSettings.on_status = staticOnStatus; + parserSettings.on_header_field = staticOnHeaderField; + parserSettings.on_header_value = staticOnHeaderValue; + parserSettings.on_body = staticOnBody; + } + + debugf("HttpConnection::onConnected: waitingQueue.count: %d", waitingQueue->count()); + + do { + HttpRequest* request = waitingQueue->peek(); + if(request == NULL) { + break; + } + + if(!executionQueue.enqueue(request)) { + debugf("The working queue is full at the moment"); + break; + } + + waitingQueue->dequeue(); + send(request); + + if(!(request->method == HTTP_GET || request->method == HTTP_HEAD)) { + // if the current request cannot be pipelined -> break; + break; + } + + HttpRequest* nextRequest = waitingQueue->peek(); + if(nextRequest != NULL && !(nextRequest->method == HTTP_GET || nextRequest->method == HTTP_HEAD)) { + // if the next request cannot be pipelined -> break for now + break; + } + } while(1); + } + + TcpClient::onConnected(err); + return ERR_OK; +} + +void HttpConnection::send(HttpRequest* request) { + sendString(http_method_str(request->method) + String(" ") + request->uri.getPathWithQuery() + " HTTP/1.1\r\nHost: " + request->uri.Host + "\r\n"); + + // Adjust the content-length + request->headers["Content-Length"] = "0"; + if(request->rawDataLength) { + request->headers["Content-Length"] = String(request->rawDataLength); + } + else if (request->stream != NULL && request->stream->length() > -1) { + request->headers["Content-Length"] = String(request->stream->length()); + } + + // TODO: represent the post params as stream ... + + + if(!request->headers.contains("Content-Length")) { + request->headers["Transfer-Encoding"] = "chunked"; + } + + if(request->postParams.count() && !request->headers.contains("Content-Type")) { + request->headers["Content-Type"] = ContentType::toString(MIME_FORM_URL_ENCODED); + } + + for (int i = 0; i < request->headers.count(); i++) + { + String write = request->headers.keyAt(i) + ": " + request->headers.valueAt(i) + "\r\n"; + sendString(write.c_str()); + } + sendString("\r\n"); + + // Send content + + // if there is input raw data -> send it + if(request->rawDataLength > 0) { + TcpClient::send((const char*)request->rawData, (uint16_t)request->rawDataLength); + } + else if(request->stream != NULL) { + send(request->stream); + + debugf("Stream completed"); + delete request->stream; + request->stream = NULL; + } +#if 0 + + // Post Params should be also stream... + + else if (request->postParams.count()) { + for(int i = 0; i < request->postParams.count(); i++) { + // TODO: prevent memory fragmentation ... + char *dest = uri_escape(NULL, 0, request->postParams.valueAt(i).c_str(), request->postParams.valueAt(i).length()); + String write = request->postParams.keyAt(i) + "=" + String(dest) + "&"; + sendString(write.c_str()); + free(dest); + } + } +#endif +} + +bool HttpConnection::send(IDataSourceStream* inputStream, bool forceCloseAfterSent /* = false*/) +{ + if(inputStream->length() != -1) { + // send the data as one big blob + do { + int len = 256; + char data[len]; + len = inputStream->readMemoryBlock(data, len); + TcpClient::send(data, len); + inputStream->seek(max(len, 0)); + } while(!inputStream->isFinished()); + + return true; + } + + // Send the data in chunked-encoding + + do { + int len = 256; + char data[len]; + len = inputStream->readMemoryBlock(data, len); + + // send the data in chunks... + sendString(String(len)+ "\r\n"); + TcpClient::send(data, len); + sendString("\n\r"); + inputStream->seek(max(len, 0)); + } while(!inputStream->isFinished()); + + sendString("0\r\n\r\n", forceCloseAfterSent); + + return true; +} + +HttpRequest* HttpConnection::getRequest() { + return currentRequest; +} + +HttpResponse* HttpConnection::getResponse() { + HttpResponse* response = new HttpResponse(); + response->code = code; + response->headers = responseHeaders; +// TODO: fix this... +// if(currentRequest) { +// response->stream = currentRequest->outputStream; +// } + + if(responseStringData.length()) { + if(response->stream != NULL) { + delete response->stream; + response->stream = NULL; + } + + MemoryDataStream* memory = new MemoryDataStream(); + memory->write((uint8_t *)responseStringData.c_str(), responseStringData.length()); + response->stream = (IDataSourceStream* )memory; + } + return response; +} + +// end of public methods for HttpConnection + +err_t HttpConnection::onReceive(pbuf *buf) { + if (buf == NULL) + { + // Disconnected, close it + return TcpClient::onReceive(buf); + } + + pbuf *cur = buf; + int parsedBytes = 0; + while (cur != NULL && cur->len > 0) { + parsedBytes += http_parser_execute(parser, &parserSettings, (char*) cur->payload, cur->len); + if(HTTP_PARSER_ERRNO(parser) != HPE_OK) { + // we ran into trouble - abort the connection + debugf("HTTP parser error: %s", http_errno_name(HTTP_PARSER_ERRNO(parser))); + cleanup(); + TcpConnection::onReceive(NULL); + return ERR_ABRT; + } + + cur = cur->next; + } + + if (parser->upgrade) { + return onProtocolUpgrade(parser); + } else if (parsedBytes != buf->tot_len) { + TcpClient::onReceive(NULL); + + return ERR_ABRT; + } + + // Fire ReadyToSend callback + TcpClient::onReceive(buf); + + return ERR_OK; +} + +void HttpConnection::onError(err_t err) { + cleanup(); + TcpClient::onError(err); +} + +void HttpConnection::cleanup() { + // TODO: clean the current request + reset(); + + // TODO: clean the current response + + // if there are requests in the executionQueue -> move them back to the waiting queue + for(int i=0; i < executionQueue.count(); i++) { + waitingQueue->enqueue(executionQueue.dequeue()); + } + + if(parser != NULL) { + delete parser; + parser = NULL; + } +} + +HttpConnection::~HttpConnection() { + cleanup(); +} + diff --git a/Sming/SmingCore/Network/Http/HttpConnection.h b/Sming/SmingCore/Network/Http/HttpConnection.h new file mode 100644 index 0000000000..99c29b85b5 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpConnection.h @@ -0,0 +1,118 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpConnection + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_CONNECTION_H_ +#define _SMING_CORE_HTTP_CONNECTION_H_ + +#include "HttpCommon.h" +#include "HttpResponse.h" +#include "HttpRequest.h" +#include "../TcpClient.h" +#include "../../DataSourceStream.h" +#include "../../Services/DateTime/DateTime.h" + +typedef SimpleConcurrentQueue RequestQueue; + +enum HttpClientMode +{ + eHCM_String = 0, + eHCM_File, // << Deprecated! Use eHCM_Stream stream instead + eHCM_Stream, + eHCM_UserDefined // << Deprecated! If you supply onBody callback then the incoming body will be processed from the callback directly +}; + +class HttpConnection : protected TcpClient { + friend class HttpClient; + +public: + HttpConnection(RequestQueue* queue); + ~HttpConnection(); + + bool connect(const String& host, int port, bool useSsl = false, uint32_t sslOptions = 0); + + void send(HttpRequest* request); + + bool isActive(); + + /** + * @brief Returns pointer to the current request + * @return HttpRequest* + */ + HttpRequest* getRequest(); + + /** + * @brief Returns pointer to the current response + * @return HttpResponse* + */ + HttpResponse* getResponse(); + + using TcpClient::close; + +#ifdef ENABLE_SSL + using TcpClient::getSsl; +#endif + + // Backported for compatibility reasons + // @deprecated + __forceinline int getResponseCode() { return code; } + String getResponseHeader(String headerName, String defaultValue = ""); + HttpHeaders &getResponseHeaders(); + DateTime getLastModifiedDate(); // Last-Modified header + DateTime getServerDate(); // Date header + + String getResponseString(); + // @enddeprecated + + + +protected: + void reset(); + + virtual err_t onConnected(err_t err); + virtual err_t onReceive(pbuf *buf); + virtual err_t onProtocolUpgrade(http_parser* parser); + + virtual void onError(err_t err); + + bool send(IDataSourceStream* inputStream, bool forceCloseAfterSent = false); + + void cleanup(); + +private: + static int IRAM_ATTR staticOnMessageBegin(http_parser* parser); + static int IRAM_ATTR staticOnStatus(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnHeadersComplete(http_parser* parser); + static int IRAM_ATTR staticOnHeaderField(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnHeaderValue(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnBody(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnChunkHeader(http_parser* parser); + static int IRAM_ATTR staticOnChunkComplete(http_parser* parser); + static int IRAM_ATTR staticOnMessageComplete(http_parser* parser); + +protected: + HttpClientMode mode; + String responseStringData; + + RequestQueue* waitingQueue; + RequestQueue executionQueue; + http_parser *parser = NULL; + http_parser_settings parserSettings; + HttpHeaders responseHeaders; + + int code = 0; + bool lastWasValue = true; + String lastData = ""; + String currentField = ""; + HttpRequest* currentRequest = NULL; +}; + +#endif /* _SMING_CORE_HTTP_CONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpRequest.cpp b/Sming/SmingCore/Network/Http/HttpRequest.cpp new file mode 100644 index 0000000000..17ef222dfd --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpRequest.cpp @@ -0,0 +1,266 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpRequest + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpRequest.h" + +HttpRequest::HttpRequest(URL uri) { + this->uri = uri; +} + +HttpRequest::HttpRequest(const HttpRequest& value) { + *this = value; + method = value.method; + uri = value.uri; + if(value.headers.count()) { + setHeaders(value.headers); + } + headersCompletedDelegate = value.headersCompletedDelegate; + requestBodyDelegate = value.requestBodyDelegate; + requestCompletedDelegate = value.requestCompletedDelegate; + + rawData = value.rawData; + rawDataLength = value.rawDataLength; + + // Notice: We do not copy streams. + +#ifdef ENABLE_SSL + sslOptions = value.sslOptions; + sslFingerprint = value.sslFingerprint; + sslClientKeyCert = value.sslClientKeyCert; +#endif +} + +HttpRequest& HttpRequest::operator = (const HttpRequest& rhs) { + if (this == &rhs) return *this; + + // TODO: FIX this... +// if (rhs.buffer) copy(rhs.buffer, rhs.len); +// else invalidate(); + + return *this; +} + +HttpRequest::~HttpRequest() { + if(queryParams != NULL) { + delete queryParams; + } +} + +HttpRequest* HttpRequest::setURL(URL uri) { + this->uri = uri; + return this; +} + +HttpRequest* HttpRequest::setMethod(const HttpMethod method) +{ + this->method = method; + return this; +} + +HttpRequest* HttpRequest::setHeaders(const HttpHeaders& headers) { + for(int i=0; i < headers.count(); i++) { + this->headers[headers.keyAt(i)] = headers.valueAt(i); + } + return this; +} + +HttpRequest* HttpRequest::setHeader(const String& name, const String& value) { + this->headers[name] = value; // TODO: add here name and/or value escaping. + return this; +} + + +HttpRequest* HttpRequest::setPostParameters(const HttpParams& params) +{ + postParams = params; + return this; +} + +HttpRequest* HttpRequest::setPostParameter(const String& name, const String& value) +{ + postParams[name] = value; + return this; +} + +#ifdef ENABLE_HTTP_REQUEST_AUTH +HttpRequest* HttpRequest::setAuth(AuthAdapter *adapter) { + adapter->setRequest(this); + auth = adapter; + return this; +} +#endif + +String HttpRequest::getHeader(const String& name) { + if(!headers.contains(name)) { + return String(""); + } + + return headers[name]; +} + +String HttpRequest::getPostParameter(const String& name) { + if(!postParams.contains(name)) { + return String(""); + } + + return postParams[name]; +} + +String HttpRequest::getQueryParameter(const String& parameterName, const String& defaultValue /* = "" */) +{ + if(queryParams == NULL) { + queryParams = new HttpParams(); + if(!uri.Query.length()) { + return defaultValue; + } + + String query = uri.Query.substring(1); + Vector parts; + splitString(query, '&' , parts); + for(int i=0; i < parts.count(); i++) { + Vector pair; + int count = splitString(parts[i], '=' , pair); + if(count != 2) { + debugf("getQueryParameter: Missing = in query string: %s", parts[i].c_str()); + continue; + } + (*queryParams)[pair.at(0)] = pair.at(1); // TODO: name and value URI decoding... + } + } + + if(queryParams->contains(parameterName)) { + return (*queryParams)[parameterName]; + } + + return defaultValue; +} + +String HttpRequest::getBody() +{ + if(stream == NULL) { + return ""; + } + + String ret; + if(stream->length() != -1 && stream->getStreamType() == eSST_Memory) { + MemoryDataStream *memory = (MemoryDataStream *)stream; + char buf[1024]; + for(int i=0; i< stream->length(); i += 1024) { + int available = memory->readMemoryBlock(buf, 1024); + ret += String(buf, available); + if(available < 1024) { + break; + } + } + } + return ret; +} + +IDataSourceStream* HttpRequest::getBodyStream() +{ + return stream; +} + +HttpRequest* HttpRequest::setResponseStream(IOutputStream *stream) { + responseStream = stream; + return this; +} + +#ifdef ENABLE_SSL +HttpRequest* HttpRequest::setSslOptions(uint32_t sslOptions) { + this->sslOptions = sslOptions; + return this; +} + +uint32_t HttpRequest::getSslOptions() { + return sslOptions; +} + +HttpRequest* HttpRequest::pinCertificate(SSLFingerprints fingerprints) { + sslFingerprint = fingerprints; + return this; +} + +HttpRequest* HttpRequest::setSslClientKeyCert(SSLKeyCertPair clientKeyCert) { + this->sslClientKeyCert = clientKeyCert; + return this; +} + +#endif + +HttpRequest* HttpRequest::setBody(const String& body) { + if(stream != NULL) { + debugf("HttpRequest::setBody: Discarding already set stream!"); + delete stream; + stream = NULL; + } + + MemoryDataStream *memory = new MemoryDataStream(); + int written = memory->write((uint8_t *)body.c_str(), body.length()); + if(written < body.length()) { + debugf("HttpRequest::setBody: Unable to store the complete body"); + } + stream = (IDataSourceStream*)memory; + return this; +} + +HttpRequest* HttpRequest::setBody(uint8_t *rawData, size_t length) { + this->rawData = rawData; + this->rawDataLength = length; + return this; +} + +HttpRequest* HttpRequest::setBody(IDataSourceStream *stream) { + this->stream = stream; + return this; +} + +HttpRequest* HttpRequest::onBody(RequestBodyDelegate delegateFunction) { + requestBodyDelegate = delegateFunction; + return this; +} + +HttpRequest* HttpRequest::onHeadersComplete(RequestHeadersCompletedDelegate delegateFunction) { + this->headersCompletedDelegate = delegateFunction; + return this; +} + +HttpRequest* HttpRequest::onRequestComplete(RequestCompletedDelegate delegateFunction) { + this->requestCompletedDelegate = delegateFunction; + return this; +} + +#ifndef SMING_RELEASE +String HttpRequest::toString() { + String content = ""; +#ifdef ENABLE_SSL + content += "> SSL options: " + String(sslOptions) + "\n"; + content += "> SSL Cert Fingerprint Length: " + String((sslFingerprint.certSha1 == NULL)? 0: SHA1_SIZE) + "\n"; + content += "> SSL PK Fingerprint Length: " + String((sslFingerprint.pkSha256 == NULL)? 0: SHA256_SIZE) + "\n"; + content += "> SSL ClientCert Length: " + String(sslClientKeyCert.certificateLength) + "\n"; + content += "> SSL ClientCert PK Length: " + String(sslClientKeyCert.keyLength) + "\n"; + content += "\n"; +#endif + + content += http_method_str(method) + String(" ") + uri.getPathWithQuery() + " HTTP/1.1\n"; + content += "Host: " + uri.Host + ":" + uri.Port + "\n"; + for(int i=0; i< headers.count(); i++) { + content += headers.keyAt(i) + ": " + headers.valueAt(i) + "\n"; + } + + if(rawDataLength) { + content += "Content-Length: " + String(rawDataLength); + } + + return content; +} +#endif diff --git a/Sming/SmingCore/Network/Http/HttpRequest.h b/Sming/SmingCore/Network/Http/HttpRequest.h new file mode 100644 index 0000000000..32603b6faf --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpRequest.h @@ -0,0 +1,161 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpRequest + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_REQUEST_H_ +#define _SMING_CORE_HTTP_REQUEST_H_ + +#include "HttpCommon.h" +#ifdef ENABLE_HTTP_REQUEST_AUTH +#include "HttpRequestAuth.h" +#endif +#include "../TcpConnection.h" +#include "../../OutputStream.h" + +class HttpClient; +class HttpServerConnection; +class HttpConnection; + +typedef Delegate RequestHeadersCompletedDelegate; +typedef Delegate RequestBodyDelegate; +typedef Delegate RequestCompletedDelegate; + +class HttpRequest { + friend class HttpClient; + friend class HttpConnection; + friend class HttpServerConnection; + +public: + + HttpRequest(URL uri); + HttpRequest(const HttpRequest& value); + __forceinline HttpRequest* clone() const { return new HttpRequest(*this); } + HttpRequest& operator = (const HttpRequest& rhs); + ~HttpRequest(); + + HttpRequest* setURL(URL uri); + + HttpRequest* setMethod(const HttpMethod method); + + HttpRequest* setHeaders(const HttpHeaders& headers); + + HttpRequest* setHeader(const String& name, const String& value); + + HttpRequest* setPostParameters(const HttpParams& params); + HttpRequest* setPostParameter(const String& name, const String& value); + + +#ifdef ENABLE_HTTP_REQUEST_AUTH + // Authentication adapters set here + HttpRequest* setAuth(AuthAdapter *adapter); +#endif + + String getHeader(const String& name); + + String getPostParameter(const String& name); + + __forceinline String getPath() { + return uri.Path; + } + + String getQueryParameter(const String& parameterName, const String& defaultValue = ""); + + /** + * @brief Returns content from the body stream as string. + * @retval String + * + * @note This method consumes the stream and it will work only with text data. + * If you have binary data in the stream use getBodyStream instead. + */ + String getBody(); + + /** + * @brief Returns pointer to the current body stream + * @retval IDataSourceStream* + */ + IDataSourceStream* getBodyStream(); + +#ifdef ENABLE_SSL + HttpRequest* setSslOptions(uint32_t sslOptions); + uint32_t getSslOptions(); + + /** + * @brief Requires(pins) the remote SSL certificate to match certain fingerprints + * Check if SHA256 hash of Subject Public Key Info matches the one given. + * @param SSLFingerprints - passes the certificate fingerprints by reference. + * + * @return bool true of success, false or failure + */ + HttpRequest* pinCertificate(SSLFingerprints fingerprints); + + /** + * @brief Sets client private key, certificate and password from memory + * @param SSLKeyCertPair + * @param bool freeAfterHandshake + * + * @return HttpRequest pointer + */ + HttpRequest* setSslClientKeyCert(SSLKeyCertPair clientKeyCert); +#endif + + HttpRequest* setBody(const String& body); + HttpRequest* setBody(IDataSourceStream *stream); + HttpRequest* setBody(uint8_t *rawData, size_t length); + + HttpRequest* setResponseStream(IOutputStream *stream); + + HttpRequest* onHeadersComplete(RequestHeadersCompletedDelegate delegateFunction); + HttpRequest* onBody(RequestBodyDelegate delegateFunction); + HttpRequest* onRequestComplete(RequestCompletedDelegate delegateFunction); + +#ifndef SMING_RELEASE + /** + * @brief Tries to present a readable version of the current request values + * @return String + */ + String toString(); +#endif + +public: + URL uri; + HttpMethod method = HTTP_GET; + HttpHeaders headers; + + HttpParams postParams; + + int retries = 0; // how many times the request should be send again... + +protected: + RequestHeadersCompletedDelegate headersCompletedDelegate; + RequestBodyDelegate requestBodyDelegate; + RequestCompletedDelegate requestCompletedDelegate; + + uint8_t *rawData = NULL; + size_t rawDataLength = 0; + IDataSourceStream *stream = NULL; + + IOutputStream *responseStream = NULL; + +#ifdef ENABLE_HTTP_REQUEST_AUTH + AuthAdapter *auth = NULL; +#endif + +#ifdef ENABLE_SSL + uint32_t sslOptions = 0; + SSLFingerprints sslFingerprint; + SSLKeyCertPair sslClientKeyCert; +#endif + +private: + HttpParams* queryParams = NULL; // << deprecated +}; + +#endif /* _SMING_CORE_HTTP_REQUEST_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpRequestAuth.cpp b/Sming/SmingCore/Network/Http/HttpRequestAuth.cpp new file mode 100644 index 0000000000..ae65e371ba --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpRequestAuth.cpp @@ -0,0 +1,77 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpRequestAuth + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpRequestAuth.h" +#include "HttpRequest.h" +#include "../../Services/WebHelpers/base64.h" + +HttpBasicAuth::HttpBasicAuth(const String& username, const String& password) { + this->username = username; + this->password = password; +} + +// Basic Auth +void HttpBasicAuth::setRequest(HttpRequest* request) { + String clearText = username+":" + password; + int hashLength = clearText.length() * 4; + char hash[hashLength]; + base64_encode(clearText.length(), (const unsigned char *)clearText.c_str(), hashLength, hash); + + request->setHeader("Authorization", "Basic "+ String(hash)); +} + +// Digest Auth +HttpDigestAuth::HttpDigestAuth(const String& username, const String& password) { + this->username = username; + this->password = password; +} + +void HttpDigestAuth::setRequest(HttpRequest* request) { + this->request = request; +} + +void HttpDigestAuth::setResponse(HttpResponse *response) { + if(response->code != HTTP_STATUS_UNAUTHORIZED) { + return; + } + + if(response->headers.contains("WWW-Authenticate") && response->headers["WWW-Authenticate"].indexOf("Digest")!=-1) { + String authHeader = response->headers["WWW-Authenticate"]; + /* + * Example (see: https://tools.ietf.org/html/rfc2069#page-4): + * + * WWW-Authenticate: Digest realm="testrealm@host.com", + nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", + opaque="5ccc069c403ebaf9f0171e9517f40e41" + * + */ + + // TODO: process WWW-Authenticate header + + String authResponse = "Digest username=\"" + username + "\""; + /* + * Example (see: https://tools.ietf.org/html/rfc2069#page-4): + * + * Authorization: Digest username="Mufasa", + realm="testrealm@host.com", + nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", + uri="/dir/index.html", + response="e966c932a9242554e42c8ee200cec7f6", + opaque="5ccc069c403ebaf9f0171e9517f40e41" + */ + + // TODO: calculate the response... + request->setHeader("Authorization", authResponse); + request->retries = 1; + } +} + diff --git a/Sming/SmingCore/Network/Http/HttpRequestAuth.h b/Sming/SmingCore/Network/Http/HttpRequestAuth.h new file mode 100644 index 0000000000..0fba5afd82 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpRequestAuth.h @@ -0,0 +1,56 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpRequestAuth + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_REQUEST_AUTH_H_ +#define _SMING_CORE_HTTP_REQUEST_AUTH_H_ + +#include "HttpResponse.h" + +class HttpRequest; + +class AuthAdapter { +public: + virtual void setRequest(HttpRequest* request) = 0; + + __forceinline virtual void setResponse(HttpResponse *response) { + return; + } + + virtual ~AuthAdapter() {} +}; + + +class HttpBasicAuth: public AuthAdapter { +public: + HttpBasicAuth(const String& username, const String& password); + + void setRequest(HttpRequest* request); +private: + String username; + String password; +}; + +class HttpDigestAuth: public AuthAdapter { +public: + HttpDigestAuth(const String& username, const String& password); + + void setRequest(HttpRequest* request); + + void setResponse(HttpResponse *response); + +private: + String username; + String password; + HttpRequest* request=NULL; +}; + +#endif /* _SMING_CORE_HTTP_REQUEST_AUTH_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpResource.cpp b/Sming/SmingCore/Network/Http/HttpResource.cpp new file mode 100644 index 0000000000..c042189e37 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpResource.cpp @@ -0,0 +1,24 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpServerResource + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpResource.h" + +HttpCompatResource::HttpCompatResource(const HttpPathDelegate& callback) +{ + this->callback = callback; + onRequestComplete = HttpResourceDelegate(&HttpCompatResource::requestComplete, this); +} + +int HttpCompatResource::requestComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) { + callback(request, response); + return 0; +} diff --git a/Sming/SmingCore/Network/Http/HttpResource.h b/Sming/SmingCore/Network/Http/HttpResource.h new file mode 100644 index 0000000000..4dc6078906 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpResource.h @@ -0,0 +1,55 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpResource + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_RESOURCE_H_ +#define _SMING_CORE_HTTP_RESOURCE_H_ + +#include "../../Wiring/WString.h" +#include "../../Wiring/WHashMap.h" +#include "../../Delegate.h" + +#include "HttpResponse.h" +#include "HttpRequest.h" + +class HttpServerConnection; + +typedef Delegate HttpServerConnectionBodyDelegate; +typedef Delegate HttpServerConnectionUpgradeDelegate; +typedef Delegate HttpResourceDelegate; +typedef Delegate HttpPathDelegate; // << deprecated + +class HttpResource { +public: + virtual ~HttpResource() {} + +public: + HttpServerConnectionBodyDelegate onBody = 0; // << called when the resource wants to process the raw body data + HttpResourceDelegate onHeadersComplete = 0; // << called when the headers are ready + HttpResourceDelegate onRequestComplete = 0; // << called when the request is complete OR upgraded + HttpServerConnectionUpgradeDelegate onUpgrade = 0; // called when the request is upgraded and raw data is passed to it +}; + +class HttpCompatResource: public HttpResource { +public: + HttpCompatResource(const HttpPathDelegate& callback); + +private: + int requestComplete(HttpServerConnection&, HttpRequest& , HttpResponse& ); + +private: + HttpPathDelegate callback; +}; + + +typedef HashMap ResourceTree; + +#endif /* _SMING_CORE_HTTP_RESOURCE_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpResponse.cpp b/Sming/SmingCore/Network/Http/HttpResponse.cpp new file mode 100644 index 0000000000..9974cab2de --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpResponse.cpp @@ -0,0 +1,182 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpResponse + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpResponse.h" +#include "../WebConstants.h" + +HttpResponse::~HttpResponse() +{ + if(stream != NULL) { + delete stream; + stream = NULL; + } +} + +HttpResponse* HttpResponse::setContentType(const String type) +{ + return setHeader("Content-Type", type); +} + +HttpResponse* HttpResponse::setContentType(enum MimeType type) +{ + return setContentType(ContentType::toString(type)); +} + +HttpResponse* HttpResponse::setCookie(const String name, const String value) +{ + return setHeader("Set-Cookie", name + "=" + value); +} + +HttpResponse* HttpResponse::setCache(int maxAgeSeconds, bool isPublic /* = false */) +{ + String chache = String(isPublic ? "public" : "private") +", max-age=" + String(maxAgeSeconds) + ", must-revalidate"; + return setHeader("Cache-Control", chache); +} + +HttpResponse* HttpResponse::setAllowCrossDomainOrigin(String controlAllowOrigin) +{ + return setHeader("Access-Control-Allow-Origin", controlAllowOrigin); +} + +HttpResponse* HttpResponse::setHeader(const String name, const String value) +{ + headers[name] = value; + return this; +} + +bool HttpResponse::sendString(const String& text) +{ + MemoryDataStream* memStream = new MemoryDataStream(); + if (memStream->write((const uint8_t*)text.c_str(), text.length()) != text.length()) { + return false; + } + + if (stream != NULL) + { + SYSTEM_ERROR("Stream already created"); + delete stream; + stream = NULL; + } + + stream = memStream; + + return true; +} + +bool HttpResponse::hasHeader(const String name) +{ + return headers.contains(name); +} + +void HttpResponse::redirect(const String& location) { + headers["Location"] = location; +} + +bool HttpResponse::sendFile(String fileName, bool allowGzipFileCheck /* = true*/) +{ + if (stream != NULL) + { + SYSTEM_ERROR("Stream already created"); + delete stream; + stream = NULL; + } + + String compressed = fileName + ".gz"; + if (allowGzipFileCheck && fileExist(compressed)) + { + debugf("found %s", compressed.c_str()); + stream = new FileStream(compressed); + headers["Content-Encoding"] = "gzip"; + } + else if (fileExist(fileName)) + { + debugf("found %s", fileName.c_str()); + stream = new FileStream(fileName); + } + else + { + code = HTTP_STATUS_NOT_FOUND; + return false; + } + + if (!hasHeader("Content-Type")) + { + const char *mime = ContentType::fromFullFileName(fileName); + if (mime != NULL) + setContentType(mime); + } + + return true; +} + + +bool HttpResponse::sendTemplate(TemplateFileStream* newTemplateInstance) +{ + if (stream != NULL) + { + SYSTEM_ERROR("Stream already created"); + delete stream; + stream = NULL; + } + + stream = newTemplateInstance; + if (!newTemplateInstance->fileExist()) + { + code = HTTP_STATUS_NOT_FOUND; + delete stream; + stream = NULL; + return false; + } + + if (!hasHeader("Content-Type")) + { + const char *mime = ContentType::fromFullFileName(newTemplateInstance->fileName()); + if (mime != NULL) + setContentType(mime); + } + return true; +} + +bool HttpResponse::sendJsonObject(JsonObjectStream* newJsonStreamInstance) +{ + if (stream != NULL) + { + SYSTEM_ERROR("Stream already created"); + delete stream; + stream = NULL; + } + + stream = newJsonStreamInstance; + if (!hasHeader("Content-Type")) { + setContentType(MIME_JSON); + } + + return true; +} + +bool HttpResponse::sendDataStream( IDataSourceStream * newDataStream , String reqContentType /* = "" */) +{ + if (stream != NULL) + { + SYSTEM_ERROR("Stream already created"); + delete stream; + stream = NULL; + } + if (reqContentType != "") + { + setContentType(reqContentType); + } + stream = newDataStream; + + return true; +} + diff --git a/Sming/SmingCore/Network/Http/HttpResponse.h b/Sming/SmingCore/Network/Http/HttpResponse.h new file mode 100644 index 0000000000..6b1952c18f --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpResponse.h @@ -0,0 +1,90 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * HttpResponse + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTP_RESPONSE_H_ +#define _SMING_CORE_HTTP_RESPONSE_H_ + +#include "HttpCommon.h" +#include "../../OutputStream.h" +#include "../../DataSourceStream.h" + +class JsonObjectStream; // << TODO: deprecated and should be removed in the next version + +class HttpResponse { + friend class HttpClient; + friend class HttpConnection; + friend class HttpServerConnection; + +public: + ~HttpResponse(); + + bool sendString(const String& text); + + // @deprecated method + + bool hasHeader(const String name); + + void redirect(const String& location); + + /** + * @deprecated Use response.code = HTTP_STATUS_FORBIDDEN instead + */ + __forceinline void forbidden() { + code = HTTP_STATUS_FORBIDDEN; + } + + /** + * @deprecated Use response.code = HTTP_STATUS_NOT_FOUND instead + */ + __forceinline void notFound() { + code = HTTP_STATUS_NOT_FOUND; + } + + HttpResponse* setContentType(const String type); + HttpResponse* setContentType(enum MimeType type); + HttpResponse* setCookie(const String name, const String value); + HttpResponse* setHeader(const String name, const String value); + HttpResponse* setCache(int maxAgeSeconds = 3600, bool isPublic = false); + HttpResponse* setAllowCrossDomainOrigin(String controlAllowOrigin); // Access-Control-Allow-Origin for AJAX from a different domain + + // Send file by name + bool sendFile(String fileName, bool allowGzipFileCheck = true); + + // @deprecated + + // Parse and send template file + bool sendTemplate(TemplateFileStream* newTemplateInstance); + + /** + * @brief Build and send JSON string + * + * @deprecated use response.sendDataStream(stream, MIME_JSON) instead + */ + bool sendJsonObject(JsonObjectStream* newJsonStreamInstance); + + // @end deprecated + + // Send Datastream, can be called with Classes derived from + bool sendDataStream( IDataSourceStream * newDataStream , enum MimeType type) { + return sendDataStream(newDataStream, ContentType::toString(type)); + } + + // Send Datastream, can be called with Classes derived from + bool sendDataStream( IDataSourceStream * newDataStream , String reqContentType = "" ); + +public: + int code; + HttpHeaders headers; + IDataSourceStream* stream = NULL; +}; + +#endif /* _SMING_CORE_HTTP_RESPONSE_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp new file mode 100644 index 0000000000..1cd4b51e4a --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -0,0 +1,411 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "HttpServerConnection.h" + +#include "HttpServer.h" +#include "TcpServer.h" +#include "../../Services/cWebsocket/websocket.h" +#include "WebConstants.h" + +HttpServerConnection::HttpServerConnection(tcp_pcb *clientTcp) + : TcpClient(clientTcp, 0, 0), state(eHCS_Ready) +{ + // create parser ... + http_parser_init(&parser, HTTP_REQUEST); + parser.data = (void*)this; + + memset(&parserSettings, 0, sizeof(parserSettings)); + // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. + parserSettings.on_message_begin = staticOnMessageBegin; + parserSettings.on_headers_complete = staticOnHeadersComplete; + parserSettings.on_message_complete = staticOnMessageComplete; + + // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; + parserSettings.on_url = staticOnPath; + parserSettings.on_header_field = staticOnHeaderField; + parserSettings.on_header_value = staticOnHeaderValue; + parserSettings.on_body = staticOnBody; +} + +HttpServerConnection::~HttpServerConnection() +{ +} + +void HttpServerConnection::setResourceTree(ResourceTree* resourceTree) { + this->resourceTree = resourceTree; +} + +int HttpServerConnection::staticOnMessageBegin(http_parser* parser) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + // Reset Response ... + connection->response.code = 200; + connection->response.headers.clear(); + if(connection->response.stream != NULL) { + delete connection->response.stream; + connection->response.stream = NULL; + } + + connection->headersSent = false; + connection->state = eHCS_Ready; + + // ... and Request + // TODO: + connection->request.setMethod((const HttpMethod)parser->method); + + // and temp data... + connection->requestHeaders.clear(); + + return 0; +} + +int HttpServerConnection::staticOnPath(http_parser *parser, const char *at, size_t length) { + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + // TODO: find the most suitable path.. + + String path = String(at, length); + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + connection->request.setURL(path); + + if(connection->resourceTree == NULL) { + debugf("WARNING: HttpServerConnection: The resource tree is not set!"); + + return -1; + } + + if (connection->resourceTree->contains(connection->request.uri.Path)) { + connection->resource = (*connection->resourceTree)[connection->request.uri.Path]; + } + else if(connection->resourceTree->contains("*")) { + connection->resource = (*connection->resourceTree)["*"]; + } + + return 0; +} + +int HttpServerConnection::staticOnMessageComplete(http_parser* parser) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + // we are finished with this request + int hasError = 0; + if(HTTP_PARSER_ERRNO(parser) != HPE_OK) { + connection->sendError(http_errno_name(HTTP_PARSER_ERRNO(parser))); + return 0; + } + + if(connection->resource != NULL && connection->resource->onRequestComplete) { + hasError = connection->resource->onRequestComplete(*connection, connection->request, connection->response); + } + + connection->send(); + + if(connection->request.responseStream != NULL) { + connection->request.responseStream->close(); + delete connection->request.responseStream; + } + + return hasError; +} + +int HttpServerConnection::staticOnHeadersComplete(http_parser* parser) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + debugf("The headers are complete"); + + /* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + */ + int error = 0; + connection->request.setHeaders(connection->requestHeaders); + + if(connection->resource != NULL && connection->resource->onHeadersComplete) { + error = connection->resource->onHeadersComplete(*connection, connection->request, connection->response); + } + + if(!error && connection->request.method == HTTP_HEAD) { + error = 1; + } + + return error; +} + +int HttpServerConnection::staticOnHeaderField(http_parser *parser, const char *at, size_t length) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if(connection == NULL) { + // something went wrong + return -1; + } + + if(connection->lastWasValue) { + // we are starting to process new header + connection->lastData = ""; + connection->lastWasValue = false; + } + connection->lastData += String(at, length); + + return 0; +} + +int HttpServerConnection::staticOnHeaderValue(http_parser *parser, const char *at, size_t length) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if (connection == NULL) { + // something went wrong + return -1; + } + + if(!connection->lastWasValue) { + connection->currentField = connection->lastData; + connection->requestHeaders[connection->currentField] = ""; + connection->lastWasValue = true; + } + connection->requestHeaders[connection->currentField] += String(at, length); + + return 0; +} + +int HttpServerConnection::staticOnBody(http_parser *parser, const char *at, size_t length) +{ + HttpServerConnection *connection = (HttpServerConnection*)parser->data; + if (connection == NULL) { + // something went wrong + return -1; + } + + if(connection->resource != NULL && connection->resource->onBody) { + return connection->resource->onBody(*connection, connection->request, at, length); + } + + // TODO: ... +// if(connection->response.inputStream != NULL) { +// int res = connection->response.inputStream->write((const uint8_t *)&at, length); +// if (res != length) { +// connection->response.inputStream->close(); +// return 1; +// } +// } + + return 0; +} + +err_t HttpServerConnection::onReceive(pbuf *buf) +{ + if (buf == NULL) + { + return TcpConnection::onReceive(buf); // close the connection on TCP error. + } + + pbuf *cur = buf; + if (parser.upgrade && resource != NULL && resource->onUpgrade) { + while (cur != NULL && cur->len > 0) { + int err = resource->onUpgrade(*this, request, (char*)cur->payload, cur->len); + if(err) { + debugf("The upgraded connection returned error: %d", err); + TcpConnection::onReceive(NULL); + return ERR_ABRT; // abort the connection + } + + cur = cur->next; + } + + TcpConnection::onReceive(buf); + + return ERR_OK; + } + + int parsedBytes = 0; + while (cur != NULL && cur->len > 0) { + parsedBytes += http_parser_execute(&parser, &parserSettings, (char*) cur->payload, cur->len); + if(HTTP_PARSER_ERRNO(&parser) != HPE_OK) { + // we ran into trouble - abort the connection + debugf("HTTP parser error: %s", http_errno_name(HTTP_PARSER_ERRNO(&parser))); + sendError(); + + if(HTTP_PARSER_ERRNO(&parser) >= HPE_INVALID_EOF_STATE) { + TcpConnection::onReceive(NULL); + return ERR_ABRT; // abort the connection on HTTP parsing error. + } + + TcpConnection::onReceive(buf); + return ERR_OK; + } + + cur = cur->next; + } + + if (parsedBytes != buf->tot_len) { + if(!parser.upgrade) { + // something went wrong + TcpConnection::onReceive(NULL); + return ERR_ABRT; // abort the c + } + + if(resource != NULL && resource->onUpgrade) { + // we have rest bytes -> process them + while (cur != NULL && cur->len > 0) { + int err = resource->onUpgrade(*this, request, (char*)cur->payload, cur->len); + if(err) { + debugf("The upgraded connection returned error: %d", err); + TcpConnection::onReceive(NULL); + return ERR_ABRT; // abort the connection + } + + cur = cur->next; + } + } + } + + // Fire ReadyToSend callback + TcpConnection::onReceive(buf); + + return ERR_OK; +} + +void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) +{ + if(state != eHCS_Sending) { + TcpClient::onReadyToSendData(sourceEvent); + return; + } + + if(!headersSent) { + String statusLine = "HTTP/1.1 "+String(response.code) + " " + getStatus((enum http_status)response.code) + "\r\n"; + writeString(statusLine, TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); + + if(response.stream != NULL && response.stream->length() != -1) { + response.headers["Content-Length"] = String(response.stream->length()); + } + if(!response.headers.contains("Content-Length") && response.stream == NULL) { + response.headers["Content-Length"] = "0"; + } + + if(!response.headers.contains("Connection")) { + if(request.headers.contains("Connection") && request.headers["Connection"] == "close") { + // the other side requests closing of the tcp connection... + response.headers["Connection"] = "close"; + } + else { + response.headers["Connection"] = "keep-alive"; // Keep-Alive to reuse the connection + } + } + +#if HTTP_SERVER_EXPOSE_NAME == 1 + response.headers["Server"] = "HttpServer/Sming"; +#endif + +#if HTTP_SERVER_EXPOSE_DATE == 1 + response.headers["Date"] = SystemClock.getSystemTimeString(); +#endif + for (int i = 0; i < response.headers.count(); i++) + { + String write = response.headers.keyAt(i) + ": " + response.headers.valueAt(i) + "\r\n"; + writeString(write.c_str(), TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); + } + writeString("\r\n", TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); + headersSent = true; + } + + do { + if(request.method == HTTP_HEAD) { + if(response.stream != NULL) { + delete response.stream; + response.stream = NULL; + } + state = eHCS_Sent; + break; + } + + if(response.stream == NULL) { + state = eHCS_Sent; + break; + } + + write(response.stream); + if (response.stream->isFinished()) { + debugf("Body stream completed"); + delete response.stream; // Free memory now! + response.stream = NULL; + state = eHCS_Sent; + } + } while(false); + + if(state == eHCS_Sent && response.headers["Connection"] == "close") { + setTimeOut(1); // decrease the timeout to 1 tick + } + + TcpClient::onReadyToSendData(sourceEvent); +} + +void HttpServerConnection::onError(err_t err) { + TcpClient::onError(err); +} + +const char * HttpServerConnection::getStatus(enum http_status code) +{ + switch (code) { +#define XX(num, name, string) case num : return #string; + HTTP_STATUS_MAP(XX) +#undef XX + default: return ""; + } +} + +void HttpServerConnection::send() +{ + state = eHCS_Sending; +} + +void HttpServerConnection::sendError(const char* message /* = NULL*/, enum http_status code /* = HTTP_STATUS_BAD_REQUEST */) +{ + debugf("SEND ERROR PAGE"); + response.code = code; + response.setContentType(MIME_HTML); + + String html = "

"; + html += message ? message : getStatus((enum http_status)response.code); + html += "

"; + response.headers["Content-Length"] = html.length(); + response.sendString(html); + + send(); +} diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.h b/Sming/SmingCore/Network/Http/HttpServerConnection.h new file mode 100644 index 0000000000..c84fcfdd6f --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.h @@ -0,0 +1,96 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_HTTPSERVERCONNECTION_H_ +#define _SMING_CORE_HTTPSERVERCONNECTION_H_ + +#include "../TcpClient.h" +#include "../../Wiring/WString.h" +#include "../../Wiring/WHashMap.h" +#include "../../Delegate.h" + +#include "HttpResource.h" +#include "HttpRequest.h" + +#ifndef HTTP_SERVER_EXPOSE_NAME +#define HTTP_SERVER_EXPOSE_NAME 1 +#endif + +#ifndef HTTP_SERVER_EXPOSE_DATE +#define HTTP_SERVER_EXPOSE_DATE 0 +#endif + +class HttpServerConnection; + +typedef Delegate HttpServerConnectionDelegate; + +enum HttpConnectionState +{ + eHCS_Ready, + eHCS_Sending, + eHCS_Sent +}; + +class HttpServerConnection: public TcpClient +{ +public: + HttpServerConnection(tcp_pcb *clientTcp); + virtual ~HttpServerConnection(); + + void setResourceTree(ResourceTree* resourceTree); + + void send(); + + using TcpClient::send; + +// virtual void close(); + +protected: + virtual err_t onReceive(pbuf *buf); + virtual void onReadyToSendData(TcpConnectionEvent sourceEvent); + virtual void sendError(const char* message = NULL, enum http_status code = HTTP_STATUS_BAD_REQUEST); + virtual void onError(err_t err); + + const char * getStatus(enum http_status s); + +private: + static int IRAM_ATTR staticOnMessageBegin(http_parser* parser); + static int IRAM_ATTR staticOnPath(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnHeadersComplete(http_parser* parser); + static int IRAM_ATTR staticOnHeaderField(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnHeaderValue(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnBody(http_parser *parser, const char *at, size_t length); + static int IRAM_ATTR staticOnMessageComplete(http_parser* parser); + +public: + void* userData = NULL; // << use to pass user data between requests + +private: + HttpConnectionState state; + + http_parser parser; + http_parser_settings parserSettings; + + ResourceTree* resourceTree = NULL; + HttpResource* resource = NULL; + + HttpRequest request = HttpRequest(URL()); + HttpResponse response; + + bool headersSent = false; + + HttpResourceDelegate headersCompleteDelegate = 0; + HttpResourceDelegate requestCompletedDelegate = 0; + HttpServerConnectionBodyDelegate onBodyDelegate = 0; + + HttpHeaders requestHeaders; + bool lastWasValue = true; + String lastData = ""; + String currentField = ""; +}; + +#endif /* _SMING_CORE_HTTPSERVERCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp new file mode 100644 index 0000000000..274a1070d0 --- /dev/null +++ b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.cpp @@ -0,0 +1,232 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "WebSocketConnection.h" +#include "../../Services/WebHelpers/aw-sha1.h" +#include "../../Services/WebHelpers/base64.h" + +WebSocketsList WebSocketConnection::websocketList; + +WebSocketConnection::WebSocketConnection(HttpServerConnection* conn) +{ + connection = conn; +} + +WebSocketConnection::~WebSocketConnection() +{ + websocketList.removeElement(*this); +} + +bool WebSocketConnection::initialize(HttpRequest& request, HttpResponse& response) +{ + String version = request.getHeader("Sec-WebSocket-Version"); + version.trim(); + if (version.toInt() != 13) // 1.3 + return false; + + state = eWSCS_Open; + String hash = request.getHeader("Sec-WebSocket-Key"); + hash.trim(); + hash = hash + secret; + unsigned char data[SHA1_SIZE]; + char secure[SHA1_SIZE * 4]; + sha1(data, hash.c_str(), hash.length()); + base64_encode(SHA1_SIZE, data, SHA1_SIZE * 4, secure); + response.code = HTTP_STATUS_SWITCHING_PROTOCOLS; + response.setHeader("Connection", "Upgrade"); + response.setHeader("Upgrade", "websocket"); + response.setHeader("Sec-WebSocket-Accept", secure); + + connection->userData = (void *)this; + + websocketList.addElement(*this); + + memset(&parserSettings, 0, sizeof(parserSettings)); + parserSettings.on_data_begin = staticOnDataBegin; + parserSettings.on_data_payload = staticOnDataPayload; + parserSettings.on_data_end = staticOnDataEnd; + parserSettings.on_control_begin = staticOnControlBegin; + parserSettings.on_control_payload = staticOnControlPayload; + parserSettings.on_control_end = staticOnControlEnd; + + ws_parser_init(&parser, &parserSettings); + parser.user_data = (void*)this; + + if(wsConnect) { + wsConnect(*this); + } + + return true; +} + +int WebSocketConnection::processFrame(HttpServerConnection& connection, HttpRequest& request, char *at, int size) +{ + int rc = ws_parser_execute(&parser, (char *)at, size); + if (rc != WS_OK) { + debugf("WebSocketResource error: %d %s\n", rc, ws_parser_error(rc)); + return -1; + } + + return 0; +} + +int WebSocketConnection::staticOnDataBegin(void* userData, ws_frame_type_t type) { + WebSocketConnection *connection = (WebSocketConnection *)userData; + if (connection == NULL) { + return -1; + } + + connection->frameType = type; + + debugf("data_begin: %s\n", + type == WS_FRAME_TEXT ? "text" : + type == WS_FRAME_BINARY ? "binary" : + "?"); + + return WS_OK; +} + +int WebSocketConnection::staticOnDataPayload(void* userData, const char *at, size_t length) { + WebSocketConnection *connection = (WebSocketConnection *)userData; + if (connection == NULL) { + return -1; + } + + if (connection->frameType == WS_FRAME_TEXT && connection->wsMessage) { + connection->wsMessage(*connection, String(at, length)); + } else if (connection->frameType == WS_FRAME_BINARY && connection->wsBinary) { + connection->wsBinary(*connection, (uint8_t *) at, length); + } + + return WS_OK; +} + +int WebSocketConnection::staticOnDataEnd(void* userData) +{ + return WS_OK; +} + +int WebSocketConnection::staticOnControlBegin(void* userData, ws_frame_type_t type) +{ + WebSocketConnection *connection = (WebSocketConnection *)userData; + if (connection == NULL) { + return -1; + } + + connection->controlFrameType = type; + + if (type == WS_FRAME_CLOSE) { + if(connection->wsDisconnect) { + connection->wsDisconnect(*connection); + } + connection->close(); + } + + return WS_OK; +} + +int WebSocketConnection::staticOnControlPayload(void* userData, const char *data, size_t length) +{ + return WS_OK; +} + +int WebSocketConnection::staticOnControlEnd(void* userData) +{ + WebSocketConnection *connection = (WebSocketConnection *)userData; + if (connection == NULL) { + return -1; + } + + if(connection->controlFrameType == WS_FRAME_PING) { + // TODO: add control frame payload processing... + connection->send((const char* )NULL, 0, WS_PONG_FRAME); + } + + return WS_OK; +} + + +void WebSocketConnection::send(const char* message, int length, wsFrameType type /* = WS_TEXT_FRAME*/) +{ + uint8_t frameHeader[16] = {0}; + size_t headSize = sizeof(frameHeader); + wsMakeFrame(nullptr, length, frameHeader, &headSize, type); + connection->send((const char* )frameHeader, (uint16_t )headSize); + if(length > 0) { + connection->send((const char* )message, (uint16_t )length); + } +} + +void WebSocketConnection::broadcast(const char* message, int length, wsFrameType type /* = WS_TEXT_FRAME*/) +{ + for (int i = 0; i < websocketList.count(); i++) { + websocketList[i].send(message, length, type); + } +} + +void WebSocketConnection::sendString(const String& message) +{ + send(message.c_str(), message.length()); +} + +void WebSocketConnection::sendBinary(const uint8_t* data, int size) +{ + send((char*)data, size, WS_BINARY_FRAME); +} + +bool WebSocketConnection::operator==(const WebSocketConnection &rhs) const +{ + return (this == &rhs); +} + +WebSocketsList& WebSocketConnection::getActiveWebSockets() +{ + return websocketList; +} + +void WebSocketConnection::close() +{ + websocketList.removeElement((const WebSocketConnection)*this); + state = eWSCS_Closed; + + if(wsDisconnect) { + wsDisconnect(*this); + } + + connection->setTimeOut(1); +} + +void WebSocketConnection::setUserData(void* userData) +{ + this->userData = userData; +} + +void* WebSocketConnection::getUserData() +{ + return userData; +} + + +void WebSocketConnection::setConnectionHandler(WebSocketDelegate handler) +{ + wsConnect = handler; +} + +void WebSocketConnection::setMessageHandler(WebSocketMessageDelegate handler) +{ + wsMessage = handler; +} + +void WebSocketConnection::setBinaryHandler(WebSocketBinaryDelegate handler) +{ + wsBinary = handler; +} + +void WebSocketConnection::setDisconnectionHandler(WebSocketDelegate handler) +{ + wsDisconnect = handler; +} diff --git a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h new file mode 100644 index 0000000000..e564bc7d08 --- /dev/null +++ b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h @@ -0,0 +1,97 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef SMINGCORE_NETWORK_WEBSOCKETCONNECTION_H_ +#define SMINGCORE_NETWORK_WEBSOCKETCONNECTION_H_ + +#include "../../TcpServer.h" +#include "../HttpServerConnection.h" +#include "../../Services/cWebsocket/websocket.h" +extern "C" { + #include "../ws_parser/ws_parser.h" +} + +class WebSocketConnection; + +typedef Vector WebSocketsList; + +typedef Delegate WebSocketDelegate; +typedef Delegate WebSocketMessageDelegate; +typedef Delegate WebSocketBinaryDelegate; + +enum WsConnectionState +{ + eWSCS_Ready, + eWSCS_Open, + eWSCS_Closed +}; + +class WebSocketConnection +{ +public: + WebSocketConnection(HttpServerConnection* conn); + virtual ~WebSocketConnection(); + + bool initialize(HttpRequest &request, HttpResponse &response); + + virtual void send(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); + void broadcast(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); + + void sendString(const String& message); + void sendBinary(const uint8_t* data, int size); + void close(); + + void setUserData(void* userData); + void* getUserData(); + +// @deprecated + bool operator==(const WebSocketConnection &rhs) const; + + WebSocketsList& getActiveWebSockets(); +// @end deprecated + + void setConnectionHandler(WebSocketDelegate handler); + void setMessageHandler(WebSocketMessageDelegate handler); + void setBinaryHandler(WebSocketBinaryDelegate handler); + void setDisconnectionHandler(WebSocketDelegate handler); + + int processFrame(HttpServerConnection& connection, HttpRequest& request, char *at, int size); + +protected: + bool is(HttpServerConnection* conn) { return connection == conn; }; + + static int staticOnDataBegin(void* userData, ws_frame_type_t type); + static int staticOnDataPayload(void* userData, const char *at, size_t length); + static int staticOnDataEnd(void* userData); + static int staticOnControlBegin(void* userData, ws_frame_type_t type); + static int staticOnControlPayload(void* userData, const char*, size_t length); + static int staticOnControlEnd(void* userData); + +protected: + WebSocketDelegate wsConnect = 0; + WebSocketMessageDelegate wsMessage = 0; + WebSocketBinaryDelegate wsBinary = 0; + WebSocketDelegate wsDisconnect = 0; + +private: + WsConnectionState state = eWSCS_Ready; + + void *userData = nullptr; + HttpServerConnection* connection = nullptr; + + ws_frame_type_t frameType = WS_FRAME_TEXT; + ws_frame_type_t controlFrameType = WS_FRAME_PING; + + ws_parser_t parser; + ws_parser_callbacks_t parserSettings; + +// @deprecated + static WebSocketsList websocketList; +// @end deprecated +}; + +#endif /* SMINGCORE_NETWORK_WEBSOCKETCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.cpp b/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.cpp new file mode 100644 index 0000000000..bc487ad8e5 --- /dev/null +++ b/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.cpp @@ -0,0 +1,66 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * + * @author: 2017 - Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "WebsocketResource.h" + +WebsocketResource::WebsocketResource() { + onHeadersComplete = HttpResourceDelegate(&WebsocketResource::checkHeaders, this); + onUpgrade = HttpServerConnectionUpgradeDelegate(&WebsocketResource::processData, this); +} + +WebsocketResource::~WebsocketResource() +{ +} + +int WebsocketResource::checkHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) { + WebSocketConnection* socket = new WebSocketConnection(&connection); + socket->setBinaryHandler(wsBinary); + socket->setMessageHandler(wsMessage); + socket->setConnectionHandler(wsConnect); + socket->setDisconnectionHandler(wsDisconnect); + if (!socket->initialize(request, response)) { + debugf("Not a valid WebsocketRequest?"); + delete socket; + return -1; + } + + connection.setTimeOut(USHRT_MAX); //Disable disconnection on connection idle (no rx/tx) + connection.userData = (void *)socket; + +// TODO: Re-Enable Command Executor... + + return 0; +} + +int WebsocketResource::processData(HttpServerConnection& connection, HttpRequest& request, char *at, int size) +{ + WebSocketConnection *socket = (WebSocketConnection *)connection.userData; + if(socket == NULL) { + return -1; + } + + return socket->processFrame(connection, request, at, size); +} + +void WebsocketResource::setConnectionHandler(WebSocketDelegate handler) { + wsConnect = handler; +} + +void WebsocketResource::setMessageHandler(WebSocketMessageDelegate handler) { + wsMessage = handler; +} + +void WebsocketResource::setBinaryHandler(WebSocketBinaryDelegate handler) { + wsBinary = handler; +} + +void WebsocketResource::setDisconnectionHandler(WebSocketDelegate handler) { + wsDisconnect = handler; +} diff --git a/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.h b/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.h new file mode 100644 index 0000000000..e69359993d --- /dev/null +++ b/Sming/SmingCore/Network/Http/Websocket/WebsocketResource.h @@ -0,0 +1,35 @@ +/* + * WebResource.h + * + * Created on: Apr 3, 2017 + * Author: slavey + */ + +#ifndef _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ +#define _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ + +#include "../HttpResource.h" +#include "WebSocketConnection.h" +#include "../../Wiring/WString.h" + +class WebsocketResource: public HttpResource { + +public: + WebsocketResource(); + ~WebsocketResource(); + int checkHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response); + int processData(HttpServerConnection& connection, HttpRequest& request, char *at, int size); + + void setConnectionHandler(WebSocketDelegate handler); + void setMessageHandler(WebSocketMessageDelegate handler); + void setBinaryHandler(WebSocketBinaryDelegate handler); + void setDisconnectionHandler(WebSocketDelegate handler); + +protected: + WebSocketDelegate wsConnect = 0; + WebSocketMessageDelegate wsMessage = 0; + WebSocketBinaryDelegate wsBinary = 0; + WebSocketDelegate wsDisconnect = 0; +}; + +#endif /* _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ */ diff --git a/Sming/SmingCore/Network/Http/Websocket/WsCommandHandlerResource.h b/Sming/SmingCore/Network/Http/Websocket/WsCommandHandlerResource.h new file mode 100644 index 0000000000..412a49befa --- /dev/null +++ b/Sming/SmingCore/Network/Http/Websocket/WsCommandHandlerResource.h @@ -0,0 +1,52 @@ +/* + * WebResource.h + * + * Created on: Apr 3, 2017 + * Author: slavey + */ + +#ifndef _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ +#define _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ + +#include "../HttpResource.h" +#include "WebSocketConnection.h" +#include "../../Wiring/WString.h" +#include "../../Services/CommandProcessing/CommandProcessingIncludes.h" // TODO: .... + +class WsCommandHandlerResource: protected WebsocketResource { + +public: + WsCommandHandlerResource(): WebsocketResource() + { + wsMessage = WebSocketMessageDelegate(&WsCommandHandlerResource::onMessage, this); + } + +protected: + int checkHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) + { + int err = WebsocketResource::checkHeaders(connection, request, response); + if(err != 0 ) { + return err; + } + + WebSocketConnection* socket = (WebSocketConnection*)connection.userData; + if(socket != NULL) { + + socket->setMessageHandler() + + // create new command handler + + } + } + + + void onMessage(WebSocketConnection& connection, const String& message) + { + commandExecutor.executorReceive(message + "\r"); + } + +private: + CommandExecutor commandExecutor; +}; + +#endif /* _SMING_SMINGCORE_NETWORK_WEBSOCKET_RESOURCE_H_ */ diff --git a/Sming/SmingCore/Network/HttpClient.cpp b/Sming/SmingCore/Network/HttpClient.cpp index d398bea294..f1bc0ba84a 100644 --- a/Sming/SmingCore/Network/HttpClient.cpp +++ b/Sming/SmingCore/Network/HttpClient.cpp @@ -3,37 +3,74 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpClient + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #include "HttpClient.h" -#include "../SmingCore.h" +/* Low Level Methods */ +bool HttpClient::send(HttpRequest* request) { + String cacheKey = getCacheKey(request->uri); + bool useSsl = (request->uri.Protocol == HTTPS_URL_PROTOCOL); -HttpClient::HttpClient(bool autoDestruct /* = false */) : TcpClient(autoDestruct) -{ - reset(); -} + if(!queue.contains(cacheKey)) { + queue[cacheKey] = new RequestQueue; + } -HttpClient::~HttpClient() -{ -} + if(!queue[cacheKey]->enqueue(request)) { + // the queue is full and we cannot add more requests at the time. + debugf("The request queue is full at the moment"); + return false; + } -bool HttpClient::downloadString(String url, HttpClientCompletedDelegate onCompleted) -{ - if (isProcessing()) return false; - URL uri = URL(url); + if(httpConnectionPool.contains(cacheKey) && + httpConnectionPool[cacheKey]->getConnectionState() > eTCS_Connecting && + !httpConnectionPool[cacheKey]->isActive() + ) { - return startDownload(uri, eHCM_String, onCompleted); + debugf("Removing stale connection: State: %d, Active: %d", (int)httpConnectionPool[cacheKey]->getConnectionState(), + (httpConnectionPool[cacheKey]->isActive() ? 1: 0)); + httpConnectionPool.remove(cacheKey); + } + + if(!httpConnectionPool.contains(cacheKey)) { + debugf("Creating new httpConnection"); + httpConnectionPool[cacheKey] = new HttpConnection(queue[cacheKey]); + } + +#ifdef ENABLE_SSL + // Based on the URL decide if we should reuse the SSL and TCP pool + if(useSsl) { + if (!sslSessionIdPool.contains(cacheKey)) { + sslSessionIdPool[cacheKey] = (SSLSessionId *)malloc(sizeof(SSLSessionId)); + sslSessionIdPool[cacheKey]->value = NULL; + sslSessionIdPool[cacheKey]->length = 0; + } + httpConnectionPool[cacheKey]->addSslOptions(request->getSslOptions()); + httpConnectionPool[cacheKey]->pinCertificate(request->sslFingerprint); + httpConnectionPool[cacheKey]->setSslClientKeyCert(request->sslClientKeyCert); + httpConnectionPool[cacheKey]->sslSessionId = sslSessionIdPool[cacheKey]; + } +#endif + + return httpConnectionPool[cacheKey]->connect(request->uri.Host, request->uri.Port, useSsl); } -bool HttpClient::downloadFile(String url, HttpClientCompletedDelegate onCompleted /* = NULL */) -{ - return downloadFile(url, "", onCompleted); +// Convenience methods + +bool HttpClient::downloadString(const String& url, RequestCompletedDelegate requestComplete) { + return send(request(url) + ->setMethod(HTTP_GET) + ->onRequestComplete(requestComplete) + ); } -bool HttpClient::downloadFile(String url, String saveFileName, HttpClientCompletedDelegate onCompleted /* = NULL */) +bool HttpClient::downloadFile(const String& url, const String& saveFileName, RequestCompletedDelegate requestComplete /* = NULL */) { - if (isProcessing()) return false; URL uri = URL(url); String file; @@ -47,231 +84,52 @@ bool HttpClient::downloadFile(String url, String saveFileName, HttpClientComplet else file = saveFileName; - saveFile = fileOpen(file.c_str(), eFO_CreateNewAlways | eFO_WriteOnly); - debugf("Download file: %s %d", file.c_str(), saveFile); - - return startDownload(uri, eHCM_File, onCompleted); -} - -bool HttpClient::startDownload(URL uri, HttpClientMode mode, HttpClientCompletedDelegate onCompleted) -{ - reset(); - this->mode = mode; - this->onCompleted = onCompleted; - - debugf("Download: %s", uri.toString().c_str()); - - if(uri.Protocol == HTTPS_URL_PROTOCOL) { - connect(uri.Host, uri.Port, true); - } - else { - connect(uri.Host, uri.Port); - } - - bool isPost = body.length(); - - sendString((isPost ? "POST " : "GET ") + uri.getPathWithQuery() + " HTTP/1.0\r\nHost: " + uri.Host + "\r\n"); - for (int i = 0; i < requestHeaders.count(); i++) - { - String write = requestHeaders.keyAt(i) + ": " + requestHeaders.valueAt(i) + "\r\n"; - sendString(write.c_str()); - } - sendString("\r\n"); - sendString(body); - - return true; -} - -void HttpClient::setRequestHeader(const String name, const String value) -{ - requestHeaders[name] = value; -} - -bool HttpClient::hasRequestHeader(const String name) -{ - return requestHeaders.contains(name); -} - -void HttpClient::setRequestContentType(String contentType) -{ - setRequestHeader("Content-Type", contentType); -} - -void HttpClient::setPostBody(const String& _body) -{ - if (!hasRequestHeader("Content-Type")) - setRequestContentType(ContentType::FormUrlEncoded); - body = _body; - setRequestHeader("Content-Length", String(body.length())); -} - + FileOutputStream* fileStream = new FileOutputStream(file); -String HttpClient::getPostBody() -{ - return body; + return send(request(url) + ->setResponseStream(fileStream) + ->setMethod(HTTP_GET) + ->onRequestComplete(requestComplete) + ); } -void HttpClient::reset() -{ - code = 0; - responseStringData = ""; - waitParse = true; - writeError = false; - responseHeaders.clear(); -} +// end convenience methods -String HttpClient::getResponseHeader(String headerName, String defaultValue /* = "" */) -{ - if (responseHeaders.contains(headerName)) - return responseHeaders[headerName]; - - return defaultValue; +HttpRequest* HttpClient::request(const String& url) { + return new HttpRequest(URL(url)); } -DateTime HttpClient::getLastModifiedDate() -{ - DateTime res; - String strLM = getResponseHeader("Last-Modified"); - if (res.parseHttpDate(strLM)) - return res; - else - return DateTime(); -} +HashMap HttpClient::httpConnectionPool; +HashMap HttpClient::queue; -DateTime HttpClient::getServerDate() -{ - DateTime res; - String strSD = getResponseHeader("Date"); - if (res.parseHttpDate(strSD)) - return res; - else - return DateTime(); -} - -void HttpClient::onFinished(TcpClientState finishState) -{ - if (finishState == eTCS_Failed) code = 0; +#ifdef ENABLE_SSL +HashMap HttpClient::sslSessionIdPool; - if (mode == eHCM_File) - { - debugf("Download file len written: %d, res^ %d", fileTell(saveFile), isSuccessful()); - if (!isSuccessful()) - fileDelete(saveFile); - fileClose(saveFile); - } - - if (onCompleted) - onCompleted(*this, isSuccessful()); - - TcpClient::onFinished(finishState); -} - -void HttpClient::parseHeaders(pbuf* buf, int headerEnd) -{ - int line, nextLine; - line = NetUtils::pbufFindStr(buf, "\r\n", 0) + 2; - do - { - nextLine = NetUtils::pbufFindStr(buf, "\r\n", line); - if (nextLine - line > 2) - { - int delim = NetUtils::pbufFindStr(buf, ":", line); - if (delim != -1) - { - String name = NetUtils::pbufStrCopy(buf, line, delim - line); - //if (server->isHeaderProcessingEnabled(name)) - { - String value = NetUtils::pbufStrCopy(buf, delim + 1, - nextLine - (delim + 1)); - value.trim(); - responseHeaders[name] = value; - debugf("%s === %s", name.c_str(), value.c_str()); - } - } - } - line = nextLine + 2; - } while (nextLine != -1 && nextLine < headerEnd); -} - -void HttpClient::writeRawData(pbuf* buf, int startPos) -{ - switch (mode) - { - case eHCM_String: - { - responseStringData += NetUtils::pbufStrCopy(buf, startPos, - buf->tot_len - startPos); - break; - } - case eHCM_File: - { - pbuf *cur = buf; - while (cur != NULL && cur->len > 0 && !writeError) - { - char* ptr = (char*) cur->payload + startPos; - int len = cur->len - startPos; - int res = fileWrite(saveFile, ptr, len); - writeError |= (res < 0); - cur = cur->next; - startPos = 0; - } - - if (writeError) - close(); +void HttpClient::freeSslSessionPool() { + for(int i=0; i< sslSessionIdPool.count(); i ++) { + String key = sslSessionIdPool.keyAt(i); + if(sslSessionIdPool[key]->value != NULL) { + free(sslSessionIdPool[key]->value); } + free(sslSessionIdPool[key]->value); } + sslSessionIdPool.clear(); } +#endif -err_t HttpClient::onReceive(pbuf *buf) -{ - if (buf == NULL) - { - // Disconnected, close it - TcpClient::onReceive(buf); - } - else - { - int startPos = 0; - if (waitParse) - { - String http = NetUtils::pbufStrCopy(buf, 0, 4); - http.toUpperCase(); - if (http == "HTTP" && code == 0) - { - int codeStart = NetUtils::pbufFindChar(buf, ' ', 4); - int codeEnd = NetUtils::pbufFindChar(buf, ' ', codeStart + 1); - if (codeStart != -1 && codeEnd != -1 && codeEnd - codeStart < 5) - { - String strCode = NetUtils::pbufStrCopy(buf, codeStart, codeEnd); - code = strCode.toInt(); - } - else - code = 0; - } - int headerEnd = NetUtils::pbufFindStr(buf, "\r\n\r\n"); - if (headerEnd != -1) - { - debugf("Header pos: %d", headerEnd); - startPos = headerEnd + 4; - waitParse = false; - if (headerEnd < NETWORK_MAX_HTTP_PARSING_LEN) - parseHeaders(buf, headerEnd); - } - } +void HttpClient::cleanup() { +#ifdef ENABLE_SSL + freeSslSessionPool(); +#endif + httpConnectionPool.clear(); + queue.clear(); +} - if (!waitParse) writeRawData(buf, startPos); - // Fire ReadyToSend callback - TcpClient::onReceive(buf); - } +HttpClient::~HttpClient() { - return ERR_OK; } -String HttpClient::getResponseString() -{ - if (mode == eHCM_String) - return responseStringData; - else - return ""; +String HttpClient::getCacheKey(URL url) { + return String(url.Host) + ":" + String(url.Port); } diff --git a/Sming/SmingCore/Network/HttpClient.h b/Sming/SmingCore/Network/HttpClient.h index cbd7348337..4631928668 100644 --- a/Sming/SmingCore/Network/HttpClient.h +++ b/Sming/SmingCore/Network/HttpClient.h @@ -3,94 +3,84 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpClient + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #ifndef _SMING_CORE_NETWORK_HTTPCLIENT_H_ #define _SMING_CORE_NETWORK_HTTPCLIENT_H_ #include "TcpClient.h" -#include "../../Wiring/WString.h" -#include "../../Wiring/WHashMap.h" -#include "../../Services/DateTime/DateTime.h" -#include "../Delegate.h" +#include "Http/HttpCommon.h" +#include "Http/HttpRequest.h" +#include "Http/HttpConnection.h" -class HttpClient; -class URL; - -//typedef void (*HttpClientCompletedCallback)(HttpClient& client, bool successful); -typedef Delegate HttpClientCompletedDelegate; - -enum HttpClientMode -{ - eHCM_String = 0, - eHCM_File, - eHCM_UserDefined -}; - -class HttpClient: protected TcpClient +class HttpClient { public: - HttpClient(bool autoDestruct = false); - virtual ~HttpClient(); + /* High-Level Method */ - // Text mode - bool downloadString(String url, HttpClientCompletedDelegate onCompleted); - String getResponseString(); // Can be used only after calling downloadString! + __forceinline bool sendRequest(const String& url, RequestCompletedDelegate requestComplete) { + return send(request(url) + ->setMethod(HTTP_GET) + ->onRequestComplete(requestComplete) + ); + } - // File mode - bool downloadFile(String url, HttpClientCompletedDelegate onCompleted = NULL); - bool downloadFile(String url, String saveFileName, HttpClientCompletedDelegate onCompleted = NULL); - void setPostBody(const String& _method); - String getPostBody(); + __forceinline bool sendRequest(const HttpMethod method, const String& url, const HttpHeaders& headers, RequestCompletedDelegate requestComplete) { + return send(request(url) + ->setMethod(HTTP_GET) + ->setHeaders(headers) + ->onRequestComplete(requestComplete) + ); + } - void setRequestHeader(const String name, const String value); - bool hasRequestHeader(const String name); - void setRequestContentType(String _content_type); + __forceinline bool sendRequest(const HttpMethod method, const String& url, const HttpHeaders& headers, const String& body, RequestCompletedDelegate requestComplete) { + return send(request(url) + ->setMethod(method) + ->setHeaders(headers) + ->setBody(body) + ->onRequestComplete(requestComplete) + ); + } - // Resulting HTTP status code - __forceinline int getResponseCode() { return code; } - __forceinline bool isSuccessful() { return (!writeError) && (code >= 200 && code <= 399); } + bool downloadString(const String& url, RequestCompletedDelegate requestComplete); - __forceinline bool isProcessing() { return TcpClient::isProcessing(); } - __forceinline TcpClientState getConnectionState() { return TcpClient::getConnectionState(); } + __forceinline bool downloadFile(const String& url, RequestCompletedDelegate requestComplete = NULL) { + return downloadFile(url, "", requestComplete); + } - String getResponseHeader(String headerName, String defaultValue = ""); - DateTime getLastModifiedDate(); // Last-Modified header - DateTime getServerDate(); // Date header + bool downloadFile(const String& url, const String& saveFileName, RequestCompletedDelegate requestComplete = NULL); - void reset(); // Reset current status, data and etc. + /* Low Level Methods */ + bool send(HttpRequest* request); + HttpRequest* request(const String& url); #ifdef ENABLE_SSL - using TcpClient::addSslOptions; - using TcpClient::setSslFingerprint; - using TcpClient::pinCertificate; - using TcpClient::setSslClientKeyCert; - using TcpClient::freeSslClientKeyCert; - using TcpClient::getSsl; + static void freeSslSessionPool(); #endif + /** + * Use this method to clean all request queues and object pools + */ + static void cleanup(); + + virtual ~HttpClient(); + protected: - bool startDownload(URL uri, HttpClientMode mode, HttpClientCompletedDelegate onCompleted); - void onFinished(TcpClientState finishState); - virtual err_t onReceive(pbuf *buf); - virtual void writeRawData(pbuf* buf, int startPos); - void parseHeaders(pbuf* buf, int headerEnd); + String getCacheKey(URL url); protected: - bool waitParse = false; - bool writeError = false; - -private: - int code; - HttpClientCompletedDelegate onCompleted; - HttpClientMode mode; - HashMap requestHeaders; - HashMap responseHeaders; - - String responseStringData; - String body = ""; - file_t saveFile; + static HashMap httpConnectionPool; + static HashMap queue; + +#ifdef ENABLE_SSL + static HashMap sslSessionIdPool; +#endif }; #endif /* _SMING_CORE_NETWORK_HTTPCLIENT_H_ */ diff --git a/Sming/SmingCore/Network/HttpRequest.cpp b/Sming/SmingCore/Network/HttpRequest.cpp deleted file mode 100644 index 50568e7752..0000000000 --- a/Sming/SmingCore/Network/HttpRequest.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#include "HttpRequest.h" -#include "HttpServer.h" -#include "NetUtils.h" -#include -#include "../../Services/WebHelpers/escape.h" - -HttpRequest::HttpRequest() -{ - requestHeaders = NULL; - requestGetParameters = NULL; - requestPostParameters = NULL; - cookies = NULL; - headerDataProcessed = 0; - postDataProcessed = 0; - bodyBuf = NULL; - tmpbuf = ""; -} - -HttpRequest::~HttpRequest() -{ - delete requestHeaders; - delete requestGetParameters; - delete requestPostParameters; - delete cookies; - postDataProcessed = 0; - if (bodyBuf != NULL) - { - os_free(bodyBuf); - } -} - -String HttpRequest::getQueryParameter(String parameterName, String defaultValue /* = "" */) -{ - if (requestGetParameters && requestGetParameters->contains(parameterName)) - return (*requestGetParameters)[parameterName]; - - return defaultValue; -} -String HttpRequest::getPostParameter(String parameterName, String defaultValue /* = "" */) -{ - if (requestPostParameters && requestPostParameters->contains(parameterName)) - return (*requestPostParameters)[parameterName]; - - return defaultValue; -} - -String HttpRequest::getHeader(String headerName, String defaultValue /* = "" */) -{ - headerName.toLowerCase(); - if (requestHeaders && requestHeaders->contains(headerName)) - return (*requestHeaders)[headerName]; - - return defaultValue; -} - -String HttpRequest::getCookie(String name, String defaultValue /* = "" */) -{ - if (cookies && cookies->contains(name)) - return (*cookies)[name]; - - return defaultValue; -} - -int HttpRequest::getContentLength() -{ - String len = getHeader("Content-Length"); - if (len.length() == 0) return -1; - - return len.toInt(); -} - -String HttpRequest::getContentType() -{ - return getHeader("Content-Type"); -} - -HttpParseResult HttpRequest::parseHeader(HttpServer *server, pbuf* buf) -{ - int headerEnd = NetUtils::pbufFindStr(buf, "\r\n\r\n"); - if (headerEnd > NETWORK_MAX_HTTP_PARSING_LEN || headerDataProcessed > NETWORK_MAX_HTTP_PARSING_LEN \ - || (headerEnd != -1 && buf->tot_len > NETWORK_MAX_HTTP_PARSING_LEN)) - { - debugf("NETWORK_MAX_HTTP_PARSING_LEN"); - return eHPR_Failed; - } - int urlEnd = 0; - tmpbuf += NetUtils::pbufStrCopy(buf, 0, buf->tot_len); - if (requestHeaders == NULL) { - // first time calling header - requestHeaders = new HashMap(); - - int urlStart = tmpbuf.indexOf(" ")+1; - urlEnd = tmpbuf.indexOf(" ", urlStart); - if (urlStart == 0 || urlEnd == -1) - { - debugf("!BadRequest"); - return eHPR_Failed; - } - - method = tmpbuf.substring(0, urlStart-1); - int urlParamsStart = tmpbuf.indexOf("?", urlStart); - if (urlParamsStart != -1 && urlParamsStart < urlEnd) - { - path = tmpbuf.substring(urlStart, urlParamsStart); - if (requestGetParameters == NULL) requestGetParameters = new HashMap(); - extractParsingItemsList(tmpbuf, urlParamsStart + 1, urlEnd, '&', ' ', requestGetParameters); - } - else - path = tmpbuf.substring(urlStart, urlEnd); - debugf("path=%s", path.c_str()); - urlEnd = tmpbuf.indexOf("\r\n", urlEnd)+2; - } - - int line, nextLine; - line = urlEnd; - do - { - - nextLine = tmpbuf.indexOf("\r\n", line); - if (nextLine - line > 2) - - { - int delim = tmpbuf.indexOf(":", line); - if (delim != -1) - { - String name = tmpbuf.substring(line, delim); - name.toLowerCase(); - if (server->isHeaderProcessingEnabled(name)) - { - debugf("Name: %s", name.c_str()); - if (name == "cookie") - { - if (cookies == NULL) cookies = new HashMap(); - extractParsingItemsList(tmpbuf, delim + 1, nextLine, ';', '\r', cookies); - } - else - { - String value = tmpbuf.substring(delim + 1, nextLine); - value.trim(); - (*requestHeaders)[name] = value; - debugf("%s === %s", name.c_str(), value.c_str()); - } - - } - } - - } - if (nextLine != -1) { - line = nextLine + 2; - } - - } while(nextLine != -1); - - if (headerEnd != -1) - { - tmpbuf = ""; - debugf("parsed"); - return eHPR_Successful; - } - headerDataProcessed += buf->tot_len; - tmpbuf = tmpbuf.substring(line, buf->tot_len); - return eHPR_Wait; -} - -HttpParseResult HttpRequest::parsePostData(HttpServer *server, pbuf* buf) -{ - int start = 0; - tmpbuf += NetUtils::pbufStrCopy(buf, 0, buf->tot_len); - // First enter - if (requestPostParameters == NULL) - { - int headerEnd = NetUtils::pbufFindStr(buf, "\r\n\r\n"); - if (headerEnd == -1) return eHPR_Failed; - if (headerEnd + getContentLength() > NETWORK_MAX_HTTP_PARSING_LEN) - { - debugf("NETWORK_MAX_HTTP_PARSING_LEN"); - return eHPR_Failed; - } - requestPostParameters = new HashMap(); - start = headerEnd + 4; - tmpbuf = tmpbuf.substring(start, tmpbuf.length()); - } - - //parse if it is FormUrlEncoded - otherwise keep in buffer - String contType = getContentType(); - contType.toLowerCase(); - if (contType.indexOf(ContentType::FormUrlEncoded) != -1) - { - tmpbuf = extractParsingItemsList(tmpbuf, 0, tmpbuf.length(), '&', ' ', requestPostParameters); - } - - postDataProcessed += buf->tot_len - start ; - - if (postDataProcessed == getContentLength()) - { - return eHPR_Successful; - } - else if (postDataProcessed > getContentLength()) - { - //avoid bufferoverflow if client announces non-correct content-length - debugf("NETWORK_MAX_HTTP_PARSING_LEN"); - return eHPR_Failed; - } - else - { - return eHPR_Wait; - } -} - -String HttpRequest::extractParsingItemsList(String& buf, int startPos, int endPos, char delimChar, char endChar, - HashMap* resultItems) -{ - - int delimItem, nextItem, startItem = startPos; - do - { - delimItem = buf.indexOf("=", startItem); - nextItem = buf.indexOf(delimChar, startItem); - //debugf("item %i - delim %i - next %i", startItem, delimItem, nextItem); - if (nextItem == -1) nextItem = buf.indexOf(endChar, delimItem+1); - if (nextItem == -1) nextItem = endPos; - if (nextItem > endPos || delimItem == -1) nextItem = endPos; - if (delimItem == -1) break; - String ItemName = buf.substring(startItem, delimItem); - String ItemValue = buf.substring(delimItem+1, nextItem); - char* nam = uri_unescape(NULL, 0, ItemName.c_str(), -1); - ItemName = nam; - free(nam); - char* val = uri_unescape(NULL, 0, ItemValue.c_str(), -1); - ItemValue = val; - free(val); - ItemName.trim(); - debugf("Item: Name = %s, Size = %d, Value = %s",ItemName.c_str(),ItemValue.length(),ItemValue.substring(0,80).c_str()); - (*resultItems)[ItemName] = ItemValue; - if (nextItem == endPos) break; - startItem = nextItem + 1; - - - } while (nextItem != -1); - return tmpbuf.substring(startItem, nextItem); - -} - - -String HttpRequest::getBody() -{ - return tmpbuf; -} - -bool HttpRequest::isAjax() -{ - String req = getHeader("HTTP_X_REQUESTED_WITH"); - return req.equalsIgnoreCase("xmlhttprequest"); -} - -bool HttpRequest::isWebSocket() -{ - String req = getHeader("Upgrade"); - return req.equalsIgnoreCase("websocket"); -} diff --git a/Sming/SmingCore/Network/HttpRequest.h b/Sming/SmingCore/Network/HttpRequest.h deleted file mode 100644 index a51b3469d8..0000000000 --- a/Sming/SmingCore/Network/HttpRequest.h +++ /dev/null @@ -1,69 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#ifndef _SMING_CORE_NETWORK_HTTPREQUEST_H_ -#define _SMING_CORE_NETWORK_HTTPREQUEST_H_ - -#define NETWORK_MAX_HTTP_PARSING_LEN 4096 - -#include "../Wiring/WHashMap.h" -#include "../Wiring/WString.h" - -class pbuf; -class HttpServer; -class TemplateFileStream; - -enum HttpParseResult -{ - eHPR_Wait = 0, - eHPR_Successful, - eHPR_Failed -}; - -class HttpRequest -{ -public: - HttpRequest(); - virtual ~HttpRequest(); - - inline String getRequestMethod() { return method; } - inline String getPath() { return path; } - String getContentType(); - int getContentLength(); - - bool isAjax(); - bool isWebSocket(); - - String getQueryParameter(String parameterName, String defaultValue = ""); - String getPostParameter(String parameterName, String defaultValue = ""); - String getHeader(String headerName, String defaultValue = ""); - String getCookie(String cookieName, String defaultValue = ""); - String getBody(); - -public: - HttpParseResult parseHeader(HttpServer *server, pbuf* buf); - HttpParseResult parsePostData(HttpServer *server, pbuf* buf); - String extractParsingItemsList(String& buf, int startPos, int endPos, - char delimChar, char endChar, - HashMap* resultItems); - -private: - String method; - String path; - String tmpbuf; - HashMap *requestHeaders; - HashMap *requestGetParameters; - HashMap *requestPostParameters; - HashMap *cookies; - int postDataProcessed; - int headerDataProcessed; - char *bodyBuf; - - friend class TemplateFileStream; -}; - -#endif /* _SMING_CORE_NETWORK_HTTPREQUEST_H_ */ diff --git a/Sming/SmingCore/Network/HttpResponse.cpp b/Sming/SmingCore/Network/HttpResponse.cpp deleted file mode 100644 index 57c8466409..0000000000 --- a/Sming/SmingCore/Network/HttpResponse.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#include "HttpResponse.h" - -#include "HttpServerConnection.h" -#include "../DataSourceStream.h" - -HttpResponse::HttpResponse() -{ - status = HttpStatusCode::OK; - stream = NULL; - headerSent = false; - bodySent = false; -} - -HttpResponse::~HttpResponse() -{ - if (stream != NULL) - delete stream; - stream = NULL; -} - -void HttpResponse::switchingProtocols() -{ - status = HttpStatusCode::SwitchingProtocols; -} -void HttpResponse::badRequest() -{ - status = HttpStatusCode::BadRequest; -} -void HttpResponse::notFound() -{ - status = HttpStatusCode::NotFound; -} -void HttpResponse::forbidden() -{ - status = HttpStatusCode::Forbidden; -} -void HttpResponse::authorizationRequired() -{ - status = HttpStatusCode::Unauthorized; -} -void HttpResponse::redirect(String location /* = "" */) -{ - status = HttpStatusCode::Found; - setHeader("Location", location); -} - -String HttpResponse::getStatusName() -{ - return status; -} - -int HttpResponse::getStatusCode() -{ - int p = status.indexOf(' '); - if (status.length() == 0 || p == -1) return 0; - - return status.substring(0, p).toInt(); -} - -bool HttpResponse::hasBody() -{ - return stream != NULL || bodySent; -} - -/// - -void HttpResponse::setContentType(const String type) -{ - setHeader("Content-Type", type); -} - -void HttpResponse::setCookie(const String name, const String value) -{ - setHeader("Set-Cookie", name + "=" + value); -} - -void HttpResponse::setCache(int maxAgeSeconds, bool isPublic /* = false */) -{ - String chache = String(isPublic ? "public" : "private") +", max-age=" + String(maxAgeSeconds) + ", must-revalidate"; - setHeader("Cache-Control", chache); -} - -void HttpResponse::setAllowCrossDomainOrigin(String controlAllowOrigin) -{ - setHeader("Access-Control-Allow-Origin", controlAllowOrigin); -} - -void HttpResponse::setHeader(const String name, const String value) -{ - responseHeaders[name] = value; -} - -bool HttpResponse::hasHeader(const String name) -{ - return responseHeaders.contains(name); -} - -/// - -void HttpResponse::sendString(const char* string) -{ - if (stream != NULL && stream->getStreamType() != eSST_Memory) - { - SYSTEM_ERROR("Stream already created"); - delete stream; - stream = NULL; - } - - if (stream == NULL) - stream = new MemoryDataStream(); - - MemoryDataStream *writable = (MemoryDataStream*)stream; - writable->write((const uint8_t*)string, strlen(string)); -} - -void HttpResponse::sendString(String string) -{ - sendString(string.c_str()); -} - -bool HttpResponse::sendFile(String fileName, bool allowGzipFileCheck /* = true*/) -{ - if (stream != NULL) - { - SYSTEM_ERROR("Stream already created"); - delete stream; - stream = NULL; - } - - String compressed = fileName + ".gz"; - if (allowGzipFileCheck && fileExist(compressed)) - { - debugf("found %s", compressed.c_str()); - stream = new FileStream(compressed); - setHeader("Content-Encoding", "gzip"); - } - else if (fileExist(fileName)) - { - debugf("found %s", fileName.c_str()); - stream = new FileStream(fileName); - } - else - { - notFound(); - return false; - } - - if (!hasHeader("Content-Type")) - { - const char *mime = ContentType::fromFullFileName(fileName); - if (mime != NULL) - setContentType(mime); - } - return true; -} - -bool HttpResponse::sendTemplate(TemplateFileStream* newTemplateInstance) -{ - if (stream != NULL) - { - SYSTEM_ERROR("Stream already created"); - delete stream; - stream = NULL; - } - - stream = newTemplateInstance; - if (!newTemplateInstance->fileExist()) - { - notFound(); - delete stream; - stream = NULL; - return false; - } - - if (!hasHeader("Content-Type")) - { - const char *mime = ContentType::fromFullFileName(newTemplateInstance->fileName()); - if (mime != NULL) - setContentType(mime); - } - return true; -} - -bool HttpResponse::sendJsonObject(JsonObjectStream* newJsonStreamInstance) -{ - if (stream != NULL) - { - SYSTEM_ERROR("Stream already created"); - delete stream; - stream = NULL; - } - - stream = newJsonStreamInstance; - if (!hasHeader("Content-Type")) - setContentType(ContentType::JSON); -} - -bool HttpResponse::sendDataStream( IDataSourceStream * newDataStream , String reqContentType /* = "" */) -{ - if (stream != NULL) - { - SYSTEM_ERROR("Stream already created"); - delete stream; - stream = NULL; - } - if (reqContentType != "") - { - setContentType(reqContentType); - } - stream = newDataStream; - - return true; -} - -/// - -void HttpResponse::sendHeader(HttpServerConnection &connection) -{ - if (headerSent) return; - - String top = "HTTP/1.1 " + status + "\r\n"; - connection.writeString(top.c_str(), TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); - for (int i = 0; i < responseHeaders.count(); i++) - { - String write = responseHeaders.keyAt(i) + ": " + responseHeaders.valueAt(i) + "\r\n"; - connection.writeString(write.c_str(), TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); - } - connection.writeString("\r\n"); - headerSent = true; -} - -bool HttpResponse::sendBody(HttpServerConnection &connection) -{ - if (!headerSent) sendHeader(connection); - if (stream == NULL) return true; - - connection.write(stream); - - if (stream->isFinished()) - { - connection.flush(); - bodySent = true; - debugf("Stream completed"); - delete stream; // Free memory now! - stream = NULL; - return true; - } - else - return false; -} diff --git a/Sming/SmingCore/Network/HttpResponse.h b/Sming/SmingCore/Network/HttpResponse.h deleted file mode 100644 index 5228aff7ca..0000000000 --- a/Sming/SmingCore/Network/HttpResponse.h +++ /dev/null @@ -1,76 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#ifndef _SMING_CORE_NETWORK_HTTPRESPONSE_H_ -#define _SMING_CORE_NETWORK_HTTPRESPONSE_H_ - -#include "../../SmingCore/DataSourceStream.h" -#include "WebConstants.h" -#include "../Wiring/WHashMap.h" -#include "../Wiring/WString.h" - -class pbuf; -class HttpServer; -class HttpServerConnection; - -class HttpResponse -{ -public: - HttpResponse(); - virtual ~HttpResponse(); - - void switchingProtocols(); - void badRequest(); - void notFound(); - void forbidden(); - void authorizationRequired(); - void redirect(String location = ""); - - void setContentType(const String type); - void setCookie(const String name, const String value); - void setHeader(const String name, const String value); - bool hasHeader(const String name); - - void setCache(int maxAgeSeconds = 3600, bool isPublic = false); - void setAllowCrossDomainOrigin(String controlAllowOrigin); // Access-Control-Allow-Origin for AJAX from a different domain - - String getStatusName(); - int getStatusCode(); - bool hasBody(); - - //*** This methods processed in background - - // Send string. Large memory allocation, only for short strings! - void sendString(const char* string); - void sendString(String string); - - // Send file by name - bool sendFile(String fileName, bool allowGzipFileCheck = true); - - // Parse and send template file - bool sendTemplate(TemplateFileStream* newTemplateInstance); - - // Build and send JSON string - bool sendJsonObject(JsonObjectStream* newJsonStreamInstance); - // Send Datastream, can be called with Classes derived from - bool sendDataStream( IDataSourceStream * newDataStream , String reqContentType = "" ); - //*** - -public: - void sendHeader(HttpServerConnection &connection); - bool sendBody(HttpServerConnection &connection); - -private: - bool headerSent; - bool bodySent; - String status; - HashMap responseHeaders; - - IDataSourceStream* stream; -}; - -#endif /* _SMING_CORE_NETWORK_HTTPRESPONSE_H_ */ diff --git a/Sming/SmingCore/Network/HttpServer.cpp b/Sming/SmingCore/Network/HttpServer.cpp index e4f7c5d820..6df1a6bec0 100644 --- a/Sming/SmingCore/Network/HttpServer.cpp +++ b/Sming/SmingCore/Network/HttpServer.cpp @@ -3,235 +3,92 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpServer + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #include "HttpServer.h" -#include "HttpRequest.h" -#include "HttpResponse.h" -#include "HttpServerConnection.h" #include "TcpClient.h" #include "../Wiring/WString.h" -#include "../../Services/cWebsocket/websocket.h" HttpServer::HttpServer() { - defaultHandler = NULL; - setTimeOut(90); - - // Default processing headers - // Add more in you app! - enableHeaderProcessing("Cookie"); - enableHeaderProcessing("Host"); - enableHeaderProcessing("Content-Type"); - enableHeaderProcessing("Content-Length"); - - enableHeaderProcessing("Upgrade"); -} - -HttpServer::~HttpServer() -{ + settings.keepAliveSeconds = 10; + configure(settings); } -TcpConnection* HttpServer::createClient(tcp_pcb *clientTcp) +HttpServer::HttpServer(HttpServerSettings settings) { - TcpConnection* con = new HttpServerConnection(this, clientTcp); - return con; + configure(settings); } -void HttpServer::enableHeaderProcessing(String headerName) -{ - headerName.toLowerCase(); - for (int i = 0; i < processingHeaders.count(); i++) - if (processingHeaders[i].equals(headerName)) - return; - - processingHeaders.add(headerName); -} - -void HttpServer::addPath(String path, HttpPathDelegate callback) -{ - if (path.length() > 1 && path.endsWith("/")) - path = path.substring(0, path.length() - 1); - if (!path.startsWith("/")) - path = "/" + path; - debugf("'%s' registered", path.c_str()); - paths[path] = callback; -} - -void HttpServer::setDefaultHandler(HttpPathDelegate callback) -{ - defaultHandler = callback; -} - -bool HttpServer::processRequest(HttpServerConnection &connection, HttpRequest &request, HttpResponse &response) -{ - if (request.isWebSocket()) - { - bool res = initWebSocket(connection, request, response); - if (!res) response.badRequest(); - } - String path = request.getPath(); - if (path.length() > 1 && path.endsWith("/")) - path = path.substring(0, path.length() - 1); - - if (paths.contains(path)) - { - paths[path](request, response); - return true; - } - - if (defaultHandler) - { - debugf("Default server handler for: '%s'", path.c_str()); - defaultHandler(request, response); - return true; +void HttpServer::configure(HttpServerSettings settings) { + this->settings = settings; + if(settings.minHeapSize != -1 && settings.minHeapSize > -1) { + minHeapSize = settings.minHeapSize; } - - debugf("ERROR at server 404: '%s' not found", path.c_str()); - return false; -} - -bool HttpServer::isHeaderProcessingEnabled(String name) -{ - for (int i = 0; i < processingHeaders.count(); i++) - if (processingHeaders[i].equals(name)) - return true; - - return false; -} - -bool HttpServer::initWebSocket(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) -{ - if (!wsEnabled) - return false; - - WebSocket *sock = new WebSocket(&connection); - if (!sock->initialize(request, response)) - return false; - - connection.setTimeOut(USHRT_MAX); //Disable disconnection on connection idle (no rx/tx) - connection.setDisconnectionHandler(HttpServerConnectionDelegate(&HttpServer::onCloseWebSocket, this)); // auto remove on close - response.sendHeader(connection); // Will push header before user data - - wsocks.addElement(sock); - if (wsConnect) wsConnect(*sock); - if (wsCommandEnabled && (request.getQueryParameter(wsCommandRequestParam) == "true")) - { -#if ENABLE_CMD_EXECUTOR - debugf("WebSocket Commandprocessor started"); -#else - debugf("WebSocket Commandprocessor support DISABLED via ENABLE_CMD_EXECUTOR"); + setTimeOut(settings.keepAliveSeconds); +#ifdef ENABLE_SSL + sslSessionCacheSize = settings.sslSessionCacheSize; #endif - sock->enableCommand(); - } } -void HttpServer::processWebSocketFrame(pbuf *buf, HttpServerConnection& connection) +HttpServer::~HttpServer() { - //TODO: process splitted payload - uint8_t* data; size_t size; - - wsFrameType frameType = (wsFrameType) 0x01; - uint8_t payloadFieldExtraBytes = 0; - size_t payloadLength = 0; - size_t payloadShift = 0; - do - { - payloadLength = getPayloadLength((uint8_t*)buf->payload + payloadShift, buf->len, &payloadFieldExtraBytes, &frameType); - -// debugf("payloadLength: %u, payLoadShift: %u, payloadFieldExtraBytes: %u\n", payloadLength, payloadShift, payloadFieldExtraBytes); - - wsFrameType frameType = wsParseInputFrame((uint8_t*)buf->payload + payloadShift, (payloadLength + 6 + payloadFieldExtraBytes), &data, &size); - WebSocket* sock = getWebSocket(connection); - - if (frameType == WS_TEXT_FRAME) - { - String msg; - msg.setString((char*)data, size); - debugf("WS: %s", msg.c_str()); - if (sock && wsMessage) wsMessage(*sock, msg); -#if ENABLE_CMD_EXECUTOR - if (sock && sock->commandExecutor) sock->commandExecutor->executorReceive(msg+"\r"); -#endif - } - else if (frameType == WS_BINARY_FRAME) - { - if (sock && wsMessage) wsBinary(*sock, data, size); + for(int i=0; i< resourceTree.count(); i++) { + if(resourceTree.valueAt(i) != NULL) { + delete resourceTree.valueAt(i); + } } - else if (frameType == WS_CLOSING_FRAME) - { - connection.close(); // it will be processed automatically in onCloseWebSocket callback - } - else if (frameType == WS_INCOMPLETE_FRAME || frameType == WS_ERROR_FRAME) - debugf("WS error reading frame: %X", frameType); - else - debugf("WS frame type: %X", frameType); - - payloadShift += payloadLength + 6 + payloadFieldExtraBytes; - } - while (buf->len > payloadShift); } -void HttpServer::setWebSocketConnectionHandler(WebSocketDelegate handler) +TcpConnection* HttpServer::createClient(tcp_pcb *clientTcp) { - wsConnect = handler; -} + HttpServerConnection* con = new HttpServerConnection(clientTcp); + con->setResourceTree(&resourceTree); + con->setCompleteDelegate(TcpClientCompleteDelegate(&HttpServer::onConnectionClose, this)); -void HttpServer::setWebSocketMessageHandler(WebSocketMessageDelegate handler) -{ - wsMessage = handler; -} + totalConnections++; + debugf("Opening connection. Total connections: %d", totalConnections); -void HttpServer::setWebSocketBinaryHandler(WebSocketBinaryDelegate handler) -{ - wsBinary = handler; -} - -void HttpServer::setWebSocketDisconnectionHandler(WebSocketDelegate handler) -{ - wsDisconnect = handler; + return con; } -WebSocket* HttpServer::getWebSocket(HttpServerConnection& connection) +void HttpServer::addPath(String path, const HttpPathDelegate& callback) { - for (int i = 0; i < wsocks.count(); i++) - if (wsocks[i].is(&connection)) - return &wsocks[i]; + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + debugf("'%s' registered", path.c_str()); - return nullptr; + HttpCompatResource* resource = new HttpCompatResource(callback); + resourceTree[path] = resource; } -void HttpServer::removeWebSocket(HttpServerConnection& connection) +void HttpServer::setDefaultHandler(const HttpPathDelegate& callback) { - debugf("WS remove connection item"); - for (int i = 0; i < wsocks.count(); i++) - if (wsocks[i].is(&connection)) - wsocks.remove(i--); + addPath("*", callback); } -void HttpServer::onCloseWebSocket(HttpServerConnection& connection) -{ - WebSocket* sock = getWebSocket(connection); - - removeWebSocket(connection); +void HttpServer::addPath(const String& path, const HttpResourceDelegate& onRequestComplete) { + HttpResource* resource = new HttpResource; + resource->onRequestComplete = onRequestComplete; + resourceTree[path] = resource; +} - debugf("WS Close"); - if (sock && wsDisconnect) wsDisconnect(*sock); +void HttpServer::addPath(const String& path, HttpResource* resource) { + resourceTree[path] = resource; } -void HttpServer::enableWebSockets(bool enabled) -{ - wsEnabled = enabled; - if (wsEnabled) - { - enableHeaderProcessing("Sec-WebSocket-Key"); - enableHeaderProcessing("Sec-WebSocket-Version"); - } +void HttpServer::setDefaultResource(HttpResource* resource) { + addPath("*", resource); } -void HttpServer::commandProcessing(bool reqEnabled, String reqRequestParam) -{ - wsCommandEnabled = reqEnabled; - wsCommandRequestParam = reqRequestParam; +void HttpServer::onConnectionClose(TcpClient& connection, bool success) { + totalConnections--; + debugf("Closing connection. Total connections: %d", totalConnections); } diff --git a/Sming/SmingCore/Network/HttpServer.h b/Sming/SmingCore/Network/HttpServer.h index 645b8452a9..56d77dfac1 100644 --- a/Sming/SmingCore/Network/HttpServer.h +++ b/Sming/SmingCore/Network/HttpServer.h @@ -3,76 +3,74 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpServer + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #ifndef _SMING_CORE_HTTPSERVER_H_ #define _SMING_CORE_HTTPSERVER_H_ #include "TcpServer.h" -#include "WebSocket.h" -#include "../../Wiring/WHashMap.h" -#include "../../Wiring/WVector.h" +#include "../Wiring/WString.h" +#include "../Wiring/WHashMap.h" #include "../Delegate.h" -#include "../../Services/CommandProcessing/CommandProcessingIncludes.h" +#include "Http/HttpResponse.h" +#include "Http/HttpRequest.h" +#include "Http/HttpResource.h" +#include "Http/HttpServerConnection.h" -class String; -class HttpServerConnection; -class HttpRequest; -class HttpResponse; - -typedef Vector WebSocketsList; - -typedef Delegate HttpPathDelegate; -typedef Delegate WebSocketDelegate; -typedef Delegate WebSocketMessageDelegate; -typedef Delegate WebSocketBinaryDelegate; +typedef struct { + int maxActiveConnections = 10; // << the maximum number of concurrent requests.. + int keepAliveSeconds = 5; // << the default seconds to keep the connection alive before closing it + int minHeapSize = -1; // << defines the min heap size that is required to accept connection. + // -1 - means use server default +#ifdef ENABLE_SSL + int sslSessionCacheSize = 10; // << number of SSL session ids to cache. Setting this to 0 will disable SSL session resumption. +#endif +} HttpServerSettings; class HttpServer: public TcpServer { friend class HttpServerConnection; + public: HttpServer(); + HttpServer(HttpServerSettings settings); virtual ~HttpServer(); - void enableHeaderProcessing(String headerName); - bool isHeaderProcessingEnabled(String name); + /* + * @brief Allows changing the server configuration + */ + void configure(HttpServerSettings settings); + + /** + * @param String path URL path. + * @note Path should start with slash. Trailing slashes will be removed. + * @param HttpPathDelegate callback - the callback that will handle this path + */ + void addPath(String path, const HttpPathDelegate& callback); + void addPath(const String& path, const HttpResourceDelegate& onRequestComplete); + void addPath(const String& path, HttpResource* resource); - void addPath(String path, HttpPathDelegate callback); - void setDefaultHandler(HttpPathDelegate callback); + void setDefaultHandler(const HttpPathDelegate& callback); + void setDefaultResource(HttpResource* resource); - /// Web Sockets - void enableWebSockets(bool enabled); - void commandProcessing(bool enabled, String reqReqestParam); - __forceinline WebSocketsList& getActiveWebSockets() { return wsocks; } - void setWebSocketConnectionHandler(WebSocketDelegate handler); - void setWebSocketMessageHandler(WebSocketMessageDelegate handler); - void setWebSocketBinaryHandler(WebSocketBinaryDelegate handler); - void setWebSocketDisconnectionHandler(WebSocketDelegate handler); protected: virtual TcpConnection* createClient(tcp_pcb *clientTcp); - virtual bool initWebSocket(HttpServerConnection &connection, HttpRequest &request, HttpResponse &response); - virtual bool processRequest(HttpServerConnection &connection, HttpRequest &request, HttpResponse &response); - virtual void processWebSocketFrame(pbuf *buf, HttpServerConnection &connection); + virtual void onConnectionClose(TcpClient& connection, bool success); - WebSocket* getWebSocket(HttpServerConnection &connection); - void removeWebSocket(HttpServerConnection &connection); - void onCloseWebSocket(HttpServerConnection &connection); +protected: +#ifdef ENABLE_SSL + int minHeapSize = 16384; +#endif private: - HttpPathDelegate defaultHandler; - Vector processingHeaders; - HashMap paths; - WebSocketsList wsocks; - - bool wsEnabled = false; - WebSocketDelegate wsConnect; - WebSocketMessageDelegate wsMessage; - WebSocketBinaryDelegate wsBinary; - WebSocketDelegate wsDisconnect; - - bool wsCommandEnabled = false; - String wsCommandRequestParam; + HttpServerSettings settings; + ResourceTree resourceTree; }; #endif /* _SMING_CORE_HTTPSERVER_H_ */ diff --git a/Sming/SmingCore/Network/HttpServerConnection.cpp b/Sming/SmingCore/Network/HttpServerConnection.cpp deleted file mode 100644 index 0d36d0731d..0000000000 --- a/Sming/SmingCore/Network/HttpServerConnection.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#include "HttpServerConnection.h" - -#include "HttpServer.h" -#include "TcpServer.h" -#include "../../Services/cWebsocket/websocket.h" - -HttpServerConnection::HttpServerConnection(HttpServer *parentServer, tcp_pcb *clientTcp) - : TcpConnection(clientTcp, true), server(parentServer), state(eHCS_Ready) -{ - TcpServer::totalConnections++; - response.setHeader("Connection", "close"); -} - -HttpServerConnection::~HttpServerConnection() -{ - TcpServer::totalConnections--; -} - -err_t HttpServerConnection::onReceive(pbuf *buf) -{ - if (buf == NULL) - { - TcpConnection::onReceive(buf); - return ERR_OK; - } - - /*{ - // split.buf.test - pbuf* dbghack = new pbuf(); - dbghack->payload = (char*)buf->payload + 100; - dbghack->len = buf->len - 100; - buf->len = 100; - buf->next = dbghack; - }*/ - - if (state == eHCS_Ready) - { - HttpParseResult res = request.parseHeader(server, buf); - if (res == eHPR_Wait) - debugf("HEADER WAIT"); - else if (res == eHPR_Failed) - { - debugf("HEADER FAILED"); - response.badRequest(); - sendError(); - } - else if (res == eHPR_Successful) - { - debugf("Request: %s, %s", request.getRequestMethod().c_str(), - (request.getContentLength() > 0 ? (String(request.getContentLength()) + " bytes").c_str() : "nodata")); - - String contType = request.getContentType(); - contType.toLowerCase(); - if (request.getContentLength() > 0 && request.getRequestMethod() == RequestMethod::POST) - state = eHCS_ParsePostData; - else - state = eHCS_ParsingCompleted; - } - } - else if (state == eHCS_WebSocketFrames) - { - server->processWebSocketFrame(buf, *this); - } - - if (state == eHCS_ParsePostData) - { - HttpParseResult res = request.parsePostData(server, buf); - if (res == eHPR_Wait) - debugf("POST WAIT"); - else if (res == eHPR_Failed) - { - debugf("POST FAILED"); - response.badRequest(); - sendError(); - } - else if (res == eHPR_Successful) - { - debugf("POST Parsed"); - state = eHCS_ParsingCompleted; - } - } - - TcpConnection::onReceive(buf); - - return ERR_OK; -} - -void HttpServerConnection::beginSendData() -{ - if (!server->processRequest(*this, request, response)) - { - response.notFound(); - sendError(); - return; - } - - if (!response.hasBody() && (response.getStatusCode() < 100 || response.getStatusCode() > 399)) - { - // Show default error message - sendError(); - return; - } - - debugf("response sendHeader"); - response.sendHeader(*this); - - if (request.isWebSocket()) - { - debugf("Switched to WebSocket Protocol"); - state = eHCS_WebSocketFrames; // Stay opened - } - else - state = eHCS_Sending; -} - -void HttpServerConnection::sendError(const char* message /* = NULL*/) -{ - debugf("SEND ERROR PAGE"); - response.setContentType(ContentType::HTML); - response.sendHeader(*this); - - writeString("

", TCP_WRITE_FLAG_MORE); - const char* statusName = response.getStatusName().c_str(); - writeString(message ? message : statusName, TCP_WRITE_FLAG_COPY); - writeString("

"); - state = eHCS_Sent; -} - -void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) -{ - TcpConnection::onReadyToSendData(sourceEvent); - - if (state == eHCS_ParsingCompleted) - beginSendData(); - - if (state == eHCS_Sending) - { - debugf("response sendBody"); - if (response.sendBody(*this)) - state = eHCS_Sent; // Completed! - } - - if (state == eHCS_Sent) - close(); -} - -void HttpServerConnection::close() -{ - if (disconnection) - { - disconnection(*this); - disconnection = nullptr; - } - TcpConnection::close(); -} - -void HttpServerConnection::onError(err_t err) -{ - if (disconnection) - { - disconnection(*this); - disconnection = nullptr; - } - TcpConnection::onError(err); -} - -void HttpServerConnection::setDisconnectionHandler(HttpServerConnectionDelegate handler) -{ - disconnection = handler; -} diff --git a/Sming/SmingCore/Network/HttpServerConnection.h b/Sming/SmingCore/Network/HttpServerConnection.h deleted file mode 100644 index d2e6dfc34a..0000000000 --- a/Sming/SmingCore/Network/HttpServerConnection.h +++ /dev/null @@ -1,59 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#ifndef _SMING_CORE_HTTPSERVERCONNECTION_H_ -#define _SMING_CORE_HTTPSERVERCONNECTION_H_ - -#include "HttpRequest.h" -#include "HttpResponse.h" -#include "TcpConnection.h" -#include "../Wiring/WString.h" -#include "../Delegate.h" - -class HttpServer; - -enum HttpConnectionState -{ - eHCS_Ready, - eHCS_ParsePostData, - eHCS_ParsingCompleted, - eHCS_Sending, - eHCS_WebSocketFrames, - eHCS_Sent -}; - -typedef Delegate HttpServerConnectionDelegate; - -class HttpServerConnection: public TcpConnection -{ -public: - HttpServerConnection(HttpServer *parentServer, tcp_pcb *clientTcp); - virtual ~HttpServerConnection(); - - virtual void close(); - void setDisconnectionHandler(HttpServerConnectionDelegate handler); - -protected: - virtual err_t onReceive(pbuf *buf); - virtual void onReadyToSendData(TcpConnectionEvent sourceEvent); - virtual void beginSendData(); - virtual void sendError(const char* message = NULL); - - virtual void onError(err_t err); - -private: - HttpServer *server; - HttpConnectionState state; - HttpRequest request; - HttpResponse response; - HttpServerConnectionDelegate disconnection; - - friend class HttpResponse; - friend class HttpRequest; -}; - -#endif /* _SMING_CORE_HTTPSERVERCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/WebConstants.h b/Sming/SmingCore/Network/WebConstants.h index 7b00a7e016..8d43855bb9 100644 --- a/Sming/SmingCore/Network/WebConstants.h +++ b/Sming/SmingCore/Network/WebConstants.h @@ -8,48 +8,63 @@ #ifndef _SMING_CORE_NETWORK_WEBCONSTANTS_H_ #define _SMING_CORE_NETWORK_WEBCONSTANTS_H_ -namespace ContentType +#define MIME_TYPE_MAP(XX) \ + /* Type, extension start, Mime type */ \ + \ + /* Texts */ \ + XX(HTML, "htm", "text/html") \ + XX(TEXT, "txt", "text/plain") \ + XX(JS, "js", "text/javascript") \ + XX(CSS, "css", "text/css") \ + XX(XML, "xml", "text/xml") \ + XX(JSON, "json", "application/json") \ + \ + /* Images */ \ + XX(JPEG, "jpg", "image/jpeg") \ + XX(GIF, "git", "image/gif") \ + XX(PNG, "png", "image/png") \ + XX(SVG, "svg", "image/svg+xml") \ + XX(ICO, "ico", "image/x-icon") \ + \ + /* Archives */ \ + XX(GZIP, "gzip", "application/x-gzip") \ + XX(ZIP, "zip", "application/zip") \ + \ + /* Binary and Form */ \ + XX(BINARY, "", "application/octet-stream") \ + XX(FORM_URL_ENCODED, "", "application/x-www-form-urlencoded") \ + XX(FORM_MULTIPART, "", "multipart/form-data") \ + +enum MimeType { - // Texts - static const char* HTML = "text/html"; - static const char* TEXT = "text/plain"; - static const char* JS = "text/javascript"; - static const char* CSS = "text/css"; - static const char* XML = "text/xml"; - - // Images - static const char* JPEG = "image/jpeg"; - static const char* GIF = "image/gif"; - static const char* PNG = "image/png"; - static const char* SVG = "image/svg+xml"; - static const char* ICO = "image/x-icon"; - - static const char* GZIP = "application/x-gzip"; - static const char* ZIP = "application/zip"; - static const char* JSON = "application/json"; - - static const char* Binary = "application/octet-stream"; - static const char* FormUrlEncoded = "application/x-www-form-urlencoded"; - static const char* FormMultipart = "multipart/form-data"; +#define XX(name, extensionStart, mime) MIME_##name, + MIME_TYPE_MAP(XX) +#undef XX +}; +namespace ContentType +{ static const char* fromFileExtension(const String extension) { String ext = extension; ext.toLowerCase(); - if (ext.startsWith("htm")) return HTML; - if (ext.equals("txt")) return TEXT; - if (ext.equals("js")) return JS; - if (ext.equals("css")) return CSS; + #define XX(name, extensionStart, mime) if(ext.startsWith(extensionStart)) { return mime; } + MIME_TYPE_MAP(XX) + #undef XX - if (ext.equals("jpg") || ext.equals("jpeg")) return JPEG; - if (ext.equals("gif")) return GIF; - if (ext.equals("png")) return PNG; - if (ext.equals("svg")) return SVG; - if (ext.equals("zip")) return ZIP; + // Unknown + return ""; + } - // Not found, we can't be sure - return NULL; + static const char *toString(enum MimeType m) + { + #define XX(name, extensionStart, mime) if(MIME_##name == m) { return mime; } + MIME_TYPE_MAP(XX) + #undef XX + + // Unknown + return ""; } static const char* fromFullFileName(const String fileName) @@ -66,27 +81,4 @@ namespace ContentType } }; -namespace RequestMethod -{ - static const char* GET = "GET"; - static const char* HEAD = "HEAD"; - static const char* POST = "POST"; - static const char* PUT = "PUT"; - static const char* DELETE = "DELETE"; -}; - -namespace HttpStatusCode -{ - static const char* OK = "200 OK"; - static const char* SwitchingProtocols = "101 Switching Protocols"; - static const char* Found = "302 Found"; - - static const char* BadRequest = "400 Bad Request"; - static const char* NotFound = "404 Not Found"; - static const char* Forbidden = "403 Forbidden"; - static const char* Unauthorized = "401 Unauthorized"; - - static const char* NotImplemented = "501 Not Implemented"; -}; - #endif /* _SMING_CORE_NETWORK_WEBCONSTANTS_H_ */ diff --git a/Sming/SmingCore/Network/WebSocket.cpp b/Sming/SmingCore/Network/WebSocket.cpp deleted file mode 100644 index 44aafe1b33..0000000000 --- a/Sming/SmingCore/Network/WebSocket.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#include "WebSocket.h" -#include "../../Services/WebHelpers/aw-sha1.h" -#include "../../Services/WebHelpers/base64.h" -#include "../../Services/CommandProcessing/CommandExecutor.h" - -WebSocket::WebSocket(HttpServerConnection* conn) -{ - connection = conn; -} - -WebSocket::~WebSocket() -{ -#if ENABLE_CMD_EXECUTOR - if (commandExecutor) - { - delete commandExecutor; - } -#endif -} - -bool WebSocket::initialize(HttpRequest& request, HttpResponse& response) -{ - String version = request.getHeader("Sec-WebSocket-Version"); - version.trim(); - if (version.toInt() != 13) // 1.3 - return false; - - String hash = request.getHeader("Sec-WebSocket-Key"); - hash.trim(); - hash = hash + secret; - unsigned char data[SHA1_SIZE]; - char secure[SHA1_SIZE * 4]; - sha1(data, hash.c_str(), hash.length()); - base64_encode(SHA1_SIZE, data, SHA1_SIZE * 4, secure); - response.switchingProtocols(); - response.setHeader("Connection", "Upgrade"); - response.setHeader("Upgrade", "websocket"); - response.setHeader("Sec-WebSocket-Accept", secure); - return true; -} - -void WebSocket::send(const char* message, int length, wsFrameType type) -{ - uint8_t frameHeader[16] = {0}; - size_t headSize = sizeof(frameHeader); - wsMakeFrame(nullptr, length, frameHeader, &headSize, type); - connection->write((char*)frameHeader, headSize, TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE); - connection->write(message, length, TCP_WRITE_FLAG_COPY); -} - -void WebSocket::sendString(const String& message) -{ - send(message.c_str(), message.length()); -} - -void WebSocket::sendBinary(const uint8_t* data, int size) -{ - send((char*)data, size, WS_BINARY_FRAME); -} - -void WebSocket::enableCommand() -{ -#if ENABLE_CMD_EXECUTOR - if (!commandExecutor) - { - commandExecutor = new CommandExecutor(this); - } -#endif -} - -void WebSocket::close() -{ - connection->close(); -} - -void WebSocket::setUserData(void* userData) -{ - this->userData = userData; -} - -void* WebSocket::getUserData() -{ - return userData; -} diff --git a/Sming/SmingCore/Network/WebSocket.h b/Sming/SmingCore/Network/WebSocket.h deleted file mode 100644 index b30caecb6b..0000000000 --- a/Sming/SmingCore/Network/WebSocket.h +++ /dev/null @@ -1,91 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - ****/ - -#ifndef SMINGCORE_NETWORK_WEBSOCKET_H_ -#define SMINGCORE_NETWORK_WEBSOCKET_H_ - -#include "TcpServer.h" -#include "HttpServerConnection.h" -#include "../../Wiring/WHashMap.h" -#include "../../Wiring/WVector.h" -#include "../Delegate.h" -#include "../../Services/cWebsocket/websocket.h" - -class HttpServer; -class CommandExecutor; - -class WebSocket -{ - friend class HttpServer; -public: - /** @brief Object class constructor - * @param conn Pointer to HttpServer connection object that this webSocket will use - */ - WebSocket(HttpServerConnection* conn); - - /** @brief Virtual object class destructor - */ - virtual ~WebSocket(); - - /** @brief Send data to other peer - * @param message Pointer to buffer to send - * @param length Length of buffer to send - * @param type Frametype of the message to be sent. Default is text frame. - */ - virtual void send(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); - - /** @brief Send a string to other peer - * @param message String object holding the message to send - */ - void sendString(const String& message); - - /** @brief Send binary data to other peer - * @param data Pointer to buffer to send - * @param size Length of buffer to send - */ - void sendBinary(const uint8_t* data, int size); - - /** @brief Enable command processing - */ - void enableCommand(); - - /** @brief Close webSocket connection - */ - void close(); - - /** @brief Set TCP connection timeout - * @param waitTimeOut TCP timeout - */ - void setTimeOut(uint16_t waitTimeOut) { if(connection) connection->setTimeOut(waitTimeOut); }; - - /** @brief Test if two sockets are the same - * @param other The other webSocket - * @return true if the two webSockets are defining the same connection - */ - bool operator==(const WebSocket &other) const { return this->connection == other.connection;}; - - /** @brief Store user data pointer for this socket(connection) - * @param userData Pointer to user defined data - */ - void setUserData(void* userData); - - /** @brief Get user data pointer for this socket(connection) - * @return Pointer to user defined data - */ - void *getUserData(); - -protected: - bool initialize(HttpRequest &request, HttpResponse &response); - bool is(HttpServerConnection* conn) { return connection == conn; }; - -private: - HttpServerConnection* connection; - CommandExecutor* commandExecutor = nullptr; - void* userData = nullptr; -}; - -#endif /* SMINGCORE_NETWORK_WEBSOCKET_H_ */ diff --git a/Sming/SmingCore/Network/rBootHttpUpdate.cpp b/Sming/SmingCore/Network/rBootHttpUpdate.cpp index 3fc5f7a0cb..9a4e66d359 100644 --- a/Sming/SmingCore/Network/rBootHttpUpdate.cpp +++ b/Sming/SmingCore/Network/rBootHttpUpdate.cpp @@ -3,6 +3,8 @@ * * Created on: 2015/09/03. * Author: Richard A Burton & Anakod + * + * Modified: 2017 - Slavey Karadzhov */ #include "rBootHttpUpdate.h" @@ -10,6 +12,51 @@ #include "URL.h" #include "../Platform/WDT.h" +void rBootItemOutputStream::setItem(rBootHttpUpdateItem* item) { + this->item = item; +} + +bool rBootItemOutputStream::init() { + if(item == NULL) { + debugf("rBootItemOutputStream: Item must be set!"); + return false; + } + + rBootWriteStatus = rboot_write_init( this->item->targetOffset ); + initilized = true; + + return true; +} + +size_t rBootItemOutputStream::write(const uint8_t* data, size_t size) { + if(!initilized && size > 0) { + if(!init()) { // unable to initialize + return -1; + } + + initilized = true; + } + + if(!rboot_write_flash(&rBootWriteStatus, (uint8_t *)data, size)) { + debugf("rboot_write_flash: Failed. Size: %d", size); + return -1; + } + + item->size += size; + + debugf("rboot_write_flash: item.size: %d", item->size); + + return size; +} + +bool rBootItemOutputStream::close() { + return rboot_write_end(&rBootWriteStatus); +} + +rBootItemOutputStream::~rBootItemOutputStream() { + close(); +} + rBootHttpUpdate::rBootHttpUpdate() { currentItem = 0; romSlot = NO_ROM_SWITCH; @@ -27,109 +74,109 @@ void rBootHttpUpdate::addItem(int offset, String firmwareFileUrl) { items.add(add); } -void rBootHttpUpdate::start() { - timer.initializeMs(500, TimerDelegate(&rBootHttpUpdate::onTimer, this)).start(); +void rBootHttpUpdate::setBaseRequest(HttpRequest *request) { + baseRequest = request; } -void rBootHttpUpdate::switchToRom(uint8 romSlot) { - this->romSlot = romSlot; -} +void rBootHttpUpdate::start() { + for(int i=0; i< items.count(); i++) { + rBootHttpUpdateItem &it = items[i]; + debugf("Download file:\r\n (%d) %s -> %X", currentItem, it.url.c_str(), it.targetOffset); + + HttpRequest *request; + if(baseRequest != NULL) { + request = baseRequest->clone(); + request->setURL(URL(it.url)); + } + else { + request = new HttpRequest(URL(it.url)); + } -void rBootHttpUpdate::setCallback(OtaUpdateDelegate reqUpdateDelegate) { - setDelegate(reqUpdateDelegate); + request->setMethod(HTTP_GET); + + rBootItemOutputStream *responseStream = getStream(); + responseStream->setItem(&it); + + request->setResponseStream(responseStream); + + if(i == items.count() - 1) { + request->onRequestComplete(RequestCompletedDelegate(&rBootHttpUpdate::updateComplete, this)); + } + else { + request->onRequestComplete(RequestCompletedDelegate(&rBootHttpUpdate::itemComplete, this)); + } + + if(!send(request)) { + debugf("ERROR: Rejected sending new request."); + break; + } + } } -void rBootHttpUpdate::setDelegate(OtaUpdateDelegate reqUpdateDelegate) { - this->updateDelegate = reqUpdateDelegate; +rBootItemOutputStream* rBootHttpUpdate::getStream() { + return new rBootItemOutputStream(); } -void rBootHttpUpdate::updateFailed() { - timer.stop(); - debugf("\r\nFirmware download failed.."); - if (updateDelegate) updateDelegate(*this, false); - items.clear(); +int rBootHttpUpdate::itemComplete(HttpConnection& client, bool success) { + if(!success) { + updateFailed(); + return -1; + } + + return 0; } -void rBootHttpUpdate::onTimer() { - - if (TcpClient::isProcessing()) return; // Will wait - - if (TcpClient::getConnectionState() == eTCS_Successful) { - - // always call writeEnd() - if (!writeEnd()) { - debugf("final checks failed!"); - writeError = 1; - } - - if (!isSuccessful()) { - updateFailed(); - return; - } - - currentItem++; - if (currentItem >= items.count()) { - debugf("\r\nFirmware download finished!"); - for (int i = 0; i < items.count(); i++) { - debugf(" - item: %d, addr: %X, len: %d bytes", i, items[i].targetOffset, items[i].size); - } - - applyUpdate(); - return; - } - - } else if (TcpClient::getConnectionState() == eTCS_Failed) { +int rBootHttpUpdate::updateComplete(HttpConnection& client, bool success) { + debugf("\r\nFirmware download finished!"); + for (int i = 0; i < items.count(); i++) { + debugf(" - item: %d, addr: %X, len: %d bytes", i, items[i].targetOffset, items[i].size); + } + + if(!success) { updateFailed(); - return; + return -1; } - - rBootHttpUpdateItem &it = items[currentItem]; - debugf("Download file:\r\n (%d) %s -> %X", currentItem, it.url.c_str(), it.targetOffset); - writeInit(); - startDownload(URL(it.url), eHCM_UserDefined, NULL); -} - - -void rBootHttpUpdate::writeRawData(pbuf* buf, int startPos) { - pbuf *cur = buf; - while (cur != NULL && cur->len > 0 && !writeError) { - uint8* ptr = (uint8*) cur->payload + startPos; - int len = cur->len - startPos; - writeError = !writeFlash(ptr, len); - if (writeError) { - debugf("Write Error!"); - } - items[currentItem].size += len; - cur = cur->next; - startPos = 0; + + if(updateDelegate) { + updateDelegate(*this, true); } + + applyUpdate(); + + return 0; } -void rBootHttpUpdate::writeInit() { - rBootWriteStatus = rboot_write_init( items[currentItem].targetOffset ); +void rBootHttpUpdate::switchToRom(uint8_t romSlot) { + this->romSlot = romSlot; } -bool rBootHttpUpdate::writeFlash(const u8 *data, u16 size) { - return rboot_write_flash(&rBootWriteStatus, (u8 *) data, size ); +void rBootHttpUpdate::setCallback(OtaUpdateDelegate reqUpdateDelegate) { + setDelegate(reqUpdateDelegate); } -bool rBootHttpUpdate::writeEnd() { - return rboot_write_end(&rBootWriteStatus); +void rBootHttpUpdate::setDelegate(OtaUpdateDelegate reqUpdateDelegate) { + this->updateDelegate = reqUpdateDelegate; +} + +void rBootHttpUpdate::updateFailed() { + debugf("\r\nFirmware download failed.."); + if (updateDelegate) { + updateDelegate(*this, false); + } + items.clear(); } void rBootHttpUpdate::applyUpdate() { - timer.stop(); + items.clear(); if (romSlot == NO_ROM_SWITCH) { debugf("Firmware updated."); - if (updateDelegate) updateDelegate(*this, true); - items.clear(); - } else { - // set to boot new rom and then reboot - debugf("Firmware updated, rebooting to rom %d...\r\n", romSlot); - rboot_set_current_rom(romSlot); - System.restart(); + return; } - return; + + // set to boot new rom and then reboot + debugf("Firmware updated, rebooting to rom %d...\r\n", romSlot); + rboot_set_current_rom(romSlot); + System.restart(); } rBootHttpUpdateItem rBootHttpUpdate::getItem(unsigned int index) { diff --git a/Sming/SmingCore/Network/rBootHttpUpdate.h b/Sming/SmingCore/Network/rBootHttpUpdate.h index 354aa73653..bf982a05ef 100644 --- a/Sming/SmingCore/Network/rBootHttpUpdate.h +++ b/Sming/SmingCore/Network/rBootHttpUpdate.h @@ -3,14 +3,15 @@ * * Created on: 2015/09/03. * Author: Richard A Burton & Anakod + * + * Modified: 2017 - Slavey Karadzhov */ #ifndef SMINGCORE_NETWORK_RBOOTHTTPUPDATE_H_ #define SMINGCORE_NETWORK_RBOOTHTTPUPDATE_H_ +#include "../OutputStream.h" #include "HttpClient.h" -#include - #include #define NO_ROM_SWITCH 0xff @@ -26,6 +27,20 @@ struct rBootHttpUpdateItem { int size; }; +class rBootItemOutputStream: public IOutputStream { +public: + void setItem(rBootHttpUpdateItem* item); + virtual bool init(); + virtual size_t write(const uint8_t* data, size_t size); + virtual bool close(); + virtual ~rBootItemOutputStream(); + +protected: + bool initilized = false; + rBootHttpUpdateItem* item = NULL; + rboot_write_status rBootWriteStatus; +}; + class rBootHttpUpdate: protected HttpClient { public: @@ -33,45 +48,40 @@ class rBootHttpUpdate: protected HttpClient { virtual ~rBootHttpUpdate(); void addItem(int offset, String firmwareFileUrl); void start(); - void switchToRom(uint8 romSlot); + void switchToRom(uint8_t romSlot); void setCallback(OtaUpdateDelegate reqUpdateDelegate); void setDelegate(OtaUpdateDelegate reqUpdateDelegate); - - // Expose request and response header information - using HttpClient::setRequestHeader; - using HttpClient::hasRequestHeader; - using HttpClient::getResponseHeader; + /* Sets the base request that can be used to pass + * - default request parameters, like request headers... + * - default SSL options + * - default SSL fingeprints + * - default SSL client certificates + * + * @param HttpRequest * + */ + void setBaseRequest(HttpRequest *request); // Allow reading items rBootHttpUpdateItem getItem(unsigned int index); -#ifdef ENABLE_SSL - using HttpClient::addSslOptions; - using HttpClient::setSslFingerprint; - using HttpClient::pinCertificate; - using HttpClient::setSslClientKeyCert; - using HttpClient::freeSslClientKeyCert; - using HttpClient::getSsl; -#endif - protected: - void onTimer(); - virtual void writeRawData(pbuf* buf, int startPos); void applyUpdate(); void updateFailed(); + virtual rBootItemOutputStream* getStream(); + virtual int itemComplete(HttpConnection& client, bool success); + virtual int updateComplete(HttpConnection& client, bool success); + + protected: Vector items; - Timer timer; int currentItem; rboot_write_status rBootWriteStatus; - uint8 romSlot; + uint8_t romSlot; OtaUpdateDelegate updateDelegate; - virtual void writeInit(); - virtual bool writeFlash(const u8 *data, u16 size); - virtual bool writeEnd(); + HttpRequest* baseRequest = NULL; }; #endif /* SMINGCORE_NETWORK_RBOOTHTTPUPDATE_H_ */ diff --git a/Sming/SmingCore/OutputStream.cpp b/Sming/SmingCore/OutputStream.cpp new file mode 100644 index 0000000000..3c88fbb9ce --- /dev/null +++ b/Sming/SmingCore/OutputStream.cpp @@ -0,0 +1,35 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#include "OutputStream.h" + +FileOutputStream::FileOutputStream(String fileName, FileOpenFlags flags /* = eFO_CreateNewAlways | eFO_WriteOnly */) { + handle = fileOpen(fileName, flags); +} + +size_t FileOutputStream::write(const uint8_t* data, size_t size) { + if(handle < 1) { + return -1; // report problems.. + } + + return fileWrite(handle, data, size); +} + +bool FileOutputStream::close() { + if(handle < 1) { + return false; // report problems.. + } + + fileClose(handle); + handle = 0; + return true; +} + +FileOutputStream::~FileOutputStream() +{ + close(); +} diff --git a/Sming/SmingCore/OutputStream.h b/Sming/SmingCore/OutputStream.h new file mode 100644 index 0000000000..99914242f4 --- /dev/null +++ b/Sming/SmingCore/OutputStream.h @@ -0,0 +1,41 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * + * Authors: 2017-... Slavey Karadzhov + * + * All files of the Sming Core are provided under the LGPL v3 license. + ****/ + +#ifndef _SMING_CORE_OUTPUTSTREAM_H_ +#define _SMING_CORE_OUTPUTSTREAM_H_ + +#include +#include "../../Wiring/WString.h" +#include "../SmingCore/FileSystem.h" + +class IOutputStream +{ +public: + virtual ~IOutputStream() {} + + virtual size_t write(const uint8_t* data, size_t size) = 0; + virtual bool close() = 0; +}; + +class FileOutputStream: public IOutputStream +{ +public: + FileOutputStream(String filename, FileOpenFlags flags = eFO_CreateNewAlways | eFO_WriteOnly); + + virtual size_t write(const uint8_t* data, size_t size); + + virtual bool close(); + + ~FileOutputStream(); + +private: + file_t handle; +}; + +#endif /* _SMING_CORE_OUTPUTSTREAM_H_ */ diff --git a/Sming/SmingCore/SmingCore.h b/Sming/SmingCore/SmingCore.h index 538b7e80e7..ca273ff3cc 100644 --- a/Sming/SmingCore/SmingCore.h +++ b/Sming/SmingCore/SmingCore.h @@ -40,8 +40,8 @@ #include "Network/MqttClient.h" #include "Network/NtpClient.h" #include "Network/HttpServer.h" -#include "Network/HttpRequest.h" -#include "Network/HttpResponse.h" +#include "Network/Http/HttpRequest.h" +#include "Network/Http/HttpResponse.h" #include "Network/FTPServer.h" #include "Network/NetUtils.h" #include "Network/TcpClient.h" diff --git a/Sming/Wiring/FIFO.h b/Sming/Wiring/FIFO.h index b26f3e7f13..0b860822fe 100644 --- a/Sming/Wiring/FIFO.h +++ b/Sming/Wiring/FIFO.h @@ -51,7 +51,7 @@ class FIFO : public Countable return raw[index]; /* unsafe */ } - private: + protected: volatile int numberOfElements; int nextIn; int nextOut; diff --git a/Sming/third-party/.patches/http-parser.patch b/Sming/third-party/.patches/http-parser.patch new file mode 100644 index 0000000000..2e340f410e --- /dev/null +++ b/Sming/third-party/.patches/http-parser.patch @@ -0,0 +1,4406 @@ +diff --git a/bench.c b/bench.c +deleted file mode 100644 +index 5b452fa..0000000 +--- a/bench.c ++++ /dev/null +@@ -1,111 +0,0 @@ +-/* Copyright Fedor Indutny. All rights reserved. +- * +- * Permission is hereby granted, free of charge, to any person obtaining a copy +- * of this software and associated documentation files (the "Software"), to +- * deal in the Software without restriction, including without limitation the +- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +- * sell copies of the Software, and to permit persons to whom the Software is +- * furnished to do so, subject to the following conditions: +- * +- * The above copyright notice and this permission notice shall be included in +- * all copies or substantial portions of the Software. +- * +- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +- * IN THE SOFTWARE. +- */ +-#include "http_parser.h" +-#include +-#include +-#include +-#include +- +-static const char data[] = +- "POST /joyent/http-parser HTTP/1.1\r\n" +- "Host: github.com\r\n" +- "DNT: 1\r\n" +- "Accept-Encoding: gzip, deflate, sdch\r\n" +- "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" +- "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " +- "AppleWebKit/537.36 (KHTML, like Gecko) " +- "Chrome/39.0.2171.65 Safari/537.36\r\n" +- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," +- "image/webp,*/*;q=0.8\r\n" +- "Referer: https://github.com/joyent/http-parser\r\n" +- "Connection: keep-alive\r\n" +- "Transfer-Encoding: chunked\r\n" +- "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n"; +-static const size_t data_len = sizeof(data) - 1; +- +-static int on_info(http_parser* p) { +- return 0; +-} +- +- +-static int on_data(http_parser* p, const char *at, size_t length) { +- return 0; +-} +- +-static http_parser_settings settings = { +- .on_message_begin = on_info, +- .on_headers_complete = on_info, +- .on_message_complete = on_info, +- .on_header_field = on_data, +- .on_header_value = on_data, +- .on_url = on_data, +- .on_status = on_data, +- .on_body = on_data +-}; +- +-int bench(int iter_count, int silent) { +- struct http_parser parser; +- int i; +- int err; +- struct timeval start; +- struct timeval end; +- float rps; +- +- if (!silent) { +- err = gettimeofday(&start, NULL); +- assert(err == 0); +- } +- +- for (i = 0; i < iter_count; i++) { +- size_t parsed; +- http_parser_init(&parser, HTTP_REQUEST); +- +- parsed = http_parser_execute(&parser, &settings, data, data_len); +- assert(parsed == data_len); +- } +- +- if (!silent) { +- err = gettimeofday(&end, NULL); +- assert(err == 0); +- +- fprintf(stdout, "Benchmark result:\n"); +- +- rps = (float) (end.tv_sec - start.tv_sec) + +- (end.tv_usec - start.tv_usec) * 1e-6f; +- fprintf(stdout, "Took %f seconds to run\n", rps); +- +- rps = (float) iter_count / rps; +- fprintf(stdout, "%f req/sec\n", rps); +- fflush(stdout); +- } +- +- return 0; +-} +- +-int main(int argc, char** argv) { +- if (argc == 2 && strcmp(argv[1], "infinite") == 0) { +- for (;;) +- bench(5000000, 1); +- return 0; +- } else { +- return bench(5000000, 0); +- } +-} +diff --git a/http_parser.c b/http_parser.c +index 895bf0c..050d0b7 100644 +--- a/http_parser.c ++++ b/http_parser.c +@@ -22,13 +22,23 @@ + * IN THE SOFTWARE. + */ + #include "http_parser.h" +-#include +-#include ++#ifdef __ets__ ++ #include ++#endif + #include + #include + #include + #include + ++#ifdef __ets__ ++ #include ++ #include "m_printf.h" ++ #undef assert ++ #define assert(condition) if (!(condition)) m_printf("ASSERT: %s %d", __FUNCTION__, __LINE__) ++#else ++ #include ++#endif ++ + #ifndef ULLONG_MAX + # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ + #endif +@@ -186,7 +196,7 @@ static const char *method_strings[] = + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +-static const char tokens[256] = { ++static const char tokens[256] PROGMEM_L32 = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ +@@ -221,7 +231,7 @@ static const char tokens[256] = { + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +-static const int8_t unhex[256] = ++static const int8_t unhex[256] PROGMEM_L32 = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +@@ -240,7 +250,7 @@ static const int8_t unhex[256] = + #endif + + +-static const uint8_t normal_url_char[32] = { ++static const uint8_t normal_url_char[32] PROGMEM_L32 = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ +diff --git a/test.c b/test.c +deleted file mode 100644 +index f5744aa..0000000 +--- a/test.c ++++ /dev/null +@@ -1,4226 +0,0 @@ +-/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. +- * +- * Permission is hereby granted, free of charge, to any person obtaining a copy +- * of this software and associated documentation files (the "Software"), to +- * deal in the Software without restriction, including without limitation the +- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +- * sell copies of the Software, and to permit persons to whom the Software is +- * furnished to do so, subject to the following conditions: +- * +- * The above copyright notice and this permission notice shall be included in +- * all copies or substantial portions of the Software. +- * +- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +- * IN THE SOFTWARE. +- */ +-#include "http_parser.h" +-#include +-#include +-#include +-#include /* rand */ +-#include +-#include +- +-#if defined(__APPLE__) +-# undef strlcat +-# undef strlncpy +-# undef strlcpy +-#endif /* defined(__APPLE__) */ +- +-#undef TRUE +-#define TRUE 1 +-#undef FALSE +-#define FALSE 0 +- +-#define MAX_HEADERS 13 +-#define MAX_ELEMENT_SIZE 2048 +-#define MAX_CHUNKS 16 +- +-#define MIN(a,b) ((a) < (b) ? (a) : (b)) +- +-static http_parser *parser; +- +-struct message { +- const char *name; // for debugging purposes +- const char *raw; +- enum http_parser_type type; +- enum http_method method; +- int status_code; +- char response_status[MAX_ELEMENT_SIZE]; +- char request_path[MAX_ELEMENT_SIZE]; +- char request_url[MAX_ELEMENT_SIZE]; +- char fragment[MAX_ELEMENT_SIZE]; +- char query_string[MAX_ELEMENT_SIZE]; +- char body[MAX_ELEMENT_SIZE]; +- size_t body_size; +- const char *host; +- const char *userinfo; +- uint16_t port; +- int num_headers; +- enum { NONE=0, FIELD, VALUE } last_header_element; +- char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; +- int should_keep_alive; +- +- int num_chunks; +- int num_chunks_complete; +- int chunk_lengths[MAX_CHUNKS]; +- +- const char *upgrade; // upgraded body +- +- unsigned short http_major; +- unsigned short http_minor; +- +- int message_begin_cb_called; +- int headers_complete_cb_called; +- int message_complete_cb_called; +- int message_complete_on_eof; +- int body_is_final; +-}; +- +-static int currently_parsing_eof; +- +-static struct message messages[5]; +-static int num_messages; +-static http_parser_settings *current_pause_parser; +- +-/* * R E Q U E S T S * */ +-const struct message requests[] = +-#define CURL_GET 0 +-{ {.name= "curl get" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /test HTTP/1.1\r\n" +- "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" +- "Host: 0.0.0.0=5000\r\n" +- "Accept: */*\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/test" +- ,.request_url= "/test" +- ,.num_headers= 3 +- ,.headers= +- { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } +- , { "Host", "0.0.0.0=5000" } +- , { "Accept", "*/*" } +- } +- ,.body= "" +- } +- +-#define FIREFOX_GET 1 +-, {.name= "firefox get" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /favicon.ico HTTP/1.1\r\n" +- "Host: 0.0.0.0=5000\r\n" +- "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" +- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +- "Accept-Language: en-us,en;q=0.5\r\n" +- "Accept-Encoding: gzip,deflate\r\n" +- "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +- "Keep-Alive: 300\r\n" +- "Connection: keep-alive\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/favicon.ico" +- ,.request_url= "/favicon.ico" +- ,.num_headers= 8 +- ,.headers= +- { { "Host", "0.0.0.0=5000" } +- , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } +- , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } +- , { "Accept-Language", "en-us,en;q=0.5" } +- , { "Accept-Encoding", "gzip,deflate" } +- , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } +- , { "Keep-Alive", "300" } +- , { "Connection", "keep-alive" } +- } +- ,.body= "" +- } +- +-#define DUMBFUCK 2 +-, {.name= "dumbfuck" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /dumbfuck HTTP/1.1\r\n" +- "aaaaaaaaaaaaa:++++++++++\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/dumbfuck" +- ,.request_url= "/dumbfuck" +- ,.num_headers= 1 +- ,.headers= +- { { "aaaaaaaaaaaaa", "++++++++++" } +- } +- ,.body= "" +- } +- +-#define FRAGMENT_IN_URI 3 +-, {.name= "fragment in url" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "page=1" +- ,.fragment= "posts-17408" +- ,.request_path= "/forums/1/topics/2375" +- /* XXX request url does include fragment? */ +- ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" +- ,.num_headers= 0 +- ,.body= "" +- } +- +-#define GET_NO_HEADERS_NO_BODY 4 +-, {.name= "get no headers no body" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE /* would need Connection: close */ +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/get_no_headers_no_body/world" +- ,.request_url= "/get_no_headers_no_body/world" +- ,.num_headers= 0 +- ,.body= "" +- } +- +-#define GET_ONE_HEADER_NO_BODY 5 +-, {.name= "get one header no body" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" +- "Accept: */*\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE /* would need Connection: close */ +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/get_one_header_no_body" +- ,.request_url= "/get_one_header_no_body" +- ,.num_headers= 1 +- ,.headers= +- { { "Accept" , "*/*" } +- } +- ,.body= "" +- } +- +-#define GET_FUNKY_CONTENT_LENGTH 6 +-, {.name= "get funky content length body hello" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" +- "conTENT-Length: 5\r\n" +- "\r\n" +- "HELLO" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/get_funky_content_length_body_hello" +- ,.request_url= "/get_funky_content_length_body_hello" +- ,.num_headers= 1 +- ,.headers= +- { { "conTENT-Length" , "5" } +- } +- ,.body= "HELLO" +- } +- +-#define POST_IDENTITY_BODY_WORLD 7 +-, {.name= "post identity body world" +- ,.type= HTTP_REQUEST +- ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" +- "Accept: */*\r\n" +- "Transfer-Encoding: identity\r\n" +- "Content-Length: 5\r\n" +- "\r\n" +- "World" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "q=search" +- ,.fragment= "hey" +- ,.request_path= "/post_identity_body_world" +- ,.request_url= "/post_identity_body_world?q=search#hey" +- ,.num_headers= 3 +- ,.headers= +- { { "Accept", "*/*" } +- , { "Transfer-Encoding", "identity" } +- , { "Content-Length", "5" } +- } +- ,.body= "World" +- } +- +-#define POST_CHUNKED_ALL_YOUR_BASE 8 +-, {.name= "post - chunked body: all your base are belong to us" +- ,.type= HTTP_REQUEST +- ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "1e\r\nall your base are belong to us\r\n" +- "0\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/post_chunked_all_your_base" +- ,.request_url= "/post_chunked_all_your_base" +- ,.num_headers= 1 +- ,.headers= +- { { "Transfer-Encoding" , "chunked" } +- } +- ,.body= "all your base are belong to us" +- ,.num_chunks_complete= 2 +- ,.chunk_lengths= { 0x1e } +- } +- +-#define TWO_CHUNKS_MULT_ZERO_END 9 +-, {.name= "two chunks ; triple zero ending" +- ,.type= HTTP_REQUEST +- ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "5\r\nhello\r\n" +- "6\r\n world\r\n" +- "000\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/two_chunks_mult_zero_end" +- ,.request_url= "/two_chunks_mult_zero_end" +- ,.num_headers= 1 +- ,.headers= +- { { "Transfer-Encoding", "chunked" } +- } +- ,.body= "hello world" +- ,.num_chunks_complete= 3 +- ,.chunk_lengths= { 5, 6 } +- } +- +-#define CHUNKED_W_TRAILING_HEADERS 10 +-, {.name= "chunked with trailing headers. blech." +- ,.type= HTTP_REQUEST +- ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "5\r\nhello\r\n" +- "6\r\n world\r\n" +- "0\r\n" +- "Vary: *\r\n" +- "Content-Type: text/plain\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/chunked_w_trailing_headers" +- ,.request_url= "/chunked_w_trailing_headers" +- ,.num_headers= 3 +- ,.headers= +- { { "Transfer-Encoding", "chunked" } +- , { "Vary", "*" } +- , { "Content-Type", "text/plain" } +- } +- ,.body= "hello world" +- ,.num_chunks_complete= 3 +- ,.chunk_lengths= { 5, 6 } +- } +- +-#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 +-, {.name= "with bullshit after the length" +- ,.type= HTTP_REQUEST +- ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" +- "6; blahblah; blah\r\n world\r\n" +- "0\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/chunked_w_bullshit_after_length" +- ,.request_url= "/chunked_w_bullshit_after_length" +- ,.num_headers= 1 +- ,.headers= +- { { "Transfer-Encoding", "chunked" } +- } +- ,.body= "hello world" +- ,.num_chunks_complete= 3 +- ,.chunk_lengths= { 5, 6 } +- } +- +-#define WITH_QUOTES 12 +-, {.name= "with quotes" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "foo=\"bar\"" +- ,.fragment= "" +- ,.request_path= "/with_\"stupid\"_quotes" +- ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define APACHEBENCH_GET 13 +-/* The server receiving this request SHOULD NOT wait for EOF +- * to know that content-length == 0. +- * How to represent this in a unit test? message_complete_on_eof +- * Compare with NO_CONTENT_LENGTH_RESPONSE. +- */ +-, {.name = "apachebench get" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /test HTTP/1.0\r\n" +- "Host: 0.0.0.0:5000\r\n" +- "User-Agent: ApacheBench/2.3\r\n" +- "Accept: */*\r\n\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/test" +- ,.request_url= "/test" +- ,.num_headers= 3 +- ,.headers= { { "Host", "0.0.0.0:5000" } +- , { "User-Agent", "ApacheBench/2.3" } +- , { "Accept", "*/*" } +- } +- ,.body= "" +- } +- +-#define QUERY_URL_WITH_QUESTION_MARK_GET 14 +-/* Some clients include '?' characters in query strings. +- */ +-, {.name = "query url with question mark" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "foo=bar?baz" +- ,.fragment= "" +- ,.request_path= "/test.cgi" +- ,.request_url= "/test.cgi?foo=bar?baz" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body= "" +- } +- +-#define PREFIX_NEWLINE_GET 15 +-/* Some clients, especially after a POST in a keep-alive connection, +- * will send an extra CRLF before the next request +- */ +-, {.name = "newline prefix get" +- ,.type= HTTP_REQUEST +- ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/test" +- ,.request_url= "/test" +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define UPGRADE_REQUEST 16 +-, {.name = "upgrade request" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /demo HTTP/1.1\r\n" +- "Host: example.com\r\n" +- "Connection: Upgrade\r\n" +- "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +- "Sec-WebSocket-Protocol: sample\r\n" +- "Upgrade: WebSocket\r\n" +- "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +- "Origin: http://example.com\r\n" +- "\r\n" +- "Hot diggity dogg" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/demo" +- ,.request_url= "/demo" +- ,.num_headers= 7 +- ,.upgrade="Hot diggity dogg" +- ,.headers= { { "Host", "example.com" } +- , { "Connection", "Upgrade" } +- , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } +- , { "Sec-WebSocket-Protocol", "sample" } +- , { "Upgrade", "WebSocket" } +- , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } +- , { "Origin", "http://example.com" } +- } +- ,.body= "" +- } +- +-#define CONNECT_REQUEST 17 +-, {.name = "connect request" +- ,.type= HTTP_REQUEST +- ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" +- "User-agent: Mozilla/1.1N\r\n" +- "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" +- "\r\n" +- "some data\r\n" +- "and yet even more data" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_CONNECT +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "0-home0.netscape.com:443" +- ,.num_headers= 2 +- ,.upgrade="some data\r\nand yet even more data" +- ,.headers= { { "User-agent", "Mozilla/1.1N" } +- , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } +- } +- ,.body= "" +- } +- +-#define REPORT_REQ 18 +-, {.name= "report request" +- ,.type= HTTP_REQUEST +- ,.raw= "REPORT /test HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_REPORT +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/test" +- ,.request_url= "/test" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body= "" +- } +- +-#define NO_HTTP_VERSION 19 +-, {.name= "request with no http version" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 0 +- ,.http_minor= 9 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body= "" +- } +- +-#define MSEARCH_REQ 20 +-, {.name= "m-search request" +- ,.type= HTTP_REQUEST +- ,.raw= "M-SEARCH * HTTP/1.1\r\n" +- "HOST: 239.255.255.250:1900\r\n" +- "MAN: \"ssdp:discover\"\r\n" +- "ST: \"ssdp:all\"\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_MSEARCH +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "*" +- ,.request_url= "*" +- ,.num_headers= 3 +- ,.headers= { { "HOST", "239.255.255.250:1900" } +- , { "MAN", "\"ssdp:discover\"" } +- , { "ST", "\"ssdp:all\"" } +- } +- ,.body= "" +- } +- +-#define LINE_FOLDING_IN_HEADER 21 +-, {.name= "line folding in header value" +- ,.type= HTTP_REQUEST +- ,.raw= "GET / HTTP/1.1\r\n" +- "Line1: abc\r\n" +- "\tdef\r\n" +- " ghi\r\n" +- "\t\tjkl\r\n" +- " mno \r\n" +- "\t \tqrs\r\n" +- "Line2: \t line2\t\r\n" +- "Line3:\r\n" +- " line3\r\n" +- "Line4: \r\n" +- " \r\n" +- "Connection:\r\n" +- " close\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 5 +- ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } +- , { "Line2", "line2\t" } +- , { "Line3", "line3" } +- , { "Line4", "" } +- , { "Connection", "close" }, +- } +- ,.body= "" +- } +- +- +-#define QUERY_TERMINATED_HOST 22 +-, {.name= "host terminated by a query string" +- ,.type= HTTP_REQUEST +- ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "hail=all" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "http://hypnotoad.org?hail=all" +- ,.host= "hypnotoad.org" +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define QUERY_TERMINATED_HOSTPORT 23 +-, {.name= "host:port terminated by a query string" +- ,.type= HTTP_REQUEST +- ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "hail=all" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "http://hypnotoad.org:1234?hail=all" +- ,.host= "hypnotoad.org" +- ,.port= 1234 +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define SPACE_TERMINATED_HOSTPORT 24 +-, {.name= "host:port terminated by a space" +- ,.type= HTTP_REQUEST +- ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "http://hypnotoad.org:1234" +- ,.host= "hypnotoad.org" +- ,.port= 1234 +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define PATCH_REQ 25 +-, {.name = "PATCH request" +- ,.type= HTTP_REQUEST +- ,.raw= "PATCH /file.txt HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "Content-Type: application/example\r\n" +- "If-Match: \"e0023aa4e\"\r\n" +- "Content-Length: 10\r\n" +- "\r\n" +- "cccccccccc" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_PATCH +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/file.txt" +- ,.request_url= "/file.txt" +- ,.num_headers= 4 +- ,.headers= { { "Host", "www.example.com" } +- , { "Content-Type", "application/example" } +- , { "If-Match", "\"e0023aa4e\"" } +- , { "Content-Length", "10" } +- } +- ,.body= "cccccccccc" +- } +- +-#define CONNECT_CAPS_REQUEST 26 +-, {.name = "connect caps request" +- ,.type= HTTP_REQUEST +- ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" +- "User-agent: Mozilla/1.1N\r\n" +- "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_CONNECT +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "HOME0.NETSCAPE.COM:443" +- ,.num_headers= 2 +- ,.upgrade="" +- ,.headers= { { "User-agent", "Mozilla/1.1N" } +- , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } +- } +- ,.body= "" +- } +- +-#if !HTTP_PARSER_STRICT +-#define UTF8_PATH_REQ 27 +-, {.name= "utf-8 path request" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" +- "Host: github.com\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "q=1" +- ,.fragment= "narf" +- ,.request_path= "/δ¶/δt/pope" +- ,.request_url= "/δ¶/δt/pope?q=1#narf" +- ,.num_headers= 1 +- ,.headers= { {"Host", "github.com" } +- } +- ,.body= "" +- } +- +-#define HOSTNAME_UNDERSCORE 28 +-, {.name = "hostname underscore" +- ,.type= HTTP_REQUEST +- ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" +- "User-agent: Mozilla/1.1N\r\n" +- "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_CONNECT +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "" +- ,.request_url= "home_0.netscape.com:443" +- ,.num_headers= 2 +- ,.upgrade="" +- ,.headers= { { "User-agent", "Mozilla/1.1N" } +- , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } +- } +- ,.body= "" +- } +-#endif /* !HTTP_PARSER_STRICT */ +- +-/* see https://github.com/ry/http-parser/issues/47 */ +-#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 +-, {.name = "eat CRLF between requests, no \"Connection: close\" header" +- ,.raw= "POST / HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "Content-Type: application/x-www-form-urlencoded\r\n" +- "Content-Length: 4\r\n" +- "\r\n" +- "q=42\r\n" /* note the trailing CRLF */ +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 3 +- ,.upgrade= 0 +- ,.headers= { { "Host", "www.example.com" } +- , { "Content-Type", "application/x-www-form-urlencoded" } +- , { "Content-Length", "4" } +- } +- ,.body= "q=42" +- } +- +-/* see https://github.com/ry/http-parser/issues/47 */ +-#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 +-, {.name = "eat CRLF between requests even if \"Connection: close\" is set" +- ,.raw= "POST / HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "Content-Type: application/x-www-form-urlencoded\r\n" +- "Content-Length: 4\r\n" +- "Connection: close\r\n" +- "\r\n" +- "q=42\r\n" /* note the trailing CRLF */ +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 4 +- ,.upgrade= 0 +- ,.headers= { { "Host", "www.example.com" } +- , { "Content-Type", "application/x-www-form-urlencoded" } +- , { "Content-Length", "4" } +- , { "Connection", "close" } +- } +- ,.body= "q=42" +- } +- +-#define PURGE_REQ 31 +-, {.name = "PURGE request" +- ,.type= HTTP_REQUEST +- ,.raw= "PURGE /file.txt HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_PURGE +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/file.txt" +- ,.request_url= "/file.txt" +- ,.num_headers= 1 +- ,.headers= { { "Host", "www.example.com" } } +- ,.body= "" +- } +- +-#define SEARCH_REQ 32 +-, {.name = "SEARCH request" +- ,.type= HTTP_REQUEST +- ,.raw= "SEARCH / HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_SEARCH +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 1 +- ,.headers= { { "Host", "www.example.com" } } +- ,.body= "" +- } +- +-#define PROXY_WITH_BASIC_AUTH 33 +-, {.name= "host:port and basic_auth" +- ,.type= HTTP_REQUEST +- ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.fragment= "" +- ,.request_path= "/toto" +- ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" +- ,.host= "hypnotoad.org" +- ,.userinfo= "a%12:b!&*$" +- ,.port= 1234 +- ,.num_headers= 0 +- ,.headers= { } +- ,.body= "" +- } +- +-#define LINE_FOLDING_IN_HEADER_WITH_LF 34 +-, {.name= "line folding in header value" +- ,.type= HTTP_REQUEST +- ,.raw= "GET / HTTP/1.1\n" +- "Line1: abc\n" +- "\tdef\n" +- " ghi\n" +- "\t\tjkl\n" +- " mno \n" +- "\t \tqrs\n" +- "Line2: \t line2\t\n" +- "Line3:\n" +- " line3\n" +- "Line4: \n" +- " \n" +- "Connection:\n" +- " close\n" +- "\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/" +- ,.request_url= "/" +- ,.num_headers= 5 +- ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } +- , { "Line2", "line2\t" } +- , { "Line3", "line3" } +- , { "Line4", "" } +- , { "Connection", "close" }, +- } +- ,.body= "" +- } +- +-#define CONNECTION_MULTI 35 +-, {.name = "multiple connection header values with folding" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /demo HTTP/1.1\r\n" +- "Host: example.com\r\n" +- "Connection: Something,\r\n" +- " Upgrade, ,Keep-Alive\r\n" +- "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +- "Sec-WebSocket-Protocol: sample\r\n" +- "Upgrade: WebSocket\r\n" +- "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +- "Origin: http://example.com\r\n" +- "\r\n" +- "Hot diggity dogg" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/demo" +- ,.request_url= "/demo" +- ,.num_headers= 7 +- ,.upgrade="Hot diggity dogg" +- ,.headers= { { "Host", "example.com" } +- , { "Connection", "Something, Upgrade, ,Keep-Alive" } +- , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } +- , { "Sec-WebSocket-Protocol", "sample" } +- , { "Upgrade", "WebSocket" } +- , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } +- , { "Origin", "http://example.com" } +- } +- ,.body= "" +- } +- +-#define CONNECTION_MULTI_LWS 36 +-, {.name = "multiple connection header values with folding and lws" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /demo HTTP/1.1\r\n" +- "Connection: keep-alive, upgrade\r\n" +- "Upgrade: WebSocket\r\n" +- "\r\n" +- "Hot diggity dogg" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/demo" +- ,.request_url= "/demo" +- ,.num_headers= 2 +- ,.upgrade="Hot diggity dogg" +- ,.headers= { { "Connection", "keep-alive, upgrade" } +- , { "Upgrade", "WebSocket" } +- } +- ,.body= "" +- } +- +-#define CONNECTION_MULTI_LWS_CRLF 37 +-, {.name = "multiple connection header values with folding and lws" +- ,.type= HTTP_REQUEST +- ,.raw= "GET /demo HTTP/1.1\r\n" +- "Connection: keep-alive, \r\n upgrade\r\n" +- "Upgrade: WebSocket\r\n" +- "\r\n" +- "Hot diggity dogg" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_GET +- ,.query_string= "" +- ,.fragment= "" +- ,.request_path= "/demo" +- ,.request_url= "/demo" +- ,.num_headers= 2 +- ,.upgrade="Hot diggity dogg" +- ,.headers= { { "Connection", "keep-alive, upgrade" } +- , { "Upgrade", "WebSocket" } +- } +- ,.body= "" +- } +- +-#define UPGRADE_POST_REQUEST 38 +-, {.name = "upgrade post request" +- ,.type= HTTP_REQUEST +- ,.raw= "POST /demo HTTP/1.1\r\n" +- "Host: example.com\r\n" +- "Connection: Upgrade\r\n" +- "Upgrade: HTTP/2.0\r\n" +- "Content-Length: 15\r\n" +- "\r\n" +- "sweet post body" +- "Hot diggity dogg" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_POST +- ,.request_path= "/demo" +- ,.request_url= "/demo" +- ,.num_headers= 4 +- ,.upgrade="Hot diggity dogg" +- ,.headers= { { "Host", "example.com" } +- , { "Connection", "Upgrade" } +- , { "Upgrade", "HTTP/2.0" } +- , { "Content-Length", "15" } +- } +- ,.body= "sweet post body" +- } +- +-#define CONNECT_WITH_BODY_REQUEST 39 +-, {.name = "connect with body request" +- ,.type= HTTP_REQUEST +- ,.raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n" +- "User-agent: Mozilla/1.1N\r\n" +- "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" +- "Content-Length: 10\r\n" +- "\r\n" +- "blarfcicle" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.method= HTTP_CONNECT +- ,.request_url= "foo.bar.com:443" +- ,.num_headers= 3 +- ,.upgrade="blarfcicle" +- ,.headers= { { "User-agent", "Mozilla/1.1N" } +- , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } +- , { "Content-Length", "10" } +- } +- ,.body= "" +- } +- +-/* Examples from the Internet draft for LINK/UNLINK methods: +- * https://tools.ietf.org/id/draft-snell-link-method-01.html#rfc.section.5 +- */ +- +-#define LINK_REQUEST 40 +-, {.name = "link request" +- ,.type= HTTP_REQUEST +- ,.raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n" +- "Host: example.com\r\n" +- "Link: ; rel=\"tag\"\r\n" +- "Link: ; rel=\"tag\"\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_LINK +- ,.request_path= "/images/my_dog.jpg" +- ,.request_url= "/images/my_dog.jpg" +- ,.query_string= "" +- ,.fragment= "" +- ,.num_headers= 3 +- ,.headers= { { "Host", "example.com" } +- , { "Link", "; rel=\"tag\"" } +- , { "Link", "; rel=\"tag\"" } +- } +- ,.body= "" +- } +- +-#define UNLINK_REQUEST 41 +-, {.name = "link request" +- ,.type= HTTP_REQUEST +- ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n" +- "Host: example.com\r\n" +- "Link: ; rel=\"tag\"\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.method= HTTP_UNLINK +- ,.request_path= "/images/my_dog.jpg" +- ,.request_url= "/images/my_dog.jpg" +- ,.query_string= "" +- ,.fragment= "" +- ,.num_headers= 2 +- ,.headers= { { "Host", "example.com" } +- , { "Link", "; rel=\"tag\"" } +- } +- ,.body= "" +- } +- +-, {.name= NULL } /* sentinel */ +-}; +- +-/* * R E S P O N S E S * */ +-const struct message responses[] = +-#define GOOGLE_301 0 +-{ {.name= "google 301" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" +- "Location: http://www.google.com/\r\n" +- "Content-Type: text/html; charset=UTF-8\r\n" +- "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" +- "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" +- "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ +- "Cache-Control: public, max-age=2592000\r\n" +- "Server: gws\r\n" +- "Content-Length: 219 \r\n" +- "\r\n" +- "\n" +- "301 Moved\n" +- "

301 Moved

\n" +- "The document has moved\n" +- "here.\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 301 +- ,.response_status= "Moved Permanently" +- ,.num_headers= 8 +- ,.headers= +- { { "Location", "http://www.google.com/" } +- , { "Content-Type", "text/html; charset=UTF-8" } +- , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } +- , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } +- , { "X-$PrototypeBI-Version", "1.6.0.3" } +- , { "Cache-Control", "public, max-age=2592000" } +- , { "Server", "gws" } +- , { "Content-Length", "219 " } +- } +- ,.body= "\n" +- "301 Moved\n" +- "

301 Moved

\n" +- "The document has moved\n" +- "here.\r\n" +- "\r\n" +- } +- +-#define NO_CONTENT_LENGTH_RESPONSE 1 +-/* The client should wait for the server's EOF. That is, when content-length +- * is not specified, and "Connection: close", the end of body is specified +- * by the EOF. +- * Compare with APACHEBENCH_GET +- */ +-, {.name= "no content-length response" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" +- "Server: Apache\r\n" +- "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" +- "Content-Type: text/xml; charset=utf-8\r\n" +- "Connection: close\r\n" +- "\r\n" +- "\n" +- "\n" +- " \n" +- " \n" +- " SOAP-ENV:Client\n" +- " Client Error\n" +- " \n" +- " \n" +- "" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 5 +- ,.headers= +- { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } +- , { "Server", "Apache" } +- , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } +- , { "Content-Type", "text/xml; charset=utf-8" } +- , { "Connection", "close" } +- } +- ,.body= "\n" +- "\n" +- " \n" +- " \n" +- " SOAP-ENV:Client\n" +- " Client Error\n" +- " \n" +- " \n" +- "" +- } +- +-#define NO_HEADERS_NO_BODY_404 2 +-, {.name= "404 no headers no body" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 404 +- ,.response_status= "Not Found" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_REASON_PHRASE 3 +-, {.name= "301 no response phrase" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 301\r\n\r\n" +- ,.should_keep_alive = FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 301 +- ,.response_status= "" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body= "" +- } +- +-#define TRAILING_SPACE_ON_CHUNKED_BODY 4 +-, {.name="200 trailing space on chunked body" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Content-Type: text/plain\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "25 \r\n" +- "This is the data in the first chunk\r\n" +- "\r\n" +- "1C\r\n" +- "and this is the second one\r\n" +- "\r\n" +- "0 \r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 2 +- ,.headers= +- { {"Content-Type", "text/plain" } +- , {"Transfer-Encoding", "chunked" } +- } +- ,.body_size = 37+28 +- ,.body = +- "This is the data in the first chunk\r\n" +- "and this is the second one\r\n" +- ,.num_chunks_complete= 3 +- ,.chunk_lengths= { 0x25, 0x1c } +- } +- +-#define NO_CARRIAGE_RET 5 +-, {.name="no carriage ret" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\n" +- "Content-Type: text/html; charset=utf-8\n" +- "Connection: close\n" +- "\n" +- "these headers are from http://news.ycombinator.com/" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 2 +- ,.headers= +- { {"Content-Type", "text/html; charset=utf-8" } +- , {"Connection", "close" } +- } +- ,.body= "these headers are from http://news.ycombinator.com/" +- } +- +-#define PROXY_CONNECTION 6 +-, {.name="proxy connection" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Content-Type: text/html; charset=UTF-8\r\n" +- "Content-Length: 11\r\n" +- "Proxy-Connection: close\r\n" +- "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" +- "\r\n" +- "hello world" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 4 +- ,.headers= +- { {"Content-Type", "text/html; charset=UTF-8" } +- , {"Content-Length", "11" } +- , {"Proxy-Connection", "close" } +- , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} +- } +- ,.body= "hello world" +- } +- +-#define UNDERSTORE_HEADER_KEY 7 +- // shown by +- // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" +-, {.name="underscore header key" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Server: DCLK-AdSvr\r\n" +- "Content-Type: text/xml\r\n" +- "Content-Length: 0\r\n" +- "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 4 +- ,.headers= +- { {"Server", "DCLK-AdSvr" } +- , {"Content-Type", "text/xml" } +- , {"Content-Length", "0" } +- , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } +- } +- ,.body= "" +- } +- +-#define BONJOUR_MADAME_FR 8 +-/* The client should not merge two headers fields when the first one doesn't +- * have a value. +- */ +-, {.name= "bonjourmadame.fr" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" +- "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" +- "Server: Apache/2.2.3 (Red Hat)\r\n" +- "Cache-Control: public\r\n" +- "Pragma: \r\n" +- "Location: http://www.bonjourmadame.fr/\r\n" +- "Vary: Accept-Encoding\r\n" +- "Content-Length: 0\r\n" +- "Content-Type: text/html; charset=UTF-8\r\n" +- "Connection: keep-alive\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.status_code= 301 +- ,.response_status= "Moved Permanently" +- ,.num_headers= 9 +- ,.headers= +- { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } +- , { "Server", "Apache/2.2.3 (Red Hat)" } +- , { "Cache-Control", "public" } +- , { "Pragma", "" } +- , { "Location", "http://www.bonjourmadame.fr/" } +- , { "Vary", "Accept-Encoding" } +- , { "Content-Length", "0" } +- , { "Content-Type", "text/html; charset=UTF-8" } +- , { "Connection", "keep-alive" } +- } +- ,.body= "" +- } +- +-#define RES_FIELD_UNDERSCORE 9 +-/* Should handle spaces in header fields */ +-, {.name= "field underscore" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" +- "Server: Apache\r\n" +- "Cache-Control: no-cache, must-revalidate\r\n" +- "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" +- ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" +- "Vary: Accept-Encoding\r\n" +- "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ +- "_onnection: Keep-Alive\r\n" /* semantic value ignored */ +- "Transfer-Encoding: chunked\r\n" +- "Content-Type: text/html\r\n" +- "Connection: close\r\n" +- "\r\n" +- "0\r\n\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 11 +- ,.headers= +- { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } +- , { "Server", "Apache" } +- , { "Cache-Control", "no-cache, must-revalidate" } +- , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } +- , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } +- , { "Vary", "Accept-Encoding" } +- , { "_eep-Alive", "timeout=45" } +- , { "_onnection", "Keep-Alive" } +- , { "Transfer-Encoding", "chunked" } +- , { "Content-Type", "text/html" } +- , { "Connection", "close" } +- } +- ,.body= "" +- ,.num_chunks_complete= 1 +- ,.chunk_lengths= {} +- } +- +-#define NON_ASCII_IN_STATUS_LINE 10 +-/* Should handle non-ASCII in status line */ +-, {.name= "non-ASCII in status line" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" +- "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" +- "Content-Length: 0\r\n" +- "Connection: close\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 500 +- ,.response_status= "Oriëntatieprobleem" +- ,.num_headers= 3 +- ,.headers= +- { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } +- , { "Content-Length", "0" } +- , { "Connection", "close" } +- } +- ,.body= "" +- } +- +-#define HTTP_VERSION_0_9 11 +-/* Should handle HTTP/0.9 */ +-, {.name= "http version 0.9" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/0.9 200 OK\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 0 +- ,.http_minor= 9 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 0 +- ,.headers= +- {} +- ,.body= "" +- } +- +-#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 +-/* The client should wait for the server's EOF. That is, when neither +- * content-length nor transfer-encoding is specified, the end of body +- * is specified by the EOF. +- */ +-, {.name= "neither content-length nor transfer-encoding response" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Content-Type: text/plain\r\n" +- "\r\n" +- "hello world" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 1 +- ,.headers= +- { { "Content-Type", "text/plain" } +- } +- ,.body= "hello world" +- } +- +-#define NO_BODY_HTTP10_KA_200 13 +-, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.0 200 OK\r\n" +- "Connection: keep-alive\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 1 +- ,.headers= +- { { "Connection", "keep-alive" } +- } +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_BODY_HTTP10_KA_204 14 +-, {.name= "HTTP/1.0 with keep-alive and a 204 status" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.0 204 No content\r\n" +- "Connection: keep-alive\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.status_code= 204 +- ,.response_status= "No content" +- ,.num_headers= 1 +- ,.headers= +- { { "Connection", "keep-alive" } +- } +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_BODY_HTTP11_KA_200 15 +-, {.name= "HTTP/1.1 with an EOF-terminated 200 status" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 0 +- ,.headers={} +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_BODY_HTTP11_KA_204 16 +-, {.name= "HTTP/1.1 with a 204 status" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 204 No content\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 204 +- ,.response_status= "No content" +- ,.num_headers= 0 +- ,.headers={} +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_BODY_HTTP11_NOKA_204 17 +-, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 204 No content\r\n" +- "Connection: close\r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 204 +- ,.response_status= "No content" +- ,.num_headers= 1 +- ,.headers= +- { { "Connection", "close" } +- } +- ,.body_size= 0 +- ,.body= "" +- } +- +-#define NO_BODY_HTTP11_KA_CHUNKED_200 18 +-, {.name= "HTTP/1.1 with chunked endocing and a 200 response" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "0\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 1 +- ,.headers= +- { { "Transfer-Encoding", "chunked" } +- } +- ,.body_size= 0 +- ,.body= "" +- ,.num_chunks_complete= 1 +- } +- +-#if !HTTP_PARSER_STRICT +-#define SPACE_IN_FIELD_RES 19 +-/* Should handle spaces in header fields */ +-, {.name= "field space" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Server: Microsoft-IIS/6.0\r\n" +- "X-Powered-By: ASP.NET\r\n" +- "en-US Content-Type: text/xml\r\n" /* this is the problem */ +- "Content-Type: text/xml\r\n" +- "Content-Length: 16\r\n" +- "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" +- "Connection: keep-alive\r\n" +- "\r\n" +- "hello" /* fake body */ +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 7 +- ,.headers= +- { { "Server", "Microsoft-IIS/6.0" } +- , { "X-Powered-By", "ASP.NET" } +- , { "en-US Content-Type", "text/xml" } +- , { "Content-Type", "text/xml" } +- , { "Content-Length", "16" } +- , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } +- , { "Connection", "keep-alive" } +- } +- ,.body= "hello" +- } +-#endif /* !HTTP_PARSER_STRICT */ +- +-#define AMAZON_COM 20 +-, {.name= "amazon.com" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 301 MovedPermanently\r\n" +- "Date: Wed, 15 May 2013 17:06:33 GMT\r\n" +- "Server: Server\r\n" +- "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n" +- "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n" +- "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n" +- "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n" +- "Vary: Accept-Encoding,User-Agent\r\n" +- "Content-Type: text/html; charset=ISO-8859-1\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "1\r\n" +- "\n\r\n" +- "0\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 301 +- ,.response_status= "MovedPermanently" +- ,.num_headers= 9 +- ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" } +- , { "Server", "Server" } +- , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" } +- , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" } +- , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" } +- , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" } +- , { "Vary", "Accept-Encoding,User-Agent" } +- , { "Content-Type", "text/html; charset=ISO-8859-1" } +- , { "Transfer-Encoding", "chunked" } +- } +- ,.body= "\n" +- ,.num_chunks_complete= 2 +- ,.chunk_lengths= { 1 } +- } +- +-#define EMPTY_REASON_PHRASE_AFTER_SPACE 20 +-, {.name= "empty reason phrase after space" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 \r\n" +- "\r\n" +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= TRUE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "" +- ,.num_headers= 0 +- ,.headers= {} +- ,.body= "" +- } +- +-#define CONTENT_LENGTH_X 21 +-, {.name= "Content-Length-X" +- ,.type= HTTP_RESPONSE +- ,.raw= "HTTP/1.1 200 OK\r\n" +- "Content-Length-X: 0\r\n" +- "Transfer-Encoding: chunked\r\n" +- "\r\n" +- "2\r\n" +- "OK\r\n" +- "0\r\n" +- "\r\n" +- ,.should_keep_alive= TRUE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 1 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 2 +- ,.headers= { { "Content-Length-X", "0" } +- , { "Transfer-Encoding", "chunked" } +- } +- ,.body= "OK" +- ,.num_chunks_complete= 2 +- ,.chunk_lengths= { 2 } +- } +- +-, {.name= NULL } /* sentinel */ +-}; +- +-/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so +- * define it ourselves. +- */ +-size_t +-strnlen(const char *s, size_t maxlen) +-{ +- const char *p; +- +- p = memchr(s, '\0', maxlen); +- if (p == NULL) +- return maxlen; +- +- return p - s; +-} +- +-size_t +-strlncat(char *dst, size_t len, const char *src, size_t n) +-{ +- size_t slen; +- size_t dlen; +- size_t rlen; +- size_t ncpy; +- +- slen = strnlen(src, n); +- dlen = strnlen(dst, len); +- +- if (dlen < len) { +- rlen = len - dlen; +- ncpy = slen < rlen ? slen : (rlen - 1); +- memcpy(dst + dlen, src, ncpy); +- dst[dlen + ncpy] = '\0'; +- } +- +- assert(len > slen + dlen); +- return slen + dlen; +-} +- +-size_t +-strlcat(char *dst, const char *src, size_t len) +-{ +- return strlncat(dst, len, src, (size_t) -1); +-} +- +-size_t +-strlncpy(char *dst, size_t len, const char *src, size_t n) +-{ +- size_t slen; +- size_t ncpy; +- +- slen = strnlen(src, n); +- +- if (len > 0) { +- ncpy = slen < len ? slen : (len - 1); +- memcpy(dst, src, ncpy); +- dst[ncpy] = '\0'; +- } +- +- assert(len > slen); +- return slen; +-} +- +-size_t +-strlcpy(char *dst, const char *src, size_t len) +-{ +- return strlncpy(dst, len, src, (size_t) -1); +-} +- +-int +-request_url_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- strlncat(messages[num_messages].request_url, +- sizeof(messages[num_messages].request_url), +- buf, +- len); +- return 0; +-} +- +-int +-header_field_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- struct message *m = &messages[num_messages]; +- +- if (m->last_header_element != FIELD) +- m->num_headers++; +- +- strlncat(m->headers[m->num_headers-1][0], +- sizeof(m->headers[m->num_headers-1][0]), +- buf, +- len); +- +- m->last_header_element = FIELD; +- +- return 0; +-} +- +-int +-header_value_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- struct message *m = &messages[num_messages]; +- +- strlncat(m->headers[m->num_headers-1][1], +- sizeof(m->headers[m->num_headers-1][1]), +- buf, +- len); +- +- m->last_header_element = VALUE; +- +- return 0; +-} +- +-void +-check_body_is_final (const http_parser *p) +-{ +- if (messages[num_messages].body_is_final) { +- fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " +- "on last on_body callback call " +- "but it doesn't! ***\n\n"); +- assert(0); +- abort(); +- } +- messages[num_messages].body_is_final = http_body_is_final(p); +-} +- +-int +-body_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- strlncat(messages[num_messages].body, +- sizeof(messages[num_messages].body), +- buf, +- len); +- messages[num_messages].body_size += len; +- check_body_is_final(p); +- // printf("body_cb: '%s'\n", requests[num_messages].body); +- return 0; +-} +- +-int +-count_body_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- assert(buf); +- messages[num_messages].body_size += len; +- check_body_is_final(p); +- return 0; +-} +- +-int +-message_begin_cb (http_parser *p) +-{ +- assert(p == parser); +- messages[num_messages].message_begin_cb_called = TRUE; +- return 0; +-} +- +-int +-headers_complete_cb (http_parser *p) +-{ +- assert(p == parser); +- messages[num_messages].method = parser->method; +- messages[num_messages].status_code = parser->status_code; +- messages[num_messages].http_major = parser->http_major; +- messages[num_messages].http_minor = parser->http_minor; +- messages[num_messages].headers_complete_cb_called = TRUE; +- messages[num_messages].should_keep_alive = http_should_keep_alive(parser); +- return 0; +-} +- +-int +-message_complete_cb (http_parser *p) +-{ +- assert(p == parser); +- if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) +- { +- fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " +- "value in both on_message_complete and on_headers_complete " +- "but it doesn't! ***\n\n"); +- assert(0); +- abort(); +- } +- +- if (messages[num_messages].body_size && +- http_body_is_final(p) && +- !messages[num_messages].body_is_final) +- { +- fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " +- "on last on_body callback call " +- "but it doesn't! ***\n\n"); +- assert(0); +- abort(); +- } +- +- messages[num_messages].message_complete_cb_called = TRUE; +- +- messages[num_messages].message_complete_on_eof = currently_parsing_eof; +- +- num_messages++; +- return 0; +-} +- +-int +-response_status_cb (http_parser *p, const char *buf, size_t len) +-{ +- assert(p == parser); +- strlncat(messages[num_messages].response_status, +- sizeof(messages[num_messages].response_status), +- buf, +- len); +- return 0; +-} +- +-int +-chunk_header_cb (http_parser *p) +-{ +- assert(p == parser); +- int chunk_idx = messages[num_messages].num_chunks; +- messages[num_messages].num_chunks++; +- if (chunk_idx < MAX_CHUNKS) { +- messages[num_messages].chunk_lengths[chunk_idx] = p->content_length; +- } +- +- return 0; +-} +- +-int +-chunk_complete_cb (http_parser *p) +-{ +- assert(p == parser); +- +- /* Here we want to verify that each chunk_header_cb is matched by a +- * chunk_complete_cb, so not only should the total number of calls to +- * both callbacks be the same, but they also should be interleaved +- * properly */ +- assert(messages[num_messages].num_chunks == +- messages[num_messages].num_chunks_complete + 1); +- +- messages[num_messages].num_chunks_complete++; +- return 0; +-} +- +-/* These dontcall_* callbacks exist so that we can verify that when we're +- * paused, no additional callbacks are invoked */ +-int +-dontcall_message_begin_cb (http_parser *p) +-{ +- if (p) { } // gcc +- fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) +-{ +- if (p || buf || len) { } // gcc +- fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) +-{ +- if (p || buf || len) { } // gcc +- fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) +-{ +- if (p || buf || len) { } // gcc +- fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_body_cb (http_parser *p, const char *buf, size_t len) +-{ +- if (p || buf || len) { } // gcc +- fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_headers_complete_cb (http_parser *p) +-{ +- if (p) { } // gcc +- fprintf(stderr, "\n\n*** on_headers_complete() called on paused " +- "parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_message_complete_cb (http_parser *p) +-{ +- if (p) { } // gcc +- fprintf(stderr, "\n\n*** on_message_complete() called on paused " +- "parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_response_status_cb (http_parser *p, const char *buf, size_t len) +-{ +- if (p || buf || len) { } // gcc +- fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n"); +- abort(); +-} +- +-int +-dontcall_chunk_header_cb (http_parser *p) +-{ +- if (p) { } // gcc +- fprintf(stderr, "\n\n*** on_chunk_header() called on paused parser ***\n\n"); +- exit(1); +-} +- +-int +-dontcall_chunk_complete_cb (http_parser *p) +-{ +- if (p) { } // gcc +- fprintf(stderr, "\n\n*** on_chunk_complete() " +- "called on paused parser ***\n\n"); +- exit(1); +-} +- +-static http_parser_settings settings_dontcall = +- {.on_message_begin = dontcall_message_begin_cb +- ,.on_header_field = dontcall_header_field_cb +- ,.on_header_value = dontcall_header_value_cb +- ,.on_url = dontcall_request_url_cb +- ,.on_status = dontcall_response_status_cb +- ,.on_body = dontcall_body_cb +- ,.on_headers_complete = dontcall_headers_complete_cb +- ,.on_message_complete = dontcall_message_complete_cb +- ,.on_chunk_header = dontcall_chunk_header_cb +- ,.on_chunk_complete = dontcall_chunk_complete_cb +- }; +- +-/* These pause_* callbacks always pause the parser and just invoke the regular +- * callback that tracks content. Before returning, we overwrite the parser +- * settings to point to the _dontcall variety so that we can verify that +- * the pause actually did, you know, pause. */ +-int +-pause_message_begin_cb (http_parser *p) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return message_begin_cb(p); +-} +- +-int +-pause_header_field_cb (http_parser *p, const char *buf, size_t len) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return header_field_cb(p, buf, len); +-} +- +-int +-pause_header_value_cb (http_parser *p, const char *buf, size_t len) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return header_value_cb(p, buf, len); +-} +- +-int +-pause_request_url_cb (http_parser *p, const char *buf, size_t len) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return request_url_cb(p, buf, len); +-} +- +-int +-pause_body_cb (http_parser *p, const char *buf, size_t len) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return body_cb(p, buf, len); +-} +- +-int +-pause_headers_complete_cb (http_parser *p) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return headers_complete_cb(p); +-} +- +-int +-pause_message_complete_cb (http_parser *p) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return message_complete_cb(p); +-} +- +-int +-pause_response_status_cb (http_parser *p, const char *buf, size_t len) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return response_status_cb(p, buf, len); +-} +- +-int +-pause_chunk_header_cb (http_parser *p) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return chunk_header_cb(p); +-} +- +-int +-pause_chunk_complete_cb (http_parser *p) +-{ +- http_parser_pause(p, 1); +- *current_pause_parser = settings_dontcall; +- return chunk_complete_cb(p); +-} +- +-int +-connect_headers_complete_cb (http_parser *p) +-{ +- headers_complete_cb(p); +- return 1; +-} +- +-int +-connect_message_complete_cb (http_parser *p) +-{ +- messages[num_messages].should_keep_alive = http_should_keep_alive(parser); +- return message_complete_cb(p); +-} +- +-static http_parser_settings settings_pause = +- {.on_message_begin = pause_message_begin_cb +- ,.on_header_field = pause_header_field_cb +- ,.on_header_value = pause_header_value_cb +- ,.on_url = pause_request_url_cb +- ,.on_status = pause_response_status_cb +- ,.on_body = pause_body_cb +- ,.on_headers_complete = pause_headers_complete_cb +- ,.on_message_complete = pause_message_complete_cb +- ,.on_chunk_header = pause_chunk_header_cb +- ,.on_chunk_complete = pause_chunk_complete_cb +- }; +- +-static http_parser_settings settings = +- {.on_message_begin = message_begin_cb +- ,.on_header_field = header_field_cb +- ,.on_header_value = header_value_cb +- ,.on_url = request_url_cb +- ,.on_status = response_status_cb +- ,.on_body = body_cb +- ,.on_headers_complete = headers_complete_cb +- ,.on_message_complete = message_complete_cb +- ,.on_chunk_header = chunk_header_cb +- ,.on_chunk_complete = chunk_complete_cb +- }; +- +-static http_parser_settings settings_count_body = +- {.on_message_begin = message_begin_cb +- ,.on_header_field = header_field_cb +- ,.on_header_value = header_value_cb +- ,.on_url = request_url_cb +- ,.on_status = response_status_cb +- ,.on_body = count_body_cb +- ,.on_headers_complete = headers_complete_cb +- ,.on_message_complete = message_complete_cb +- ,.on_chunk_header = chunk_header_cb +- ,.on_chunk_complete = chunk_complete_cb +- }; +- +-static http_parser_settings settings_connect = +- {.on_message_begin = message_begin_cb +- ,.on_header_field = header_field_cb +- ,.on_header_value = header_value_cb +- ,.on_url = request_url_cb +- ,.on_status = response_status_cb +- ,.on_body = dontcall_body_cb +- ,.on_headers_complete = connect_headers_complete_cb +- ,.on_message_complete = connect_message_complete_cb +- ,.on_chunk_header = chunk_header_cb +- ,.on_chunk_complete = chunk_complete_cb +- }; +- +-static http_parser_settings settings_null = +- {.on_message_begin = 0 +- ,.on_header_field = 0 +- ,.on_header_value = 0 +- ,.on_url = 0 +- ,.on_status = 0 +- ,.on_body = 0 +- ,.on_headers_complete = 0 +- ,.on_message_complete = 0 +- ,.on_chunk_header = 0 +- ,.on_chunk_complete = 0 +- }; +- +-void +-parser_init (enum http_parser_type type) +-{ +- num_messages = 0; +- +- assert(parser == NULL); +- +- parser = malloc(sizeof(http_parser)); +- +- http_parser_init(parser, type); +- +- memset(&messages, 0, sizeof messages); +- +-} +- +-void +-parser_free () +-{ +- assert(parser); +- free(parser); +- parser = NULL; +-} +- +-size_t parse (const char *buf, size_t len) +-{ +- size_t nparsed; +- currently_parsing_eof = (len == 0); +- nparsed = http_parser_execute(parser, &settings, buf, len); +- return nparsed; +-} +- +-size_t parse_count_body (const char *buf, size_t len) +-{ +- size_t nparsed; +- currently_parsing_eof = (len == 0); +- nparsed = http_parser_execute(parser, &settings_count_body, buf, len); +- return nparsed; +-} +- +-size_t parse_pause (const char *buf, size_t len) +-{ +- size_t nparsed; +- http_parser_settings s = settings_pause; +- +- currently_parsing_eof = (len == 0); +- current_pause_parser = &s; +- nparsed = http_parser_execute(parser, current_pause_parser, buf, len); +- return nparsed; +-} +- +-size_t parse_connect (const char *buf, size_t len) +-{ +- size_t nparsed; +- currently_parsing_eof = (len == 0); +- nparsed = http_parser_execute(parser, &settings_connect, buf, len); +- return nparsed; +-} +- +-static inline int +-check_str_eq (const struct message *m, +- const char *prop, +- const char *expected, +- const char *found) { +- if ((expected == NULL) != (found == NULL)) { +- printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); +- printf("expected %s\n", (expected == NULL) ? "NULL" : expected); +- printf(" found %s\n", (found == NULL) ? "NULL" : found); +- return 0; +- } +- if (expected != NULL && 0 != strcmp(expected, found)) { +- printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); +- printf("expected '%s'\n", expected); +- printf(" found '%s'\n", found); +- return 0; +- } +- return 1; +-} +- +-static inline int +-check_num_eq (const struct message *m, +- const char *prop, +- int expected, +- int found) { +- if (expected != found) { +- printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); +- printf("expected %d\n", expected); +- printf(" found %d\n", found); +- return 0; +- } +- return 1; +-} +- +-#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ +- if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 +- +-#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ +- if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 +- +-#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ +-do { \ +- char ubuf[256]; \ +- \ +- if ((u)->field_set & (1 << (fn))) { \ +- memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ +- (u)->field_data[(fn)].len); \ +- ubuf[(u)->field_data[(fn)].len] = '\0'; \ +- } else { \ +- ubuf[0] = '\0'; \ +- } \ +- \ +- check_str_eq(expected, #prop, expected->prop, ubuf); \ +-} while(0) +- +-int +-message_eq (int index, int connect, const struct message *expected) +-{ +- int i; +- struct message *m = &messages[index]; +- +- MESSAGE_CHECK_NUM_EQ(expected, m, http_major); +- MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); +- +- if (expected->type == HTTP_REQUEST) { +- MESSAGE_CHECK_NUM_EQ(expected, m, method); +- } else { +- MESSAGE_CHECK_NUM_EQ(expected, m, status_code); +- MESSAGE_CHECK_STR_EQ(expected, m, response_status); +- } +- +- if (!connect) { +- MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); +- MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); +- } +- +- assert(m->message_begin_cb_called); +- assert(m->headers_complete_cb_called); +- assert(m->message_complete_cb_called); +- +- +- MESSAGE_CHECK_STR_EQ(expected, m, request_url); +- +- /* Check URL components; we can't do this w/ CONNECT since it doesn't +- * send us a well-formed URL. +- */ +- if (*m->request_url && m->method != HTTP_CONNECT) { +- struct http_parser_url u; +- +- if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { +- fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", +- m->request_url); +- abort(); +- } +- +- if (expected->host) { +- MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); +- } +- +- if (expected->userinfo) { +- MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); +- } +- +- m->port = (u.field_set & (1 << UF_PORT)) ? +- u.port : 0; +- +- MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); +- MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); +- MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); +- MESSAGE_CHECK_NUM_EQ(expected, m, port); +- } +- +- if (connect) { +- check_num_eq(m, "body_size", 0, m->body_size); +- } else if (expected->body_size) { +- MESSAGE_CHECK_NUM_EQ(expected, m, body_size); +- } else { +- MESSAGE_CHECK_STR_EQ(expected, m, body); +- } +- +- if (connect) { +- check_num_eq(m, "num_chunks_complete", 0, m->num_chunks_complete); +- } else { +- assert(m->num_chunks == m->num_chunks_complete); +- MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete); +- for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) { +- MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]); +- } +- } +- +- MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); +- +- int r; +- for (i = 0; i < m->num_headers; i++) { +- r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); +- if (!r) return 0; +- r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); +- if (!r) return 0; +- } +- +- MESSAGE_CHECK_STR_EQ(expected, m, upgrade); +- +- return 1; +-} +- +-/* Given a sequence of varargs messages, return the number of them that the +- * parser should successfully parse, taking into account that upgraded +- * messages prevent all subsequent messages from being parsed. +- */ +-size_t +-count_parsed_messages(const size_t nmsgs, ...) { +- size_t i; +- va_list ap; +- +- va_start(ap, nmsgs); +- +- for (i = 0; i < nmsgs; i++) { +- struct message *m = va_arg(ap, struct message *); +- +- if (m->upgrade) { +- va_end(ap); +- return i + 1; +- } +- } +- +- va_end(ap); +- return nmsgs; +-} +- +-/* Given a sequence of bytes and the number of these that we were able to +- * parse, verify that upgrade bodies are correct. +- */ +-void +-upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { +- va_list ap; +- size_t i; +- size_t off = 0; +- +- va_start(ap, nmsgs); +- +- for (i = 0; i < nmsgs; i++) { +- struct message *m = va_arg(ap, struct message *); +- +- off += strlen(m->raw); +- +- if (m->upgrade) { +- off -= strlen(m->upgrade); +- +- /* Check the portion of the response after its specified upgrade */ +- if (!check_str_eq(m, "upgrade", body + off, body + nread)) { +- abort(); +- } +- +- /* Fix up the response so that message_eq() will verify the beginning +- * of the upgrade */ +- *(body + nread + strlen(m->upgrade)) = '\0'; +- messages[num_messages -1 ].upgrade = body + nread; +- +- va_end(ap); +- return; +- } +- } +- +- va_end(ap); +- printf("\n\n*** Error: expected a message with upgrade ***\n"); +- +- abort(); +-} +- +-static void +-print_error (const char *raw, size_t error_location) +-{ +- fprintf(stderr, "\n*** %s ***\n\n", +- http_errno_description(HTTP_PARSER_ERRNO(parser))); +- +- int this_line = 0, char_len = 0; +- size_t i, j, len = strlen(raw), error_location_line = 0; +- for (i = 0; i < len; i++) { +- if (i == error_location) this_line = 1; +- switch (raw[i]) { +- case '\r': +- char_len = 2; +- fprintf(stderr, "\\r"); +- break; +- +- case '\n': +- fprintf(stderr, "\\n\n"); +- +- if (this_line) goto print; +- +- error_location_line = 0; +- continue; +- +- default: +- char_len = 1; +- fputc(raw[i], stderr); +- break; +- } +- if (!this_line) error_location_line += char_len; +- } +- +- fprintf(stderr, "[eof]\n"); +- +- print: +- for (j = 0; j < error_location_line; j++) { +- fputc(' ', stderr); +- } +- fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); +-} +- +-void +-test_preserve_data (void) +-{ +- char my_data[] = "application-specific data"; +- http_parser parser; +- parser.data = my_data; +- http_parser_init(&parser, HTTP_REQUEST); +- if (parser.data != my_data) { +- printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); +- abort(); +- } +-} +- +-struct url_test { +- const char *name; +- const char *url; +- int is_connect; +- struct http_parser_url u; +- int rv; +-}; +- +-const struct url_test url_tests[] = +-{ {.name="proxy request" +- ,.url="http://hostname/" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) +- ,.port=0 +- ,.field_data= +- {{ 0, 4 } /* UF_SCHEMA */ +- ,{ 7, 8 } /* UF_HOST */ +- ,{ 0, 0 } /* UF_PORT */ +- ,{ 15, 1 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="proxy request with port" +- ,.url="http://hostname:444/" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) +- ,.port=444 +- ,.field_data= +- {{ 0, 4 } /* UF_SCHEMA */ +- ,{ 7, 8 } /* UF_HOST */ +- ,{ 16, 3 } /* UF_PORT */ +- ,{ 19, 1 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="CONNECT request" +- ,.url="hostname:443" +- ,.is_connect=1 +- ,.u= +- {.field_set=(1 << UF_HOST) | (1 << UF_PORT) +- ,.port=443 +- ,.field_data= +- {{ 0, 0 } /* UF_SCHEMA */ +- ,{ 0, 8 } /* UF_HOST */ +- ,{ 9, 3 } /* UF_PORT */ +- ,{ 0, 0 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="CONNECT request but not connect" +- ,.url="hostname:443" +- ,.is_connect=0 +- ,.rv=1 +- } +- +-, {.name="proxy ipv6 request" +- ,.url="http://[1:2::3:4]/" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) +- ,.port=0 +- ,.field_data= +- {{ 0, 4 } /* UF_SCHEMA */ +- ,{ 8, 8 } /* UF_HOST */ +- ,{ 0, 0 } /* UF_PORT */ +- ,{ 17, 1 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="proxy ipv6 request with port" +- ,.url="http://[1:2::3:4]:67/" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) +- ,.port=67 +- ,.field_data= +- {{ 0, 4 } /* UF_SCHEMA */ +- ,{ 8, 8 } /* UF_HOST */ +- ,{ 18, 2 } /* UF_PORT */ +- ,{ 20, 1 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="CONNECT ipv6 address" +- ,.url="[1:2::3:4]:443" +- ,.is_connect=1 +- ,.u= +- {.field_set=(1 << UF_HOST) | (1 << UF_PORT) +- ,.port=443 +- ,.field_data= +- {{ 0, 0 } /* UF_SCHEMA */ +- ,{ 1, 8 } /* UF_HOST */ +- ,{ 11, 3 } /* UF_PORT */ +- ,{ 0, 0 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="ipv4 in ipv6 address" +- ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) +- ,.port=0 +- ,.field_data= +- {{ 0, 4 } /* UF_SCHEMA */ +- ,{ 8, 37 } /* UF_HOST */ +- ,{ 0, 0 } /* UF_PORT */ +- ,{ 46, 1 } /* UF_PATH */ +- ,{ 0, 0 } /* UF_QUERY */ +- ,{ 0, 0 } /* UF_FRAGMENT */ +- ,{ 0, 0 } /* UF_USERINFO */ +- } +- } +- ,.rv=0 +- } +- +-, {.name="extra ? in query string" +- ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," +- "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," +- "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" +- ,.is_connect=0 +- ,.u= +- {.field_set=(1<field_set, u->port); +- for (i = 0; i < UF_MAX; i++) { +- if ((u->field_set & (1 << i)) == 0) { +- printf("\tfield_data[%u]: unset\n", i); +- continue; +- } +- +- printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", +- i, +- u->field_data[i].off, +- u->field_data[i].len, +- u->field_data[i].len, +- url + u->field_data[i].off); +- } +-} +- +-void +-test_parse_url (void) +-{ +- struct http_parser_url u; +- const struct url_test *test; +- unsigned int i; +- int rv; +- +- for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { +- test = &url_tests[i]; +- memset(&u, 0, sizeof(u)); +- +- rv = http_parser_parse_url(test->url, +- strlen(test->url), +- test->is_connect, +- &u); +- +- if (test->rv == 0) { +- if (rv != 0) { +- printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " +- "unexpected rv %d ***\n\n", test->url, test->name, rv); +- abort(); +- } +- +- if (memcmp(&u, &test->u, sizeof(u)) != 0) { +- printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", +- test->url, test->name); +- +- printf("target http_parser_url:\n"); +- dump_url(test->url, &test->u); +- printf("result http_parser_url:\n"); +- dump_url(test->url, &u); +- +- abort(); +- } +- } else { +- /* test->rv != 0 */ +- if (rv == 0) { +- printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " +- "unexpected rv %d ***\n\n", test->url, test->name, rv); +- abort(); +- } +- } +- } +-} +- +-void +-test_method_str (void) +-{ +- assert(0 == strcmp("GET", http_method_str(HTTP_GET))); +- assert(0 == strcmp("", http_method_str(1337))); +-} +- +-void +-test_message (const struct message *message) +-{ +- size_t raw_len = strlen(message->raw); +- size_t msg1len; +- for (msg1len = 0; msg1len < raw_len; msg1len++) { +- parser_init(message->type); +- +- size_t read; +- const char *msg1 = message->raw; +- const char *msg2 = msg1 + msg1len; +- size_t msg2len = raw_len - msg1len; +- +- if (msg1len) { +- read = parse(msg1, msg1len); +- +- if (message->upgrade && parser->upgrade && num_messages > 0) { +- messages[num_messages - 1].upgrade = msg1 + read; +- goto test; +- } +- +- if (read != msg1len) { +- print_error(msg1, read); +- abort(); +- } +- } +- +- +- read = parse(msg2, msg2len); +- +- if (message->upgrade && parser->upgrade) { +- messages[num_messages - 1].upgrade = msg2 + read; +- goto test; +- } +- +- if (read != msg2len) { +- print_error(msg2, read); +- abort(); +- } +- +- read = parse(NULL, 0); +- +- if (read != 0) { +- print_error(message->raw, read); +- abort(); +- } +- +- test: +- +- if (num_messages != 1) { +- printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); +- abort(); +- } +- +- if(!message_eq(0, 0, message)) abort(); +- +- parser_free(); +- } +-} +- +-void +-test_message_count_body (const struct message *message) +-{ +- parser_init(message->type); +- +- size_t read; +- size_t l = strlen(message->raw); +- size_t i, toread; +- size_t chunk = 4024; +- +- for (i = 0; i < l; i+= chunk) { +- toread = MIN(l-i, chunk); +- read = parse_count_body(message->raw + i, toread); +- if (read != toread) { +- print_error(message->raw, read); +- abort(); +- } +- } +- +- +- read = parse_count_body(NULL, 0); +- if (read != 0) { +- print_error(message->raw, read); +- abort(); +- } +- +- if (num_messages != 1) { +- printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); +- abort(); +- } +- +- if(!message_eq(0, 0, message)) abort(); +- +- parser_free(); +-} +- +-void +-test_simple (const char *buf, enum http_errno err_expected) +-{ +- parser_init(HTTP_REQUEST); +- +- enum http_errno err; +- +- parse(buf, strlen(buf)); +- err = HTTP_PARSER_ERRNO(parser); +- parse(NULL, 0); +- +- parser_free(); +- +- /* In strict mode, allow us to pass with an unexpected HPE_STRICT as +- * long as the caller isn't expecting success. +- */ +-#if HTTP_PARSER_STRICT +- if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { +-#else +- if (err_expected != err) { +-#endif +- fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", +- http_errno_name(err_expected), http_errno_name(err), buf); +- abort(); +- } +-} +- +-void +-test_invalid_header_content (int req, const char* str) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? +- "GET / HTTP/1.1\r\n" : +- "HTTP/1.1 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = str; +- size_t buflen = strlen(buf); +- +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); +- return; +- } +- +- fprintf(stderr, +- "\n*** Error expected but none in invalid header content test ***\n"); +- abort(); +-} +- +-void +-test_invalid_header_field_content_error (int req) +-{ +- test_invalid_header_content(req, "Foo: F\01ailure"); +- test_invalid_header_content(req, "Foo: B\02ar"); +-} +- +-void +-test_invalid_header_field (int req, const char* str) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? +- "GET / HTTP/1.1\r\n" : +- "HTTP/1.1 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = str; +- size_t buflen = strlen(buf); +- +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); +- return; +- } +- +- fprintf(stderr, +- "\n*** Error expected but none in invalid header token test ***\n"); +- abort(); +-} +- +-void +-test_invalid_header_field_token_error (int req) +-{ +- test_invalid_header_field(req, "Fo@: Failure"); +- test_invalid_header_field(req, "Foo\01\test: Bar"); +-} +- +-void +-test_double_content_length_error (int req) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? +- "GET / HTTP/1.1\r\n" : +- "HTTP/1.1 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"; +- size_t buflen = strlen(buf); +- +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); +- return; +- } +- +- fprintf(stderr, +- "\n*** Error expected but none in double content-length test ***\n"); +- abort(); +-} +- +-void +-test_chunked_content_length_error (int req) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? +- "GET / HTTP/1.1\r\n" : +- "HTTP/1.1 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n"; +- size_t buflen = strlen(buf); +- +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); +- return; +- } +- +- fprintf(stderr, +- "\n*** Error expected but none in chunked content-length test ***\n"); +- abort(); +-} +- +-void +-test_header_cr_no_lf_error (int req) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? +- "GET / HTTP/1.1\r\n" : +- "HTTP/1.1 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = "Foo: 1\rBar: 1\r\n\r\n"; +- size_t buflen = strlen(buf); +- +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED); +- return; +- } +- +- fprintf(stderr, +- "\n*** Error expected but none in header whitespace test ***\n"); +- abort(); +-} +- +-void +-test_header_overflow_error (int req) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- const char *buf; +- buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- buf = "header-key: header-value\r\n"; +- size_t buflen = strlen(buf); +- +- int i; +- for (i = 0; i < 10000; i++) { +- parsed = http_parser_execute(&parser, &settings_null, buf, buflen); +- if (parsed != buflen) { +- //fprintf(stderr, "error found on iter %d\n", i); +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); +- return; +- } +- } +- +- fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); +- abort(); +-} +- +- +-void +-test_header_nread_value () +-{ +- http_parser parser; +- http_parser_init(&parser, HTTP_REQUEST); +- size_t parsed; +- const char *buf; +- buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"; +- parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); +- assert(parsed == strlen(buf)); +- +- assert(parser.nread == strlen(buf)); +-} +- +- +-static void +-test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) +-{ +- http_parser parser; +- http_parser_init(&parser, HTTP_RESPONSE); +- http_parser_execute(&parser, &settings_null, buf, buflen); +- +- if (expect_ok) +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); +- else +- assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); +-} +- +-void +-test_header_content_length_overflow_error (void) +-{ +-#define X(size) \ +- "HTTP/1.1 200 OK\r\n" \ +- "Content-Length: " #size "\r\n" \ +- "\r\n" +- const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */ +- const char b[] = X(18446744073709551615); /* 2^64-1 */ +- const char c[] = X(18446744073709551616); /* 2^64 */ +-#undef X +- test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ +- test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ +- test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +-} +- +-void +-test_chunk_content_length_overflow_error (void) +-{ +-#define X(size) \ +- "HTTP/1.1 200 OK\r\n" \ +- "Transfer-Encoding: chunked\r\n" \ +- "\r\n" \ +- #size "\r\n" \ +- "..." +- const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */ +- const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ +- const char c[] = X(10000000000000000); /* 2^64 */ +-#undef X +- test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ +- test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ +- test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +-} +- +-void +-test_no_overflow_long_body (int req, size_t length) +-{ +- http_parser parser; +- http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); +- size_t parsed; +- size_t i; +- char buf1[3000]; +- size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", +- req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); +- parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); +- if (parsed != buf1len) +- goto err; +- +- for (i = 0; i < length; i++) { +- char foo = 'a'; +- parsed = http_parser_execute(&parser, &settings_null, &foo, 1); +- if (parsed != 1) +- goto err; +- } +- +- parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); +- if (parsed != buf1len) goto err; +- return; +- +- err: +- fprintf(stderr, +- "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", +- req ? "REQUEST" : "RESPONSE", +- (unsigned long)length); +- abort(); +-} +- +-void +-test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) +-{ +- int message_count = count_parsed_messages(3, r1, r2, r3); +- +- char total[ strlen(r1->raw) +- + strlen(r2->raw) +- + strlen(r3->raw) +- + 1 +- ]; +- total[0] = '\0'; +- +- strcat(total, r1->raw); +- strcat(total, r2->raw); +- strcat(total, r3->raw); +- +- parser_init(r1->type); +- +- size_t read; +- +- read = parse(total, strlen(total)); +- +- if (parser->upgrade) { +- upgrade_message_fix(total, read, 3, r1, r2, r3); +- goto test; +- } +- +- if (read != strlen(total)) { +- print_error(total, read); +- abort(); +- } +- +- read = parse(NULL, 0); +- +- if (read != 0) { +- print_error(total, read); +- abort(); +- } +- +-test: +- +- if (message_count != num_messages) { +- fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); +- abort(); +- } +- +- if (!message_eq(0, 0, r1)) abort(); +- if (message_count > 1 && !message_eq(1, 0, r2)) abort(); +- if (message_count > 2 && !message_eq(2, 0, r3)) abort(); +- +- parser_free(); +-} +- +-/* SCAN through every possible breaking to make sure the +- * parser can handle getting the content in any chunks that +- * might come from the socket +- */ +-void +-test_scan (const struct message *r1, const struct message *r2, const struct message *r3) +-{ +- char total[80*1024] = "\0"; +- char buf1[80*1024] = "\0"; +- char buf2[80*1024] = "\0"; +- char buf3[80*1024] = "\0"; +- +- strcat(total, r1->raw); +- strcat(total, r2->raw); +- strcat(total, r3->raw); +- +- size_t read; +- +- int total_len = strlen(total); +- +- int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; +- int ops = 0 ; +- +- size_t buf1_len, buf2_len, buf3_len; +- int message_count = count_parsed_messages(3, r1, r2, r3); +- +- int i,j,type_both; +- for (type_both = 0; type_both < 2; type_both ++ ) { +- for (j = 2; j < total_len; j ++ ) { +- for (i = 1; i < j; i ++ ) { +- +- if (ops % 1000 == 0) { +- printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); +- fflush(stdout); +- } +- ops += 1; +- +- parser_init(type_both ? HTTP_BOTH : r1->type); +- +- buf1_len = i; +- strlncpy(buf1, sizeof(buf1), total, buf1_len); +- buf1[buf1_len] = 0; +- +- buf2_len = j - i; +- strlncpy(buf2, sizeof(buf1), total+i, buf2_len); +- buf2[buf2_len] = 0; +- +- buf3_len = total_len - j; +- strlncpy(buf3, sizeof(buf1), total+j, buf3_len); +- buf3[buf3_len] = 0; +- +- read = parse(buf1, buf1_len); +- +- if (parser->upgrade) goto test; +- +- if (read != buf1_len) { +- print_error(buf1, read); +- goto error; +- } +- +- read += parse(buf2, buf2_len); +- +- if (parser->upgrade) goto test; +- +- if (read != buf1_len + buf2_len) { +- print_error(buf2, read); +- goto error; +- } +- +- read += parse(buf3, buf3_len); +- +- if (parser->upgrade) goto test; +- +- if (read != buf1_len + buf2_len + buf3_len) { +- print_error(buf3, read); +- goto error; +- } +- +- parse(NULL, 0); +- +-test: +- if (parser->upgrade) { +- upgrade_message_fix(total, read, 3, r1, r2, r3); +- } +- +- if (message_count != num_messages) { +- fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", +- message_count, num_messages); +- goto error; +- } +- +- if (!message_eq(0, 0, r1)) { +- fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); +- goto error; +- } +- +- if (message_count > 1 && !message_eq(1, 0, r2)) { +- fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); +- goto error; +- } +- +- if (message_count > 2 && !message_eq(2, 0, r3)) { +- fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); +- goto error; +- } +- +- parser_free(); +- } +- } +- } +- puts("\b\b\b\b100%"); +- return; +- +- error: +- fprintf(stderr, "i=%d j=%d\n", i, j); +- fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); +- fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); +- fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); +- abort(); +-} +- +-// user required to free the result +-// string terminated by \0 +-char * +-create_large_chunked_message (int body_size_in_kb, const char* headers) +-{ +- int i; +- size_t wrote = 0; +- size_t headers_len = strlen(headers); +- size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; +- char * buf = malloc(bufsize); +- +- memcpy(buf, headers, headers_len); +- wrote += headers_len; +- +- for (i = 0; i < body_size_in_kb; i++) { +- // write 1kb chunk into the body. +- memcpy(buf + wrote, "400\r\n", 5); +- wrote += 5; +- memset(buf + wrote, 'C', 1024); +- wrote += 1024; +- strcpy(buf + wrote, "\r\n"); +- wrote += 2; +- } +- +- memcpy(buf + wrote, "0\r\n\r\n", 6); +- wrote += 6; +- assert(wrote == bufsize); +- +- return buf; +-} +- +-/* Verify that we can pause parsing at any of the bytes in the +- * message and still get the result that we're expecting. */ +-void +-test_message_pause (const struct message *msg) +-{ +- char *buf = (char*) msg->raw; +- size_t buflen = strlen(msg->raw); +- size_t nread; +- +- parser_init(msg->type); +- +- do { +- nread = parse_pause(buf, buflen); +- +- // We can only set the upgrade buffer once we've gotten our message +- // completion callback. +- if (messages[0].message_complete_cb_called && +- msg->upgrade && +- parser->upgrade) { +- messages[0].upgrade = buf + nread; +- goto test; +- } +- +- if (nread < buflen) { +- +- // Not much do to if we failed a strict-mode check +- if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { +- parser_free(); +- return; +- } +- +- assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); +- } +- +- buf += nread; +- buflen -= nread; +- http_parser_pause(parser, 0); +- } while (buflen > 0); +- +- nread = parse_pause(NULL, 0); +- assert (nread == 0); +- +-test: +- if (num_messages != 1) { +- printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); +- abort(); +- } +- +- if(!message_eq(0, 0, msg)) abort(); +- +- parser_free(); +-} +- +-/* Verify that body and next message won't be parsed in responses to CONNECT */ +-void +-test_message_connect (const struct message *msg) +-{ +- char *buf = (char*) msg->raw; +- size_t buflen = strlen(msg->raw); +- +- parser_init(msg->type); +- +- parse_connect(buf, buflen); +- +- if (num_messages != 1) { +- printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); +- abort(); +- } +- +- if(!message_eq(0, 1, msg)) abort(); +- +- parser_free(); +-} +- +-int +-main (void) +-{ +- parser = NULL; +- int i, j, k; +- int request_count; +- int response_count; +- unsigned long version; +- unsigned major; +- unsigned minor; +- unsigned patch; +- +- version = http_parser_version(); +- major = (version >> 16) & 255; +- minor = (version >> 8) & 255; +- patch = version & 255; +- printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version); +- +- printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); +- +- for (request_count = 0; requests[request_count].name; request_count++); +- for (response_count = 0; responses[response_count].name; response_count++); +- +- //// API +- test_preserve_data(); +- test_parse_url(); +- test_method_str(); +- +- //// NREAD +- test_header_nread_value(); +- +- //// OVERFLOW CONDITIONS +- +- test_header_overflow_error(HTTP_REQUEST); +- test_no_overflow_long_body(HTTP_REQUEST, 1000); +- test_no_overflow_long_body(HTTP_REQUEST, 100000); +- +- test_header_overflow_error(HTTP_RESPONSE); +- test_no_overflow_long_body(HTTP_RESPONSE, 1000); +- test_no_overflow_long_body(HTTP_RESPONSE, 100000); +- +- test_header_content_length_overflow_error(); +- test_chunk_content_length_overflow_error(); +- +- //// HEADER FIELD CONDITIONS +- test_double_content_length_error(HTTP_REQUEST); +- test_chunked_content_length_error(HTTP_REQUEST); +- test_header_cr_no_lf_error(HTTP_REQUEST); +- test_invalid_header_field_token_error(HTTP_REQUEST); +- test_invalid_header_field_content_error(HTTP_REQUEST); +- test_double_content_length_error(HTTP_RESPONSE); +- test_chunked_content_length_error(HTTP_RESPONSE); +- test_header_cr_no_lf_error(HTTP_RESPONSE); +- test_invalid_header_field_token_error(HTTP_RESPONSE); +- test_invalid_header_field_content_error(HTTP_RESPONSE); +- +- //// RESPONSES +- +- for (i = 0; i < response_count; i++) { +- test_message(&responses[i]); +- } +- +- for (i = 0; i < response_count; i++) { +- test_message_pause(&responses[i]); +- } +- +- for (i = 0; i < response_count; i++) { +- test_message_connect(&responses[i]); +- } +- +- for (i = 0; i < response_count; i++) { +- if (!responses[i].should_keep_alive) continue; +- for (j = 0; j < response_count; j++) { +- if (!responses[j].should_keep_alive) continue; +- for (k = 0; k < response_count; k++) { +- test_multiple3(&responses[i], &responses[j], &responses[k]); +- } +- } +- } +- +- test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); +- test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); +- +- // test very large chunked response +- { +- char * msg = create_large_chunked_message(31337, +- "HTTP/1.0 200 OK\r\n" +- "Transfer-Encoding: chunked\r\n" +- "Content-Type: text/plain\r\n" +- "\r\n"); +- struct message large_chunked = +- {.name= "large chunked" +- ,.type= HTTP_RESPONSE +- ,.raw= msg +- ,.should_keep_alive= FALSE +- ,.message_complete_on_eof= FALSE +- ,.http_major= 1 +- ,.http_minor= 0 +- ,.status_code= 200 +- ,.response_status= "OK" +- ,.num_headers= 2 +- ,.headers= +- { { "Transfer-Encoding", "chunked" } +- , { "Content-Type", "text/plain" } +- } +- ,.body_size= 31337*1024 +- ,.num_chunks_complete= 31338 +- }; +- for (i = 0; i < MAX_CHUNKS; i++) { +- large_chunked.chunk_lengths[i] = 1024; +- } +- test_message_count_body(&large_chunked); +- free(msg); +- } +- +- +- +- printf("response scan 1/2 "); +- test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] +- , &responses[NO_BODY_HTTP10_KA_204] +- , &responses[NO_REASON_PHRASE] +- ); +- +- printf("response scan 2/2 "); +- test_scan( &responses[BONJOUR_MADAME_FR] +- , &responses[UNDERSTORE_HEADER_KEY] +- , &responses[NO_CARRIAGE_RET] +- ); +- +- puts("responses okay"); +- +- +- /// REQUESTS +- +- test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); +- +- // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js +- test_simple("GET / HTTP/1.1\r\n" +- "Test: Düsseldorf\r\n", +- HPE_OK); +- +- // Well-formed but incomplete +- test_simple("GET / HTTP/1.1\r\n" +- "Content-Type: text/plain\r\n" +- "Content-Length: 6\r\n" +- "\r\n" +- "fooba", +- HPE_OK); +- +- static const char *all_methods[] = { +- "DELETE", +- "GET", +- "HEAD", +- "POST", +- "PUT", +- //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel +- "OPTIONS", +- "TRACE", +- "COPY", +- "LOCK", +- "MKCOL", +- "MOVE", +- "PROPFIND", +- "PROPPATCH", +- "SEARCH", +- "UNLOCK", +- "BIND", +- "REBIND", +- "UNBIND", +- "ACL", +- "REPORT", +- "MKACTIVITY", +- "CHECKOUT", +- "MERGE", +- "M-SEARCH", +- "NOTIFY", +- "SUBSCRIBE", +- "UNSUBSCRIBE", +- "PATCH", +- "PURGE", +- "MKCALENDAR", +- "LINK", +- "UNLINK", +- 0 }; +- const char **this_method; +- for (this_method = all_methods; *this_method; this_method++) { +- char buf[200]; +- sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); +- test_simple(buf, HPE_OK); +- } +- +- static const char *bad_methods[] = { +- "ASDF", +- "C******", +- "COLA", +- "GEM", +- "GETA", +- "M****", +- "MKCOLA", +- "PROPPATCHA", +- "PUN", +- "PX", +- "SA", +- "hello world", +- 0 }; +- for (this_method = bad_methods; *this_method; this_method++) { +- char buf[200]; +- sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); +- test_simple(buf, HPE_INVALID_METHOD); +- } +- +- // illegal header field name line folding +- test_simple("GET / HTTP/1.1\r\n" +- "name\r\n" +- " : value\r\n" +- "\r\n", +- HPE_INVALID_HEADER_TOKEN); +- +- const char *dumbfuck2 = +- "GET / HTTP/1.1\r\n" +- "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" +- "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" +- "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" +- "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" +- "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" +- "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" +- "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" +- "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" +- "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" +- "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" +- "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" +- "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" +- "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" +- "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" +- "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" +- "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" +- "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" +- "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" +- "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" +- "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" +- "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" +- "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" +- "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" +- "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" +- "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" +- "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" +- "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" +- "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" +- "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" +- "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" +- "\tRA==\r\n" +- "\t-----END CERTIFICATE-----\r\n" +- "\r\n"; +- test_simple(dumbfuck2, HPE_OK); +- +- const char *corrupted_connection = +- "GET / HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "Connection\r\033\065\325eep-Alive\r\n" +- "Accept-Encoding: gzip\r\n" +- "\r\n"; +- test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN); +- +- const char *corrupted_header_name = +- "GET / HTTP/1.1\r\n" +- "Host: www.example.com\r\n" +- "X-Some-Header\r\033\065\325eep-Alive\r\n" +- "Accept-Encoding: gzip\r\n" +- "\r\n"; +- test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN); +- +-#if 0 +- // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body +- // until EOF. +- // +- // no content-length +- // error if there is a body without content length +- const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" +- "Accept: */*\r\n" +- "\r\n" +- "HELLO"; +- test_simple(bad_get_no_headers_no_body, 0); +-#endif +- /* TODO sending junk and large headers gets rejected */ +- +- +- /* check to make sure our predefined requests are okay */ +- for (i = 0; requests[i].name; i++) { +- test_message(&requests[i]); +- } +- +- for (i = 0; i < request_count; i++) { +- test_message_pause(&requests[i]); +- } +- +- for (i = 0; i < request_count; i++) { +- if (!requests[i].should_keep_alive) continue; +- for (j = 0; j < request_count; j++) { +- if (!requests[j].should_keep_alive) continue; +- for (k = 0; k < request_count; k++) { +- test_multiple3(&requests[i], &requests[j], &requests[k]); +- } +- } +- } +- +- printf("request scan 1/4 "); +- test_scan( &requests[GET_NO_HEADERS_NO_BODY] +- , &requests[GET_ONE_HEADER_NO_BODY] +- , &requests[GET_NO_HEADERS_NO_BODY] +- ); +- +- printf("request scan 2/4 "); +- test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] +- , &requests[POST_IDENTITY_BODY_WORLD] +- , &requests[GET_FUNKY_CONTENT_LENGTH] +- ); +- +- printf("request scan 3/4 "); +- test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] +- , &requests[CHUNKED_W_TRAILING_HEADERS] +- , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] +- ); +- +- printf("request scan 4/4 "); +- test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] +- , &requests[PREFIX_NEWLINE_GET ] +- , &requests[CONNECT_REQUEST] +- ); +- +- puts("requests okay"); +- +- return 0; +-} diff --git a/Sming/third-party/.patches/ws_parser.patch b/Sming/third-party/.patches/ws_parser.patch new file mode 100644 index 0000000000..c7fc5bf08e --- /dev/null +++ b/Sming/third-party/.patches/ws_parser.patch @@ -0,0 +1,14 @@ +diff --git a/ws_parser.c b/ws_parser.c +index 88e4810..3da68e1 100644 +--- a/ws_parser.c ++++ b/ws_parser.c +@@ -244,7 +244,8 @@ ws_parser_execute(ws_parser_t* parser, /* mutates! */ char* buff, size_t len) + } + + if(parser->mask_flag) { +- for(size_t i = 0; i < chunk_length; i++) { ++ size_t i = 0; ++ for(i = 0; i < chunk_length; i++) { + buff[i] ^= parser->mask[parser->mask_pos++]; + } + } diff --git a/Sming/third-party/http-parser b/Sming/third-party/http-parser new file mode 160000 index 0000000000..335850f6b8 --- /dev/null +++ b/Sming/third-party/http-parser @@ -0,0 +1 @@ +Subproject commit 335850f6b868d3411968cbf5a4d59fe619dee36f diff --git a/Sming/third-party/ws_parser b/Sming/third-party/ws_parser new file mode 160000 index 0000000000..5d3fa49e2e --- /dev/null +++ b/Sming/third-party/ws_parser @@ -0,0 +1 @@ +Subproject commit 5d3fa49e2e291f38340249e3150c40184244b7da diff --git a/samples/Arducam/app/application.cpp b/samples/Arducam/app/application.cpp index 43cdc5e25b..397704c6ba 100644 --- a/samples/Arducam/app/application.cpp +++ b/samples/Arducam/app/application.cpp @@ -132,12 +132,12 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onFile(HttpRequest &request, HttpResponse &response) { - String file = request.getPath(); + String file = request.uri.Path; if (file[0] == '/') file = file.substring(1); if (file[0] == '.') - response.forbidden(); + response.code = HTTP_STATUS_FORBIDDEN; else { response.setCache(86400, true); // It's important to use cache for better performance. @@ -149,7 +149,7 @@ void onCamSetup(HttpRequest &request, HttpResponse &response) { String size, type; - if (request.getRequestMethod() == RequestMethod::POST) + if (request.method == HTTP_POST) { type = request.getPostParameter("type"); debugf("set type %s", type.c_str()); @@ -224,7 +224,7 @@ void onStream(HttpRequest &request, HttpResponse &response) { } void onFavicon(HttpRequest &request, HttpResponse &response) { - response.notFound(); + response.code = HTTP_STATUS_NOT_FOUND; } diff --git a/samples/Basic_Ssl/app/application.cpp b/samples/Basic_Ssl/app/application.cpp index ffd7105b1d..21bead78b8 100644 --- a/samples/Basic_Ssl/app/application.cpp +++ b/samples/Basic_Ssl/app/application.cpp @@ -71,13 +71,13 @@ void displayCipher(SSL *ssl) m_printf("\n"); } -void onDownload(HttpClient& client, bool success) +int onDownload(HttpConnection& connection, bool success) { - debugf("Got response code: %d", client.getResponseCode()); - debugf("Got content starting with: %s", client.getResponseString().substring(0, 50).c_str()); + debugf("Got response code: %d", connection.getResponseCode()); + debugf("Got content starting with: %s", connection.getResponseString().substring(0, 50).c_str()); debugf("Success: %d", success); - SSL* ssl = downloadClient.getSsl(); + SSL* ssl = connection.getSsl(); if (ssl) { const char *common_name = ssl_get_cert_dn(ssl,SSL_X509_CERT_COMMON_NAME); if (common_name) { @@ -99,20 +99,30 @@ void gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) }; debugf("Connected. Got IP: %s", ip.toString().c_str()); - downloadClient.addSslOptions(SSL_SERVER_VERIFY_LATER); - /* - * The line below shows how to trust only a certificate in which the public key matches the SHA256 fingerprint. - * When google changes the private key that they use in their certificate the SHA256 fingerprint should not match any longer. - */ - downloadClient.pinCertificate(googlePublicKeyFingerprint, eSFT_PkSha256); + HttpRequest* request = new HttpRequest(URL("https://www.google.com/")); + request->setSslOptions(SSL_SERVER_VERIFY_LATER); + + SSLFingerprints fingerprint; /* * The line below shows how to trust only a certificate that matches the SHA1 fingerprint. * When google changes their certificate the SHA1 fingerprint should not match any longer. */ - downloadClient.pinCertificate(googleSha1Fingerprint, eSFT_CertSha1); - downloadClient.downloadString("https://www.google.com/", onDownload); + fingerprint.certSha1 = new uint8_t[SHA1_SIZE]; + memcpy(fingerprint.certSha1, googleSha1Fingerprint, SHA1_SIZE); + + /* + * The line below shows how to trust only a certificate in which the public key matches the SHA256 fingerprint. + * When google changes the private key that they use in their certificate the SHA256 fingerprint should not match any longer. + */ + fingerprint.pkSha256 = new uint8_t[SHA256_SIZE]; + memcpy(fingerprint.pkSha256, googlePublicKeyFingerprint, SHA256_SIZE); + + request->pinCertificate(fingerprint); + request->onRequestComplete(onDownload); + + downloadClient.send(request); } void connectFail(String ssid, uint8_t ssid_len, uint8_t bssid[6], uint8_t reason) diff --git a/samples/Basic_WebClient/.cproject b/samples/Basic_WebClient/.cproject new file mode 100644 index 0000000000..aca5dca953 --- /dev/null +++ b/samples/Basic_WebClient/.cproject @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Basic_WebClient/.project b/samples/Basic_WebClient/.project new file mode 100644 index 0000000000..8b4f000586 --- /dev/null +++ b/samples/Basic_WebClient/.project @@ -0,0 +1,28 @@ + + + Basic_WebClient + + + 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/Basic_WebClient/Makefile b/samples/Basic_WebClient/Makefile new file mode 100644 index 0000000000..16d76cd676 --- /dev/null +++ b/samples/Basic_WebClient/Makefile @@ -0,0 +1,24 @@ +##################################################################### +#### Please don't change this file. Use Makefile-user.mk instead #### +##################################################################### +# Including user Makefile. +# Should be used to set project-specific parameters +include ./Makefile-user.mk + +# Important parameters check. +# We need to make sure SMING_HOME and ESP_HOME variables are set. +# You can use Makefile-user.mk in each project or use enviromental variables to set it globally. + +ifndef SMING_HOME +$(error SMING_HOME is not set. Please configure it in Makefile-user.mk) +endif +ifndef ESP_HOME +$(error ESP_HOME is not set. Please configure it in Makefile-user.mk) +endif + +# Include main Sming Makefile +ifeq ($(RBOOT_ENABLED), 1) +include $(SMING_HOME)/Makefile-rboot.mk +else +include $(SMING_HOME)/Makefile-project.mk +endif diff --git a/samples/Basic_WebClient/Makefile-user.mk b/samples/Basic_WebClient/Makefile-user.mk new file mode 100644 index 0000000000..3901e89699 --- /dev/null +++ b/samples/Basic_WebClient/Makefile-user.mk @@ -0,0 +1,51 @@ +## Local build configuration +## Parameters configured here will override default and ENV values. +## Uncomment and change examples: + +#Add your source directories here separated by space +MODULES = app + +## ESP_HOME sets the path where ESP tools and SDK are located. +## Windows: +# ESP_HOME = c:/Espressif + +## MacOS / Linux: +#ESP_HOME = /opt/esp-open-sdk + +## SMING_HOME sets the path where Sming framework is located. +## Windows: +# SMING_HOME = c:/tools/sming/Sming + +# MacOS / Linux +# SMING_HOME = /opt/sming/Sming + +## COM port parameter is reqruied to flash firmware correctly. +## Windows: +# COM_PORT = COM3 + +# MacOS / Linux: +# COM_PORT = /dev/tty.usbserial + +# Com port speed +# COM_SPEED = 115200 + +## Configure flash parameters (for ESP12-E and other new boards): +# SPI_MODE = dio + +## SPIFFS options +DISABLE_SPIFFS = 1 +# SPIFF_FILES = files + +# We need rBoot in order to be able to run bigger Flash roms. + +#### overridable rBoot options #### +## use rboot build mode +RBOOT_ENABLED ?= 1 +## enable big flash support (for multiple roms, each in separate 1mb block of flash) +RBOOT_BIG_FLASH ?= 1 +## two rom mode (where two roms sit in the same 1mb block of flash) +#RBOOT_TWO_ROMS ?= 1 +## size of the flash chip +SPI_SIZE ?= 4M + +ENABLE_SSL=1 diff --git a/samples/Basic_WebClient/README.md b/samples/Basic_WebClient/README.md new file mode 100644 index 0000000000..f8854df5e2 --- /dev/null +++ b/samples/Basic_WebClient/README.md @@ -0,0 +1,43 @@ +# Compilation +In Sming the SSL support is not enabled by default. + +In order to enable it you should first recompile SmingFramework with *ENABLE_SSL=1* directive. +This can be done using the following commands: + +```bash +cd /Sming +make clean +make ENABLE_SSL=1 +``` + +Once you have enabled the SSL support in SmingFramework you can go forward and compile +your application with the same directive. +For example the Basic_Ssl project should be compiled with + +```bash +cd /samples/Basic_Ssl +make clean +make ENABLE_SSL=1 +``` + +Now you can flash your application to your ESP8266 device. + +# Debug Information +If you want to see more debug information during compile type you should +add the directive *SSL_DEBUG=1*. A recompilation of SmingFramework with SSL support +and SSL dubug information can be done with the following commands: + + +```bash +cd /Sming +make clean +make ENABLE_SSL=1 SSL_DEBUG=1 +``` + +# Slow SSL negotiation +The initial SSL negotiation is CPU intensive. By default SmingFramework switches the CPU +frequency from 80 to 160 MHz. After the negotiation the CPU is switched back to 80 MHz. + +If your device is running on battery this can drain the battery much faster. If you do not +want the switch from 80 to 160 MHz to happen then make sure to recompile SmingFramework with +*SSL_SLOW_CONNECT* directive. diff --git a/samples/Basic_WebClient/app/application.cpp b/samples/Basic_WebClient/app/application.cpp new file mode 100644 index 0000000000..dbb20c6722 --- /dev/null +++ b/samples/Basic_WebClient/app/application.cpp @@ -0,0 +1,183 @@ +/** + * Please, note, that in order to run this sample you should recompile Sming with ENABLE_SSL=1. + * The following three commands should be enough: + * + * cd Sming/Sming + * make clean + * make ENABLE_SSL=1 + */ + +#include +#include + +#include "Network/HttpClient.h" + +// If you want, you can define WiFi settings globally in Eclipse Environment Variables +#ifndef WIFI_SSID + #define WIFI_SSID "PleaseEnterSSID" // Put you SSID and Password here + #define WIFI_PWD "PleaseEnterPass" +#endif + +Timer procTimer; +HttpClient downloadClient; + +/* Debug SSL functions */ +void displaySessionId(SSL *ssl) +{ + int i; + const uint8_t *session_id = ssl_get_session_id(ssl); + int sess_id_size = ssl_get_session_id_size(ssl); + + if (sess_id_size > 0) + { + debugf("-----BEGIN SSL SESSION PARAMETERS-----"); + for (i = 0; i < sess_id_size; i++) + { + m_printf("%02x", session_id[i]); + } + + debugf("\n-----END SSL SESSION PARAMETERS-----"); + } +} + +/** + * Display what cipher we are using + */ +void displayCipher(SSL *ssl) +{ + m_printf("CIPHER is "); + switch (ssl_get_cipher_id(ssl)) + { + case SSL_AES128_SHA: + m_printf("AES128-SHA"); + break; + + case SSL_AES256_SHA: + m_printf("AES256-SHA"); + break; + + case SSL_AES128_SHA256: + m_printf("SSL_AES128_SHA256"); + break; + + case SSL_AES256_SHA256: + m_printf("SSL_AES256_SHA256"); + break; + + default: + m_printf("Unknown - %d", ssl_get_cipher_id(ssl)); + break; + } + + m_printf("\n"); +} + +int onDownload(HttpConnection& connection, bool success) +{ + debugf("Got response code: %d", connection.getResponseCode()); + debugf("Success: %d", success); + if(connection.getRequest()->method != HTTP_HEAD) { + debugf("Got content starting with: %s", connection.getResponseString().substring(0, 50).c_str()); + } + SSL* ssl = connection.getSsl(); + if (ssl) { + const char *common_name = ssl_get_cert_dn(ssl,SSL_X509_CERT_COMMON_NAME); + if (common_name) { + debugf("Common Name:\t\t\t%s\n", common_name); + } + displayCipher(ssl); + displaySessionId(ssl); + } + + return 1; +} + +void connectOk(IPAddress ip, IPAddress mask, IPAddress gateway) +{ + const uint8_t googleSha1Fingerprint[] = { + 0xc5, 0xf9, 0xf0, 0x66, 0xc9, 0x0a, 0x21, 0x4a, 0xbc, 0x37, + 0xae, 0x6c, 0x48, 0xcc, 0x97, 0xa5, 0xc3, 0x35, 0x16, 0xdc + }; + + const uint8_t googlePublicKeyFingerprint[] = { + 0x33, 0x47, 0xd1, 0x8a, 0xc8, 0x52, 0xd4, 0xd6, 0xd0, 0xa2, 0xcb, 0x3f, 0x4b, 0x54, 0x1f, 0x91, + 0x64, 0x94, 0xa0, 0x9c, 0xa1, 0xe2, 0xf2, 0x4c, 0x68, 0xae, 0xc5, 0x27, 0x1c, 0x60, 0x83, 0xad + }; + + debugf("Connected. Got IP: %s", ip.toString().c_str()); + + /* + * Notice: If we use SSL we need to set the SSL settings only for the first request + * and all consecutive requests to the same host:port will try to reuse those settings + */ + + HttpHeaders requestHeaders; + requestHeaders["User-Agent"] = "WebClient/Sming"; + + downloadClient.send( + downloadClient.request("https://www.attachix.com/assets/css/local.css") + ->setHeaders(requestHeaders) + ->setSslOptions(SSL_SERVER_VERIFY_LATER) + /* + * The line below shows how to trust only a certificate in which the public key matches the SHA256 fingerprint. + * When google changes the private key that they use in their certificate the SHA256 fingerprint should not match any longer. + */ +// ->pinCertificate(googlePublicKeyFingerprint, eSFT_PkSha256) + /* + * The line below shows how to trust only a certificate that matches the SHA1 fingerprint. + * When google changes their certificate the SHA1 fingerprint should not match any longer. + */ +// ->pinCertificate(googleSha1Fingerprint, eSFT_CertSha1) + ->onRequestComplete(onDownload) + ); + + + downloadClient.send( + downloadClient.request("https://www.attachix.com/services/") + ->setMethod(HTTP_HEAD) + ->setHeaders(requestHeaders) + ->onRequestComplete(onDownload) + ); + + + downloadClient.send( + downloadClient.request("https://www.attachix.com/business/") + ->setMethod(HTTP_HEAD) + ->onRequestComplete(onDownload) + ); + + downloadClient.sendRequest(HTTP_HEAD, "https://www.attachix.com/intl/en/policies/privacy/", requestHeaders, onDownload); + + // The code above should make ONE tcp connection, ONE SSL handshake and then all requests should be pipelined to the + // remote server taking care to speed the fetching of a page as fast as possible. + +#if 1 + // If we create a second web client instance it will create a new TCP connection and will try to reuse the SSL session id + // from previous connections + HttpClient secondClient; + secondClient.send( + secondClient.request("https://www.attachix.com/") + ->setMethod(HTTP_HEAD) + ->setSslOptions(SSL_SERVER_VERIFY_LATER) + ->onRequestComplete(onDownload) + ); +#endif +} + +void connectFail(String ssid, uint8_t ssidLength, uint8_t *bssid, uint8_t reason) { + debugf("Disconnected from %s. Reason: %d", ssid.c_str(), reason); +} + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); + Serial.systemDebugOutput(true); // Allow debug print to serial + Serial.println("Ready for SSL tests"); + + // Setup the WIFI connection + WifiStation.enable(true); + WifiStation.config(WIFI_SSID, WIFI_PWD); // Put you SSID and Password here + + WifiEvents.onStationGotIP(connectOk); + WifiEvents.onStationDisconnect(connectFail); +} diff --git a/samples/Basic_WebClient/include/ssl/cert.h b/samples/Basic_WebClient/include/ssl/cert.h new file mode 100644 index 0000000000..30c7b65882 --- /dev/null +++ b/samples/Basic_WebClient/include/ssl/cert.h @@ -0,0 +1,43 @@ +unsigned char default_certificate[] = { + 0x30, 0x82, 0x01, 0xd7, 0x30, 0x82, 0x01, 0x40, 0x02, 0x09, 0x00, 0xab, + 0x08, 0x18, 0xa7, 0x03, 0x07, 0x27, 0xfd, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x34, + 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x29, 0x61, + 0x78, 0x54, 0x4c, 0x53, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x44, 0x6f, 0x64, 0x67, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x32, + 0x32, 0x36, 0x32, 0x32, 0x33, 0x33, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x32, + 0x34, 0x30, 0x39, 0x30, 0x33, 0x32, 0x32, 0x33, 0x33, 0x33, 0x39, 0x5a, + 0x30, 0x2c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x0d, 0x61, 0x78, 0x54, 0x4c, 0x53, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x30, 0x81, + 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, + 0x81, 0x81, 0x00, 0xcd, 0xfd, 0x89, 0x48, 0xbe, 0x36, 0xb9, 0x95, 0x76, + 0xd4, 0x13, 0x30, 0x0e, 0xbf, 0xb2, 0xed, 0x67, 0x0a, 0xc0, 0x16, 0x3f, + 0x51, 0x09, 0x9d, 0x29, 0x2f, 0xb2, 0x6d, 0x3f, 0x3e, 0x6c, 0x2f, 0x90, + 0x80, 0xa1, 0x71, 0xdf, 0xbe, 0x38, 0xc5, 0xcb, 0xa9, 0x9a, 0x40, 0x14, + 0x90, 0x0a, 0xf9, 0xb7, 0x07, 0x0b, 0xe1, 0xda, 0xe7, 0x09, 0xbf, 0x0d, + 0x57, 0x41, 0x86, 0x60, 0xa1, 0xc1, 0x27, 0x91, 0x5b, 0x0a, 0x98, 0x46, + 0x1b, 0xf6, 0xa2, 0x84, 0xf8, 0x65, 0xc7, 0xce, 0x2d, 0x96, 0x17, 0xaa, + 0x91, 0xf8, 0x61, 0x04, 0x50, 0x70, 0xeb, 0xb4, 0x43, 0xb7, 0xdc, 0x9a, + 0xcc, 0x31, 0x01, 0x14, 0xd4, 0xcd, 0xcc, 0xc2, 0x37, 0x6d, 0x69, 0x82, + 0xd6, 0xc6, 0xc4, 0xbe, 0xf2, 0x34, 0xa5, 0xc9, 0xa6, 0x19, 0x53, 0x32, + 0x7a, 0x86, 0x0e, 0x91, 0x82, 0x0f, 0xa1, 0x42, 0x54, 0xaa, 0x01, 0x02, + 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x40, + 0xb4, 0x94, 0x9a, 0xa8, 0x89, 0x72, 0x1d, 0x07, 0xe5, 0xb3, 0x6b, 0x88, + 0x21, 0xc2, 0x38, 0x36, 0x9e, 0x7a, 0x8c, 0x49, 0x48, 0x68, 0x0c, 0x06, + 0xe8, 0xdb, 0x1f, 0x4e, 0x05, 0xe6, 0x31, 0xe3, 0xfd, 0xe6, 0x0d, 0x6b, + 0xd8, 0x13, 0x17, 0xe0, 0x2d, 0x0d, 0xb8, 0x7e, 0xcb, 0x20, 0x6c, 0xa8, + 0x73, 0xa7, 0xfd, 0xe3, 0xa7, 0xfa, 0xf3, 0x02, 0x60, 0x78, 0x1f, 0x13, + 0x40, 0x45, 0xee, 0x75, 0xf5, 0x10, 0xfd, 0x8f, 0x68, 0x74, 0xd4, 0xac, + 0xae, 0x04, 0x09, 0x55, 0x2c, 0xdb, 0xd8, 0x07, 0x07, 0x65, 0x69, 0x27, + 0x6e, 0xbf, 0x5e, 0x61, 0x40, 0x56, 0x8b, 0xd7, 0x33, 0x3b, 0xff, 0x6e, + 0x53, 0x7e, 0x9d, 0x3f, 0xc0, 0x40, 0x3a, 0xab, 0xa0, 0x50, 0x4e, 0x80, + 0x47, 0x46, 0x0d, 0x1e, 0xdb, 0x4c, 0xf1, 0x1b, 0x5d, 0x3c, 0x2a, 0x54, + 0xa7, 0x4d, 0xfa, 0x7b, 0x72, 0x66, 0xc5 +}; +unsigned int default_certificate_len = 475; diff --git a/samples/Basic_WebClient/include/ssl/private_key.h b/samples/Basic_WebClient/include/ssl/private_key.h new file mode 100644 index 0000000000..ce7985c5a7 --- /dev/null +++ b/samples/Basic_WebClient/include/ssl/private_key.h @@ -0,0 +1,54 @@ +unsigned char default_private_key[] = { + 0x30, 0x82, 0x02, 0x5d, 0x02, 0x01, 0x00, 0x02, 0x81, 0x81, 0x00, 0xcd, + 0xfd, 0x89, 0x48, 0xbe, 0x36, 0xb9, 0x95, 0x76, 0xd4, 0x13, 0x30, 0x0e, + 0xbf, 0xb2, 0xed, 0x67, 0x0a, 0xc0, 0x16, 0x3f, 0x51, 0x09, 0x9d, 0x29, + 0x2f, 0xb2, 0x6d, 0x3f, 0x3e, 0x6c, 0x2f, 0x90, 0x80, 0xa1, 0x71, 0xdf, + 0xbe, 0x38, 0xc5, 0xcb, 0xa9, 0x9a, 0x40, 0x14, 0x90, 0x0a, 0xf9, 0xb7, + 0x07, 0x0b, 0xe1, 0xda, 0xe7, 0x09, 0xbf, 0x0d, 0x57, 0x41, 0x86, 0x60, + 0xa1, 0xc1, 0x27, 0x91, 0x5b, 0x0a, 0x98, 0x46, 0x1b, 0xf6, 0xa2, 0x84, + 0xf8, 0x65, 0xc7, 0xce, 0x2d, 0x96, 0x17, 0xaa, 0x91, 0xf8, 0x61, 0x04, + 0x50, 0x70, 0xeb, 0xb4, 0x43, 0xb7, 0xdc, 0x9a, 0xcc, 0x31, 0x01, 0x14, + 0xd4, 0xcd, 0xcc, 0xc2, 0x37, 0x6d, 0x69, 0x82, 0xd6, 0xc6, 0xc4, 0xbe, + 0xf2, 0x34, 0xa5, 0xc9, 0xa6, 0x19, 0x53, 0x32, 0x7a, 0x86, 0x0e, 0x91, + 0x82, 0x0f, 0xa1, 0x42, 0x54, 0xaa, 0x01, 0x02, 0x03, 0x01, 0x00, 0x01, + 0x02, 0x81, 0x81, 0x00, 0x95, 0xaa, 0x6e, 0x11, 0xf5, 0x6a, 0x8b, 0xa2, + 0xc6, 0x48, 0xc6, 0x7c, 0x37, 0x6b, 0x1f, 0x55, 0x10, 0x76, 0x26, 0x24, + 0xc3, 0xf2, 0x5c, 0x5a, 0xdd, 0x2e, 0xf3, 0xa4, 0x1e, 0xbc, 0x7b, 0x1c, + 0x80, 0x10, 0x85, 0xbc, 0xd8, 0x45, 0x3c, 0xb8, 0xb2, 0x06, 0x53, 0xb5, + 0xd5, 0x7a, 0xe7, 0x0e, 0x92, 0xe6, 0x42, 0xc2, 0xe2, 0x2a, 0xd5, 0xd1, + 0x03, 0x9f, 0x6f, 0x53, 0x74, 0x68, 0x72, 0x8e, 0xbf, 0x03, 0xbb, 0xab, + 0xbd, 0xa1, 0xf9, 0x81, 0x7d, 0x12, 0xd4, 0x9d, 0xb6, 0xae, 0x4c, 0xad, + 0xca, 0xa8, 0xc9, 0x80, 0x8d, 0x0d, 0xd5, 0xd0, 0xa1, 0xbf, 0xec, 0x60, + 0x48, 0x49, 0xed, 0x97, 0x0f, 0x5e, 0xed, 0xfc, 0x39, 0x15, 0x96, 0x9e, + 0x5d, 0xe2, 0xb4, 0x5d, 0x2e, 0x04, 0xdc, 0x08, 0xa2, 0x65, 0x29, 0x2d, + 0x37, 0xfb, 0x62, 0x90, 0x1b, 0x7b, 0xe5, 0x3a, 0x58, 0x05, 0x55, 0xc1, + 0x02, 0x41, 0x00, 0xfc, 0x69, 0x28, 0xc9, 0xa8, 0xc4, 0x5c, 0xe3, 0xd0, + 0x5e, 0xaa, 0xda, 0xde, 0x87, 0x74, 0xdb, 0xcb, 0x40, 0x78, 0x8e, 0x1d, + 0x12, 0x96, 0x16, 0x61, 0x3f, 0xb3, 0x3e, 0xa3, 0x0d, 0xdc, 0x49, 0xa5, + 0x25, 0x87, 0xc5, 0x97, 0x85, 0x9d, 0xbb, 0xb4, 0xf0, 0x44, 0xfd, 0x6c, + 0xe8, 0xd2, 0x8c, 0xec, 0x33, 0x81, 0x46, 0x1e, 0x10, 0x12, 0x33, 0x16, + 0x95, 0x00, 0x4f, 0x75, 0xb4, 0xe5, 0x79, 0x02, 0x41, 0x00, 0xd0, 0xeb, + 0x65, 0x07, 0x10, 0x3b, 0xd9, 0x03, 0xeb, 0xdc, 0x6f, 0x4b, 0x8f, 0xc3, + 0x87, 0xce, 0x76, 0xd6, 0xc5, 0x14, 0x21, 0x4e, 0xe7, 0x4f, 0x1b, 0xe8, + 0x05, 0xf8, 0x84, 0x1a, 0xe0, 0xc5, 0xd6, 0xe3, 0x08, 0xb3, 0x54, 0x57, + 0x02, 0x1f, 0xd4, 0xd9, 0xfb, 0xff, 0x40, 0xb1, 0x56, 0x1c, 0x60, 0xf7, + 0xac, 0x91, 0xf3, 0xd3, 0xc6, 0x7f, 0x84, 0xfd, 0x84, 0x9d, 0xea, 0x26, + 0xee, 0xc9, 0x02, 0x41, 0x00, 0xa6, 0xcf, 0x1c, 0x6c, 0x81, 0x03, 0x1c, + 0x5c, 0x56, 0x05, 0x6a, 0x26, 0x70, 0xef, 0xd6, 0x13, 0xb7, 0x74, 0x28, + 0xf7, 0xca, 0x50, 0xd1, 0x2d, 0x83, 0x21, 0x64, 0xe4, 0xdd, 0x3f, 0x38, + 0xb8, 0xd6, 0xd2, 0x41, 0xb3, 0x1c, 0x9a, 0xea, 0x0d, 0xf5, 0xda, 0xdf, + 0xcd, 0x17, 0x9f, 0x9a, 0x1e, 0x15, 0xaf, 0x48, 0x1c, 0xbd, 0x9b, 0x63, + 0x5b, 0xad, 0xed, 0xd4, 0xa1, 0xae, 0xa9, 0x59, 0x09, 0x02, 0x40, 0x4e, + 0x08, 0xce, 0xa8, 0x8f, 0xc0, 0xba, 0xf3, 0x83, 0x02, 0xc8, 0x33, 0x62, + 0x14, 0x77, 0xc2, 0x7f, 0x93, 0x02, 0xf3, 0xdc, 0xe9, 0x1a, 0xee, 0xea, + 0x8e, 0x84, 0xc4, 0x69, 0x9b, 0x9c, 0x7f, 0x69, 0x1f, 0x4e, 0x1d, 0xa5, + 0x90, 0x06, 0x44, 0x1b, 0x7d, 0xfc, 0x69, 0x40, 0x21, 0xbc, 0xf7, 0x46, + 0xa4, 0xdc, 0x39, 0x7b, 0xe8, 0x8b, 0x49, 0x10, 0x44, 0x9d, 0x67, 0x5a, + 0x91, 0x86, 0x39, 0x02, 0x40, 0x41, 0x2c, 0x4e, 0xfe, 0xd9, 0x90, 0x89, + 0x00, 0x5c, 0x94, 0x0a, 0x4a, 0x7e, 0x1b, 0x1a, 0x80, 0x06, 0x01, 0x37, + 0xda, 0x50, 0x61, 0x9d, 0x9c, 0xfe, 0x25, 0x7f, 0xd8, 0xd4, 0xc4, 0x9e, + 0x81, 0xf2, 0x0c, 0x1e, 0x38, 0x21, 0x1e, 0x90, 0x3f, 0xd4, 0xba, 0x6c, + 0x53, 0xcb, 0xf0, 0x77, 0x79, 0x9b, 0xf1, 0xfa, 0x3f, 0x81, 0xdc, 0xf3, + 0x21, 0x02, 0x6d, 0xb7, 0x95, 0xc3, 0x2e, 0xce, 0xd5 +}; +unsigned int default_private_key_len = 609; diff --git a/samples/Basic_WebClient/include/user_config.h b/samples/Basic_WebClient/include/user_config.h new file mode 100644 index 0000000000..ae46bf06b9 --- /dev/null +++ b/samples/Basic_WebClient/include/user_config.h @@ -0,0 +1,50 @@ +#ifndef __USER_CONFIG_H__ +#define __USER_CONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + // UART config + #define SERIAL_BAUD_RATE COM_SPEED_SERIAL + + // ESP SDK config + #define LWIP_OPEN_SRC + #define USE_US_TIMER + + // Default types + #define __CORRECT_ISO_CPP_STDLIB_H_PROTO + #include + #include + + // Override c_types.h include and remove buggy espconn + #define _C_TYPES_H_ + #define _NO_ESPCON_ + + // Updated, compatible version of c_types.h + // Just removed types declared in + #include + + // System API declarations + #include + + // C++ Support + #include + // Extended string conversion for compatibility + #include + // Network base API + #include + + // Beta boards + #define BOARD_ESP01 + + // axTLS stuff + #include + #include "util/time.h" + #include "compat/lwipr_compat.h" + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/samples/Basic_WebSkeletonApp/app/webserver.cpp b/samples/Basic_WebSkeletonApp/app/webserver.cpp index e8623a6448..691c1ef0c6 100644 --- a/samples/Basic_WebSkeletonApp/app/webserver.cpp +++ b/samples/Basic_WebSkeletonApp/app/webserver.cpp @@ -13,7 +13,7 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onConfiguration(HttpRequest &request, HttpResponse &response) { - if (request.getRequestMethod() == RequestMethod::POST) + if (request.method == HTTP_POST) { debugf("Update config"); // Update config @@ -74,16 +74,16 @@ void onConfiguration_json(HttpRequest &request, HttpResponse &response) json["StaPassword"] = ActiveConfig.StaPassword; json["StaEnable"] = ActiveConfig.StaEnable; - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void onFile(HttpRequest &request, HttpResponse &response) { - String file = request.getPath(); + String file = request.uri.Path; if (file[0] == '/') file = file.substring(1); if (file[0] == '.') - response.forbidden(); + response.code = HTTP_STATUS_FORBIDDEN; else { response.setCache(86400, true); // It's important to use cache for better performance. @@ -98,7 +98,7 @@ void onAJAXGetState(HttpRequest &request, HttpResponse &response) json["counter"] = counter; - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } diff --git a/samples/CommandProcessing_Debug/app/application.cpp b/samples/CommandProcessing_Debug/app/application.cpp index 56f2f55137..0623904833 100644 --- a/samples/CommandProcessing_Debug/app/application.cpp +++ b/samples/CommandProcessing_Debug/app/application.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -29,12 +30,12 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onFile(HttpRequest &request, HttpResponse &response) { - String file = request.getPath(); + String file = request.uri.Path; if (file[0] == '/') file = file.substring(1); if (file[0] == '.') - response.forbidden(); + response.code = HTTP_STATUS_FORBIDDEN; else { response.setCache(86400, true); // It's important to use cache for better performance. @@ -44,24 +45,24 @@ void onFile(HttpRequest &request, HttpResponse &response) int msgCount = 0; -void wsConnected(WebSocket& socket) +void wsConnected(WebSocketConnection& socket) { Serial.printf("Socket connected\r\n"); } -void wsMessageReceived(WebSocket& socket, const String& message) +void wsMessageReceived(WebSocketConnection& socket, const String& message) { - Serial.printf("WebSocket message received:\r\n%s\r\n", message.c_str()); + Serial.printf("WebSocketConnection message received:\r\n%s\r\n", message.c_str()); String response = "Echo: " + message; socket.sendString(response); } -void wsBinaryReceived(WebSocket& socket, uint8_t* data, size_t size) +void wsBinaryReceived(WebSocketConnection& socket, uint8_t* data, size_t size) { Serial.printf("Websocket binary data recieved, size: %d\r\n", size); } -void wsDisconnected(WebSocket& socket) +void wsDisconnected(WebSocketConnection& socket) { Serial.printf("Socket disconnected"); } @@ -78,13 +79,14 @@ void StartServers() server.setDefaultHandler(onFile); // Web Sockets configuration - server.enableWebSockets(true); - server.commandProcessing(true,"command"); + WebsocketResource* wsResource = new WebsocketResource(); + wsResource->setConnectionHandler(wsConnected); + wsResource->setMessageHandler(wsMessageReceived); + wsResource->setBinaryHandler(wsBinaryReceived); + wsResource->setDisconnectionHandler(wsDisconnected); + + server.addPath("/ws", wsResource); - server.setWebSocketConnectionHandler(wsConnected); - server.setWebSocketMessageHandler(wsMessageReceived); - server.setWebSocketBinaryHandler(wsBinaryReceived); - server.setWebSocketDisconnectionHandler(wsDisconnected); Serial.println("\r\n=== WEB SERVER STARTED ==="); Serial.println(WifiStation.getIP()); diff --git a/samples/DNSCaptivePortal/app/application.cpp b/samples/DNSCaptivePortal/app/application.cpp index 2ca725f3c6..47613fbb06 100644 --- a/samples/DNSCaptivePortal/app/application.cpp +++ b/samples/DNSCaptivePortal/app/application.cpp @@ -16,7 +16,7 @@ void onDefault(HttpRequest &request, HttpResponse &response) void onIndex(HttpRequest &request, HttpResponse &response) { - response.setContentType(ContentType::HTML); + response.setContentType(MIME_HTML); response.sendString("SMING captive portal"); } diff --git a/samples/HttpClient_Instapush/app/application.cpp b/samples/HttpClient_Instapush/app/application.cpp index 8380d0bb8e..805413037f 100644 --- a/samples/HttpClient_Instapush/app/application.cpp +++ b/samples/HttpClient_Instapush/app/application.cpp @@ -39,9 +39,13 @@ class InstapushApplication : protected HttpClient void notify(String event, InstapushTrackers &trackersInfo) { debugf("preparing request"); - setRequestContentType("application/json"); - setRequestHeader("x-instapush-appid", app); - setRequestHeader("x-instapush-appsecret", secret); + + HttpRequest *request = new HttpRequest(URL(url)); + + HttpHeaders requestHeaders; + requestHeaders["Content-Type"] = "application/json"; + requestHeaders["x-instapush-appid"] = app; + requestHeaders["x-instapush-appsecret"] = secret; DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); @@ -55,13 +59,17 @@ class InstapushApplication : protected HttpClient String tempString; root.printTo(tempString); - setPostBody(tempString); - downloadString(url, HttpClientCompletedDelegate(&InstapushApplication::processed, this)); + request->setBody(tempString); + request->onRequestComplete(RequestCompletedDelegate(&InstapushApplication::processed, this)); + + send(request); } - void processed(HttpClient& client, bool successful) + int processed(HttpConnection& client, bool successful) { Serial.println(client.getResponseString()); + + return 0; } private: diff --git a/samples/HttpClient_ThingSpeak/app/application.cpp b/samples/HttpClient_ThingSpeak/app/application.cpp index 91a7b81cd2..53bce394f0 100644 --- a/samples/HttpClient_ThingSpeak/app/application.cpp +++ b/samples/HttpClient_ThingSpeak/app/application.cpp @@ -11,7 +11,7 @@ Timer procTimer; int sensorValue = 0; HttpClient thingSpeak; -void onDataSent(HttpClient& client, bool successful) +int onDataSent(HttpConnection& client, bool successful) { if (successful) Serial.println("Success sent"); @@ -27,12 +27,12 @@ void onDataSent(HttpClient& client, bool successful) if (intVal == 0) Serial.println("Sensor value wasn't accepted. May be we need to wait a little?"); } + + return 0; } void sendData() { - if (thingSpeak.isProcessing()) return; // We need to wait while request processing was completed - // Read our sensor value :) sensorValue++; diff --git a/samples/HttpServer_AJAX/app/application.cpp b/samples/HttpServer_AJAX/app/application.cpp index 2e584f8456..c227da3e69 100644 --- a/samples/HttpServer_AJAX/app/application.cpp +++ b/samples/HttpServer_AJAX/app/application.cpp @@ -62,7 +62,7 @@ void onAjaxInput(HttpRequest &request, HttpResponse &response) for (int i = 0; i < countInputs; i++) gpio[namesInput[i].c_str()] = digitalRead(inputs[i]); - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void onAjaxFrequency(HttpRequest &request, HttpResponse &response) @@ -75,7 +75,7 @@ void onAjaxFrequency(HttpRequest &request, HttpResponse &response) json["status"] = (bool)true; json["value"] = (int)System.getCpuFrequency(); - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void startWebServer() diff --git a/samples/HttpServer_Bootstrap/app/application.cpp b/samples/HttpServer_Bootstrap/app/application.cpp index a60477b6fe..ea03ec6c6e 100644 --- a/samples/HttpServer_Bootstrap/app/application.cpp +++ b/samples/HttpServer_Bootstrap/app/application.cpp @@ -29,7 +29,7 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onHello(HttpRequest &request, HttpResponse &response) { - response.setContentType(ContentType::HTML); + response.setContentType(MIME_HTML); // Use direct strings output only for small amount of data (huge memory allocation) response.sendString("Sming. Let's do smart things."); } @@ -66,24 +66,13 @@ HttpClient downloadClient; int dowfid = 0; void downloadContentFiles() { - if (downloadClient.isProcessing()) return; // Please, wait. - - if (downloadClient.isSuccessful()) - dowfid++; // Success. Go to next file! - downloadClient.reset(); // Reset current download status - - if (dowfid == 0) - downloadClient.downloadFile("http://simple.anakod.ru/templates/index.html"); - else if (dowfid == 1) - downloadClient.downloadFile("http://simple.anakod.ru/templates/bootstrap.css.gz"); - else if (dowfid == 2) - downloadClient.downloadFile("http://simple.anakod.ru/templates/jquery.js.gz"); - else - { - // Content download was completed - downloadTimer.stop(); - startWebServer(); - } + downloadClient.downloadFile("http://simple.anakod.ru/templates/index.html"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/bootstrap.css.gz"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/jquery.js.gz", (RequestCompletedDelegate)([](HttpConnection& connection, bool success) -> int { + if(success) { + startWebServer(); + } + })); } void gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) @@ -91,7 +80,7 @@ void gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) if (!fileExist("index.html") || !fileExist("bootstrap.css.gz") || !fileExist("jquery.js.gz")) { // Download server content at first - downloadTimer.initializeMs(3000, downloadContentFiles).start(); + downloadContentFiles(); } else { diff --git a/samples/HttpServer_ConfigNetwork/app/application.cpp b/samples/HttpServer_ConfigNetwork/app/application.cpp index 40dcb6e245..81e4ad4312 100644 --- a/samples/HttpServer_ConfigNetwork/app/application.cpp +++ b/samples/HttpServer_ConfigNetwork/app/application.cpp @@ -18,7 +18,7 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onIpConfig(HttpRequest &request, HttpResponse &response) { - if (request.getRequestMethod() == RequestMethod::POST) + if (request.method == HTTP_POST) { AppSettings.dhcp = request.getPostParameter("dhcp") == "1"; AppSettings.ip = request.getPostParameter("ip"); @@ -94,7 +94,7 @@ void onAjaxNetworkList(HttpRequest &request, HttpResponse &response) } response.setAllowCrossDomainOrigin("*"); - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void makeConnection() @@ -148,7 +148,7 @@ void onAjaxConnect(HttpRequest &request, HttpResponse &response) json["error"] = WifiStation.getConnectionStatusName(); response.setAllowCrossDomainOrigin("*"); - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void startWebServer() diff --git a/samples/HttpServer_WebSockets/app/CUserData.cpp b/samples/HttpServer_WebSockets/app/CUserData.cpp index a1cf987df7..8ef99cca0a 100644 --- a/samples/HttpServer_WebSockets/app/CUserData.cpp +++ b/samples/HttpServer_WebSockets/app/CUserData.cpp @@ -10,13 +10,13 @@ CUserData::~CUserData() logOut(); } -void CUserData::addSession(WebSocket& connection) +void CUserData::addSession(WebSocketConnection& connection) { activeWebSockets.addElement(&connection); connection.setUserData((void*)this); } -void CUserData::removeSession(WebSocket& connection) +void CUserData::removeSession(WebSocketConnection& connection) { for(int i=0; i < activeWebSockets.count(); i++) { @@ -30,7 +30,7 @@ void CUserData::removeSession(WebSocket& connection) } } -void CUserData::printMessage(WebSocket& connection,const String &msg) +void CUserData::printMessage(WebSocketConnection& connection,const String &msg) { int i=0; for(; i < activeWebSockets.count(); i++) diff --git a/samples/HttpServer_WebSockets/app/application.cpp b/samples/HttpServer_WebSockets/app/application.cpp index 98fe00eb47..24876d8b7a 100644 --- a/samples/HttpServer_WebSockets/app/application.cpp +++ b/samples/HttpServer_WebSockets/app/application.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "CUserData.h" // If you want, you can define WiFi settings globally in Eclipse Environment Variables @@ -36,19 +37,19 @@ void onFile(HttpRequest &request, HttpResponse &response) } } -void wsConnected(WebSocket& socket) +void wsConnected(WebSocketConnection& socket) { totalActiveSockets++; //Use a global instance and add this new connection. Normally userGeorge.addSession(socket); // Notify everybody about new connection - WebSocketsList &clients = server.getActiveWebSockets(); - for (int i = 0; i < clients.count(); i++) - clients[i].sendString("New friend arrived! Total: " + String(totalActiveSockets)); + + String message = "New friend arrived! Total: " + String(totalActiveSockets); + socket.broadcast(message.c_str(), message.length()); } -void wsMessageReceived(WebSocket& socket, const String& message) +void wsMessageReceived(WebSocketConnection& socket, const String& message) { Serial.printf("WebSocket message received:\r\n%s\r\n", message.c_str()); String response = "Echo: " + message; @@ -62,12 +63,12 @@ void wsMessageReceived(WebSocket& socket, const String& message) } } -void wsBinaryReceived(WebSocket& socket, uint8_t* data, size_t size) +void wsBinaryReceived(WebSocketConnection& socket, uint8_t* data, size_t size) { Serial.printf("Websocket binary data recieved, size: %d\r\n", size); } -void wsDisconnected(WebSocket& socket) +void wsDisconnected(WebSocketConnection& socket) { totalActiveSockets--; @@ -79,9 +80,8 @@ void wsDisconnected(WebSocket& socket) } // Notify everybody about lost connection - WebSocketsList &clients = server.getActiveWebSockets(); - for (int i = 0; i < clients.count(); i++) - clients[i].sendString("We lost our friend :( Total: " + String(totalActiveSockets)); + String message = "We lost our friend :( Total: " + String(totalActiveSockets); + socket.broadcast(message.c_str(), message.length()); } void startWebServer() @@ -91,11 +91,13 @@ void startWebServer() server.setDefaultHandler(onFile); // Web Sockets configuration - server.enableWebSockets(true); - server.setWebSocketConnectionHandler(wsConnected); - server.setWebSocketMessageHandler(wsMessageReceived); - server.setWebSocketBinaryHandler(wsBinaryReceived); - server.setWebSocketDisconnectionHandler(wsDisconnected); + WebsocketResource* wsResource=new WebsocketResource(); + wsResource->setConnectionHandler(wsConnected); + wsResource->setMessageHandler(wsMessageReceived); + wsResource->setBinaryHandler(wsBinaryReceived); + wsResource->setDisconnectionHandler(wsDisconnected); + + server.addPath("/ws", wsResource); Serial.println("\r\n=== WEB SERVER STARTED ==="); Serial.println(WifiStation.getIP()); diff --git a/samples/HttpServer_WebSockets/include/CUserData.h b/samples/HttpServer_WebSockets/include/CUserData.h index d5ff82cd72..bf1655d738 100644 --- a/samples/HttpServer_WebSockets/include/CUserData.h +++ b/samples/HttpServer_WebSockets/include/CUserData.h @@ -10,14 +10,14 @@ class CUserData public: CUserData(const char* uName, const char* uData); ~CUserData(); - void addSession(WebSocket& connection); - void removeSession(WebSocket& connection); - void printMessage(WebSocket& connection,const String &msg); + void addSession(WebSocketConnection& connection); + void removeSession(WebSocketConnection& connection); + void printMessage(WebSocketConnection& connection,const String &msg); void logOut(); private: String userName; String userData; - Vector activeWebSockets; + Vector activeWebSockets; }; #endif /*C_USER_DATA_H_SAMPLE*/ diff --git a/samples/MeteoControl/app/application.cpp b/samples/MeteoControl/app/application.cpp index 8dd4aa621d..3c506fbfdd 100644 --- a/samples/MeteoControl/app/application.cpp +++ b/samples/MeteoControl/app/application.cpp @@ -160,13 +160,13 @@ uint32_t lastClockUpdate = 0; DateTime clockValue; const int clockUpdateIntervalMs = 10 * 60 * 1000; // Update web clock every 10 minutes -void onClockUpdating(HttpClient& client, bool successful) +int onClockUpdating(HttpConnection& client, bool successful) { if (!successful) { debugf("CLOCK UPDATE FAILED %d (code: %d)", successful, client.getResponseCode()); lastClockUpdate = 0; - return; + return -1; } // Extract date header from response @@ -174,13 +174,15 @@ void onClockUpdating(HttpClient& client, bool successful) if (clockValue.isNull()) clockValue = client.getLastModifiedDate(); if (!clockValue.isNull()) clockValue.addMilliseconds(ActiveConfig.AddTZ * 1000 * 60 * 60); + + return 0; } void refreshClockTime() { uint32_t nowClock = millis(); if (nowClock < lastClockUpdate) lastClockUpdate = 0; // Prevent overflow, restart - if ((lastClockUpdate == 0 || nowClock - lastClockUpdate > clockUpdateIntervalMs) && !clockWebClient.isProcessing()) + if ((lastClockUpdate == 0 || nowClock - lastClockUpdate > clockUpdateIntervalMs)) { clockWebClient.downloadString("google.com", onClockUpdating); lastClockUpdate = nowClock; diff --git a/samples/MeteoControl/app/webserver.cpp b/samples/MeteoControl/app/webserver.cpp index dd571da48d..1a9a30382c 100644 --- a/samples/MeteoControl/app/webserver.cpp +++ b/samples/MeteoControl/app/webserver.cpp @@ -19,7 +19,7 @@ void onIndex(HttpRequest &request, HttpResponse &response) void onConfiguration(HttpRequest &request, HttpResponse &response) { MeteoConfig cfg = loadConfig(); - if (request.getRequestMethod() == RequestMethod::POST) + if (request.method == HTTP_POST) { debugf("Update config"); // Update config @@ -42,7 +42,7 @@ void onConfiguration(HttpRequest &request, HttpResponse &response) } saveConfig(cfg); startWebClock(); // Apply time zone settings - response.redirect(); + response.redirect("/"); } debugf("Send template"); @@ -91,7 +91,7 @@ void onApiSensors(HttpRequest &request, HttpResponse &response) JsonObject& sensors = json.createNestedObject("sensors"); sensors["temperature"] = StrT.c_str(); sensors["humidity"] = StrRH.c_str(); - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void onApiOutput(HttpRequest &request, HttpResponse &response) @@ -106,7 +106,7 @@ void onApiOutput(HttpRequest &request, HttpResponse &response) JsonObject& json = stream->getRoot(); json["status"] = val != -1; if (val == -1) json["error"] = "Wrong control parameter value, please use: ?control=0|1"; - response.sendJsonObject(stream); + response.sendDataStream(stream, MIME_JSON); } void startWebServer() @@ -130,37 +130,19 @@ void startWebServer() /// FileSystem Initialization /// -Timer downloadTimer; HttpClient downloadClient; int dowfid = 0; void downloadContentFiles() { - if (!downloadTimer.isStarted()) - { - downloadTimer.initializeMs(3000, downloadContentFiles).start(); - } - - if (downloadClient.isProcessing()) return; // Please, wait. debugf("DownloadContentFiles"); - if (downloadClient.isSuccessful()) - dowfid++; // Success. Go to next file! - downloadClient.reset(); // Reset current download status - - if (dowfid == 0) - downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoControl.html", "index.html"); - else if (dowfid == 1) - downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoConfig.html", "config.html"); - else if (dowfid == 2) - downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoAPI.html", "api.html"); - else if (dowfid == 3) - downloadClient.downloadFile("http://simple.anakod.ru/templates/bootstrap.css.gz"); - else if (dowfid == 4) - downloadClient.downloadFile("http://simple.anakod.ru/templates/jquery.js.gz"); - else - { - // Content download was completed - downloadTimer.stop(); - startWebServer(); - } + downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoControl.html", "index.html"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoConfig.html", "config.html"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/MeteoControl/MeteoAPI.html", "api.html"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/bootstrap.css.gz"); + downloadClient.downloadFile("http://simple.anakod.ru/templates/jquery.js.gz", (RequestCompletedDelegate)([](HttpConnection& connection, bool success) -> int { + if(success) { + startWebServer(); + } + })); } From ed431c87c8187250d9b0322a56cde89be1ffb657 Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 4 May 2017 22:45:18 +0200 Subject: [PATCH 03/35] Fixed the JsonStream length. (#1114) Helps the http clients to figure out the size of the response stream without waiting for connection close. --- Sming/SmingCore/DataSourceStream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/SmingCore/DataSourceStream.cpp b/Sming/SmingCore/DataSourceStream.cpp index 0d2eefc807..61fe9e94ef 100644 --- a/Sming/SmingCore/DataSourceStream.cpp +++ b/Sming/SmingCore/DataSourceStream.cpp @@ -341,8 +341,8 @@ uint16_t JsonObjectStream::readMemoryBlock(char* data, int bufSize) int JsonObjectStream::length() { - if (rootNode != JsonObject::invalid()) { - return -1; + if (rootNode == JsonObject::invalid()) { + return 0; } return rootNode.measureLength(); From f8fa86297e537eeb9ab34e734b16bfe061372589 Mon Sep 17 00:00:00 2001 From: slaff Date: Fri, 5 May 2017 17:22:51 +0200 Subject: [PATCH 04/35] Fixed typo in the sendRequest definition. (#1119) --- Sming/SmingCore/Network/HttpClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/SmingCore/Network/HttpClient.h b/Sming/SmingCore/Network/HttpClient.h index 4631928668..173bc97c5d 100644 --- a/Sming/SmingCore/Network/HttpClient.h +++ b/Sming/SmingCore/Network/HttpClient.h @@ -33,7 +33,7 @@ class HttpClient __forceinline bool sendRequest(const HttpMethod method, const String& url, const HttpHeaders& headers, RequestCompletedDelegate requestComplete) { return send(request(url) - ->setMethod(HTTP_GET) + ->setMethod(method) ->setHeaders(headers) ->onRequestComplete(requestComplete) ); From 42d231fe64d041eb65a4a6d9a5c7f4b3ce38d2fa Mon Sep 17 00:00:00 2001 From: Jarek Zgoda Date: Mon, 8 May 2017 22:15:08 +0200 Subject: [PATCH 05/35] Reformat "optional items" part of readme (#1122) To make it more readable, fixes display of options list as well --- Readme.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index b2c8f0159f..9448dfb424 100644 --- a/Readme.md +++ b/Readme.md @@ -58,14 +58,16 @@ n/a = The selected SDK is not available on that OS ## Optional features
There are multiple custom features that can be enabled by default. For example: SSL support, custom LWIP, open PWM, custom heap allocation, more verbose debugging, etc. Click here to see the details

-- Custom LWIP:(default:ON) By default we are using custom compiled LWIP stack instead of the binary one provided from Espressif. This is increasing the free memory and decreasing the space on the flash. All espconn_* functions are turned off by default. If your application requires the use of some of the espconn_* functions then add the ENABLE_ESPCONN=1 directive. See `Makefile-user.mk` from the [Basic_SmartConfig](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_SmartConfig/Makefile-user.mk#L41) application for examples. If you would like to use the binary LWIP then you should turn off the custom LWIP compilation by providing ENABLE_CUSTOM_LWIP=0. -- SSL:(default:off) The SSL support is not built-in by default to conserve resources. If you want to enable it then take a look at the [Readme](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_Ssl/README.md) in the Basic_Ssl samples. -- Custom PWM:(default:off) If you want to use the [open PWM implementation](https://github.com/StefanBruens/ESP8266_new_pwm) then compile your application with ENABLE_CUSTOM_PWM=1. There is no need to recompile the Sming library. -- Custom serial baud rate: (default:off) The default serial baud rate is 115200. If you want to change it to a higher baud rate you can recompile Sming and your application changing the COM_SPEED_SERIAL directive. For example COM_SPEED_SERIAL=921600 -- Custom Heap Allocation:(default:off) If your application is experiencing heap fragmentation then you can try the Umm Malloc heap allocation. To enable it compile Sming with ENABLE_CUSTOM_HEAP=1. In order to use it in your sample/application make sure to compile the sample with ENABLE_CUSTOM_HEAP=1. Avoid enabling your custom heap allocation AND -mforce-l32 compiler flag. -- Debug information log level and format: There are four debug levels: debug=3, info=2, warn=1, error=0. Using DEBUG_VERBOSE_LEVEL you can set the desired level (0-3). For example DEBUG_VERBOSE_LEVEL=2 will show only info messages and above. Another make directive is DEBUG_PRINT_FILENAME_AND_LINE=1 which enables printing the filename and line number of every debug line. This will require extra space on flash. Note: You can compile the Sming library with a set of debug directives and your project with another settings, this way you can control debugging sepparately for sming and your application code. -- Debug information for custom LWIP: If you use custom LWIP(see above) some debug information will be printed for critical errors and situations. You can enable debug information printing altogether using ENABLE_LWIPDEBUG=1. To increase debugging for certain areas you can modify debug options in third-party/esp-open-lwip/include/lwipopts.h -- CommandExecutor feature disabling. This feature enables execution of certain commands by registering token handlers for text received via serial, websocket or telnet connection. If this feature is not used additional RAM/Flash can be obtained by setting ENABLE_CMD_EXECUTOR=0. This will save ~1KB RAM and ~3KB of flash memory. + +- Custom LWIP: (default: ON) By default we are using custom compiled LWIP stack instead of the binary one provided from Espressif. This is increasing the free memory and decreasing the space on the flash. All espconn_* functions are turned off by default. If your application requires the use of some of the espconn_* functions then add the ENABLE_ESPCONN=1 directive. See `Makefile-user.mk` from the [Basic_SmartConfig](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_SmartConfig/Makefile-user.mk#L41) application for examples. If you would like to use the binary LWIP then you should turn off the custom LWIP compilation by providing `ENABLE_CUSTOM_LWIP=0`. +- SSL: (default: OFF) The SSL support is not built-in by default to conserve resources. If you want to enable it then take a look at the [Readme](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_Ssl/README.md) in the Basic_Ssl samples. +- Custom PWM: (default: OFF) If you want to use the [open PWM implementation](https://github.com/StefanBruens/ESP8266_new_pwm) then compile your application with `ENABLE_CUSTOM_PWM=1`. There is no need to recompile the Sming library. +- Custom serial baud rate: (default: OFF) The default serial baud rate is 115200. If you want to change it to a higher baud rate you can recompile Sming and your application changing the `COM_SPEED_SERIAL` directive. For example `COM_SPEED_SERIAL=921600`. +- Custom heap allocation: (default: OFF) If your application is experiencing heap fragmentation then you can try the [umm_malloc](https://github.com/rhempel/umm_malloc) heap allocation. To enable it compile Sming with `ENABLE_CUSTOM_HEAP=1`. In order to use it in your sample/application make sure to compile the sample with `ENABLE_CUSTOM_HEAP=1`. **Do not enable custom heap allocation and -mforce-l32 compiler flag together**. +- Debug information log level and format: There are four debug levels: debug=3, info=2, warn=1, error=0. Using `DEBUG_VERBOSE_LEVEL` you can set the desired level (0-3). For example `DEBUG_VERBOSE_LEVEL=2` will show only info messages and above. Another make directive is `DEBUG_PRINT_FILENAME_AND_LINE=1` which enables printing the filename and line number of every debug line. This will require extra space on flash. Note: you can compile the Sming library with a set of debug directives and your project with another settings, this way you can control debugging sepparately for sming and your application code. +- Debug information for custom LWIP: If you use custom LWIP (see above) some debug information will be printed for critical errors and situations. You can enable debug information printing altogether using `ENABLE_LWIPDEBUG=1`. To increase debugging for certain areas you can modify debug options in `third-party/esp-open-lwip/include/lwipopts.h`. +- CommandExecutor feature: This feature enables execution of certain commands by registering token handlers for text received via serial, websocket or telnet connection. If this feature is not used additional RAM/Flash can be obtained by setting `ENABLE_CMD_EXECUTOR=0`. This will save ~1KB RAM and ~3KB of flash memory. +

## Compilation and flashing From bc64de4fb5aec42976a0092a1ad9ec66143c8c55 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 10 May 2017 10:06:14 +0200 Subject: [PATCH 06/35] HttpServer: (#1123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added infrastructure for body parsers. Form-url-encoded is default body parser for HttpServer, that can be disabled with сержер configuration setting, if needed. --- .../SmingCore/Network/Http/HttpBodyParser.cpp | 97 +++++++++++++++++++ Sming/SmingCore/Network/Http/HttpBodyParser.h | 46 +++++++++ Sming/SmingCore/Network/Http/HttpRequest.h | 2 + .../Network/Http/HttpServerConnection.cpp | 29 +++++- .../Network/Http/HttpServerConnection.h | 12 ++- Sming/SmingCore/Network/HttpServer.cpp | 11 +++ Sming/SmingCore/Network/HttpServer.h | 5 + 7 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 Sming/SmingCore/Network/Http/HttpBodyParser.cpp create mode 100644 Sming/SmingCore/Network/Http/HttpBodyParser.h diff --git a/Sming/SmingCore/Network/Http/HttpBodyParser.cpp b/Sming/SmingCore/Network/Http/HttpBodyParser.cpp new file mode 100644 index 0000000000..36061047a5 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpBodyParser.cpp @@ -0,0 +1,97 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpBodyParser + * + * @author: 2017 - Slavey Karadzhov + * + ****/ + +#include "HttpBodyParser.h" +#include "../WebHelpers/escape.h" + +void formUrlParser(HttpRequest& request, const char *at, int length) +{ + FormUrlParserState* state = (FormUrlParserState*)request.args; + + if(length == -1) { + if(state != NULL) { + delete state; + } + state = new FormUrlParserState; + request.args = (void *)state; + return; + } + + if(length == -2) { + int maxLength = 0; + for(int i=0; isearchChar); + if(pos == -1) { + if(state->searchChar == '=') { + state->postName += data; + } + else { + request.postParams[state->postName] += data; + } + + return; + } + + String buf = data.substring(0, pos); + if(state->searchChar == '=') { + state->postName += buf; + state->searchChar = '&'; + } + else { + request.postParams[state->postName] += buf; + state->searchChar = '='; + state->postName = ""; + } + + data = data.substring(pos + 1); + } +} diff --git a/Sming/SmingCore/Network/Http/HttpBodyParser.h b/Sming/SmingCore/Network/Http/HttpBodyParser.h new file mode 100644 index 0000000000..98ebb881fd --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpBodyParser.h @@ -0,0 +1,46 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpBodyParser + * + * @author: 2017 - Slavey Karadzhov + * + ****/ + +#ifndef _SMING_CORE_HTTP_BODY_PARSER_H_ +#define _SMING_CORE_HTTP_BODY_PARSER_H_ + +#include "HttpCommon.h" +#include "HttpRequest.h" + +typedef Delegate HttpBodyParserDelegate; +typedef HashMap BodyParsers; + +typedef struct { + char searchChar = '='; + String postName = ""; +} FormUrlParserState; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Parses application/x-www-form-urlencoded body data + * @param HttpRequest& + * @param const *char + * @param int length Negative lengths are used to specify special cases + * -1 - start of incoming data + * -2 - end of incoming data + */ +void formUrlParser(HttpRequest& request, const char *at, int length); + +#ifdef __cplusplus +} +#endif + + +#endif /* _SMING_CORE_HTTP_BODY_PARSER_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpRequest.h b/Sming/SmingCore/Network/Http/HttpRequest.h index 32603b6faf..5f1b079697 100644 --- a/Sming/SmingCore/Network/Http/HttpRequest.h +++ b/Sming/SmingCore/Network/Http/HttpRequest.h @@ -133,6 +133,8 @@ class HttpRequest { int retries = 0; // how many times the request should be send again... + void *args = NULL; // Used to store data that should be valid during a single request + protected: RequestHeadersCompletedDelegate headersCompletedDelegate; RequestBodyDelegate requestBodyDelegate; diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 1cd4b51e4a..04447b6342 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -3,6 +3,11 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpServerConnection + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #include "HttpServerConnection.h" @@ -36,10 +41,16 @@ HttpServerConnection::~HttpServerConnection() { } -void HttpServerConnection::setResourceTree(ResourceTree* resourceTree) { +void HttpServerConnection::setResourceTree(ResourceTree* resourceTree) +{ this->resourceTree = resourceTree; } +void HttpServerConnection::setBodyParsers(BodyParsers* bodyParsers) +{ + this->bodyParsers = bodyParsers; +} + int HttpServerConnection::staticOnMessageBegin(http_parser* parser) { HttpServerConnection *connection = (HttpServerConnection*)parser->data; @@ -65,6 +76,7 @@ int HttpServerConnection::staticOnMessageBegin(http_parser* parser) // and temp data... connection->requestHeaders.clear(); + connection->bodyParser = 0; return 0; } @@ -116,6 +128,10 @@ int HttpServerConnection::staticOnMessageComplete(http_parser* parser) return 0; } + if(connection->bodyParser) { + connection->bodyParser(connection->request, NULL, -2); + } + if(connection->resource != NULL && connection->resource->onRequestComplete) { hasError = connection->resource->onRequestComplete(*connection, connection->request, connection->response); } @@ -165,6 +181,13 @@ int HttpServerConnection::staticOnHeadersComplete(http_parser* parser) error = 1; } + if(connection->request.headers.contains("Content-Type")) { + if(connection->bodyParsers->contains(connection->request.headers["Content-Type"])) { + connection->bodyParser = (*connection->bodyParsers)[connection->request.headers["Content-Type"]]; + connection->bodyParser(connection->request, NULL, -1); + } + } + return error; } @@ -212,6 +235,10 @@ int HttpServerConnection::staticOnBody(http_parser *parser, const char *at, size return -1; } + if(connection->bodyParser) { + connection->bodyParser(connection->request, at, length); + } + if(connection->resource != NULL && connection->resource->onBody) { return connection->resource->onBody(*connection, connection->request, at, length); } diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.h b/Sming/SmingCore/Network/Http/HttpServerConnection.h index c84fcfdd6f..639edb9a70 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.h +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.h @@ -3,6 +3,11 @@ * Created 2015 by Skurydin Alexey * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpServerConnection + * + * Modified: 2017 - Slavey Karadzhov + * ****/ #ifndef _SMING_CORE_HTTPSERVERCONNECTION_H_ @@ -15,6 +20,7 @@ #include "HttpResource.h" #include "HttpRequest.h" +#include "HttpBodyParser.h" #ifndef HTTP_SERVER_EXPOSE_NAME #define HTTP_SERVER_EXPOSE_NAME 1 @@ -42,13 +48,12 @@ class HttpServerConnection: public TcpClient virtual ~HttpServerConnection(); void setResourceTree(ResourceTree* resourceTree); + void setBodyParsers(BodyParsers* bodyParsers); void send(); using TcpClient::send; -// virtual void close(); - protected: virtual err_t onReceive(pbuf *buf); virtual void onReadyToSendData(TcpConnectionEvent sourceEvent); @@ -91,6 +96,9 @@ class HttpServerConnection: public TcpClient bool lastWasValue = true; String lastData = ""; String currentField = ""; + + BodyParsers* bodyParsers; + HttpBodyParserDelegate bodyParser; }; #endif /* _SMING_CORE_HTTPSERVERCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/HttpServer.cpp b/Sming/SmingCore/Network/HttpServer.cpp index 6df1a6bec0..7e6b2a6ed5 100644 --- a/Sming/SmingCore/Network/HttpServer.cpp +++ b/Sming/SmingCore/Network/HttpServer.cpp @@ -31,6 +31,11 @@ void HttpServer::configure(HttpServerSettings settings) { if(settings.minHeapSize != -1 && settings.minHeapSize > -1) { minHeapSize = settings.minHeapSize; } + + if(settings.useDefaultBodyParsers) { + setBodyParser(ContentType::toString(MIME_FORM_URL_ENCODED), formUrlParser); + } + setTimeOut(settings.keepAliveSeconds); #ifdef ENABLE_SSL sslSessionCacheSize = settings.sslSessionCacheSize; @@ -46,10 +51,16 @@ HttpServer::~HttpServer() } } +void HttpServer::setBodyParser(const String& contentType, HttpBodyParserDelegate parser) +{ + bodyParsers[contentType] = parser; +} + TcpConnection* HttpServer::createClient(tcp_pcb *clientTcp) { HttpServerConnection* con = new HttpServerConnection(clientTcp); con->setResourceTree(&resourceTree); + con->setBodyParsers(&bodyParsers); con->setCompleteDelegate(TcpClientCompleteDelegate(&HttpServer::onConnectionClose, this)); totalConnections++; diff --git a/Sming/SmingCore/Network/HttpServer.h b/Sming/SmingCore/Network/HttpServer.h index 56d77dfac1..573a1cc081 100644 --- a/Sming/SmingCore/Network/HttpServer.h +++ b/Sming/SmingCore/Network/HttpServer.h @@ -21,12 +21,14 @@ #include "Http/HttpRequest.h" #include "Http/HttpResource.h" #include "Http/HttpServerConnection.h" +#include "Http/HttpBodyParser.h" typedef struct { int maxActiveConnections = 10; // << the maximum number of concurrent requests.. int keepAliveSeconds = 5; // << the default seconds to keep the connection alive before closing it int minHeapSize = -1; // << defines the min heap size that is required to accept connection. // -1 - means use server default + bool useDefaultBodyParsers = 1; // << if the default body parsers, as form-url-encoded, should be used #ifdef ENABLE_SSL int sslSessionCacheSize = 10; // << number of SSL session ids to cache. Setting this to 0 will disable SSL session resumption. #endif @@ -46,6 +48,8 @@ class HttpServer: public TcpServer */ void configure(HttpServerSettings settings); + void setBodyParser(const String& contentType, HttpBodyParserDelegate parser); + /** * @param String path URL path. * @note Path should start with slash. Trailing slashes will be removed. @@ -71,6 +75,7 @@ class HttpServer: public TcpServer private: HttpServerSettings settings; ResourceTree resourceTree; + BodyParsers bodyParsers; }; #endif /* _SMING_CORE_HTTPSERVER_H_ */ From 277c716fb67c95e37f11d324129a0c58ac3be198 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 17 May 2017 15:31:29 +0200 Subject: [PATCH 07/35] HttpServer: extract the actual content-type value. (#1129) --- Sming/SmingCore/Network/Http/HttpServerConnection.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 04447b6342..486bdb7154 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -182,8 +182,14 @@ int HttpServerConnection::staticOnHeadersComplete(http_parser* parser) } if(connection->request.headers.contains("Content-Type")) { - if(connection->bodyParsers->contains(connection->request.headers["Content-Type"])) { - connection->bodyParser = (*connection->bodyParsers)[connection->request.headers["Content-Type"]]; + String contentType = connection->request.headers["Content-Type"]; + int endPos = contentType.indexOf(';'); + if(endPos != -1) { + contentType = contentType.substring(0, endPos); + } + + if(connection->bodyParsers->contains(contentType)) { + connection->bodyParser = (*connection->bodyParsers)[contentType]; connection->bodyParser(connection->request, NULL, -1); } } From 1168f8dd86f810feadb2f348352f8e9f0250865f Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 1 Jun 2017 12:01:16 +0400 Subject: [PATCH 08/35] Ported support for C++11 iterators. (#1141) See https://github.com/esp8266/Arduino/pull/2267. --- Sming/Wiring/WString.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sming/Wiring/WString.h b/Sming/Wiring/WString.h index f9e26b68b8..00364b068f 100644 --- a/Sming/Wiring/WString.h +++ b/Sming/Wiring/WString.h @@ -215,7 +215,11 @@ class String { getBytes((unsigned char *)buf, bufsize, index); } - const char * c_str() const { return buffer; } + const char* c_str() const { return buffer; } + char* begin() { return buffer; } + char* end() { return buffer + length(); } + const char* begin() const { return c_str(); } + const char* end() const { return c_str() + length(); } // search int IRAM_ATTR indexOf(char ch) const; From fd7a20d88ceb7850980988f819b2975414d9b85b Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 1 Jun 2017 18:06:59 +0400 Subject: [PATCH 09/35] Detect division by zero. (#1142) See https://github.com/esp8266/Arduino/pull/2397 for details. --- Sming/Wiring/WMath.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sming/Wiring/WMath.cpp b/Sming/Wiring/WMath.cpp index 65ff351ec3..6dfd927480 100644 --- a/Sming/Wiring/WMath.cpp +++ b/Sming/Wiring/WMath.cpp @@ -79,9 +79,12 @@ long random(long howsmall, long howbig) } -long map(long x, long in_min, long in_max, long out_min, long out_max) -{ - return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min; +long map(long x, long in_min, long in_max, long out_min, long out_max) { + long divisor = (in_max - in_min) + out_min; + if(divisor == 0){ + return -1; //AVR returns -1, SAM returns 0 + } + return (x - in_min) * (out_max - out_min) / divisor; } From bfccdd2991b60b117c31092e2974e9773f5b5366 Mon Sep 17 00:00:00 2001 From: slaff Date: Tue, 13 Jun 2017 09:12:36 +0200 Subject: [PATCH 10/35] Fix/memory related (#1143) * Prevent memory fragmentation when a lot of data is transferred. * Fix for possible memory leaks. * Fixed memory leak in TcpServer in SSL mode. --- .../SmingCore/Network/FTPServerConnection.cpp | 3 +-- Sming/SmingCore/Network/TcpConnection.cpp | 19 +++++++++++++------ Sming/SmingCore/Network/TcpServer.cpp | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Sming/SmingCore/Network/FTPServerConnection.cpp b/Sming/SmingCore/Network/FTPServerConnection.cpp index bdb715e105..492557a25a 100644 --- a/Sming/SmingCore/Network/FTPServerConnection.cpp +++ b/Sming/SmingCore/Network/FTPServerConnection.cpp @@ -77,10 +77,9 @@ class FTPDataRetrieve : public FTPDataStream virtual void transferData(TcpConnectionEvent sourceEvent) { if (completed) return; - char * buf = new char [1024]; + char buf[1024]; int len = fileRead(file, buf, 1024); write(buf, len, TCP_WRITE_FLAG_COPY); - delete buf; if (fileIsEOF(file)) { completed = true; diff --git a/Sming/SmingCore/Network/TcpConnection.cpp b/Sming/SmingCore/Network/TcpConnection.cpp index 0e0f304292..bb4a24e943 100644 --- a/Sming/SmingCore/Network/TcpConnection.cpp +++ b/Sming/SmingCore/Network/TcpConnection.cpp @@ -51,13 +51,15 @@ bool TcpConnection::connect(String server, int port, bool useSsl /* = false */, #ifdef ENABLE_SSL this->sslOptions |= sslOptions; - if(ssl_ext == NULL) { - ssl_ext = ssl_ext_new(); - ssl_ext->host_name = (char *)malloc(server.length() + 1); - strcpy(ssl_ext->host_name, server.c_str()); - - ssl_ext->max_fragment_size = 4*1024; // 4K max size + if(ssl_ext != NULL) { + ssl_ext_free(ssl_ext); } + + ssl_ext = ssl_ext_new(); + ssl_ext->host_name = (char *)malloc(server.length() + 1); + strcpy(ssl_ext->host_name, server.c_str()); + + ssl_ext->max_fragment_size = 4*1024; // 4K max size #endif debugf("connect to: %s", server.c_str()); @@ -405,6 +407,11 @@ err_t TcpConnection::staticOnConnected(void *arg, tcp_pcb *tcp, err_t err) System.setCpuFrequency(eCF_160MHz); // For shorter waiting time, more power consumption. #endif debugf("SSL: handshake start (%d ms)", millis()); + + if(con->ssl != NULL) { + ssl_free(con->ssl); + } + con->sslContext = ssl_ctx_new(SSL_CONNECT_IN_PARTS | sslOptions, 1); if (con->clientKeyCert.keyLength && con->clientKeyCert.certificateLength) { diff --git a/Sming/SmingCore/Network/TcpServer.cpp b/Sming/SmingCore/Network/TcpServer.cpp index 3566455811..828d725563 100644 --- a/Sming/SmingCore/Network/TcpServer.cpp +++ b/Sming/SmingCore/Network/TcpServer.cpp @@ -166,6 +166,7 @@ err_t TcpServer::onAccept(tcp_pcb *clientTcp, err_t err) if(useSsl) { int clientfd = axl_append(clientTcp); if(clientfd == -1) { + delete client; debugf("SSL: Unable to initiate tcp "); return ERR_ABRT; } From a41edb7f50e203029c0053ccfea627184e835c1f Mon Sep 17 00:00:00 2001 From: slaff Date: Tue, 13 Jun 2017 17:28:58 +0200 Subject: [PATCH 11/35] Changed the httpclient sample callbacks to return 0 on success. (#1156) --- samples/Basic_Ssl/app/application.cpp | 2 ++ samples/Basic_WebClient/app/application.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/Basic_Ssl/app/application.cpp b/samples/Basic_Ssl/app/application.cpp index 21bead78b8..378195ab17 100644 --- a/samples/Basic_Ssl/app/application.cpp +++ b/samples/Basic_Ssl/app/application.cpp @@ -86,6 +86,8 @@ int onDownload(HttpConnection& connection, bool success) displayCipher(ssl); displaySessionId(ssl); } + + return 0; // return 0 on success in your callbacks } void gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) diff --git a/samples/Basic_WebClient/app/application.cpp b/samples/Basic_WebClient/app/application.cpp index dbb20c6722..e501b59fc2 100644 --- a/samples/Basic_WebClient/app/application.cpp +++ b/samples/Basic_WebClient/app/application.cpp @@ -89,7 +89,7 @@ int onDownload(HttpConnection& connection, bool success) displaySessionId(ssl); } - return 1; + return 0; // return 0 on success in your callbacks } void connectOk(IPAddress ip, IPAddress mask, IPAddress gateway) From d6a1aaf4c95bbae0773aec7aca06c2d7d50acbb1 Mon Sep 17 00:00:00 2001 From: it-guru Date: Wed, 14 Jun 2017 08:31:13 +0200 Subject: [PATCH 12/35] add Timer::tick() for better class derivation from Timer class (#1135) --- Sming/SmingCore/Timer.cpp | 29 ++++++++++++++++++----------- Sming/SmingCore/Timer.h | 9 ++++++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Sming/SmingCore/Timer.cpp b/Sming/SmingCore/Timer.cpp index 85bb0fc75b..2e6ac3690b 100644 --- a/Sming/SmingCore/Timer.cpp +++ b/Sming/SmingCore/Timer.cpp @@ -48,8 +48,7 @@ void Timer::start(bool repeating/* = true*/) { this->repeating = repeating; stop(); - if(interval == 0 || (!callback && !delegate_func)) - return; + if (interval == 0) return; ets_timer_setfn(&timer, (os_timer_func_t *)processing, this); if (interval > 10000) @@ -182,15 +181,23 @@ void Timer::processing(void *arg) ptimer->stop(); } } - - if (ptimer->callback) - { - ptimer->callback(); - } - else if (ptimer->delegate_func) - { - ptimer->delegate_func(); - } + ptimer->tick(); } } + + +void Timer::tick() +{ + if (callback) + { + callback(); + } + else if (delegate_func) + { + delegate_func(); + } + else{ + stop(); + } +} diff --git a/Sming/SmingCore/Timer.h b/Sming/SmingCore/Timer.h index bf80996540..247c5ef6cb 100644 --- a/Sming/SmingCore/Timer.h +++ b/Sming/SmingCore/Timer.h @@ -126,11 +126,16 @@ class Timer */ void IRAM_ATTR setCallback(TimerDelegate delegateFunction); - /** @} */ protected: static void IRAM_ATTR processing(void *arg); + /** @brief virtual timer loop() method + * @note Can be override in class derivations. If overwriten, + * no classic other callbacks are working. + */ + virtual void tick(); + /** @} */ private: os_timer_t timer; @@ -144,6 +149,8 @@ class Timer // was added to allow for longer timer intervals. uint16_t long_intvl_cntr = 0; uint16_t long_intvl_cntr_lim = 0; + + }; #endif /* _SMING_CORE_Timer_H_ */ From 507410d47968719482ff2a42d7d5c4605f21ad82 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 14 Jun 2017 13:25:18 +0200 Subject: [PATCH 13/35] Changed the delay function to call wdt_reset for intervals that are bigger than the max-safe-delay period. (#1154) --- Sming/SmingCore/Clock.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sming/SmingCore/Clock.cpp b/Sming/SmingCore/Clock.cpp index 77b5b9e90d..d9af135bc2 100644 --- a/Sming/SmingCore/Clock.cpp +++ b/Sming/SmingCore/Clock.cpp @@ -8,6 +8,8 @@ #include "../SmingCore/Clock.h" #include "../Wiring/WiringFrameworkIncludes.h" +#define MAX_SAFE_DELAY 1000 + unsigned long millis(void) { return system_get_time() / 1000UL; @@ -20,7 +22,18 @@ unsigned long micros(void) void delay(uint32_t time) { - os_delay_us(time * 1000); + int quotient = time / MAX_SAFE_DELAY; + int remainder = time % MAX_SAFE_DELAY; + for(int i=0, max = quotient + 1; i < max ; i++) { + if(i == quotient) { + os_delay_us(remainder * 1000); + } + else { + os_delay_us(MAX_SAFE_DELAY * 1000); + } + + system_soft_wdt_feed (); + } } void delayMicroseconds(uint32_t time) From e4ee2b3d4b40bc60f9add12d4224c525a0d31497 Mon Sep 17 00:00:00 2001 From: Jarek Zgoda Date: Wed, 14 Jun 2017 14:55:19 +0200 Subject: [PATCH 14/35] Cleaned up Nokia 5110 LCD sample code (#1091) --- samples/ScreenLCD_5110/app/application.cpp | 54 +++++++++------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/samples/ScreenLCD_5110/app/application.cpp b/samples/ScreenLCD_5110/app/application.cpp index 39518c7fa7..5a965f82d5 100644 --- a/samples/ScreenLCD_5110/app/application.cpp +++ b/samples/ScreenLCD_5110/app/application.cpp @@ -2,43 +2,33 @@ #include #include -// Software SPI (slower updates, more flexible pin options): - // pin 7 - Serial clock out (SCLK) - // pin 6 - Serial data out (DIN) - // pin 5 - Data/Command select (D/C) - // pin 4 - LCD chip select (CS) - // pin 3 - LCD reset (RST) -Adafruit_PCD8544 display = Adafruit_PCD8544(14, 13, 12, 5, 4); +// GPIO13/D7 - Serial clock out (SCLK) +// GPIO12/D6 - Serial data out (DIN) +// GPIO14/D5 - Data/Command select (D/C) +// GPIO5/D1 - LCD chip select (CS) +// GPIO4/D2 - LCD reset (RST) +Adafruit_PCD8544 display = Adafruit_PCD8544(13, 12, 14, 5, 4); + + void displayTest() { display.begin(); - // init done - - // you can change the contrast around to adapt the display - // for the best viewing! - display.setContrast(10); - - display.display(); // show splashscreen - delay(200); - display.clearDisplay(); // clears the screen and buffer - - display.setRotation(4); // rotate 90 degrees counter clockwise, can also use values of 2 and 3 to go further. - display.setTextSize(1); - display.setTextColor(BLACK); - display.setCursor(0,0); - display.println("Sming"); - display.setTextSize(2); - display.println("Example!"); - display.display(); + display.setContrast(10); + display.display(); // show splashscreen + delay(2000); + display.clearDisplay(); // no changes will be visible until display() is called + display.setRotation(4); // rotate 90 degrees counter clockwise, can also use values of 2 and 3 to go further. + display.setTextSize(1); + display.setTextColor(BLACK); + display.setCursor(0, 0); + display.println("Sming"); + display.setTextSize(2); + display.println("Example"); + display.display(); } + void init() { - Serial.begin(SERIAL_BAUD_RATE); // 115200 by default - Serial.systemDebugOutput(true); // Enable debug output to serial - - WifiStation.enable(false); - WifiAccessPoint.enable(true); - + Serial.begin(SERIAL_BAUD_RATE); displayTest(); } - From 68c0df1330b1db11144c7fe9747158e40e494bc9 Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 19 Jun 2017 09:07:23 +0200 Subject: [PATCH 15/35] WsConnection: declared broadcast as static so that it can be called anywhere in the application. (#1160) --- Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h index e564bc7d08..0412fb3140 100644 --- a/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h +++ b/Sming/SmingCore/Network/Http/Websocket/WebSocketConnection.h @@ -39,7 +39,7 @@ class WebSocketConnection bool initialize(HttpRequest &request, HttpResponse &response); virtual void send(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); - void broadcast(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); + static void broadcast(const char* message, int length, wsFrameType type = WS_TEXT_FRAME); void sendString(const String& message); void sendBinary(const uint8_t* data, int size); From f6c1199e3ff238e709ad60911b0d43fb55b7fe6d Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 19 Jun 2017 09:20:28 +0200 Subject: [PATCH 16/35] HttpClient: memory usage fixes. (#1161) Related to #1138. --- .../SmingCore/Network/Http/HttpConnection.cpp | 63 +++++++++---------- Sming/SmingCore/Network/Http/HttpConnection.h | 2 +- Sming/SmingCore/Network/HttpClient.cpp | 3 + Sming/SmingCore/Network/HttpClient.h | 10 +++ 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/Sming/SmingCore/Network/Http/HttpConnection.cpp b/Sming/SmingCore/Network/Http/HttpConnection.cpp index 8ef0b7463f..079ec22f7d 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpConnection.cpp @@ -22,6 +22,26 @@ HttpConnection::HttpConnection(RequestQueue* queue): TcpClient(false), mode(eHCM_String) { this->waitingQueue = queue; + + http_parser_init(&parser, HTTP_RESPONSE); + parser.data = (void*)this; + + memset(&parserSettings, 0, sizeof(parserSettings)); + + // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. + parserSettings.on_message_begin = staticOnMessageBegin; + parserSettings.on_headers_complete = staticOnHeadersComplete; + parserSettings.on_message_complete = staticOnMessageComplete; + + parserSettings.on_chunk_header = staticOnChunkHeader; + parserSettings.on_chunk_complete = staticOnChunkComplete; + + + // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; + parserSettings.on_status = staticOnStatus; + parserSettings.on_header_field = staticOnHeaderField; + parserSettings.on_header_value = staticOnHeaderValue; + parserSettings.on_body = staticOnBody; } bool HttpConnection::connect(const String& host, int port, bool useSsl /* = false */, uint32_t sslOptions /* = 0 */) { @@ -112,7 +132,9 @@ void HttpConnection::reset() } code = 0; - responseStringData = ""; + if(responseStringData.length()) { + responseStringData = ""; + } responseHeaders.clear(); lastWasValue = true; @@ -324,34 +346,12 @@ int HttpConnection::staticOnChunkComplete(http_parser* parser) { err_t HttpConnection::onConnected(err_t err) { if (err == ERR_OK) { - // create parser ... - if(parser == NULL) { - parser = new http_parser; - http_parser_init(parser, HTTP_RESPONSE); - parser->data = (void*)this; - - memset(&parserSettings, 0, sizeof(parserSettings)); - // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. - parserSettings.on_message_begin = staticOnMessageBegin; - parserSettings.on_headers_complete = staticOnHeadersComplete; - parserSettings.on_message_complete = staticOnMessageComplete; - - parserSettings.on_chunk_header = staticOnChunkHeader; - parserSettings.on_chunk_complete = staticOnChunkComplete; - - - // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; - parserSettings.on_status = staticOnStatus; - parserSettings.on_header_field = staticOnHeaderField; - parserSettings.on_header_value = staticOnHeaderValue; - parserSettings.on_body = staticOnBody; - } - debugf("HttpConnection::onConnected: waitingQueue.count: %d", waitingQueue->count()); do { HttpRequest* request = waitingQueue->peek(); if(request == NULL) { + debugf("Nothing in the waiting queue"); break; } @@ -511,10 +511,10 @@ err_t HttpConnection::onReceive(pbuf *buf) { pbuf *cur = buf; int parsedBytes = 0; while (cur != NULL && cur->len > 0) { - parsedBytes += http_parser_execute(parser, &parserSettings, (char*) cur->payload, cur->len); - if(HTTP_PARSER_ERRNO(parser) != HPE_OK) { + parsedBytes += http_parser_execute(&parser, &parserSettings, (char*) cur->payload, cur->len); + if(HTTP_PARSER_ERRNO(&parser) != HPE_OK) { // we ran into trouble - abort the connection - debugf("HTTP parser error: %s", http_errno_name(HTTP_PARSER_ERRNO(parser))); + debugf("HTTP parser error: %s", http_errno_name(HTTP_PARSER_ERRNO(&parser))); cleanup(); TcpConnection::onReceive(NULL); return ERR_ABRT; @@ -523,8 +523,8 @@ err_t HttpConnection::onReceive(pbuf *buf) { cur = cur->next; } - if (parser->upgrade) { - return onProtocolUpgrade(parser); + if (parser.upgrade) { + return onProtocolUpgrade(&parser); } else if (parsedBytes != buf->tot_len) { TcpClient::onReceive(NULL); @@ -552,11 +552,6 @@ void HttpConnection::cleanup() { for(int i=0; i < executionQueue.count(); i++) { waitingQueue->enqueue(executionQueue.dequeue()); } - - if(parser != NULL) { - delete parser; - parser = NULL; - } } HttpConnection::~HttpConnection() { diff --git a/Sming/SmingCore/Network/Http/HttpConnection.h b/Sming/SmingCore/Network/Http/HttpConnection.h index 99c29b85b5..15147c7815 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.h +++ b/Sming/SmingCore/Network/Http/HttpConnection.h @@ -104,7 +104,7 @@ class HttpConnection : protected TcpClient { RequestQueue* waitingQueue; RequestQueue executionQueue; - http_parser *parser = NULL; + http_parser parser; http_parser_settings parserSettings; HttpHeaders responseHeaders; diff --git a/Sming/SmingCore/Network/HttpClient.cpp b/Sming/SmingCore/Network/HttpClient.cpp index f1bc0ba84a..0ef64dc0de 100644 --- a/Sming/SmingCore/Network/HttpClient.cpp +++ b/Sming/SmingCore/Network/HttpClient.cpp @@ -24,6 +24,7 @@ bool HttpClient::send(HttpRequest* request) { if(!queue[cacheKey]->enqueue(request)) { // the queue is full and we cannot add more requests at the time. debugf("The request queue is full at the moment"); + delete request; return false; } @@ -34,6 +35,8 @@ bool HttpClient::send(HttpRequest* request) { debugf("Removing stale connection: State: %d, Active: %d", (int)httpConnectionPool[cacheKey]->getConnectionState(), (httpConnectionPool[cacheKey]->isActive() ? 1: 0)); + delete httpConnectionPool[cacheKey]; + httpConnectionPool[cacheKey] = NULL; httpConnectionPool.remove(cacheKey); } diff --git a/Sming/SmingCore/Network/HttpClient.h b/Sming/SmingCore/Network/HttpClient.h index 173bc97c5d..da1bd1fedd 100644 --- a/Sming/SmingCore/Network/HttpClient.h +++ b/Sming/SmingCore/Network/HttpClient.h @@ -57,7 +57,17 @@ class HttpClient bool downloadFile(const String& url, const String& saveFileName, RequestCompletedDelegate requestComplete = NULL); /* Low Level Methods */ + + /* + * @brief This method queues a request and sends it, once it is connected to the remote server. + * @param HttpRequest* request The request object will be freed inside of the method. + * Do not try to reuse it outside of the send method as it will lead to unpredicted results + * + * @retval bool true if the request was queued, false otherwise. + * + */ bool send(HttpRequest* request); + HttpRequest* request(const String& url); #ifdef ENABLE_SSL From d44599d78aafac7cc7f490467bb60a269362a365 Mon Sep 17 00:00:00 2001 From: Slavey Karadzhov Date: Tue, 20 Jun 2017 10:03:00 +0200 Subject: [PATCH 17/35] TemplateStream: the total length should be calculated from the browser. HttpServer: The default keepalive time is set to 0. --- Sming/SmingCore/DataSourceStream.h | 6 ++++++ Sming/SmingCore/Network/HttpServer.cpp | 2 +- Sming/SmingCore/Network/HttpServer.h | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sming/SmingCore/DataSourceStream.h b/Sming/SmingCore/DataSourceStream.h index 862b36f51f..91466b99fe 100644 --- a/Sming/SmingCore/DataSourceStream.h +++ b/Sming/SmingCore/DataSourceStream.h @@ -244,6 +244,12 @@ class TemplateFileStream : public FileStream */ inline TemplateVariables& variables() { return templateData; } + /** + * @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + int length() { return -1; } + private: TemplateVariables templateData; TemplateExpandState state; diff --git a/Sming/SmingCore/Network/HttpServer.cpp b/Sming/SmingCore/Network/HttpServer.cpp index 7e6b2a6ed5..fa2e2b8ae5 100644 --- a/Sming/SmingCore/Network/HttpServer.cpp +++ b/Sming/SmingCore/Network/HttpServer.cpp @@ -17,7 +17,7 @@ HttpServer::HttpServer() { - settings.keepAliveSeconds = 10; + settings.keepAliveSeconds = 0; configure(settings); } diff --git a/Sming/SmingCore/Network/HttpServer.h b/Sming/SmingCore/Network/HttpServer.h index 573a1cc081..85b8b0516c 100644 --- a/Sming/SmingCore/Network/HttpServer.h +++ b/Sming/SmingCore/Network/HttpServer.h @@ -25,7 +25,7 @@ typedef struct { int maxActiveConnections = 10; // << the maximum number of concurrent requests.. - int keepAliveSeconds = 5; // << the default seconds to keep the connection alive before closing it + int keepAliveSeconds = 0; // << the default seconds to keep the connection alive before closing it int minHeapSize = -1; // << defines the min heap size that is required to accept connection. // -1 - means use server default bool useDefaultBodyParsers = 1; // << if the default body parsers, as form-url-encoded, should be used From d870c27c6edd225381829f4ee0a0edf1816ac92b Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 21 Jun 2017 09:28:27 +0200 Subject: [PATCH 18/35] Custom PWM is enabled by default. (#1164) --- Sming/Makefile | 3 ++- Sming/Makefile-project.mk | 3 ++- Sming/Makefile-rboot.mk | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sming/Makefile b/Sming/Makefile index 4fbf170496..49fbf68dfa 100644 --- a/Sming/Makefile +++ b/Sming/Makefile @@ -215,7 +215,8 @@ endif # libraries used in this project, mainly provided by the SDK LIBS = microc microgcc hal phy pp net80211 $(LIBLWIP) wpa main -# WARNING: In the next versions ENABLE_CUSTOM_PWM will be set to 1 by default + +ENABLE_CUSTOM_PWM ?= 1 ifeq ($(ENABLE_CUSTOM_PWM), 1) THIRD_PARTY_DATA += third-party/pwm/pwm.c endif diff --git a/Sming/Makefile-project.mk b/Sming/Makefile-project.mk index 371156d4e0..03e7b76c53 100644 --- a/Sming/Makefile-project.mk +++ b/Sming/Makefile-project.mk @@ -196,7 +196,8 @@ ifeq ($(ENABLE_CUSTOM_LWIP), 1) endif LIBPWM = pwm -# WARNING: In the next versions ENABLE_CUSTOM_PWM will be set to 1 by default + +ENABLE_CUSTOM_PWM ?= 1 ifeq ($(ENABLE_CUSTOM_PWM), 1) LIBPWM = pwm_open CUSTOM_TARGETS += $(USER_LIBDIR)/lib$(LIBPWM).a diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index 3c1c2d64e8..b26f761668 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -244,7 +244,8 @@ ifeq ($(ENABLE_CUSTOM_LWIP), 1) endif LIBPWM = pwm -# WARNING: In the next versions ENABLE_CUSTOM_PWM will be set to 1 by default + +ENABLE_CUSTOM_PWM ?= 1 ifeq ($(ENABLE_CUSTOM_PWM), 1) LIBPWM = pwm_open CUSTOM_TARGETS += $(USER_LIBDIR)/lib$(LIBPWM).a From fab7b5bc2974e4a7a4cfcfdfef9ffeef141853ee Mon Sep 17 00:00:00 2001 From: Slavey Karadzhov Date: Mon, 26 Jun 2017 12:11:34 +0200 Subject: [PATCH 19/35] Reset response and request data after completion. --- Sming/SmingCore/Network/Http/HttpRequest.cpp | 10 ++++++++++ Sming/SmingCore/Network/Http/HttpRequest.h | 2 ++ Sming/SmingCore/Network/Http/HttpResponse.cpp | 8 ++++++++ Sming/SmingCore/Network/Http/HttpResponse.h | 2 ++ Sming/SmingCore/Network/Http/HttpServerConnection.cpp | 10 ++++++++++ Sming/SmingCore/Network/Http/HttpServerConnection.h | 2 +- 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Sming/SmingCore/Network/Http/HttpRequest.cpp b/Sming/SmingCore/Network/Http/HttpRequest.cpp index 17ef222dfd..614c3b7f35 100644 --- a/Sming/SmingCore/Network/Http/HttpRequest.cpp +++ b/Sming/SmingCore/Network/Http/HttpRequest.cpp @@ -239,6 +239,16 @@ HttpRequest* HttpRequest::onRequestComplete(RequestCompletedDelegate delegateFun return this; } +void HttpRequest::reset() +{ + headers.clear(); + postParams.clear(); + if(queryParams != NULL) { + delete queryParams; + queryParams = NULL; + } +} + #ifndef SMING_RELEASE String HttpRequest::toString() { String content = ""; diff --git a/Sming/SmingCore/Network/Http/HttpRequest.h b/Sming/SmingCore/Network/Http/HttpRequest.h index 5f1b079697..35f7807c1b 100644 --- a/Sming/SmingCore/Network/Http/HttpRequest.h +++ b/Sming/SmingCore/Network/Http/HttpRequest.h @@ -116,6 +116,8 @@ class HttpRequest { HttpRequest* onBody(RequestBodyDelegate delegateFunction); HttpRequest* onRequestComplete(RequestCompletedDelegate delegateFunction); + void reset(); + #ifndef SMING_RELEASE /** * @brief Tries to present a readable version of the current request values diff --git a/Sming/SmingCore/Network/Http/HttpResponse.cpp b/Sming/SmingCore/Network/Http/HttpResponse.cpp index 9974cab2de..8711251aec 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.cpp +++ b/Sming/SmingCore/Network/Http/HttpResponse.cpp @@ -180,3 +180,11 @@ bool HttpResponse::sendDataStream( IDataSourceStream * newDataStream , String re return true; } +void HttpResponse::reset() +{ + headers.clear(); + if(stream != NULL) { + delete stream; + stream = NULL; + } +} diff --git a/Sming/SmingCore/Network/Http/HttpResponse.h b/Sming/SmingCore/Network/Http/HttpResponse.h index 6b1952c18f..ec6c7e96fb 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.h +++ b/Sming/SmingCore/Network/Http/HttpResponse.h @@ -81,6 +81,8 @@ class HttpResponse { // Send Datastream, can be called with Classes derived from bool sendDataStream( IDataSourceStream * newDataStream , String reqContentType = "" ); + void reset(); + public: int code; HttpHeaders headers; diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 486bdb7154..00d73c5f67 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -173,6 +173,11 @@ int HttpServerConnection::staticOnHeadersComplete(http_parser* parser) int error = 0; connection->request.setHeaders(connection->requestHeaders); + connection->lastWasValue = true; + connection->lastData = ""; + connection->currentField = ""; + connection->requestHeaders.clear(); + if(connection->resource != NULL && connection->resource->onHeadersComplete) { error = connection->resource->onHeadersComplete(*connection, connection->request, connection->response); } @@ -406,6 +411,11 @@ void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) setTimeOut(1); // decrease the timeout to 1 tick } + if(state == eHCS_Sent) { + response.reset(); + request.reset(); + } + TcpClient::onReadyToSendData(sourceEvent); } diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.h b/Sming/SmingCore/Network/Http/HttpServerConnection.h index 639edb9a70..e6d207ffce 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.h +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.h @@ -97,7 +97,7 @@ class HttpServerConnection: public TcpClient String lastData = ""; String currentField = ""; - BodyParsers* bodyParsers; + BodyParsers* bodyParsers = NULL; HttpBodyParserDelegate bodyParser; }; From 61cede030d78d71633e29499a4756b03db049cce Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 6 Jul 2017 14:00:41 +0200 Subject: [PATCH 20/35] Small fixes to comparison and memory related issues. (#1172) * Fixes for possible memory leaks. * Unsigned value cannot be negative. --- Sming/Libraries/MCP23S17/MCP23S17.cpp | 20 ++++++++++++++----- .../CommandProcessing/CommandHandler.cpp | 4 +++- Sming/SmingCore/Network/Http/HttpResponse.cpp | 1 + Sming/Wiring/Print.h | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Sming/Libraries/MCP23S17/MCP23S17.cpp b/Sming/Libraries/MCP23S17/MCP23S17.cpp index e199ff4e37..7433c0b88d 100644 --- a/Sming/Libraries/MCP23S17/MCP23S17.cpp +++ b/Sming/Libraries/MCP23S17/MCP23S17.cpp @@ -107,8 +107,10 @@ void MCP::wordWrite(uint8_t reg, unsigned int word) void MCP::pinMode(uint8_t pin, uint8_t mode) { // Accept the pin # and I/O mode - if (pin < 0 | pin > 15) + if (pin > 15) { return; // If the pin value is not valid (0-15) return, do nothing and return + } + if (mode == INPUT) { // Determine the mode before changing the bit state in the mode cache _modeCache |= 1 << pin; // Since input = "HIGH", OR in a 1 in the appropriate place @@ -132,8 +134,10 @@ void MCP::pinMode(unsigned int mode) void MCP::pullupMode(uint8_t pin, uint8_t mode) { - if (pin < 0 | pin > 15) + if (pin > 15) { return; + } + if (mode == ON) { _pullupCache |= 1 << pin; @@ -155,8 +159,10 @@ void MCP::pullupMode(unsigned int mode) void MCP::inputInvert(uint8_t pin, uint8_t mode) { - if (pin < 0 | pin > 15) + if (pin > 15) { return; + } + if (mode == ON) { _invertCache |= 1 << pin; @@ -178,8 +184,10 @@ void MCP::inputInvert(unsigned int mode) void MCP::digitalWrite(uint8_t pin, uint8_t value) { - if (pin < 0 | pin > 15) + if (pin > 15) { return; + } + if (value) { _outputCache |= 1 << pin; @@ -240,7 +248,9 @@ uint8_t MCP::byteRead(uint8_t reg) uint8_t MCP::digitalRead(uint8_t pin) { // Return a single bit value, supply the necessary bit (1-16) - if (pin < 0 | pin > 15) + if (pin > 15) { return 0x0; // If the pin value is not valid (1-16) return, do nothing and return + } + return digitalRead() & (1 << pin) ? HIGH : LOW; // Call the word reading function, extract HIGH/LOW information from the requested pin } diff --git a/Sming/Services/CommandProcessing/CommandHandler.cpp b/Sming/Services/CommandProcessing/CommandHandler.cpp index c6d5191910..f7c0ad6154 100644 --- a/Sming/Services/CommandProcessing/CommandHandler.cpp +++ b/Sming/Services/CommandProcessing/CommandHandler.cpp @@ -15,7 +15,9 @@ CommandHandler::CommandHandler() CommandHandler::~CommandHandler() { - // TODO Auto-generated destructor stub + if(registeredCommands != NULL) { + delete registeredCommands; + } } void CommandHandler::registerSystemCommands() diff --git a/Sming/SmingCore/Network/Http/HttpResponse.cpp b/Sming/SmingCore/Network/Http/HttpResponse.cpp index 8711251aec..262cc92a0e 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.cpp +++ b/Sming/SmingCore/Network/Http/HttpResponse.cpp @@ -57,6 +57,7 @@ bool HttpResponse::sendString(const String& text) { MemoryDataStream* memStream = new MemoryDataStream(); if (memStream->write((const uint8_t*)text.c_str(), text.length()) != text.length()) { + delete memStream; return false; } diff --git a/Sming/Wiring/Print.h b/Sming/Wiring/Print.h index bbde129ed0..884318fef6 100644 --- a/Sming/Wiring/Print.h +++ b/Sming/Wiring/Print.h @@ -32,6 +32,7 @@ class Print { public: Print() : write_error(0) {} + virtual ~Print() {} int getWriteError() { return write_error; } void clearWriteError() { setWriteError(0); } From d5d0f2527f5bc56ffa29428fc49db6254693c00e Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 10 Jul 2017 17:54:11 +0200 Subject: [PATCH 21/35] Changes related to POST parameter unescaping. (#1177) Related to #1176. --- Sming/SmingCore/Network/Http/HttpBodyParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/SmingCore/Network/Http/HttpBodyParser.cpp b/Sming/SmingCore/Network/Http/HttpBodyParser.cpp index 36061047a5..ee13b77541 100644 --- a/Sming/SmingCore/Network/Http/HttpBodyParser.cpp +++ b/Sming/SmingCore/Network/Http/HttpBodyParser.cpp @@ -41,14 +41,14 @@ void formUrlParser(HttpRequest& request, const char *at, int length) String key = request.postParams.keyAt(i); String value = request.postParams.valueAt(i); - uri_unescape(buffer, maxLength, key.c_str(), key.length()); + uri_unescape(buffer, maxLength + 1, key.c_str(), key.length()); String newKey = buffer; if(newKey != key) { request.postParams.remove(key); } - uri_unescape(buffer, maxLength, value.c_str(), value.length()); + uri_unescape(buffer, maxLength + 1, value.c_str(), value.length()); request.postParams[newKey] = buffer; } delete[] buffer; From 393d51d3146fc88230ead1413a3870fd3cb711f9 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 12 Jul 2017 10:09:38 +0200 Subject: [PATCH 22/35] Adding fixes for issues reported by Vera++ and CppCheck. (#1178) * Implementing cppcheck recommendations for passing objects by reference. * Removed unused variables. * Fixed looping condition. * Removed redundant assignment to itself. * Added initialization for some variables. --- Sming/SmingCore/DataSourceStream.cpp | 12 ++++++----- Sming/SmingCore/DataSourceStream.h | 6 +++--- Sming/SmingCore/FileSystem.cpp | 20 +++++++++---------- Sming/SmingCore/FileSystem.h | 20 +++++++++---------- Sming/SmingCore/Network/DNSServer.cpp | 7 +++---- Sming/SmingCore/Network/DNSServer.h | 6 +++--- Sming/SmingCore/Network/FTPServer.cpp | 2 +- Sming/SmingCore/Network/FTPServer.h | 2 +- .../SmingCore/Network/FTPServerConnection.cpp | 8 ++++---- Sming/SmingCore/Network/Http/HttpResponse.cpp | 12 +++++------ Sming/SmingCore/Network/Http/HttpResponse.h | 12 +++++------ Sming/SmingCore/Network/MqttClient.cpp | 10 +++++----- Sming/SmingCore/Network/MqttClient.h | 12 +++++------ Sming/SmingCore/Network/TcpClient.cpp | 2 +- Sming/SmingCore/Network/TcpClient.h | 2 +- Sming/SmingCore/Network/TcpConnection.cpp | 2 +- Sming/SmingCore/Network/TcpConnection.h | 2 +- Sming/SmingCore/Network/WebsocketClient.cpp | 2 +- Sming/SmingCore/Network/WebsocketClient.h | 2 +- Sming/SmingCore/Platform/AccessPoint.cpp | 2 +- Sming/SmingCore/Platform/AccessPoint.h | 2 +- Sming/SmingCore/Platform/Station.cpp | 4 ++-- Sming/SmingCore/Platform/Station.h | 4 ++-- 23 files changed, 77 insertions(+), 76 deletions(-) diff --git a/Sming/SmingCore/DataSourceStream.cpp b/Sming/SmingCore/DataSourceStream.cpp index 61fe9e94ef..24d062d7ed 100644 --- a/Sming/SmingCore/DataSourceStream.cpp +++ b/Sming/SmingCore/DataSourceStream.cpp @@ -17,8 +17,10 @@ MemoryDataStream::MemoryDataStream() MemoryDataStream::~MemoryDataStream() { - free(buf); - buf = NULL; + if(buf != NULL) { + free(buf); + buf = NULL; + } pos = NULL; size = 0; } @@ -94,12 +96,12 @@ FileStream::FileStream() pos = 0; } -FileStream::FileStream(String filename) +FileStream::FileStream(const String& filename) { attach(filename, eFO_ReadOnly); } -bool FileStream::attach(String fileName, FileOpenFlags openFlags) +bool FileStream::attach(const String& fileName, FileOpenFlags openFlags) { handle = fileOpen(fileName.c_str(), openFlags); if (handle == -1) @@ -178,7 +180,7 @@ bool FileStream::fileExist() /////////////////////////////////////////////////////////////////////////// -TemplateFileStream::TemplateFileStream(String templateFileName) +TemplateFileStream::TemplateFileStream(const String& templateFileName) : FileStream(templateFileName) { state = eTES_Wait; diff --git a/Sming/SmingCore/DataSourceStream.h b/Sming/SmingCore/DataSourceStream.h index 91466b99fe..0ea982f69e 100644 --- a/Sming/SmingCore/DataSourceStream.h +++ b/Sming/SmingCore/DataSourceStream.h @@ -151,10 +151,10 @@ class FileStream : public IDataSourceStream * @param fileName Name of file to open */ FileStream(); - FileStream(String fileName); + FileStream(const String& fileName); virtual ~FileStream(); - virtual bool attach(String fileName, FileOpenFlags openFlags); + virtual bool attach(const String& fileName, FileOpenFlags openFlags); //Use base class documentation virtual StreamType getStreamType() { return eSST_File; } @@ -215,7 +215,7 @@ class TemplateFileStream : public FileStream /** @brief Create a template file stream * @param templateFileName Template filename */ - TemplateFileStream(String templateFileName); + TemplateFileStream(const String& templateFileName); virtual ~TemplateFileStream(); //Use base class documentation diff --git a/Sming/SmingCore/FileSystem.cpp b/Sming/SmingCore/FileSystem.cpp index f55e3d6677..9107fe3ea9 100644 --- a/Sming/SmingCore/FileSystem.cpp +++ b/Sming/SmingCore/FileSystem.cpp @@ -8,7 +8,7 @@ #include "FileSystem.h" #include "../Wiring/WString.h" -file_t fileOpen(const String name, FileOpenFlags flags) +file_t fileOpen(const String& name, FileOpenFlags flags) { int res; @@ -74,7 +74,7 @@ int fileFlush(file_t file) return SPIFFS_fflush(&_filesystemStorageHandle, file); } -int fileStats(const String name, spiffs_stat *stat) +int fileStats(const String& name, spiffs_stat *stat) { return SPIFFS_stat(&_filesystemStorageHandle, name.c_str(), stat); } @@ -84,7 +84,7 @@ int fileStats(file_t file, spiffs_stat *stat) return SPIFFS_fstat(&_filesystemStorageHandle, file, stat); } -void fileDelete(const String name) +void fileDelete(const String& name) { SPIFFS_remove(&_filesystemStorageHandle, name.c_str()); } @@ -94,7 +94,7 @@ void fileDelete(file_t file) SPIFFS_fremove(&_filesystemStorageHandle, file); } -bool fileExist(const String name) +bool fileExist(const String& name) { spiffs_stat stat = {0}; if (fileStats(name.c_str(), &stat) < 0) return false; @@ -112,19 +112,19 @@ void fileClearLastError(file_t fd) SPIFFS_clearerr(&_filesystemStorageHandle); } -void fileSetContent(const String fileName, const String& content) +void fileSetContent(const String& fileName, const String& content) { fileSetContent(fileName, content.c_str()); } -void fileSetContent(const String fileName, const char *content) +void fileSetContent(const String& fileName, const char *content) { file_t file = fileOpen(fileName.c_str(), eFO_CreateNewAlways | eFO_WriteOnly); fileWrite(file, content, strlen(content)); fileClose(file); } -uint32_t fileGetSize(const String fileName) +uint32_t fileGetSize(const String& fileName) { file_t file = fileOpen(fileName.c_str(), eFO_ReadOnly); // Get size @@ -134,7 +134,7 @@ uint32_t fileGetSize(const String fileName) return size; } -void fileRename(const String oldName, const String newName) +void fileRename(const String& oldName, const String& newName) { SPIFFS_rename(&_filesystemStorageHandle, oldName.c_str(), newName.c_str()); } @@ -155,7 +155,7 @@ Vector fileList() return result; } -String fileGetContent(const String fileName) +String fileGetContent(const String& fileName) { file_t file = fileOpen(fileName.c_str(), eFO_ReadOnly); // Get size @@ -176,7 +176,7 @@ String fileGetContent(const String fileName) return res; } -int fileGetContent(const String fileName, char* buffer, int bufSize) +int fileGetContent(const String& fileName, char* buffer, int bufSize) { if (buffer == NULL || bufSize == 0) return 0; *buffer = 0; diff --git a/Sming/SmingCore/FileSystem.h b/Sming/SmingCore/FileSystem.h index 8e4461fcbd..6903f3a820 100644 --- a/Sming/SmingCore/FileSystem.h +++ b/Sming/SmingCore/FileSystem.h @@ -48,7 +48,7 @@ typedef enum * @param flags Mode to open file * @retval file File ID or negative error code */ -file_t fileOpen(const String name, FileOpenFlags flags); +file_t fileOpen(const String& name, FileOpenFlags flags); /** @brief Clode file * @param file ID of file to open @@ -118,7 +118,7 @@ void fileClearLastError(file_t fd); populates the file with the content of a c-string buffer. Remember to terminate your c-string buffer with a null (0). */ -void fileSetContent(const String fileName, const char *content); +void fileSetContent(const String& fileName, const char *content); /** @brief Create or replace file with defined content * @param fileName Name of file to create or replace @@ -126,19 +126,19 @@ void fileSetContent(const String fileName, const char *content); * @note This function creates a new file or replaces an existing file and populates the file with the content of a string. */ -void fileSetContent(const String fileName, const String& content); +void fileSetContent(const String& fileName, const String& content); /** @brief Get size of file * @param fileName Name of file * @retval uint32_t Size of file in bytes */ -uint32_t fileGetSize(const String fileName); +uint32_t fileGetSize(const String& fileName); /** @brief Rename file * @param oldName Original name of file to rename * @param newName New name for file */ -void fileRename(const String oldName, const String newName); +void fileRename(const String& oldName, const String& newName); /** @brief Get list of files on file system * @retval Vector Vector of strings. @@ -151,7 +151,7 @@ Vector fileList(); * @retval String String variable in to which to read the file content * @note After calling this function the content of the file is placed in to a string */ -String fileGetContent(const String fileName); +String fileGetContent(const String& fileName); /** @brief Read content of a file * @param fileName Name of file to read from @@ -162,7 +162,7 @@ String fileGetContent(const String fileName); Ensure there is sufficient space in the buffer for file content plus extra trailing null, i.e. at least bufSize + 1 */ -int fileGetContent(const String fileName, char* buffer, int bufSize); +int fileGetContent(const String& fileName, char* buffer, int bufSize); /** brief Get file statistics * @param name File name @@ -171,7 +171,7 @@ int fileGetContent(const String fileName, char* buffer, int bufSize); * @note Pass a pointer to an instantiated fileStats structure * @todo Document the return value of fileStats */ -int fileStats(const String name, spiffs_stat *stat); +int fileStats(const String& name, spiffs_stat *stat); /** brief Get file statistics * @param file File ID @@ -185,7 +185,7 @@ int fileStats(file_t file, spiffs_stat *stat); /** @brief Delete file * @param name Name of file to delete */ -void fileDelete(const String name); +void fileDelete(const String& name); /** @brief Delete file * @param file ID of file to delete @@ -196,7 +196,7 @@ void fileDelete(file_t file); * @param name Name of file to check for * @retval bool True if file exists */ -bool fileExist(const String name); +bool fileExist(const String& name); /** @} */ #endif /* _SMING_CORE_FILESYSTEM_H_ */ diff --git a/Sming/SmingCore/Network/DNSServer.cpp b/Sming/SmingCore/Network/DNSServer.cpp index 1d048de190..8f11f46432 100644 --- a/Sming/SmingCore/Network/DNSServer.cpp +++ b/Sming/SmingCore/Network/DNSServer.cpp @@ -73,9 +73,9 @@ bool DNSServer::requestIncludesOnlyOneQuestion() void DNSServer::onReceive(pbuf* buf, IPAddress remoteIP, uint16_t remotePort) { - - - if (_buffer != NULL) free(_buffer); + if (_buffer != NULL) { + free(_buffer); + } _buffer = (char*)malloc(buf->tot_len * sizeof(char)); if (_buffer == NULL) return; pbuf_copy_partial(buf, _buffer, buf->tot_len, 0); @@ -91,7 +91,6 @@ void DNSServer::onReceive(pbuf* buf, IPAddress remoteIP, uint16_t remotePort) int idx = buf->tot_len; _dnsHeader->QR = DNS_QR_RESPONSE; _dnsHeader->ANCount = _dnsHeader->QDCount; - _dnsHeader->QDCount = _dnsHeader->QDCount; memcpy(response, _buffer, idx); //Set a pointer to the domain name in the question section response[idx] = 0xC0; diff --git a/Sming/SmingCore/Network/DNSServer.h b/Sming/SmingCore/Network/DNSServer.h index d5b74c0ec4..679ddeaa43 100644 --- a/Sming/SmingCore/Network/DNSServer.h +++ b/Sming/SmingCore/Network/DNSServer.h @@ -68,11 +68,11 @@ class DNSServer : public UdpConnection void stop(); private: - uint16_t _port; + uint16_t _port = 0; String _domainName; char _resolvedIP[4]; - char* _buffer; - DNSHeader* _dnsHeader; + char* _buffer = NULL; + DNSHeader* _dnsHeader = NULL; uint32_t _ttl; DNSReplyCode _errorReplyCode; diff --git a/Sming/SmingCore/Network/FTPServer.cpp b/Sming/SmingCore/Network/FTPServer.cpp index f092e4393a..2fc6714b87 100644 --- a/Sming/SmingCore/Network/FTPServer.cpp +++ b/Sming/SmingCore/Network/FTPServer.cpp @@ -34,7 +34,7 @@ void FTPServer::addUser(String login, String pass) users[login] = pass; } -bool FTPServer::checkUser(String login, String pass) +bool FTPServer::checkUser(String login, const String& pass) { debugf("checkUser: %s %s", login.c_str(), pass.c_str()); if (!users.contains(login)) diff --git a/Sming/SmingCore/Network/FTPServer.h b/Sming/SmingCore/Network/FTPServer.h index 7667c4b7fb..3b3ca17479 100644 --- a/Sming/SmingCore/Network/FTPServer.h +++ b/Sming/SmingCore/Network/FTPServer.h @@ -23,7 +23,7 @@ class FTPServer: public TcpServer virtual ~FTPServer(); void addUser(String login, String pass); - bool checkUser(String login, String pass); + bool checkUser(String login, const String& pass); protected: virtual TcpConnection* createClient(tcp_pcb *clientTcp); diff --git a/Sming/SmingCore/Network/FTPServerConnection.cpp b/Sming/SmingCore/Network/FTPServerConnection.cpp index 492557a25a..d57a10bb68 100644 --- a/Sming/SmingCore/Network/FTPServerConnection.cpp +++ b/Sming/SmingCore/Network/FTPServerConnection.cpp @@ -66,7 +66,7 @@ class FTPDataFileList : public FTPDataStream class FTPDataRetrieve : public FTPDataStream { public: - FTPDataRetrieve(FTPServerConnection* connection, String fileName) : FTPDataStream(connection) + FTPDataRetrieve(FTPServerConnection* connection, const String& fileName) : FTPDataStream(connection) { file = fileOpen(fileName, eFO_ReadOnly); } @@ -94,7 +94,7 @@ class FTPDataRetrieve : public FTPDataStream class FTPDataStore : public FTPDataStream { public: - FTPDataStore(FTPServerConnection* connection, String fileName) : FTPDataStream(connection) + FTPDataStore(FTPServerConnection* connection, const String& fileName) : FTPDataStream(connection) { file = fileOpen(fileName, eFO_WriteOnly | eFO_CreateNewAlways); } @@ -114,9 +114,9 @@ class FTPDataStore : public FTPDataStream } pbuf *cur = buf; - while (cur) + while (cur != NULL && cur->len > 0) { - int len = fileWrite(file, (uint8_t *)cur->payload, cur->len); + fileWrite(file, (uint8_t *)cur->payload, cur->len); cur = cur->next; } diff --git a/Sming/SmingCore/Network/Http/HttpResponse.cpp b/Sming/SmingCore/Network/Http/HttpResponse.cpp index 262cc92a0e..56fcab6482 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.cpp +++ b/Sming/SmingCore/Network/Http/HttpResponse.cpp @@ -21,7 +21,7 @@ HttpResponse::~HttpResponse() } } -HttpResponse* HttpResponse::setContentType(const String type) +HttpResponse* HttpResponse::setContentType(const String& type) { return setHeader("Content-Type", type); } @@ -31,7 +31,7 @@ HttpResponse* HttpResponse::setContentType(enum MimeType type) return setContentType(ContentType::toString(type)); } -HttpResponse* HttpResponse::setCookie(const String name, const String value) +HttpResponse* HttpResponse::setCookie(const String& name, const String& value) { return setHeader("Set-Cookie", name + "=" + value); } @@ -42,12 +42,12 @@ HttpResponse* HttpResponse::setCache(int maxAgeSeconds, bool isPublic /* = false return setHeader("Cache-Control", chache); } -HttpResponse* HttpResponse::setAllowCrossDomainOrigin(String controlAllowOrigin) +HttpResponse* HttpResponse::setAllowCrossDomainOrigin(const String& controlAllowOrigin) { return setHeader("Access-Control-Allow-Origin", controlAllowOrigin); } -HttpResponse* HttpResponse::setHeader(const String name, const String value) +HttpResponse* HttpResponse::setHeader(const String& name, const String& value) { headers[name] = value; return this; @@ -73,7 +73,7 @@ bool HttpResponse::sendString(const String& text) return true; } -bool HttpResponse::hasHeader(const String name) +bool HttpResponse::hasHeader(const String& name) { return headers.contains(name); } @@ -164,7 +164,7 @@ bool HttpResponse::sendJsonObject(JsonObjectStream* newJsonStreamInstance) return true; } -bool HttpResponse::sendDataStream( IDataSourceStream * newDataStream , String reqContentType /* = "" */) +bool HttpResponse::sendDataStream( IDataSourceStream * newDataStream , const String& reqContentType /* = "" */) { if (stream != NULL) { diff --git a/Sming/SmingCore/Network/Http/HttpResponse.h b/Sming/SmingCore/Network/Http/HttpResponse.h index ec6c7e96fb..c0a57def4f 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.h +++ b/Sming/SmingCore/Network/Http/HttpResponse.h @@ -31,7 +31,7 @@ class HttpResponse { // @deprecated method - bool hasHeader(const String name); + bool hasHeader(const String& name); void redirect(const String& location); @@ -49,12 +49,12 @@ class HttpResponse { code = HTTP_STATUS_NOT_FOUND; } - HttpResponse* setContentType(const String type); + HttpResponse* setContentType(const String& type); HttpResponse* setContentType(enum MimeType type); - HttpResponse* setCookie(const String name, const String value); - HttpResponse* setHeader(const String name, const String value); + HttpResponse* setCookie(const String& name, const String& value); + HttpResponse* setHeader(const String& name, const String& value); HttpResponse* setCache(int maxAgeSeconds = 3600, bool isPublic = false); - HttpResponse* setAllowCrossDomainOrigin(String controlAllowOrigin); // Access-Control-Allow-Origin for AJAX from a different domain + HttpResponse* setAllowCrossDomainOrigin(const String& controlAllowOrigin); // Access-Control-Allow-Origin for AJAX from a different domain // Send file by name bool sendFile(String fileName, bool allowGzipFileCheck = true); @@ -79,7 +79,7 @@ class HttpResponse { } // Send Datastream, can be called with Classes derived from - bool sendDataStream( IDataSourceStream * newDataStream , String reqContentType = "" ); + bool sendDataStream( IDataSourceStream * newDataStream , const String& reqContentType = "" ); void reset(); diff --git a/Sming/SmingCore/Network/MqttClient.cpp b/Sming/SmingCore/Network/MqttClient.cpp index e5497c32ce..ed35b037b6 100644 --- a/Sming/SmingCore/Network/MqttClient.cpp +++ b/Sming/SmingCore/Network/MqttClient.cpp @@ -51,17 +51,17 @@ void MqttClient::setPingRepeatTime(int seconds) PingRepeatTime = seconds; } -bool MqttClient::setWill(String topic, String message, int QoS, bool retained /* = false*/) +bool MqttClient::setWill(const String& topic, const String& message, int QoS, bool retained /* = false*/) { return mqtt_set_will(&broker, topic.c_str(), message.c_str(), QoS, retained); } -bool MqttClient::connect(String clientName, boolean useSsl /* = false */, uint32_t sslOptions /* = 0 */) +bool MqttClient::connect(const String& clientName, boolean useSsl /* = false */, uint32_t sslOptions /* = 0 */) { return MqttClient::connect(clientName, "", "", useSsl, sslOptions); } -bool MqttClient::connect(String clientName, String username, String password, boolean useSsl /* = false */, uint32_t sslOptions /* = 0 */) +bool MqttClient::connect(const String& clientName, const String& username, const String& password, boolean useSsl /* = false */, uint32_t sslOptions /* = 0 */) { if (getConnectionState() != eTCS_Ready) { @@ -119,7 +119,7 @@ int MqttClient::staticSendPacket(void* userInfo, const void* buf, unsigned int c return sent ? count : 0; } -bool MqttClient::subscribe(String topic) +bool MqttClient::subscribe(const String& topic) { uint16_t msgId = 0; debugf("subscription '%s' registered", topic.c_str()); @@ -127,7 +127,7 @@ bool MqttClient::subscribe(String topic) return res > 0; } -bool MqttClient::unsubscribe(String topic) +bool MqttClient::unsubscribe(const String& topic) { uint16_t msgId = 0; debugf("unsubscribing from '%s'", topic.c_str()); diff --git a/Sming/SmingCore/Network/MqttClient.h b/Sming/SmingCore/Network/MqttClient.h index 247802fa43..3f9b1b09ea 100644 --- a/Sming/SmingCore/Network/MqttClient.h +++ b/Sming/SmingCore/Network/MqttClient.h @@ -33,10 +33,10 @@ class MqttClient: protected TcpClient void setKeepAlive(int seconds); //send to broker void setPingRepeatTime(int seconds); //used by client // Sets Last Will and Testament - bool setWill(String topic, String message, int QoS, bool retained = false); + bool setWill(const String& topic, const String& message, int QoS, bool retained = false); - bool connect(String clientName, boolean useSsl = false, uint32_t sslOptions = 0); - bool connect(String clientName, String username, String password, boolean useSsl = false, uint32_t sslOptions = 0); + bool connect(const String& clientName, boolean useSsl = false, uint32_t sslOptions = 0); + bool connect(const String& clientName,const String& username, const String& password, boolean useSsl = false, uint32_t sslOptions = 0); using TcpClient::setCompleteDelegate; @@ -46,8 +46,8 @@ class MqttClient: protected TcpClient bool publish(String topic, String message, bool retained = false); bool publishWithQoS(String topic, String message, int QoS, bool retained = false, MqttMessageDeliveredCallback onDelivery = NULL); - bool subscribe(String topic); - bool unsubscribe(String topic); + bool subscribe(const String& topic); + bool unsubscribe(const String& topic); #ifdef ENABLE_SSL using TcpClient::addSslOptions; @@ -76,7 +76,7 @@ class MqttClient: protected TcpClient MqttStringSubscriptionCallback callback; int keepAlive = 60; int PingRepeatTime = 20; - unsigned long lastMessage; + unsigned long lastMessage = 0; HashMap onDeliveryQueue; }; diff --git a/Sming/SmingCore/Network/TcpClient.cpp b/Sming/SmingCore/Network/TcpClient.cpp index df09b847b1..a70401d4e7 100644 --- a/Sming/SmingCore/Network/TcpClient.cpp +++ b/Sming/SmingCore/Network/TcpClient.cpp @@ -69,7 +69,7 @@ bool TcpClient::connect(IPAddress addr, uint16_t port, boolean useSsl /* = false return TcpConnection::connect(addr, port, useSsl, sslOptions); } -bool TcpClient::sendString(String data, bool forceCloseAfterSent /* = false*/) +bool TcpClient::sendString(const String& data, bool forceCloseAfterSent /* = false*/) { return send(data.c_str(), data.length(), forceCloseAfterSent); } diff --git a/Sming/SmingCore/Network/TcpClient.h b/Sming/SmingCore/Network/TcpClient.h index 7b694066d5..30fbf3a9bc 100644 --- a/Sming/SmingCore/Network/TcpClient.h +++ b/Sming/SmingCore/Network/TcpClient.h @@ -58,7 +58,7 @@ class TcpClient : public TcpConnection void setCompleteDelegate(TcpClientCompleteDelegate completeCb = NULL); bool send(const char* data, uint16_t len, bool forceCloseAfterSent = false); - bool sendString(String data, bool forceCloseAfterSent = false); + bool sendString(const String& data, bool forceCloseAfterSent = false); __forceinline bool isProcessing() { return state == eTCS_Connected || state == eTCS_Connecting; } __forceinline TcpClientState getConnectionState() { return state; } diff --git a/Sming/SmingCore/Network/TcpConnection.cpp b/Sming/SmingCore/Network/TcpConnection.cpp index bb4a24e943..ee9e2066e4 100644 --- a/Sming/SmingCore/Network/TcpConnection.cpp +++ b/Sming/SmingCore/Network/TcpConnection.cpp @@ -175,7 +175,7 @@ void TcpConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) if (sourceEvent != eTCE_Poll) debugf("onReadyToSendData: %d", sourceEvent); } -int TcpConnection::writeString(const String data, uint8_t apiflags /* = TCP_WRITE_FLAG_COPY*/) +int TcpConnection::writeString(const String& data, uint8_t apiflags /* = TCP_WRITE_FLAG_COPY*/) { return writeString(data.c_str(), apiflags); } diff --git a/Sming/SmingCore/Network/TcpConnection.h b/Sming/SmingCore/Network/TcpConnection.h index b1879e8ecc..3d4855a45d 100644 --- a/Sming/SmingCore/Network/TcpConnection.h +++ b/Sming/SmingCore/Network/TcpConnection.h @@ -83,7 +83,7 @@ class TcpConnection // return -1 on error int writeString(const char* data, uint8_t apiflags = TCP_WRITE_FLAG_COPY); - int writeString(const String data, uint8_t apiflags = TCP_WRITE_FLAG_COPY); + int writeString(const String& data, uint8_t apiflags = TCP_WRITE_FLAG_COPY); // return -1 on error virtual int write(const char* data, int len, uint8_t apiflags = TCP_WRITE_FLAG_COPY); // flags: TCP_WRITE_FLAG_COPY, TCP_WRITE_FLAG_MORE int write(IDataSourceStream* stream); diff --git a/Sming/SmingCore/Network/WebsocketClient.cpp b/Sming/SmingCore/Network/WebsocketClient.cpp index 874ad75ae8..30166c8172 100644 --- a/Sming/SmingCore/Network/WebsocketClient.cpp +++ b/Sming/SmingCore/Network/WebsocketClient.cpp @@ -204,7 +204,7 @@ void WebsocketClient::sendMessage(char* msg, uint16_t length) _sendFrame(WSFrameType::text, (uint8_t*) msg, length); } -void WebsocketClient::sendMessage(String str) +void WebsocketClient::sendMessage(const String& str) { _sendFrame(WSFrameType::text, (uint8_t*) str.c_str(), str.length() + 1); } diff --git a/Sming/SmingCore/Network/WebsocketClient.h b/Sming/SmingCore/Network/WebsocketClient.h index 971a20474f..1ed1cdf231 100644 --- a/Sming/SmingCore/Network/WebsocketClient.h +++ b/Sming/SmingCore/Network/WebsocketClient.h @@ -90,7 +90,7 @@ class WebsocketClient: protected TcpClient * @param msg Pointer to NULL-terminated string buffer to be send to websocket server * @param length length of the NULL-terminated string buffer */ - void sendMessage(String str); + void sendMessage(const String& str); /** @brief Send text message to websocket server * @param C++ String to be send to websocket server */ diff --git a/Sming/SmingCore/Platform/AccessPoint.cpp b/Sming/SmingCore/Platform/AccessPoint.cpp index 2bc7637a77..855f4840ec 100644 --- a/Sming/SmingCore/Platform/AccessPoint.cpp +++ b/Sming/SmingCore/Platform/AccessPoint.cpp @@ -35,7 +35,7 @@ bool AccessPointClass::isEnabled() return wifi_get_opmode() & SOFTAP_MODE; } -bool AccessPointClass::config(String ssid, String password, AUTH_MODE mode, bool hidden /* = false*/, int channel /* = 7*/, int beaconInterval /* = 200*/) +bool AccessPointClass::config(const String& ssid, String password, AUTH_MODE mode, bool hidden /* = false*/, int channel /* = 7*/, int beaconInterval /* = 200*/) { softap_config config = {0}; if (mode == AUTH_WEP) return false; // Not supported! diff --git a/Sming/SmingCore/Platform/AccessPoint.h b/Sming/SmingCore/Platform/AccessPoint.h index e84e6a085d..22c97de1ee 100644 --- a/Sming/SmingCore/Platform/AccessPoint.h +++ b/Sming/SmingCore/Platform/AccessPoint.h @@ -53,7 +53,7 @@ class AccessPointClass : protected ISystemReadyHandler * @param beaconInterval WiFi AP beacon interval in milliseconds (Default: 200ms) * @retval bool True on success */ - bool config(String ssid, String password, AUTH_MODE mode, bool hidden = false, int channel = 7, int beaconInterval = 200); + bool config(const String& ssid, String password, AUTH_MODE mode, bool hidden = false, int channel = 7, int beaconInterval = 200); /** @brief Get WiFi AP IP address * @retval IPAddress WiFi AP IP address diff --git a/Sming/SmingCore/Platform/Station.cpp b/Sming/SmingCore/Platform/Station.cpp index c494c740bd..2cf16d3496 100644 --- a/Sming/SmingCore/Platform/Station.cpp +++ b/Sming/SmingCore/Platform/Station.cpp @@ -49,7 +49,7 @@ bool StationClass::isEnabled() return wifi_get_opmode() & STATION_MODE; } -bool StationClass::config(String ssid, String password, bool autoConnectOnStartup /* = true*/, bool save /* = true */) +bool StationClass::config(const String& ssid, const String& password, bool autoConnectOnStartup /* = true*/, bool save /* = true */) { station_config config = {0}; @@ -142,7 +142,7 @@ void StationClass::enableDHCP(bool enable) wifi_station_dhcpc_stop(); } -void StationClass::setHostname(String hostname) +void StationClass::setHostname(const String& hostname) { wifi_station_set_hostname((char*)hostname.c_str()); } diff --git a/Sming/SmingCore/Platform/Station.h b/Sming/SmingCore/Platform/Station.h index 87047741bf..670e776d76 100644 --- a/Sming/SmingCore/Platform/Station.h +++ b/Sming/SmingCore/Platform/Station.h @@ -101,7 +101,7 @@ class StationClass : protected ISystemReadyHandler * @param autoConnectOnStartup True to auto connect. False for manual. (Default: True) * @param save True to save the SSID and password in Flash. False otherwise. (Default: True) */ - bool config(String ssid, String password, bool autoConnectOnStartup = true, bool save = true); + bool config(const String& ssid, const String& password, bool autoConnectOnStartup = true, bool save = true); /** @brief Connect WiFi station to network */ @@ -144,7 +144,7 @@ class StationClass : protected ISystemReadyHandler /** @brief Set WiFi station DHCP hostname * @param hostname - WiFi station DHCP hostname */ - void setHostname(String hostname); + void setHostname(const String& hostname); /** @brief Set WiFi station DHCP hostname * @retval WiFi station DHCP hostname From bd291831dc348ed09b06d24c9b25c80aef1c1091 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 12 Jul 2017 11:13:47 +0200 Subject: [PATCH 23/35] Station::waitConnection method was deprecated and is now removed. (#1171) Use WifiEvents instead. --- Sming/SmingCore/Platform/Station.cpp | 67 ---------------------------- Sming/SmingCore/Platform/Station.h | 24 ---------- 2 files changed, 91 deletions(-) diff --git a/Sming/SmingCore/Platform/Station.cpp b/Sming/SmingCore/Platform/Station.cpp index 2cf16d3496..3e2c3ed84e 100644 --- a/Sming/SmingCore/Platform/Station.cpp +++ b/Sming/SmingCore/Platform/Station.cpp @@ -14,17 +14,10 @@ StationClass::StationClass() { System.onReady(this); runScan = false; - onConnectOk = nullptr; - onConnectFail = nullptr; - connectionTimeOut = 0; - connectionTimer = NULL; - connectionStarted = 0; } StationClass::~StationClass() { - delete connectionTimer; - connectionTimer = NULL; } void StationClass::enable(bool enabled, bool save) @@ -36,8 +29,6 @@ void StationClass::enable(bool enabled, bool save) mode = wifi_get_opmode() & ~STATION_MODE; if (enabled) mode |= STATION_MODE; - else if (connectionTimer) - delete connectionTimer; if (save) wifi_set_opmode(mode); else @@ -288,27 +279,6 @@ bool StationClass::startScan(ScanCompletedDelegate scanCompleted) return res; } -void StationClass::waitConnection(ConnectionDelegate successfulConnected) -{ - waitConnection(successfulConnected, -1, NULL); -} - -void StationClass::waitConnection(ConnectionDelegate successfulConnected, int secondsTimeOut, ConnectionDelegate connectionNotEstablished) -{ - if (onConnectOk || onConnectFail ) - { - SYSTEM_ERROR("WRONG CALL waitConnection method.."); - return; - } - - onConnectOk = successfulConnected; - connectionTimeOut = secondsTimeOut; - onConnectFail = connectionNotEstablished; - connectionTimer = new Timer(); - connectionTimer->initializeMs(50, staticCheckConnection).start(); - connectionStarted = millis(); -} - //////////// void StationClass::staticScanCompleted(void *arg, STATUS status) @@ -347,43 +317,6 @@ void StationClass::onSystemReady() } } -void StationClass::internalCheckConnection() -{ - uint32 duration = millis() - connectionStarted; - if (isConnected()) - { - ConnectionDelegate callOk = nullptr; - if (onConnectOk) { - callOk = onConnectOk; - } - - onConnectOk = nullptr; - onConnectFail = nullptr; - delete connectionTimer; - connectionTimeOut = 0; - - if (callOk) { - callOk(); - } - } - else if (connectionTimeOut > 0 && duration > (uint32)connectionTimeOut * 1000) - { - ConnectionDelegate call = onConnectFail; - onConnectOk = nullptr; - onConnectFail = nullptr; - delete connectionTimer; - connectionTimeOut = 0; - - if (call) - call(); - } -} - -void StationClass::staticCheckConnection() -{ - WifiStation.internalCheckConnection(); -} - const char* StationClass::getConnectionStatusName() { switch (getConnectionStatus()) diff --git a/Sming/SmingCore/Platform/Station.h b/Sming/SmingCore/Platform/Station.h index 670e776d76..b78484ee5f 100644 --- a/Sming/SmingCore/Platform/Station.h +++ b/Sming/SmingCore/Platform/Station.h @@ -208,24 +208,6 @@ class StationClass : protected ISystemReadyHandler */ bool startScan(ScanCompletedDelegate scanCompleted); - /** @brief Assign handler for WiFi station connection - * @note The handler will be cleared if the WiFi Station is disabled. If you subsequently reenable WiFi Station, another call to waitConnection must be made if you want the handler to be reinstalled. - * @param successfulConnected Function to call when WiFi station connects to network - */ - void waitConnection(ConnectionDelegate successfulConnected); - - /** @brief Assign handler for WiFi station connection with timeout - * @note The handler will be cleared if the WiFi Station is disabled. If you subsequently reenable WiFi Station, another call to waitConnection must be made if you want the handler to be reinstalled. - * @param successfulConnected Function to call when WiFi station connects to network - * @param secondsTimeOut Quantity of seconds to wait for connection - * @param connectionNotEstablished Function to call if WiFi station fails to connect to network - * - * @deprecated This method is deprecated and will be removed in the next versions. Use WifiEvents instead. - * For an example of WifiEvents take a look at the Basic_Wifi sample code. - * - */ - void waitConnection(ConnectionDelegate successfulConnected, int secondsTimeOut, ConnectionDelegate connectionNotEstablished); - /** @brief Start WiFi station smart configuration * @param sctype Smart configuration type * @param callback Function to call on WiFi staton smart configuration complete (Default: none) @@ -250,12 +232,6 @@ class StationClass : protected ISystemReadyHandler ScanCompletedDelegate scanCompletedCallback; SmartConfigDelegate smartConfigCallback = NULL; bool runScan; - - ConnectionDelegate onConnectOk; - ConnectionDelegate onConnectFail; - int connectionTimeOut; - uint32 connectionStarted; - Timer* connectionTimer; }; class BssInfo From 3afb5ddcea1a57dc429a667bd708a5cd9ae0f7c3 Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 13 Jul 2017 13:34:48 +0200 Subject: [PATCH 24/35] Release Candidate 1 for version 3.3.0. (#1185) This version includes: - Refactored HttpClient and HttpServer - Support for SSL session resumption in TcpConnection and TcpServer. - Removed deprecated Station::waitConnection. - Custom PWM is enabled by default. - Multiple fixes to issues reported from Codacy's Vera++ and CppCheck reports. --- Readme.md | 6 +++--- Sming/SmingCore/SmingCore.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 9448dfb424..5acd9f49d6 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ Sming - Open Source framework for high efficiency WiFi SoC ESP8266 native develo * Out of the box support for HTTP, MQTT and Websocket client connections over SSL. * Out of the box support for OTA over HTTPS. * [SNI](https://tools.ietf.org/html/rfc6066#page-6) and [Maximum Fragment Length](https://tools.ietf.org/html/rfc6066#page-8) SSL support. -* Optional alternative PWM support based on [Stefan Bruens PWM](https://github.com/StefanBruens/ESP8266_new_pwm.git) +* PWM support based on [Stefan Bruens PWM](https://github.com/StefanBruens/ESP8266_new_pwm.git) * Optional custom heap allocation based on [Umm Malloc](https://github.com/rhempel/umm_malloc.git) * Based on Espressif NONOS SDK. Tested with versions 1.4, 1.5 and 2.0. @@ -61,12 +61,12 @@ n/a = The selected SDK is not available on that OS - Custom LWIP: (default: ON) By default we are using custom compiled LWIP stack instead of the binary one provided from Espressif. This is increasing the free memory and decreasing the space on the flash. All espconn_* functions are turned off by default. If your application requires the use of some of the espconn_* functions then add the ENABLE_ESPCONN=1 directive. See `Makefile-user.mk` from the [Basic_SmartConfig](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_SmartConfig/Makefile-user.mk#L41) application for examples. If you would like to use the binary LWIP then you should turn off the custom LWIP compilation by providing `ENABLE_CUSTOM_LWIP=0`. - SSL: (default: OFF) The SSL support is not built-in by default to conserve resources. If you want to enable it then take a look at the [Readme](https://github.com/SmingHub/Sming/blob/develop/samples/Basic_Ssl/README.md) in the Basic_Ssl samples. -- Custom PWM: (default: OFF) If you want to use the [open PWM implementation](https://github.com/StefanBruens/ESP8266_new_pwm) then compile your application with `ENABLE_CUSTOM_PWM=1`. There is no need to recompile the Sming library. +- Custom PWM: (default: ON) If you don't want to use the [open PWM implementation](https://github.com/StefanBruens/ESP8266_new_pwm) then compile your application with `ENABLE_CUSTOM_PWM=0`. There is no need to recompile the Sming library. - Custom serial baud rate: (default: OFF) The default serial baud rate is 115200. If you want to change it to a higher baud rate you can recompile Sming and your application changing the `COM_SPEED_SERIAL` directive. For example `COM_SPEED_SERIAL=921600`. - Custom heap allocation: (default: OFF) If your application is experiencing heap fragmentation then you can try the [umm_malloc](https://github.com/rhempel/umm_malloc) heap allocation. To enable it compile Sming with `ENABLE_CUSTOM_HEAP=1`. In order to use it in your sample/application make sure to compile the sample with `ENABLE_CUSTOM_HEAP=1`. **Do not enable custom heap allocation and -mforce-l32 compiler flag together**. - Debug information log level and format: There are four debug levels: debug=3, info=2, warn=1, error=0. Using `DEBUG_VERBOSE_LEVEL` you can set the desired level (0-3). For example `DEBUG_VERBOSE_LEVEL=2` will show only info messages and above. Another make directive is `DEBUG_PRINT_FILENAME_AND_LINE=1` which enables printing the filename and line number of every debug line. This will require extra space on flash. Note: you can compile the Sming library with a set of debug directives and your project with another settings, this way you can control debugging sepparately for sming and your application code. - Debug information for custom LWIP: If you use custom LWIP (see above) some debug information will be printed for critical errors and situations. You can enable debug information printing altogether using `ENABLE_LWIPDEBUG=1`. To increase debugging for certain areas you can modify debug options in `third-party/esp-open-lwip/include/lwipopts.h`. -- CommandExecutor feature: This feature enables execution of certain commands by registering token handlers for text received via serial, websocket or telnet connection. If this feature is not used additional RAM/Flash can be obtained by setting `ENABLE_CMD_EXECUTOR=0`. This will save ~1KB RAM and ~3KB of flash memory. +- CommandExecutor feature: (default: ON) This feature enables execution of certain commands by registering token handlers for text received via serial, websocket or telnet connection. If this feature is not used additional RAM/Flash can be obtained by setting `ENABLE_CMD_EXECUTOR=0`. This will save ~1KB RAM and ~3KB of flash memory.

diff --git a/Sming/SmingCore/SmingCore.h b/Sming/SmingCore/SmingCore.h index ca273ff3cc..cc5de45cd6 100644 --- a/Sming/SmingCore/SmingCore.h +++ b/Sming/SmingCore/SmingCore.h @@ -8,7 +8,7 @@ #ifndef _NET_WIRING_ #define _NET_WIRING_ -#define SMING_VERSION "3.2.0" // Major Minor Sub +#define SMING_VERSION "3.3.0" // Major Minor Sub #include "../Wiring/WiringFrameworkIncludes.h" From 32dcb952ad3535814fc2c3eb8e03a06d47f3da03 Mon Sep 17 00:00:00 2001 From: slaff Date: Fri, 14 Jul 2017 17:24:16 +0200 Subject: [PATCH 25/35] decode-stacktrace.py: Added python3 compatability. (#1190) --- tools/decode-stacktrace.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/decode-stacktrace.py b/tools/decode-stacktrace.py index d2abc2659e..77791cbfb2 100755 --- a/tools/decode-stacktrace.py +++ b/tools/decode-stacktrace.py @@ -12,21 +12,21 @@ import re def usage(): - print "Usage: \n\t%s []" % sys.argv[0] + print("Usage: \n\t%s []" % sys.argv[0]) def extractAddresses(data): m = re.findall("(40[0-2](\d|[a-f]){5})", data) if len(m) == 0: return m - addresses = [] + addresses = [] for item in m: addresses.append(item[0]) return addresses if __name__ == "__main__": - if len(sys.argv) not in range(2,4): + if len(sys.argv) not in list(range(2,4)): usage() sys.exit(1) @@ -35,15 +35,15 @@ def extractAddresses(data): if len(sys.argv) > 2: data = open(sys.argv[2]).read() - pipe.communicate("\n".join(extractAddresses(data))) + pipe.communicate("\n".join(extractAddresses(data)).encode('ascii')) else: while True: - data = raw_input() + data = input() addresses = extractAddresses(data) if len(addresses) == 0: continue -# print "[",addresses,"]" +# print ( "[",addresses,"]" ) line = "\r\n".join(addresses)+"\r\n" # line = line.ljust(125," ") From 8fe2d3aa4bac509acf8573996568aaae76ba753d Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 17 Jul 2017 22:19:23 +0200 Subject: [PATCH 26/35] HttpServer tuning to allow serving of more requests. (#1187) * - Workaround for issue with incorrect size of files (Disadvantages: The loading process looks slower because the browser waits for the TIMEOUT in order to determine the size) - TcpClient: Added reporting for condition that shouldn't happen and is still not handled correctly at the moment. - TcpServer: Decreased the minimum required heap size. - LWIP: Decreased the TCP_MSL to 2 seconds. That will lead to faster garbage collection of tcp connections in TIME_WAIT state and will release resources faster. * Decrease the memory usage by sharing the same parser settings. --- Sming/SmingCore/DataSourceStream.h | 2 +- .../SmingCore/Network/Http/HttpConnection.cpp | 38 +++++++++++++------ Sming/SmingCore/Network/Http/HttpConnection.h | 7 +++- .../Network/Http/HttpServerConnection.cpp | 28 ++++++++------ .../Network/Http/HttpServerConnection.h | 3 +- Sming/SmingCore/Network/TcpClient.cpp | 5 ++- Sming/SmingCore/Network/TcpServer.h | 2 +- .../third-party/.patches/esp-open-lwip.patch | 13 +++++++ 8 files changed, 70 insertions(+), 28 deletions(-) diff --git a/Sming/SmingCore/DataSourceStream.h b/Sming/SmingCore/DataSourceStream.h index 0ea982f69e..2adbcb12d3 100644 --- a/Sming/SmingCore/DataSourceStream.h +++ b/Sming/SmingCore/DataSourceStream.h @@ -182,7 +182,7 @@ class FileStream : public IDataSourceStream * @brief Return the total length of the stream * @retval int -1 is returned when the size cannot be determined */ - int length() { return size; } + int length() { return -1; } private: file_t handle; diff --git a/Sming/SmingCore/Network/Http/HttpConnection.cpp b/Sming/SmingCore/Network/Http/HttpConnection.cpp index 079ec22f7d..7d4e3c33d6 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpConnection.cpp @@ -20,28 +20,38 @@ #include "lwip/tcp_impl.h" #endif +bool HttpConnection::parserSettingsInitialized = false; +http_parser_settings HttpConnection::parserSettings; + HttpConnection::HttpConnection(RequestQueue* queue): TcpClient(false), mode(eHCM_String) { this->waitingQueue = queue; http_parser_init(&parser, HTTP_RESPONSE); parser.data = (void*)this; - memset(&parserSettings, 0, sizeof(parserSettings)); + if(!parserSettingsInitialized) { + memset(&parserSettings, 0, sizeof(parserSettings)); - // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. - parserSettings.on_message_begin = staticOnMessageBegin; - parserSettings.on_headers_complete = staticOnHeadersComplete; - parserSettings.on_message_complete = staticOnMessageComplete; + // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. + parserSettings.on_message_begin = staticOnMessageBegin; + parserSettings.on_headers_complete = staticOnHeadersComplete; + parserSettings.on_message_complete = staticOnMessageComplete; - parserSettings.on_chunk_header = staticOnChunkHeader; - parserSettings.on_chunk_complete = staticOnChunkComplete; +#ifndef COMPACT_MODE + parserSettings.on_chunk_header = staticOnChunkHeader; + parserSettings.on_chunk_complete = staticOnChunkComplete; +#endif + // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; +#ifndef COMPACT_MODE + parserSettings.on_status = staticOnStatus; +#endif + parserSettings.on_header_field = staticOnHeaderField; + parserSettings.on_header_value = staticOnHeaderValue; + parserSettings.on_body = staticOnBody; - // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; - parserSettings.on_status = staticOnStatus; - parserSettings.on_header_field = staticOnHeaderField; - parserSettings.on_header_value = staticOnHeaderValue; - parserSettings.on_body = staticOnBody; + parserSettingsInitialized = true; + } } bool HttpConnection::connect(const String& host, int port, bool useSsl /* = false */, uint32_t sslOptions /* = 0 */) { @@ -266,9 +276,11 @@ int HttpConnection::staticOnHeadersComplete(http_parser* parser) return error; } +#ifndef COMPACT_MODE int HttpConnection::staticOnStatus(http_parser *parser, const char *at, size_t length) { return 0; } +#endif int HttpConnection::staticOnHeaderField(http_parser *parser, const char *at, size_t length) { @@ -334,6 +346,7 @@ int HttpConnection::staticOnBody(http_parser *parser, const char *at, size_t len return 0; } +#ifndef COMPACT_MODE int HttpConnection::staticOnChunkHeader(http_parser* parser) { debugf("On chunk header"); return 0; @@ -343,6 +356,7 @@ int HttpConnection::staticOnChunkComplete(http_parser* parser) { debugf("On chunk complete"); return 0; } +#endif err_t HttpConnection::onConnected(err_t err) { if (err == ERR_OK) { diff --git a/Sming/SmingCore/Network/Http/HttpConnection.h b/Sming/SmingCore/Network/Http/HttpConnection.h index 15147c7815..2ea81f7072 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.h +++ b/Sming/SmingCore/Network/Http/HttpConnection.h @@ -89,13 +89,17 @@ class HttpConnection : protected TcpClient { private: static int IRAM_ATTR staticOnMessageBegin(http_parser* parser); +#ifndef COMPACT_MODE static int IRAM_ATTR staticOnStatus(http_parser *parser, const char *at, size_t length); +#endif static int IRAM_ATTR staticOnHeadersComplete(http_parser* parser); static int IRAM_ATTR staticOnHeaderField(http_parser *parser, const char *at, size_t length); static int IRAM_ATTR staticOnHeaderValue(http_parser *parser, const char *at, size_t length); static int IRAM_ATTR staticOnBody(http_parser *parser, const char *at, size_t length); +#ifndef COMPACT_MODE static int IRAM_ATTR staticOnChunkHeader(http_parser* parser); static int IRAM_ATTR staticOnChunkComplete(http_parser* parser); +#endif static int IRAM_ATTR staticOnMessageComplete(http_parser* parser); protected: @@ -105,7 +109,8 @@ class HttpConnection : protected TcpClient { RequestQueue* waitingQueue; RequestQueue executionQueue; http_parser parser; - http_parser_settings parserSettings; + static http_parser_settings parserSettings; + static bool parserSettingsInitialized; HttpHeaders responseHeaders; int code = 0; diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 00d73c5f67..4030ea7797 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -17,6 +17,9 @@ #include "../../Services/cWebsocket/websocket.h" #include "WebConstants.h" +bool HttpServerConnection::parserSettingsInitialized = false; +http_parser_settings HttpServerConnection::parserSettings; + HttpServerConnection::HttpServerConnection(tcp_pcb *clientTcp) : TcpClient(clientTcp, 0, 0), state(eHCS_Ready) { @@ -24,17 +27,20 @@ HttpServerConnection::HttpServerConnection(tcp_pcb *clientTcp) http_parser_init(&parser, HTTP_REQUEST); parser.data = (void*)this; - memset(&parserSettings, 0, sizeof(parserSettings)); - // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. - parserSettings.on_message_begin = staticOnMessageBegin; - parserSettings.on_headers_complete = staticOnHeadersComplete; - parserSettings.on_message_complete = staticOnMessageComplete; - - // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; - parserSettings.on_url = staticOnPath; - parserSettings.on_header_field = staticOnHeaderField; - parserSettings.on_header_value = staticOnHeaderValue; - parserSettings.on_body = staticOnBody; + if(!parserSettingsInitialized) { + memset(&parserSettings, 0, sizeof(parserSettings)); + // Notification callbacks: on_message_begin, on_headers_complete, on_message_complete. + parserSettings.on_message_begin = staticOnMessageBegin; + parserSettings.on_headers_complete = staticOnHeadersComplete; + parserSettings.on_message_complete = staticOnMessageComplete; + + // Data callbacks: on_url, (common) on_header_field, on_header_value, on_body; + parserSettings.on_url = staticOnPath; + parserSettings.on_header_field = staticOnHeaderField; + parserSettings.on_header_value = staticOnHeaderValue; + parserSettings.on_body = staticOnBody; + parserSettingsInitialized = true; + } } HttpServerConnection::~HttpServerConnection() diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.h b/Sming/SmingCore/Network/Http/HttpServerConnection.h index e6d207ffce..6a68e415fa 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.h +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.h @@ -78,7 +78,8 @@ class HttpServerConnection: public TcpClient HttpConnectionState state; http_parser parser; - http_parser_settings parserSettings; + static http_parser_settings parserSettings; + static bool parserSettingsInitialized; ResourceTree* resourceTree = NULL; HttpResource* resource = NULL; diff --git a/Sming/SmingCore/Network/TcpClient.cpp b/Sming/SmingCore/Network/TcpClient.cpp index a70401d4e7..b327af36eb 100644 --- a/Sming/SmingCore/Network/TcpClient.cpp +++ b/Sming/SmingCore/Network/TcpClient.cpp @@ -81,8 +81,11 @@ bool TcpClient::send(const char* data, uint16_t len, bool forceCloseAfterSent /* if (stream == NULL) stream = new MemoryDataStream(); - if (stream->write((const uint8_t*)data, len) != len) + if (stream->write((const uint8_t*)data, len) != len) { + debug_e("ERROR: Unable to store %d bytes in output stream", len); return false; + } + debugf("Storing %d bytes in stream", len); asyncTotalLen += len; diff --git a/Sming/SmingCore/Network/TcpServer.h b/Sming/SmingCore/Network/TcpServer.h index dfd7c392a9..fd3fe8f3e3 100644 --- a/Sming/SmingCore/Network/TcpServer.h +++ b/Sming/SmingCore/Network/TcpServer.h @@ -48,7 +48,7 @@ class TcpServer: public TcpConnection { uint16_t activeClients = 0; protected: - int minHeapSize = 6500; + int minHeapSize = 3000; #ifdef ENABLE_SSL int sslSessionCacheSize = 50; diff --git a/Sming/third-party/.patches/esp-open-lwip.patch b/Sming/third-party/.patches/esp-open-lwip.patch index e3bde91a99..9e1875e5b6 100644 --- a/Sming/third-party/.patches/esp-open-lwip.patch +++ b/Sming/third-party/.patches/esp-open-lwip.patch @@ -293,3 +293,16 @@ index ddb5984..fb677c6 100644 *optptr++ = DHCP_OPTION_ROUTER; *optptr++ = 4; *optptr++ = ip4_addr1( &if_ip.gw); +diff --git a/include/lwip/tcp_impl.h b/include/lwip/tcp_impl.h +index 24ca8bb..0c20b6a 100644 +--- a/include/lwip/tcp_impl.h ++++ b/include/lwip/tcp_impl.h +@@ -130,7 +130,7 @@ u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb)ICACHE_FLASH_ATTR; + #define TCP_OOSEQ_TIMEOUT 6U /* x RTO */ + + #ifndef TCP_MSL +-#define TCP_MSL 60000UL /* The maximum segment lifetime in milliseconds */ ++#define TCP_MSL 2000UL /* The maximum segment lifetime in milliseconds */ + #endif + + /* Keepalive values, compliant with RFC 1122. Don't change this unless you know what you're doing */ From 4389a0a9a46b1b1411725bb384817ca0ed679c1c Mon Sep 17 00:00:00 2001 From: slaff Date: Fri, 21 Jul 2017 10:13:03 +0200 Subject: [PATCH 27/35] Feature/httpserver etag caching (#1194) * Added etag caching mechanism. If a stream provides a unique id then the HttpServer will use it as HTTP etag. The Etag generation can be disabled if needed. --- Sming/SmingCore/DataSourceStream.cpp | 12 +++++++++++ Sming/SmingCore/DataSourceStream.h | 8 +++++++ Sming/SmingCore/Network/Http/HttpResponse.cpp | 4 ++-- .../Network/Http/HttpServerConnection.cpp | 21 ++++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Sming/SmingCore/DataSourceStream.cpp b/Sming/SmingCore/DataSourceStream.cpp index 24d062d7ed..760c8d1b78 100644 --- a/Sming/SmingCore/DataSourceStream.cpp +++ b/Sming/SmingCore/DataSourceStream.cpp @@ -178,6 +178,18 @@ bool FileStream::fileExist() return size != -1; } +String FileStream::id() +{ + spiffs_stat stat; + fileStats(handle, &stat); + +#define ETAG_SIZE 16 + char buf[ETAG_SIZE]; + m_snprintf(buf, ETAG_SIZE, "00f-%x-%x0-%x", stat.obj_id, stat.size, strlen((char*)stat.name)); + + return String(buf); +} + /////////////////////////////////////////////////////////////////////////// TemplateFileStream::TemplateFileStream(const String& templateFileName) diff --git a/Sming/SmingCore/DataSourceStream.h b/Sming/SmingCore/DataSourceStream.h index 2adbcb12d3..fd81116c66 100644 --- a/Sming/SmingCore/DataSourceStream.h +++ b/Sming/SmingCore/DataSourceStream.h @@ -81,6 +81,12 @@ class IDataSourceStream * @retval int -1 is returned when the size cannot be determined */ virtual int length() { return -1; } + + /** + * @brief Returns unique id of the resource. + * @retval String the unique id of the stream. + */ + virtual String id() { return String(); } }; /// Memory data stream class @@ -184,6 +190,8 @@ class FileStream : public IDataSourceStream */ int length() { return -1; } + virtual String id(); + private: file_t handle; int pos; diff --git a/Sming/SmingCore/Network/Http/HttpResponse.cpp b/Sming/SmingCore/Network/Http/HttpResponse.cpp index 56fcab6482..1715b23b70 100644 --- a/Sming/SmingCore/Network/Http/HttpResponse.cpp +++ b/Sming/SmingCore/Network/Http/HttpResponse.cpp @@ -38,8 +38,8 @@ HttpResponse* HttpResponse::setCookie(const String& name, const String& value) HttpResponse* HttpResponse::setCache(int maxAgeSeconds, bool isPublic /* = false */) { - String chache = String(isPublic ? "public" : "private") +", max-age=" + String(maxAgeSeconds) + ", must-revalidate"; - return setHeader("Cache-Control", chache); + String cache = String(isPublic ? "public" : "private") +", max-age=" + String(maxAgeSeconds) + ", must-revalidate"; + return setHeader("Cache-Control", cache); } HttpResponse* HttpResponse::setAllowCrossDomainOrigin(const String& controlAllowOrigin) diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 4030ea7797..d672da16cd 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -352,7 +352,26 @@ void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) return; } + bool sendContent = (request.method != HTTP_HEAD); + if(!headersSent) { +#ifndef DISABLE_HTTPSRV_ETAG + if(response.stream != NULL && !response.headers.contains("ETag")) { + String tag = response.stream->id(); + if(tag.length() > 0) { + response.headers["ETag"] = String('"' + tag + '"'); + } + } + + if(request.headers.contains("If-Match") && response.headers.contains("ETag") && + request.headers["If-Match"] == response.headers["ETag"]) { + if(request.method == HTTP_GET || request.method == HTTP_HEAD) { + response.code = HTTP_STATUS_NOT_MODIFIED; + response.headers["Content-Length"] = "0"; + sendContent = false; + } + } +#endif /* DISABLE_HTTPSRV_ETAG */ String statusLine = "HTTP/1.1 "+String(response.code) + " " + getStatus((enum http_status)response.code) + "\r\n"; writeString(statusLine, TCP_WRITE_FLAG_MORE | TCP_WRITE_FLAG_COPY); @@ -390,7 +409,7 @@ void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) } do { - if(request.method == HTTP_HEAD) { + if(sendContent == false) { if(response.stream != NULL) { delete response.stream; response.stream = NULL; From 08143b57ef714242ad9be14b5d073455b851d280 Mon Sep 17 00:00:00 2001 From: slaff Date: Fri, 21 Jul 2017 10:35:02 +0200 Subject: [PATCH 28/35] Fixes for styling issues reported by Codacy (#1196) * Fixing python related issues reported by Codacy. * Fixed docker related issues. --- .../RF24/tests/pingpair_blocking/runtest.py | 2 +- .../RF24/tests/pingpair_test/runtest.py | 2 +- docker/Dockerfile | 2 +- tools/decode-stacktrace.py | 23 +++++++++--------- tools/memanalyzer.py | 24 +++++++++---------- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Sming/Libraries/RF24/tests/pingpair_blocking/runtest.py b/Sming/Libraries/RF24/tests/pingpair_blocking/runtest.py index 0772f95053..76b41ada68 100644 --- a/Sming/Libraries/RF24/tests/pingpair_blocking/runtest.py +++ b/Sming/Libraries/RF24/tests/pingpair_blocking/runtest.py @@ -3,7 +3,7 @@ import sys,serial def read_until(token): - while 1: + while 1: line = ser.readline(None) sys.stdout.write(line) diff --git a/Sming/Libraries/RF24/tests/pingpair_test/runtest.py b/Sming/Libraries/RF24/tests/pingpair_test/runtest.py index 45fb65cec1..03ab9a8b37 100644 --- a/Sming/Libraries/RF24/tests/pingpair_test/runtest.py +++ b/Sming/Libraries/RF24/tests/pingpair_test/runtest.py @@ -3,7 +3,7 @@ import sys,serial def read_until(token): - while 1: + while 1: line = ser.readline(None,"\r") sys.stdout.write(line) diff --git a/docker/Dockerfile b/docker/Dockerfile index c148de5df6..98b53dfe02 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,7 +8,7 @@ MAINTAINER Slavey Karadzhov COPY assets/welcome.html /cloud9/plugins/c9.ide.welcome/welcome.html COPY assets/welcome.js /cloud9/plugins/c9.ide.welcome/welcome.js -RUN cd /workspace && git clone https://github.com/SmingHub/Sming.git +RUN git clone https://github.com/SmingHub/Sming.git /workspace/Sming ENV SMING_HOME /workspace/Sming/Sming diff --git a/tools/decode-stacktrace.py b/tools/decode-stacktrace.py index 77791cbfb2..b1ac29e871 100755 --- a/tools/decode-stacktrace.py +++ b/tools/decode-stacktrace.py @@ -6,33 +6,32 @@ # ######################################################## import shlex -import select import subprocess import sys import re def usage(): print("Usage: \n\t%s []" % sys.argv[0]) - + def extractAddresses(data): m = re.findall("(40[0-2](\d|[a-f]){5})", data) if len(m) == 0: return m - + addresses = [] for item in m: - addresses.append(item[0]) - - return addresses - + addresses.append(item[0]) + + return addresses + if __name__ == "__main__": if len(sys.argv) not in list(range(2,4)): usage() sys.exit(1) - + command = "xtensa-lx106-elf-addr2line -aipfC -e '%s' " % sys.argv[1] pipe = subprocess.Popen(shlex.split(command), bufsize=1, stdin=subprocess.PIPE) - + if len(sys.argv) > 2: data = open(sys.argv[2]).read() pipe.communicate("\n".join(extractAddresses(data)).encode('ascii')) @@ -42,12 +41,12 @@ def extractAddresses(data): addresses = extractAddresses(data) if len(addresses) == 0: continue - + # print ( "[",addresses,"]" ) - + line = "\r\n".join(addresses)+"\r\n" # line = line.ljust(125," ") - + pipe.stdin.write(line) pipe.stdin.flush() diff --git a/tools/memanalyzer.py b/tools/memanalyzer.py index 715fb9d235..abfcc1c53f 100644 --- a/tools/memanalyzer.py +++ b/tools/memanalyzer.py @@ -20,7 +20,7 @@ ("rodata", "ReadOnly Data (RAM)"), ("bss", "Uninitialized Data (RAM)"), ("text", "Cached Code (IRAM)"), - ("irom0_text", "Uncached Code (SPI)") + ("irom0_text", "Uncached Code (SPI)") ]) if len(sys.argv) < 2: @@ -35,10 +35,10 @@ sys.exit(1) -command = "%s -t '%s' " % (sys.argv[1], sys.argv[2]) +command = "%s -t '%s' " % (sys.argv[1], sys.argv[2]) response = subprocess.check_output(shlex.split(command)) if isinstance(response, bytes): - response = response.decode('utf-8') + response = response.decode('utf-8') lines = response.split('\n') print("{0: >10}|{1: >30}|{2: >12}|{3: >12}|{4: >8}".format("Section", "Description", "Start (hex)", "End (hex)", "Used space")); @@ -48,32 +48,32 @@ usedIRAM = 0; i = 0 -for (id, descr) in list(sections.items()): - sectionStartToken = " _%s_start" % id - sectionEndToken = " _%s_end" % id; +for (name, descr) in list(sections.items()): + sectionStartToken = " _%s_start" % name + sectionEndToken = " _%s_end" % name sectionStart = -1; sectionEnd = -1; for line in lines: if sectionStartToken in line: data = line.split(' ') sectionStart = int(data[0], 16) - - if sectionEndToken in line: + + if sectionEndToken in line: data = line.split(' ') sectionEnd = int(data[0], 16) - + if sectionStart != -1 and sectionEnd != -1: break - + sectionLength = sectionEnd - sectionStart if i < 3: usedRAM += sectionLength if i == 3: usedIRAM = TOTAL_DRAM - sectionLength; - print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id, descr, sectionStart, sectionEnd, sectionLength)) + print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(name, descr, sectionStart, sectionEnd, sectionLength)) i += 1 - + print("Total Used RAM : %d" % usedRAM) print("Free RAM : %d" % (TOTAL_IRAM - usedRAM)) print("Free IRam : %d" % usedIRAM) From f4c2d01834a49b10ae0d374d7feeeab38f197d95 Mon Sep 17 00:00:00 2001 From: jochenjagers Date: Fri, 21 Jul 2017 11:21:31 +0200 Subject: [PATCH 29/35] Handle incomplete frame to prevent WebsocketClient ending in an endless loop (#1189) --- Sming/SmingCore/Network/WebsocketFrame.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sming/SmingCore/Network/WebsocketFrame.cpp b/Sming/SmingCore/Network/WebsocketFrame.cpp index 2497787992..bb694ee873 100644 --- a/Sming/SmingCore/Network/WebsocketFrame.cpp +++ b/Sming/SmingCore/Network/WebsocketFrame.cpp @@ -185,6 +185,12 @@ uint8_t WebsocketFrameClass::_getFrameSizes(uint8_t* buffer, size_t length) { _nextReadOffset = 0; // single websocket frame in buffer } + else if(length < _nextReadOffset) + { + // Frame is incomplete + _frameType = WSFrameType::incomplete; + return false; + } return true; } @@ -197,7 +203,8 @@ uint8_t WebsocketFrameClass::decodeFrame(uint8_t * buffer, size_t length) WSFrameType op = (WSFrameType)(buffer[0] & 0b00001111); // Extracting Opcode uint8_t fin = buffer[0] & 0b10000000; // Extracting Fin Bit (Single Frame) - if (op == WSFrameType::continuation || op == WSFrameType::text || op == WSFrameType::binary) //Data frames + // At least there must be one byte that op and fin are vaild + if (length > 0 && op == WSFrameType::continuation || op == WSFrameType::text || op == WSFrameType::binary) //Data frames { if (fin > 0) { From 63c447b5c7ebbc8816b2ab17487092cf6a44b5dc Mon Sep 17 00:00:00 2001 From: slaff Date: Fri, 21 Jul 2017 20:49:19 +0200 Subject: [PATCH 30/35] Feature/auto deployment on release (#1198) * Automated the API documentation update on new release. * Simplified the CI process. CUSTOM_LWIP is enabled by default and there is no need for a separate check any longer. * Updated the networking documentation. --- .travis.yml | 23 ++++++++++++++---- .travis/deploy.sh | 29 +++++++++++++++++++++++ Sming/SmingCore/Network/DNSServer.h | 6 +++++ Sming/SmingCore/Network/FTPServer.h | 7 ++++++ Sming/SmingCore/Network/HttpClient.h | 7 ++++++ Sming/SmingCore/Network/HttpServer.h | 7 ++++++ Sming/SmingCore/Network/MqttClient.h | 7 ++++++ Sming/SmingCore/Network/NetUtils.h | 5 ++++ Sming/SmingCore/Network/NtpClient.h | 1 + Sming/SmingCore/Network/TcpClient.h | 7 ++++++ Sming/SmingCore/Network/TcpConnection.h | 6 +++++ Sming/SmingCore/Network/TcpServer.h | 9 +++++++ Sming/SmingCore/Network/TelnetServer.h | 7 ++++++ Sming/SmingCore/Network/URL.h | 8 +++++++ Sming/SmingCore/Network/UdpConnection.h | 7 ++++++ Sming/SmingCore/Network/WebConstants.h | 8 +++++++ Sming/SmingCore/Network/WebsocketClient.h | 7 ++++++ 17 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 .travis/deploy.sh diff --git a/.travis.yml b/.travis.yml index a428ad2054..c5cd0aa543 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ language: cpp env: global: - SDK_BUILD=258 - + - secure: Db9Cnv+oOmmQMm6aIf3H5VYHaas04uGOtvJMsq7NW05oyDVGCgV5FayjFPVzNHHgv85em1u1CPqX3p7eHWLSKMjARcEpLavXM31HW0RPxvserSpXI7f49EBi6ENgCPre4NGMWpdJgvIhmAfcEMwyLUBqRDmfH4KqqE8V5ZbhvacCbaD2cEOsPDjbkpA66VYp3srTmVUR4cC0ehUdg7odxVOWHaJbA068jJjwz/ggbYMkVljcFkjVHtZAIiZDN1vBGRWGwTjM4TMXwJmw07WumBwhZw6Qm7OShGfBTyg5/6Obqk1QZ///6BQCUD+bGhLlfEleEB48YJXwLD96HXgUwu/wZ7xWStQCWW5GQRPgXaufwZcc1TySJxaDeMhk90cIbUsjXGhmhV+rpM/5PMZTvpTOJbKrz2oY2qqFEZJGEYGoH24LHp4yPpAvQCmqjeLcQj/JiHeL9nKUy+mC3yOPLR/DOTa7t7zN8vuKqm0G0Af3DXXuum2kkzBgHFCNiiLedF2/7BVe/nAbOJLpuQwfVVOqpxIpTzKg05oPmjSsFPp4u03Yso3Kc6ulJQ354mYmnZD34jah5FaWo8YkGCmAGLIaqpcV62LCTSDK58ZMpEU1fy3jANlmktDQlaCUvniUo+c0mqmlstRpzGathb4u1nNHdgB/rjzDx64ug2MmllI= + matrix: include: - os: linux @@ -18,8 +19,15 @@ addons: apt: sources: - ubuntu-toolchain-r-test + - sourceline: 'ppa:libreoffice/libreoffice-4-2' packages: - bsdtar + - doxygen + - doxygen-doc + - doxygen-latex + - doxygen-gui + - graphviz + install: - if [ "$SDK_VERSION" != "2.0.0" ] && [ "$TRAVIS_OS_NAME" == "osx" ]; then export SDK_FILE_NAME="esp-alt-sdk-v${SDK_VERSION}.${SDK_BUILD}-macos-x86_64.zip"; fi - if [ "$SDK_VERSION" != "2.0.0" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then export SDK_FILE_NAME="esp-alt-sdk-v${SDK_VERSION}.${SDK_BUILD}-linux-x86_64.tar.gz"; fi @@ -27,7 +35,8 @@ install: - if [ "$SDK_VERSION" != "2.0.0" ]; then wget https://bintray.com/artifact/download/kireevco/generic/${SDK_FILE_NAME}; fi - if [ "$SDK_VERSION" != "2.0.0" ]; then bsdtar -xf ${SDK_FILE_NAME} -C $TRAVIS_BUILD_DIR/opt/esp-alt-sdk; fi - if [ "$SDK_VERSION" == "2.0.0" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then wget https://github.com/nodemcu/nodemcu-firmware/raw/master/tools/esp-open-sdk.tar.xz; tar -Jxvf esp-open-sdk.tar.xz; ln -s `pwd`/esp-open-sdk/xtensa-lx106-elf $TRAVIS_BUILD_DIR/opt/esp-alt-sdk/. ; fi - - if [ "$SDK_VERSION" == "2.0.0" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then wget http://bbs.espressif.com/download/file.php?id=1690 -O sdk.zip; unzip sdk.zip; ln -s `pwd`/ESP8266_NONOS_SDK/ $TRAVIS_BUILD_DIR/opt/esp-alt-sdk/sdk; fi + - if [ "$SDK_VERSION" == "2.0.0" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then wget http://bbs.espressif.com/download/file.php?id=1690 -O sdk.zip; unzip sdk.zip; ln -s `pwd`/ESP8266_NONOS_SDK/ $TRAVIS_BUILD_DIR/opt/esp-alt-sdk/sdk; export DEPLOY='true'; fi + script: - export CHANGED_FILES=`git diff --diff-filter=AMD HEAD HEAD^ --name-only` - export CHANGED_PROJECTS=`for i in $CHANGED_FILES; do echo "$i" | grep '^samples/' | cut -d'/' -f2; done | uniq` @@ -47,5 +56,11 @@ script: - make clean samples-clean - make ENABLE_CUSTOM_HEAP=1 - make Basic_Blink ENABLE_CUSTOM_HEAP=1 - - make clean samples-clean - - make ENABLE_CUSTOM_LWIP=1; make samples ENABLE_CUSTOM_LWIP=1 + +deploy: + provider: script + script: sh $TRAVIS_BUILD_DIR/.travis/deploy.sh $TRAVIS_TAG + skip_cleanup: true + on: + tags: true + condition: $DEPLOY == true diff --git a/.travis/deploy.sh b/.travis/deploy.sh new file mode 100644 index 0000000000..a614d0783c --- /dev/null +++ b/.travis/deploy.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e # exit with nonzero exit code if anything fails + +TAG=$1 +if [ -z $TAG ]; then + echo "Usage:\n\t$0 \n"; + exit 1; +fi + + +# Get information about the release +# TODO: ... + +# Update documentation +cd $SMING_HOME +make docs +cd .. + +git fetch origin gh-pages:gh-pages +git checkout gh-pages + +DOCS_DIR=$SMING_HOME/../api + +rm -rf $DOCS_DIR +cp -r $SMING_HOME/../docs/api/sming/ $DOCS_DIR +git add -A $DOCS_DIR +git commit -m "Updated the API docs to version $TAG." || 1 + +git push https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages diff --git a/Sming/SmingCore/Network/DNSServer.h b/Sming/SmingCore/Network/DNSServer.h index 679ddeaa43..aa897c9f1b 100644 --- a/Sming/SmingCore/Network/DNSServer.h +++ b/Sming/SmingCore/Network/DNSServer.h @@ -11,6 +11,11 @@ * Created on March 4, 2016 */ +/** @defgroup dnsserver DNS server + * @brief Provides DNS server + * @ingroup udp + * @{ + */ #ifndef DNSServer_h #define DNSServer_h @@ -83,4 +88,5 @@ class DNSServer : public UdpConnection }; +/** @} */ #endif //DNSServer_h diff --git a/Sming/SmingCore/Network/FTPServer.h b/Sming/SmingCore/Network/FTPServer.h index 3b3ca17479..0fcee67c2b 100644 --- a/Sming/SmingCore/Network/FTPServer.h +++ b/Sming/SmingCore/Network/FTPServer.h @@ -5,6 +5,12 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup ftpserver FTP server + * @brief Provides FTP server + * @ingroup tcpserver + * @{ + */ + #ifndef _SMING_CORE_FTPSERVER_H_ #define _SMING_CORE_FTPSERVER_H_ @@ -33,4 +39,5 @@ class FTPServer: public TcpServer HashMap users; }; +/** @} */ #endif /* _SMING_CORE_FTPServer_H_ */ diff --git a/Sming/SmingCore/Network/HttpClient.h b/Sming/SmingCore/Network/HttpClient.h index da1bd1fedd..afca2fe1ee 100644 --- a/Sming/SmingCore/Network/HttpClient.h +++ b/Sming/SmingCore/Network/HttpClient.h @@ -10,6 +10,12 @@ * ****/ +/** @defgroup httpclient HTTP client + * @brief Provides HTTP/S client + * @ingroup tcpclient + * @{ + */ + #ifndef _SMING_CORE_NETWORK_HTTPCLIENT_H_ #define _SMING_CORE_NETWORK_HTTPCLIENT_H_ @@ -93,4 +99,5 @@ class HttpClient #endif }; +/** @} */ #endif /* _SMING_CORE_NETWORK_HTTPCLIENT_H_ */ diff --git a/Sming/SmingCore/Network/HttpServer.h b/Sming/SmingCore/Network/HttpServer.h index 85b8b0516c..dfb68a555a 100644 --- a/Sming/SmingCore/Network/HttpServer.h +++ b/Sming/SmingCore/Network/HttpServer.h @@ -10,6 +10,12 @@ * ****/ +/** @defgroup httpserver HTTP server + * @brief Provides powerful HTTP/S + Websocket server + * @ingroup tcpserver + * @{ + */ + #ifndef _SMING_CORE_HTTPSERVER_H_ #define _SMING_CORE_HTTPSERVER_H_ @@ -78,4 +84,5 @@ class HttpServer: public TcpServer BodyParsers bodyParsers; }; +/** @} */ #endif /* _SMING_CORE_HTTPSERVER_H_ */ diff --git a/Sming/SmingCore/Network/MqttClient.h b/Sming/SmingCore/Network/MqttClient.h index 3f9b1b09ea..3c5d424ecf 100644 --- a/Sming/SmingCore/Network/MqttClient.h +++ b/Sming/SmingCore/Network/MqttClient.h @@ -5,6 +5,12 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup mqttclient MQTT client + * @brief Provides MQTT client + * @ingroup tcpclient + * @{ + */ + #ifndef _SMING_CORE_NETWORK_MqttClient_H_ #define _SMING_CORE_NETWORK_MqttClient_H_ @@ -80,4 +86,5 @@ class MqttClient: protected TcpClient HashMap onDeliveryQueue; }; +/** @} */ #endif /* _SMING_CORE_NETWORK_MqttClient_H_ */ diff --git a/Sming/SmingCore/Network/NetUtils.h b/Sming/SmingCore/Network/NetUtils.h index 32108f0027..e2459a2cc5 100644 --- a/Sming/SmingCore/Network/NetUtils.h +++ b/Sming/SmingCore/Network/NetUtils.h @@ -5,6 +5,10 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup networking Networking + * @{ + */ + #ifndef _SMING_CORE_NETWORK_NETUTILS_H_ #define _SMING_CORE_NETWORK_NETUTILS_H_ @@ -37,4 +41,5 @@ class NetUtils static bool ipClientRoutingFixed; }; +/** @} */ #endif /* _SMING_CORE_NETWORK_NETUTILS_H_ */ diff --git a/Sming/SmingCore/Network/NtpClient.h b/Sming/SmingCore/Network/NtpClient.h index 5ca4651d7b..57357553e1 100644 --- a/Sming/SmingCore/Network/NtpClient.h +++ b/Sming/SmingCore/Network/NtpClient.h @@ -1,6 +1,7 @@ /** @defgroup ntp Network Time Protocol client * @brief Provides NTP client * @ingroup datetime + * @ingroup udp * @{ */ #ifndef APP_NTPCLIENT_H_ diff --git a/Sming/SmingCore/Network/TcpClient.h b/Sming/SmingCore/Network/TcpClient.h index 30fbf3a9bc..4f27342ca0 100644 --- a/Sming/SmingCore/Network/TcpClient.h +++ b/Sming/SmingCore/Network/TcpClient.h @@ -5,6 +5,12 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup tcpclient Clients + * @brief Provides base TCP client + * @ingroup tcp + * @{ + */ + #ifndef _SMING_CORE_TCPCLIENT_H_ #define _SMING_CORE_TCPCLIENT_H_ @@ -83,4 +89,5 @@ class TcpClient : public TcpConnection int16_t asyncTotalLen = 0; }; +/** @} */ #endif /* _SMING_CORE_TCPCLIENT_H_ */ diff --git a/Sming/SmingCore/Network/TcpConnection.h b/Sming/SmingCore/Network/TcpConnection.h index 3d4855a45d..a902e3b55a 100644 --- a/Sming/SmingCore/Network/TcpConnection.h +++ b/Sming/SmingCore/Network/TcpConnection.h @@ -5,6 +5,11 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup tcp TCP + * @ingroup networking + * @{ + */ + #ifndef _SMING_CORE_TCPCONNECTION_H_ #define _SMING_CORE_TCPCONNECTION_H_ @@ -231,4 +236,5 @@ class TcpConnection bool useSsl = false; }; +/** @} */ #endif /* _SMING_CORE_TCPCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/TcpServer.h b/Sming/SmingCore/Network/TcpServer.h index fd3fe8f3e3..f70d69b171 100644 --- a/Sming/SmingCore/Network/TcpServer.h +++ b/Sming/SmingCore/Network/TcpServer.h @@ -5,6 +5,14 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ + +/** @defgroup tcpserver Servers + * @brief Provides the base for building TCP servers + * @ingroup tcp + * + * @{ + */ + #ifndef _SMING_CORE_TCPSERVER_H_ #define _SMING_CORE_TCPSERVER_H_ @@ -61,4 +69,5 @@ class TcpServer: public TcpConnection { TcpClientConnectDelegate clientConnectDelegate = NULL; }; +/** @} */ #endif /* _SMING_CORE_TCPSERVER_H_ */ diff --git a/Sming/SmingCore/Network/TelnetServer.h b/Sming/SmingCore/Network/TelnetServer.h index 0c7582d8e2..dba4e8c60b 100644 --- a/Sming/SmingCore/Network/TelnetServer.h +++ b/Sming/SmingCore/Network/TelnetServer.h @@ -5,6 +5,12 @@ * Author: Herman */ +/** @defgroup telnetserver Telnet server + * @brief Provides Telnet server + * @ingroup tcpserver + * @{ + */ + #ifndef APP_TELNETSERVER_H_ #define APP_TELNETSERVER_H_ @@ -42,4 +48,5 @@ class TelnetServer : public TcpServer bool telnetCommand = true; }; +/** @} */ #endif /* APP_TELNETSERVER_H_ */ diff --git a/Sming/SmingCore/Network/URL.h b/Sming/SmingCore/Network/URL.h index 33e16e3b2f..1912b12e08 100644 --- a/Sming/SmingCore/Network/URL.h +++ b/Sming/SmingCore/Network/URL.h @@ -5,6 +5,13 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup url URL + * @brief Provides URL handling + * @ingroup httpserver + * @ingroup httpclient + * @{ + */ + #ifndef _SMING_CORE_NETWORK_URL_H_ #define _SMING_CORE_NETWORK_URL_H_ @@ -31,4 +38,5 @@ class URL String Query; }; +/** @} */ #endif /* _SMING_CORE_NETWORK_URL_H_ */ diff --git a/Sming/SmingCore/Network/UdpConnection.h b/Sming/SmingCore/Network/UdpConnection.h index a321263d10..0123dbe135 100644 --- a/Sming/SmingCore/Network/UdpConnection.h +++ b/Sming/SmingCore/Network/UdpConnection.h @@ -5,6 +5,12 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup udp UDP + * @brief Provides base for UDP clients or services + * @ingroup networking + * @{ + */ + #ifndef SMINGCORE_NETWORK_UDPCONNECTION_H_ #define SMINGCORE_NETWORK_UDPCONNECTION_H_ @@ -49,4 +55,5 @@ class UdpConnection UdpConnectionDataDelegate onDataCallback; }; +/** @} */ #endif /* SMINGCORE_NETWORK_UDPCONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/WebConstants.h b/Sming/SmingCore/Network/WebConstants.h index 8d43855bb9..1b8e708d26 100644 --- a/Sming/SmingCore/Network/WebConstants.h +++ b/Sming/SmingCore/Network/WebConstants.h @@ -5,6 +5,13 @@ * All files of the Sming Core are provided under the LGPL v3 license. ****/ +/** @defgroup httpconsts HTTP constants to be used with HTTP client or HTTP server + * @brief Provides HTTP constants + * @ingroup httpserver + * @ingroup httpclient + * @{ + */ + #ifndef _SMING_CORE_NETWORK_WEBCONSTANTS_H_ #define _SMING_CORE_NETWORK_WEBCONSTANTS_H_ @@ -81,4 +88,5 @@ namespace ContentType } }; +/** @} */ #endif /* _SMING_CORE_NETWORK_WEBCONSTANTS_H_ */ diff --git a/Sming/SmingCore/Network/WebsocketClient.h b/Sming/SmingCore/Network/WebsocketClient.h index 1ed1cdf231..7342332ec3 100644 --- a/Sming/SmingCore/Network/WebsocketClient.h +++ b/Sming/SmingCore/Network/WebsocketClient.h @@ -10,6 +10,12 @@ * */ +/** @defgroup wsclient WebSocket client + * @brief Provides WebSocket client + * @ingroup tcpclient + * @{ + */ + #ifndef WEBSOCKETCLIENT_H #define WEBSOCKETCLIENT_H @@ -130,5 +136,6 @@ class WebsocketClient: protected TcpClient String _key; }; +/** @} */ #endif /* WEBSOCKETCLIENT_H */ From a7d3360fc9ed677d119c599e94bcf87c209e6397 Mon Sep 17 00:00:00 2001 From: Andriy Petrynchyn Date: Tue, 25 Jul 2017 10:43:15 +0300 Subject: [PATCH 31/35] Fixed WebSocket sample HTML page (#1201) --- samples/HttpServer_WebSockets/files/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/HttpServer_WebSockets/files/index.html b/samples/HttpServer_WebSockets/files/index.html index 9546122011..50ca1f54ec 100644 --- a/samples/HttpServer_WebSockets/files/index.html +++ b/samples/HttpServer_WebSockets/files/index.html @@ -16,7 +16,7 @@ function testWebSocket() { - var wsUri = "ws://" + location.host + "/"; + var wsUri = "ws://" + location.host + "/ws"; websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) }; websocket.onclose = function(evt) { onClose(evt) }; From 6968d3eab5b82d440d9db0e6484a8c1d61b0d217 Mon Sep 17 00:00:00 2001 From: slaff Date: Tue, 25 Jul 2017 09:56:45 +0200 Subject: [PATCH 32/35] Added example demonstrating Js and CSS combining ... (#1200) * Added example detailing how to optimize file delivery. * Added Last-Modified header example. --- samples/HttpServer_ConfigNetwork/.gitignore | 1 + samples/HttpServer_ConfigNetwork/Makefile | 7 + samples/HttpServer_ConfigNetwork/Readme.md | 25 ++ .../app/application.cpp | 17 + samples/HttpServer_ConfigNetwork/gulpfile.js | 89 ++++ samples/HttpServer_ConfigNetwork/package.json | 22 + .../web/build/.lastModified | 1 + .../web/build/bootstrap-core.css.gz | Bin 0 -> 15861 bytes .../web/build/bootstrap.css.gz | Bin 15615 -> 0 bytes .../web/build/core.js.gz | Bin 0 -> 30474 bytes .../web/build/index.html | 298 +------------- .../web/build/jquery.js.gz | Bin 30153 -> 0 bytes .../web/build/settings.html | 114 +----- .../web/build/style.css | 382 ------------------ .../web/dev/index.html | 5 + .../web/dev/settings.html | 6 + 16 files changed, 177 insertions(+), 790 deletions(-) create mode 100644 samples/HttpServer_ConfigNetwork/.gitignore create mode 100644 samples/HttpServer_ConfigNetwork/Readme.md create mode 100644 samples/HttpServer_ConfigNetwork/gulpfile.js create mode 100644 samples/HttpServer_ConfigNetwork/package.json create mode 100644 samples/HttpServer_ConfigNetwork/web/build/.lastModified create mode 100644 samples/HttpServer_ConfigNetwork/web/build/bootstrap-core.css.gz delete mode 100644 samples/HttpServer_ConfigNetwork/web/build/bootstrap.css.gz create mode 100644 samples/HttpServer_ConfigNetwork/web/build/core.js.gz delete mode 100644 samples/HttpServer_ConfigNetwork/web/build/jquery.js.gz delete mode 100644 samples/HttpServer_ConfigNetwork/web/build/style.css diff --git a/samples/HttpServer_ConfigNetwork/.gitignore b/samples/HttpServer_ConfigNetwork/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/samples/HttpServer_ConfigNetwork/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/samples/HttpServer_ConfigNetwork/Makefile b/samples/HttpServer_ConfigNetwork/Makefile index 16d76cd676..254c343ac5 100644 --- a/samples/HttpServer_ConfigNetwork/Makefile +++ b/samples/HttpServer_ConfigNetwork/Makefile @@ -22,3 +22,10 @@ include $(SMING_HOME)/Makefile-rboot.mk else include $(SMING_HOME)/Makefile-project.mk endif + +web-pack: + $(Q) gulp + $(Q) date +'%a, %d %b %Y %H:%M:%S GMT' -u > web/build/.lastModified + +web-upload: web-pack spiff_update + $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) $(SPIFF_START_OFFSET) $(SPIFF_BIN_OUT) diff --git a/samples/HttpServer_ConfigNetwork/Readme.md b/samples/HttpServer_ConfigNetwork/Readme.md new file mode 100644 index 0000000000..930221384e --- /dev/null +++ b/samples/HttpServer_ConfigNetwork/Readme.md @@ -0,0 +1,25 @@ +# 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). Serving multiple files at once can be problematic. It is not the size of the files that can cause problems, but the number of simultaneous files that need to be delivered. Therefore if you serve multiple CSS or JS files you can optimize your web application before uploading it into your ESP8266 using the advice below. + +# Optimizing File Delivery + +In this example you will see how to combine CSS and JS files, compress them and deliver the optimized content via the HTTP server. + +## Installation + +The file optimization uses `gulp`. To install it and the needed gulp packages you need to install first [npm](https://www.npmjs.com/). +Npm is the Node.JS package manager. Once you are done with the installation you can run from the command line the following: + + npm install + + The command above will install gulp and its dependencies. + +## Usage + +During the development of your web application you should work only in the `web/dev/` folder. Once you are ready with the application you can `pack` the resources and `upload` them to your device. The commands are + + make web-pack + make web-upload + +That should be it. \ No newline at end of file diff --git a/samples/HttpServer_ConfigNetwork/app/application.cpp b/samples/HttpServer_ConfigNetwork/app/application.cpp index 81e4ad4312..faf2234b5d 100644 --- a/samples/HttpServer_ConfigNetwork/app/application.cpp +++ b/samples/HttpServer_ConfigNetwork/app/application.cpp @@ -9,6 +9,8 @@ BssList networks; String network, password; Timer connectionTimer; +String lastModified; + void onIndex(HttpRequest &request, HttpResponse &response) { TemplateFileStream *tmpl = new TemplateFileStream("index.html"); @@ -53,6 +55,11 @@ void onIpConfig(HttpRequest &request, HttpResponse &response) void onFile(HttpRequest &request, HttpResponse &response) { + if (lastModified.length() >0 && request.getHeader("If-Modified-Since").equals(lastModified)) { + response.code = HTTP_STATUS_NOT_MODIFIED; + return; + } + String file = request.getPath(); if (file[0] == '/') file = file.substring(1); @@ -61,6 +68,10 @@ void onFile(HttpRequest &request, HttpResponse &response) response.forbidden(); else { + if(lastModified.length() > 0) { + response.setHeader("Last-Modified", lastModified); + } + response.setCache(86400, true); // It's important to use cache for better performance. response.sendFile(file); } @@ -193,6 +204,12 @@ 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 AppSettings.load(); diff --git a/samples/HttpServer_ConfigNetwork/gulpfile.js b/samples/HttpServer_ConfigNetwork/gulpfile.js new file mode 100644 index 0000000000..ea3e869b99 --- /dev/null +++ b/samples/HttpServer_ConfigNetwork/gulpfile.js @@ -0,0 +1,89 @@ +/* + +ESP8266 file system builder with PlatformIO support + +Copyright (C) 2016 by Xose Pérez + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +// ----------------------------------------------------------------------------- +// File system builder +// ----------------------------------------------------------------------------- + +const gulp = require('gulp'); +const plumber = require('gulp-plumber'); +const htmlmin = require('gulp-htmlmin'); +const cleancss = require('gulp-clean-css'); +const uglify = require('gulp-uglify'); +const gzip = require('gulp-gzip'); +const del = require('del'); +const useref = require('gulp-useref'); +const gulpif = require('gulp-if'); +const inline = require('gulp-inline'); + +/* Clean destination folder */ +gulp.task('clean', function() { + return del(['data/*']); +}); + +/* Copy static files */ +gulp.task('files', function() { + return gulp.src([ + 'web/dev/*.{jpg,jpeg,png,ico,gif}', + ]) + .pipe(gulp.dest('web/build')); +}); + +/* Process HTML, CSS, JS --- INLINE --- */ +gulp.task('inline', function() { + return gulp.src('web/dev/*.html') + .pipe(inline({ + base: 'web/dev/', + js: uglify, + css: cleancss, + disabledTypes: ['svg', 'img'] + })) + .pipe(htmlmin({ + collapseWhitespace: true, + removeComments: true, + minifyCSS: true, + minifyJS: true + })) + .pipe(gzip()) + .pipe(gulp.dest('web/build/')); +}) + +/* Process HTML, CSS, JS */ +gulp.task('html', function() { + return gulp.src('web/dev/*.html') + .pipe(useref()) + .pipe(plumber()) + .pipe(gulpif('*.css', cleancss())) + .pipe(gulpif('*.js', uglify())) + .pipe(gulpif('*.html', htmlmin({ + collapseWhitespace: true, + removeComments: true, + minifyCSS: true, + minifyJS: true + }))) + .pipe(gulpif(['*.css','*.js'], gzip())) + .pipe(gulp.dest('web/build/')); +}); + +/* Build file system */ +gulp.task('buildfs', ['clean', 'files', 'html']); +gulp.task('buildfs2', ['clean', 'files', 'inline']); +gulp.task('default', ['buildfs']); diff --git a/samples/HttpServer_ConfigNetwork/package.json b/samples/HttpServer_ConfigNetwork/package.json new file mode 100644 index 0000000000..2172fccfa7 --- /dev/null +++ b/samples/HttpServer_ConfigNetwork/package.json @@ -0,0 +1,22 @@ +{ + "name": "esp8266-filesystem-builder", + "version": "0.1.0", + "description": "Gulp based build script for ESP8266 file system files", + "main": "gulpfile.js", + "author": "Xose Pérez ", + "license": "MIT", + "devDependencies": { + "del": "^2.2.1", + "gulp": "^3.9.1", + "gulp-clean-css": "^2.0.10", + "gulp-gzip": "^1.4.0", + "gulp-htmlmin": "^2.0.0", + "gulp-if": "^2.0.1", + "gulp-inline": "^0.1.1", + "gulp-plumber": "^1.1.0", + "gulp-uglify": "^1.5.3", + "gulp-useref": "^3.1.2", + "yargs": "^5.0.0" + }, + "dependencies": {} +} diff --git a/samples/HttpServer_ConfigNetwork/web/build/.lastModified b/samples/HttpServer_ConfigNetwork/web/build/.lastModified new file mode 100644 index 0000000000..9685abd72b --- /dev/null +++ b/samples/HttpServer_ConfigNetwork/web/build/.lastModified @@ -0,0 +1 @@ +Mon, 24 Jul 2017 17:20:31 GMT diff --git a/samples/HttpServer_ConfigNetwork/web/build/bootstrap-core.css.gz b/samples/HttpServer_ConfigNetwork/web/build/bootstrap-core.css.gz new file mode 100644 index 0000000000000000000000000000000000000000..8483876af3e9ba369e93d223ee6c03ca8ca9f77a GIT binary patch literal 15861 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≺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 F{|_Xg0#E<| literal 0 HcmV?d00001 diff --git a/samples/HttpServer_ConfigNetwork/web/build/bootstrap.css.gz b/samples/HttpServer_ConfigNetwork/web/build/bootstrap.css.gz deleted file mode 100644 index 55569c476553212b24dc3ce546bebe5ac8ab480d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15615 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
xUJv1gU! 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 diff --git a/samples/HttpServer_ConfigNetwork/web/build/core.js.gz b/samples/HttpServer_ConfigNetwork/web/build/core.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..6765d5850bb1af07a91fbe34174bbaddefc10c1c GIT binary patch literal 30474 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 RkUbR7{twGu9YXg|0RTa39)AD; literal 0 HcmV?d00001 diff --git a/samples/HttpServer_ConfigNetwork/web/build/index.html b/samples/HttpServer_ConfigNetwork/web/build/index.html index 0bf43f16b8..a89f8c23ad 100644 --- a/samples/HttpServer_ConfigNetwork/web/build/index.html +++ b/samples/HttpServer_ConfigNetwork/web/build/index.html @@ -1,298 +1,4 @@ - - - - - - - - - - - - - Sming Framework WiFi Network configuration - - - - - - - - - - - - -
-
- -

Sming Based

-
- -
-

Network Settings

- -

Wireless network connection -

-
- -
-
- -
-
-
-

Networks

- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
- -
- - +

Sming Based

Network Settings

Wireless network connection

Networks

\ No newline at end of file diff --git a/samples/HttpServer_ConfigNetwork/web/build/jquery.js.gz b/samples/HttpServer_ConfigNetwork/web/build/jquery.js.gz deleted file mode 100644 index 54fb84cab838898640767850e8fa436129f1feb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30153 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 - - - - - - - - - - Sming Framework WiFi Network configuration - - - - - - - - - - - - -
-
- -

Sming Based

-
- -
-

Network Settings

-

Wireless network connection -

-
-
-
- -

Settings

-
-
- IP Address -
-
- -
- - -
-
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
-
-
-
- -
- -
- - +

Sming Based

Network Settings

Wireless network connection

Settings

IP Address
\ No newline at end of file diff --git a/samples/HttpServer_ConfigNetwork/web/build/style.css b/samples/HttpServer_ConfigNetwork/web/build/style.css deleted file mode 100644 index bff5d2c274..0000000000 --- a/samples/HttpServer_ConfigNetwork/web/build/style.css +++ /dev/null @@ -1,382 +0,0 @@ -#floatingCirclesG{ - position:absolute; - z-index: 100; - width:50px; - height:50px; - top: 50px; - left: 50%; - -moz-transform:scale(0.6); - -webkit-transform:scale(0.6); - -ms-transform:scale(0.6); - -o-transform:scale(0.6); - transform:scale(0.6); -} - -.f_circleG{ - position:absolute; - background-color:#FFFFFF; - height:9px; - width:9px; - -moz-border-radius:5px; - -moz-animation-name:f_fadeG; - -moz-animation-duration:1.04s; - -moz-animation-iteration-count:infinite; - -moz-animation-direction:normal; - -webkit-border-radius:5px; - -webkit-animation-name:f_fadeG; - -webkit-animation-duration:1.04s; - -webkit-animation-iteration-count:infinite; - -webkit-animation-direction:normal; - -ms-border-radius:5px; - -ms-animation-name:f_fadeG; - -ms-animation-duration:1.04s; - -ms-animation-iteration-count:infinite; - -ms-animation-direction:normal; - -o-border-radius:5px; - -o-animation-name:f_fadeG; - -o-animation-duration:1.04s; - -o-animation-iteration-count:infinite; - -o-animation-direction:normal; - border-radius:5px; - animation-name:f_fadeG; - animation-duration:1.04s; - animation-iteration-count:infinite; - animation-direction:normal; -} - -#frotateG_01{ - left:0; - top:20px; - -moz-animation-delay:0.39s; - -webkit-animation-delay:0.39s; - -ms-animation-delay:0.39s; - -o-animation-delay:0.39s; - animation-delay:0.39s; -} - -#frotateG_02{ - left:6px; - top:6px; - -moz-animation-delay:0.52s; - -webkit-animation-delay:0.52s; - -ms-animation-delay:0.52s; - -o-animation-delay:0.52s; - animation-delay:0.52s; -} - -#frotateG_03{ - left:20px; - top:0; - -moz-animation-delay:0.65s; - -webkit-animation-delay:0.65s; - -ms-animation-delay:0.65s; - -o-animation-delay:0.65s; - animation-delay:0.65s; -} - -#frotateG_04{ - right:6px; - top:6px; - -moz-animation-delay:0.78s; - -webkit-animation-delay:0.78s; - -ms-animation-delay:0.78s; - -o-animation-delay:0.78s; - animation-delay:0.78s; -} - -#frotateG_05{ - right:0; - top:20px; - -moz-animation-delay:0.91s; - -webkit-animation-delay:0.91s; - -ms-animation-delay:0.91s; - -o-animation-delay:0.91s; - animation-delay:0.91s; -} - -#frotateG_06{ - right:6px; - bottom:6px; - -moz-animation-delay:1.04s; - -webkit-animation-delay:1.04s; - -ms-animation-delay:1.04s; - -o-animation-delay:1.04s; - animation-delay:1.04s; -} - -#frotateG_07{ - left:20px; - bottom:0; - -moz-animation-delay:1.17s; - -webkit-animation-delay:1.17s; - -ms-animation-delay:1.17s; - -o-animation-delay:1.17s; - animation-delay:1.17s; -} - -#frotateG_08{ - left:6px; - bottom:6px; - -moz-animation-delay:1.3s; - -webkit-animation-delay:1.3s; - -ms-animation-delay:1.3s; - -o-animation-delay:1.3s; - animation-delay:1.3s; -} - -@-moz-keyframes f_fadeG{ - 0%{ - background-color:#000000} - - 100%{ - background-color:#FFFFFF} - -} - -@-webkit-keyframes f_fadeG{ - 0%{ - background-color:#000000} - - 100%{ - background-color:#FFFFFF} - -} - -@-ms-keyframes f_fadeG{ - 0%{ - background-color:#000000} - - 100%{ - background-color:#FFFFFF} - -} - -@-o-keyframes f_fadeG{ - 0%{ - background-color:#000000} - - 100%{ - background-color:#FFFFFF} - -} - -@keyframes f_fadeG{ - 0%{ - background-color:#000000} - - 100%{ - background-color:#FFFFFF} - -} - -#circularG{ - position:relative; - width:30px; - height:30px} - -.circularG{ - position:absolute; - background-color:#000000; - width:7px; - height:7px; - -moz-border-radius:5px; - -moz-animation-name:bounce_circularG; - -moz-animation-duration:1.04s; - -moz-animation-iteration-count:infinite; - -moz-animation-direction:normal; - -webkit-border-radius:5px; - -webkit-animation-name:bounce_circularG; - -webkit-animation-duration:1.04s; - -webkit-animation-iteration-count:infinite; - -webkit-animation-direction:normal; - -ms-border-radius:5px; - -ms-animation-name:bounce_circularG; - -ms-animation-duration:1.04s; - -ms-animation-iteration-count:infinite; - -ms-animation-direction:normal; - -o-border-radius:5px; - -o-animation-name:bounce_circularG; - -o-animation-duration:1.04s; - -o-animation-iteration-count:infinite; - -o-animation-direction:normal; - border-radius:5px; - animation-name:bounce_circularG; - animation-duration:1.04s; - animation-iteration-count:infinite; - animation-direction:normal; -} - -#circularG_1{ - left:0; - top:12px; - -moz-animation-delay:0.39s; - -webkit-animation-delay:0.39s; - -ms-animation-delay:0.39s; - -o-animation-delay:0.39s; - animation-delay:0.39s; -} - -#circularG_2{ - left:3px; - top:3px; - -moz-animation-delay:0.52s; - -webkit-animation-delay:0.52s; - -ms-animation-delay:0.52s; - -o-animation-delay:0.52s; - animation-delay:0.52s; -} - -#circularG_3{ - top:0; - left:12px; - -moz-animation-delay:0.65s; - -webkit-animation-delay:0.65s; - -ms-animation-delay:0.65s; - -o-animation-delay:0.65s; - animation-delay:0.65s; -} - -#circularG_4{ - right:3px; - top:3px; - -moz-animation-delay:0.78s; - -webkit-animation-delay:0.78s; - -ms-animation-delay:0.78s; - -o-animation-delay:0.78s; - animation-delay:0.78s; -} - -#circularG_5{ - right:0; - top:12px; - -moz-animation-delay:0.91s; - -webkit-animation-delay:0.91s; - -ms-animation-delay:0.91s; - -o-animation-delay:0.91s; - animation-delay:0.91s; -} - -#circularG_6{ - right:3px; - bottom:3px; - -moz-animation-delay:1.04s; - -webkit-animation-delay:1.04s; - -ms-animation-delay:1.04s; - -o-animation-delay:1.04s; - animation-delay:1.04s; -} - -#circularG_7{ - left:12px; - bottom:0; - -moz-animation-delay:1.17s; - -webkit-animation-delay:1.17s; - -ms-animation-delay:1.17s; - -o-animation-delay:1.17s; - animation-delay:1.17s; -} - -#circularG_8{ - left:3px; - bottom:3px; - -moz-animation-delay:1.3s; - -webkit-animation-delay:1.3s; - -ms-animation-delay:1.3s; - -o-animation-delay:1.3s; - animation-delay:1.3s; -} - -@-moz-keyframes bounce_circularG{ - 0%{ - -moz-transform:scale(1)} - - 100%{ - -moz-transform:scale(.3)} - -} - -@-webkit-keyframes bounce_circularG{ - 0%{ - -webkit-transform:scale(1)} - - 100%{ - -webkit-transform:scale(.3)} - -} - -@-ms-keyframes bounce_circularG{ - 0%{ - -ms-transform:scale(1)} - - 100%{ - -ms-transform:scale(.3)} - -} - -@-o-keyframes bounce_circularG{ - 0%{ - -o-transform:scale(1)} - - 100%{ - -o-transform:scale(.3)} - -} - -@keyframes bounce_circularG{ - 0%{ - transform:scale(1)} - - 100%{ - transform:scale(.3)} - -} - -.network-info{ - padding-top: 10px; -} - -.connect-btn{ - float: left; -} - -.connect-btn-wrapper{ - margin-top: 10px; - height: 34px -} - -.reload-btn-wrapper{ - margin-bottom: 10px; -} - -.wifi{ - background: url(/wifi-sprites.png) no-repeat; - float: left; - margin-right: 10px; -} - -.wifi-4{ - background-position: 0 0; - width: 64px; - height: 64px; -} - -.wifi-3{ - background-position: -64px 0; - width: 64px; - height: 64px; -} - -.wifi-2{ - background-position: -128px 0; - width: 64px; - height: 64px; -} - -.wifi-1{ - background-position: -192px 0; - width: 64px; - height: 64px; -} -.error{ - margin-top: 10px; -} \ No newline at end of file diff --git a/samples/HttpServer_ConfigNetwork/web/dev/index.html b/samples/HttpServer_ConfigNetwork/web/dev/index.html index 0bf43f16b8..9686d09d1e 100644 --- a/samples/HttpServer_ConfigNetwork/web/dev/index.html +++ b/samples/HttpServer_ConfigNetwork/web/dev/index.html @@ -13,9 +13,14 @@ Sming Framework WiFi Network configuration + + + + + + + + + + +