Merge pull request #732 from kiwix/HTTP404HtmlResponse

New way of building 404 error HTML responses
This commit is contained in:
Kelson 2022-03-28 15:27:46 +02:00 committed by GitHub
commit 49f24d18df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 67 deletions

View File

@ -73,9 +73,6 @@ IllustrationInfo getBookIllustrationInfo(const Book& book)
kainjow::mustache::object getSingleBookData(const Book& book) kainjow::mustache::object getSingleBookData(const Book& book)
{ {
const auto bookDate = book.getDate() + "T00:00:00Z"; const auto bookDate = book.getDate() + "T00:00:00Z";
const MustacheData bookUrl = book.getUrl().empty()
? MustacheData(false)
: MustacheData(book.getUrl());
return kainjow::mustache::object{ return kainjow::mustache::object{
{"id", book.getId()}, {"id", book.getId()},
{"name", book.getName()}, {"name", book.getName()},
@ -92,7 +89,7 @@ kainjow::mustache::object getSingleBookData(const Book& book)
{"media_count", to_string(book.getMediaCount())}, {"media_count", to_string(book.getMediaCount())},
{"author_name", book.getCreator()}, {"author_name", book.getCreator()},
{"publisher_name", book.getPublisher()}, {"publisher_name", book.getPublisher()},
{"url", bookUrl}, {"url", onlyAsNonEmptyMustacheValue(book.getUrl())},
{"size", to_string(book.getSize())}, {"size", to_string(book.getSize())},
{"icons", getBookIllustrationInfo(book)}, {"icons", getBookIllustrationInfo(book)},
}; };
@ -194,7 +191,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
{"date", gen_date_str()}, {"date", gen_date_str()},
{"root", rootLocation}, {"root", rootLocation},
{"feed_id", gen_uuid(libraryId + "/catalog/search?"+query)}, {"feed_id", gen_uuid(libraryId + "/catalog/search?"+query)},
{"filter", query.empty() ? MustacheData(false) : MustacheData(query)}, {"filter", onlyAsNonEmptyMustacheValue(query)},
{"totalResults", to_string(m_totalResults)}, {"totalResults", to_string(m_totalResults)},
{"startIndex", to_string(m_startIndex)}, {"startIndex", to_string(m_startIndex)},
{"itemsPerPage", to_string(m_count)}, {"itemsPerPage", to_string(m_count)},
@ -214,7 +211,7 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
{"date", gen_date_str()}, {"date", gen_date_str()},
{"endpoint_root", endpointRoot}, {"endpoint_root", endpointRoot},
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)}, {"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
{"filter", query.empty() ? MustacheData(false) : MustacheData(query)}, {"filter", onlyAsNonEmptyMustacheValue(query)},
{"query", query.empty() ? "" : "?" + urlEncode(query)}, {"query", query.empty() ? "" : "?" + urlEncode(query)},
{"totalResults", to_string(m_totalResults)}, {"totalResults", to_string(m_totalResults)},
{"startIndex", to_string(m_startIndex)}, {"startIndex", to_string(m_startIndex)},

View File

@ -278,8 +278,10 @@ MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& request) std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& request)
{ {
try { try {
if (! request.is_valid_url()) if (! request.is_valid_url()) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
}
const ETag etag = get_matching_if_none_match_etag(request); const ETag etag = get_matching_if_none_match_etag(request);
if ( etag ) if ( etag )
@ -387,6 +389,15 @@ SuggestionsList_t getSuggestions(SuggestionSearcherCache& cache, const zim::Arch
return suggestions; return suggestions;
} }
namespace
{
std::string noSuchBookErrorMsg(const std::string& bookName)
{
return "No such book: " + bookName;
}
} // unnamed namespace
std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& request) std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& request)
{ {
@ -405,8 +416,9 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
} }
if (archive == nullptr) { if (archive == nullptr) {
const std::string error_details = "No such book: " + bookName; return HTTP404HtmlResponse(*this, request)
return Response::build_404(*this, "", bookName, "", error_details); + noSuchBookErrorMsg(bookName)
+ TaskbarInfo(bookName);
} }
const auto queryString = request.get_optional_param("term", std::string()); const auto queryString = request.get_optional_param("term", std::string());
@ -476,7 +488,8 @@ std::unique_ptr<Response> InternalServer::handle_skin(const RequestContext& requ
response->set_cacheable(); response->set_cacheable();
return std::move(response); return std::move(response);
} catch (const ResourceNotFound& e) { } catch (const ResourceNotFound& e) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
} }
@ -519,9 +532,8 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
data.set("pattern", encodeDiples(patternString)); data.set("pattern", encodeDiples(patternString));
data.set("root", m_root); data.set("root", m_root);
auto response = ContentResponse::build(*this, RESOURCE::templates::no_search_result_html, data, "text/html; charset=utf-8"); auto response = ContentResponse::build(*this, RESOURCE::templates::no_search_result_html, data, "text/html; charset=utf-8");
response->set_taskbar(bookName, archive ? getArchiveTitle(*archive) : "");
response->set_code(MHD_HTTP_NOT_FOUND); response->set_code(MHD_HTTP_NOT_FOUND);
return std::move(response); return withTaskbarInfo(bookName, archive.get(), std::move(response));
} }
std::shared_ptr<zim::Searcher> searcher; std::shared_ptr<zim::Searcher> searcher;
@ -591,9 +603,7 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
renderer.setSearchProtocolPrefix(m_root + "/search?"); renderer.setSearchProtocolPrefix(m_root + "/search?");
renderer.setPageLength(pageLength); renderer.setPageLength(pageLength);
auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8"); auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
response->set_taskbar(bookName, archive ? getArchiveTitle(*archive) : ""); return withTaskbarInfo(bookName, archive.get(), std::move(response));
return std::move(response);
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
return Response::build_500(*this, e.what()); return Response::build_500(*this, e.what());
@ -617,8 +627,9 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
} }
if (archive == nullptr) { if (archive == nullptr) {
const std::string error_details = "No such book: " + bookName; return HTTP404HtmlResponse(*this, request)
return Response::build_404(*this, "", bookName, "", error_details); + noSuchBookErrorMsg(bookName)
+ TaskbarInfo(bookName);
} }
try { try {
@ -626,7 +637,8 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
return build_redirect(bookName, getFinalItem(*archive, entry)); return build_redirect(bookName, getFinalItem(*archive, entry));
} catch(zim::EntryNotFound& e) { } catch(zim::EntryNotFound& e) {
const std::string error_details = "Oops! Failed to pick a random article :("; const std::string error_details = "Oops! Failed to pick a random article :(";
return Response::build_404(*this, "", bookName, getArchiveTitle(*archive), error_details); auto response = Response::build_404(*this, "", error_details);
return withTaskbarInfo(bookName, archive.get(), std::move(response));
} }
} }
@ -637,8 +649,10 @@ std::unique_ptr<Response> InternalServer::handle_captured_external(const Request
source = kiwix::urlDecode(request.get_argument("source")); source = kiwix::urlDecode(request.get_argument("source"));
} catch (const std::out_of_range& e) {} } catch (const std::out_of_range& e) {}
if (source.empty()) if (source.empty()) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
}
auto data = get_default_data(); auto data = get_default_data();
data.set("source", source); data.set("source", source);
@ -657,7 +671,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
host = request.get_header("Host"); host = request.get_header("Host");
url = request.get_url_part(1); url = request.get_url_part(1);
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
if (url == "v2") { if (url == "v2") {
@ -665,7 +680,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
} }
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") { if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
if (url == "searchdescription.xml") { if (url == "searchdescription.xml") {
@ -802,7 +818,8 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern, true); // Make a full search on the entire library. std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern, true); // Make a full search on the entire library.
const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern)); const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern));
return Response::build_404(*this, request.get_full_url(), bookName, "", details); auto response = Response::build_404(*this, request.get_full_url(), details);
return withTaskbarInfo(bookName, nullptr, std::move(response));
} }
auto urlStr = request.get_url().substr(bookName.size()+1); auto urlStr = request.get_url().substr(bookName.size()+1);
@ -819,7 +836,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
} }
auto response = ItemResponse::build(*this, request, entry.getItem()); auto response = ItemResponse::build(*this, request, entry.getItem());
try { try {
dynamic_cast<ContentResponse&>(*response).set_taskbar(bookName, getArchiveTitle(*archive)); dynamic_cast<ContentResponse&>(*response).set_taskbar(bookName, archive.get());
} catch (std::bad_cast& e) {} } catch (std::bad_cast& e) {}
if (m_verbose.load()) { if (m_verbose.load()) {
@ -835,7 +852,8 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern, true); // Make a search on this specific book only. std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern, true); // Make a search on this specific book only.
const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern)); const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern));
return Response::build_404(*this, request.get_full_url(), bookName, getArchiveTitle(*archive), details); auto response = Response::build_404(*this, request.get_full_url(), details);
return withTaskbarInfo(bookName, archive.get(), std::move(response));
} }
} }
@ -852,12 +870,13 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
bookName = request.get_url_part(1); bookName = request.get_url_part(1);
kind = request.get_url_part(2); kind = request.get_url_part(2);
} catch (const std::out_of_range& e) { } catch (const std::out_of_range& e) {
return Response::build_404(*this, request.get_full_url(), bookName, "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
if (kind != "meta" && kind!= "content") { if (kind != "meta" && kind!= "content") {
const std::string error_details = kind + " is not a valid request for raw content."; const std::string error_details = kind + " is not a valid request for raw content.";
return Response::build_404(*this, request.get_full_url(), bookName, "", error_details); return Response::build_404(*this, request.get_full_url(), error_details);
} }
std::shared_ptr<zim::Archive> archive; std::shared_ptr<zim::Archive> archive;
@ -867,8 +886,9 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
} catch (const std::out_of_range& e) {} } catch (const std::out_of_range& e) {}
if (archive == nullptr) { if (archive == nullptr) {
const std::string error_details = "No such book: " + bookName; return HTTP404HtmlResponse(*this, request)
return Response::build_404(*this, request.get_full_url(), bookName, "", error_details); + urlNotFoundMsg
+ noSuchBookErrorMsg(bookName);
} }
// Remove the beggining of the path: // Remove the beggining of the path:
@ -893,7 +913,7 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
printf("Failed to find %s\n", itemPath.c_str()); printf("Failed to find %s\n", itemPath.c_str());
} }
const std::string error_details = "Cannot find " + kind + " entry " + itemPath; const std::string error_details = "Cannot find " + kind + " entry " + itemPath;
return Response::build_404(*this, request.get_full_url(), bookName, getArchiveTitle(*archive), error_details); return Response::build_404(*this, request.get_full_url(), error_details);
} }
} }

