diff --git a/include/server.h b/include/server.h index 9e050215..0fbdfab6 100644 --- a/include/server.h +++ b/include/server.h @@ -64,6 +64,7 @@ namespace kiwix void setBlockExternalLinks(bool blockExternalLinks) { m_blockExternalLinks = blockExternalLinks; } void setCatalogOnlyMode(bool enable) { m_catalogOnlyMode = enable; } + void setContentServerUrl(std::string url) { m_contentServerUrl = url; } void setIpMode(IpMode mode) { m_ipMode = mode; } int getPort() const; IpAddress getAddress() const; @@ -85,6 +86,7 @@ namespace kiwix IpMode m_ipMode = IpMode::AUTO; int m_ipConnectionLimit = 0; bool m_catalogOnlyMode = false; + std::string m_contentServerUrl; std::unique_ptr mp_server; }; } diff --git a/src/library_dumper.h b/src/library_dumper.h index 3438ef71..880836c7 100644 --- a/src/library_dumper.h +++ b/src/library_dumper.h @@ -50,6 +50,13 @@ class LibraryDumper */ void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; } + /** + * Set the URL of the content server. + * + * @param url the URL of the content server to use. + */ + void setContentServerUrl(const std::string& url) { this->contentServerUrl = url; } + /** * Set some informations about the search results. * @@ -58,10 +65,10 @@ class LibraryDumper * @param count the number of result of the current set (or page). */ void setOpenSearchInfo(int totalResult, int startIndex, int count); - + /** * Sets user default language - * + * * @param userLang the user language to be set */ void setUserLanguage(std::string userLang) { this->m_userLang = userLang; } @@ -81,6 +88,7 @@ class LibraryDumper const kiwix::NameMapper* const nameMapper; std::string libraryId; std::string rootLocation; + std::string contentServerUrl; std::string m_userLang; int m_totalResults; int m_startIndex; diff --git a/src/opds_dumper.cpp b/src/opds_dumper.cpp index 8d216355..bd4c1573 100644 --- a/src/opds_dumper.cpp +++ b/src/opds_dumper.cpp @@ -62,11 +62,15 @@ IllustrationInfo getBookIllustrationInfo(const Book& book) return illustrations; } -std::string fullEntryXML(const Book& book, const std::string& rootLocation, const std::string& contentId) +std::string fullEntryXML(const Book& book, + const std::string& rootLocation, + const std::string& contentServerUrl, + const std::string& contentId) { const auto bookDate = book.getDate() + "T00:00:00Z"; const kainjow::mustache::object data{ {"root", rootLocation}, + {"contentServerUrl", onlyAsNonEmptyMustacheValue(contentServerUrl)}, {"id", book.getId()}, {"name", book.getName()}, {"title", book.getTitle()}, @@ -103,7 +107,12 @@ std::string partialEntryXML(const Book& book, const std::string& rootLocation) return render_template(xmlTemplate, data); } -BooksData getBooksData(const Library* library, const NameMapper* nameMapper, const std::vector& bookIds, const std::string& rootLocation, bool partial) +BooksData getBooksData(const Library* library, + const NameMapper* nameMapper, + const std::vector& bookIds, + const std::string& rootLocation, + const std::string& contentServerUrl, + bool partial) { BooksData booksData; for ( const auto& bookId : bookIds ) { @@ -112,7 +121,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con const std::string contentId = nameMapper->getNameForId(bookId); const auto entryXML = partial ? partialEntryXML(book, rootLocation) - : fullEntryXML(book, rootLocation, contentId); + : fullEntryXML(book, rootLocation, contentServerUrl, contentId); booksData.push_back(kainjow::mustache::object{ {"entry", entryXML} }); } catch ( const std::out_of_range& ) { // the book was removed from the library since its id was obtained @@ -127,7 +136,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con string OPDSDumper::dumpOPDSFeed(const std::vector& bookIds, const std::string& query) const { - const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false); + const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentServerUrl, false); const kainjow::mustache::object template_data{ {"date", gen_date_str()}, {"root", rootLocation}, @@ -145,7 +154,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector& bookIds, const s string OPDSDumper::dumpOPDSFeedV2(const std::vector& bookIds, const std::string& query, bool partial) const { const auto endpointRoot = rootLocation + "/catalog/v2"; - const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial); + const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentServerUrl, partial); const char* const endpoint = partial ? "/partial_entries" : "/entries"; const std::string url = endpoint + (query.empty() ? "" : "?" + query); @@ -170,7 +179,7 @@ std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const const std::string contentId = nameMapper->getNameForId(bookId); return XML_HEADER + "\n" - + fullEntryXML(book, rootLocation, contentId); + + fullEntryXML(book, rootLocation, contentServerUrl, contentId); } std::string OPDSDumper::categoriesOPDSFeed() const diff --git a/src/server.cpp b/src/server.cpp index 9dc9c41b..82dc1b54 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -54,7 +54,8 @@ bool Server::start() { m_ipMode, m_indexTemplateString, m_ipConnectionLimit, - m_catalogOnlyMode)); + m_catalogOnlyMode, + m_contentServerUrl)); return mp_server->start(); } diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 784bbe9b..6e5443cc 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -436,7 +436,8 @@ InternalServer::InternalServer(LibraryPtr library, IpMode ipMode, std::string indexTemplateString, int ipConnectionLimit, - bool catalogOnlyMode) : + bool catalogOnlyMode, + std::string contentServerUrl) : m_addr(addr), m_port(port), m_root(normalizeRootUrl(root)), @@ -456,7 +457,8 @@ InternalServer::InternalServer(LibraryPtr library, searchCache(getEnvVar("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)), suggestionSearcherCache(getEnvVar("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))), m_customizedResources(new CustomizedResources), - m_catalogOnlyMode(catalogOnlyMode) + m_catalogOnlyMode(catalogOnlyMode), + m_contentServerUrl(contentServerUrl) { m_root = urlEncode(m_root); } diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 81f6e48d..af8f7bc1 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -107,7 +107,8 @@ class InternalServer { IpMode ipMode, std::string indexTemplateString, int ipConnectionLimit, - bool catalogOnlyMode); + bool catalogOnlyMode, + std::string zimViewerURL); virtual ~InternalServer(); MHD_Result handlerCallback(struct MHD_Connection* connection, @@ -161,6 +162,7 @@ class InternalServer { std::string getLibraryId() const; std::string getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const; + OPDSDumper getOPDSDumper() const; private: // types class LockableSuggestionSearcher; @@ -195,6 +197,7 @@ class InternalServer { std::unique_ptr m_customizedResources; const bool m_catalogOnlyMode; + const std::string m_contentServerUrl; }; } diff --git a/src/server/internalServer_catalog.cpp b/src/server/internalServer_catalog.cpp index 1c48117f..70754975 100644 --- a/src/server/internalServer_catalog.cpp +++ b/src/server/internalServer_catalog.cpp @@ -51,6 +51,19 @@ const std::string opdsMimeType[] = { } // unnamed namespace +OPDSDumper InternalServer::getOPDSDumper() const +{ + kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); + opdsDumper.setRootLocation(m_root); + opdsDumper.setLibraryId(getLibraryId()); + if ( !m_contentServerUrl.empty() ) { + opdsDumper.setContentServerUrl(m_contentServerUrl); + } else if ( !m_catalogOnlyMode ) { + opdsDumper.setContentServerUrl(m_root); + } + return opdsDumper; +} + std::unique_ptr InternalServer::handle_catalog(const RequestContext& request) { if (m_verbose.load()) { @@ -80,9 +93,7 @@ std::unique_ptr InternalServer::handle_catalog(const RequestContext& r } zim::Uuid uuid; - kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); - opdsDumper.setRootLocation(m_root); - opdsDumper.setLibraryId(getLibraryId()); + kiwix::OPDSDumper opdsDumper = getOPDSDumper(); std::vector bookIdsToDump; if (url == "root.xml") { uuid = zim::Uuid::generate(host); @@ -158,9 +169,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestCo std::unique_ptr InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial) { - OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); - opdsDumper.setRootLocation(m_root); - opdsDumper.setLibraryId(getLibraryId()); + kiwix::OPDSDumper opdsDumper = getOPDSDumper(); const auto bookIds = search_catalog(request, opdsDumper); const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial); return ContentResponse::build( @@ -177,9 +186,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_complete_entry(const return UrlNotFoundResponse(request); } - OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); - opdsDumper.setRootLocation(m_root); - opdsDumper.setLibraryId(getLibraryId()); + kiwix::OPDSDumper opdsDumper = getOPDSDumper(); const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId); return ContentResponse::build( opdsFeed, @@ -189,9 +196,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_complete_entry(const std::unique_ptr InternalServer::handle_catalog_v2_categories(const RequestContext& request) { - OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); - opdsDumper.setRootLocation(m_root); - opdsDumper.setLibraryId(getLibraryId()); + kiwix::OPDSDumper opdsDumper = getOPDSDumper(); return ContentResponse::build( opdsDumper.categoriesOPDSFeed(), opdsMimeType[OPDS_NAVIGATION_FEED] @@ -200,9 +205,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_categories(const Req std::unique_ptr InternalServer::handle_catalog_v2_languages(const RequestContext& request) { - OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get()); - opdsDumper.setRootLocation(m_root); - opdsDumper.setLibraryId(getLibraryId()); + kiwix::OPDSDumper opdsDumper = getOPDSDumper(); return ContentResponse::build( opdsDumper.languagesOPDSFeed(), opdsMimeType[OPDS_NAVIGATION_FEED] diff --git a/static/templates/catalog_v2_entry.xml b/static/templates/catalog_v2_entry.xml index f7c11f1f..f2ede5a7 100644 --- a/static/templates/catalog_v2_entry.xml +++ b/static/templates/catalog_v2_entry.xml @@ -13,7 +13,8 @@ {{#icons}} - {{/icons}} + {{/icons}}{{#contentServerUrl}} + {{/contentServerUrl}} {{author_name}} diff --git a/test/library_server.cpp b/test/library_server.cpp index 4c532724..7ffed4c1 100644 --- a/test/library_server.cpp +++ b/test/library_server.cpp @@ -18,9 +18,11 @@ protected: const int PORT = 8002; protected: - void resetServer(ZimFileServer::Options options) { + void resetServer(ZimFileServer::Options options, std::string contentServerUrl="") { + ZimFileServer::Cfg cfg(options); + cfg.contentServerUrl = contentServerUrl; zfs1_.reset(); - zfs1_.reset(new ZimFileServer(PORT, options, "./test/library.xml")); + zfs1_.reset(new ZimFileServer(PORT, cfg, "./test/library.xml")); } void SetUp() override { @@ -723,7 +725,12 @@ TEST_F(LibraryServerTest, catalog_v2_entries) TEST_F(LibraryServerTest, catalog_v2_entries_catalog_only_mode) { - resetServer(ZimFileServer::CATALOG_ONLY_MODE); + const std::string contentServerUrl = "https://demo.kiwix.org"; + const auto fixContentLinks = [=](std::string s) -> std::string { + s = replace(s, "/ROOT%23%3F/content", contentServerUrl + "/content"); + return s; + }; + resetServer(ZimFileServer::CATALOG_ONLY_MODE, contentServerUrl); const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries"); EXPECT_EQ(r->status, 200); EXPECT_EQ(maskVariableOPDSFeedData(r->body), @@ -731,10 +738,10 @@ TEST_F(LibraryServerTest, catalog_v2_entries_catalog_only_mode) " All Entries\n" " YYYY-MM-DDThh:mm:ssZ\n" "\n" - CHARLES_RAY_CATALOG_ENTRY - INACCESSIBLEZIMFILE_CATALOG_ENTRY - RAY_CHARLES_CATALOG_ENTRY - UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY + + fixContentLinks(CHARLES_RAY_CATALOG_ENTRY) + + fixContentLinks(INACCESSIBLEZIMFILE_CATALOG_ENTRY) + + fixContentLinks(RAY_CHARLES_CATALOG_ENTRY) + + fixContentLinks(UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY) + "\n" ); } diff --git a/test/server_testing_tools.h b/test/server_testing_tools.h index 74b840c9..8f8ea2d5 100644 --- a/test/server_testing_tools.h +++ b/test/server_testing_tools.h @@ -72,6 +72,7 @@ public: // types struct Cfg { std::string root = "ROOT#?"; + std::string contentServerUrl = ""; Options options = DEFAULT_OPTIONS; Cfg(Options opts = DEFAULT_OPTIONS) : options(opts) {} @@ -151,6 +152,7 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString) server->setBlockExternalLinks(cfg.options & BLOCK_EXTERNAL_LINKS); server->setMultiZimSearchLimit(3); server->setCatalogOnlyMode(cfg.options & CATALOG_ONLY_MODE); + server->setContentServerUrl(cfg.contentServerUrl); if (!indexTemplateString.empty()) { server->setIndexTemplateString(indexTemplateString); }