Skip to content

Commit 879d57c

Browse files
Merge pull request #270 from JosePineiro/refactor/etag-from-gzip-crc32
Refactor _getEtag to get etag from gzip instead of buffer
2 parents a53a062 + 66a369f commit 879d57c

File tree

3 files changed

+39
-38
lines changed

3 files changed

+39
-38
lines changed

src/AsyncWebServerRequest.cpp

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,16 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content
2929
const String gzPath = path + asyncsrv::T__gz;
3030
File gzFile = fs.open(gzPath, fs::FileOpenMode::read);
3131

32-
// Compressed file not found or invalid
33-
if (!gzFile.seek(gzFile.size() - 8)) {
34-
send(404);
35-
gzFile.close();
36-
return;
37-
}
38-
3932
// ETag validation
4033
if (this->hasHeader(asyncsrv::T_INM)) {
4134
// Generate server ETag from CRC in gzip trailer
42-
uint8_t crcInTrailer[4];
43-
gzFile.read(crcInTrailer, 4);
4435
char serverETag[9];
45-
_getEtag(crcInTrailer, serverETag);
36+
if (!_getEtag(gzFile, serverETag)) {
37+
// Compressed file not found or invalid
38+
send(404);
39+
gzFile.close();
40+
return;
41+
}
4642

4743
// Compare with client's ETag
4844
const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM);
@@ -59,27 +55,36 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content
5955
}
6056

6157
/**
62-
* @brief Generates an ETag string from a 4-byte trailer
58+
* @brief Generates an ETag string from the CRC32 trailer of a GZIP file.
59+
*
60+
* This function reads the CRC32 checksum (4 bytes) located at the end of a GZIP-compressed file
61+
* and converts it into an 8-character hexadecimal ETag string (null-terminated).
6362
*
64-
* This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
63+
* @param gzFile Opened file handle pointing to the GZIP file.
64+
* @param eTag Output buffer to store the generated ETag.
65+
* Must be pre-allocated with at least 9 bytes (8 for hex digits + 1 for null terminator).
6566
*
66-
* @param trailer[4] Input array of 4 bytes to convert to hexadecimal
67-
* @param serverETag Output buffer to store the ETag
68-
* Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator)
67+
* @return true if the ETag was successfully generated, false otherwise (e.g., file too short or seek failed).
6968
*/
70-
void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) {
69+
bool AsyncWebServerRequest::_getEtag(File gzFile, char *etag) {
7170
static constexpr char hexChars[] = "0123456789ABCDEF";
7271

73-
uint32_t data;
74-
memcpy(&data, trailer, 4);
72+
if (!gzFile.seek(gzFile.size() - 8)) {
73+
return false;
74+
}
75+
76+
uint32_t crc;
77+
gzFile.read(reinterpret_cast<uint8_t *>(&crc), sizeof(crc));
78+
79+
etag[0] = hexChars[(crc >> 4) & 0x0F];
80+
etag[1] = hexChars[crc & 0x0F];
81+
etag[2] = hexChars[(crc >> 12) & 0x0F];
82+
etag[3] = hexChars[(crc >> 8) & 0x0F];
83+
etag[4] = hexChars[(crc >> 20) & 0x0F];
84+
etag[5] = hexChars[(crc >> 16) & 0x0F];
85+
etag[6] = hexChars[(crc >> 28)];
86+
etag[7] = hexChars[(crc >> 24) & 0x0F];
87+
etag[8] = '\0';
7588

76-
serverETag[0] = hexChars[(data >> 4) & 0x0F];
77-
serverETag[1] = hexChars[data & 0x0F];
78-
serverETag[2] = hexChars[(data >> 12) & 0x0F];
79-
serverETag[3] = hexChars[(data >> 8) & 0x0F];
80-
serverETag[4] = hexChars[(data >> 20) & 0x0F];
81-
serverETag[5] = hexChars[(data >> 16) & 0x0F];
82-
serverETag[6] = hexChars[(data >> 28)];
83-
serverETag[7] = hexChars[(data >> 24) & 0x0F];
84-
serverETag[8] = '\0';
89+
return true;
8590
}

src/ESPAsyncWebServer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ class AsyncWebServerRequest {
275275
void _send();
276276
void _runMiddlewareChain();
277277

278-
static void _getEtag(uint8_t trailer[4], char *serverETag);
278+
static bool _getEtag(File gzFile, char *eTag);
279279

280280
public:
281281
File _tempFile;

src/WebResponses.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -699,29 +699,23 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
699699

700700
// Try to open the uncompressed version first
701701
_content = fs.open(path, fs::FileOpenMode::read);
702-
if (_content.available()) {
703-
_contentLength = _content.size();
704-
} else {
705-
// Try to open the compressed version (.gz)
702+
if (!_content.available()) {
703+
// If not available try to open the compressed version (.gz)
706704
String gzPath;
707705
uint16_t pathLen = path.length();
708706
gzPath.reserve(pathLen + 3);
709707
gzPath.concat(path);
710708
gzPath.concat(asyncsrv::T__gz);
711709
_content = fs.open(gzPath, fs::FileOpenMode::read);
712-
_contentLength = _content.size();
713710

714-
if (_content.seek(_contentLength - 8)) {
711+
char serverETag[9];
712+
if (AsyncWebServerRequest::_getEtag(_content, serverETag)) {
715713
addHeader(T_Content_Encoding, T_gzip, false);
716714
_callback = nullptr; // Unable to process zipped templates
717715
_sendContentLength = true;
718716
_chunked = false;
719717

720718
// Add ETag and cache headers
721-
uint8_t crcInTrailer[4];
722-
_content.read(crcInTrailer, sizeof(crcInTrailer));
723-
char serverETag[9];
724-
AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
725719
addHeader(T_ETag, serverETag, true);
726720
addHeader(T_Cache_Control, T_no_cache, true);
727721

@@ -733,6 +727,8 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con
733727
}
734728
}
735729

730+
_contentLength = _content.size();
731+
736732
if (*contentType == '\0') {
737733
_setContentTypeFromPath(path);
738734
} else {

0 commit comments

Comments
 (0)