fix: oom exceptions for OPDS, KOSync, and OTA via wolfssl (#2475)

This commit is contained in:
Justin Mitchell
2026-07-12 01:53:26 +03:00
committed by GitHub
parent 1f5669a08a
commit 3e627112f6
13 changed files with 413 additions and 233 deletions
+55 -18
View File
@@ -5,6 +5,17 @@
#include <cstring>
namespace {
constexpr size_t ENTRY_STORAGE_CAPACITY = 64;
constexpr size_t MAX_ENTRIES = ENTRY_STORAGE_CAPACITY - 2;
constexpr size_t MAX_TITLE_CHARS = 160;
constexpr size_t MAX_AUTHOR_CHARS = 120;
constexpr size_t MAX_ID_CHARS = 128;
constexpr size_t MAX_HREF_CHARS = 768;
constexpr size_t MAX_SEARCH_TEMPLATE_CHARS = 768;
constexpr size_t MAX_PAGE_URL_CHARS = 768;
} // namespace
OpdsParser::OpdsParser() {
parser = XML_ParserCreate(nullptr);
if (!parser) {
@@ -12,6 +23,7 @@ OpdsParser::OpdsParser() {
LOG_DBG("OPDS", "Couldn't allocate memory for parser");
return;
}
entries.reserve(ENTRY_STORAGE_CAPACITY);
XML_SetUserData(parser, this);
XML_SetElementHandler(parser, startElement, endElement);
XML_SetCharacterDataHandler(parser, characterData);
@@ -71,6 +83,8 @@ void OpdsParser::clear() {
currentEntry = OpdsEntry{};
currentText.clear();
inEntry = inTitle = inAuthor = inAuthorName = inId = false;
collectCurrentEntry = false;
feedTruncated = false;
}
std::vector<OpdsEntry> OpdsParser::getBooks() const {
@@ -88,9 +102,33 @@ const char* OpdsParser::findAttribute(const XML_Char** atts, const char* name) {
return nullptr;
}
void OpdsParser::assignBounded(std::string& target, const char* value, const size_t maxLen) {
if (!value) {
target.clear();
return;
}
target.assign(value, strnlen(value, maxLen));
}
void OpdsParser::appendBounded(std::string& target, const char* value, const size_t len, const size_t maxLen) {
if (target.size() >= maxLen) return;
const size_t remaining = maxLen - target.size();
target.append(value, len < remaining ? len : remaining);
}
void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
auto* self = static_cast<OpdsParser*>(userData);
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
self->inEntry = true;
self->collectCurrentEntry = self->entries.size() < MAX_ENTRIES;
self->feedTruncated = self->feedTruncated || !self->collectCurrentEntry;
self->currentEntry = OpdsEntry{};
self->currentText.clear();
self->inTitle = self->inAuthor = self->inAuthorName = self->inId = false;
return;
}
if (strcmp(name, "link") == 0 || strstr(name, ":link") != nullptr) {
const char* href = findAttribute(atts, "href");
if (href) {
@@ -98,17 +136,16 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
const char* type = findAttribute(atts, "type");
if (rel && strcmp(rel, "search") == 0) {
std::string sHref(href);
if (sHref.find("{searchTerms}") != std::string::npos) {
self->searchTemplate = sHref;
if (strstr(href, "{searchTerms}") != nullptr) {
assignBounded(self->searchTemplate, href, MAX_SEARCH_TEMPLATE_CHARS);
}
} else if (rel && strcmp(rel, "next") == 0 && !self->inEntry) {
self->nextPageUrl = href;
assignBounded(self->nextPageUrl, href, MAX_PAGE_URL_CHARS);
} else if (rel && strcmp(rel, "previous") == 0 && !self->inEntry) {
self->prevPageUrl = href;
assignBounded(self->prevPageUrl, href, MAX_PAGE_URL_CHARS);
}
if (self->inEntry) {
if (self->inEntry && self->collectCurrentEntry) {
if (rel && type && strstr(rel, "opds-spec.org/acquisition") != nullptr &&
strcmp(type, "application/epub+zip") == 0) {
// Prefer plain EPUB links over derived formats when multiple
@@ -119,25 +156,19 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
self->currentEntry.href.find("/epub/") != std::string::npos);
if (self->currentEntry.type != OpdsEntryType::BOOK || (isPlainEpub && !alreadyHasPlainEpub)) {
self->currentEntry.type = OpdsEntryType::BOOK;
self->currentEntry.href = href;
assignBounded(self->currentEntry.href, href, MAX_HREF_CHARS);
}
} else if (type && strstr(type, "application/atom+xml") != nullptr) {
if (self->currentEntry.type != OpdsEntryType::BOOK) {
self->currentEntry.type = OpdsEntryType::NAVIGATION;
self->currentEntry.href = href;
assignBounded(self->currentEntry.href, href, MAX_HREF_CHARS);
}
}
}
}
}
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
self->inEntry = true;
self->currentEntry = OpdsEntry{};
return;
}
if (!self->inEntry) return;
if (!self->inEntry || !self->collectCurrentEntry) return;
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
self->inTitle = true;
@@ -157,10 +188,11 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
auto* self = static_cast<OpdsParser*>(userData);
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
if (!self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
if (self->collectCurrentEntry && !self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
self->entries.push_back(self->currentEntry);
}
self->inEntry = false;
self->collectCurrentEntry = false;
} else if (self->inEntry) {
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
if (self->inTitle) self->currentEntry.title = self->currentText;
@@ -179,7 +211,12 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
void XMLCALL OpdsParser::characterData(void* userData, const XML_Char* s, const int len) {
auto* self = static_cast<OpdsParser*>(userData);
if (self->inTitle || self->inAuthorName || self->inId) {
self->currentText.append(s, len);
if (!self->collectCurrentEntry) return;
if (self->inTitle) {
appendBounded(self->currentText, s, len, MAX_TITLE_CHARS);
} else if (self->inAuthorName) {
appendBounded(self->currentText, s, len, MAX_AUTHOR_CHARS);
} else if (self->inId) {
appendBounded(self->currentText, s, len, MAX_ID_CHARS);
}
}