View File

@ -43,7 +43,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
try { try {
url = request.get_url_part(2); url = request.get_url_part(2);
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
if (url == "root.xml") { if (url == "root.xml") {
@ -69,7 +70,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
} else if (url == "illustration") { } else if (url == "illustration") {
return handle_catalog_v2_illustration(request); return handle_catalog_v2_illustration(request);
} else { } else {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
} }
@ -110,7 +112,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
try { try {
mp_library->getBookById(entryId); mp_library->getBookById(entryId);
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
OPDSDumper opdsDumper(mp_library); OPDSDumper opdsDumper(mp_library);
@ -158,7 +161,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_illustration(const R
auto illustration = book.getIllustration(size); auto illustration = book.getIllustration(size);
return ContentResponse::build(*this, illustration->getData(), illustration->mimeType); return ContentResponse::build(*this, illustration->getData(), illustration->mimeType);
} catch(...) { } catch(...) {
return Response::build_404(*this, request.get_full_url(), "", ""); return HTTP404HtmlResponse(*this, request)
+ urlNotFoundMsg;
} }
} }

View File

@ -25,6 +25,7 @@
#include "tools/regexTools.h" #include "tools/regexTools.h"
#include "tools/stringTools.h" #include "tools/stringTools.h"
#include "tools/otherTools.h" #include "tools/otherTools.h"
#include "tools/archiveTools.h"
#include "string.h" #include "string.h"
#include <mustache.hpp> #include <mustache.hpp>
@ -83,19 +84,71 @@ std::unique_ptr<Response> Response::build_304(const InternalServer& server, cons
return response; return response;
} }
std::unique_ptr<Response> Response::build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details) kainjow::mustache::data make404ResponseData(const std::string& url, const std::string& details)
{ {
MustacheData results; kainjow::mustache::list pList;
if ( !url.empty() ) { if ( !url.empty() ) {
results.set("url", url); kainjow::mustache::mustache msgTmpl(R"(The requested URL "{{url}}" was not found on this server.)");
const auto urlNotFoundMsg = msgTmpl.render({"url", url});
pList.push_back({"p", urlNotFoundMsg});
} }
results.set("details", details); pList.push_back({"p", details});
return {"details", pList};
}
auto response = ContentResponse::build(server, RESOURCE::templates::_404_html, results, "text/html"); std::unique_ptr<ContentResponse> Response::build_404(const InternalServer& server, const std::string& url, const std::string& details)
{
return build_404(server, make404ResponseData(url, details));
}
std::unique_ptr<ContentResponse> Response::build_404(const InternalServer& server, const kainjow::mustache::data& data)
{
auto response = ContentResponse::build(server, RESOURCE::templates::_404_html, data, "text/html");
response->set_code(MHD_HTTP_NOT_FOUND); response->set_code(MHD_HTTP_NOT_FOUND);
response->set_taskbar(bookName, bookTitle);
return std::move(response); return response;
}
extern const UrlNotFoundMsg urlNotFoundMsg;
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
{
auto r = ContentResponse::build(m_server, m_template, m_data, m_mimeType);
r->set_code(m_httpStatusCode);
return m_taskbarInfo
? withTaskbarInfo(m_taskbarInfo->bookName, m_taskbarInfo->archive, std::move(r))
: std::move(r);
}
HTTP404HtmlResponse::HTTP404HtmlResponse(const InternalServer& server,
const RequestContext& request)
: ContentResponseBlueprint(&server,
&request,
MHD_HTTP_NOT_FOUND,
"text/html",
RESOURCE::templates::_404_html)
{
kainjow::mustache::list emptyList;
this->m_data = kainjow::mustache::object{{"details", emptyList}};
}
HTTP404HtmlResponse& HTTP404HtmlResponse::operator+(UrlNotFoundMsg /*unused*/)
{
const std::string requestUrl = m_request.get_full_url();
kainjow::mustache::mustache msgTmpl(R"(The requested URL "{{url}}" was not found on this server.)");
return *this + msgTmpl.render({"url", requestUrl});
}
HTTP404HtmlResponse& HTTP404HtmlResponse::operator+(const std::string& msg)
{
m_data["details"].push_back({"p", msg});
return *this;
}
ContentResponseBlueprint& ContentResponseBlueprint::operator+(const TaskbarInfo& taskbarInfo)
{
this->m_taskbarInfo.reset(new TaskbarInfo(taskbarInfo));
return *this;
} }
std::unique_ptr<Response> Response::build_416(const InternalServer& server, size_t resourceLength) std::unique_ptr<Response> Response::build_416(const InternalServer& server, size_t resourceLength)
@ -332,10 +385,10 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
return ret; return ret;
} }
void ContentResponse::set_taskbar(const std::string& bookName, const std::string& bookTitle) void ContentResponse::set_taskbar(const std::string& bookName, const zim::Archive* archive)
{ {
m_bookName = bookName; m_bookName = bookName;
m_bookTitle = bookTitle; m_bookTitle = archive ? getArchiveTitle(*archive) : "";
} }
@ -383,6 +436,15 @@ std::unique_ptr<ContentResponse> ContentResponse::build(
return ContentResponse::build(server, content, mimetype, isHomePage); return ContentResponse::build(server, content, mimetype, isHomePage);
} }
std::unique_ptr<ContentResponse> withTaskbarInfo(
const std::string& bookName,
const zim::Archive* archive,
std::unique_ptr<ContentResponse> r)
{
r->set_taskbar(bookName, archive);
return r;
}
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) : ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
Response(verbose), Response(verbose),
m_item(item), m_item(item),

