1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00
esp8266/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp

485 lines
14 KiB
C++

/*
ESP8266WebServer.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 <libb64/cencode.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "ESP8266WebServer.h"
#include "FS.h"
#include "detail/RequestHandlersImpl.h"
// #define DEBUG
#define DEBUG_OUTPUT Serial
const char * AUTHORIZATION_HEADER = "Authorization";
ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port)
: _server(addr, port)
, _currentMethod(HTTP_ANY)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
{
}
ESP8266WebServer::ESP8266WebServer(int port)
: _server(port)
, _currentMethod(HTTP_ANY)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
{
}
ESP8266WebServer::~ESP8266WebServer() {
if (_currentHeaders)
delete[]_currentHeaders;
_headerKeysCount = 0;
RequestHandler* handler = _firstHandler;
while (handler) {
RequestHandler* next = handler->next();
delete handler;
handler = next;
}
}
void ESP8266WebServer::begin() {
_server.begin();
if(!_headerKeysCount)
collectHeaders(0, 0);
}
bool ESP8266WebServer::authenticate(const char * username, const char * password){
if(hasHeader(AUTHORIZATION_HEADER)){
String authReq = header(AUTHORIZATION_HEADER);
if(authReq.startsWith("Basic")){
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username)+strlen(password)+1;
char *toencode = new char[toencodeLen];
if(toencode == NULL){
authReq = String();
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
if(encoded == NULL){
authReq = String();
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
authReq = String();
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
}
authReq = String();
}
return false;
}
void ESP8266WebServer::requestAuthentication(){
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
send(401);
}
void ESP8266WebServer::on(const char* uri, ESP8266WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void ESP8266WebServer::addHandler(RequestHandler* handler) {
_addRequestHandler(handler);
}
void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
}
else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void ESP8266WebServer::handleClient() {
WiFiClient client = _server.available();
if (!client) {
return;
}
#ifdef DEBUG
DEBUG_OUTPUT.println("New client");
#endif
// Wait for data from client to become available
uint16_t maxWait = HTTP_MAX_DATA_WAIT;
while(client.connected() && !client.available() && maxWait--){
delay(1);
}
if (!_parseRequest(client)) {
return;
}
_currentClient = client;
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
}
void ESP8266WebServer::sendHeader(const String& name, const String& value, bool first) {
String headerLine = name;
headerLine += ": ";
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
}
else {
_responseHeaders += headerLine;
}
}
void ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
response = "HTTP/1.1 ";
response += String(code);
response += " ";
response += _responseCodeToString(code);
response += "\r\n";
if (!content_type)
content_type = "text/html";
sendHeader("Content-Type", content_type, true);
if (_contentLength != CONTENT_LENGTH_UNKNOWN && _contentLength != CONTENT_LENGTH_NOT_SET) {
sendHeader("Content-Length", String(_contentLength));
}
else if (contentLength > 0){
sendHeader("Content-Length", String(contentLength));
}
sendHeader("Connection", "close");
sendHeader("Access-Control-Allow-Origin", "*");
response += _responseHeaders;
response += "\r\n";
_responseHeaders = String();
}
void ESP8266WebServer::send(int code, const char* content_type, const String& content) {
String header;
_prepareHeader(header, code, content_type, content.length());
sendContent(header);
sendContent(content);
}
void ESP8266WebServer::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];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
sendContent(header);
sendContent_P(content);
}
void ESP8266WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void ESP8266WebServer::send(int code, char* content_type, const String& content) {
send(code, (const char*)content_type, content);
}
void ESP8266WebServer::send(int code, const String& content_type, const String& content) {
send(code, (const char*)content_type.c_str(), content);
}
void ESP8266WebServer::sendContent(const String& content) {
const size_t unit_size = HTTP_DOWNLOAD_UNIT_SIZE;
size_t size_to_send = content.length();
const char* send_start = content.c_str();
while (size_to_send) {
size_t will_send = (size_to_send < unit_size) ? size_to_send : unit_size;
size_t sent = _currentClient.write(send_start, will_send);
if (sent == 0) {
break;
}
size_to_send -= sent;
send_start += sent;
}
}
void ESP8266WebServer::sendContent_P(PGM_P content) {
char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
while (content != NULL) {
size_t contentUnitLen;
PGM_P contentNext;
// due to the memccpy signature, lots of casts are needed
contentNext = (PGM_P)memccpy_P((void*)contentUnit, (PGM_VOID_P)content, 0, HTTP_DOWNLOAD_UNIT_SIZE);
if (contentNext == NULL) {
// no terminator, more data available
content += HTTP_DOWNLOAD_UNIT_SIZE;
contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
}
else {
// reached terminator. Do not send the terminator
contentUnitLen = contentNext - contentUnit - 1;
content = NULL;
}
// write is so overloaded, had to use the cast to get it pick the right one
_currentClient.write((const char*)contentUnit, contentUnitLen);
}
}
void ESP8266WebServer::sendContent_P(PGM_P content, size_t size) {
char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
size_t remaining_size = size;
while (content != NULL && remaining_size > 0) {
size_t contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
if (remaining_size < HTTP_DOWNLOAD_UNIT_SIZE) contentUnitLen = remaining_size;
// due to the memcpy signature, lots of casts are needed
memcpy_P((void*)contentUnit, (PGM_VOID_P)content, contentUnitLen);
content += contentUnitLen;
remaining_size -= contentUnitLen;
// write is so overloaded, had to use the cast to get it pick the right one
_currentClient.write((const char*)contentUnit, contentUnitLen);
}
}
String ESP8266WebServer::arg(const char* name) {
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return _currentArgs[i].value;
}
return String();
}
String ESP8266WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return String();
}
String ESP8266WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return String();
}
int ESP8266WebServer::args() {
return _currentArgCount;
}
bool ESP8266WebServer::hasArg(const char* name) {
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String ESP8266WebServer::header(const char* name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key == name)
return _currentHeaders[i].value;
}
return String();
}
void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 1;
if (_currentHeaders)
delete[]_currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders[0].key = AUTHORIZATION_HEADER;
for (int i = 1; i < _headerKeysCount; i++){
_currentHeaders[i].key = headerKeys[i-1];
}
}
String ESP8266WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return String();
}
String ESP8266WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return String();
}
int ESP8266WebServer::headers() {
return _headerKeysCount;
}
bool ESP8266WebServer::hasHeader(const char* name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String ESP8266WebServer::hostHeader() {
return _hostHeader;
}
void ESP8266WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void ESP8266WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void ESP8266WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler){
#ifdef DEBUG
DEBUG_OUTPUT.println("request handler not found");
#endif
}
else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
#ifdef DEBUG
if (!handled) {
DEBUG_OUTPUT.println("request handler failed to handle request");
}
#endif
}
if (!handled) {
if(_notFoundHandler) {
_notFoundHandler();
}
else {
send(404, "text/plain", String("Not found: ") + _currentUri);
}
}
uint16_t maxWait = HTTP_MAX_CLOSE_WAIT;
while(_currentClient.connected() && maxWait--) {
delay(1);
}
_currentClient = WiFiClient();
_currentUri = String();
}
const char* ESP8266WebServer::_responseCodeToString(int code) {
switch (code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Time-out";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Large";
case 415: return "Unsupported Media Type";
case 416: return "Requested range not satisfiable";
case 417: return "Expectation Failed";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Time-out";
case 505: return "HTTP Version not supported";
default: return "";
}
}