From 3c3cf08a1ac55cd23c98a51ac673c29f80cf936d Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Thu, 15 Apr 2021 21:54:54 +0400 Subject: [PATCH] Serving /catalog/v2/root.xml Note: This commit somewhat relaxes validation of non variable `` elements in the OPDS feed - the contents of any `` element is replaced with the YYYY-MM-DDThh:mm:ssZ placeholder. --- src/server/internalServer.cpp | 58 ++++++++++++++++++++++++++++++++ src/server/internalServer.h | 2 ++ static/catalog_v2_root.xml | 31 +++++++++++++++++ static/resources_list.txt | 1 + test/server.cpp | 63 +++++++++++++++++++++++++++++------ 5 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 static/catalog_v2_root.xml diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index c626da4e7..460d92765 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -76,6 +76,23 @@ extern "C" { namespace kiwix { +namespace +{ + +inline std::string gen_uuid(const std::string& s) +{ + return to_string(zim::Uuid::generate(s)); +} + +inline std::string normalizeRootUrl(const std::string& rootUrl) +{ + return (rootUrl.empty() || rootUrl[0] == '/') + ? rootUrl + : "/" + rootUrl; +} + +} // unnamed namespace + static IdNameMapper defaultNameMapper; static MHD_Result staticHandlerCallback(void* cls, @@ -606,6 +623,10 @@ std::unique_ptr InternalServer::handle_catalog(const RequestContext& r return Response::build_404(*this, request, ""); } + if (url == "v2") { + return handle_catalog_v2(request); + } + if (url != "searchdescription.xml" && url != "root.xml" && url != "search") { return Response::build_404(*this, request, ""); } @@ -686,6 +707,43 @@ InternalServer::search_catalog(const RequestContext& request, return bookIdsToDump; } +std::unique_ptr InternalServer::handle_catalog_v2(const RequestContext& request) +{ + if (m_verbose.load()) { + printf("** running handle_catalog_v2"); + } + + std::string url; + try { + url = request.get_url_part(2); + } catch (const std::out_of_range&) { + return Response::build_404(*this, request, ""); + } + + if (url == "root.xml") { + return handle_catalog_v2_root(request); + } else { + return Response::build_404(*this, request, ""); + } +} + +std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestContext& request) +{ + const std::string root_url = normalizeRootUrl(m_root); + return ContentResponse::build( + *this, + RESOURCE::catalog_v2_root_xml, + kainjow::mustache::object{ + {"date", gen_date_str()}, + {"endpoint_root", root_url + "/catalog/v2"}, + {"feed_id", gen_uuid(m_server_id)}, + {"all_entries_feed_id", gen_uuid(m_server_id + "/entries")}, + {"category_list_feed_id", gen_uuid(m_server_id + "/categories")} + }, + "application/atom+xml;profile=opds-catalog;kind=navigation" + ); +} + namespace { diff --git a/src/server/internalServer.h b/src/server/internalServer.h index c37c736f2..7e98638a3 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -73,6 +73,8 @@ class InternalServer { std::unique_ptr build_homepage(const RequestContext& request); std::unique_ptr handle_skin(const RequestContext& request); std::unique_ptr handle_catalog(const RequestContext& request); + std::unique_ptr handle_catalog_v2(const RequestContext& request); + std::unique_ptr handle_catalog_v2_root(const RequestContext& request); std::unique_ptr handle_meta(const RequestContext& request); std::unique_ptr handle_search(const RequestContext& request); std::unique_ptr handle_suggest(const RequestContext& request); diff --git a/static/catalog_v2_root.xml b/static/catalog_v2_root.xml new file mode 100644 index 000000000..928f069bf --- /dev/null +++ b/static/catalog_v2_root.xml @@ -0,0 +1,31 @@ + + + {{feed_id}} + + + OPDS Catalog Root + {{date}} + + + All entries + + {{date}} + {{all_entries_feed_id}} + All entries from this catalog. + + + List of categories + + {{date}} + {{category_list_feed_id}} + List of all categories in this catalog. + + diff --git a/static/resources_list.txt b/static/resources_list.txt index 383c8228e..5b6341a37 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -37,3 +37,4 @@ templates/taskbar_part.html templates/external_blocker_part.html templates/captured_external.html opensearchdescription.xml +catalog_v2_root.xml diff --git a/test/server.cpp b/test/server.cpp index 68b18fef5..9c2575023 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -571,18 +571,21 @@ protected: } }; -// Returns a copy of 'text' with every line that fully matches 'pattern' -// replaced with the fixed string 'replacement' +// Returns a copy of 'text' where every line that fully matches 'pattern' +// preceded by optional whitespace is replaced with the fixed string +// 'replacement' preserving the leading whitespace std::string replaceLines(const std::string& text, const std::string& pattern, const std::string& replacement) { - std::regex regex("^" + pattern + "$"); + std::regex regex("^ *" + pattern + "$"); std::ostringstream oss; std::istringstream iss(text); std::string line; while ( std::getline(iss, line) ) { if ( std::regex_match(line, regex) ) { + for ( size_t i = 0; i < line.size() && line[i] == ' '; ++i ) + oss << ' '; oss << replacement << "\n"; } else { oss << line << "\n"; @@ -593,10 +596,10 @@ std::string replaceLines(const std::string& text, std::string maskVariableOPDSFeedData(std::string s) { - s = replaceLines(s, " .+", - " YYYY-MM-DDThh:mm:ssZ"); - s = replaceLines(s, " .+", - " 12345678-90ab-cdef-1234-567890abcdef"); + s = replaceLines(s, R"(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ)", + "YYYY-MM-DDThh:mm:ssZ"); + s = replaceLines(s, "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}", + "12345678-90ab-cdef-1234-567890abcdef"); return s; } @@ -616,7 +619,7 @@ std::string maskVariableOPDSFeedData(std::string s) " Charles, Ray\n" \ " Wikipedia articles about Ray Charles\n" \ " eng\n" \ - " 2020-03-31T00:00:00Z\n" \ + " YYYY-MM-DDThh:mm:ssZ\n" \ " wikipedia_en_ray_charles\n" \ " \n" \ " jazz\n" \ @@ -640,7 +643,7 @@ std::string maskVariableOPDSFeedData(std::string s) " Ray Charles\n" \ " Wikipedia articles about Ray Charles\n" \ " eng\n" \ - " 2020-03-31T00:00:00Z\n" \ + " YYYY-MM-DDThh:mm:ssZ\n" \ " wikipedia_en_ray_charles\n" \ " \n" \ " wikipedia\n" \ @@ -664,7 +667,7 @@ std::string maskVariableOPDSFeedData(std::string s) " Ray (uncategorized) Charles\n" \ " No category is assigned to this library entry.\n" \ " eng\n" \ - " 2020-03-31T00:00:00Z\n" \ + " YYYY-MM-DDThh:mm:ssZ\n" \ " wikipedia_en_ray_charles\n" \ " \n" \ " \n" \ @@ -898,3 +901,43 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination) ); } } + +TEST_F(LibraryServerTest, catalog_v2_root) +{ + const auto r = zfs1_->GET("/catalog/v2/root.xml"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), +R"( + + 12345678-90ab-cdef-1234-567890abcdef + + + OPDS Catalog Root + YYYY-MM-DDThh:mm:ssZ + + + All entries + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + All entries from this catalog. + + + List of categories + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + List of all categories in this catalog. + + +)" + ); +}