mirror of https://github.com/kiwix/libkiwix.git
Serving /catalog/v2/root.xml
Note: This commit somewhat relaxes validation of non variable `<updated>` elements in the OPDS feed - the contents of any `<updated>` element is replaced with the YYYY-MM-DDThh:mm:ssZ placeholder.
This commit is contained in:
parent
54b78eaf56
commit
3c3cf08a1a
|
@ -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<Response> 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<Response> 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<Response> 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
|
||||
{
|
||||
|
||||
|
|
|
@ -73,6 +73,8 @@ class InternalServer {
|
|||
std::unique_ptr<Response> build_homepage(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_skin(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_root(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_meta(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_search(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_suggest(const RequestContext& request);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>{{feed_id}}</id>
|
||||
<link rel="self"
|
||||
href="{{endpoint_root}}/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start"
|
||||
href="{{endpoint_root}}/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<title>OPDS Catalog Root</title>
|
||||
<updated>{{date}}</updated>
|
||||
|
||||
<entry>
|
||||
<title>All entries</title>
|
||||
<link rel="subsection"
|
||||
href="{{endpoint_root}}/entries"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>{{date}}</updated>
|
||||
<id>{{all_entries_feed_id}}</id>
|
||||
<content type="text">All entries from this catalog.</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>List of categories</title>
|
||||
<link rel="subsection"
|
||||
href="{{endpoint_root}}/categories"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<updated>{{date}}</updated>
|
||||
<id>{{category_list_feed_id}}</id>
|
||||
<content type="text">List of all categories in this catalog.</content>
|
||||
</entry>
|
||||
</feed>
|
|
@ -37,3 +37,4 @@ templates/taskbar_part.html
|
|||
templates/external_blocker_part.html
|
||||
templates/captured_external.html
|
||||
opensearchdescription.xml
|
||||
catalog_v2_root.xml
|
||||
|
|
|
@ -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, " <updated>.+</updated>",
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>");
|
||||
s = replaceLines(s, " <id>.+</id>",
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>");
|
||||
s = replaceLines(s, R"(<updated>\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ</updated>)",
|
||||
"<updated>YYYY-MM-DDThh:mm:ssZ</updated>");
|
||||
s = replaceLines(s, "<id>[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}</id>",
|
||||
"<id>12345678-90ab-cdef-1234-567890abcdef</id>");
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -616,7 +619,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
|||
" <title>Charles, Ray</title>\n" \
|
||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00:00Z</updated>\n" \
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category>jazz</category>\n" \
|
||||
|
@ -640,7 +643,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
|||
" <title>Ray Charles</title>\n" \
|
||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00:00Z</updated>\n" \
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category>wikipedia</category>\n" \
|
||||
|
@ -664,7 +667,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
|||
" <title>Ray (uncategorized) Charles</title>\n" \
|
||||
" <summary>No category is assigned to this library entry.</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00:00Z</updated>\n" \
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category></category>\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"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<link rel="self"
|
||||
href="/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start"
|
||||
href="/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<title>OPDS Catalog Root</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
|
||||
<entry>
|
||||
<title>All entries</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<content type="text">All entries from this catalog.</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>List of categories</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/categories"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<content type="text">List of all categories in this catalog.</content>
|
||||
</entry>
|
||||
</feed>
|
||||
)"
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue