diff --git a/src/search_renderer.cpp b/src/search_renderer.cpp
index 8a41bed1a..5f1d5b675 100644
--- a/src/search_renderer.cpp
+++ b/src/search_renderer.cpp
@@ -166,13 +166,14 @@ kainjow::mustache::data buildPagination(
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
{
+ const std::string absPathPrefix = protocolPrefix + "content/";
// Build the results list
kainjow::mustache::data items{kainjow::mustache::data::type::list};
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
kainjow::mustache::data result;
std::string zim_id(it.getZimId());
result.set("title", it.getTitle());
- result.set("absolutePath", protocolPrefix + urlEncode(mp_nameMapper->getNameForId(zim_id), true) + "/" + urlEncode(it.getPath()));
+ result.set("absolutePath", absPathPrefix + urlEncode(mp_nameMapper->getNameForId(zim_id), true) + "/" + urlEncode(it.getPath()));
result.set("snippet", it.getSnippet());
if (mp_library) {
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp
index 2adad59dd..43411260f 100644
--- a/src/server/internalServer.cpp
+++ b/src/server/internalServer.cpp
@@ -524,6 +524,16 @@ MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
return ret;
}
+namespace
+{
+
+bool isEndpointUrl(const std::string& url, const std::string& endpoint)
+{
+ return startsWith(url, "/" + endpoint + "/") || url == "/" + endpoint;
+};
+
+} // unnamed namespace
+
std::unique_ptr InternalServer::handle_request(const RequestContext& request)
{
try {
@@ -536,39 +546,42 @@ std::unique_ptr InternalServer::handle_request(const RequestContext& r
if ( etag )
return Response::build_304(*this, etag);
- if ( isLocallyCustomizedResource(request.get_url()) )
+ const auto url = request.get_url();
+ if ( isLocallyCustomizedResource(url) )
return handle_locally_customized_resource(request);
- if (startsWith(request.get_url(), "/skin/"))
+ if (url == "/" )
+ return build_homepage(request);
+
+ if (isEndpointUrl(url, "skin"))
return handle_skin(request);
- if (startsWith(request.get_url(), "/catalog/"))
+ if (isEndpointUrl(url, "content"))
+ return handle_content(request);
+
+ if (isEndpointUrl(url, "catalog"))
return handle_catalog(request);
- if (startsWith(request.get_url(), "/raw/"))
+ if (isEndpointUrl(url, "raw"))
return handle_raw(request);
- if (request.get_url() == "/search")
+ if (isEndpointUrl(url, "search"))
return handle_search(request);
- if (request.get_url() == "/search/searchdescription.xml") {
- return ContentResponse::build(
- *this,
- RESOURCE::ft_opensearchdescription_xml,
- get_default_data(),
- "application/opensearchdescription+xml");
- }
-
- if (request.get_url() == "/suggest")
+ if (isEndpointUrl(url, "suggest"))
return handle_suggest(request);
- if (request.get_url() == "/random")
+ if (isEndpointUrl(url, "random"))
return handle_random(request);
- if (request.get_url() == "/catch/external")
- return handle_captured_external(request);
+ if (isEndpointUrl(url, "catch"))
+ return handle_catch(request);
- return handle_content(request);
+ std::string contentUrl = m_root + "/content" + url;
+ const std::string query = request.get_query();
+ if ( ! query.empty() )
+ contentUrl += "?" + query;
+ return Response::build_redirect(*this, contentUrl);
} catch (std::exception& e) {
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
return HTTP500Response(*this, request)
@@ -623,6 +636,11 @@ std::unique_ptr InternalServer::handle_suggest(const RequestContext& r
printf("** running handle_suggest\n");
}
+ if ( startsWith(request.get_url(), "/suggest/") ) {
+ return HTTP404Response(*this, request)
+ + urlNotFoundMsg;
+ }
+
std::string bookName, bookId;
std::shared_ptr archive;
try {
@@ -722,6 +740,18 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re
printf("** running handle_search\n");
}
+ if ( startsWith(request.get_url(), "/search/") ) {
+ if (request.get_url() == "/search/searchdescription.xml") {
+ return ContentResponse::build(
+ *this,
+ RESOURCE::ft_opensearchdescription_xml,
+ get_default_data(),
+ "application/opensearchdescription+xml");
+ }
+ return HTTP404Response(*this, request)
+ + urlNotFoundMsg;
+ }
+
try {
auto searchInfo = getSearchInfo(request);
auto bookIds = searchInfo.getBookIds();
@@ -805,6 +835,11 @@ std::unique_ptr InternalServer::handle_random(const RequestContext& re
printf("** running handle_random\n");
}
+ if ( startsWith(request.get_url(), "/random/") ) {
+ return HTTP404Response(*this, request)
+ + urlNotFoundMsg;
+ }
+
std::string bookName;
std::shared_ptr archive;
try {
@@ -848,6 +883,20 @@ std::unique_ptr InternalServer::handle_captured_external(const Request
return ContentResponse::build(*this, RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
}
+std::unique_ptr InternalServer::handle_catch(const RequestContext& request)
+{
+ if (m_verbose.load()) {
+ printf("** running handle_catch\n");
+ }
+
+ if ( request.get_url() == "/catch/external" ) {
+ return handle_captured_external(request);
+ }
+
+ return HTTP404Response(*this, request)
+ + urlNotFoundMsg;
+}
+
std::unique_ptr InternalServer::handle_catalog(const RequestContext& request)
{
if (m_verbose.load()) {
@@ -919,15 +968,6 @@ InternalServer::search_catalog(const RequestContext& request,
namespace
{
-std::string get_book_name(const RequestContext& request)
-{
- try {
- return request.get_url_part(0);
- } catch (const std::out_of_range& e) {
- return std::string();
- }
-}
-
ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::string& pattern)
{
return ParameterizedMessage("suggest-search",
@@ -942,7 +982,8 @@ ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::s
std::unique_ptr
InternalServer::build_redirect(const std::string& bookName, const zim::Item& item) const
{
- auto redirectUrl = m_root + "/" + bookName + "/" + kiwix::urlEncode(item.getPath());
+ const auto path = kiwix::urlEncode(item.getPath());
+ const auto redirectUrl = m_root + "/content/" + bookName + "/" + path;
return Response::build_redirect(*this, redirectUrl);
}
@@ -954,9 +995,10 @@ std::unique_ptr InternalServer::handle_content(const RequestContext& r
printf("** running handle_content\n");
}
- const std::string bookName = get_book_name(request);
- if (bookName.empty())
- return build_homepage(request);
+ const std::string contentPrefix = "/content/";
+ const bool isContentPrefixedUrl = startsWith(url, contentPrefix);
+ const size_t prefixLength = isContentPrefixedUrl ? contentPrefix.size() : 1;
+ const std::string bookName = request.get_url_part(isContentPrefixedUrl);
std::shared_ptr archive;
try {
@@ -972,7 +1014,7 @@ std::unique_ptr InternalServer::handle_content(const RequestContext& r
+ TaskbarInfo(bookName);
}
- auto urlStr = request.get_url().substr(bookName.size()+1);
+ auto urlStr = url.substr(prefixLength + bookName.size());
if (urlStr[0] == '/') {
urlStr = urlStr.substr(1);
}
diff --git a/src/server/internalServer.h b/src/server/internalServer.h
index 011fce2e4..82574378c 100644
--- a/src/server/internalServer.h
+++ b/src/server/internalServer.h
@@ -138,6 +138,7 @@ class InternalServer {
std::unique_ptr handle_search(const RequestContext& request);
std::unique_ptr handle_suggest(const RequestContext& request);
std::unique_ptr handle_random(const RequestContext& request);
+ std::unique_ptr handle_catch(const RequestContext& request);
std::unique_ptr handle_captured_external(const RequestContext& request);
std::unique_ptr handle_content(const RequestContext& request);
std::unique_ptr handle_raw(const RequestContext& request);
diff --git a/static/templates/catalog_v2_entry.xml b/static/templates/catalog_v2_entry.xml
index b572da69c..c92a692df 100644
--- a/static/templates/catalog_v2_entry.xml
+++ b/static/templates/catalog_v2_entry.xml
@@ -18,7 +18,7 @@
{{#icons}}
- {{/icons}}
+ {{/icons}}
{{author_name}}
diff --git a/test/data/customized_resources.txt b/test/data/customized_resources.txt
index 5c5f2b72e..16c4b69c2 100644
--- a/test/data/customized_resources.txt
+++ b/test/data/customized_resources.txt
@@ -2,4 +2,5 @@
/ text/html ./test/welcome.html
/skin/index.css application/json ./test/helloworld.txt
/zimfile/A/Ray_Charles ray/charles ./test/welcome.html
+/content/zimfile/A/Ray_Charles charles/ray ./test/welcome.html
/search text/html ./test/helloworld.txt
diff --git a/test/library_server.cpp b/test/library_server.cpp
index 5b2cf0fad..d8f1ecacf 100644
--- a/test/library_server.cpp
+++ b/test/library_server.cpp
@@ -83,7 +83,7 @@ std::string maskVariableOPDSFeedData(std::string s)
" unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes \n" \
" 284 \n" \
" 2 \n" \
- " \n" \
+ " \n" \
" \n" \
" Wikipedia \n" \
" \n" \
@@ -110,7 +110,7 @@ std::string maskVariableOPDSFeedData(std::string s)
" \n" \
- " \n" \
+ " \n" \
" \n" \
" Wikipedia \n" \
" \n" \
@@ -134,7 +134,7 @@ std::string maskVariableOPDSFeedData(std::string s)
" public_tag_with_a_value:value_of_a_public_tag;_private_tag_with_a_value:value_of_a_private_tag;wikipedia;_pictures:no;_videos:no;_details:no \n" \
" 284 \n" \
" 2 \n" \
- " \n" \
+ " \n" \
" \n" \
" Wikipedia \n" \
" \n" \
diff --git a/test/server.cpp b/test/server.cpp
index 034a398cb..886fcf542 100644
--- a/test/server.cpp
+++ b/test/server.cpp
@@ -6,6 +6,8 @@
#define SERVER_PORT 8001
#include "server_testing_tools.h"
+#include "../src/tools/stringTools.h"
+
bool is_valid_etag(const std::string& etag)
{
@@ -53,8 +55,8 @@ const ResourceCollection resources200Compressible{
{ NO_ETAG, "/ROOT/catch/external?source=www.example.com" },
- { WITH_ETAG, "/ROOT/zimfile/A/index" },
- { WITH_ETAG, "/ROOT/zimfile/A/Ray_Charles" },
+ { WITH_ETAG, "/ROOT/content/zimfile/A/index" },
+ { WITH_ETAG, "/ROOT/content/zimfile/A/Ray_Charles" },
{ WITH_ETAG, "/ROOT/raw/zimfile/content/A/index" },
{ WITH_ETAG, "/ROOT/raw/zimfile/content/A/Ray_Charles" },
@@ -74,11 +76,12 @@ const ResourceCollection resources200Uncompressible{
{ NO_ETAG, "/ROOT/catalog/v2/illustration/6f1d19d0-633f-087b-fb55-7ac324ff9baf?size=48" },
- { WITH_ETAG, "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
+ { WITH_ETAG, "/ROOT/content/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
+
+ { WITH_ETAG, "/ROOT/content/corner_cases/A/empty.html" },
+ { WITH_ETAG, "/ROOT/content/corner_cases/-/empty.css" },
+ { WITH_ETAG, "/ROOT/content/corner_cases/-/empty.js" },
- { WITH_ETAG, "/ROOT/corner_cases/A/empty.html" },
- { WITH_ETAG, "/ROOT/corner_cases/-/empty.css" },
- { WITH_ETAG, "/ROOT/corner_cases/-/empty.js" },
// The following url's responses are too small to be compressed
{ NO_ETAG, "/ROOT/catalog/root.xml" },
@@ -193,7 +196,7 @@ R"EXPECTEDRESULT(
@@ -248,23 +251,27 @@ TEST_F(ServerTest, 400)
const char* urls404[] = {
"/",
"/zimfile",
- "/ROOT/non-existent-item",
"/ROOT/skin/non-existent-skin-resource",
"/ROOT/catalog",
+ "/ROOT/catalog/",
"/ROOT/catalog/non-existent-item",
- "/ROOT/catalogBLABLABLA/root.xml",
"/ROOT/catalog/v2/illustration/zimfile?size=48",
"/ROOT/catalog/v2/illustration/6f1d19d0-633f-087b-fb55-7ac324ff9baf?size=96",
- "/ROOT/meta",
- "/ROOT/meta?content=zimfile",
- "/ROOT/meta?content=zimfile&name=non-existent-item",
- "/ROOT/meta?content=non-existent-book&name=title",
"/ROOT/random",
"/ROOT/random?content=non-existent-book",
+ "/ROOT/random/",
+ "/ROOT/random/number",
"/ROOT/suggest",
"/ROOT/suggest?content=non-existent-book&term=abcd",
- "/ROOT/catch/external",
- "/ROOT/zimfile/A/non-existent-article",
+ "/ROOT/suggest/",
+ "/ROOT/suggest/fr",
+ "/ROOT/search/",
+ "/ROOT/search/anythingotherthansearchdescription.xml",
+ "/ROOT/catch/",
+ "/ROOT/catch/external", // missing ?source=URL
+ "/ROOT/catch/external?source=",
+ "/ROOT/catch/anythingotherthanexternal",
+ "/ROOT/content/zimfile/A/non-existent-article",
"/ROOT/raw/non-existent-book/meta/Title",
"/ROOT/raw/zimfile/wrong-kind/Foo",
@@ -335,6 +342,13 @@ TEST_F(CustomizedServerTest, ContentOfAnyServableUrlCanBeOverriden)
EXPECT_EQ(r->body, "Welcome\n");
}
+ {
+ const auto r = zfs1_->GET("/ROOT/content/zimfile/A/Ray_Charles");
+ EXPECT_EQ(r->status, 200);
+ EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "charles/ray");
+ EXPECT_EQ(r->body, "Welcome\n");
+ }
+
{
const auto r = zfs1_->GET("/ROOT/search?pattern=la+femme");
EXPECT_EQ(r->status, 200);
@@ -661,62 +675,62 @@ TEST_F(ServerTest, Http404HtmlError)
)" },
- { /* url */ "/ROOT/invalid-book/whatever",
+ { /* url */ "/ROOT/content/invalid-book/whatever",
expected_body==R"(
Not Found
- The requested URL "/ROOT/invalid-book/whatever" was not found on this server.
+ The requested URL "/ROOT/content/invalid-book/whatever" was not found on this server.
Make a full text search for whatever
)" },
- { /* url */ "/ROOT/zimfile/invalid-article",
+ { /* url */ "/ROOT/content/zimfile/invalid-article",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_body==R"(
Not Found
- The requested URL "/ROOT/zimfile/invalid-article" was not found on this server.
+ The requested URL "/ROOT/content/zimfile/invalid-article" was not found on this server.
Make a full text search for invalid-article
)" },
- { /* url */ R"(/ROOT/">)",
+ { /* url */ R"(/ROOT/content/">)",
expected_body==R"(
Not Found
- The requested URL "/ROOT/"><svg onload=alert(1)>" was not found on this server.
+ The requested URL "/ROOT/content/"><svg onload=alert(1)>" was not found on this server.
Make a full text search for "><svg onload=alert(1)>
)" },
- { /* url */ R"(/ROOT/zimfile/">)",
+ { /* url */ R"(/ROOT/content/zimfile/">)",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_body==R"(
Not Found
- The requested URL "/ROOT/zimfile/"><svg onload=alert(1)>" was not found on this server.
+ The requested URL "/ROOT/content/zimfile/"><svg onload=alert(1)>" was not found on this server.
Make a full text search for "><svg onload=alert(1)>
)" },
- { /* url */ "/ROOT/zimfile/invalid-article?userlang=hy",
+ { /* url */ "/ROOT/content/zimfile/invalid-article?userlang=hy",
expected_page_title=="Սխալ հասցե" &&
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_body==R"(
Սխալ հասցե
- Սխալ հասցե՝ /ROOT/zimfile/invalid-article
+ Սխալ հասցե՝ /ROOT/content/zimfile/invalid-article
Որոնել invalid-article
@@ -964,9 +978,11 @@ TEST_F(ServerTest, 500)