Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
40 changes: 40 additions & 0 deletions src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,52 @@ class AsyncWebServerRequest {
}

void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
// If-None-Match header
if (this->hasHeader(asyncsrv::T_INM) && !download && fs.exists(path + asyncsrv::T__gz)) {
// CRC32-based ETag of the trailer, bytes 4-7 from the end
File file = fs.open(path + asyncsrv::T__gz, fs::FileOpenMode::read);
if (file && file.size() >= 8) {
file.seek(file.size() - 8);

uint8_t trailer[4];
if (file.read(trailer, sizeof(trailer)) == sizeof(trailer)) {
char serverETag[11];
_getEtag(trailer, serverETag);

// Compare with client's If-None-Match header
const char *clientETag = this->getHeader(asyncsrv::T_INM)->value().c_str();
if (strcmp(clientETag, serverETag) == 0) {
file.close();
this->send(304); // Not Modified
return;
}
}
file.close();
}
}

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

void _getEtag(uint8_t trailer[4], char *serverETag) {
serverETag[0] = '"';
for (int i = 0; i < 4; ++i) {
uint8_t byte = trailer[i];
uint8_t highNibble = (byte >> 4) & 0x0F;
uint8_t lowNibble = byte & 0x0F;

serverETag[1 + i * 2] = (highNibble < 10) ? ('0' + highNibble) : ('A' + highNibble - 10);
serverETag[2 + i * 2] = (lowNibble < 10) ? ('0' + lowNibble) : ('A' + lowNibble - 10);
}
serverETag[9] = '"';
serverETag[10] = '\0';
}

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
31 changes: 31 additions & 0 deletions src/WebResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,23 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();

// CRC32-based ETag of the trailer, bytes 4-7 from the end
if (_content && _contentLength >= 8) {
_content.seek(_contentLength - 8);
uint8_t trailer[4];
if (_content.read(trailer, sizeof(trailer)) == sizeof(trailer)) {
char serverETag[11];
_getEtag(trailer, 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);
Expand All @@ -682,6 +699,20 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
addHeader(T_Content_Disposition, buf, false);
}

void AsyncFileResponse::_getEtag(uint8_t trailer[4], char *serverETag) {
serverETag[0] = '"';
for (int i = 0; i < 4; ++i) {
uint8_t byte = trailer[i];
uint8_t highNibble = (byte >> 4) & 0x0F;
uint8_t lowNibble = byte & 0x0F;

serverETag[1 + i * 2] = (highNibble < 10) ? ('0' + highNibble) : ('A' + highNibble - 10);
serverETag[2 + i * 2] = (lowNibble < 10) ? ('0' + lowNibble) : ('A' + lowNibble - 10);
}
serverETag[9] = '"';
serverETag[10] = '\0';
}

AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
Expand Down
Loading