View File

@ -33,12 +33,16 @@ extern "C" {
#include "microhttpd_wrapper.h" #include "microhttpd_wrapper.h"
} }
namespace zim {
class Archive;
} // namespace zim
namespace kiwix { namespace kiwix {
class InternalServer; class InternalServer;
class RequestContext; class RequestContext;
class EntryResponse; class ContentResponse;
class Response { class Response {
public: public:
@ -47,7 +51,8 @@ class Response {
static std::unique_ptr<Response> build(const InternalServer& server); static std::unique_ptr<Response> build(const InternalServer& server);
static std::unique_ptr<Response> build_304(const InternalServer& server, const ETag& etag); static std::unique_ptr<Response> build_304(const InternalServer& server, const ETag& etag);
static std::unique_ptr<Response> build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details=""); static std::unique_ptr<ContentResponse> build_404(const InternalServer& server, const kainjow::mustache::data& data);
static std::unique_ptr<ContentResponse> build_404(const InternalServer& server, const std::string& url, const std::string& details="");
static std::unique_ptr<Response> build_416(const InternalServer& server, size_t resourceLength); static std::unique_ptr<Response> build_416(const InternalServer& server, size_t resourceLength);
static std::unique_ptr<Response> build_500(const InternalServer& server, const std::string& msg); static std::unique_ptr<Response> build_500(const InternalServer& server, const std::string& msg);
static std::unique_ptr<Response> build_redirect(const InternalServer& server, const std::string& redirectUrl); static std::unique_ptr<Response> build_redirect(const InternalServer& server, const std::string& redirectUrl);
@ -100,7 +105,7 @@ class ContentResponse : public Response {
const std::string& mimetype, const std::string& mimetype,
bool isHomePage = false); bool isHomePage = false);
void set_taskbar(const std::string& bookName, const std::string& bookTitle); void set_taskbar(const std::string& bookName, const zim::Archive* archive);
private: private:
MHD_Response* create_mhd_response(const RequestContext& request); MHD_Response* create_mhd_response(const RequestContext& request);
@ -124,6 +129,84 @@ class ContentResponse : public Response {
std::string m_bookTitle; std::string m_bookTitle;
}; };
struct TaskbarInfo
{
const std::string bookName;
const zim::Archive* const archive;
TaskbarInfo(const std::string& bookName, const zim::Archive* a = nullptr)
: bookName(bookName)
, archive(a)
{}
};
std::unique_ptr<ContentResponse> withTaskbarInfo(const std::string& bookName,
const zim::Archive* archive,
std::unique_ptr<ContentResponse> r);
class ContentResponseBlueprint
{
public: // functions
ContentResponseBlueprint(const InternalServer* server,
const RequestContext* request,
int httpStatusCode,
const std::string& mimeType,
const std::string& templateStr)
: m_server(*server)
, m_request(*request)
, m_httpStatusCode(httpStatusCode)
, m_mimeType(mimeType)
, m_template(templateStr)
{}
virtual ~ContentResponseBlueprint() = default;
ContentResponseBlueprint& operator+(kainjow::mustache::data&& data)
{
this->m_data = std::move(data);
return *this;
}
operator std::unique_ptr<ContentResponse>() const
{
return generateResponseObject();
}
operator std::unique_ptr<Response>() const
{
return operator std::unique_ptr<ContentResponse>();
}
ContentResponseBlueprint& operator+(const TaskbarInfo& taskbarInfo);
protected: // functions
virtual std::unique_ptr<ContentResponse> generateResponseObject() const;
public: //data
const InternalServer& m_server;
const RequestContext& m_request;
const int m_httpStatusCode;
const std::string m_mimeType;
const std::string m_template;
kainjow::mustache::data m_data;
std::unique_ptr<TaskbarInfo> m_taskbarInfo;
};
class UrlNotFoundMsg {};
extern const UrlNotFoundMsg urlNotFoundMsg;
struct HTTP404HtmlResponse : ContentResponseBlueprint
{
HTTP404HtmlResponse(const InternalServer& server,
const RequestContext& request);
using ContentResponseBlueprint::operator+;
HTTP404HtmlResponse& operator+(UrlNotFoundMsg /*unused*/);
HTTP404HtmlResponse& operator+(const std::string& errorDetails);
};
class ItemResponse : public Response { class ItemResponse : public Response {
public: public:
ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange); ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);

View File

@ -370,6 +370,13 @@ std::string kiwix::gen_uuid(const std::string& s)
return kiwix::to_string(zim::Uuid::generate(s)); return kiwix::to_string(zim::Uuid::generate(s));
} }
kainjow::mustache::data kiwix::onlyAsNonEmptyMustacheValue(const std::string& s)
{
return s.empty()
? kainjow::mustache::data(false)
: kainjow::mustache::data(s);
}
std::string kiwix::render_template(const std::string& template_str, kainjow::mustache::data data) std::string kiwix::render_template(const std::string& template_str, kainjow::mustache::data data)
{ {
kainjow::mustache::mustache tmpl(template_str); kainjow::mustache::mustache tmpl(template_str);

View File

@ -48,6 +48,10 @@ namespace kiwix
std::string gen_date_str(); std::string gen_date_str();
std::string gen_uuid(const std::string& s); std::string gen_uuid(const std::string& s);
// if s is empty then returns kainjow::mustache::data(false)
// otherwise kainjow::mustache::data(value)
kainjow::mustache::data onlyAsNonEmptyMustacheValue(const std::string& s);
std::string render_template(const std::string& template_str, kainjow::mustache::data data); std::string render_template(const std::string& template_str, kainjow::mustache::data data);
} }

