1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-25 20:02:37 +03:00

add begin(port) to esp8266webserver, move some strings to flash, some refactoring (#4148)

* add begin(port) to esp8266webserver, move some strings to flash, some refactoring

* Moved more strings to flash, unified some strings

* move mimetable strings into a standalone file

* more string moves to flash, remove duplicates, refactor of template method, minor styling

* Reverted moving small string to flash (no heap advantage, reduces bin size)
This commit is contained in:
Develo 2018-02-07 00:30:07 -03:00 committed by GitHub
parent 0339bbb11c
commit bcbd5961c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 125 deletions

View File

@ -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=\"");
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 = String();
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("");
}
}

View File

@ -74,6 +74,7 @@ public:
virtual ~ESP8266WebServer();
virtual void begin();
virtual void begin(uint16_t port);
virtual void handleClient();
virtual void close();
@ -120,7 +121,7 @@ public:
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 @@ public:
static String urlDecode(const String& text);
template<typename T> 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<typename T>
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 @@ protected:
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;

View File

@ -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 @@ public:
// 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");

View File

@ -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" }
};
}

View File

@ -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