[core] Add WebServer library from ESP32
This commit is contained in:
@@ -173,11 +173,13 @@ SoftwareSerial | ❌
|
||||
SPI | ❌
|
||||
Wire | ❌
|
||||
**OTHER LIBRARIES** |
|
||||
Wi-Fi Station | ✔️
|
||||
Wi-Fi Access Point | ✔️
|
||||
Wi-Fi Events | ❌
|
||||
Wi-Fi STA/AP/Mixed | ✔️
|
||||
Wi-Fi Client (SSL) | ✔️ (❌)
|
||||
Wi-Fi Server | ✔️
|
||||
Wi-Fi Events | ❌
|
||||
IPv6 | ❌
|
||||
HTTP Client (SSL) | ✔️ (❌)
|
||||
HTTP Server | ✔️
|
||||
NVS / Preferences | ❌
|
||||
SPIFFS | ❌
|
||||
BLE | -
|
||||
|
||||
56
arduino/libretuya/libraries/WebServer/HTTP_Method.h
Normal file
56
arduino/libretuya/libraries/WebServer/HTTP_Method.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* WebDAV */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
XX(16, BIND, BIND) \
|
||||
XX(17, REBIND, REBIND) \
|
||||
XX(18, UNBIND, UNBIND) \
|
||||
XX(19, ACL, ACL) \
|
||||
/* subversion */ \
|
||||
XX(20, REPORT, REPORT) \
|
||||
XX(21, MKACTIVITY, MKACTIVITY) \
|
||||
XX(22, CHECKOUT, CHECKOUT) \
|
||||
XX(23, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(24, MSEARCH, M - SEARCH) \
|
||||
XX(25, NOTIFY, NOTIFY) \
|
||||
XX(26, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(28, PATCH, PATCH) \
|
||||
XX(29, PURGE, PURGE) \
|
||||
/* CalDAV */ \
|
||||
XX(30, MKCALENDAR, MKCALENDAR) \
|
||||
/* RFC-2068, section 19.6.1.2 */ \
|
||||
XX(31, LINK, LINK) \
|
||||
XX(32, UNLINK, UNLINK) \
|
||||
/* icecast */ \
|
||||
XX(33, SOURCE, SOURCE)
|
||||
|
||||
enum http_method {
|
||||
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
typedef enum http_method HTTPMethod;
|
||||
#define HTTP_ANY (HTTPMethod)(255)
|
||||
605
arduino/libretuya/libraries/WebServer/Parsing.cpp
Normal file
605
arduino/libretuya/libraries/WebServer/Parsing.cpp
Normal file
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
Parsing.cpp - HTTP request parsing.
|
||||
|
||||
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "WebServer.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WiFiServer.h"
|
||||
#include "detail/mimetable.h"
|
||||
|
||||
#ifndef WEBSERVER_MAX_POST_ARGS
|
||||
#define WEBSERVER_MAX_POST_ARGS 32
|
||||
#endif
|
||||
|
||||
#define __STR(a) #a
|
||||
#define _STR(a) __STR(a)
|
||||
const char *_http_method_str[] = {
|
||||
#define XX(num, name, string) _STR(name),
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
static const char Content_Type[] PROGMEM = "Content-Type";
|
||||
static const char filename[] PROGMEM = "filename";
|
||||
|
||||
static char *readBytesWithTimeout(WiFiClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
|
||||
char *buf = nullptr;
|
||||
dataLength = 0;
|
||||
while (dataLength < maxLength) {
|
||||
int tries = timeout_ms;
|
||||
size_t newLength;
|
||||
while (!(newLength = client.available()) && tries--)
|
||||
delay(1);
|
||||
if (!newLength) {
|
||||
break;
|
||||
}
|
||||
if (!buf) {
|
||||
buf = (char *)malloc(newLength + 1);
|
||||
if (!buf) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
|
||||
if (!newBuf) {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
buf = newBuf;
|
||||
}
|
||||
client.readBytes(buf + dataLength, newLength);
|
||||
dataLength += newLength;
|
||||
buf[dataLength] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool WebServer::_parseRequest(WiFiClient &client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
// reset header value
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
_currentHeaders[i].value = String();
|
||||
}
|
||||
|
||||
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||
// Retrieve the "/path" part by finding the spaces
|
||||
int addr_start = req.indexOf(' ');
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
log_e("Invalid request: %s", req.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
String url = req.substring(addr_start + 1, addr_end);
|
||||
String versionEnd = req.substring(addr_end + 8);
|
||||
_currentVersion = atoi(versionEnd.c_str());
|
||||
String searchStr = "";
|
||||
int hasSearch = url.indexOf('?');
|
||||
if (hasSearch != -1) {
|
||||
searchStr = url.substring(hasSearch + 1);
|
||||
url = url.substring(0, hasSearch);
|
||||
}
|
||||
_currentUri = url;
|
||||
_chunked = false;
|
||||
|
||||
HTTPMethod method = HTTP_ANY;
|
||||
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||
for (size_t i = 0; i < num_methods; i++) {
|
||||
if (methodStr == _http_method_str[i]) {
|
||||
method = (HTTPMethod)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (method == HTTP_ANY) {
|
||||
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||
return false;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
||||
|
||||
// attach handler
|
||||
RequestHandler *handler;
|
||||
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||
if (handler->canHandle(_currentMethod, _currentUri))
|
||||
break;
|
||||
}
|
||||
_currentHandler = handler;
|
||||
|
||||
String formData;
|
||||
// below is needed only when POST type request
|
||||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
|
||||
String boundaryStr;
|
||||
String headerName;
|
||||
String headerValue;
|
||||
bool isForm = false;
|
||||
bool isEncoded = false;
|
||||
uint32_t contentLength = 0;
|
||||
// parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "")
|
||||
break; // no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 1);
|
||||
headerValue.trim();
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
using namespace mime;
|
||||
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
|
||||
isForm = false;
|
||||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
|
||||
isForm = false;
|
||||
isEncoded = true;
|
||||
} else if (headerValue.startsWith(F("multipart/"))) {
|
||||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
||||
boundaryStr.replace("\"", "");
|
||||
isForm = true;
|
||||
}
|
||||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
|
||||
contentLength = headerValue.toInt();
|
||||
} else if (headerName.equalsIgnoreCase(F("Host"))) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isForm) {
|
||||
size_t plainLength;
|
||||
char *plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if (plainLength < contentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
}
|
||||
if (contentLength > 0) {
|
||||
if (isEncoded) {
|
||||
// url encoded form
|
||||
if (searchStr != "")
|
||||
searchStr += '&';
|
||||
searchStr += plainBuf;
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
if (!isEncoded) {
|
||||
// plain post json or other data
|
||||
RequestArgument &arg = _currentArgs[_currentArgCount++];
|
||||
arg.key = F("plain");
|
||||
arg.value = String(plainBuf);
|
||||
}
|
||||
|
||||
log_v("Plain: %s", plainBuf);
|
||||
free(plainBuf);
|
||||
} else {
|
||||
// No content - but we can still have arguments in the URL.
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (isForm) {
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, contentLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String headerName;
|
||||
String headerValue;
|
||||
// parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "")
|
||||
break; // no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 2);
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
log_v("headerName: %s", headerName.c_str());
|
||||
log_v("headerValue: %s", headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase("Host")) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
client.flush();
|
||||
|
||||
log_v("Request: %s", url.c_str());
|
||||
log_v(" Arguments: %s", searchStr.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
|
||||
for (int i = 0; i < _headerKeysCount; i++) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||
_currentHeaders[i].value = headerValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::_parseArguments(String data) {
|
||||
log_v("args: %s", data.c_str());
|
||||
if (_currentArgs)
|
||||
delete[] _currentArgs;
|
||||
_currentArgs = 0;
|
||||
if (data.length() == 0) {
|
||||
_currentArgCount = 0;
|
||||
_currentArgs = new RequestArgument[1];
|
||||
return;
|
||||
}
|
||||
_currentArgCount = 1;
|
||||
|
||||
for (int i = 0; i < (int)data.length();) {
|
||||
i = data.indexOf('&', i);
|
||||
if (i == -1)
|
||||
break;
|
||||
++i;
|
||||
++_currentArgCount;
|
||||
}
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
|
||||
_currentArgs = new RequestArgument[_currentArgCount + 1];
|
||||
int pos = 0;
|
||||
int iarg;
|
||||
for (iarg = 0; iarg < _currentArgCount;) {
|
||||
int equal_sign_index = data.indexOf('=', pos);
|
||||
int next_arg_index = data.indexOf('&', pos);
|
||||
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||
log_e("arg missing value: %d", iarg);
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
continue;
|
||||
}
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
||||
++iarg;
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
}
|
||||
|
||||
void WebServer::_uploadWriteByte(uint8_t b) {
|
||||
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->currentSize = 0;
|
||||
}
|
||||
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||
}
|
||||
|
||||
int WebServer::_uploadReadByte(WiFiClient &client) {
|
||||
int res = client.read();
|
||||
if (res < 0) {
|
||||
// keep trying until you either read a valid byte or timeout
|
||||
unsigned long startMillis = millis();
|
||||
long timeoutIntervalMillis = client.getTimeout();
|
||||
boolean timedOut = false;
|
||||
for (;;) {
|
||||
if (!client.connected())
|
||||
return -1;
|
||||
// loosely modeled after blinkWithoutDelay pattern
|
||||
while (!timedOut && !client.available() && client.connected()) {
|
||||
delay(2);
|
||||
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||
}
|
||||
|
||||
res = client.read();
|
||||
if (res >= 0) {
|
||||
return res; // exit on a valid read
|
||||
}
|
||||
// NOTE: it is possible to get here and have all of the following
|
||||
// assertions hold true
|
||||
//
|
||||
// -- client.available() > 0
|
||||
// -- client.connected == true
|
||||
// -- res == -1
|
||||
//
|
||||
// a simple retry strategy overcomes this which is to say the
|
||||
// assertion is not permanent, but the reason that this works
|
||||
// is elusive, and possibly indicative of a more subtle underlying
|
||||
// issue
|
||||
|
||||
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||
if (timedOut) {
|
||||
return res; // exit on a timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool WebServer::_parseForm(WiFiClient &client, String boundary, uint32_t len) {
|
||||
(void)len;
|
||||
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
|
||||
String line;
|
||||
int retry = 0;
|
||||
do {
|
||||
line = client.readStringUntil('\r');
|
||||
++retry;
|
||||
} while (line.length() == 0 && retry < 3);
|
||||
|
||||
client.readStringUntil('\n');
|
||||
// start reading the form
|
||||
if (line == ("--" + boundary)) {
|
||||
if (_postArgs)
|
||||
delete[] _postArgs;
|
||||
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
||||
_postArgsLen = 0;
|
||||
while (1) {
|
||||
String argName;
|
||||
String argValue;
|
||||
String argType;
|
||||
String argFilename;
|
||||
bool argIsFile = false;
|
||||
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
|
||||
int nameStart = line.indexOf('=');
|
||||
if (nameStart != -1) {
|
||||
argName = line.substring(nameStart + 2);
|
||||
nameStart = argName.indexOf('=');
|
||||
if (nameStart == -1) {
|
||||
argName = argName.substring(0, argName.length() - 1);
|
||||
} else {
|
||||
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
|
||||
argName = argName.substring(0, argName.indexOf('"'));
|
||||
argIsFile = true;
|
||||
log_v("PostArg FileName: %s", argFilename.c_str());
|
||||
// use GET to set the filename if uploading using blob
|
||||
if (argFilename == F("blob") && hasArg(FPSTR(filename)))
|
||||
argFilename = arg(FPSTR(filename));
|
||||
}
|
||||
log_v("PostArg Name: %s", argName.c_str());
|
||||
using namespace mime;
|
||||
argType = FPSTR(mimeTable[txt].mimeType);
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
argType = line.substring(line.indexOf(':') + 2);
|
||||
// skip next line
|
||||
client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
}
|
||||
log_v("PostArg Type: %s", argType.c_str());
|
||||
if (!argIsFile) {
|
||||
while (1) {
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.startsWith("--" + boundary))
|
||||
break;
|
||||
if (argValue.length() > 0)
|
||||
argValue += "\n";
|
||||
argValue += line;
|
||||
}
|
||||
log_v("PostArg Value: %s", argValue.c_str());
|
||||
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = argName;
|
||||
arg.value = argValue;
|
||||
|
||||
if (line == ("--" + boundary + "--")) {
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
||||
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_currentUpload.reset(new HTTPUpload());
|
||||
_currentUpload->status = UPLOAD_FILE_START;
|
||||
_currentUpload->name = argName;
|
||||
_currentUpload->filename = argFilename;
|
||||
_currentUpload->type = argType;
|
||||
_currentUpload->totalSize = 0;
|
||||
_currentUpload->currentSize = 0;
|
||||
log_v(
|
||||
"Start File: %s Type: %s",
|
||||
_currentUpload->filename.c_str(),
|
||||
_currentUpload->type.c_str()
|
||||
);
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||
int argByte = _uploadReadByte(client);
|
||||
readfile:
|
||||
|
||||
while (argByte != 0x0D) {
|
||||
if (argByte < 0)
|
||||
return _parseFormUploadAborted();
|
||||
_uploadWriteByte(argByte);
|
||||
argByte = _uploadReadByte(client);
|
||||
}
|
||||
|
||||
argByte = _uploadReadByte(client);
|
||||
if (argByte < 0)
|
||||
return _parseFormUploadAborted();
|
||||
if (argByte == 0x0A) {
|
||||
argByte = _uploadReadByte(client);
|
||||
if (argByte < 0)
|
||||
return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-') {
|
||||
// continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
goto readfile;
|
||||
} else {
|
||||
argByte = _uploadReadByte(client);
|
||||
if (argByte < 0)
|
||||
return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-') {
|
||||
// continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
goto readfile;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t endBuf[boundary.length()];
|
||||
uint32_t i = 0;
|
||||
while (i < boundary.length()) {
|
||||
argByte = _uploadReadByte(client);
|
||||
if (argByte < 0)
|
||||
return _parseFormUploadAborted();
|
||||
if ((char)argByte == 0x0D) {
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t j = 0;
|
||||
while (j < i) {
|
||||
_uploadWriteByte(endBuf[j++]);
|
||||
}
|
||||
goto readfile;
|
||||
}
|
||||
endBuf[i++] = (uint8_t)argByte;
|
||||
}
|
||||
|
||||
if (strstr((const char *)endBuf, boundary.c_str()) != NULL) {
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
log_v(
|
||||
"End File: %s Type: %s Size: %d",
|
||||
_currentUpload->filename.c_str(),
|
||||
_currentUpload->type.c_str(),
|
||||
_currentUpload->totalSize
|
||||
);
|
||||
line = client.readStringUntil(0x0D);
|
||||
client.readStringUntil(0x0A);
|
||||
if (line == "--") {
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t i = 0;
|
||||
while (i < boundary.length()) {
|
||||
_uploadWriteByte(endBuf[i++]);
|
||||
}
|
||||
argByte = _uploadReadByte(client);
|
||||
goto readfile;
|
||||
}
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
goto readfile;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int iarg;
|
||||
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)
|
||||
? (WEBSERVER_MAX_POST_ARGS - _postArgsLen)
|
||||
: _currentArgCount;
|
||||
for (iarg = 0; iarg < totalArgs; iarg++) {
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = _currentArgs[iarg].key;
|
||||
arg.value = _currentArgs[iarg].value;
|
||||
}
|
||||
if (_currentArgs)
|
||||
delete[] _currentArgs;
|
||||
_currentArgs = new RequestArgument[_postArgsLen];
|
||||
for (iarg = 0; iarg < _postArgsLen; iarg++) {
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = _postArgs[iarg].key;
|
||||
arg.value = _postArgs[iarg].value;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
_postArgs = nullptr;
|
||||
_postArgsLen = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
log_e("Error: line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::urlDecode(const String &text) {
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len) {
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
} else {
|
||||
if (encodedChar == '+') {
|
||||
decodedChar = ' ';
|
||||
} else {
|
||||
decodedChar = encodedChar; // normal ascii char
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool WebServer::_parseFormUploadAborted() {
|
||||
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
return false;
|
||||
}
|
||||
29
arduino/libretuya/libraries/WebServer/Uri.h
Normal file
29
arduino/libretuya/libraries/WebServer/Uri.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
class Uri {
|
||||
|
||||
protected:
|
||||
const String _uri;
|
||||
|
||||
public:
|
||||
Uri(const char *uri) : _uri(uri) {}
|
||||
|
||||
Uri(const String &uri) : _uri(uri) {}
|
||||
|
||||
Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {}
|
||||
|
||||
virtual ~Uri() {}
|
||||
|
||||
virtual Uri *clone() const {
|
||||
return new Uri(_uri);
|
||||
};
|
||||
|
||||
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
|
||||
|
||||
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
|
||||
return _uri == requestUri;
|
||||
}
|
||||
};
|
||||
717
arduino/libretuya/libraries/WebServer/WebServer.cpp
Normal file
717
arduino/libretuya/libraries/WebServer/WebServer.cpp
Normal file
@@ -0,0 +1,717 @@
|
||||
/*
|
||||
WebServer.cpp - Dead simple web-server.
|
||||
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "FS.h"
|
||||
#include "WebServer.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WiFiServer.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
// #include "mbedtls/md5.h"
|
||||
#include <libb64/cencode.h>
|
||||
|
||||
static const char AUTHORIZATION_HEADER[] = "Authorization";
|
||||
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||
static const char WWW_Authenticate[] = "WWW-Authenticate";
|
||||
static const char Content_Length[] = "Content-Length";
|
||||
|
||||
WebServer::WebServer(IPAddress addr, int port)
|
||||
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
|
||||
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
|
||||
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
|
||||
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
|
||||
log_v("WebServer::Webserver(addr=%s, port=%d)", addr.toString().c_str(), port);
|
||||
}
|
||||
|
||||
WebServer::WebServer(int port)
|
||||
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
|
||||
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
|
||||
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
|
||||
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
|
||||
log_v("WebServer::Webserver(port=%d)", port);
|
||||
}
|
||||
|
||||
WebServer::~WebServer() {
|
||||
_server.close();
|
||||
if (_currentHeaders)
|
||||
delete[] _currentHeaders;
|
||||
RequestHandler *handler = _firstHandler;
|
||||
while (handler) {
|
||||
RequestHandler *next = handler->next();
|
||||
delete handler;
|
||||
handler = next;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::begin() {
|
||||
close();
|
||||
_server.begin();
|
||||
_server.setNoDelay(true);
|
||||
}
|
||||
|
||||
void WebServer::begin(uint16_t port) {
|
||||
close();
|
||||
_server.begin(port);
|
||||
_server.setNoDelay(true);
|
||||
}
|
||||
|
||||
String WebServer::_extractParam(String &authReq, const String ¶m, const char delimit) {
|
||||
int _begin = authReq.indexOf(param);
|
||||
if (_begin == -1)
|
||||
return "";
|
||||
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||
}
|
||||
|
||||
static String md5str(String &in) {
|
||||
/* char out[33] = {0};
|
||||
mbedtls_md5_context _ctx;
|
||||
uint8_t i;
|
||||
uint8_t *_buf = (uint8_t *)malloc(16);
|
||||
if (_buf == NULL)
|
||||
return String(out);
|
||||
memset(_buf, 0x00, 16);
|
||||
mbedtls_md5_init(&_ctx);
|
||||
mbedtls_md5_starts_ret(&_ctx);
|
||||
mbedtls_md5_update_ret(&_ctx, (const uint8_t *)in.c_str(), in.length());
|
||||
mbedtls_md5_finish_ret(&_ctx, _buf);
|
||||
for (i = 0; i < 16; i++) {
|
||||
sprintf(out + (i * 2), "%02x", _buf[i]);
|
||||
}
|
||||
out[32] = 0;
|
||||
free(_buf);
|
||||
return String(out); */
|
||||
return "";
|
||||
}
|
||||
|
||||
bool WebServer::authenticate(const char *username, const char *password) {
|
||||
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 = "";
|
||||
return false;
|
||||
}
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
if (encoded == NULL) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
} else if (authReq.startsWith(F("Digest"))) {
|
||||
authReq = authReq.substring(7);
|
||||
log_v("%s", authReq.c_str());
|
||||
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 = _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 = "";
|
||||
return false;
|
||||
}
|
||||
// parameters for the RFC 2617 newer Digest
|
||||
String _nc, _cnonce;
|
||||
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_nc = _extractParam(authReq, F("nc="), ',');
|
||||
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
|
||||
}
|
||||
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||
log_v("Hash of user:realm:pass=%s", _H1.c_str());
|
||||
String _H2 = "";
|
||||
if (_currentMethod == HTTP_GET) {
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_POST) {
|
||||
_H2 = md5str(String(F("POST:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_PUT) {
|
||||
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||
} else if (_currentMethod == HTTP_DELETE) {
|
||||
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||
} else {
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}
|
||||
log_v("Hash of GET:uri=%s", _H2.c_str());
|
||||
String _responsecheck = "";
|
||||
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||
} else {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||
}
|
||||
log_v("The Proper response=%s", _responsecheck.c_str());
|
||||
if (_response == _responsecheck) {
|
||||
authReq = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
authReq = "";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::_getRandomHexString() {
|
||||
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
sprintf(buffer + (i * 8), "%08x", rand());
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
void WebServer::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(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
|
||||
} else {
|
||||
_snonce = _getRandomHexString();
|
||||
_sopaque = _getRandomHexString();
|
||||
sendHeader(
|
||||
String(FPSTR(WWW_Authenticate)),
|
||||
String(F("Digest realm=\"")) + _srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce +
|
||||
String(F("\", opaque=\"")) + _sopaque + String(F("\""))
|
||||
);
|
||||
}
|
||||
using namespace mime;
|
||||
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
|
||||
on(uri, HTTP_ANY, handler);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
|
||||
on(uri, method, fn, _fileUploadHandler);
|
||||
}
|
||||
|
||||
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
|
||||
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
||||
}
|
||||
|
||||
void WebServer::addHandler(RequestHandler *handler) {
|
||||
_addRequestHandler(handler);
|
||||
}
|
||||
|
||||
void WebServer::_addRequestHandler(RequestHandler *handler) {
|
||||
if (!_lastHandler) {
|
||||
_firstHandler = handler;
|
||||
_lastHandler = handler;
|
||||
} else {
|
||||
_lastHandler->next(handler);
|
||||
_lastHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::serveStatic(const char *uri, FS &fs, const char *path, const char *cache_header) {
|
||||
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||
}
|
||||
|
||||
void WebServer::handleClient() {
|
||||
if (_currentStatus == HC_NONE) {
|
||||
WiFiClient client = _server.available();
|
||||
if (!client) {
|
||||
if (_nullDelay) {
|
||||
delay(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log_v("New client: client.localIP()=%s", client.localIP().toString().c_str());
|
||||
|
||||
_currentClient = client;
|
||||
_currentStatus = HC_WAIT_READ;
|
||||
_statusChange = millis();
|
||||
}
|
||||
|
||||
bool keepCurrentClient = false;
|
||||
bool callYield = false;
|
||||
|
||||
if (_currentClient.connected()) {
|
||||
switch (_currentStatus) {
|
||||
case HC_NONE:
|
||||
// No-op to avoid C++ compiler warning
|
||||
break;
|
||||
case HC_WAIT_READ:
|
||||
// Wait for data from client to become available
|
||||
if (_currentClient.available()) {
|
||||
if (_parseRequest(_currentClient)) {
|
||||
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
|
||||
// it must be divided by 1000
|
||||
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
|
||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||
_handleRequest();
|
||||
|
||||
// Fix for issue with Chrome based browsers:
|
||||
// https://github.com/espressif/arduino-esp32/issues/3652
|
||||
// if (_currentClient.connected()) {
|
||||
// _currentStatus = HC_WAIT_CLOSE;
|
||||
// _statusChange = millis();
|
||||
// keepCurrentClient = true;
|
||||
// }
|
||||
}
|
||||
} else { // !_currentClient.available()
|
||||
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
}
|
||||
callYield = true;
|
||||
}
|
||||
break;
|
||||
case HC_WAIT_CLOSE:
|
||||
// Wait for client to close the connection
|
||||
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
callYield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepCurrentClient) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
_currentUpload.reset();
|
||||
}
|
||||
|
||||
if (callYield) {
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::close() {
|
||||
_server.close();
|
||||
_currentStatus = HC_NONE;
|
||||
if (!_headerKeysCount)
|
||||
collectHeaders(0, 0);
|
||||
}
|
||||
|
||||
void WebServer::stop() {
|
||||
close();
|
||||
}
|
||||
|
||||
void WebServer::sendHeader(const String &name, const String &value, bool first) {
|
||||
String headerLine = name;
|
||||
headerLine += F(": ");
|
||||
headerLine += value;
|
||||
headerLine += "\r\n";
|
||||
|
||||
if (first) {
|
||||
_responseHeaders = headerLine + _responseHeaders;
|
||||
} else {
|
||||
_responseHeaders += headerLine;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::setContentLength(const size_t contentLength) {
|
||||
_contentLength = contentLength;
|
||||
}
|
||||
|
||||
void WebServer::enableDelay(boolean value) {
|
||||
_nullDelay = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCORS(boolean value) {
|
||||
_corsEnabled = value;
|
||||
}
|
||||
|
||||
void WebServer::enableCrossOrigin(boolean value) {
|
||||
enableCORS(value);
|
||||
}
|
||||
|
||||
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
|
||||
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
|
||||
response += String(code);
|
||||
response += ' ';
|
||||
response += _responseCodeToString(code);
|
||||
response += "\r\n";
|
||||
|
||||
using namespace mime;
|
||||
if (!content_type)
|
||||
content_type = mimeTable[html].mimeType;
|
||||
|
||||
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
|
||||
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
|
||||
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||
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(String(F("Accept-Ranges")), String(F("none")));
|
||||
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
|
||||
}
|
||||
if (_corsEnabled) {
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
|
||||
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
|
||||
}
|
||||
sendHeader(String(F("Connection")), String(F("close")));
|
||||
|
||||
response += _responseHeaders;
|
||||
response += "\r\n";
|
||||
_responseHeaders = "";
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const char *content_type, const String &content) {
|
||||
String header;
|
||||
// Can we asume the following?
|
||||
// if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
_prepareHeader(header, code, content_type, content.length());
|
||||
_currentClientWrite(header.c_str(), header.length());
|
||||
if (content.length())
|
||||
sendContent(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
size_t contentLength = 0;
|
||||
|
||||
if (content != NULL) {
|
||||
contentLength = strlen_P(content);
|
||||
}
|
||||
|
||||
String header;
|
||||
char type[64];
|
||||
strncpy_P(type, (PGM_P)content_type, sizeof(type));
|
||||
_prepareHeader(header, code, (const char *)type, contentLength);
|
||||
_currentClientWrite(header.c_str(), header.length());
|
||||
sendContent_P(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||
String header;
|
||||
char type[64];
|
||||
strncpy_P(type, (PGM_P)content_type, sizeof(type));
|
||||
_prepareHeader(header, code, (const char *)type, contentLength);
|
||||
sendContent(header);
|
||||
sendContent_P(content, contentLength);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, char *content_type, const String &content) {
|
||||
send(code, (const char *)content_type, content);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const String &content_type, const String &content) {
|
||||
send(code, (const char *)content_type.c_str(), content);
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const String &content) {
|
||||
sendContent(content.c_str(), content.length());
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const char *content, size_t contentLength) {
|
||||
const char *footer = "\r\n";
|
||||
if (_chunked) {
|
||||
char *chunkSize = (char *)malloc(11);
|
||||
if (chunkSize) {
|
||||
sprintf(chunkSize, "%x%s", contentLength, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite(content, contentLength);
|
||||
if (_chunked) {
|
||||
_currentClient.write(footer, 2);
|
||||
if (contentLength == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content) {
|
||||
sendContent_P(content, strlen_P(content));
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content, size_t size) {
|
||||
const char *footer = "\r\n";
|
||||
if (_chunked) {
|
||||
char *chunkSize = (char *)malloc(11);
|
||||
if (chunkSize) {
|
||||
sprintf(chunkSize, "%x%s", size, footer);
|
||||
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClientWrite_P(content, size);
|
||||
if (_chunked) {
|
||||
_currentClient.write(footer, 2);
|
||||
if (size == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType) {
|
||||
using namespace mime;
|
||||
setContentLength(fileSize);
|
||||
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
|
||||
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
|
||||
contentType != String(FPSTR(mimeTable[none].mimeType))) {
|
||||
sendHeader(F("Content-Encoding"), F("gzip"));
|
||||
}
|
||||
send(200, contentType, "");
|
||||
}
|
||||
|
||||
String WebServer::pathArg(unsigned int i) {
|
||||
if (_currentHandler != nullptr)
|
||||
return _currentHandler->pathArg(i);
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if (_postArgs[j].key == name)
|
||||
return _postArgs[j].value;
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name)
|
||||
return _currentArgs[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::arg(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].value;
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::argName(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].key;
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::args() {
|
||||
return _currentArgCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasArg(String name) {
|
||||
for (int j = 0; j < _postArgsLen; ++j) {
|
||||
if (_postArgs[j].key == name)
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::header(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(name))
|
||||
return _currentHeaders[i].value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
|
||||
_headerKeysCount = headerKeysCount + 1;
|
||||
if (_currentHeaders)
|
||||
delete[] _currentHeaders;
|
||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||
for (int i = 1; i < _headerKeysCount; i++) {
|
||||
_currentHeaders[i].key = headerKeys[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::header(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].value;
|
||||
return "";
|
||||
}
|
||||
|
||||
String WebServer::headerName(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].key;
|
||||
return "";
|
||||
}
|
||||
|
||||
int WebServer::headers() {
|
||||
return _headerKeysCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasHeader(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::hostHeader() {
|
||||
return _hostHeader;
|
||||
}
|
||||
|
||||
void WebServer::onFileUpload(THandlerFunction fn) {
|
||||
_fileUploadHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::onNotFound(THandlerFunction fn) {
|
||||
_notFoundHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::_handleRequest() {
|
||||
bool handled = false;
|
||||
if (!_currentHandler) {
|
||||
log_e("request handler not found");
|
||||
} else {
|
||||
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||
if (!handled) {
|
||||
log_e("request handler failed to handle request");
|
||||
}
|
||||
}
|
||||
if (!handled && _notFoundHandler) {
|
||||
_notFoundHandler();
|
||||
handled = true;
|
||||
}
|
||||
if (!handled) {
|
||||
using namespace mime;
|
||||
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
_finalizeResponse();
|
||||
}
|
||||
_currentUri = "";
|
||||
}
|
||||
|
||||
void WebServer::_finalizeResponse() {
|
||||
if (_chunked) {
|
||||
sendContent("");
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::_responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100:
|
||||
return F("Continue");
|
||||
case 101:
|
||||
return F("Switching Protocols");
|
||||
case 200:
|
||||
return F("OK");
|
||||
case 201:
|
||||
return F("Created");
|
||||
case 202:
|
||||
return F("Accepted");
|
||||
case 203:
|
||||
return F("Non-Authoritative Information");
|
||||
case 204:
|
||||
return F("No Content");
|
||||
case 205:
|
||||
return F("Reset Content");
|
||||
case 206:
|
||||
return F("Partial Content");
|
||||
case 300:
|
||||
return F("Multiple Choices");
|
||||
case 301:
|
||||
return F("Moved Permanently");
|
||||
case 302:
|
||||
return F("Found");
|
||||
case 303:
|
||||
return F("See Other");
|
||||
case 304:
|
||||
return F("Not Modified");
|
||||
case 305:
|
||||
return F("Use Proxy");
|
||||
case 307:
|
||||
return F("Temporary Redirect");
|
||||
case 400:
|
||||
return F("Bad Request");
|
||||
case 401:
|
||||
return F("Unauthorized");
|
||||
case 402:
|
||||
return F("Payment Required");
|
||||
case 403:
|
||||
return F("Forbidden");
|
||||
case 404:
|
||||
return F("Not Found");
|
||||
case 405:
|
||||
return F("Method Not Allowed");
|
||||
case 406:
|
||||
return F("Not Acceptable");
|
||||
case 407:
|
||||
return F("Proxy Authentication Required");
|
||||
case 408:
|
||||
return F("Request Time-out");
|
||||
case 409:
|
||||
return F("Conflict");
|
||||
case 410:
|
||||
return F("Gone");
|
||||
case 411:
|
||||
return F("Length Required");
|
||||
case 412:
|
||||
return F("Precondition Failed");
|
||||
case 413:
|
||||
return F("Request Entity Too Large");
|
||||
case 414:
|
||||
return F("Request-URI Too Large");
|
||||
case 415:
|
||||
return F("Unsupported Media Type");
|
||||
case 416:
|
||||
return F("Requested range not satisfiable");
|
||||
case 417:
|
||||
return F("Expectation Failed");
|
||||
case 500:
|
||||
return F("Internal Server Error");
|
||||
case 501:
|
||||
return F("Not Implemented");
|
||||
case 502:
|
||||
return F("Bad Gateway");
|
||||
case 503:
|
||||
return F("Service Unavailable");
|
||||
case 504:
|
||||
return F("Gateway Time-out");
|
||||
case 505:
|
||||
return F("HTTP Version not supported");
|
||||
default:
|
||||
return F("");
|
||||
}
|
||||
}
|
||||
224
arduino/libretuya/libraries/WebServer/WebServer.h
Normal file
224
arduino/libretuya/libraries/WebServer/WebServer.h
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
WebServer.h - Dead simple web-server.
|
||||
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HTTP_Method.h"
|
||||
#include "Uri.h"
|
||||
#include <WiFi.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED };
|
||||
|
||||
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
|
||||
|
||||
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||
|
||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||
|
||||
#ifndef HTTP_UPLOAD_BUFLEN
|
||||
#define HTTP_UPLOAD_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_DATA_WAIT 5000 // ms to wait for the client to send the request
|
||||
#define HTTP_MAX_POST_WAIT 5000 // ms to wait for POST data to arrive
|
||||
#define HTTP_MAX_SEND_WAIT 5000 // ms to wait for data chunk to be ACKed
|
||||
#define HTTP_MAX_CLOSE_WAIT 2000 // ms to wait for the client to close the connection
|
||||
|
||||
#define CONTENT_LENGTH_UNKNOWN ((size_t)-1)
|
||||
#define CONTENT_LENGTH_NOT_SET ((size_t)-2)
|
||||
|
||||
class WebServer;
|
||||
|
||||
typedef struct {
|
||||
HTTPUploadStatus status;
|
||||
String filename;
|
||||
String name;
|
||||
String type;
|
||||
size_t totalSize; // file size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||
} HTTPUpload;
|
||||
|
||||
#include "detail/RequestHandler.h"
|
||||
|
||||
namespace fs {
|
||||
class FS;
|
||||
}
|
||||
|
||||
class WebServer {
|
||||
public:
|
||||
WebServer(IPAddress addr, int port = 80);
|
||||
WebServer(int port = 80);
|
||||
virtual ~WebServer();
|
||||
|
||||
virtual void begin();
|
||||
virtual void begin(uint16_t port);
|
||||
virtual void handleClient();
|
||||
|
||||
virtual void close();
|
||||
void stop();
|
||||
|
||||
bool authenticate(const char *username, const char *password);
|
||||
void requestAuthentication(
|
||||
HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String("")
|
||||
);
|
||||
|
||||
typedef std::function<void(void)> THandlerFunction;
|
||||
void on(const Uri &uri, THandlerFunction fn);
|
||||
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); // ufn handles file uploads
|
||||
void addHandler(RequestHandler *handler);
|
||||
void serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_header = NULL);
|
||||
void onNotFound(THandlerFunction fn); // called when handler is not assigned
|
||||
void onFileUpload(THandlerFunction ufn); // handle file uploads
|
||||
|
||||
String uri() {
|
||||
return _currentUri;
|
||||
}
|
||||
|
||||
HTTPMethod method() {
|
||||
return _currentMethod;
|
||||
}
|
||||
|
||||
virtual WiFiClient client() {
|
||||
return _currentClient;
|
||||
}
|
||||
|
||||
HTTPUpload &upload() {
|
||||
return *_currentUpload;
|
||||
}
|
||||
|
||||
String pathArg(unsigned int i); // get request path argument by number
|
||||
String arg(String name); // get request argument value by name
|
||||
String arg(int i); // get request argument value by number
|
||||
String argName(int i); // get request argument name by number
|
||||
int args(); // get arguments count
|
||||
bool hasArg(String name); // check if argument exists
|
||||
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||
String header(String name); // get request header value by name
|
||||
String header(int i); // get request header value by number
|
||||
String headerName(int i); // get request header name by number
|
||||
int headers(); // get header count
|
||||
bool hasHeader(String name); // check if header exists
|
||||
|
||||
String hostHeader(); // get request host header if available or empty String if not
|
||||
|
||||
// send response to the client
|
||||
// code - HTTP response code, can be 200 or 404
|
||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||
// content - actual content body
|
||||
void send(int code, const char *content_type = NULL, const String &content = String(""));
|
||||
void send(int code, char *content_type, const String &content);
|
||||
void send(int code, const String &content_type, const String &content);
|
||||
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 enableDelay(boolean value);
|
||||
void enableCORS(boolean value = true);
|
||||
void enableCrossOrigin(boolean value = true);
|
||||
|
||||
void setContentLength(const size_t contentLength);
|
||||
void sendHeader(const String &name, const String &value, bool first = false);
|
||||
void sendContent(const String &content);
|
||||
void sendContent(const char *content, size_t contentLength);
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
|
||||
static String urlDecode(const String &text);
|
||||
|
||||
template <typename T>
|
||||
size_t streamFile(T &file, const String &contentType) {
|
||||
_streamFileCore(file.size(), file.name(), 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);
|
||||
}
|
||||
|
||||
void _addRequestHandler(RequestHandler *handler);
|
||||
void _handleRequest();
|
||||
void _finalizeResponse();
|
||||
bool _parseRequest(WiFiClient &client);
|
||||
void _parseArguments(String data);
|
||||
static String _responseCodeToString(int code);
|
||||
bool _parseForm(WiFiClient &client, String boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
int _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 _extractParam(String &authReq, const String ¶m, const char delimit = '"');
|
||||
|
||||
struct RequestArgument {
|
||||
String key;
|
||||
String value;
|
||||
};
|
||||
|
||||
boolean _corsEnabled;
|
||||
WiFiServer _server;
|
||||
|
||||
WiFiClient _currentClient;
|
||||
HTTPMethod _currentMethod;
|
||||
String _currentUri;
|
||||
uint8_t _currentVersion;
|
||||
HTTPClientStatus _currentStatus;
|
||||
unsigned long _statusChange;
|
||||
boolean _nullDelay;
|
||||
|
||||
RequestHandler *_currentHandler;
|
||||
RequestHandler *_firstHandler;
|
||||
RequestHandler *_lastHandler;
|
||||
THandlerFunction _notFoundHandler;
|
||||
THandlerFunction _fileUploadHandler;
|
||||
|
||||
int _currentArgCount;
|
||||
RequestArgument *_currentArgs;
|
||||
int _postArgsLen;
|
||||
RequestArgument *_postArgs;
|
||||
|
||||
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||
|
||||
int _headerKeysCount;
|
||||
RequestArgument *_currentHeaders;
|
||||
size_t _contentLength;
|
||||
String _responseHeaders;
|
||||
|
||||
String _hostHeader;
|
||||
bool _chunked;
|
||||
|
||||
String _snonce; // Store noance and opaque for future comparison
|
||||
String _sopaque;
|
||||
String _srealm; // Store the Auth realm between Calls
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
virtual ~RequestHandler() {}
|
||||
|
||||
virtual bool canHandle(HTTPMethod method, String uri) {
|
||||
(void)method;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool canUpload(String uri) {
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) {
|
||||
(void)server;
|
||||
(void)requestMethod;
|
||||
(void)requestUri;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload) {
|
||||
(void)server;
|
||||
(void)requestUri;
|
||||
(void)upload;
|
||||
}
|
||||
|
||||
RequestHandler *next() {
|
||||
return _next;
|
||||
}
|
||||
|
||||
void next(RequestHandler *r) {
|
||||
_next = r;
|
||||
}
|
||||
|
||||
private:
|
||||
RequestHandler *_next = nullptr;
|
||||
|
||||
protected:
|
||||
std::vector<String> pathArgs;
|
||||
|
||||
public:
|
||||
const String &pathArg(unsigned int i) {
|
||||
assert(i < pathArgs.size());
|
||||
return pathArgs[i];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include "RequestHandler.h"
|
||||
#include "Uri.h"
|
||||
#include "WString.h"
|
||||
#include "mimetable.h"
|
||||
|
||||
using namespace mime;
|
||||
|
||||
class FunctionRequestHandler : public RequestHandler {
|
||||
public:
|
||||
FunctionRequestHandler(
|
||||
WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method
|
||||
)
|
||||
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
|
||||
_uri->initPathArgs(pathArgs);
|
||||
}
|
||||
|
||||
~FunctionRequestHandler() {
|
||||
delete _uri;
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod)
|
||||
return false;
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs);
|
||||
}
|
||||
|
||||
bool canUpload(String requestUri) override {
|
||||
if (!_ufn || !canHandle(HTTP_POST, requestUri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
(void)server;
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
_fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
void upload(WebServer &server, String requestUri, HTTPUpload &upload) override {
|
||||
(void)server;
|
||||
(void)upload;
|
||||
if (canUpload(requestUri))
|
||||
_ufn();
|
||||
}
|
||||
|
||||
protected:
|
||||
WebServer::THandlerFunction _fn;
|
||||
WebServer::THandlerFunction _ufn;
|
||||
Uri *_uri;
|
||||
HTTPMethod _method;
|
||||
};
|
||||
|
||||
class StaticRequestHandler : public RequestHandler {
|
||||
public:
|
||||
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header)
|
||||
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
|
||||
File f = fs.open(path);
|
||||
_isFile = (f && (!f.isDirectory()));
|
||||
log_v(
|
||||
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n",
|
||||
path,
|
||||
uri,
|
||||
_isFile,
|
||||
cache_header ? cache_header : ""
|
||||
); // issue 5506 - cache_header can be nullptr
|
||||
_baseUriLength = _uri.length();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (requestMethod != HTTP_GET)
|
||||
return false;
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
|
||||
String path(_path);
|
||||
|
||||
if (!_isFile) {
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (requestUri.endsWith("/"))
|
||||
requestUri += "index.htm";
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
}
|
||||
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
|
||||
String contentType = getContentType(path);
|
||||
|
||||
// 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(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if (_fs.exists(pathWithGz))
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f || !f.available())
|
||||
return false;
|
||||
|
||||
if (_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
|
||||
server.streamFile(f, contentType);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String getContentType(const String &path) {
|
||||
char buff[sizeof(mimeTable[0].mimeType)];
|
||||
// Check all entries but last one for match, return if found
|
||||
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
|
||||
strcpy_P(buff, mimeTable[i].endsWith);
|
||||
if (path.endsWith(buff)) {
|
||||
strcpy_P(buff, mimeTable[i].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
}
|
||||
// Fall-through and just return default type
|
||||
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
33
arduino/libretuya/libraries/WebServer/detail/mimetable.cpp
Normal file
33
arduino/libretuya/libraries/WebServer/detail/mimetable.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#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] = {
|
||||
{".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" }
|
||||
};
|
||||
|
||||
} // namespace mime
|
||||
38
arduino/libretuya/libraries/WebServer/detail/mimetable.h
Normal file
38
arduino/libretuya/libraries/WebServer/detail/mimetable.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
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];
|
||||
} // namespace mime
|
||||
61
arduino/libretuya/libraries/WebServer/uri/UriBraces.h
Normal file
61
arduino/libretuya/libraries/WebServer/uri/UriBraces.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "Uri.h"
|
||||
|
||||
class UriBraces : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriBraces(const char *uri) : Uri(uri){};
|
||||
explicit UriBraces(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriBraces(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
int numParams = 0, start = 0;
|
||||
do {
|
||||
start = _uri.indexOf("{}", start);
|
||||
if (start > 0) {
|
||||
numParams++;
|
||||
start += 2;
|
||||
}
|
||||
} while (start > 0);
|
||||
pathArgs.resize(numParams);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs))
|
||||
return true;
|
||||
|
||||
size_t uriLength = _uri.length();
|
||||
unsigned int pathArgIndex = 0;
|
||||
unsigned int requestUriIndex = 0;
|
||||
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
|
||||
char uriChar = _uri[i];
|
||||
char requestUriChar = requestUri[requestUriIndex];
|
||||
|
||||
if (uriChar == requestUriChar)
|
||||
continue;
|
||||
if (uriChar != '{')
|
||||
return false;
|
||||
|
||||
i += 2; // index of char after '}'
|
||||
if (i >= uriLength) {
|
||||
// there is no char after '}'
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
|
||||
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
|
||||
} else {
|
||||
char charEnd = _uri[i];
|
||||
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
|
||||
if (uriIndex < 0)
|
||||
return false;
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
|
||||
requestUriIndex = (unsigned int)uriIndex;
|
||||
}
|
||||
pathArgIndex++;
|
||||
}
|
||||
|
||||
return requestUriIndex >= requestUri.length();
|
||||
}
|
||||
};
|
||||
19
arduino/libretuya/libraries/WebServer/uri/UriGlob.h
Normal file
19
arduino/libretuya/libraries/WebServer/uri/UriGlob.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Uri.h"
|
||||
#include <fnmatch.h>
|
||||
|
||||
class UriGlob : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriGlob(const char *uri) : Uri(uri){};
|
||||
explicit UriGlob(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriGlob(_uri);
|
||||
};
|
||||
|
||||
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
|
||||
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
|
||||
}
|
||||
};
|
||||
41
arduino/libretuya/libraries/WebServer/uri/UriRegex.h
Normal file
41
arduino/libretuya/libraries/WebServer/uri/UriRegex.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "Uri.h"
|
||||
#include <regex>
|
||||
|
||||
class UriRegex : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriRegex(const char *uri) : Uri(uri){};
|
||||
explicit UriRegex(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriRegex(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
std::regex rgx((_uri + "|").c_str());
|
||||
std::smatch matches;
|
||||
std::string s{""};
|
||||
std::regex_search(s, matches, rgx);
|
||||
pathArgs.resize(matches.size() - 1);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs))
|
||||
return true;
|
||||
|
||||
unsigned int pathArgIndex = 0;
|
||||
std::regex rgx(_uri.c_str());
|
||||
std::smatch matches;
|
||||
std::string s(requestUri.c_str());
|
||||
if (std::regex_search(s, matches, rgx)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // skip first
|
||||
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
|
||||
pathArgIndex++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -21,6 +21,7 @@ env.Prepend(
|
||||
join(LT_API_DIR, "compat"),
|
||||
join(LT_API_DIR, "libraries", "base64"),
|
||||
join(LT_API_DIR, "libraries", "HTTPClient"),
|
||||
join(LT_API_DIR, "libraries", "WebServer"),
|
||||
join(LT_API_DIR, "libraries", "WiFiMulti"),
|
||||
# fmt: on
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user