Review amendments
This commit is contained in:
@@ -21,9 +21,8 @@ std::string stripHtml(const std::string& html) {
|
|||||||
for (size_t i = 0; i < html.size(); ++i) {
|
for (size_t i = 0; i < html.size(); ++i) {
|
||||||
const char c = html[i];
|
const char c = html[i];
|
||||||
if (c == '<') {
|
if (c == '<') {
|
||||||
// Only treat as a tag if followed by a tag-like character; otherwise keep literal '<'
|
// Only treat as a tag if immediately followed (no space skip) by a tag-like character
|
||||||
size_t j = i + 1;
|
const size_t j = i + 1;
|
||||||
while (j < html.size() && html[j] == ' ') ++j;
|
|
||||||
if (j < html.size() &&
|
if (j < html.size() &&
|
||||||
(isalpha(static_cast<unsigned char>(html[j])) || html[j] == '/' || html[j] == '!' || html[j] == '?')) {
|
(isalpha(static_cast<unsigned char>(html[j])) || html[j] == '/' || html[j] == '!' || html[j] == '?')) {
|
||||||
inTag = true;
|
inTag = true;
|
||||||
@@ -33,7 +32,11 @@ std::string stripHtml(const std::string& html) {
|
|||||||
result += c;
|
result += c;
|
||||||
}
|
}
|
||||||
} else if (c == '>') {
|
} else if (c == '>') {
|
||||||
inTag = false;
|
if (inTag) {
|
||||||
|
inTag = false;
|
||||||
|
} else {
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
} else if (!inTag) {
|
} else if (!inTag) {
|
||||||
if (c == '&') {
|
if (c == '&') {
|
||||||
// Decode common HTML entities not covered by Expat
|
// 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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,9 +260,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (strcmp(metaName, "cover") == 0) {
|
if (strcmp(metaName, "cover") == 0) {
|
||||||
self->coverItemId = metaContent;
|
self->coverItemId = metaContent;
|
||||||
} else if (strcmp(metaName, "calibre:series") == 0 && self->series.empty()) {
|
} 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()) {
|
} 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) {
|
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<size_t>(len), remaining));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->state == IN_BOOK_SERIES_INDEX) {
|
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<size_t>(len), remaining));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,17 +36,23 @@ static void writeString(FsFile& file, const std::string& s) {
|
|||||||
file.write(reinterpret_cast<const uint8_t*>(s.data()), len);
|
file.write(reinterpret_cast<const uint8_t*>(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;
|
uint32_t len;
|
||||||
readPod(is, len);
|
readPod(is, len);
|
||||||
|
if (len > MAX_STRING_LENGTH) return false;
|
||||||
s.resize(len);
|
s.resize(len);
|
||||||
is.read(&s[0], 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;
|
uint32_t len;
|
||||||
readPod(file, len);
|
readPod(file, len);
|
||||||
|
if (len > MAX_STRING_LENGTH) return false;
|
||||||
s.resize(len);
|
s.resize(len);
|
||||||
file.read(&s[0], len);
|
file.read(reinterpret_cast<uint8_t*>(&s[0]), len);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace serialization
|
} // namespace serialization
|
||||||
|
|||||||
@@ -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,
|
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 =
|
auto it =
|
||||||
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
||||||
if (it != recentBooks.end()) {
|
if (it != recentBooks.end()) {
|
||||||
RecentBook& book = *it;
|
RecentBook& book = *it;
|
||||||
book.title = title;
|
book.title = title;
|
||||||
book.author = author;
|
book.author = author;
|
||||||
|
book.series = series;
|
||||||
book.coverBmpPath = coverBmpPath;
|
book.coverBmpPath = coverBmpPath;
|
||||||
saveToFile();
|
saveToFile();
|
||||||
}
|
}
|
||||||
@@ -125,15 +126,22 @@ bool RecentBooksStore::loadFromBinaryFile() {
|
|||||||
recentBooks.reserve(count);
|
recentBooks.reserve(count);
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
std::string path;
|
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
|
// load book to get missing data
|
||||||
RecentBook book = getDataFromBook(path);
|
RecentBook book = getDataFromBook(path);
|
||||||
if (book.title.empty() && book.author.empty() && version == 2) {
|
if (book.title.empty() && book.author.empty() && version == 2) {
|
||||||
// Fall back to loading what we can from the store
|
// Fall back to loading what we can from the store
|
||||||
std::string title, author;
|
std::string title, author;
|
||||||
serialization::readString(inputFile, title);
|
if (!serialization::readString(inputFile, title) || !serialization::readString(inputFile, author)) {
|
||||||
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, "", ""});
|
recentBooks.push_back({path, title, author, "", ""});
|
||||||
} else {
|
} else {
|
||||||
recentBooks.push_back(book);
|
recentBooks.push_back(book);
|
||||||
@@ -149,10 +157,12 @@ bool RecentBooksStore::loadFromBinaryFile() {
|
|||||||
|
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
std::string path, title, author, coverBmpPath;
|
std::string path, title, author, coverBmpPath;
|
||||||
serialization::readString(inputFile, path);
|
if (!serialization::readString(inputFile, path) || !serialization::readString(inputFile, title) ||
|
||||||
serialization::readString(inputFile, title);
|
!serialization::readString(inputFile, author) || !serialization::readString(inputFile, coverBmpPath)) {
|
||||||
serialization::readString(inputFile, author);
|
LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i);
|
||||||
serialization::readString(inputFile, coverBmpPath);
|
inputFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Omit books with missing title (e.g. saved before metadata was available)
|
// Omit books with missing title (e.g. saved before metadata was available)
|
||||||
if (title.empty()) {
|
if (title.empty()) {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class RecentBooksStore {
|
|||||||
const std::string& coverBmpPath);
|
const std::string& coverBmpPath);
|
||||||
|
|
||||||
void updateBook(const std::string& path, const std::string& title, const std::string& author,
|
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)
|
// Get the list of recent books (most recent first)
|
||||||
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
|||||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||||
bool success = epub.generateThumbBmp(coverHeight);
|
bool success = epub.generateThumbBmp(coverHeight);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, "");
|
||||||
book.coverBmpPath = "";
|
book.coverBmpPath = "";
|
||||||
}
|
}
|
||||||
coverRendered = false;
|
coverRendered = false;
|
||||||
@@ -91,7 +91,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
|||||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||||
bool success = xtc.generateThumbBmp(coverHeight);
|
bool success = xtc.generateThumbBmp(coverHeight);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, "");
|
||||||
book.coverBmpPath = "";
|
book.coverBmpPath = "";
|
||||||
}
|
}
|
||||||
coverRendered = false;
|
coverRendered = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user