* feat(webserver): Middleware with default middleware for cors, authc, curl-like logging * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: Rodrigo Garcia <rodrigo.garcia@espressif.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
622 lines
20 KiB
C++
622 lines
20 KiB
C++
/*
|
|
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 <esp32-hal-log.h>
|
|
#include "NetworkServer.h"
|
|
#include "NetworkClient.h"
|
|
#include "WebServer.h"
|
|
#include "detail/mimetable.h"
|
|
|
|
#ifndef WEBSERVER_MAX_POST_ARGS
|
|
#define WEBSERVER_MAX_POST_ARGS 32
|
|
#endif
|
|
|
|
#define __STR(a) #a
|
|
#define _STR(a) __STR(a)
|
|
static const char *_http_method_str[] = {
|
|
#define XX(num, name, string) _STR(name),
|
|
HTTP_METHOD_MAP(XX)
|
|
#undef XX
|
|
};
|
|
|
|
static const char Content_Type[] PROGMEM = "Content-Type";
|
|
static const char filename[] PROGMEM = "filename";
|
|
|
|
static char *readBytesWithTimeout(NetworkClient &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(NetworkClient &client) {
|
|
// Read the first line of HTTP request
|
|
String req = client.readStringUntil('\r');
|
|
client.readStringUntil('\n');
|
|
//reset header value
|
|
if (_collectAllHeaders) {
|
|
// clear previous headers
|
|
collectAllHeaders();
|
|
} else {
|
|
// clear previous headers
|
|
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
|
|
header->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) {
|
|
log_e("Invalid request: %s", req.c_str());
|
|
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 = url.substring(hasSearch + 1);
|
|
url = url.substring(0, hasSearch);
|
|
}
|
|
_currentUri = url;
|
|
_chunked = false;
|
|
_clientContentLength = 0; // not known yet, or invalid
|
|
|
|
HTTPMethod method = HTTP_ANY;
|
|
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
|
for (size_t i = 0; i < num_methods; i++) {
|
|
if (methodStr == _http_method_str[i]) {
|
|
method = (HTTPMethod)i;
|
|
break;
|
|
}
|
|
}
|
|
if (method == HTTP_ANY) {
|
|
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
|
return false;
|
|
}
|
|
_currentMethod = method;
|
|
|
|
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
|
|
|
//attach handler
|
|
RequestHandler *handler;
|
|
for (handler = _firstHandler; handler; handler = handler->next()) {
|
|
if (handler->canHandle(*this, _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;
|
|
//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());
|
|
|
|
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
|
|
using namespace mime;
|
|
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
|
|
isForm = false;
|
|
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
|
|
isForm = false;
|
|
isEncoded = true;
|
|
} else if (headerValue.startsWith(F("multipart/"))) {
|
|
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
|
boundaryStr.replace("\"", "");
|
|
isForm = true;
|
|
}
|
|
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
|
|
_clientContentLength = headerValue.toInt();
|
|
} else if (headerName.equalsIgnoreCase(F("Host"))) {
|
|
_hostHeader = headerValue;
|
|
}
|
|
}
|
|
|
|
if (!isForm && _currentHandler && _currentHandler->canRaw(*this, _currentUri)) {
|
|
log_v("Parse raw");
|
|
_currentRaw.reset(new HTTPRaw());
|
|
_currentRaw->status = RAW_START;
|
|
_currentRaw->totalSize = 0;
|
|
_currentRaw->currentSize = 0;
|
|
log_v("Start Raw");
|
|
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
|
_currentRaw->status = RAW_WRITE;
|
|
|
|
while (_currentRaw->totalSize < _clientContentLength) {
|
|
size_t read_len = std::min(_clientContentLength - _currentRaw->totalSize, (size_t)HTTP_RAW_BUFLEN);
|
|
_currentRaw->currentSize = client.readBytes(_currentRaw->buf, read_len);
|
|
_currentRaw->totalSize += _currentRaw->currentSize;
|
|
if (_currentRaw->currentSize == 0) {
|
|
_currentRaw->status = RAW_ABORTED;
|
|
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
|
return false;
|
|
}
|
|
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
|
}
|
|
_currentRaw->status = RAW_END;
|
|
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
|
log_v("Finish Raw");
|
|
} else if (!isForm) {
|
|
size_t plainLength;
|
|
char *plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
|
|
if (plainLength < _clientContentLength) {
|
|
free(plainBuf);
|
|
return false;
|
|
}
|
|
if (_clientContentLength > 0) {
|
|
if (isEncoded) {
|
|
//url encoded form
|
|
if (searchStr != "") {
|
|
searchStr += '&';
|
|
}
|
|
searchStr += plainBuf;
|
|
}
|
|
_parseArguments(searchStr);
|
|
if (!isEncoded) {
|
|
//plain post json or other data
|
|
RequestArgument &arg = _currentArgs[_currentArgCount++];
|
|
arg.key = F("plain");
|
|
arg.value = String(plainBuf);
|
|
}
|
|
|
|
log_v("Plain: %s", plainBuf);
|
|
free(plainBuf);
|
|
} else {
|
|
// No content - but we can still have arguments in the URL.
|
|
_parseArguments(searchStr);
|
|
}
|
|
} else {
|
|
// it IS a form
|
|
_parseArguments(searchStr);
|
|
if (!_parseForm(client, boundaryStr, _clientContentLength)) {
|
|
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());
|
|
|
|
if (headerName.equalsIgnoreCase("Host")) {
|
|
_hostHeader = headerValue;
|
|
}
|
|
}
|
|
_parseArguments(searchStr);
|
|
}
|
|
client.clear();
|
|
|
|
log_v("Request: %s", url.c_str());
|
|
log_v(" Arguments: %s", searchStr.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
|
|
RequestArgument *last = nullptr;
|
|
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
|
|
if (header->next == nullptr) {
|
|
last = header;
|
|
}
|
|
if (header->key.equalsIgnoreCase(headerName)) {
|
|
header->value = headerValue;
|
|
log_v("header collected: %s: %s", headerName, headerValue);
|
|
return true;
|
|
}
|
|
}
|
|
assert(last);
|
|
if (_collectAllHeaders) {
|
|
last->next = new RequestArgument();
|
|
last->next->key = headerName;
|
|
last->next->value = headerValue;
|
|
_headerKeysCount++;
|
|
log_v("header collected: %s: %s", headerName, headerValue);
|
|
return true;
|
|
}
|
|
|
|
log_v("header skipped: %s: %s", headerName, headerValue);
|
|
|
|
return false;
|
|
}
|
|
|
|
void WebServer::_parseArguments(const String &data) {
|
|
log_v("args: %s", data.c_str());
|
|
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;
|
|
}
|
|
log_v("args count: %d", _currentArgCount);
|
|
|
|
_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);
|
|
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
|
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
|
log_e("arg missing value: %d", iarg);
|
|
if (next_arg_index == -1) {
|
|
break;
|
|
}
|
|
pos = next_arg_index + 1;
|
|
continue;
|
|
}
|
|
RequestArgument &arg = _currentArgs[iarg];
|
|
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
|
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
|
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
|
++iarg;
|
|
if (next_arg_index == -1) {
|
|
break;
|
|
}
|
|
pos = next_arg_index + 1;
|
|
}
|
|
_currentArgCount = iarg;
|
|
log_v("args count: %d", _currentArgCount);
|
|
}
|
|
|
|
void WebServer::_uploadWriteByte(uint8_t b) {
|
|
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
|
|
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
|
}
|
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
|
_currentUpload->currentSize = 0;
|
|
}
|
|
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
|
}
|
|
|
|
int WebServer::_uploadReadByte(NetworkClient &client) {
|
|
int res = client.read();
|
|
|
|
if (res < 0) {
|
|
// keep trying until you either read a valid byte or timeout
|
|
const unsigned long startMillis = millis();
|
|
const long timeoutIntervalMillis = client.getTimeout();
|
|
bool timedOut = false;
|
|
for (;;) {
|
|
if (!client.connected()) {
|
|
return -1;
|
|
}
|
|
// loosely modeled after blinkWithoutDelay pattern
|
|
while (!timedOut && !client.available() && client.connected()) {
|
|
delay(2);
|
|
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
|
}
|
|
|
|
res = client.read();
|
|
if (res >= 0) {
|
|
return res; // exit on a valid read
|
|
}
|
|
// NOTE: it is possible to get here and have all of the following
|
|
// assertions hold true
|
|
//
|
|
// -- client.available() > 0
|
|
// -- client.connected == true
|
|
// -- res == -1
|
|
//
|
|
// a simple retry strategy overcomes this which is to say the
|
|
// assertion is not permanent, but the reason that this works
|
|
// is elusive, and possibly indicative of a more subtle underlying
|
|
// issue
|
|
|
|
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
|
if (timedOut) {
|
|
return res; // exit on a timeout
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool WebServer::_parseForm(NetworkClient &client, const String &boundary, uint32_t len) {
|
|
(void)len;
|
|
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
|
|
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)) {
|
|
if (_postArgs) {
|
|
delete[] _postArgs;
|
|
}
|
|
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
|
_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(F("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;
|
|
log_v("PostArg FileName: %s", argFilename.c_str());
|
|
//use GET to set the filename if uploading using blob
|
|
if (argFilename == F("blob") && hasArg(FPSTR(filename))) {
|
|
argFilename = arg(FPSTR(filename));
|
|
}
|
|
}
|
|
log_v("PostArg Name: %s", argName.c_str());
|
|
using namespace mime;
|
|
argType = FPSTR(mimeTable[txt].mimeType);
|
|
line = client.readStringUntil('\r');
|
|
client.readStringUntil('\n');
|
|
while (line.length() > 0) {
|
|
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
|
|
argType = line.substring(line.indexOf(':') + 2);
|
|
}
|
|
//skip over any other headers
|
|
line = client.readStringUntil('\r');
|
|
client.readStringUntil('\n');
|
|
}
|
|
log_v("PostArg Type: %s", argType.c_str());
|
|
if (!argIsFile) {
|
|
while (1) {
|
|
line = client.readStringUntil('\r');
|
|
client.readStringUntil('\n');
|
|
if (line.startsWith("--" + boundary)) {
|
|
break;
|
|
}
|
|
if (argValue.length() > 0) {
|
|
argValue += "\n";
|
|
}
|
|
argValue += line;
|
|
}
|
|
log_v("PostArg Value: %s", argValue.c_str());
|
|
|
|
RequestArgument &arg = _postArgs[_postArgsLen++];
|
|
arg.key = argName;
|
|
arg.value = argValue;
|
|
|
|
if (line == ("--" + boundary + "--")) {
|
|
log_v("Done Parsing POST");
|
|
break;
|
|
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
|
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
|
return false;
|
|
}
|
|
} else {
|
|
_currentUpload.reset(new HTTPUpload());
|
|
_currentUpload->status = UPLOAD_FILE_START;
|
|
_currentUpload->name = argName;
|
|
_currentUpload->filename = argFilename;
|
|
_currentUpload->type = argType;
|
|
_currentUpload->totalSize = 0;
|
|
_currentUpload->currentSize = 0;
|
|
log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str());
|
|
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
|
}
|
|
_currentUpload->status = UPLOAD_FILE_WRITE;
|
|
|
|
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
|
|
char fastBoundary[fastBoundaryLen];
|
|
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
|
|
int boundaryPtr = 0;
|
|
while (true) {
|
|
int ret = _uploadReadByte(client);
|
|
if (ret < 0) {
|
|
// Unexpected, we should have had data available per above
|
|
return _parseFormUploadAborted();
|
|
}
|
|
char in = (char)ret;
|
|
if (in == fastBoundary[boundaryPtr]) {
|
|
// The input matched the current expected character, advance and possibly exit this file
|
|
boundaryPtr++;
|
|
if (boundaryPtr == fastBoundaryLen - 1) {
|
|
// We read the whole boundary line, we're done here!
|
|
break;
|
|
}
|
|
} else {
|
|
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
|
|
for (int i = 0; i < boundaryPtr; i++) {
|
|
_uploadWriteByte(fastBoundary[i]);
|
|
}
|
|
if (in == fastBoundary[0]) {
|
|
// This could be the start of the real end, mark it so and don't emit/skip it
|
|
boundaryPtr = 1;
|
|
} else {
|
|
// Not the 1st char of our pattern, so emit and ignore
|
|
_uploadWriteByte(in);
|
|
boundaryPtr = 0;
|
|
}
|
|
}
|
|
}
|
|
// Found the boundary string, finish processing this file upload
|
|
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
|
}
|
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
|
_currentUpload->status = UPLOAD_FILE_END;
|
|
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
|
}
|
|
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), (int)_currentUpload->totalSize);
|
|
if (!client.connected()) {
|
|
return _parseFormUploadAborted();
|
|
}
|
|
line = client.readStringUntil('\r');
|
|
client.readStringUntil('\n');
|
|
if (line == "--") { // extra two dashes mean we reached the end of all form fields
|
|
log_v("Done Parsing POST");
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int iarg;
|
|
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount) ? (WEBSERVER_MAX_POST_ARGS - _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;
|
|
_postArgs = nullptr;
|
|
_postArgsLen = 0;
|
|
}
|
|
return true;
|
|
}
|
|
log_e("Error: line: %s", line.c_str());
|
|
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(*this, _currentUri)) {
|
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
|
}
|
|
return false;
|
|
}
|