diff --git a/lib/KOReaderSync/ChapterXPathIndexer.cpp b/lib/KOReaderSync/ChapterXPathIndexer.cpp index 2ba774d4..4a6dbc35 100644 --- a/lib/KOReaderSync/ChapterXPathIndexer.cpp +++ b/lib/KOReaderSync/ChapterXPathIndexer.cpp @@ -1,7 +1,7 @@ #include "ChapterXPathIndexer.h" +#include #include -#include #include #include @@ -388,10 +388,34 @@ static std::optional parseSpineItem(const std::shared_ptr& ep return std::nullopt; } + // Phase 1: decompress EPUB entry to a temp file on SD. + // This keeps the ~1.3 KB uzlib_uncomp struct off the stack before Expat runs. + const std::string tmpPath = epub->getCachePath() + "/.tmp_kox.html"; + { + if (Storage.exists(tmpPath.c_str())) { + Storage.remove(tmpPath.c_str()); + } + FsFile tmpFile; + if (!Storage.openFileForWrite("KOX", tmpPath, tmpFile)) { + LOG_ERR("KOX", "Failed to create temp file for spine=%d", spineIndex); + return std::nullopt; + } + constexpr size_t kChunkSize = 1024; + const bool ok = epub->readItemContentsToStream(spineItem.href, tmpFile, kChunkSize); + tmpFile.close(); + if (!ok) { + Storage.remove(tmpPath.c_str()); + LOG_ERR("KOX", "Failed to decompress spine=%d to temp file", spineIndex); + return std::nullopt; + } + } + + // Phase 2: parse the temp file with Expat in chunks. ParserState state(spineIndex); XML_Parser parser = XML_ParserCreate(nullptr); if (!parser) { + Storage.remove(tmpPath.c_str()); LOG_ERR("KOX", "Failed to allocate XML parser for spine=%d", spineIndex); return std::nullopt; } @@ -401,30 +425,29 @@ static std::optional parseSpineItem(const std::shared_ptr& ep XML_SetCharacterDataHandler(parser, onCharacterData); XML_SetDefaultHandlerExpand(parser, onDefaultHandlerExpand); - // Feed decompressed data to Expat incrementally to avoid a large contiguous allocation. - class ExpatPrint : public Print { - public: - XML_Parser parser; - bool ok = true; - size_t write(uint8_t b) override { return write(&b, 1); } - size_t write(const uint8_t* buf, size_t size) override { - if (!ok) return 0; - if (XML_Parse(parser, reinterpret_cast(buf), static_cast(size), XML_FALSE) == - XML_STATUS_ERROR) { - ok = false; - return 0; + FsFile tmpFile; + bool parseOk = Storage.openFileForRead("KOX", tmpPath, tmpFile); + + if (parseOk) { + constexpr size_t kParseBufSize = 1024; + int done; + do { + void* const buf = XML_GetBuffer(parser, kParseBufSize); + if (!buf) { + parseOk = false; + break; } - return size; - } - }; + const size_t len = tmpFile.read(buf, kParseBufSize); + done = tmpFile.available() == 0; + if (XML_ParseBuffer(parser, static_cast(len), done) == XML_STATUS_ERROR) { + parseOk = false; + break; + } + } while (!done); + tmpFile.close(); + } - ExpatPrint ep; - ep.parser = parser; - constexpr size_t kChunkSize = 512; - epub->readItemContentsToStream(spineItem.href, ep, kChunkSize); - - // Finalise the parse regardless of streaming result - const bool parseOk = ep.ok && XML_Parse(parser, "", 0, XML_TRUE) != XML_STATUS_ERROR; + Storage.remove(tmpPath.c_str()); if (!parseOk) { LOG_ERR("KOX", "XPath parse failed for spine=%d at line %lu: %s", spineIndex, XML_GetCurrentLineNumber(parser),