123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- /*
- Parsing.cpp - HTTP request parsing.
- Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
- */
- #include <Arduino.h>
- #include "WebServer.h"
- #include "WiFiClient.h"
- #include "WiFiServer.h"
- //#define DEBUG_ESP_HTTP_SERVER
- #ifdef DEBUG_ESP_PORT
- #define DEBUG_OUTPUT DEBUG_ESP_PORT
- #else
- #define DEBUG_OUTPUT Serial
- #endif
- static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength,
- size_t& dataLength, int timeout_ms) {
- char* buf = nullptr;
- dataLength = 0;
- while (dataLength < maxLength) {
- int tries = timeout_ms;
- size_t newLength;
- while (!(newLength = client.available()) && tries--) delay(1);
- if (!newLength) {
- break;
- }
- if (!buf) {
- buf = (char*)malloc(newLength + 1);
- if (!buf) {
- return nullptr;
- }
- } else {
- char* newBuf = (char*)realloc(buf, dataLength + newLength + 1);
- if (!newBuf) {
- free(buf);
- return nullptr;
- }
- buf = newBuf;
- }
- client.readBytes(buf + dataLength, newLength);
- dataLength += newLength;
- buf[dataLength] = '\0';
- }
- return buf;
- }
- bool WebServer::_parseRequest(WiFiClient& client) {
- // Read the first line of HTTP request
- String req = client.readStringUntil('\r');
- client.readStringUntil('\n');
- // reset header value
- for (int i = 0; i < _headerKeysCount; ++i) {
- _currentHeaders[i].value = String();
- }
- // First line of HTTP request looks like "GET /path HTTP/1.1"
- // Retrieve the "/path" part by finding the spaces
- int addr_start = req.indexOf(' ');
- int addr_end = req.indexOf(' ', addr_start + 1);
- if (addr_start == -1 || addr_end == -1) {
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Invalid request: ");
- DEBUG_OUTPUT.println(req);
- #endif
- return false;
- }
- String methodStr = req.substring(0, addr_start);
- String url = req.substring(addr_start + 1, addr_end);
- String versionEnd = req.substring(addr_end + 8);
- _currentVersion = atoi(versionEnd.c_str());
- String searchStr = "";
- int hasSearch = url.indexOf('?');
- if (hasSearch != -1) {
- searchStr = urlDecode(url.substring(hasSearch + 1));
- url = url.substring(0, hasSearch);
- }
- _currentUri = url;
- _chunked = false;
- HTTPMethod method = HTTP_GET;
- if (methodStr == "POST") {
- method = HTTP_POST;
- } else if (methodStr == "DELETE") {
- method = HTTP_DELETE;
- } else if (methodStr == "OPTIONS") {
- method = HTTP_OPTIONS;
- } else if (methodStr == "PUT") {
- method = HTTP_PUT;
- } else if (methodStr == "PATCH") {
- method = HTTP_PATCH;
- }
- _currentMethod = method;
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("method: ");
- DEBUG_OUTPUT.print(methodStr);
- DEBUG_OUTPUT.print(" url: ");
- DEBUG_OUTPUT.print(url);
- DEBUG_OUTPUT.print(" search: ");
- DEBUG_OUTPUT.println(searchStr);
- #endif
- // attach handler
- RequestHandler* handler;
- for (handler = _firstHandler; handler; handler = handler->next()) {
- if (handler->canHandle(_currentMethod, _currentUri)) break;
- }
- _currentHandler = handler;
- String formData;
- // below is needed only when POST type request
- if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH ||
- method == HTTP_DELETE) {
- String boundaryStr;
- String headerName;
- String headerValue;
- bool isForm = false;
- bool isEncoded = false;
- uint32_t contentLength = 0;
- // parse headers
- while (1) {
- req = client.readStringUntil('\r');
- client.readStringUntil('\n');
- if (req == "") break; // no moar headers
- int headerDiv = req.indexOf(':');
- if (headerDiv == -1) {
- break;
- }
- headerName = req.substring(0, headerDiv);
- headerValue = req.substring(headerDiv + 1);
- headerValue.trim();
- _collectHeader(headerName.c_str(), headerValue.c_str());
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("headerName: ");
- DEBUG_OUTPUT.println(headerName);
- DEBUG_OUTPUT.print("headerValue: ");
- DEBUG_OUTPUT.println(headerValue);
- #endif
- if (headerName.equalsIgnoreCase("Content-Type")) {
- if (headerValue.startsWith("text/plain")) {
- isForm = false;
- } else if (headerValue.startsWith(
- "application/x-www-form-urlencoded")) {
- isForm = false;
- isEncoded = true;
- } else if (headerValue.startsWith("multipart/")) {
- boundaryStr =
- headerValue.substring(headerValue.indexOf('=') + 1);
- isForm = true;
- }
- } else if (headerName.equalsIgnoreCase("Content-Length")) {
- contentLength = headerValue.toInt();
- } else if (headerName.equalsIgnoreCase("Host")) {
- _hostHeader = headerValue;
- }
- }
- if (!isForm) {
- size_t plainLength;
- char* plainBuf = readBytesWithTimeout(
- client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
- if (plainLength < contentLength) {
- free(plainBuf);
- return false;
- }
- if (contentLength > 0) {
- if (searchStr != "") searchStr += '&';
- if (isEncoded) {
- // url encoded form
- String decoded = urlDecode(plainBuf);
- size_t decodedLen = decoded.length();
- memcpy(plainBuf, decoded.c_str(), decodedLen);
- plainBuf[decodedLen] = 0;
- searchStr += plainBuf;
- }
- _parseArguments(searchStr);
- if (!isEncoded) {
- // plain post json or other data
- RequestArgument& arg = _currentArgs[_currentArgCount++];
- arg.key = "plain";
- arg.value = String(plainBuf);
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Plain: ");
- DEBUG_OUTPUT.println(plainBuf);
- #endif
- free(plainBuf);
- }
- }
- if (isForm) {
- _parseArguments(searchStr);
- if (!_parseForm(client, boundaryStr, contentLength)) {
- return false;
- }
- }
- } else {
- String headerName;
- String headerValue;
- // parse headers
- while (1) {
- req = client.readStringUntil('\r');
- client.readStringUntil('\n');
- if (req == "") break; // no moar headers
- int headerDiv = req.indexOf(':');
- if (headerDiv == -1) {
- break;
- }
- headerName = req.substring(0, headerDiv);
- headerValue = req.substring(headerDiv + 2);
- _collectHeader(headerName.c_str(), headerValue.c_str());
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("headerName: ");
- DEBUG_OUTPUT.println(headerName);
- DEBUG_OUTPUT.print("headerValue: ");
- DEBUG_OUTPUT.println(headerValue);
- #endif
- if (headerName.equalsIgnoreCase("Host")) {
- _hostHeader = headerValue;
- }
- }
- _parseArguments(searchStr);
- }
- client.flush();
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Request: ");
- DEBUG_OUTPUT.println(url);
- DEBUG_OUTPUT.print(" Arguments: ");
- DEBUG_OUTPUT.println(searchStr);
- #endif
- return true;
- }
- bool WebServer::_collectHeader(const char* headerName,
- const char* headerValue) {
- for (int i = 0; i < _headerKeysCount; i++) {
- if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
- _currentHeaders[i].value = headerValue;
- return true;
- }
- }
- return false;
- }
- void WebServer::_parseArguments(String data) {
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("args: ");
- DEBUG_OUTPUT.println(data);
- #endif
- if (_currentArgs) delete[] _currentArgs;
- _currentArgs = 0;
- if (data.length() == 0) {
- _currentArgCount = 0;
- _currentArgs = new RequestArgument[1];
- return;
- }
- _currentArgCount = 1;
- for (int i = 0; i < (int)data.length();) {
- i = data.indexOf('&', i);
- if (i == -1) break;
- ++i;
- ++_currentArgCount;
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("args count: ");
- DEBUG_OUTPUT.println(_currentArgCount);
- #endif
- _currentArgs = new RequestArgument[_currentArgCount + 1];
- int pos = 0;
- int iarg;
- for (iarg = 0; iarg < _currentArgCount;) {
- int equal_sign_index = data.indexOf('=', pos);
- int next_arg_index = data.indexOf('&', pos);
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("pos ");
- DEBUG_OUTPUT.print(pos);
- DEBUG_OUTPUT.print("=@ ");
- DEBUG_OUTPUT.print(equal_sign_index);
- DEBUG_OUTPUT.print(" &@ ");
- DEBUG_OUTPUT.println(next_arg_index);
- #endif
- if ((equal_sign_index == -1) ||
- ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("arg missing value: ");
- DEBUG_OUTPUT.println(iarg);
- #endif
- if (next_arg_index == -1) break;
- pos = next_arg_index + 1;
- continue;
- }
- RequestArgument& arg = _currentArgs[iarg];
- arg.key = data.substring(pos, equal_sign_index);
- arg.value = data.substring(equal_sign_index + 1, next_arg_index);
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("arg ");
- DEBUG_OUTPUT.print(iarg);
- DEBUG_OUTPUT.print(" key: ");
- DEBUG_OUTPUT.print(arg.key);
- DEBUG_OUTPUT.print(" value: ");
- DEBUG_OUTPUT.println(arg.value);
- #endif
- ++iarg;
- if (next_arg_index == -1) break;
- pos = next_arg_index + 1;
- }
- _currentArgCount = iarg;
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("args count: ");
- DEBUG_OUTPUT.println(_currentArgCount);
- #endif
- }
- void WebServer::_uploadWriteByte(uint8_t b) {
- if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN) {
- if (_currentHandler && _currentHandler->canUpload(_currentUri))
- _currentHandler->upload(*this, _currentUri, _currentUpload);
- _currentUpload.totalSize += _currentUpload.currentSize;
- _currentUpload.currentSize = 0;
- }
- _currentUpload.buf[_currentUpload.currentSize++] = b;
- }
- uint8_t WebServer::_uploadReadByte(WiFiClient& client) {
- int res = client.read();
- if (res == -1) {
- while (!client.available() && client.connected()) yield();
- res = client.read();
- }
- return (uint8_t)res;
- }
- bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len) {
- (void)len;
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Parse Form: Boundary: ");
- DEBUG_OUTPUT.print(boundary);
- DEBUG_OUTPUT.print(" Length: ");
- DEBUG_OUTPUT.println(len);
- #endif
- String line;
- int retry = 0;
- do {
- line = client.readStringUntil('\r');
- ++retry;
- } while (line.length() == 0 && retry < 3);
- client.readStringUntil('\n');
- // start reading the form
- if (line == ("--" + boundary)) {
- RequestArgument* postArgs = new RequestArgument[32];
- int postArgsLen = 0;
- while (1) {
- String argName;
- String argValue;
- String argType;
- String argFilename;
- bool argIsFile = false;
- line = client.readStringUntil('\r');
- client.readStringUntil('\n');
- if (line.length() > 19 &&
- line.substring(0, 19).equalsIgnoreCase("Content-Disposition")) {
- int nameStart = line.indexOf('=');
- if (nameStart != -1) {
- argName = line.substring(nameStart + 2);
- nameStart = argName.indexOf('=');
- if (nameStart == -1) {
- argName = argName.substring(0, argName.length() - 1);
- } else {
- argFilename = argName.substring(nameStart + 2,
- argName.length() - 1);
- argName = argName.substring(0, argName.indexOf('"'));
- argIsFile = true;
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("PostArg FileName: ");
- DEBUG_OUTPUT.println(argFilename);
- #endif
- // use GET to set the filename if uploading using blob
- if (argFilename == "blob" && hasArg("filename"))
- argFilename = arg("filename");
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("PostArg Name: ");
- DEBUG_OUTPUT.println(argName);
- #endif
- argType = "text/plain";
- line = client.readStringUntil('\r');
- client.readStringUntil('\n');
- if (line.length() > 12 &&
- line.substring(0, 12).equalsIgnoreCase(
- "Content-Type")) {
- argType = line.substring(line.indexOf(':') + 2);
- // skip next line
- client.readStringUntil('\r');
- client.readStringUntil('\n');
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("PostArg Type: ");
- DEBUG_OUTPUT.println(argType);
- #endif
- if (!argIsFile) {
- while (1) {
- line = client.readStringUntil('\r');
- client.readStringUntil('\n');
- if (line.startsWith("--" + boundary)) break;
- if (argValue.length() > 0) argValue += "\n";
- argValue += line;
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("PostArg Value: ");
- DEBUG_OUTPUT.println(argValue);
- DEBUG_OUTPUT.println();
- #endif
- RequestArgument& arg = postArgs[postArgsLen++];
- arg.key = argName;
- arg.value = argValue;
- if (line == ("--" + boundary + "--")) {
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.println("Done Parsing POST");
- #endif
- break;
- }
- } else {
- _currentUpload.status = UPLOAD_FILE_START;
- _currentUpload.name = argName;
- _currentUpload.filename = argFilename;
- _currentUpload.type = argType;
- _currentUpload.totalSize = 0;
- _currentUpload.currentSize = 0;
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Start File: ");
- DEBUG_OUTPUT.print(_currentUpload.filename);
- DEBUG_OUTPUT.print(" Type: ");
- DEBUG_OUTPUT.println(_currentUpload.type);
- #endif
- if (_currentHandler &&
- _currentHandler->canUpload(_currentUri))
- _currentHandler->upload(*this, _currentUri,
- _currentUpload);
- _currentUpload.status = UPLOAD_FILE_WRITE;
- uint8_t argByte = _uploadReadByte(client);
- readfile:
- while (argByte != 0x0D) {
- if (!client.connected())
- return _parseFormUploadAborted();
- _uploadWriteByte(argByte);
- argByte = _uploadReadByte(client);
- }
- argByte = _uploadReadByte(client);
- if (!client.connected())
- return _parseFormUploadAborted();
- if (argByte == 0x0A) {
- argByte = _uploadReadByte(client);
- if (!client.connected())
- return _parseFormUploadAborted();
- if ((char)argByte != '-') {
- // continue reading the file
- _uploadWriteByte(0x0D);
- _uploadWriteByte(0x0A);
- goto readfile;
- } else {
- argByte = _uploadReadByte(client);
- if (!client.connected())
- return _parseFormUploadAborted();
- if ((char)argByte != '-') {
- // continue reading the file
- _uploadWriteByte(0x0D);
- _uploadWriteByte(0x0A);
- _uploadWriteByte((uint8_t)('-'));
- goto readfile;
- }
- }
- uint8_t endBuf[boundary.length()];
- client.readBytes(endBuf, boundary.length());
- if (strstr((const char*)endBuf, boundary.c_str()) !=
- NULL) {
- if (_currentHandler &&
- _currentHandler->canUpload(_currentUri))
- _currentHandler->upload(*this, _currentUri,
- _currentUpload);
- _currentUpload.totalSize +=
- _currentUpload.currentSize;
- _currentUpload.status = UPLOAD_FILE_END;
- if (_currentHandler &&
- _currentHandler->canUpload(_currentUri))
- _currentHandler->upload(*this, _currentUri,
- _currentUpload);
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("End File: ");
- DEBUG_OUTPUT.print(_currentUpload.filename);
- DEBUG_OUTPUT.print(" Type: ");
- DEBUG_OUTPUT.print(_currentUpload.type);
- DEBUG_OUTPUT.print(" Size: ");
- DEBUG_OUTPUT.println(_currentUpload.totalSize);
- #endif
- line = client.readStringUntil(0x0D);
- client.readStringUntil(0x0A);
- if (line == "--") {
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.println("Done Parsing POST");
- #endif
- break;
- }
- continue;
- } else {
- _uploadWriteByte(0x0D);
- _uploadWriteByte(0x0A);
- _uploadWriteByte((uint8_t)('-'));
- _uploadWriteByte((uint8_t)('-'));
- uint32_t i = 0;
- while (i < boundary.length()) {
- _uploadWriteByte(endBuf[i++]);
- }
- argByte = _uploadReadByte(client);
- goto readfile;
- }
- } else {
- _uploadWriteByte(0x0D);
- goto readfile;
- }
- break;
- }
- }
- }
- }
- int iarg;
- int totalArgs = ((32 - postArgsLen) < _currentArgCount)
- ? (32 - postArgsLen)
- : _currentArgCount;
- for (iarg = 0; iarg < totalArgs; iarg++) {
- RequestArgument& arg = postArgs[postArgsLen++];
- arg.key = _currentArgs[iarg].key;
- arg.value = _currentArgs[iarg].value;
- }
- if (_currentArgs) delete[] _currentArgs;
- _currentArgs = new RequestArgument[postArgsLen];
- for (iarg = 0; iarg < postArgsLen; iarg++) {
- RequestArgument& arg = _currentArgs[iarg];
- arg.key = postArgs[iarg].key;
- arg.value = postArgs[iarg].value;
- }
- _currentArgCount = iarg;
- if (postArgs) delete[] postArgs;
- return true;
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.print("Error: line: ");
- DEBUG_OUTPUT.println(line);
- #endif
- return false;
- }
- String WebServer::urlDecode(const String& text) {
- String decoded = "";
- char temp[] = "0x00";
- unsigned int len = text.length();
- unsigned int i = 0;
- while (i < len) {
- char decodedChar;
- char encodedChar = text.charAt(i++);
- if ((encodedChar == '%') && (i + 1 < len)) {
- temp[2] = text.charAt(i++);
- temp[3] = text.charAt(i++);
- decodedChar = strtol(temp, NULL, 16);
- } else {
- if (encodedChar == '+') {
- decodedChar = ' ';
- } else {
- decodedChar = encodedChar; // normal ascii char
- }
- }
- decoded += decodedChar;
- }
- return decoded;
- }
- bool WebServer::_parseFormUploadAborted() {
- _currentUpload.status = UPLOAD_FILE_ABORTED;
- if (_currentHandler && _currentHandler->canUpload(_currentUri))
- _currentHandler->upload(*this, _currentUri, _currentUpload);
- return false;
- }
|