commit b1f276a1a71bdb4ab5cfe552dfe0369498db7a21 Author: Me No Dev Date: Sat Dec 19 18:53:33 2015 +0200 Initial Import diff --git a/keywords.txt b/keywords.txt new file mode 100755 index 0000000..c391e6c --- /dev/null +++ b/keywords.txt @@ -0,0 +1,3 @@ +JsonArray KEYWORD1 +add KEYWORD2 +createArray KEYWORD3 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..b2354cf --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=ESP8266 Async Web Server +version=1.0.0 +author=ESP8266 +maintainer=Me-No-Dev +sentence=Async Web Server for ESP8266 +paragraph=Async Web Server for ESP8266 +category=Other +url=https://github.com/me-no-dev/ESPAsyncWebServer +architectures=* diff --git a/src/AsyncWebServerHandlerImpl.h b/src/AsyncWebServerHandlerImpl.h new file mode 100644 index 0000000..9b22bb7 --- /dev/null +++ b/src/AsyncWebServerHandlerImpl.h @@ -0,0 +1,74 @@ +/* + * AsyncWebServerHandlerImpl.h + * + * Created on: 19.12.2015 г. + * Author: ficeto + */ + +#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ +#define ASYNCWEBSERVERHANDLERIMPL_H_ + +#include "stddef.h" + +class AsyncStaticWebHandler: public AsyncWebHandler { + private: + protected: + FS _fs; + String _uri; + String _path; + String _cache_header; + bool _isFile; + public: + AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) + : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header){ + _isFile = _fs.exists(path); + } + bool canHandle(AsyncWebServerRequest *request); + void handleRequest(AsyncWebServerRequest *request); +}; + +class AsyncCallbackWebHandler: public AsyncWebHandler { + private: + protected: + String _uri; + WebRequestMethod _method; + ArRequestHandlerFunction _onRequest; + ArUploadHandlerFunction _onUpload; + ArBodyHandlerFunction _onBody; + public: + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL){} + void setUri(String uri){ _uri = uri; } + void setMethod(WebRequestMethod method){ _method = method; } + void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } + void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } + void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } + + bool canHandle(AsyncWebServerRequest *request){ + if(!_onRequest) + return false; + + if(_method != HTTP_ANY && request->method() != _method) + return false; + + if(_uri.length() > 1 && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + return true; + } + void handleRequest(AsyncWebServerRequest *request){ + if(_onRequest) + _onRequest(request); + else + request->send(500); + } + void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if(_onUpload) + _onUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + if(_onBody) + _onBody(request, data, len, index, total); + } +}; + +#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/src/AsyncWebServerResponseImpl.h b/src/AsyncWebServerResponseImpl.h new file mode 100644 index 0000000..f7b7ff8 --- /dev/null +++ b/src/AsyncWebServerResponseImpl.h @@ -0,0 +1,53 @@ +/* + * AsyncWebImpl.h + * + * Created on: 19.12.2015 г. + * Author: ficeto + */ + +#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ +#define ASYNCWEBSERVERRESPONSEIMPL_H_ + +class AsyncBasicResponse: public AsyncWebServerResponse { + private: + String _content; + public: + AsyncBasicResponse(int code, String contentType=String(), String content=String()); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +class AsyncFileResponse: public AsyncWebServerResponse { + private: + File _content; + String _path; + String _head; + void _setContentType(String path); + public: + AsyncFileResponse(FS &fs, String path, String contentType=String(), bool download=false); + ~AsyncFileResponse(); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +class AsyncStreamResponse: public AsyncWebServerResponse { + private: + Stream *_content; + String _head; + public: + AsyncStreamResponse(Stream &stream, String contentType, size_t len); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +class AsyncCallbackResponse: public AsyncWebServerResponse { + private: + AwsResponseFiller _content; + String _head; + public: + AsyncCallbackResponse(String contentType, size_t len, AwsResponseFiller callback); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h new file mode 100644 index 0000000..95f92c8 --- /dev/null +++ b/src/ESPAsyncWebServer.h @@ -0,0 +1,274 @@ +#ifndef _AsyncWebServer_H_ +#define _AsyncWebServer_H_ + +#include "Arduino.h" + +#include +#include +#include + +#include "StringArray.h" + +class AsyncWebServer; +class AsyncWebServerRequest; +class AsyncWebServerResponse; +class AsyncWebHeader; +class AsyncWebParameter; +class AsyncWebHandler; + +typedef enum { + HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS +} WebRequestMethod; + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + AsyncWebParameter *next; + + AsyncWebParameter(String name, String value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file), next(NULL){} + String name(){ return _name; } + String value(){ return _value; } + size_t size(){ return _size; } + bool isPost(){ return _isForm; } + bool isFile(){ return _isFile; } +}; + +/* + * HEADER :: Chainable object to hold the headers + * */ + +class AsyncWebHeader { + private: + String _name; + String _value; + + public: + AsyncWebHeader *next; + + AsyncWebHeader(String name, String value): _name(name), _value(value), next(NULL){} + AsyncWebHeader(String data): _name(), _value(), next(NULL){ + if(!data || !data.length() || data.indexOf(':') < 0) + return; + _name = data.substring(0, data.indexOf(':')); + _value = data.substring(data.indexOf(':') + 2); + } + ~AsyncWebHeader(){} + String name(){ return _name; } + String value(){ return _value; } + String toString(){ return String(_name+": "+_value+"\r\n"); } +}; + +/* + * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect + * */ + +typedef std::function AwsResponseFiller; + +class AsyncWebServerRequest { + private: + AsyncClient* _client; + AsyncWebServer* _server; + AsyncWebHandler* _handler; + AsyncWebServerResponse* _response; + StringArray* _interestingHeaders; + + String _temp; + uint8_t _parseState; + + WebRequestMethod _method; + String _url; + String _host; + String _contentType; + String _boundary; + String _authorization; + bool _isMultipart; + bool _isPlainPost; + bool _expectingContinue; + size_t _contentLength; + size_t _parsedLength; + + AsyncWebHeader *_headers; + AsyncWebParameter *_params; + + uint8_t _multiParseState; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + void _onPoll(); + void _onAck(size_t len, uint32_t time); + void _onError(err_t error); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t len); + + void _addParam(AsyncWebParameter*); + String _urlDecode(const String& text); + + bool _parseReqHead(); + bool _parseReqHeader(); + void _parseLine(); + void _parseByte(uint8_t data); + void _parsePlainPostChar(uint8_t data); + void _parseMultipartPostByte(uint8_t data, bool last); + void _addGetParam(String param); + + void _handleUploadStart(); + void _handleUploadByte(uint8_t data, bool last); + void _handleUploadEnd(); + + public: + File _tempFile; + + AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); + ~AsyncWebServerRequest(); + + AsyncClient* client(){ return _client; } + WebRequestMethod method(){ return _method; } + String url(){ return _url; } + String host(){ return _host; } + String contentType(){ return _contentType; } + size_t contentLength(){ return _contentLength; } + bool multipart(){ return _isMultipart; } + + bool authenticate(const char * username, const char * password); + bool authenticate(const char * hash); + void requestAuthentication(); + + void setHandler(AsyncWebHandler *handler){ _handler = handler; } + void addInterestingHeader(String name); + + void send(AsyncWebServerResponse *response); + void send(int code, String contentType=String(), String content=String()); + void send(FS &fs, String path, String contentType=String(), bool download=false); + void send(Stream &stream, String contentType, size_t len); + void send(String contentType, size_t len, AwsResponseFiller callback); + + AsyncWebServerResponse *beginResponse(int code, String contentType=String(), String content=String()); + AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false); + AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len); + AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback); + + int headers(); // get header count + bool hasHeader(String name); + AsyncWebHeader* getHeader(String name); + AsyncWebHeader* getHeader(int num); + + int params(); // get arguments count + bool hasParam(String name, bool post=false, bool file=false); + AsyncWebParameter* getParam(String name, bool post=false, bool file=false); + AsyncWebParameter* getParam(int num); + + int args(){ return params(); } // get arguments count + String arg(const char* 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 + bool hasArg(const char* name); // check if argument exists + + String header(const char* 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 + bool hasHeader(const char* name); // check if header exists + +}; + +/* + * HANDLER :: One instance can be attached to any Request (done by the Server) + * */ + +class AsyncWebHandler { + public: + AsyncWebHandler* next; + AsyncWebHandler(): next(NULL){} + virtual ~AsyncWebHandler(){} + virtual bool canHandle(AsyncWebServerRequest *request){ return false; } + virtual void handleRequest(AsyncWebServerRequest *request){} + virtual void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){} + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){} +}; + +/* + * RESPONSE :: One instance is created for each Request (attached by the Handler) + * */ + +typedef enum { + RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED +} WebResponseState; + +class AsyncWebServerResponse { + protected: + int _code; + AsyncWebHeader *_headers; + String _contentType; + size_t _contentLength; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + WebResponseState _state; + const char* _responseCodeToString(int code); + + public: + AsyncWebServerResponse(); + virtual ~AsyncWebServerResponse(); + virtual void addHeader(String name, String value); + virtual String _assembleHead(); + virtual bool _finished(); + virtual bool _failed(); + virtual void _respond(AsyncWebServerRequest *request); + virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +/* + * SERVER :: One instance + * */ + +typedef std::function ArRequestHandlerFunction; +typedef std::function ArUploadHandlerFunction; +typedef std::function ArBodyHandlerFunction; + +class AsyncWebServer { + private: + AsyncServer _server; + AsyncWebHandler* _handlers; + AsyncWebHandler* _catchAllHandler; + public: + AsyncWebServer(uint16_t port); + ~AsyncWebServer(){} + + void begin(); + void addHandler(AsyncWebHandler* handler); + + void on(const char* uri, ArRequestHandlerFunction onRequest); + void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest); + void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); + void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + + void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL); + + void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned + void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads + void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) + + void _handleDisconnect(AsyncWebServerRequest *request); + void _handleRequest(AsyncWebServerRequest *request); +}; + + +#endif /* _AsyncWebServer_H_ */ diff --git a/src/StringArray.h b/src/StringArray.h new file mode 100644 index 0000000..1d89bbb --- /dev/null +++ b/src/StringArray.h @@ -0,0 +1,125 @@ +/* + * StringArray.h + * + * Created on: 18.12.2015 г. + * Author: ficeto + */ + +#ifndef STRINGARRAY_H_ +#define STRINGARRAY_H_ + +#include "stddef.h" +#include "String.h" + +class StringArrayItem; +class StringArrayItem { + private: + String _string; + public: + StringArrayItem *next; + StringArrayItem(String str):_string(str), next(NULL){} + ~StringArrayItem(){} + String string(){ return _string; } +}; + +class StringArray { + private: + StringArrayItem *_items; + public: + StringArray():_items(NULL){} + StringArray(StringArrayItem *items):_items(items){} + ~StringArray(){} + StringArrayItem *items(){ + return _items; + } + void add(String str){ + StringArrayItem *it = new StringArrayItem(str); + if(_items == NULL){ + _items = it; + } else { + StringArrayItem *i = _items; + while(i->next != NULL) i = i->next; + i->next = it; + } + } + size_t length(){ + size_t i = 0; + StringArrayItem *it = _items; + while(it != NULL){ + i++; + it = it->next; + } + return i; + } + bool contains(String str){ + StringArrayItem *it = _items; + while(it != NULL){ + if(it->string() == str) + return true; + it = it->next; + } + return false; + } + String get(size_t index){ + size_t i = 0; + StringArrayItem *it = _items; + while(it != NULL){ + if(i++ == index) + return it->string(); + it = it->next; + } + return String(); + } + bool remove(size_t index){ + StringArrayItem *it = _items; + if(!index){ + if(_items == NULL) + return false; + _items = _items->next; + delete it; + return true; + } + size_t i = 0; + StringArrayItem *pit = _items; + while(it != NULL){ + if(i++ == index){ + pit->next = it->next; + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + bool remove(String str){ + StringArrayItem *it = _items; + StringArrayItem *pit = _items; + while(it != NULL){ + if(it->string() == str){ + if(it == _items){ + _items = _items->next; + } else { + pit->next = it->next; + } + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + void free(){ + StringArrayItem *it; + while(_items != NULL){ + it = _items; + _items = _items->next; + delete it; + } + } +}; + + + +#endif /* STRINGARRAY_H_ */ diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp new file mode 100644 index 0000000..af9d324 --- /dev/null +++ b/src/WebHandlers.cpp @@ -0,0 +1,40 @@ +/* + * WebHandlers.cpp + * + * Created on: 18.12.2015 г. + * Author: ficeto + */ +#include "ESPAsyncWebServer.h" +#include "AsyncWebServerHandlerImpl.h" + +bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET) + return false; + if ((_isFile && request->url() != _uri) || !request->url().startsWith(_uri)) + return false; + return true; +} + +void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request){ + String path = request->url(); + if(!_isFile){ + if(_uri != "/"){ + path = path.substring(_uri.length()); + if(!path.length()) + path = "/"; + } + if(path.endsWith("/")) + path += "index.htm"; + } + + if(_fs.exists(path) || _fs.exists(path+".gz")){ + AsyncWebServerResponse * response = request->beginResponse(_fs, path); + if (_cache_header.length() != 0) + response->addHeader("Cache-Control", _cache_header); + request->send(response); + } else + request->send(404); + path = String(); +} + + diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp new file mode 100644 index 0000000..beeb911 --- /dev/null +++ b/src/WebResponses.cpp @@ -0,0 +1,447 @@ +#include "ESPAsyncWebServer.h" +#include "AsyncWebServerResponseImpl.h" + +/* + * Abstract Response + * */ +const char* AsyncWebServerResponse::_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 ""; + } +} + +AsyncWebServerResponse::AsyncWebServerResponse() +:_code(0) +,_headers(NULL) +,_contentType() +,_contentLength(0) +,_headLength(0) +,_sentLength(0) +,_ackedLength(0) +,_state(RESPONSE_SETUP) +{ + addHeader("Connection","close"); + addHeader("Access-Control-Allow-Origin","*"); +} + +AsyncWebServerResponse::~AsyncWebServerResponse(){ + while(_headers != NULL){ + AsyncWebHeader *h = _headers; + _headers = h->next; + delete h; + } +} + +void AsyncWebServerResponse::addHeader(String name, String value){ + AsyncWebHeader *header = new AsyncWebHeader(name, value); + if(_headers == NULL){ + _headers = header; + } else { + AsyncWebHeader *h = _headers; + while(h->next != NULL) h = h->next; + h->next = header; + } +} + +String AsyncWebServerResponse::_assembleHead(){ + String out = "HTTP/1.1 " + String(_code) + " " + _responseCodeToString(_code) + "\r\n"; + out += "Content-Length: " + String(_contentLength) + "\r\n"; + if(_contentType.length()){ + out += "Content-Type: " + _contentType + "\r\n"; + } + AsyncWebHeader *h; + while(_headers != NULL){ + h = _headers; + _headers = _headers->next; + out += h->toString(); + delete h; + } + out += "\r\n"; + _headLength = out.length(); + return out; +} + +bool AsyncWebServerResponse::_finished(){ return _state > RESPONSE_WAIT_ACK; } +bool AsyncWebServerResponse::_failed(){ return _state == RESPONSE_FAILED; } +void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ return 0; } + +/* + * String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String content){ + _code = code; + _content = content; + _contentType = contentType; + if(_content.length()){ + _contentLength = _content.length(); + if(!_contentType.length()) + _contentType = "text/plain"; + } +} + +void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ + _state = RESPONSE_HEADERS; + String out = _assembleHead(); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if(!_contentLength && space >= outLen){ + request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(_contentLength && space >= outLen + _contentLength){ + out += _content; + outLen += _contentLength; + request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(space && space < out.length()){ + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if(space > outLen && space < (outLen + _contentLength)){ + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } +} + +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + _ackedLength += len; + if(_state == RESPONSE_CONTENT){ + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if(space > available){ + request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + request->client()->write(out.c_str(), space); + return space; + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= (_headLength+_contentLength)){ + _state = RESPONSE_END; + } + } + return 0; +} + +/* + * File Response + * */ + +AsyncFileResponse::~AsyncFileResponse(){ + if(_content) + _content.close(); +} + +void AsyncFileResponse::_setContentType(String path){ + if (path.endsWith(".html")) _contentType = "text/html"; + else if (path.endsWith(".htm")) _contentType = "text/html"; + else if (path.endsWith(".css")) _contentType = "text/css"; + else if (path.endsWith(".txt")) _contentType = "text/plain"; + else if (path.endsWith(".js")) _contentType = "application/javascript"; + else if (path.endsWith(".png")) _contentType = "image/png"; + else if (path.endsWith(".gif")) _contentType = "image/gif"; + else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; + else if (path.endsWith(".xml")) _contentType = "text/xml"; + else if (path.endsWith(".pdf")) _contentType = "application/pdf"; + else if (path.endsWith(".zip")) _contentType = "application/zip"; + else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; + else _contentType = "application/octet-stream"; +} + +AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bool download){ + _code = 200; + _path = path; + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ + _path = _path+".gz"; + addHeader("Content-Encoding", "gzip"); + } + + if(download) + _contentType = "application/octet-stream"; + else + _setContentType(path); + _content = fs.open(_path, "r"); + _contentLength = _content.size(); +} + +void AsyncFileResponse::_respond(AsyncWebServerRequest *request){ + if(!_content){ + _state = RESPONSE_FAILED; + request->send(500); + return; + } + _head = _assembleHead(); + _state = RESPONSE_HEADERS; + size_t outLen = _head.length(); + size_t space = request->client()->space(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + out = String(); + } +} + +size_t AsyncFileResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + if(!_content){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + if(_state == RESPONSE_CONTENT){ + size_t remaining = _contentLength - _sentLength; + size_t outLen = (remaining > space)?space:remaining; + uint8_t *buf = (uint8_t *)os_malloc(outLen); + _content.read(buf, outLen); + request->client()->write((const char*)buf, outLen); + _sentLength += outLen; + os_free(buf); + if(_sentLength == _contentLength){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + } else if(_state == RESPONSE_HEADERS){ + size_t outLen = _head.length(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + return outLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= (_headLength+_contentLength)){ + _state = RESPONSE_END; + } + } + return 0; +} + +/* + * Stream Response + * */ + +AsyncStreamResponse::AsyncStreamResponse(Stream &stream, String contentType, size_t len){ + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; +} + +void AsyncStreamResponse::_respond(AsyncWebServerRequest *request){ + if(!_content){ + _state = RESPONSE_FAILED; + request->send(500); + return; + } + _head = _assembleHead(); + _state = RESPONSE_HEADERS; + size_t outLen = _head.length(); + size_t space = request->client()->space(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + out = String(); + } +} + +size_t AsyncStreamResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + if(!_content){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + if(_state == RESPONSE_CONTENT){ + size_t remaining = _contentLength - _sentLength; + size_t available = _content->available(); + available = (remaining >= available)?available:remaining; + size_t outLen = (available > space)?space:available; + uint8_t *buf = (uint8_t *)os_malloc(outLen); + size_t i; + for(i=0;iread(); + request->client()->write((const char*)buf, outLen); + _sentLength += outLen; + os_free(buf); + if(_sentLength == _contentLength){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + } else if(_state == RESPONSE_HEADERS){ + size_t outLen = _head.length(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + return outLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= (_headLength+_contentLength)){ + _state = RESPONSE_END; + } + } + return 0; +} + +/* + * Callback Response + * */ + +AsyncCallbackResponse::AsyncCallbackResponse(String contentType, size_t len, AwsResponseFiller callback){ + _code = 200; + _content = callback; + _contentLength = len; + _contentType = contentType; +} + +void AsyncCallbackResponse::_respond(AsyncWebServerRequest *request){ + if(!_content){ + _state = RESPONSE_FAILED; + request->send(500); + return; + } + _head = _assembleHead(); + _state = RESPONSE_HEADERS; + size_t outLen = _head.length(); + size_t space = request->client()->space(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + out = String(); + } +} + +size_t AsyncCallbackResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + if(!_content){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + if(_state == RESPONSE_CONTENT){ + size_t remaining = _contentLength - _sentLength; + size_t outLen = (remaining > space)?space:remaining; + uint8_t *buf = (uint8_t *)os_malloc(outLen); + outLen = _content(buf, outLen, remaining); + request->client()->write((const char*)buf, outLen); + _sentLength += outLen; + os_free(buf); + if(_sentLength == _contentLength){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + } else if(_state == RESPONSE_HEADERS){ + size_t outLen = _head.length(); + if(space >= outLen){ + request->client()->write(_head.c_str(), outLen); + _head = String(); + _state = RESPONSE_CONTENT; + return outLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= (_headLength+_contentLength)){ + _state = RESPONSE_END; + } + } + return 0; +} + + + + + + + + diff --git a/src/WebServer.cpp b/src/WebServer.cpp new file mode 100644 index 0000000..5efef55 --- /dev/null +++ b/src/WebServer.cpp @@ -0,0 +1,90 @@ +#include "ESPAsyncWebServer.h" +#include "AsyncWebServerHandlerImpl.h" + + +AsyncWebServer::AsyncWebServer(uint16_t port):_server(port), _handlers(0), _catchAllHandler(new AsyncCallbackWebHandler()){ + _server.onClient([](void *s, AsyncClient* c){ + new AsyncWebServerRequest((AsyncWebServer*)s, c); + }, this); +} + +void AsyncWebServer::addHandler(AsyncWebHandler* handler){ + if(_handlers == NULL){ + _handlers = handler; + } else { + AsyncWebHandler* h = _handlers; + while(h->next != NULL) h = h->next; + h->next = handler; + } +} + +void AsyncWebServer::begin(){ + _server.begin(); +} + +void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ + delete request; +} + +void AsyncWebServer::_handleRequest(AsyncWebServerRequest *request){ + if(_handlers){ + AsyncWebHandler* h = _handlers; + while(h && !h->canHandle(request)) h = h->next; + if(h){ + request->setHandler(h); + return; + } + } + request->addInterestingHeader("ANY"); + request->setHandler(_catchAllHandler); +} + + +void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + handler->onBody(onBody); + addHandler(handler); +} + +void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + addHandler(handler); +} + +void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + addHandler(handler); +} + +void AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ + on(uri, HTTP_ANY, onRequest); +} + +void AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header){ + addHandler(new AsyncStaticWebHandler(fs, path, uri, cache_header)); +} + +void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ + ((AsyncCallbackWebHandler*)_catchAllHandler)->onRequest(fn); +} + +void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ + ((AsyncCallbackWebHandler*)_catchAllHandler)->onUpload(fn); +} + +void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ + ((AsyncCallbackWebHandler*)_catchAllHandler)->onBody(fn); +} + + diff --git a/src/WebServerClient.cpp b/src/WebServerClient.cpp new file mode 100644 index 0000000..2d0cf81 --- /dev/null +++ b/src/WebServerClient.cpp @@ -0,0 +1,747 @@ +/* + * WebServerClient.cpp + * + * Created on: 18.12.2015 г. + * Author: ficeto + */ + +#include "ESPAsyncWebServer.h" +#include "AsyncWebServerResponseImpl.h" +#include + +bool __is_param_char(char c){ + return c && (c != '{') && (c != '[') && (c != '&') && (c != '='); +} + +enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; + +AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) + : _client(c) + , _server(s) + , _handler(NULL) + , _response(NULL) + , _interestingHeaders(new StringArray()) + , _temp() + , _parseState(0) + , _method(HTTP_ANY) + , _url() + , _host() + , _contentType() + , _boundary() + , _authorization() + , _isMultipart(false) + , _isPlainPost(false) + , _expectingContinue(false) + , _contentLength(0) + , _parsedLength(0) + , _headers(NULL) + , _params(NULL) + , _multiParseState(0) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) +{ + c->onError([](void *r, AsyncClient* c, err_t error){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); + c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); + c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); }, this); + c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); + c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); + c->onPoll([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onPoll(); }, this); +} + +AsyncWebServerRequest::~AsyncWebServerRequest(){ + while(_headers != NULL){ + AsyncWebHeader *h = _headers; + _headers = h->next; + delete h; + } + + while(_params != NULL){ + AsyncWebParameter *p = _params; + _params = p->next; + delete p; + } + + _interestingHeaders->free(); + delete _interestingHeaders; + + if(_response != NULL){ + delete _response; + } + +} + +void AsyncWebServerRequest::_onData(void *buf, size_t len){ + //os_printf("r:%u\n", len); + if(_parseState < PARSE_REQ_BODY){ + size_t i; + for(i=0; i 0){ + name = param.substring(0, param.indexOf('=')); + value = param.substring(param.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(name, value)); +} + + +bool AsyncWebServerRequest::_parseReqHead(){ + String m = _temp.substring(0, _temp.indexOf(' ')); + if(m == "GET"){ + _method = HTTP_GET; + } else if(m == "POST"){ + _method = HTTP_POST; + } else if(m == "DELETE"){ + _method = HTTP_DELETE; + } else if(m == "PUT"){ + _method = HTTP_PUT; + } else if(m == "PATCH"){ + _method = HTTP_PATCH; + } else if(m == "HEAD"){ + _method = HTTP_HEAD; + } else if(m == "OPTIONS"){ + _method = HTTP_OPTIONS; + } + + _temp = _temp.substring(_temp.indexOf(' ')+1); + String u = _temp.substring(0, _temp.indexOf(' ')); + String g = String(); + if(u.indexOf('?') > 0){ + g = u.substring(u.indexOf('?') + 1); + u = u.substring(0, u.indexOf('?')); + } + _url = u; + if(g.length()){ + while(true){ + if(g.length() == 0) + break; + if(g.indexOf('&') > 0){ + _addGetParam(g.substring(0, g.indexOf('&'))); + g = g.substring(g.indexOf('&') + 1); + } else { + _addGetParam(g); + break; + } + } + } + + _temp = String(); + return true; +} + +bool AsyncWebServerRequest::_parseReqHeader(){ + if(_temp.indexOf(':')){ + AsyncWebHeader *h = new AsyncWebHeader(_temp); + if(h->name() == "Host"){ + _host = h->value(); + delete h; + _server->_handleRequest(this); + } else if(h->name() == "Content-Type"){ + if (h->value().startsWith("multipart/")){ + _boundary = h->value().substring(h->value().indexOf('=')+1); + _contentType = h->value().substring(0, h->value().indexOf(';')); + _isMultipart = true; + } else { + _contentType = h->value(); + } + delete h; + } else if(h->name() == "Content-Length"){ + _contentLength = atoi(h->value().c_str()); + delete h; + } else if(h->name() == "Expect" && h->value() == "100-continue"){ + _expectingContinue = true; + delete h; + } else if(h->name() == "Authorization"){ + if(h->value().startsWith("Basic")){ + _authorization = h->value().substring(6); + } + delete h; + } else { + if(_interestingHeaders->contains(h->name()) || _interestingHeaders->contains("ANY")){ + if(_headers == NULL) + _headers = h; + else { + AsyncWebHeader *hs = _headers; + while(hs->next != NULL) hs = hs->next; + hs->next = h; + } + } else + delete h; + } + + } + _temp = String(); + return true; +} + +void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ + if(data && (char)data != '\r' && (char)data != '\n' && (char)data != '&') + _temp += (char)data; + if(!data || (char)data == '\n' || (char)data == '&' || _parsedLength == _contentLength){ + _temp = _urlDecode(_temp); + String name = _temp; + String value = ""; + if(_temp.indexOf('=') > 0){ + name = _temp.substring(0, _temp.indexOf('=')); + value = _temp.substring(_temp.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(name, value, true)); + _temp = String(); + } +} + +void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ + _itemBuffer[_itemBufferIndex++] = data; + if(last){ + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); + _itemBufferIndex = 0; + } +} + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ +#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) + if(!_parsedLength){ + _multiParseState = EXPECT_BOUNDARY; + _temp = String(); + _itemName = String(); + _itemFilename = String(); + _itemType = String(); + } + + if(_multiParseState == EXPECT_BOUNDARY){ + if(_parsedLength < 2 && data != '-'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 3 == _boundary.length()){ + if(data != '\n'){ + _multiParseState = PARSE_ERROR; + return; + } + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } + } else if(_multiParseState == PARSE_HEADERS){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n'){ + if(_temp.length()){ + if(_temp.startsWith("Content-Type:")){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.startsWith("Content-Disposition:")){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + os_free(_itemBuffer); + _itemBuffer = (uint8_t*)os_malloc(1460); + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == WAIT_FOR_RETURN1){ + if(data != '\r'){ + itemWriteByte(data); + } else { + _multiParseState = EXPECT_FEED1; + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte(data); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte(data); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte(data); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + itemWriteByte(data); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + os_free(_itemBuffer); + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _contentLength == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte(data); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); itemWriteByte(data); + } + } +} + +void AsyncWebServerRequest::_parseLine(){ + if(_parseState == PARSE_REQ_START){ + if(!_temp.length()){ + _parseState = PARSE_REQ_FAIL; + _client->close(); + } else { + _parseReqHead(); + _parseState = PARSE_REQ_HEADERS; + } + return; + } + + if(_parseState == PARSE_REQ_HEADERS){ + if(!_temp.length()){ + //end of headers + if(_expectingContinue){ + const char * response = "HTTP/1.1 100 Continue\r\n\r\n"; + _client->write(response, os_strlen(response)); + } + if(_contentLength){ + _parseState = PARSE_REQ_BODY; + } else { + _parseState = PARSE_REQ_END; + if(_handler) _handler->handleRequest(this); + else send(501); + } + } else _parseReqHeader(); + } +} + +void AsyncWebServerRequest::_parseByte(uint8_t data){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n') + _parseLine(); +} + + + +int AsyncWebServerRequest::headers(){ + int i = 0; + AsyncWebHeader* h = _headers; + while(h != NULL){ + i++; h = h->next; + } + return i; +} + +bool AsyncWebServerRequest::hasHeader(String name){ + AsyncWebHeader* h = _headers; + while(h != NULL){ + if(h->name() == name) + return true; + h = h->next; + } + return false; +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(String name){ + AsyncWebHeader* h = _headers; + while(h != NULL){ + if(h->name() == name) + return h; + h = h->next; + } + return NULL; +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(int num){ + int i = 0; + AsyncWebHeader* h = _headers; + while(h != NULL){ + if(num == i++) + return h; + h = h->next; + } + return NULL; +} + +int AsyncWebServerRequest::params(){ + int i = 0; + AsyncWebParameter* h = _params; + while(h != NULL){ + i++; h = h->next; + } + return i; +} + +bool AsyncWebServerRequest::hasParam(String name, bool post, bool file){ + AsyncWebParameter* h = _params; + while(h != NULL){ + if(h->name() == name && h->isPost() == post && h->isFile() == file) + return true; + h = h->next; + } + return false; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(String name, bool post, bool file){ + AsyncWebParameter* h = _params; + while(h != NULL){ + if(h->name() == name && h->isPost() == post && h->isFile() == file) + return h; + h = h->next; + } + return NULL; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(int num){ + int i = 0; + AsyncWebParameter* h = _params; + while(h != NULL){ + if(num == i++) + return h; + h = h->next; + } + return NULL; +} + +void AsyncWebServerRequest::addInterestingHeader(String name){ + if(!_interestingHeaders->contains(name)) _interestingHeaders->add(name); +} + + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ + _response = response; + _response->_respond(this); +} + + + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, String contentType, String content){ + return new AsyncBasicResponse(code, contentType, content); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, String path, String contentType, bool download){ + if(fs.exists(path) || (!download && fs.exists(path+".gz"))) + return new AsyncFileResponse(fs, path, contentType, download); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, String contentType, size_t len){ + return new AsyncStreamResponse(stream, contentType, len); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(String contentType, size_t len, AwsResponseFiller callback){ + return new AsyncCallbackResponse(contentType, len, callback); +} + +void AsyncWebServerRequest::send(int code, String contentType, String content){ + send(new AsyncBasicResponse(code, contentType, content)); +} + +void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool download){ + if(fs.exists(path) || (!download && fs.exists(path+".gz"))){ + send(new AsyncFileResponse(fs, path, contentType, download)); + } else send(404); +} + +void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){ + send(new AsyncStreamResponse(stream, contentType, len)); +} + +void AsyncWebServerRequest::send(String contentType, size_t len, AwsResponseFiller callback){ + send(new AsyncCallbackResponse(contentType, len, callback)); +} + + +bool AsyncWebServerRequest::authenticate(const char * username, const char * password){ + if(_authorization.length()){ + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen]; + if(toencode == NULL){ + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && _authorization.equals(encoded)){ + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } + return false; +} + +bool AsyncWebServerRequest::authenticate(const char * hash){ + return (_authorization.length() && (_authorization == String(hash))); +} + +void AsyncWebServerRequest::requestAuthentication(){ + AsyncWebServerResponse * r = beginResponse(401); + r->addHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); + send(r); +} + +bool AsyncWebServerRequest::hasArg(const char* name){ + AsyncWebParameter* h = _params; + while(h != NULL){ + if(h->name() == String(name)) + return true; + h = h->next; + } + return false; +} + +String AsyncWebServerRequest::arg(const char* name){ + AsyncWebParameter* h = _params; + while(h != NULL){ + if(h->name() == String(name)) + return h->value(); + h = h->next; + } + return String(); +} + +String AsyncWebServerRequest::arg(int i){ + return getParam(i)->value(); +} + +String AsyncWebServerRequest::argName(int i){ + return getParam(i)->name(); +} + +String AsyncWebServerRequest::header(const char* name){ + AsyncWebHeader* h = getHeader(String(name)); + if(h) + return h->value(); + return String(); +} + +String AsyncWebServerRequest::header(int i){ + AsyncWebHeader* h = getHeader(i); + if(h) + return h->value(); + return String(); +} + +String AsyncWebServerRequest::headerName(int i){ + AsyncWebHeader* h = getHeader(i); + if(h) + return h->name(); + return String(); +} + +bool AsyncWebServerRequest::hasHeader(const char* name){ + return hasHeader(String(name)); +} + + +String AsyncWebServerRequest::_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; +} +