diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 58ea3225..5651d5ab 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -21,9 +21,8 @@ std::string stripHtml(const std::string& html) { for (size_t i = 0; i < html.size(); ++i) { const char c = html[i]; if (c == '<') { - // Only treat as a tag if followed by a tag-like character; otherwise keep literal '<' - size_t j = i + 1; - while (j < html.size() && html[j] == ' ') ++j; + // Only treat as a tag if immediately followed (no space skip) by a tag-like character + const size_t j = i + 1; if (j < html.size() && (isalpha(static_cast(html[j])) || html[j] == '/' || html[j] == '!' || html[j] == '?')) { inTag = true; @@ -33,7 +32,11 @@ std::string stripHtml(const std::string& html) { result += c; } } else if (c == '>') { - inTag = false; + if (inTag) { + inTag = false; + } else { + result += c; + } } else if (!inTag) { if (c == '&') { // Decode common HTML entities not covered by Expat @@ -196,7 +199,10 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name } if (self->state == IN_METADATA && strcmp(name, "dc:description") == 0) { - self->state = IN_BOOK_DESCRIPTION; + // Only capture the first dc:description element; subsequent ones are alternate/localized variants + if (self->description.empty()) { + self->state = IN_BOOK_DESCRIPTION; + } return; } @@ -254,9 +260,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (strcmp(metaName, "cover") == 0) { self->coverItemId = metaContent; } else if (strcmp(metaName, "calibre:series") == 0 && self->series.empty()) { - self->series = metaContent; + self->series = std::string(metaContent).substr(0, MAX_DESCRIPTION_LENGTH); } else if (strcmp(metaName, "calibre:series_index") == 0 && self->seriesIndex.empty()) { - self->seriesIndex = metaContent; + self->seriesIndex = std::string(metaContent).substr(0, MAX_DESCRIPTION_LENGTH); } } @@ -457,12 +463,18 @@ void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s, } if (self->state == IN_BOOK_SERIES) { - self->series.append(s, len); + if (self->series.size() < MAX_DESCRIPTION_LENGTH) { + const size_t remaining = MAX_DESCRIPTION_LENGTH - self->series.size(); + self->series.append(s, std::min(static_cast(len), remaining)); + } return; } if (self->state == IN_BOOK_SERIES_INDEX) { - self->seriesIndex.append(s, len); + if (self->seriesIndex.size() < MAX_DESCRIPTION_LENGTH) { + const size_t remaining = MAX_DESCRIPTION_LENGTH - self->seriesIndex.size(); + self->seriesIndex.append(s, std::min(static_cast(len), remaining)); + } return; } } diff --git a/lib/Serialization/Serialization.h b/lib/Serialization/Serialization.h index 1308822f..37df1c83 100644 --- a/lib/Serialization/Serialization.h +++ b/lib/Serialization/Serialization.h @@ -36,17 +36,23 @@ static void writeString(FsFile& file, const std::string& s) { file.write(reinterpret_cast(s.data()), len); } -static void readString(std::istream& is, std::string& s) { +constexpr uint32_t MAX_STRING_LENGTH = 4096; + +static bool readString(std::istream& is, std::string& s) { uint32_t len; readPod(is, len); + if (len > MAX_STRING_LENGTH) return false; s.resize(len); is.read(&s[0], len); + return true; } -static void readString(FsFile& file, std::string& s) { +static bool readString(FsFile& file, std::string& s) { uint32_t len; readPod(file, len); + if (len > MAX_STRING_LENGTH) return false; s.resize(len); - file.read(&s[0], len); + file.read(reinterpret_cast(&s[0]), len); + return true; } } // namespace serialization diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index 18a98c43..a013db7a 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -41,13 +41,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title } void RecentBooksStore::updateBook(const std::string& path, const std::string& title, const std::string& author, - const std::string& coverBmpPath) { + const std::string& series, const std::string& coverBmpPath) { auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { RecentBook& book = *it; book.title = title; book.author = author; + book.series = series; book.coverBmpPath = coverBmpPath; saveToFile(); } @@ -125,15 +126,22 @@ bool RecentBooksStore::loadFromBinaryFile() { recentBooks.reserve(count); for (uint8_t i = 0; i < count; i++) { std::string path; - serialization::readString(inputFile, path); + if (!serialization::readString(inputFile, path)) { + LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i); + inputFile.close(); + return false; + } // load book to get missing data RecentBook book = getDataFromBook(path); if (book.title.empty() && book.author.empty() && version == 2) { // Fall back to loading what we can from the store std::string title, author; - serialization::readString(inputFile, title); - serialization::readString(inputFile, author); + if (!serialization::readString(inputFile, title) || !serialization::readString(inputFile, author)) { + LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i); + inputFile.close(); + return false; + } recentBooks.push_back({path, title, author, "", ""}); } else { recentBooks.push_back(book); @@ -149,10 +157,12 @@ bool RecentBooksStore::loadFromBinaryFile() { for (uint8_t i = 0; i < count; i++) { std::string path, title, author, coverBmpPath; - serialization::readString(inputFile, path); - serialization::readString(inputFile, title); - serialization::readString(inputFile, author); - serialization::readString(inputFile, coverBmpPath); + if (!serialization::readString(inputFile, path) || !serialization::readString(inputFile, title) || + !serialization::readString(inputFile, author) || !serialization::readString(inputFile, coverBmpPath)) { + LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i); + inputFile.close(); + return false; + } // Omit books with missing title (e.g. saved before metadata was available) if (title.empty()) { diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 100bff0d..bd7ad16d 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -36,7 +36,7 @@ class RecentBooksStore { const std::string& coverBmpPath); void updateBook(const std::string& path, const std::string& title, const std::string& author, - const std::string& coverBmpPath); + const std::string& series, const std::string& coverBmpPath); // Get the list of recent books (most recent first) const std::vector& getBooks() const { return recentBooks; } diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 810cb50b..bc2180fa 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -74,7 +74,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) { GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); bool success = epub.generateThumbBmp(coverHeight); if (!success) { - RECENT_BOOKS.updateBook(book.path, book.title, book.author, ""); + RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, ""); book.coverBmpPath = ""; } coverRendered = false; @@ -91,7 +91,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) { GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); bool success = xtc.generateThumbBmp(coverHeight); if (!success) { - RECENT_BOOKS.updateBook(book.path, book.title, book.author, ""); + RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, ""); book.coverBmpPath = ""; } coverRendered = false;