diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 2aa34ffe..141e7c3c 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -118,7 +118,7 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata, const } if (!imageRef.empty()) { - bookMetadata.coverItemHref = FsHelpers::normalisePath(coverPageBase + imageRef); + bookMetadata.coverItemHref = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(coverPageBase + imageRef)); LOG_DBG("EBP", "Found cover image from guide: %s", bookMetadata.coverItemHref.c_str()); } } diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 1b3916f7..149cc1d8 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -9,7 +9,7 @@ #include "FsHelpers.h" namespace { -constexpr uint8_t BOOK_CACHE_VERSION = 6; +constexpr uint8_t BOOK_CACHE_VERSION = 7; constexpr char bookBinFile[] = "/book.bin"; constexpr char tmpSpineBinFile[] = "/spine.bin.tmp"; constexpr char tmpTocBinFile[] = "/toc.bin.tmp"; diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 9d1277b9..d344ad73 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -445,7 +445,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* { // Resolve the image path relative to the HTML file - std::string resolvedPath = FsHelpers::normalisePath(self->contentBase + src); + std::string resolvedPath = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(self->contentBase + src)); if (ImageDecoderFactory::isFormatSupported(resolvedPath)) { // Create a unique filename for the cached image diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 8f797561..85bc7a68 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -186,7 +186,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (strcmp(atts[i], "id") == 0) { itemId = atts[i + 1]; } else if (strcmp(atts[i], "href") == 0) { - href = FsHelpers::normalisePath(self->baseContentPath + atts[i + 1]); + href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(self->baseContentPath + atts[i + 1])); } else if (strcmp(atts[i], "media-type") == 0) { mediaType = atts[i + 1]; } else if (strcmp(atts[i], "properties") == 0) { @@ -315,7 +315,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (strcmp(atts[i], "type") == 0) { type = atts[i + 1]; } else if (strcmp(atts[i], "href") == 0) { - guideHref = FsHelpers::normalisePath(self->baseContentPath + atts[i + 1]); + guideHref = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(self->baseContentPath + atts[i + 1])); } } if (!guideHref.empty()) { diff --git a/lib/Epub/Epub/parsers/TocNavParser.cpp b/lib/Epub/Epub/parsers/TocNavParser.cpp index 01ca8bef..325dd405 100644 --- a/lib/Epub/Epub/parsers/TocNavParser.cpp +++ b/lib/Epub/Epub/parsers/TocNavParser.cpp @@ -126,13 +126,14 @@ void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) { if (strcmp(name, "a") == 0 && self->state == IN_ANCHOR) { // Create TOC entry when closing anchor tag (we have all data now) if (!self->currentLabel.empty() && !self->currentHref.empty()) { - std::string href = FsHelpers::normalisePath(self->baseContentPath + self->currentHref); + const std::string rawTarget = self->baseContentPath + self->currentHref; + const size_t pos = rawTarget.find('#'); + const std::string rawPath = pos == std::string::npos ? rawTarget : rawTarget.substr(0, pos); + std::string href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(rawPath)); std::string anchor; - const size_t pos = href.find('#'); if (pos != std::string::npos) { - anchor = href.substr(pos + 1); - href = href.substr(0, pos); + anchor = FsHelpers::decodeUriEscapes(rawTarget.substr(pos + 1)); } if (self->cache) { diff --git a/lib/Epub/Epub/parsers/TocNcxParser.cpp b/lib/Epub/Epub/parsers/TocNcxParser.cpp index 5c0400aa..87cc1106 100644 --- a/lib/Epub/Epub/parsers/TocNcxParser.cpp +++ b/lib/Epub/Epub/parsers/TocNcxParser.cpp @@ -145,13 +145,14 @@ void XMLCALL TocNcxParser::endElement(void* userData, const XML_Char* name) { // This is the safest place to push the data, assuming always comes before . // NCX spec says navLabel comes before content. if (!self->currentLabel.empty() && !self->currentSrc.empty()) { - std::string href = FsHelpers::normalisePath(self->baseContentPath + self->currentSrc); + const std::string rawTarget = self->baseContentPath + self->currentSrc; + const size_t pos = rawTarget.find('#'); + const std::string rawPath = pos == std::string::npos ? rawTarget : rawTarget.substr(0, pos); + std::string href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(rawPath)); std::string anchor; - const size_t pos = href.find('#'); if (pos != std::string::npos) { - anchor = href.substr(pos + 1); - href = href.substr(0, pos); + anchor = FsHelpers::decodeUriEscapes(rawTarget.substr(pos + 1)); } if (self->cache) { diff --git a/lib/FsHelpers/FsHelpers.cpp b/lib/FsHelpers/FsHelpers.cpp index fe48587f..1e3c68a6 100644 --- a/lib/FsHelpers/FsHelpers.cpp +++ b/lib/FsHelpers/FsHelpers.cpp @@ -7,6 +7,34 @@ namespace FsHelpers { +namespace { +bool isHexDigit(const char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } + +uint8_t hexValue(const char c) { + if (c >= '0' && c <= '9') return static_cast(c - '0'); + if (c >= 'a' && c <= 'f') return static_cast(10 + (c - 'a')); + return static_cast(10 + (c - 'A')); +} +} // namespace + +std::string decodeUriEscapes(const std::string& path) { + std::string decoded; + decoded.reserve(path.size()); + + for (size_t i = 0; i < path.size(); i++) { + if (path[i] == '%' && i + 2 < path.size() && isHexDigit(path[i + 1]) && isHexDigit(path[i + 2])) { + const uint8_t value = static_cast((hexValue(path[i + 1]) << 4) | hexValue(path[i + 2])); + decoded += static_cast(value); + i += 2; + continue; + } + + decoded += path[i]; + } + + return decoded; +} + std::string normalisePath(const std::string& path) { std::vector components; std::string component; diff --git a/lib/FsHelpers/FsHelpers.h b/lib/FsHelpers/FsHelpers.h index aa9c44f4..728b190f 100644 --- a/lib/FsHelpers/FsHelpers.h +++ b/lib/FsHelpers/FsHelpers.h @@ -7,6 +7,8 @@ namespace FsHelpers { +std::string decodeUriEscapes(const std::string& path); + std::string normalisePath(const std::string& path); void sortFileList(std::vector& strs);