fix(epub): prevent blank pages from corrupted index files

- Clean up incomplete temp files before retry attempts in Section.cpp
- Remove failed stream files immediately to prevent corruption
- Add data consistency validation in TextBlock deserialization (wc == xc == sc)
- Add sanity check for unreasonably large word counts (max 10000)
- Add iterator bounds validation before rendering to prevent overflow

This fixes an issue where pages after a certain point would appear blank
due to corrupted .bin files from failed SD card streaming retries.
This commit is contained in:
Eunchurn Park 2025-12-26 23:07:49 +09:00
parent f41e7496ad
commit abe3e6c6db
No known key found for this signature in database
GPG Key ID: 29D94D9C697E3F92
2 changed files with 33 additions and 1 deletions

View File

@ -127,12 +127,23 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
delay(50); // Brief delay before retry delay(50); // Brief delay before retry
} }
// Remove any incomplete file from previous attempt before retrying
if (SD.exists(tmpHtmlPath.c_str())) {
SD.remove(tmpHtmlPath.c_str());
}
File tmpHtml; File tmpHtml;
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) { if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
continue; continue;
} }
success = epub->readItemContentsToStream(localPath, tmpHtml, 1024); success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
tmpHtml.close(); tmpHtml.close();
// If streaming failed, remove the incomplete file immediately
if (!success && SD.exists(tmpHtmlPath.c_str())) {
SD.remove(tmpHtmlPath.c_str());
Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis());
}
} }
if (!success) { if (!success) {

View File

@ -4,11 +4,18 @@
#include <Serialization.h> #include <Serialization.h>
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
// Validate iterator bounds before rendering
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
Serial.printf("[%lu] [TXB] Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
(uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
return;
}
auto wordIt = words.begin(); auto wordIt = words.begin();
auto wordStylesIt = wordStyles.begin(); auto wordStylesIt = wordStyles.begin();
auto wordXposIt = wordXpos.begin(); auto wordXposIt = wordXpos.begin();
for (int i = 0; i < words.size(); i++) { for (size_t i = 0; i < words.size(); i++) {
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt); renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
std::advance(wordIt, 1); std::advance(wordIt, 1);
@ -46,6 +53,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
// words // words
serialization::readPod(file, wc); serialization::readPod(file, wc);
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
if (wc > 10000) {
Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc);
return nullptr;
}
words.resize(wc); words.resize(wc);
for (auto& w : words) serialization::readString(file, w); for (auto& w : words) serialization::readString(file, w);
@ -59,6 +73,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
wordStyles.resize(sc); wordStyles.resize(sc);
for (auto& s : wordStyles) serialization::readPod(file, s); for (auto& s : wordStyles) serialization::readPod(file, s);
// Validate data consistency: all three lists must have the same size
if (wc != xc || wc != sc) {
Serial.printf("[%lu] [TXB] Deserialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), wc, xc,
sc);
return nullptr;
}
// style // style
serialization::readPod(file, style); serialization::readPod(file, style);