refactor: RAII scoped open/close for ZipFile (#1433) by znelson

This commit is contained in:
jpirnay
2026-04-10 10:59:26 +02:00
parent 1459e9fb61
commit c42452df8e
+50 -130
View File
@@ -18,6 +18,28 @@ namespace {
constexpr uint16_t ZIP_METHOD_STORED = 0; constexpr uint16_t ZIP_METHOD_STORED = 0;
constexpr uint16_t ZIP_METHOD_DEFLATED = 8; constexpr uint16_t ZIP_METHOD_DEFLATED = 8;
// RAII zip: opens the zip if not already open, closes on destruction only if
// it performed the open. Removes the wasOpen/close boilerplate from every method.
class ScopedOpenClose final {
public:
[[nodiscard]] explicit ScopedOpenClose(ZipFile& zf) : zf(zf), needsClose(!zf.isOpen()) {
if (needsClose) ok = zf.open();
}
~ScopedOpenClose() {
if (needsClose && ok) zf.close();
}
ScopedOpenClose(const ScopedOpenClose&) = delete;
ScopedOpenClose& operator=(const ScopedOpenClose&) = delete;
ScopedOpenClose(ScopedOpenClose&&) = delete;
ScopedOpenClose& operator=(ScopedOpenClose&&) = delete;
explicit operator bool() const { return ok || !needsClose; }
private:
ZipFile& zf;
bool needsClose = false;
bool ok = true; // true when zip was already open (no open() call needed)
};
int zipReadCallback(uzlib_uncomp* uncomp) { int zipReadCallback(uzlib_uncomp* uncomp) {
auto* ctx = reinterpret_cast<ZipInflateCtx*>(uncomp); auto* ctx = reinterpret_cast<ZipInflateCtx*>(uncomp);
if (ctx->fileRemaining == 0) return -1; if (ctx->fileRemaining == 0) return -1;
@@ -35,17 +57,10 @@ int zipReadCallback(uzlib_uncomp* uncomp) {
} // namespace } // namespace
bool ZipFile::loadAllFileStatSlims() { bool ZipFile::loadAllFileStatSlims() {
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return false;
return false;
}
if (!loadZipDetails()) { if (!loadZipDetails()) return false;
if (!wasOpen) {
close();
}
return false;
}
file.seek(zipDetails.centralDirOffset); file.seek(zipDetails.centralDirOffset);
@@ -89,9 +104,6 @@ bool ZipFile::loadAllFileStatSlims() {
lastCentralDirPos = zipDetails.centralDirOffset; lastCentralDirPos = zipDetails.centralDirOffset;
lastCentralDirPosValid = true; lastCentralDirPosValid = true;
if (!wasOpen) {
close();
}
return true; return true;
} }
@@ -105,17 +117,10 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
return false; return false;
} }
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return false;
return false;
}
if (!loadZipDetails()) { if (!loadZipDetails()) return false;
if (!wasOpen) {
close();
}
return false;
}
// Phase 1: Try scanning from cursor position first // Phase 1: Try scanning from cursor position first
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset; uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
@@ -179,17 +184,12 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
file.seekCur(m + k); file.seekCur(m + k);
} }
if (!wasOpen) {
close();
}
return found; return found;
} }
long ZipFile::getDataOffset(const FileStatSlim& fileStat) { long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return -1;
return -1;
}
constexpr auto localHeaderSize = 30; constexpr auto localHeaderSize = 30;
@@ -198,9 +198,6 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
file.seek(fileOffset); file.seek(fileOffset);
const size_t read = file.read(pLocalHeader, localHeaderSize); const size_t read = file.read(pLocalHeader, localHeaderSize);
if (!wasOpen) {
close();
}
if (read != localHeaderSize) { if (read != localHeaderSize) {
LOG_ERR("ZIP", "Something went wrong reading the local header"); LOG_ERR("ZIP", "Something went wrong reading the local header");
@@ -223,17 +220,12 @@ bool ZipFile::loadZipDetails() {
return true; return true;
} }
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return false;
return false;
}
const size_t fileSize = file.size(); const size_t fileSize = file.size();
if (fileSize < 22) { if (fileSize < 22) {
LOG_ERR("ZIP", "File too small to be a valid zip"); LOG_ERR("ZIP", "File too small to be a valid zip");
if (!wasOpen) {
close();
}
return false; // Minimum EOCD size is 22 bytes return false; // Minimum EOCD size is 22 bytes
} }
@@ -243,9 +235,6 @@ bool ZipFile::loadZipDetails() {
const auto buffer = static_cast<uint8_t*>(malloc(scanRange)); const auto buffer = static_cast<uint8_t*>(malloc(scanRange));
if (!buffer) { if (!buffer) {
LOG_ERR("ZIP", "Failed to allocate memory for EOCD scan buffer"); LOG_ERR("ZIP", "Failed to allocate memory for EOCD scan buffer");
if (!wasOpen) {
close();
}
return false; return false;
} }
@@ -265,9 +254,6 @@ bool ZipFile::loadZipDetails() {
if (foundOffset == -1) { if (foundOffset == -1) {
LOG_ERR("ZIP", "EOCD signature not found in zip file"); LOG_ERR("ZIP", "EOCD signature not found in zip file");
free(buffer); free(buffer);
if (!wasOpen) {
close();
}
return false; return false;
} }
@@ -280,9 +266,6 @@ bool ZipFile::loadZipDetails() {
zipDetails.isSet = true; zipDetails.isSet = true;
free(buffer); free(buffer);
if (!wasOpen) {
close();
}
return true; return true;
} }
@@ -317,17 +300,10 @@ int ZipFile::fillUncompressedSizes(std::vector<SizeTarget>& targets, std::vector
return 0; return 0;
} }
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return 0;
return 0;
}
if (!loadZipDetails()) { if (!loadZipDetails()) return 0;
if (!wasOpen) {
close();
}
return 0;
}
file.seek(zipDetails.centralDirOffset); file.seek(zipDetails.centralDirOffset);
@@ -384,34 +360,18 @@ int ZipFile::fillUncompressedSizes(std::vector<SizeTarget>& targets, std::vector
file.seekCur(m + k); file.seekCur(m + k);
} }
if (!wasOpen) {
close();
}
return matched; return matched;
} }
uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) { uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) {
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return nullptr;
return nullptr;
}
FileStatSlim fileStat = {}; FileStatSlim fileStat = {};
if (!loadFileStatSlim(filename, &fileStat)) { if (!loadFileStatSlim(filename, &fileStat)) return nullptr;
if (!wasOpen) {
close();
}
return nullptr;
}
const long fileOffset = getDataOffset(fileStat); const long fileOffset = getDataOffset(fileStat);
if (fileOffset < 0) { if (fileOffset < 0) return nullptr;
if (!wasOpen) {
close();
}
return nullptr;
}
file.seek(fileOffset); file.seek(fileOffset);
@@ -421,18 +381,12 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
const auto data = static_cast<uint8_t*>(malloc(dataSize)); const auto data = static_cast<uint8_t*>(malloc(dataSize));
if (data == nullptr) { if (data == nullptr) {
LOG_ERR("ZIP", "Failed to allocate memory for output buffer (%zu bytes)", dataSize); LOG_ERR("ZIP", "Failed to allocate memory for output buffer (%zu bytes)", dataSize);
if (!wasOpen) {
close();
}
return nullptr; return nullptr;
} }
if (fileStat.method == ZIP_METHOD_STORED) { if (fileStat.method == ZIP_METHOD_STORED) {
// no deflation, just read content // no deflation, just read content
const size_t dataRead = file.read(data, inflatedDataSize); const size_t dataRead = file.read(data, inflatedDataSize);
if (!wasOpen) {
close();
}
if (dataRead != inflatedDataSize) { if (dataRead != inflatedDataSize) {
LOG_ERR("ZIP", "Failed to read data"); LOG_ERR("ZIP", "Failed to read data");
@@ -446,16 +400,11 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize)); const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
if (deflatedData == nullptr) { if (deflatedData == nullptr) {
LOG_ERR("ZIP", "Failed to allocate memory for decompression buffer"); LOG_ERR("ZIP", "Failed to allocate memory for decompression buffer");
if (!wasOpen) { free(data);
close();
}
return nullptr; return nullptr;
} }
const size_t dataRead = file.read(deflatedData, deflatedDataSize); const size_t dataRead = file.read(deflatedData, deflatedDataSize);
if (!wasOpen) {
close();
}
if (dataRead != deflatedDataSize) { if (dataRead != deflatedDataSize) {
LOG_ERR("ZIP", "Failed to read data, expected %d got %d", deflatedDataSize, dataRead); LOG_ERR("ZIP", "Failed to read data, expected %d got %d", deflatedDataSize, dataRead);
@@ -482,9 +431,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
// Continue out of block with data set // Continue out of block with data set
} else { } else {
LOG_ERR("ZIP", "Unsupported compression method"); LOG_ERR("ZIP", "Unsupported compression method");
if (!wasOpen) { free(data);
close();
}
return nullptr; return nullptr;
} }
@@ -494,20 +441,14 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
} }
bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize) { bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize) {
const bool wasOpen = isOpen(); const ScopedOpenClose zip{*this};
if (!wasOpen && !open()) { if (!zip) return false;
return false;
}
FileStatSlim fileStat = {}; FileStatSlim fileStat = {};
if (!loadFileStatSlim(filename, &fileStat)) { if (!loadFileStatSlim(filename, &fileStat)) return false;
return false;
}
const long fileOffset = getDataOffset(fileStat); const long fileOffset = getDataOffset(fileStat);
if (fileOffset < 0) { if (fileOffset < 0) return false;
return false;
}
file.seek(fileOffset); file.seek(fileOffset);
const auto deflatedDataSize = fileStat.compressedSize; const auto deflatedDataSize = fileStat.compressedSize;
@@ -518,9 +459,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize)); const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
if (!buffer) { if (!buffer) {
LOG_ERR("ZIP", "Failed to allocate memory for buffer"); LOG_ERR("ZIP", "Failed to allocate memory for buffer");
if (!wasOpen) {
close();
}
return false; return false;
} }
@@ -530,19 +468,17 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
if (dataRead == 0) { if (dataRead == 0) {
LOG_ERR("ZIP", "Could not read more bytes"); LOG_ERR("ZIP", "Could not read more bytes");
free(buffer); free(buffer);
if (!wasOpen) {
close();
}
return false; return false;
} }
out.write(buffer, dataRead); if (out.write(buffer, dataRead) != dataRead) {
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
free(buffer);
return false;
}
remaining -= dataRead; remaining -= dataRead;
} }
if (!wasOpen) {
close();
}
free(buffer); free(buffer);
return true; return true;
} }
@@ -551,9 +487,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
auto* fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize)); auto* fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
if (!fileReadBuffer) { if (!fileReadBuffer) {
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer"); LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
if (!wasOpen) {
close();
}
return false; return false;
} }
@@ -561,9 +494,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
if (!outputBuffer) { if (!outputBuffer) {
LOG_ERR("ZIP", "Failed to allocate memory for output buffer"); LOG_ERR("ZIP", "Failed to allocate memory for output buffer");
free(fileReadBuffer); free(fileReadBuffer);
if (!wasOpen) {
close();
}
return false; return false;
} }
@@ -577,9 +507,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
LOG_ERR("ZIP", "Failed to init inflate reader"); LOG_ERR("ZIP", "Failed to init inflate reader");
free(outputBuffer); free(outputBuffer);
free(fileReadBuffer); free(fileReadBuffer);
if (!wasOpen) {
close();
}
return false; return false;
} }
ctx.reader.setReadCallback(zipReadCallback); ctx.reader.setReadCallback(zipReadCallback);
@@ -623,18 +550,11 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
// InflateStatus::Ok: output buffer full, continue // InflateStatus::Ok: output buffer full, continue
} }
if (!wasOpen) {
close();
}
free(outputBuffer); free(outputBuffer);
free(fileReadBuffer); free(fileReadBuffer);
return success; // ctx.reader destructor frees the ring buffer return success; // ctx.reader destructor frees the ring buffer
} }
if (!wasOpen) {
close();
}
LOG_ERR("ZIP", "Unsupported compression method"); LOG_ERR("ZIP", "Unsupported compression method");
return false; return false;
} }