875 lines
28 KiB
C++
875 lines
28 KiB
C++
/*
|
|
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 "cbuf.h"
|
|
|
|
using namespace asyncsrv;
|
|
|
|
// Since ESP8266 does not link memchr by default, here's its implementation.
|
|
void* memchr(void* ptr, int ch, size_t count) {
|
|
unsigned char* p = static_cast<unsigned char*>(ptr);
|
|
while (count--)
|
|
if (*p++ == static_cast<unsigned char>(ch))
|
|
return --p;
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* Abstract Response
|
|
*
|
|
*/
|
|
|
|
#ifndef ESP8266
|
|
const char* AsyncWebServerResponse::responseCodeToString(int code) {
|
|
switch (code) {
|
|
case 100:
|
|
return T_HTTP_CODE_100;
|
|
case 101:
|
|
return T_HTTP_CODE_101;
|
|
case 200:
|
|
return T_HTTP_CODE_200;
|
|
case 201:
|
|
return T_HTTP_CODE_201;
|
|
case 202:
|
|
return T_HTTP_CODE_202;
|
|
case 203:
|
|
return T_HTTP_CODE_203;
|
|
case 204:
|
|
return T_HTTP_CODE_204;
|
|
case 205:
|
|
return T_HTTP_CODE_205;
|
|
case 206:
|
|
return T_HTTP_CODE_206;
|
|
case 300:
|
|
return T_HTTP_CODE_300;
|
|
case 301:
|
|
return T_HTTP_CODE_301;
|
|
case 302:
|
|
return T_HTTP_CODE_302;
|
|
case 303:
|
|
return T_HTTP_CODE_303;
|
|
case 304:
|
|
return T_HTTP_CODE_304;
|
|
case 305:
|
|
return T_HTTP_CODE_305;
|
|
case 307:
|
|
return T_HTTP_CODE_307;
|
|
case 400:
|
|
return T_HTTP_CODE_400;
|
|
case 401:
|
|
return T_HTTP_CODE_401;
|
|
case 402:
|
|
return T_HTTP_CODE_402;
|
|
case 403:
|
|
return T_HTTP_CODE_403;
|
|
case 404:
|
|
return T_HTTP_CODE_404;
|
|
case 405:
|
|
return T_HTTP_CODE_405;
|
|
case 406:
|
|
return T_HTTP_CODE_406;
|
|
case 407:
|
|
return T_HTTP_CODE_407;
|
|
case 408:
|
|
return T_HTTP_CODE_408;
|
|
case 409:
|
|
return T_HTTP_CODE_409;
|
|
case 410:
|
|
return T_HTTP_CODE_410;
|
|
case 411:
|
|
return T_HTTP_CODE_411;
|
|
case 412:
|
|
return T_HTTP_CODE_412;
|
|
case 413:
|
|
return T_HTTP_CODE_413;
|
|
case 414:
|
|
return T_HTTP_CODE_414;
|
|
case 415:
|
|
return T_HTTP_CODE_415;
|
|
case 416:
|
|
return T_HTTP_CODE_416;
|
|
case 417:
|
|
return T_HTTP_CODE_417;
|
|
case 500:
|
|
return T_HTTP_CODE_500;
|
|
case 501:
|
|
return T_HTTP_CODE_501;
|
|
case 502:
|
|
return T_HTTP_CODE_502;
|
|
case 503:
|
|
return T_HTTP_CODE_503;
|
|
case 504:
|
|
return T_HTTP_CODE_504;
|
|
case 505:
|
|
return T_HTTP_CODE_505;
|
|
default:
|
|
return T_HTTP_CODE_ANY;
|
|
}
|
|
}
|
|
#else // ESP8266
|
|
const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code)
|
|
{
|
|
switch (code) {
|
|
case 100:
|
|
return FPSTR(T_HTTP_CODE_100);
|
|
case 101:
|
|
return FPSTR(T_HTTP_CODE_101);
|
|
case 200:
|
|
return FPSTR(T_HTTP_CODE_200);
|
|
case 201:
|
|
return FPSTR(T_HTTP_CODE_201);
|
|
case 202:
|
|
return FPSTR(T_HTTP_CODE_202);
|
|
case 203:
|
|
return FPSTR(T_HTTP_CODE_203);
|
|
case 204:
|
|
return FPSTR(T_HTTP_CODE_204);
|
|
case 205:
|
|
return FPSTR(T_HTTP_CODE_205);
|
|
case 206:
|
|
return FPSTR(T_HTTP_CODE_206);
|
|
case 300:
|
|
return FPSTR(T_HTTP_CODE_300);
|
|
case 301:
|
|
return FPSTR(T_HTTP_CODE_301);
|
|
case 302:
|
|
return FPSTR(T_HTTP_CODE_302);
|
|
case 303:
|
|
return FPSTR(T_HTTP_CODE_303);
|
|
case 304:
|
|
return FPSTR(T_HTTP_CODE_304);
|
|
case 305:
|
|
return FPSTR(T_HTTP_CODE_305);
|
|
case 307:
|
|
return FPSTR(T_HTTP_CODE_307);
|
|
case 400:
|
|
return FPSTR(T_HTTP_CODE_400);
|
|
case 401:
|
|
return FPSTR(T_HTTP_CODE_401);
|
|
case 402:
|
|
return FPSTR(T_HTTP_CODE_402);
|
|
case 403:
|
|
return FPSTR(T_HTTP_CODE_403);
|
|
case 404:
|
|
return FPSTR(T_HTTP_CODE_404);
|
|
case 405:
|
|
return FPSTR(T_HTTP_CODE_405);
|
|
case 406:
|
|
return FPSTR(T_HTTP_CODE_406);
|
|
case 407:
|
|
return FPSTR(T_HTTP_CODE_407);
|
|
case 408:
|
|
return FPSTR(T_HTTP_CODE_408);
|
|
case 409:
|
|
return FPSTR(T_HTTP_CODE_409);
|
|
case 410:
|
|
return FPSTR(T_HTTP_CODE_410);
|
|
case 411:
|
|
return FPSTR(T_HTTP_CODE_411);
|
|
case 412:
|
|
return FPSTR(T_HTTP_CODE_412);
|
|
case 413:
|
|
return FPSTR(T_HTTP_CODE_413);
|
|
case 414:
|
|
return FPSTR(T_HTTP_CODE_414);
|
|
case 415:
|
|
return FPSTR(T_HTTP_CODE_415);
|
|
case 416:
|
|
return FPSTR(T_HTTP_CODE_416);
|
|
case 417:
|
|
return FPSTR(T_HTTP_CODE_417);
|
|
case 500:
|
|
return FPSTR(T_HTTP_CODE_500);
|
|
case 501:
|
|
return FPSTR(T_HTTP_CODE_501);
|
|
case 502:
|
|
return FPSTR(T_HTTP_CODE_502);
|
|
case 503:
|
|
return FPSTR(T_HTTP_CODE_503);
|
|
case 504:
|
|
return FPSTR(T_HTTP_CODE_504);
|
|
case 505:
|
|
return FPSTR(T_HTTP_CODE_505);
|
|
default:
|
|
return FPSTR(T_HTTP_CODE_ANY);
|
|
}
|
|
}
|
|
#endif // ESP8266
|
|
|
|
AsyncWebServerResponse::AsyncWebServerResponse()
|
|
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
|
|
for (const auto& header : DefaultHeaders::Instance()) {
|
|
_headers.emplace_back(header);
|
|
}
|
|
}
|
|
|
|
AsyncWebServerResponse::~AsyncWebServerResponse() = default;
|
|
|
|
void AsyncWebServerResponse::setCode(int code) {
|
|
if (_state == RESPONSE_SETUP)
|
|
_code = code;
|
|
}
|
|
|
|
void AsyncWebServerResponse::setContentLength(size_t len) {
|
|
if (_state == RESPONSE_SETUP)
|
|
_contentLength = len;
|
|
}
|
|
|
|
void AsyncWebServerResponse::setContentType(const char* type) {
|
|
if (_state == RESPONSE_SETUP)
|
|
_contentType = type;
|
|
}
|
|
|
|
bool AsyncWebServerResponse::removeHeader(const char* name) {
|
|
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
|
if (i->name().equalsIgnoreCase(name)) {
|
|
_headers.erase(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) {
|
|
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
|
if (i->name().equalsIgnoreCase(name)) {
|
|
// header already set
|
|
if (replaceExisting) {
|
|
// remove, break and add the new one
|
|
_headers.erase(i);
|
|
break;
|
|
} else {
|
|
// do not update
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// header was not found found, or existing one was removed
|
|
_headers.emplace_back(name, value);
|
|
return true;
|
|
}
|
|
|
|
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
|
|
if (version) {
|
|
addHeader(T_Accept_Ranges, T_none, false);
|
|
if (_chunked)
|
|
addHeader(T_Transfer_Encoding, T_chunked, false);
|
|
}
|
|
String out;
|
|
constexpr size_t bufSize = 300;
|
|
char buf[bufSize];
|
|
|
|
#ifndef ESP8266
|
|
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, responseCodeToString(_code));
|
|
#else
|
|
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, String(responseCodeToString(_code)).c_str());
|
|
#endif
|
|
out.concat(buf);
|
|
|
|
if (_sendContentLength) {
|
|
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
|
|
out.concat(buf);
|
|
}
|
|
if (_contentType.length()) {
|
|
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
|
|
out.concat(buf);
|
|
}
|
|
|
|
for (const auto& header : _headers) {
|
|
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
|
|
out.concat(buf);
|
|
}
|
|
_headers.clear();
|
|
|
|
out.concat(T_rn);
|
|
_headLength = out.length();
|
|
return out;
|
|
}
|
|
|
|
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
|
|
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
|
|
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
|
|
bool AsyncWebServerResponse::_sourceValid() const { return false; }
|
|
void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) {
|
|
_state = RESPONSE_END;
|
|
request->client()->close();
|
|
}
|
|
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
|
(void)request;
|
|
(void)len;
|
|
(void)time;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* String/Code Response
|
|
* */
|
|
AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) {
|
|
_code = code;
|
|
_content = content;
|
|
_contentType = contentType;
|
|
if (_content.length()) {
|
|
_contentLength = _content.length();
|
|
if (!_contentType.length())
|
|
_contentType = T_text_plain;
|
|
}
|
|
addHeader(T_Connection, T_close, false);
|
|
}
|
|
|
|
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
|
|
_state = RESPONSE_HEADERS;
|
|
String out = _assembleHead(request->version());
|
|
size_t outLen = out.length();
|
|
size_t space = request->client()->space();
|
|
if (!_contentLength && space >= outLen) {
|
|
_writtenLength += request->client()->write(out.c_str(), outLen);
|
|
_state = RESPONSE_WAIT_ACK;
|
|
} else if (_contentLength && space >= outLen + _contentLength) {
|
|
out += _content;
|
|
outLen += _contentLength;
|
|
_writtenLength += request->client()->write(out.c_str(), outLen);
|
|
_state = RESPONSE_WAIT_ACK;
|
|
} else if (space && space < outLen) {
|
|
String partial = out.substring(0, space);
|
|
_content = out.substring(space) + _content;
|
|
_contentLength += outLen - space;
|
|
_writtenLength += 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);
|
|
_writtenLength += 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) {
|
|
(void)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) {
|
|
_writtenLength += request->client()->write(_content.c_str(), available);
|
|
_content = emptyString;
|
|
_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;
|
|
_writtenLength += request->client()->write(out.c_str(), space);
|
|
return space;
|
|
} else if (_state == RESPONSE_WAIT_ACK) {
|
|
if (_ackedLength >= _writtenLength) {
|
|
_state = RESPONSE_END;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Abstract Response
|
|
* */
|
|
|
|
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
|
|
// In case of template processing, we're unable to determine real response size
|
|
if (callback) {
|
|
_contentLength = 0;
|
|
_sendContentLength = false;
|
|
_chunked = true;
|
|
}
|
|
}
|
|
|
|
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
|
|
addHeader(T_Connection, T_close, false);
|
|
_head = _assembleHead(request->version());
|
|
_state = RESPONSE_HEADERS;
|
|
_ack(request, 0, 0);
|
|
}
|
|
|
|
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
|
(void)time;
|
|
if (!_sourceValid()) {
|
|
_state = RESPONSE_FAILED;
|
|
request->client()->close();
|
|
return 0;
|
|
}
|
|
_ackedLength += len;
|
|
size_t space = request->client()->space();
|
|
|
|
size_t headLen = _head.length();
|
|
if (_state == RESPONSE_HEADERS) {
|
|
if (space >= headLen) {
|
|
_state = RESPONSE_CONTENT;
|
|
space -= headLen;
|
|
} else {
|
|
String out = _head.substring(0, space);
|
|
_head = _head.substring(space);
|
|
_writtenLength += request->client()->write(out.c_str(), out.length());
|
|
return out.length();
|
|
}
|
|
}
|
|
|
|
if (_state == RESPONSE_CONTENT) {
|
|
size_t outLen;
|
|
if (_chunked) {
|
|
if (space <= 8) {
|
|
return 0;
|
|
}
|
|
outLen = space;
|
|
} else if (!_sendContentLength) {
|
|
outLen = space;
|
|
} else {
|
|
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
|
|
}
|
|
|
|
uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
|
|
if (!buf) {
|
|
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
|
return 0;
|
|
}
|
|
|
|
if (headLen) {
|
|
memcpy(buf, _head.c_str(), _head.length());
|
|
}
|
|
|
|
size_t readLen = 0;
|
|
|
|
if (_chunked) {
|
|
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
|
|
// See RFC2616 sections 2, 3.6.1.
|
|
readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
|
|
if (readLen == RESPONSE_TRY_AGAIN) {
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen;
|
|
while (outLen < headLen + 4)
|
|
buf[outLen++] = ' ';
|
|
buf[outLen++] = '\r';
|
|
buf[outLen++] = '\n';
|
|
outLen += readLen;
|
|
buf[outLen++] = '\r';
|
|
buf[outLen++] = '\n';
|
|
} else {
|
|
readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
|
|
if (readLen == RESPONSE_TRY_AGAIN) {
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
outLen = readLen + headLen;
|
|
}
|
|
|
|
if (headLen) {
|
|
_head = emptyString;
|
|
}
|
|
|
|
if (outLen) {
|
|
_writtenLength += request->client()->write((const char*)buf, outLen);
|
|
}
|
|
|
|
if (_chunked) {
|
|
_sentLength += readLen;
|
|
} else {
|
|
_sentLength += outLen - headLen;
|
|
}
|
|
|
|
free(buf);
|
|
|
|
if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
|
|
_state = RESPONSE_WAIT_ACK;
|
|
}
|
|
return outLen;
|
|
|
|
} else if (_state == RESPONSE_WAIT_ACK) {
|
|
if (!_sendContentLength || _ackedLength >= _writtenLength) {
|
|
_state = RESPONSE_END;
|
|
if (!_chunked && !_sendContentLength)
|
|
request->client()->close(true);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) {
|
|
// If we have something in cache, copy it to buffer
|
|
const size_t readFromCache = std::min(len, _cache.size());
|
|
if (readFromCache) {
|
|
memcpy(data, _cache.data(), readFromCache);
|
|
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
|
|
}
|
|
// If we need to read more...
|
|
const size_t needFromFile = len - readFromCache;
|
|
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
|
|
return readFromCache + readFromContent;
|
|
}
|
|
|
|
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
|
|
if (!_callback)
|
|
return _fillBuffer(data, len);
|
|
|
|
const size_t originalLen = len;
|
|
len = _readDataFromCacheOrContent(data, len);
|
|
// Now we've read 'len' bytes, either from cache or from file
|
|
// Search for template placeholders
|
|
uint8_t* pTemplateStart = data;
|
|
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
|
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
|
// temporary buffer to hold parameter name
|
|
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
|
String paramName;
|
|
// If closing placeholder is found:
|
|
if (pTemplateEnd) {
|
|
// prepare argument to callback
|
|
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
|
|
if (paramNameLength) {
|
|
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
|
buf[paramNameLength] = 0;
|
|
paramName = String(reinterpret_cast<char*>(buf));
|
|
} else { // double percent sign encountered, this is single percent sign escaped.
|
|
// remove the 2nd percent sign
|
|
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
|
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
|
++pTemplateStart;
|
|
}
|
|
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
|
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
|
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
|
if (readFromCacheOrContent) {
|
|
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
|
if (pTemplateEnd) {
|
|
// prepare argument to callback
|
|
*pTemplateEnd = 0;
|
|
paramName = String(reinterpret_cast<char*>(buf));
|
|
// Copy remaining read-ahead data into cache
|
|
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
|
pTemplateEnd = &data[len - 1];
|
|
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
|
{
|
|
// but first, store read file data in cache
|
|
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
|
++pTemplateStart;
|
|
}
|
|
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
++pTemplateStart;
|
|
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
++pTemplateStart;
|
|
if (paramName.length()) {
|
|
// call callback and replace with result.
|
|
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
|
// Data after pTemplateEnd may need to be moved.
|
|
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
|
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
|
const String paramValue(_callback(paramName));
|
|
const char* pvstr = paramValue.c_str();
|
|
const unsigned int pvlen = paramValue.length();
|
|
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
|
// make room for param value
|
|
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
|
if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
|
|
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
|
// 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
|
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
|
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
|
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
|
// 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
|
// Move the entire data after the placeholder
|
|
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
|
// 3. replace placeholder with actual value
|
|
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
|
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
|
if (numBytesCopied < pvlen) {
|
|
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
|
|
} else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
|
// there is some free room, fill it from cache
|
|
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
|
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
|
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
|
} else { // result is copied fully; it is longer than placeholder text
|
|
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
|
len = std::min(len + roomTaken, originalLen);
|
|
}
|
|
}
|
|
} // while(pTemplateStart)
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* File Response
|
|
* */
|
|
|
|
AsyncFileResponse::~AsyncFileResponse() {
|
|
if (_content)
|
|
_content.close();
|
|
}
|
|
|
|
void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
|
|
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
|
#ifndef ESP8266
|
|
extern const char* getContentType(const String& path);
|
|
#else
|
|
extern const __FlashStringHelper* getContentType(const String& path);
|
|
#endif
|
|
_contentType = getContentType(path);
|
|
#else
|
|
if (path.endsWith(T__html))
|
|
_contentType = T_text_html;
|
|
else if (path.endsWith(T__htm))
|
|
_contentType = T_text_html;
|
|
else if (path.endsWith(T__css))
|
|
_contentType = T_text_css;
|
|
else if (path.endsWith(T__json))
|
|
_contentType = T_application_json;
|
|
else if (path.endsWith(T__js))
|
|
_contentType = T_application_javascript;
|
|
else if (path.endsWith(T__png))
|
|
_contentType = T_image_png;
|
|
else if (path.endsWith(T__gif))
|
|
_contentType = T_image_gif;
|
|
else if (path.endsWith(T__jpg))
|
|
_contentType = T_image_jpeg;
|
|
else if (path.endsWith(T__ico))
|
|
_contentType = T_image_x_icon;
|
|
else if (path.endsWith(T__svg))
|
|
_contentType = T_image_svg_xml;
|
|
else if (path.endsWith(T__eot))
|
|
_contentType = T_font_eot;
|
|
else if (path.endsWith(T__woff))
|
|
_contentType = T_font_woff;
|
|
else if (path.endsWith(T__woff2))
|
|
_contentType = T_font_woff2;
|
|
else if (path.endsWith(T__ttf))
|
|
_contentType = T_font_ttf;
|
|
else if (path.endsWith(T__xml))
|
|
_contentType = T_text_xml;
|
|
else if (path.endsWith(T__pdf))
|
|
_contentType = T_application_pdf;
|
|
else if (path.endsWith(T__zip))
|
|
_contentType = T_application_zip;
|
|
else if (path.endsWith(T__gz))
|
|
_contentType = T_application_x_gzip;
|
|
else
|
|
_contentType = T_text_plain;
|
|
#endif
|
|
}
|
|
|
|
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
|
_code = 200;
|
|
_path = path;
|
|
|
|
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
|
|
_path = _path + T__gz;
|
|
addHeader(T_Content_Encoding, T_gzip, false);
|
|
_callback = nullptr; // Unable to process zipped templates
|
|
_sendContentLength = true;
|
|
_chunked = false;
|
|
}
|
|
|
|
_content = fs.open(_path, fs::FileOpenMode::read);
|
|
_contentLength = _content.size();
|
|
|
|
if (strlen(contentType) == 0)
|
|
_setContentTypeFromPath(path);
|
|
else
|
|
_contentType = contentType;
|
|
|
|
int filenameStart = path.lastIndexOf('/') + 1;
|
|
char buf[26 + path.length() - filenameStart];
|
|
char* filename = (char*)path.c_str() + filenameStart;
|
|
|
|
if (download) {
|
|
// set filename and force download
|
|
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
|
} else {
|
|
// set filename and force rendering
|
|
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
|
}
|
|
addHeader(T_Content_Disposition, buf, false);
|
|
}
|
|
|
|
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
|
_code = 200;
|
|
_path = path;
|
|
|
|
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
|
|
addHeader(T_Content_Encoding, T_gzip, false);
|
|
_callback = nullptr; // Unable to process gzipped templates
|
|
_sendContentLength = true;
|
|
_chunked = false;
|
|
}
|
|
|
|
_content = content;
|
|
_contentLength = _content.size();
|
|
|
|
if (strlen(contentType) == 0)
|
|
_setContentTypeFromPath(path);
|
|
else
|
|
_contentType = contentType;
|
|
|
|
int filenameStart = path.lastIndexOf('/') + 1;
|
|
char buf[26 + path.length() - filenameStart];
|
|
char* filename = (char*)path.c_str() + filenameStart;
|
|
|
|
if (download) {
|
|
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
|
} else {
|
|
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
|
}
|
|
addHeader(T_Content_Disposition, buf, false);
|
|
}
|
|
|
|
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|
return _content.read(data, len);
|
|
}
|
|
|
|
/*
|
|
* Stream Response
|
|
* */
|
|
|
|
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
|
_code = 200;
|
|
_content = &stream;
|
|
_contentLength = len;
|
|
_contentType = contentType;
|
|
}
|
|
|
|
size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|
size_t available = _content->available();
|
|
size_t outLen = (available > len) ? len : available;
|
|
size_t i;
|
|
for (i = 0; i < outLen; i++)
|
|
data[i] = _content->read();
|
|
return outLen;
|
|
}
|
|
|
|
/*
|
|
* Callback Response
|
|
* */
|
|
|
|
AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
|
|
_code = 200;
|
|
_content = callback;
|
|
_contentLength = len;
|
|
if (!len)
|
|
_sendContentLength = false;
|
|
_contentType = contentType;
|
|
_filledLength = 0;
|
|
}
|
|
|
|
size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|
size_t ret = _content(data, len, _filledLength);
|
|
if (ret != RESPONSE_TRY_AGAIN) {
|
|
_filledLength += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Chunked Response
|
|
* */
|
|
|
|
AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
|
|
_code = 200;
|
|
_content = callback;
|
|
_contentLength = 0;
|
|
_contentType = contentType;
|
|
_sendContentLength = false;
|
|
_chunked = true;
|
|
_filledLength = 0;
|
|
}
|
|
|
|
size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|
size_t ret = _content(data, len, _filledLength);
|
|
if (ret != RESPONSE_TRY_AGAIN) {
|
|
_filledLength += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Progmem Response
|
|
* */
|
|
|
|
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
|
_code = code;
|
|
_content = content;
|
|
_contentType = contentType;
|
|
_contentLength = len;
|
|
_readLength = 0;
|
|
}
|
|
|
|
size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|
size_t left = _contentLength - _readLength;
|
|
if (left > len) {
|
|
memcpy_P(data, _content + _readLength, len);
|
|
_readLength += len;
|
|
return len;
|
|
}
|
|
memcpy_P(data, _content + _readLength, left);
|
|
_readLength += left;
|
|
return left;
|
|
}
|
|
|
|
/*
|
|
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
|
* */
|
|
|
|
AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) {
|
|
_code = 200;
|
|
_contentLength = 0;
|
|
_contentType = contentType;
|
|
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); // std::make_unique<cbuf>(bufferSize);
|
|
}
|
|
|
|
AsyncResponseStream::~AsyncResponseStream() = default;
|
|
|
|
size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
|
|
return _content->read((char*)buf, maxLen);
|
|
}
|
|
|
|
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
|
|
if (_started())
|
|
return 0;
|
|
|
|
if (len > _content->room()) {
|
|
size_t needed = len - _content->room();
|
|
_content->resizeAdd(needed);
|
|
}
|
|
size_t written = _content->write((const char*)data, len);
|
|
_contentLength += written;
|
|
return written;
|
|
}
|
|
|
|
size_t AsyncResponseStream::write(uint8_t data) {
|
|
return write(&data, 1);
|
|
}
|