View File

@ -6,15 +6,10 @@
</head> </head>
<body> <body>
<h1>Not Found</h1> <h1>Not Found</h1>
{{#url}} {{#details}}
<p> <p>
The requested URL "{{url}}" was not found on this server. {{{p}}}
</p> </p>
{{/url}} {{/details}}
{{#details}}
<p>
{{{details}}}
</p>
{{/details}}
</body> </body>
</html> </html>

View File

@ -530,7 +530,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
{ /* url */ "/ROOT/random?content=non-existent-book", { /* url */ "/ROOT/random?content=non-existent-book",
expected_body==R"( expected_body==R"(
<h1>Not Found</h1> <h1>Not Found</h1>
//EOLWHITESPACEMARKER
<p> <p>
No such book: non-existent-book No such book: non-existent-book
</p> </p>
@ -539,7 +538,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
{ /* url */ "/ROOT/suggest?content=no-such-book&term=whatever", { /* url */ "/ROOT/suggest?content=no-such-book&term=whatever",
expected_body==R"( expected_body==R"(
<h1>Not Found</h1> <h1>Not Found</h1>
//EOLWHITESPACEMARKER
<p> <p>
No such book: no-such-book No such book: no-such-book
</p> </p>
@ -551,9 +549,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
<p> <p>
The requested URL "/ROOT/catalog/" was not found on this server. The requested URL "/ROOT/catalog/" was not found on this server.
</p> </p>
<p>
//EOLWHITESPACEMARKER
</p>
)" }, )" },
{ /* url */ "/ROOT/catalog/invalid_endpoint", { /* url */ "/ROOT/catalog/invalid_endpoint",
@ -562,9 +557,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
<p> <p>
The requested URL "/ROOT/catalog/invalid_endpoint" was not found on this server. The requested URL "/ROOT/catalog/invalid_endpoint" was not found on this server.
</p> </p>
<p>
//EOLWHITESPACEMARKER
</p>
)" }, )" },
{ /* url */ "/ROOT/invalid-book/whatever", { /* url */ "/ROOT/invalid-book/whatever",
@ -638,8 +630,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
)" }, )" },
{ /* url */ "/ROOT/raw/zimfile/meta/invalid-metadata", { /* url */ "/ROOT/raw/zimfile/meta/invalid-metadata",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_body==R"( expected_body==R"(
<h1>Not Found</h1> <h1>Not Found</h1>
<p> <p>
@ -651,8 +641,6 @@ TEST_F(ServerTest, 404WithBodyTesting)
)" }, )" },
{ /* url */ "/ROOT/raw/zimfile/content/invalid-article", { /* url */ "/ROOT/raw/zimfile/content/invalid-article",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_body==R"( expected_body==R"(
<h1>Not Found</h1> <h1>Not Found</h1>
<p> <p>