mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #732 from kiwix/HTTP404HtmlResponse
New way of building 404 error HTML responses
This commit is contained in:
commit
49f24d18df
|
@ -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)},
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue