/* 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 #include #include "WiFiServer.h" #include "WiFiClient.h" #include "ESP8266WebServer.h" #include "FS.h" #include "detail/RequestHandlersImpl.h" //#define DEBUG_ESP_HTTP_SERVER #ifdef DEBUG_ESP_PORT #define DEBUG_OUTPUT DEBUG_ESP_PORT #else #define DEBUG_OUTPUT Serial #endif 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; } close(); } void ESP8266WebServer::begin() { _currentStatus = HC_NONE; _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() { if (_currentStatus == HC_NONE) { WiFiClient client = _server.available(); if (!client) { return; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("New client"); #endif _currentClient = client; _currentStatus = HC_WAIT_READ; _statusChange = millis(); } if (!_currentClient.connected()) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } // Wait for data from client to become available if (_currentStatus == HC_WAIT_READ) { if (!_currentClient.available()) { if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; } yield(); return; } if (!_parseRequest(_currentClient)) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } _contentLength = CONTENT_LENGTH_NOT_SET; _handleRequest(); if (!_currentClient.connected()) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } else { _currentStatus = HC_WAIT_CLOSE; _statusChange = millis(); return; } } if (_currentStatus == HC_WAIT_CLOSE) { if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; } else { yield(); return; } } } void ESP8266WebServer::close() { _server.close(); } void ESP8266WebServer::stop() { close(); } 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_NOT_SET) { sendHeader("Content-Length", String(contentLength)); } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { 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(String 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(String 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(String 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_ESP_HTTP_SERVER DEBUG_OUTPUT.println("request handler not found"); #endif } else { handled = _currentHandler->handle(*this, _currentMethod, _currentUri); #ifdef DEBUG_ESP_HTTP_SERVER 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); } } _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 ""; } }