diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 6950012217..4eef8725a6 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -36,7 +36,13 @@ #define DEBUG_OUTPUT Serial #endif -const char * AUTHORIZATION_HEADER = "Authorization"; +//const char * AUTHORIZATION_HEADER = "Authorization"; +static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization"; +static const char qop_auth[] PROGMEM = "qop=auth"; +static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate"; +static const char colon[] PROGMEM = ":"; +static const char Content_Length[] PROGMEM = "Content-Length"; + ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port) : _server(addr, port) @@ -44,13 +50,13 @@ ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port) , _currentVersion(0) , _currentStatus(HC_NONE) , _statusChange(0) -, _currentHandler(0) -, _firstHandler(0) -, _lastHandler(0) +, _currentHandler(nullptr) +, _firstHandler(nullptr) +, _lastHandler(nullptr) , _currentArgCount(0) -, _currentArgs(0) +, _currentArgs(nullptr) , _headerKeysCount(0) -, _currentHeaders(0) +, _currentHeaders(nullptr) , _contentLength(0) , _chunked(false) { @@ -62,13 +68,13 @@ ESP8266WebServer::ESP8266WebServer(int port) , _currentVersion(0) , _currentStatus(HC_NONE) , _statusChange(0) -, _currentHandler(0) -, _firstHandler(0) -, _lastHandler(0) +, _currentHandler(nullptr) +, _firstHandler(nullptr) +, _lastHandler(nullptr) , _currentArgCount(0) -, _currentArgs(0) +, _currentArgs(nullptr) , _headerKeysCount(0) -, _currentHeaders(0) +, _currentHeaders(nullptr) , _contentLength(0) , _chunked(false) { @@ -88,79 +94,83 @@ ESP8266WebServer::~ESP8266WebServer() { } void ESP8266WebServer::begin() { - _currentStatus = HC_NONE; + close(); _server.begin(); - if(!_headerKeysCount) - collectHeaders(0, 0); } -String ESP8266WebServer::_exractParam(String& authReq,const String& param,const char delimit){ +void ESP8266WebServer::begin(uint16_t port) { + close(); + _server.begin(port); +} + +String ESP8266WebServer::_extractParam(String& authReq,const String& param,const char delimit){ int _begin = authReq.indexOf(param); - if (_begin==-1) return ""; + if (_begin==-1) + return ""; return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); } bool ESP8266WebServer::authenticate(const char * username, const char * password){ - if(hasHeader(AUTHORIZATION_HEADER)){ - String authReq = header(AUTHORIZATION_HEADER); - if(authReq.startsWith("Basic")){ + if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { + String authReq = header(FPSTR(AUTHORIZATION_HEADER)); + if(authReq.startsWith(F("Basic"))){ authReq = authReq.substring(6); authReq.trim(); char toencodeLen = strlen(username)+strlen(password)+1; char *toencode = new char[toencodeLen + 1]; if(toencode == NULL){ - authReq = String(); + authReq = ""; return false; } char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; if(encoded == NULL){ - authReq = String(); + authReq = ""; delete[] toencode; return false; } sprintf(toencode, "%s:%s", username, password); if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { - authReq = String(); + authReq = ""; delete[] toencode; delete[] encoded; return true; } delete[] toencode; delete[] encoded; - }else if(authReq.startsWith("Digest")){ + } else if(authReq.startsWith(F("Digest"))) { authReq = authReq.substring(7); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println(authReq); #endif - String _username = _exractParam(authReq,"username=\""); - if((!_username.length())||_username!=String(username)){ - authReq = String(); + String _username = _extractParam(authReq,F("username=\"")); + if(!_username.length() || _username != String(username)) { + authReq = ""; return false; } // extracting required parameters for RFC 2069 simpler Digest - String _realm = _exractParam(authReq,"realm=\""); - String _nonce = _exractParam(authReq,"nonce=\""); - String _uri = _exractParam(authReq,"uri=\""); - String _response = _exractParam(authReq,"response=\""); - String _opaque = _exractParam(authReq,"opaque=\""); - - if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ - authReq = String(); + String _realm = _extractParam(authReq, F("realm=\"")); + String _nonce = _extractParam(authReq, F("nonce=\"")); + String _uri = _extractParam(authReq, F("uri=\"")); + String _response = _extractParam(authReq, F("response=\"")); + String _opaque = _extractParam(authReq, F("opaque=\"")); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { + authReq = ""; return false; } - if((_opaque!=_sopaque)||(_nonce!=_snonce)||(_realm!=_srealm)){ - authReq = String(); + if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { + authReq = ""; return false; } // parameters for the RFC 2617 newer Digest String _nc,_cnonce; - if(authReq.indexOf("qop=auth") != -1){ - _nc = _exractParam(authReq,"nc=",','); - _cnonce = _exractParam(authReq,"cnonce=\""); + if(authReq.indexOf(FPSTR(qop_auth)) != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\"")); } MD5Builder md5; md5.begin(); - md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user + md5.add(String(username) + ':' + _realm + ':' + String(password)); // md5 of the user:realm:user md5.calculate(); String _H1 = md5.toString(); #ifdef DEBUG_ESP_HTTP_SERVER @@ -168,15 +178,15 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password #endif md5.begin(); if(_currentMethod == HTTP_GET){ - md5.add("GET:"+_uri); + md5.add(String(F("GET:")) + _uri); }else if(_currentMethod == HTTP_POST){ - md5.add("POST:"+_uri); + md5.add(String(F("POST:")) + _uri); }else if(_currentMethod == HTTP_PUT){ - md5.add("PUT:"+_uri); + md5.add(String(F("PUT:")) + _uri); }else if(_currentMethod == HTTP_DELETE){ - md5.add("DELETE:"+_uri); + md5.add(String(F("DELETE:")) + _uri); }else{ - md5.add("GET:"+_uri); + md5.add(String(F("GET:")) + _uri); } md5.calculate(); String _H2 = md5.toString(); @@ -184,49 +194,50 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); #endif md5.begin(); - if(authReq.indexOf("qop=auth") != -1){ - md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); + if(authReq.indexOf(FPSTR(qop_auth)) != -1) { + md5.add(_H1 + FPSTR(colon) + _nonce + FPSTR(colon) + _nc + FPSTR(colon) + _cnonce + ':auth:' + _H2); }else{ - md5.add(_H1+":"+_nonce+":"+_H2); + md5.add(_H1 + FPSTR(colon) + _nonce + FPSTR(colon) + _H2); } md5.calculate(); String _responsecheck = md5.toString(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); #endif - if(_response==_responsecheck){ - authReq = String(); + if(_response == _responsecheck){ + authReq = ""; return true; } } - authReq = String(); + authReq = ""; } return false; } -String ESP8266WebServer::_getRandomHexString(){ +String ESP8266WebServer::_getRandomHexString() { char buffer[33]; // buffer to hold 32 Hex Digit + /0 int i; - for(i=0;i<4;i++){ - sprintf (buffer+(i*8), "%08x", RANDOM_REG32); + for(i = 0; i < 4; i++) { + sprintf (buffer + (i*8), "%08x", RANDOM_REG32); } return String(buffer); } -void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg){ - if(realm==NULL){ - _srealm = "Login Required"; - }else{ +void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) { + if(realm == NULL) { + _srealm = String(F("Login Required")); + } else { _srealm = String(realm); } - if(mode==BASIC_AUTH){ - sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\""); - }else{ + if(mode == BASIC_AUTH) { + sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); + } else { _snonce=_getRandomHexString(); _sopaque=_getRandomHexString(); - sendHeader("WWW-Authenticate", "Digest realm=\"" +_srealm + "\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); + sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); } - send(401,"text/html",authFailMsg); + using namespace mime; + send(401, mimeTable[html].mimeType, authFailMsg); } void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) { @@ -327,6 +338,9 @@ void ESP8266WebServer::handleClient() { void ESP8266WebServer::close() { _server.close(); + _currentStatus = HC_NONE; + if(!_headerKeysCount) + collectHeaders(0, 0); } void ESP8266WebServer::stop() { @@ -335,7 +349,7 @@ void ESP8266WebServer::stop() { void ESP8266WebServer::sendHeader(const String& name, const String& value, bool first) { String headerLine = name; - headerLine += ": "; + headerLine += F(": "); headerLine += value; headerLine += "\r\n"; @@ -347,36 +361,37 @@ void ESP8266WebServer::sendHeader(const String& name, const String& value, bool } } -void ESP8266WebServer::setContentLength(size_t contentLength) { +void ESP8266WebServer::setContentLength(const size_t contentLength) { _contentLength = contentLength; } void ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { - response = "HTTP/1."+String(_currentVersion)+" "; + response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; response += String(code); - response += " "; + response += ' '; response += _responseCodeToString(code); response += "\r\n"; + using namespace mime; if (!content_type) - content_type = "text/html"; + content_type = mimeTable[html].mimeType; - sendHeader("Content-Type", content_type, true); + sendHeader(String(F("Content-Type")), content_type, true); if (_contentLength == CONTENT_LENGTH_NOT_SET) { - sendHeader("Content-Length", String(contentLength)); + sendHeader(String(FPSTR(Content_Length)), String(contentLength)); } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { - sendHeader("Content-Length", String(_contentLength)); + sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client //let's do chunked _chunked = true; - sendHeader("Accept-Ranges","none"); - sendHeader("Transfer-Encoding","chunked"); + sendHeader(String(F("Accept-Ranges")),String(F("none"))); + sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); } - sendHeader("Connection", "close"); + sendHeader(String(F("Connection")), String(F("close"))); response += _responseHeaders; response += "\r\n"; - _responseHeaders = String(); + _responseHeaders = ""; } void ESP8266WebServer::send(int code, const char* content_type, const String& content) { @@ -466,24 +481,37 @@ void ESP8266WebServer::sendContent_P(PGM_P content, size_t size) { } +void ESP8266WebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType) +{ + using namespace mime; + setContentLength(fileSize); + if (fileName.endsWith(mimeTable[gz].endsWith) && + contentType != mimeTable[gz].mimeType && + contentType != mimeTable[none].mimeType) { + sendHeader(F("Content-Encoding"), F("gzip")); + } + send(200, contentType, ""); +} + + String ESP8266WebServer::arg(String name) { for (int i = 0; i < _currentArgCount; ++i) { if ( _currentArgs[i].key == name ) return _currentArgs[i].value; } - return String(); + return ""; } String ESP8266WebServer::arg(int i) { if (i < _currentArgCount) return _currentArgs[i].value; - return String(); + return ""; } String ESP8266WebServer::argName(int i) { if (i < _currentArgCount) return _currentArgs[i].key; - return String(); + return ""; } int ESP8266WebServer::args() { @@ -504,7 +532,7 @@ String ESP8266WebServer::header(String name) { if (_currentHeaders[i].key.equalsIgnoreCase(name)) return _currentHeaders[i].value; } - return String(); + return ""; } void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { @@ -512,7 +540,7 @@ void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t hea if (_currentHeaders) delete[]_currentHeaders; _currentHeaders = new RequestArgument[_headerKeysCount]; - _currentHeaders[0].key = AUTHORIZATION_HEADER; + _currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER); for (int i = 1; i < _headerKeysCount; i++){ _currentHeaders[i].key = headerKeys[i-1]; } @@ -521,13 +549,13 @@ void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t hea String ESP8266WebServer::header(int i) { if (i < _headerKeysCount) return _currentHeaders[i].value; - return String(); + return ""; } String ESP8266WebServer::headerName(int i) { if (i < _headerKeysCount) return _currentHeaders[i].key; - return String(); + return ""; } int ESP8266WebServer::headers() { @@ -574,13 +602,14 @@ void ESP8266WebServer::_handleRequest() { handled = true; } if (!handled) { - send(404, "text/plain", String("Not found: ") + _currentUri); + using namespace mime; + send(404, mimeTable[html].mimeType, String(F("Not found: ")) + _currentUri); handled = true; } if (handled) { _finalizeResponse(); } - _currentUri = String(); + _currentUri = ""; } @@ -632,6 +661,6 @@ String ESP8266WebServer::_responseCodeToString(int code) { case 503: return F("Service Unavailable"); case 504: return F("Gateway Time-out"); case 505: return F("HTTP Version not supported"); - default: return ""; + default: return F(""); } } diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 6de40e07b7..0bfa302a4c 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -74,6 +74,7 @@ class ESP8266WebServer virtual ~ESP8266WebServer(); virtual void begin(); + virtual void begin(uint16_t port); virtual void handleClient(); virtual void close(); @@ -120,7 +121,7 @@ class ESP8266WebServer void send_P(int code, PGM_P content_type, PGM_P content); void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength); - void setContentLength(size_t contentLength); + void setContentLength(const size_t contentLength); void sendHeader(const String& name, const String& value, bool first = false); void sendContent(const String& content); void sendContent_P(PGM_P content); @@ -128,17 +129,12 @@ class ESP8266WebServer static String urlDecode(const String& text); -template size_t streamFile(T &file, const String& contentType){ - setContentLength(file.size()); - if (String(file.name()).endsWith(".gz") && - contentType != "application/x-gzip" && - contentType != "application/octet-stream"){ - sendHeader("Content-Encoding", "gzip"); + template + size_t streamFile(T &file, const String& contentType) { + _streamFileCore(file.size(), file.name(), contentType); + return _currentClient.write(file); } - send(200, contentType, ""); - return _currentClient.write(file); -} - + protected: virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient.write( b, l ); } virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient.write_P( b, l ); } @@ -154,10 +150,12 @@ template size_t streamFile(T &file, const String& contentType){ uint8_t _uploadReadByte(WiFiClient& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); - + + void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); + String _getRandomHexString(); // for extracting Auth parameters - String _exractParam(String& authReq,const String& param,const char delimit = '"'); + String _extractParam(String& authReq,const String& param,const char delimit = '"'); struct RequestArgument { String key; diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h index 692052359d..b1de4a6005 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h @@ -2,32 +2,9 @@ #define REQUESTHANDLERSIMPL_H #include "RequestHandler.h" +#include "mimetable.h" -// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules -static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] ICACHE_RODATA_ATTR = { - { ".html", "text/html" }, - { ".htm", "text/html" }, - { ".css", "text/css" }, - { ".txt", "text/plain" }, - { ".js", "application/javascript" }, - { ".json", "application/json" }, - { ".png", "image/png" }, - { ".gif", "image/gif" }, - { ".jpg", "image/jpeg" }, - { ".ico", "image/x-icon" }, - { ".svg", "image/svg+xml" }, - { ".ttf", "application/x-font-ttf" }, - { ".otf", "application/x-font-opentype" }, - { ".woff", "application/font-woff" }, - { ".woff2", "application/font-woff2" }, - { ".eot", "application/vnd.ms-fontobject" }, - { ".sfnt", "application/font-sfnt" }, - { ".xml", "text/xml" }, - { ".pdf", "application/pdf" }, - { ".zip", "application/zip" }, - { ".gz", "application/x-gzip" }, - { ".appcache", "text/cache-manifest" }, - { "", "application/octet-stream" } }; +using namespace mime; class FunctionRequestHandler : public RequestHandler { public: @@ -124,10 +101,10 @@ class StaticRequestHandler : public RequestHandler { // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... - if (!path.endsWith(".gz") && !_fs.exists(path)) { - String pathWithGz = path + ".gz"; + if (!path.endsWith(mimeTable[gz].endsWith) && !_fs.exists(path)) { + String pathWithGz = path + mimeTable[gz].endsWith; if(_fs.exists(pathWithGz)) - path += ".gz"; + path += mimeTable[gz].endsWith; } File f = _fs.open(path, "r"); diff --git a/libraries/ESP8266WebServer/src/detail/mimetable.cpp b/libraries/ESP8266WebServer/src/detail/mimetable.cpp new file mode 100644 index 0000000000..51a1f60bc7 --- /dev/null +++ b/libraries/ESP8266WebServer/src/detail/mimetable.cpp @@ -0,0 +1,35 @@ +#include "mimetable.h" +#include "pgmspace.h" + +namespace mime +{ + +// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules +const Entry mimeTable[maxType] ICACHE_RODATA_ATTR = +{ + { ".html", "text/html" }, + { ".htm", "text/html" }, + { ".css", "text/css" }, + { ".txt", "text/plain" }, + { ".js", "application/javascript" }, + { ".json", "application/json" }, + { ".png", "image/png" }, + { ".gif", "image/gif" }, + { ".jpg", "image/jpeg" }, + { ".ico", "image/x-icon" }, + { ".svg", "image/svg+xml" }, + { ".ttf", "application/x-font-ttf" }, + { ".otf", "application/x-font-opentype" }, + { ".woff", "application/font-woff" }, + { ".woff2", "application/font-woff2" }, + { ".eot", "application/vnd.ms-fontobject" }, + { ".sfnt", "application/font-sfnt" }, + { ".xml", "text/xml" }, + { ".pdf", "application/pdf" }, + { ".zip", "application/zip" }, + { ".gz", "application/x-gzip" }, + { ".appcache", "text/cache-manifest" }, + { "", "application/octet-stream" } +}; + +} diff --git a/libraries/ESP8266WebServer/src/detail/mimetable.h b/libraries/ESP8266WebServer/src/detail/mimetable.h new file mode 100644 index 0000000000..191356c489 --- /dev/null +++ b/libraries/ESP8266WebServer/src/detail/mimetable.h @@ -0,0 +1,47 @@ +#ifndef __MIMETABLE_H__ +#define __MIMETABLE_H__ + + +namespace mime +{ + +enum type +{ + html, + htm, + css, + txt, + js, + json, + png, + gif, + jpg, + ico, + svg, + ttf, + otf, + woff, + woff2, + eot, + sfnt, + xml, + pdf, + zip, + gz, + appcache, + none, + maxType +}; + +struct Entry +{ + const char endsWith[16]; + const char mimeType[32]; +}; + + +extern const Entry mimeTable[maxType]; +} + + +#endif