diff options
| author | schererleander <leander@schererleander.de> | 2026-01-20 08:34:54 +0100 |
|---|---|---|
| committer | schererleander <leander@schererleander.de> | 2026-01-20 08:34:54 +0100 |
| commit | 85ea4e995a75abe061f6fc375ea0481084dddd43 (patch) | |
| tree | 7eb5d57653ecd8f041aeac4e68d7d554c1168681 /libraries/ESP_Async_WebServer/src/WebResponses.cpp | |
Diffstat (limited to 'libraries/ESP_Async_WebServer/src/WebResponses.cpp')
| -rw-r--r-- | libraries/ESP_Async_WebServer/src/WebResponses.cpp | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/libraries/ESP_Async_WebServer/src/WebResponses.cpp b/libraries/ESP_Async_WebServer/src/WebResponses.cpp new file mode 100644 index 0000000..3de8f32 --- /dev/null +++ b/libraries/ESP_Async_WebServer/src/WebResponses.cpp @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.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 + * + */ + +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 429: return T_HTTP_CODE_429; + 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; + } +} + +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); + } +} + +void AsyncWebServerResponse::setCode(int code) { + if (_state == RESPONSE_SETUP) { + _code = code; + } +} + +void AsyncWebServerResponse::setContentLength(size_t len) { + if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) { + _contentLength = len; + } +} + +void AsyncWebServerResponse::setContentType(const char *type) { + if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) { + _contentType = type; + } +} + +bool AsyncWebServerResponse::removeHeader(const char *name) { + bool h_erased = false; + for (auto i = _headers.begin(); i != _headers.end();) { + if (i->name().equalsIgnoreCase(name)) { + _headers.erase(i); + h_erased = true; + } else { + ++i; + } + } + return h_erased; +} + +bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) { + for (auto i = _headers.begin(); i != _headers.end(); ++i) { + if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) { + _headers.erase(i); + return true; + } + } + return false; +} + +const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const { + auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) { + return header.name().equalsIgnoreCase(name); + }); + return (iter == std::end(_headers)) ? nullptr : &(*iter); +} + +bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) { + for (uint8_t i = 0; i < T_only_once_headers_len; i++) { + if (name.equalsIgnoreCase(T_only_once_headers[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 if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name + // do not update + return false; + } else { + break; // accept multiple headers with the same name + } + } + } + // header was not found found, or existing one was removed + _headers.emplace_back(name, value); + return true; +} + +void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) { + if (version) { + addHeader(T_Accept_Ranges, T_none, false); + if (_chunked) { + addHeader(T_Transfer_Encoding, T_chunked, false); + } + } + + if (_sendContentLength) { + addHeader(T_Content_Length, String(_contentLength), false); + } + + if (_contentType.length()) { + addHeader(T_Content_Type, _contentType.c_str(), false); + } + + // precompute buffer size to avoid reallocations by String class + size_t len = 0; + len += 50; // HTTP/1.1 200 <reason>\r\n + for (const auto &header : _headers) { + len += header.name().length() + header.value().length() + 4; + } + + // prepare buffer + buffer.reserve(len); + + // HTTP header +#ifdef ESP8266 + buffer.concat(PSTR("HTTP/1.")); +#else + buffer.concat("HTTP/1."); +#endif + buffer.concat(version); + buffer.concat(' '); + buffer.concat(_code); + buffer.concat(' '); + buffer.concat(responseCodeToString(_code)); + buffer.concat(T_rn); + + // Add headers + for (const auto &header : _headers) { + buffer.concat(header.name()); +#ifdef ESP8266 + buffer.concat(PSTR(": ")); +#else + buffer.concat(": "); +#endif + buffer.concat(header.value()); + buffer.concat(T_rn); + } + + buffer.concat(T_rn); + _headLength = buffer.length(); +} + +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(out, 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); + _assembleHead(_head, 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; + } + +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT + // return a credit for each chunk of acked data (polls does not give any credits) + if (len) { + ++_in_flight_credit; + } + + // for chunked responses ignore acks if there are no _in_flight_credits left + if (_chunked && !_in_flight_credit) { +#ifdef ESP32 + log_d("(chunk) out of in-flight credits"); +#endif + return 0; + } + + _in_flight -= (_in_flight > len) ? len : _in_flight; + // get the size of available sock space +#endif + + _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()); +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT + _in_flight += out.length(); + --_in_flight_credit; // take a credit +#endif + return out.length(); + } + } + + if (_state == RESPONSE_CONTENT) { +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT + // for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency, + // but flood asynctcp's queue and fragment socket buffer space for large responses. + // Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space. + // That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q + if (_in_flight > space) { + // log_d("defer user call %u/%u", _in_flight, space); + // take the credit back since we are ignoring this ack and rely on other inflight data + if (len) { + --_in_flight_credit; + } + return 0; + } +#endif + + 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) { +#ifdef ESP32 + log_e("Failed to allocate"); +#endif + request->abort(); + 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((char *)buf + headLen, "%04x", readLen) + headLen; + 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 ASYNCWEBSERVER_USE_CHUNK_INFLIGHT + _in_flight += outLen; + --_in_flight_credit; // take a credit +#endif + } + + 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 + * */ + +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; + // internal buffer will be null on allocation failure + _content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); + if (bufferSize && _content->size() < bufferSize) { +#ifdef ESP32 + log_e("Failed to allocate"); +#endif + } +} + +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); + // log a warning if allocation failed, but do not return: keep writing the bytes we can + // with _content->write: if len is more than the available size in the buffer, only + // the available size will be written + if (len > _content->room()) { +#ifdef ESP32 + log_e("Failed to allocate"); +#endif + } + } + size_t written = _content->write((const char *)data, len); + _contentLength += written; + return written; +} + +size_t AsyncResponseStream::write(uint8_t data) { + return write(&data, 1); +} |
