Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/AsyncWebServerRequest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <ESPAsyncWebServer.h>

void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
const String gzPath = path + asyncsrv::T__gz;
const bool useCompressedVersion = !download && fs.exists(gzPath);

// If-None-Match header
if (useCompressedVersion && this->hasHeader(asyncsrv::T_INM)) {
// CRC32-based ETag of the trailer, bytes 4-7 from the end
File file = fs.open(gzPath, fs::FileOpenMode::read);
if (file && file.size() >= 18) { // 18 is the minimum size of valid gzip file
file.seek(file.size() - 8);

uint8_t crcFromGzipTrailer[4];
if (file.read(crcFromGzipTrailer, sizeof(crcFromGzipTrailer)) == sizeof(crcFromGzipTrailer)) {
char serverETag[11]; // " + 8 hex chars + " + '\0'
_getEtag(crcFromGzipTrailer, serverETag);

// Compare with client's If-None-Match header
const AsyncWebHeader* inmHeader = this->getHeader(asyncsrv::T_INM);
if (inmHeader && inmHeader->value().equals(serverETag)) {
file.close();
this->send(304); // Not Modified
return;
}
}
file.close();
}
}

// If we get here, create and send the normal response
if (fs.exists(path) || useCompressedVersion) {
send(beginResponse(fs, path, contentType, download, callback));
} else {
send(404);
}
}

/**
* @brief Generates an ETag string from a 4-byte trailer using basic loop implementation
*
* This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
*
* @param trailer[4] Input array of 4 bytes to convert to hexadecimal
* @param serverETag Output buffer that must be at least 11 bytes long to store the ETag
* Must be pre-allocated with minimum 11 bytes (8 hex + 2 quotes + 1 null terminator)
*/
void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char* serverETag) {
static constexpr char hexChars[] = "0123456789ABCDEF";

uint32_t data;
memcpy(&data, trailer, 4);

serverETag[0] = '"';
serverETag[1] = hexChars[(data >> 4) & 0x0F];
serverETag[2] = hexChars[data & 0x0F];
serverETag[3] = hexChars[(data >> 12) & 0x0F];
serverETag[4] = hexChars[(data >> 8) & 0x0F];
serverETag[5] = hexChars[(data >> 20) & 0x0F];
serverETag[6] = hexChars[(data >> 16) & 0x0F];
serverETag[7] = hexChars[(data >> 28)];
serverETag[8] = hexChars[(data >> 24) & 0x0F];
serverETag[9] = '"';
serverETag[10] = '\0';
}
10 changes: 3 additions & 7 deletions src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ class AsyncWebServerRequest {
void addInterestingHeader(__unused const String &name) {
}

static void _getEtag(uint8_t trailer[4], char* serverETag);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why public ? should be private


/**
* @brief issue HTTP redirect response with Location header
*
Expand Down Expand Up @@ -367,13 +369,7 @@ class AsyncWebServerRequest {
send(beginResponse(code, contentType, content, len, callback));
}

void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
send(beginResponse(fs, path, contentType, download, callback));
} else {
send(404);
}
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(fs, path, contentType.c_str(), download, callback);
}
Expand Down
38 changes: 29 additions & 9 deletions src/WebResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,22 +649,41 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
const String gzPath = path + asyncsrv::T__gz;

if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
_path = _path + T__gz;
if (!download && !fs.exists(path) && fs.exists(gzPath)) {
_path = gzPath;
_content = fs.open(gzPath, fs::FileOpenMode::read);
_contentLength = _content.size();
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;

// CRC32-based ETag of the trailer, bytes 4-7 from the end
_content.seek(_contentLength - 8);
uint8_t crcInTrailer[4];
if (_content.read(crcInTrailer, sizeof(crcInTrailer)) == sizeof(crcInTrailer)) {
char serverETag[11];
AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
addHeader(T_ETag, serverETag, false);
addHeader(T_Cache_Control, T_no_cache, false);
}

// Return to the beginning of the file
_content.seek(0);
}

_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (!_content) {
_path = path;
_content = fs.open(path, fs::FileOpenMode::read);
_contentLength = _content.size();
}

if (strlen(contentType) == 0) {
if (*contentType != '\0') {
_setContentTypeFromPath(path);
} else {
}
else {
_contentType = contentType;
}

Expand All @@ -675,7 +694,8 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
if (download) {
// set filename and force download
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
} else {
}
else {
// set filename and force rendering
snprintf_P(buf, sizeof(buf), PSTR("inline"));
}
Expand Down