diff --git a/src/AsyncWebServerRequest.cpp b/src/AsyncWebServerRequest.cpp index 18b4da05..c7326fba 100644 --- a/src/AsyncWebServerRequest.cpp +++ b/src/AsyncWebServerRequest.cpp @@ -29,20 +29,16 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content const String gzPath = path + asyncsrv::T__gz; File gzFile = fs.open(gzPath, fs::FileOpenMode::read); - // Compressed file not found or invalid - if (!gzFile.seek(gzFile.size() - 8)) { - send(404); - gzFile.close(); - return; - } - // ETag validation if (this->hasHeader(asyncsrv::T_INM)) { // Generate server ETag from CRC in gzip trailer - uint8_t crcInTrailer[4]; - gzFile.read(crcInTrailer, 4); char serverETag[9]; - _getEtag(crcInTrailer, serverETag); + if (!_getEtag(gzFile, serverETag)) { + // Compressed file not found or invalid + send(404); + gzFile.close(); + return; + } // Compare with client's ETag const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM); @@ -59,27 +55,40 @@ void AsyncWebServerRequest::send(FS &fs, const String &path, const char *content } /** - * @brief Generates an ETag string from a 4-byte trailer + * @brief Generates an ETag string from the CRC32 trailer of a GZIP file. * - * This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes. + * This function reads the CRC32 checksum (4 bytes) located at the end of a GZIP-compressed file + * and converts it into an 8-character hexadecimal ETag string (null-terminated). * - * @param trailer[4] Input array of 4 bytes to convert to hexadecimal - * @param serverETag Output buffer to store the ETag - * Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator) + * @param gzFile Opened file handle pointing to the GZIP file. + * @param eTag Output buffer to store the generated ETag. + * Must be pre-allocated with at least 9 bytes (8 for hex digits + 1 for null terminator). + * + * @return true if the ETag was successfully generated, false otherwise (e.g., file too short or seek failed). */ -void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) { +bool AsyncWebServerRequest::_getEtag(File gzFile, char *etag) { static constexpr char hexChars[] = "0123456789ABCDEF"; + // Compressed file not found or invalid + if (!gzFile.seek(gzFile.size() - 8)) { + return false; + } + + uint8_t crcInTrailer[4]; + gzFile.read(crcInTrailer, 4); + uint32_t data; - memcpy(&data, trailer, 4); + memcpy(&data, crcInTrailer, 4); + + etag[0] = hexChars[(data >> 4) & 0x0F]; + etag[1] = hexChars[data & 0x0F]; + etag[2] = hexChars[(data >> 12) & 0x0F]; + etag[3] = hexChars[(data >> 8) & 0x0F]; + etag[4] = hexChars[(data >> 20) & 0x0F]; + etag[5] = hexChars[(data >> 16) & 0x0F]; + etag[6] = hexChars[(data >> 28)]; + etag[7] = hexChars[(data >> 24) & 0x0F]; + etag[8] = '\0'; - serverETag[0] = hexChars[(data >> 4) & 0x0F]; - serverETag[1] = hexChars[data & 0x0F]; - serverETag[2] = hexChars[(data >> 12) & 0x0F]; - serverETag[3] = hexChars[(data >> 8) & 0x0F]; - serverETag[4] = hexChars[(data >> 20) & 0x0F]; - serverETag[5] = hexChars[(data >> 16) & 0x0F]; - serverETag[6] = hexChars[(data >> 28)]; - serverETag[7] = hexChars[(data >> 24) & 0x0F]; - serverETag[8] = '\0'; + return true; } diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 0cf13238..7d5eea17 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -275,7 +275,7 @@ class AsyncWebServerRequest { void _send(); void _runMiddlewareChain(); - static void _getEtag(uint8_t trailer[4], char *serverETag); + static bool _getEtag(File gzFile, char *eTag); public: File _tempFile; diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index c58fc60a..35903005 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -711,24 +711,19 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con _content = fs.open(path, fs::FileOpenMode::read); if (_content.available()) { _path = path; - _contentLength = _content.size(); } else { // Try to open the compressed version (.gz) _path = path + asyncsrv::T__gz; _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); - if (_content.seek(_contentLength - 8)) { + char serverETag[9]; + if (AsyncWebServerRequest::_getEtag(_content, serverETag)) { addHeader(T_Content_Encoding, T_gzip, false); _callback = nullptr; // Unable to process zipped templates _sendContentLength = true; _chunked = false; // Add ETag and cache headers - uint8_t crcInTrailer[4]; - _content.read(crcInTrailer, sizeof(crcInTrailer)); - char serverETag[9]; - AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag); addHeader(T_ETag, serverETag, true); addHeader(T_Cache_Control, T_no_cache, true); @@ -740,6 +735,8 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *con } } + _contentLength = _content.size(); + if (*contentType == '\0') { _setContentTypeFromPath(path); } else {