/* Asynchronous WebServer library for Espressif MCUs Copyright (c) 2016 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. 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 */ #include "ESPAsyncWebServer.h" #include "WebResponseImpl.h" #include "WebAuthentication.h" #ifndef ESP8266 #define os_strlen strlen #endif #define __is_param_char(c) ((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) , _version(0) , _method(HTTP_ANY) , _url() , _host() , _contentType() , _boundary() , _authorization() , _isDigest(false) , _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) , _tempObject(NULL) , next(NULL) { c->onError([](void *r, AsyncClient* c, int8_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(); delete c; }, 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; } if(_tempObject != NULL){ free(_tempObject); } } void AsyncWebServerRequest::_onData(void *buf, size_t len){ if(_parseState < PARSE_REQ_BODY){ // Find new line in buf char *str = (char*)buf; size_t i = 0; for (; i < len; i++) if (str[i] == '\n') break; if (i == len) { // No new line, just add the buffer in _temp char ch = str[len-1]; str[len-1] = 0; _temp.reserve(_temp.length()+len); _temp.concat(str); _temp.concat(ch); } else { // Found new line - extract it and parse str[i] = 0; // Terminate the string at the end of the line. _temp.concat(str); _temp.trim(); _parseLine(); if (++i < len) _onData(str+i, len-i); // Still have more buffer to process } } else if(_parseState == PARSE_REQ_BODY){ if(_isMultipart){ size_t i; for(i=0; i end) equal = end; String name = params.substring(start, equal); String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); _addParam(new AsyncWebParameter(name, value)); start = end + 1; } } bool AsyncWebServerRequest::_parseReqHead(){ // Split the head into method, url and version int index = _temp.indexOf(' '); String m = _temp.substring(0, index); index = _temp.indexOf(' ', index+1); String u = _temp.substring(m.length()+1, index); _temp = _temp.substring(index+1); 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; } u = urlDecode(u); String g = String(); index = u.indexOf('?'); if(index > 0){ g = u.substring(index +1); u = u.substring(0, index); } _url = u; _addGetParams(g); if(!_temp.startsWith("HTTP/1.0")) _version = 1; _temp = String(); return true; } bool AsyncWebServerRequest::_parseReqHeader(){ int index = _temp.indexOf(':'); if(index){ String name = _temp.substring(0, index); String value = _temp.substring(index + 2); if(name.equalsIgnoreCase("Host")){ _host = value; _server->_rewriteRequest(this); _server->_attachHandler(this); } else if(name.equalsIgnoreCase("Content-Type")){ if (value.startsWith("multipart/")){ _boundary = value.substring(value.indexOf('=')+1); _contentType = value.substring(0, value.indexOf(';')); _isMultipart = true; } else { _contentType = value; } } else if(name.equalsIgnoreCase("Content-Length")){ _contentLength = atoi(value.c_str()); } else if(name.equalsIgnoreCase("Expect") && value == "100-continue"){ _expectingContinue = true; } else if(name.equalsIgnoreCase("Authorization")){ if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase("Basic")){ _authorization = value.substring(6); } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase("Digest")){ _isDigest = true; _authorization = value.substring(7); } } else { if(_interestingHeaders->containsIgnoreCase(name) || _interestingHeaders->containsIgnoreCase("ANY")){ AsyncWebHeader *h = new AsyncWebHeader(name, value); if(_headers == NULL) _headers = h; else { AsyncWebHeader *hs = _headers; while(hs->next != NULL) hs = hs->next; hs->next = h; } } } } _temp = String(); return true; } void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ if(data && (char)data != '&') _temp += (char)data; if(!data || (char)data == '&' || _parsedLength == _contentLength){ _temp = urlDecode(_temp); String name = "body"; String value = _temp; if(!_temp.startsWith("{") && !_temp.startsWith("[") && _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 || _itemBufferIndex == 1460){ //check if authenticated before calling the upload 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 == WAIT_FOR_RETURN1){ if(data != '\r'){ itemWriteByte(data); } else { _multiParseState = EXPECT_FEED1; } } else 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.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type:")){ _itemType = _temp.substring(14); _itemIsFile = true; } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("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) free(_itemBuffer); _itemBuffer = (uint8_t*)malloc(1460); if(_itemBuffer == NULL){ _multiParseState = PARSE_ERROR; return; } _itemBufferIndex = 0; } } } } else if(_multiParseState == EXPECT_FEED1){ if(data != '\n'){ _multiParseState = WAIT_FOR_RETURN1; itemWriteByte('\r'); _parseMultipartPostByte(data, last); } else { _multiParseState = EXPECT_DASH1; } } else if(_multiParseState == EXPECT_DASH1){ if(data != '-'){ _multiParseState = WAIT_FOR_RETURN1; itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); } else { _multiParseState = EXPECT_DASH2; } } else if(_multiParseState == EXPECT_DASH2){ if(data != '-'){ _multiParseState = WAIT_FOR_RETURN1; itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); } 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]); _parseMultipartPostByte(data, last); } else if(_boundaryPosition == _boundary.length() - 1){ _multiParseState = DASH3_OR_RETURN2; if(!_itemIsFile){ _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); } else { if(_itemSize){ //check if authenticated before calling the upload if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); _itemBufferIndex = 0; _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); } free(_itemBuffer); } } else { _boundaryPosition++; } } else if(_multiParseState == DASH3_OR_RETURN2){ if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); _contentLength = _parsedLength + 4;//lets close the request gracefully } 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]); _parseMultipartPostByte(data, last); } } 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'); _parseMultipartPostByte(data, last); } } } 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)); } //check handler for authentication if(_contentLength){ _parseState = PARSE_REQ_BODY; } else { _parseState = PARSE_REQ_END; if(_handler) _handler->handleRequest(this); else send(501); } } else _parseReqHeader(); } } 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().equalsIgnoreCase(name)) return true; h = h->next; } return false; } AsyncWebHeader* AsyncWebServerRequest::getHeader(String name){ AsyncWebHeader* h = _headers; while(h != NULL){ if(h->name().equalsIgnoreCase(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->containsIgnoreCase(name)) _interestingHeaders->add(name); } void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ _response = response; if(_response == NULL){ _client->close(true); _onDisconnect(); return; } if(!_response->_sourceValid()){ delete response; _response = NULL; send(500); } else _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(File content, String path, String contentType, bool download){ if(content == true) return new AsyncFileResponse(content, 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); } AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(String contentType, AwsResponseFiller callback){ if(_version) return new AsyncChunkedResponse(contentType, callback); return new AsyncCallbackResponse(contentType, 0, callback); } AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(String contentType, size_t bufferSize){ return new AsyncResponseStream(contentType, bufferSize); } AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, String contentType, const uint8_t * content, size_t len){ return new AsyncProgmemResponse(code, contentType, content, len); } AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, String contentType, PGM_P content){ return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content)); } void AsyncWebServerRequest::send(int code, String contentType, String content){ send(beginResponse(code, contentType, content)); } void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool download){ if(fs.exists(path) || (!download && fs.exists(path+".gz"))){ send(beginResponse(fs, path, contentType, download)); } else send(404); } void AsyncWebServerRequest::send(File content, String path, String contentType, bool download){ if(content == true){ send(beginResponse(content, path, contentType, download)); } else send(404); } void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){ send(beginResponse(stream, contentType, len)); } void AsyncWebServerRequest::send(String contentType, size_t len, AwsResponseFiller callback){ send(beginResponse(contentType, len, callback)); } void AsyncWebServerRequest::sendChunked(String contentType, AwsResponseFiller callback){ send(beginChunkedResponse(contentType, callback)); } void AsyncWebServerRequest::send_P(int code, String contentType, const uint8_t * content, size_t len){ send(beginResponse_P(code, contentType, content, len)); } void AsyncWebServerRequest::send_P(int code, String contentType, PGM_P content){ send(beginResponse_P(code, contentType, content)); } void AsyncWebServerRequest::redirect(String url){ AsyncWebServerResponse * response = beginResponse(302); response->addHeader("Location",url); send(response); } bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ if(_authorization.length()){ if(_isDigest) return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); else if(!passwordIsHash) return checkBasicAuthentication(_authorization.c_str(), username, password); else return _authorization.equals(password); } return false; } bool AsyncWebServerRequest::authenticate(const char * hash){ if(!_authorization.length() || hash == NULL) return false; if(_isDigest){ String hStr = String(hash); int separator = hStr.indexOf(":"); if(separator <= 0) return false; String username = hStr.substring(0, separator); hStr = hStr.substring(separator + 1); separator = hStr.indexOf(":"); if(separator <= 0) return false; String realm = hStr.substring(0, separator); hStr = hStr.substring(separator + 1); return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); } return (_authorization.equals(hash)); } void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ AsyncWebServerResponse * r = beginResponse(401); if(!isDigest && realm == NULL){ r->addHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); } else if(!isDigest){ String header = "Basic realm=\""; header.concat(realm); header.concat("\""); r->addHeader("WWW-Authenticate", header); } else { String header = "Digest "; header.concat(requestDigestAuthentication(realm)); r->addHeader("WWW-Authenticate", header); } 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){ char temp[] = "0x00"; unsigned int len = text.length(); unsigned int i = 0; String decoded = String(); decoded.reserve(len); // Allocate the string internal buffer - never longer from source text 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.concat(decodedChar); } return decoded; } const char * AsyncWebServerRequest::methodToString(){ if(_method == HTTP_ANY) return "ANY"; else if(_method & HTTP_GET) return "GET"; else if(_method & HTTP_POST) return "POST"; else if(_method & HTTP_DELETE) return "DELETE"; else if(_method & HTTP_PUT) return "PUT"; else if(_method & HTTP_PATCH) return "PATCH"; else if(_method & HTTP_HEAD) return "HEAD"; else if(_method & HTTP_OPTIONS) return "OPTIONS"; return "UNKNOWN"; }