mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #1032 from kiwix/error_response_i18n
Translation of error pages
This commit is contained in:
commit
d2f20dba66
|
@ -112,8 +112,12 @@ std::string expandParameterizedString(const std::string& lang,
|
||||||
const std::string& key,
|
const std::string& key,
|
||||||
const Parameters& params)
|
const Parameters& params)
|
||||||
{
|
{
|
||||||
|
kainjow::mustache::object mustacheParams;
|
||||||
|
for( const auto& kv : params ) {
|
||||||
|
mustacheParams[kv.first] = kv.second;
|
||||||
|
}
|
||||||
const std::string tmpl = getTranslatedString(lang, key);
|
const std::string tmpl = getTranslatedString(lang, key);
|
||||||
return render_template(tmpl, params);
|
return render_template(tmpl, mustacheParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace i18n
|
} // namespace i18n
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#ifndef KIWIX_SERVER_I18N
|
#ifndef KIWIX_SERVER_I18N
|
||||||
#define KIWIX_SERVER_I18N
|
#define KIWIX_SERVER_I18N
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mustache.hpp>
|
#include <mustache.hpp>
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ std::string getTranslatedString(const std::string& lang, const std::string& key)
|
||||||
namespace i18n
|
namespace i18n
|
||||||
{
|
{
|
||||||
|
|
||||||
typedef kainjow::mustache::object Parameters;
|
typedef std::map<std::string, std::string> Parameters;
|
||||||
|
|
||||||
std::string expandParameterizedString(const std::string& lang,
|
std::string expandParameterizedString(const std::string& lang,
|
||||||
const std::string& key,
|
const std::string& key,
|
||||||
|
@ -93,10 +94,10 @@ private:
|
||||||
|
|
||||||
} // namespace i18n
|
} // namespace i18n
|
||||||
|
|
||||||
struct ParameterizedMessage
|
class ParameterizedMessage
|
||||||
{
|
{
|
||||||
public: // types
|
public: // types
|
||||||
typedef kainjow::mustache::object Parameters;
|
typedef i18n::Parameters Parameters;
|
||||||
|
|
||||||
public: // functions
|
public: // functions
|
||||||
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
||||||
|
@ -106,6 +107,9 @@ public: // functions
|
||||||
|
|
||||||
std::string getText(const std::string& lang) const;
|
std::string getText(const std::string& lang) const;
|
||||||
|
|
||||||
|
const std::string& getMsgId() const { return msgId; }
|
||||||
|
const Parameters& getParams() const { return params; }
|
||||||
|
|
||||||
private: // data
|
private: // data
|
||||||
const std::string msgId;
|
const std::string msgId;
|
||||||
const Parameters params;
|
const Parameters params;
|
||||||
|
|
|
@ -513,6 +513,19 @@ static MHD_Result staticHandlerCallback(void* cls,
|
||||||
cont_cls);
|
cont_cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
MHD_Result add_name_value_pair(void *nvp, enum MHD_ValueKind kind,
|
||||||
|
const char *key, const char *value)
|
||||||
|
{
|
||||||
|
auto& nameValuePairs = *reinterpret_cast<RequestContext::NameValuePairs*>(nvp);
|
||||||
|
nameValuePairs.push_back({key, value});
|
||||||
|
return MHD_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||||
const char* fullUrl,
|
const char* fullUrl,
|
||||||
const char* method,
|
const char* method,
|
||||||
|
@ -529,7 +542,10 @@ MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto url = fullURL2LocalURL(fullUrl, m_rootPrefixOfDecodedURL);
|
const auto url = fullURL2LocalURL(fullUrl, m_rootPrefixOfDecodedURL);
|
||||||
RequestContext request(connection, m_root, url, method, version);
|
RequestContext::NameValuePairs headers, queryArgs;
|
||||||
|
MHD_get_connection_values(connection, MHD_HEADER_KIND, add_name_value_pair, &headers);
|
||||||
|
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, add_name_value_pair, &queryArgs);
|
||||||
|
RequestContext request(m_root, url, method, version, headers, queryArgs);
|
||||||
|
|
||||||
if (m_verbose.load() ) {
|
if (m_verbose.load() ) {
|
||||||
request.print_debug_info();
|
request.print_debug_info();
|
||||||
|
@ -926,7 +942,8 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
||||||
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
|
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
|
||||||
"fulltext-search-unavailable",
|
"fulltext-search-unavailable",
|
||||||
"404-page-heading",
|
"404-page-heading",
|
||||||
cssUrl);
|
cssUrl,
|
||||||
|
/*includeKiwixResponseData=*/true);
|
||||||
response += nonParameterizedMessage("no-search-results");
|
response += nonParameterizedMessage("no-search-results");
|
||||||
// XXX: Now this has to be handled by the iframe-based viewer which
|
// XXX: Now this has to be handled by the iframe-based viewer which
|
||||||
// XXX: has to resolve if the book selection resulted in a single book.
|
// XXX: has to resolve if the book selection resulted in a single book.
|
||||||
|
|
|
@ -51,11 +51,12 @@ RequestMethod str2RequestMethod(const std::string& method) {
|
||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
RequestContext::RequestContext(struct MHD_Connection* connection,
|
RequestContext::RequestContext(const std::string& _rootLocation, // URI-encoded
|
||||||
const std::string& _rootLocation, // URI-encoded
|
|
||||||
const std::string& unrootedUrl, // URI-decoded
|
const std::string& unrootedUrl, // URI-decoded
|
||||||
const std::string& _method,
|
const std::string& _method,
|
||||||
const std::string& version) :
|
const std::string& version,
|
||||||
|
const NameValuePairs& headers,
|
||||||
|
const NameValuePairs& queryArgs) :
|
||||||
rootLocation(_rootLocation),
|
rootLocation(_rootLocation),
|
||||||
url(unrootedUrl),
|
url(unrootedUrl),
|
||||||
method(str2RequestMethod(_method)),
|
method(str2RequestMethod(_method)),
|
||||||
|
@ -64,9 +65,13 @@ RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||||
acceptEncodingGzip(false),
|
acceptEncodingGzip(false),
|
||||||
byteRange_()
|
byteRange_()
|
||||||
{
|
{
|
||||||
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
|
for ( const auto& kv : headers ) {
|
||||||
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
|
add_header(kv.first, kv.second);
|
||||||
MHD_get_connection_values(connection, MHD_COOKIE_KIND, &RequestContext::fill_cookie, this);
|
}
|
||||||
|
|
||||||
|
for ( const auto& kv : queryArgs ) {
|
||||||
|
add_argument(kv.first, kv.second);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
acceptEncodingGzip =
|
acceptEncodingGzip =
|
||||||
|
@ -83,18 +88,14 @@ RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||||
RequestContext::~RequestContext()
|
RequestContext::~RequestContext()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
MHD_Result RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
|
void RequestContext::add_header(const char *key, const char *value)
|
||||||
const char *key, const char *value)
|
|
||||||
{
|
{
|
||||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
this->headers[lcAll(key)] = value;
|
||||||
_this->headers[lcAll(key)] = value;
|
|
||||||
return MHD_YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
void RequestContext::add_argument(const char *key, const char* value)
|
||||||
const char *key, const char* value)
|
|
||||||
{
|
{
|
||||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
RequestContext *_this = this;
|
||||||
_this->arguments[key].push_back(value == nullptr ? "" : value);
|
_this->arguments[key].push_back(value == nullptr ? "" : value);
|
||||||
if ( ! _this->queryString.empty() ) {
|
if ( ! _this->queryString.empty() ) {
|
||||||
_this->queryString += "&";
|
_this->queryString += "&";
|
||||||
|
@ -104,15 +105,6 @@ MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||||
_this->queryString += "=";
|
_this->queryString += "=";
|
||||||
_this->queryString += urlEncode(value);
|
_this->queryString += urlEncode(value);
|
||||||
}
|
}
|
||||||
return MHD_YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
MHD_Result RequestContext::fill_cookie(void *__this, enum MHD_ValueKind kind,
|
|
||||||
const char *key, const char* value)
|
|
||||||
{
|
|
||||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
|
||||||
_this->cookies[key] = value == nullptr ? "" : value;
|
|
||||||
return MHD_YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestContext::print_debug_info() const {
|
void RequestContext::print_debug_info() const {
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "byte_range.h"
|
#include "byte_range.h"
|
||||||
#include "tools/stringTools.h"
|
#include "../tools/stringTools.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "microhttpd_wrapper.h"
|
#include "microhttpd_wrapper.h"
|
||||||
|
@ -55,12 +55,17 @@ class IndexError: public std::runtime_error {};
|
||||||
|
|
||||||
|
|
||||||
class RequestContext {
|
class RequestContext {
|
||||||
|
public: // types
|
||||||
|
typedef std::vector<std::pair<const char*, const char*>> NameValuePairs;
|
||||||
|
|
||||||
public: // functions
|
public: // functions
|
||||||
RequestContext(struct MHD_Connection* connection,
|
RequestContext(const std::string& rootLocation, // URI-encoded
|
||||||
const std::string& rootLocation, // URI-encoded
|
|
||||||
const std::string& unrootedUrl, // URI-decoded
|
const std::string& unrootedUrl, // URI-decoded
|
||||||
const std::string& method,
|
const std::string& method,
|
||||||
const std::string& version);
|
const std::string& version,
|
||||||
|
const NameValuePairs& headers,
|
||||||
|
const NameValuePairs& queryArgs);
|
||||||
|
|
||||||
~RequestContext();
|
~RequestContext();
|
||||||
|
|
||||||
void print_debug_info() const;
|
void print_debug_info() const;
|
||||||
|
@ -145,16 +150,14 @@ class RequestContext {
|
||||||
ByteRange byteRange_;
|
ByteRange byteRange_;
|
||||||
std::map<std::string, std::string> headers;
|
std::map<std::string, std::string> headers;
|
||||||
std::map<std::string, std::vector<std::string>> arguments;
|
std::map<std::string, std::vector<std::string>> arguments;
|
||||||
std::map<std::string, std::string> cookies;
|
|
||||||
std::string queryString;
|
std::string queryString;
|
||||||
UserLanguage userlang;
|
UserLanguage userlang;
|
||||||
|
|
||||||
private: // functions
|
private: // functions
|
||||||
UserLanguage determine_user_language() const;
|
UserLanguage determine_user_language() const;
|
||||||
|
|
||||||
static MHD_Result fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
void add_header(const char* name, const char* value);
|
||||||
static MHD_Result fill_cookie(void *, enum MHD_ValueKind, const char*, const char*);
|
void add_argument(const char* name, const char* value);
|
||||||
static MHD_Result fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<> std::string RequestContext::get_argument(const std::string& name) const;
|
template<> std::string RequestContext::get_argument(const std::string& name) const;
|
||||||
|
|
|
@ -32,6 +32,9 @@
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
// This is somehow a magic value.
|
// This is somehow a magic value.
|
||||||
// If this value is too small, we will compress (and lost cpu time) too much
|
// If this value is too small, we will compress (and lost cpu time) too much
|
||||||
|
@ -47,6 +50,8 @@ namespace kiwix {
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
typedef kainjow::mustache::data MustacheData;
|
||||||
|
|
||||||
// some utilities
|
// some utilities
|
||||||
|
|
||||||
std::string get_mime_type(const zim::Item& item)
|
std::string get_mime_type(const zim::Item& item)
|
||||||
|
@ -151,14 +156,214 @@ std::unique_ptr<Response> Response::build_304(const ETag& etag)
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ContentResponseBlueprint::getMessage(const std::string& msgId) const
|
|
||||||
|
namespace
|
||||||
{
|
{
|
||||||
return getTranslatedString(m_request.get_user_language(), msgId);
|
|
||||||
|
// This class was introduced in order to work around the missing support
|
||||||
|
// for std::variant (and std::optional) under some of the current build
|
||||||
|
// platforms.
|
||||||
|
template<class T>
|
||||||
|
class Optional
|
||||||
|
{
|
||||||
|
public: // functions
|
||||||
|
Optional() {}
|
||||||
|
Optional(const T& t) : ptr(new T(t)) {}
|
||||||
|
Optional(const Optional& o) : ptr(o.has_value() ? new T(*o) : nullptr) {}
|
||||||
|
Optional(Optional&& o) : ptr(std::move(o.ptr)) {}
|
||||||
|
|
||||||
|
Optional& operator=(const Optional& o)
|
||||||
|
{
|
||||||
|
*this = Optional(o);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional& operator=(Optional&& o)
|
||||||
|
{
|
||||||
|
ptr = std::move(o.ptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_value() const { return ptr.get() != nullptr; }
|
||||||
|
const T& operator*() const { return *ptr; }
|
||||||
|
T& operator*() { return *ptr; }
|
||||||
|
|
||||||
|
private: // data
|
||||||
|
std::unique_ptr<T> ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
|
class ContentResponseBlueprint::Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::list<Data> List;
|
||||||
|
typedef std::map<std::string, Data> Object;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// std::variant<std::string, bool, List, Object> data;
|
||||||
|
// XXX: libkiwix is compiled on platforms where std::variant
|
||||||
|
// XXX: is not yet supported. Hence this hack. Only one
|
||||||
|
// XXX: of the below data members is expected to contain a value.
|
||||||
|
Optional<std::string> m_stringValue;
|
||||||
|
Optional<bool> m_boolValue;
|
||||||
|
Optional<List> m_listValue;
|
||||||
|
Optional<Object> m_objectValue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Data() {}
|
||||||
|
Data(const std::string& s) : m_stringValue(s) {}
|
||||||
|
Data(bool b) : m_boolValue(b) {}
|
||||||
|
Data(const List& l) : m_listValue(l) {}
|
||||||
|
Data(const Object& o) : m_objectValue(o) {}
|
||||||
|
|
||||||
|
MustacheData toMustache(const std::string& lang) const;
|
||||||
|
|
||||||
|
Data& operator[](const std::string& key)
|
||||||
|
{
|
||||||
|
return (*m_objectValue)[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const Data& d) { (*m_listValue).push_back(d); }
|
||||||
|
|
||||||
|
static Data onlyAsNonEmptyValue(const std::string& s)
|
||||||
|
{
|
||||||
|
return s.empty() ? Data(false) : Data(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Data from(const ParameterizedMessage& pmsg)
|
||||||
|
{
|
||||||
|
Object obj;
|
||||||
|
for(const auto& kv : pmsg.getParams()) {
|
||||||
|
obj[kv.first] = kv.second;
|
||||||
|
}
|
||||||
|
return Object{
|
||||||
|
{ "msgid", pmsg.getMsgId() },
|
||||||
|
{ "params", Data(obj) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string asJSON() const;
|
||||||
|
void dumpJSON(std::ostream& os) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isString() const { return m_stringValue.has_value(); }
|
||||||
|
bool isList() const { return m_listValue.has_value(); }
|
||||||
|
bool isObject() const { return m_objectValue.has_value(); }
|
||||||
|
|
||||||
|
const std::string& stringValue() const { return *m_stringValue; }
|
||||||
|
bool boolValue() const { return *m_boolValue; }
|
||||||
|
const List& listValue() const { return *m_listValue; }
|
||||||
|
const Object& objectValue() const { return *m_objectValue; }
|
||||||
|
|
||||||
|
const Data* get(const std::string& key) const
|
||||||
|
{
|
||||||
|
if ( !isObject() )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const auto& obj = objectValue();
|
||||||
|
const auto it = obj.find(key);
|
||||||
|
return it != obj.end() ? &it->second : nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MustacheData ContentResponseBlueprint::Data::toMustache(const std::string& lang) const
|
||||||
|
{
|
||||||
|
if ( this->isList() ) {
|
||||||
|
kainjow::mustache::list l;
|
||||||
|
for ( const auto& x : this->listValue() ) {
|
||||||
|
l.push_back(x.toMustache(lang));
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
} else if ( this->isObject() ) {
|
||||||
|
const Data* msgId = this->get("msgid");
|
||||||
|
const Data* msgParams = this->get("params");
|
||||||
|
if ( msgId && msgId->isString() && msgParams && msgParams->isObject() ) {
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
for(const auto& kv : msgParams->objectValue()) {
|
||||||
|
params[kv.first] = kv.second.stringValue();
|
||||||
|
}
|
||||||
|
const ParameterizedMessage msg(msgId->stringValue(), ParameterizedMessage::Parameters(params));
|
||||||
|
return msg.getText(lang);
|
||||||
|
} else {
|
||||||
|
kainjow::mustache::object o;
|
||||||
|
for ( const auto& kv : this->objectValue() ) {
|
||||||
|
o[kv.first] = kv.second.toMustache(lang);
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
} else if ( this->isString() ) {
|
||||||
|
return this->stringValue();
|
||||||
|
} else {
|
||||||
|
return this->boolValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentResponseBlueprint::Data::dumpJSON(std::ostream& os) const
|
||||||
|
{
|
||||||
|
if ( this->isString() ) {
|
||||||
|
os << '"' << escapeForJSON(this->stringValue()) << '"';
|
||||||
|
} else if ( this->isList() ) {
|
||||||
|
const char * sep = " ";
|
||||||
|
os << "[";
|
||||||
|
|
||||||
|
for ( const auto& x : this->listValue() ) {
|
||||||
|
os << sep;
|
||||||
|
x.dumpJSON(os);
|
||||||
|
sep = ", ";
|
||||||
|
}
|
||||||
|
os << " ]";
|
||||||
|
} else if ( this->isObject() ) {
|
||||||
|
const char * sep = " ";
|
||||||
|
os << "{";
|
||||||
|
for ( const auto& kv : this->objectValue() ) {
|
||||||
|
os << sep << '"' << kv.first << "\" : ";
|
||||||
|
kv.second.dumpJSON(os);
|
||||||
|
sep = ", ";
|
||||||
|
}
|
||||||
|
os << " }";
|
||||||
|
} else {
|
||||||
|
os << (this->boolValue() ? "true" : "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ContentResponseBlueprint::Data::asJSON() const
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
this->dumpJSON(oss);
|
||||||
|
|
||||||
|
// This JSON is going to be used in HTML inside a <script></script> tag.
|
||||||
|
// If it contains "</script>" (or "</script >") as a substring, then the HTML
|
||||||
|
// parser will be confused. Since for a valid JSON that may happen only inside
|
||||||
|
// a JSON string, we can safely take advantage of the answers to
|
||||||
|
// https://stackoverflow.com/questions/28259389/how-to-put-script-in-a-javascript-string
|
||||||
|
// and work around the issue by inserting an otherwise harmless backslash.
|
||||||
|
return std::regex_replace(oss.str(), std::regex("</script"), "</scr\\ipt");
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentResponseBlueprint::ContentResponseBlueprint(const RequestContext* request,
|
||||||
|
int httpStatusCode,
|
||||||
|
const std::string& mimeType,
|
||||||
|
const std::string& templateStr,
|
||||||
|
bool includeKiwixResponseData)
|
||||||
|
: m_request(*request)
|
||||||
|
, m_httpStatusCode(httpStatusCode)
|
||||||
|
, m_mimeType(mimeType)
|
||||||
|
, m_template(templateStr)
|
||||||
|
, m_includeKiwixResponseData(includeKiwixResponseData)
|
||||||
|
, m_data(new Data)
|
||||||
|
{}
|
||||||
|
|
||||||
|
ContentResponseBlueprint::~ContentResponseBlueprint() = default;
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
|
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
|
||||||
{
|
{
|
||||||
auto r = ContentResponse::build(m_template, m_data, m_mimeType);
|
kainjow::mustache::data d = m_data->toMustache(m_request.get_user_language());
|
||||||
|
if ( m_includeKiwixResponseData ) {
|
||||||
|
d.set("KIWIX_RESPONSE_TEMPLATE", escapeForJSON(m_template, false));
|
||||||
|
d.set("KIWIX_RESPONSE_DATA", m_data->asJSON());
|
||||||
|
}
|
||||||
|
auto r = ContentResponse::build(m_template, d, m_mimeType);
|
||||||
r->set_code(m_httpStatusCode);
|
r->set_code(m_httpStatusCode);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -167,26 +372,30 @@ HTTPErrorResponse::HTTPErrorResponse(const RequestContext& request,
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& pageTitleMsgId,
|
const std::string& pageTitleMsgId,
|
||||||
const std::string& headingMsgId,
|
const std::string& headingMsgId,
|
||||||
const std::string& cssUrl)
|
const std::string& cssUrl,
|
||||||
|
bool includeKiwixResponseData)
|
||||||
: ContentResponseBlueprint(&request,
|
: ContentResponseBlueprint(&request,
|
||||||
httpStatusCode,
|
httpStatusCode,
|
||||||
request.get_requested_format() == "html" ? "text/html; charset=utf-8" : "application/xml; charset=utf-8",
|
request.get_requested_format() == "html" ? "text/html; charset=utf-8" : "application/xml; charset=utf-8",
|
||||||
request.get_requested_format() == "html" ? RESOURCE::templates::error_html : RESOURCE::templates::error_xml)
|
request.get_requested_format() == "html" ? RESOURCE::templates::error_html : RESOURCE::templates::error_xml,
|
||||||
|
includeKiwixResponseData)
|
||||||
{
|
{
|
||||||
kainjow::mustache::list emptyList;
|
Data::List emptyList;
|
||||||
this->m_data = kainjow::mustache::object{
|
*this->m_data = Data(Data::Object{
|
||||||
{"CSS_URL", onlyAsNonEmptyMustacheValue(cssUrl) },
|
{"CSS_URL", Data::onlyAsNonEmptyValue(cssUrl) },
|
||||||
{"PAGE_TITLE", getMessage(pageTitleMsgId)},
|
{"PAGE_TITLE", Data::from(nonParameterizedMessage(pageTitleMsgId))},
|
||||||
{"PAGE_HEADING", getMessage(headingMsgId)},
|
{"PAGE_HEADING", Data::from(nonParameterizedMessage(headingMsgId))},
|
||||||
{"details", emptyList}
|
{"details", emptyList}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTP404Response::HTTP404Response(const RequestContext& request)
|
HTTP404Response::HTTP404Response(const RequestContext& request)
|
||||||
: HTTPErrorResponse(request,
|
: HTTPErrorResponse(request,
|
||||||
MHD_HTTP_NOT_FOUND,
|
MHD_HTTP_NOT_FOUND,
|
||||||
"404-page-title",
|
"404-page-title",
|
||||||
"404-page-heading")
|
"404-page-heading",
|
||||||
|
std::string(),
|
||||||
|
/*includeKiwixResponseData=*/true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,8 +408,7 @@ UrlNotFoundResponse::UrlNotFoundResponse(const RequestContext& request)
|
||||||
|
|
||||||
HTTPErrorResponse& HTTPErrorResponse::operator+(const ParameterizedMessage& details)
|
HTTPErrorResponse& HTTPErrorResponse::operator+(const ParameterizedMessage& details)
|
||||||
{
|
{
|
||||||
const std::string msg = details.getText(m_request.get_user_language());
|
(*m_data)["details"].push_back(Data::Object{{"p", Data::from(details)}});
|
||||||
m_data["details"].push_back({"p", msg});
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +423,9 @@ HTTP400Response::HTTP400Response(const RequestContext& request)
|
||||||
: HTTPErrorResponse(request,
|
: HTTPErrorResponse(request,
|
||||||
MHD_HTTP_BAD_REQUEST,
|
MHD_HTTP_BAD_REQUEST,
|
||||||
"400-page-title",
|
"400-page-title",
|
||||||
"400-page-heading")
|
"400-page-heading",
|
||||||
|
std::string(),
|
||||||
|
/*includeKiwixResponseData=*/true)
|
||||||
{
|
{
|
||||||
std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
||||||
const auto query = m_request.get_query();
|
const auto query = m_request.get_query();
|
||||||
|
@ -229,19 +439,13 @@ HTTP500Response::HTTP500Response(const RequestContext& request)
|
||||||
: HTTPErrorResponse(request,
|
: HTTPErrorResponse(request,
|
||||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||||
"500-page-title",
|
"500-page-title",
|
||||||
"500-page-heading")
|
"500-page-heading",
|
||||||
|
std::string(),
|
||||||
|
/*includeKiwixResponseData=*/true)
|
||||||
{
|
{
|
||||||
*this += nonParameterizedMessage("500-page-text");
|
*this += nonParameterizedMessage("500-page-text");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> HTTP500Response::generateResponseObject() const
|
|
||||||
{
|
|
||||||
const std::string mimeType = "text/html;charset=utf-8";
|
|
||||||
auto r = ContentResponse::build(m_template, m_data, mimeType);
|
|
||||||
r->set_code(m_httpStatusCode);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Response> Response::build_416(size_t resourceLength)
|
std::unique_ptr<Response> Response::build_416(size_t resourceLength)
|
||||||
{
|
{
|
||||||
auto response = Response::build();
|
auto response = Response::build();
|
||||||
|
|
|
@ -101,6 +101,9 @@ class ContentResponse : public Response {
|
||||||
kainjow::mustache::data data,
|
kainjow::mustache::data data,
|
||||||
const std::string& mimetype);
|
const std::string& mimetype);
|
||||||
|
|
||||||
|
const std::string& getContent() const { return m_content; }
|
||||||
|
const std::string& getMimeType() const { return m_mimeType; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||||
|
|
||||||
|
@ -118,31 +121,28 @@ public: // functions
|
||||||
ContentResponseBlueprint(const RequestContext* request,
|
ContentResponseBlueprint(const RequestContext* request,
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& mimeType,
|
const std::string& mimeType,
|
||||||
const std::string& templateStr)
|
const std::string& templateStr,
|
||||||
: m_request(*request)
|
bool includeKiwixResponseData = false);
|
||||||
, m_httpStatusCode(httpStatusCode)
|
|
||||||
, m_mimeType(mimeType)
|
|
||||||
, m_template(templateStr)
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual ~ContentResponseBlueprint() = default;
|
~ContentResponseBlueprint();
|
||||||
|
|
||||||
operator std::unique_ptr<Response>() const
|
operator std::unique_ptr<Response>() const
|
||||||
{
|
{
|
||||||
return generateResponseObject();
|
return generateResponseObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ContentResponse> generateResponseObject() const;
|
||||||
|
|
||||||
protected: // functions
|
protected: // types
|
||||||
std::string getMessage(const std::string& msgId) const;
|
class Data;
|
||||||
virtual std::unique_ptr<ContentResponse> generateResponseObject() const;
|
|
||||||
|
|
||||||
public: //data
|
protected: //data
|
||||||
const RequestContext& m_request;
|
const RequestContext& m_request;
|
||||||
const int m_httpStatusCode;
|
const int m_httpStatusCode;
|
||||||
const std::string m_mimeType;
|
const std::string m_mimeType;
|
||||||
const std::string m_template;
|
const std::string m_template;
|
||||||
kainjow::mustache::data m_data;
|
const bool m_includeKiwixResponseData;
|
||||||
|
std::unique_ptr<Data> m_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HTTPErrorResponse : ContentResponseBlueprint
|
struct HTTPErrorResponse : ContentResponseBlueprint
|
||||||
|
@ -151,7 +151,8 @@ struct HTTPErrorResponse : ContentResponseBlueprint
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& pageTitleMsgId,
|
const std::string& pageTitleMsgId,
|
||||||
const std::string& headingMsgId,
|
const std::string& headingMsgId,
|
||||||
const std::string& cssUrl = "");
|
const std::string& cssUrl = "",
|
||||||
|
bool includeKiwixResponseData = false);
|
||||||
|
|
||||||
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
||||||
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
||||||
|
@ -175,11 +176,6 @@ struct HTTP400Response : HTTPErrorResponse
|
||||||
struct HTTP500Response : HTTPErrorResponse
|
struct HTTP500Response : HTTPErrorResponse
|
||||||
{
|
{
|
||||||
explicit HTTP500Response(const RequestContext& request);
|
explicit HTTP500Response(const RequestContext& request);
|
||||||
|
|
||||||
private: // overrides
|
|
||||||
// generateResponseObject() is overriden in order to produce a minimal
|
|
||||||
// response without any need for additional resources from the server
|
|
||||||
std::unique_ptr<ContentResponse> generateResponseObject() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ItemResponse : public Response {
|
class ItemResponse : public Response {
|
||||||
|
|
|
@ -327,17 +327,27 @@ std::string kiwix::render_template(const std::string& template_str, kainjow::mus
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
// The escapeQuote parameter of escapeForJSON() defaults to true.
|
||||||
{
|
// This constant makes the calls to escapeForJSON() where the quote symbol
|
||||||
|
// should not be escaped (as it is later replaced with the HTML character entity
|
||||||
|
// ") more readable.
|
||||||
|
static const bool DONT_ESCAPE_QUOTE = false;
|
||||||
|
|
||||||
std::string escapeForJSON(const std::string& s)
|
std::string kiwix::escapeForJSON(const std::string& s, bool escapeQuote)
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
for (char c : s) {
|
for (char c : s) {
|
||||||
if ( c == '\\' ) {
|
if ( c == '\\' ) {
|
||||||
oss << "\\\\";
|
oss << "\\\\";
|
||||||
} else if ( unsigned(c) < 0x20U ) {
|
} else if ( unsigned(c) < 0x20U ) {
|
||||||
oss << "\\u" << std::setw(4) << std::setfill('0') << unsigned(c);
|
switch ( c ) {
|
||||||
|
case '\n': oss << "\\n"; break;
|
||||||
|
case '\r': oss << "\\r"; break;
|
||||||
|
case '\t': oss << "\\t"; break;
|
||||||
|
default: oss << "\\u" << std::setw(4) << std::setfill('0') << unsigned(c);
|
||||||
|
}
|
||||||
|
} else if ( c == '"' && escapeQuote ) {
|
||||||
|
oss << "\\\"";
|
||||||
} else {
|
} else {
|
||||||
oss << c;
|
oss << c;
|
||||||
}
|
}
|
||||||
|
@ -345,6 +355,9 @@ std::string escapeForJSON(const std::string& s)
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
std::string makeFulltextSearchSuggestion(const std::string& lang,
|
std::string makeFulltextSearchSuggestion(const std::string& lang,
|
||||||
const std::string& queryString)
|
const std::string& queryString)
|
||||||
{
|
{
|
||||||
|
@ -370,10 +383,10 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
|
||||||
? suggestion.getSnippet()
|
? suggestion.getSnippet()
|
||||||
: suggestion.getTitle();
|
: suggestion.getTitle();
|
||||||
|
|
||||||
result.set("label", escapeForJSON(label));
|
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
|
||||||
result.set("value", escapeForJSON(suggestion.getTitle()));
|
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
|
||||||
result.set("kind", "path");
|
result.set("kind", "path");
|
||||||
result.set("path", escapeForJSON(suggestion.getPath()));
|
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
|
||||||
result.set("first", m_data.is_empty_list());
|
result.set("first", m_data.is_empty_list());
|
||||||
m_data.push_back(result);
|
m_data.push_back(result);
|
||||||
}
|
}
|
||||||
|
@ -383,8 +396,8 @@ void kiwix::Suggestions::addFTSearchSuggestion(const std::string& uiLang,
|
||||||
{
|
{
|
||||||
kainjow::mustache::data result;
|
kainjow::mustache::data result;
|
||||||
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
|
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
|
||||||
result.set("label", escapeForJSON(label));
|
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
|
||||||
result.set("value", escapeForJSON(queryString + " "));
|
result.set("value", escapeForJSON(queryString + " ", DONT_ESCAPE_QUOTE));
|
||||||
result.set("kind", "pattern");
|
result.set("kind", "pattern");
|
||||||
result.set("first", m_data.is_empty_list());
|
result.set("first", m_data.is_empty_list());
|
||||||
m_data.push_back(result);
|
m_data.push_back(result);
|
||||||
|
|
|
@ -53,6 +53,7 @@ private:
|
||||||
const icu::Locale locale;
|
const icu::Locale locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string escapeForJSON(const std::string& s, bool escapeQuote = true);
|
||||||
|
|
||||||
/* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
|
/* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
|
||||||
* difference that the slash (/) symbol is NOT encoded. */
|
* difference that the slash (/) symbol is NOT encoded. */
|
||||||
|
|
|
@ -69,6 +69,37 @@ function $t(msgId, params={}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const I18n = {
|
||||||
|
instantiateParameterizedMessages: function(data) {
|
||||||
|
if ( data.__proto__ == Array.prototype ) {
|
||||||
|
const result = [];
|
||||||
|
for ( const x of data ) {
|
||||||
|
result.push(this.instantiateParameterizedMessages(x));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else if ( data.__proto__ == Object.prototype ) {
|
||||||
|
const msgId = data.msgid;
|
||||||
|
const msgParams = data.params;
|
||||||
|
if ( msgId && msgId.__proto__ == String.prototype && msgParams && msgParams.__proto__ == Object.prototype ) {
|
||||||
|
return $t(msgId, msgParams);
|
||||||
|
} else {
|
||||||
|
const result = {};
|
||||||
|
for ( const p in data ) {
|
||||||
|
result[p] = this.instantiateParameterizedMessages(data[p]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function (template, params) {
|
||||||
|
params = this.instantiateParameterizedMessages(params);
|
||||||
|
return mustache.render(template, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_UI_LANGUAGE = 'en';
|
const DEFAULT_UI_LANGUAGE = 'en';
|
||||||
|
|
||||||
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
|
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
|
||||||
|
@ -145,3 +176,4 @@ window.$t = $t;
|
||||||
window.getUserLanguage = getUserLanguage;
|
window.getUserLanguage = getUserLanguage;
|
||||||
window.setUserLanguage = setUserLanguage;
|
window.setUserLanguage = setUserLanguage;
|
||||||
window.initUILanguageSelector = initUILanguageSelector;
|
window.initUILanguageSelector = initUILanguageSelector;
|
||||||
|
window.I18n = I18n;
|
||||||
|
|
|
@ -249,6 +249,25 @@ function handle_location_hash_change() {
|
||||||
history.replaceState(viewerState, null);
|
history.replaceState(viewerState, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateErrorPageIfNeeded() {
|
||||||
|
const cw = contentIframe.contentWindow;
|
||||||
|
if ( cw.KIWIX_RESPONSE_TEMPLATE && cw.KIWIX_RESPONSE_DATA ) {
|
||||||
|
const template = htmlDecode(cw.KIWIX_RESPONSE_TEMPLATE);
|
||||||
|
|
||||||
|
// cw.KIWIX_RESPONSE_DATA belongs to the iframe context and running
|
||||||
|
// I18n.render() on it directly in the top context doesn't work correctly
|
||||||
|
// because the type checks (obj.__proto__ == ???.prototype) in
|
||||||
|
// I18n.instantiateParameterizedMessages() always fail (String.prototype
|
||||||
|
// refers to different objects in different contexts).
|
||||||
|
// Work arround that issue by copying the object into our context.
|
||||||
|
const params = JSON.parse(JSON.stringify(cw.KIWIX_RESPONSE_DATA));
|
||||||
|
|
||||||
|
const html = I18n.render(template, params);
|
||||||
|
const htmlDoc = new DOMParser().parseFromString(html, "text/html");
|
||||||
|
cw.document.documentElement.innerHTML = htmlDoc.documentElement.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handle_content_url_change() {
|
function handle_content_url_change() {
|
||||||
const iframeLocation = contentIframe.contentWindow.location;
|
const iframeLocation = contentIframe.contentWindow.location;
|
||||||
console.log('handle_content_url_change: ' + iframeLocation.href);
|
console.log('handle_content_url_change: ' + iframeLocation.href);
|
||||||
|
@ -258,6 +277,7 @@ function handle_content_url_change() {
|
||||||
const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery);
|
const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery);
|
||||||
history.replaceState(viewerState, null, makeURL(location.search, newHash));
|
history.replaceState(viewerState, null, makeURL(location.search, newHash));
|
||||||
updateCurrentBookIfNeeded(newHash);
|
updateCurrentBookIfNeeded(newHash);
|
||||||
|
translateErrorPageIfNeeded();
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -496,6 +516,7 @@ function changeUILanguage() {
|
||||||
viewerState.uiLanguage = lang;
|
viewerState.uiLanguage = lang;
|
||||||
setUserLanguage(lang, () => {
|
setUserLanguage(lang, () => {
|
||||||
updateUIText();
|
updateUIText();
|
||||||
|
translateErrorPageIfNeeded();
|
||||||
history.pushState(viewerState, null);
|
history.pushState(viewerState, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
<title>{{PAGE_TITLE}}</title>
|
<title>{{PAGE_TITLE}}</title>
|
||||||
{{#CSS_URL}}
|
{{#CSS_URL}}
|
||||||
<link type="text/css" href="{{{CSS_URL}}}" rel="Stylesheet" />
|
<link type="text/css" href="{{{CSS_URL}}}" rel="Stylesheet" />
|
||||||
{{/CSS_URL}}
|
{{/CSS_URL}}{{#KIWIX_RESPONSE_DATA}} <script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
|
||||||
|
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
|
||||||
|
</script>{{/KIWIX_RESPONSE_DATA}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{{PAGE_HEADING}}</h1>
|
<h1>{{PAGE_HEADING}}</h1>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
#include "../src/server/i18n.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
using namespace kiwix;
|
||||||
|
|
||||||
|
TEST(ParameterizedMessage, parameterlessMessages)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const ParameterizedMessage msg("404-page-title", {});
|
||||||
|
|
||||||
|
EXPECT_EQ(msg.getText("en"), "Content not found");
|
||||||
|
EXPECT_EQ(msg.getText("test"), "[I18N TESTING] Not Found - Try Again");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Make sure that msgId influences the result of getText()
|
||||||
|
const ParameterizedMessage msg("random-page-button-text", {});
|
||||||
|
|
||||||
|
EXPECT_EQ(msg.getText("en"), "Go to a randomly selected page");
|
||||||
|
EXPECT_EQ(msg.getText("test"), "[I18N TESTING] I am tired of determinism");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Demonstrate that unwanted parameters are silently ignored
|
||||||
|
const ParameterizedMessage msg("404-page-title", {{"abc", "xyz"}});
|
||||||
|
|
||||||
|
EXPECT_EQ(msg.getText("en"), "Content not found");
|
||||||
|
EXPECT_EQ(msg.getText("test"), "[I18N TESTING] Not Found - Try Again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ParameterizedMessage, messagesWithParameters)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const ParameterizedMessage msg("filter-by-tag",
|
||||||
|
{{"TAG", "scifi"}}
|
||||||
|
);
|
||||||
|
|
||||||
|
EXPECT_EQ(msg.getText("en"), "Filter by tag \"scifi\"");
|
||||||
|
EXPECT_EQ(msg.getText("test"), "Filter [I18N] by [TESTING] tag \"scifi\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Omitting expected parameters amounts to using empty values for them
|
||||||
|
const ParameterizedMessage msg("filter-by-tag", {});
|
||||||
|
|
||||||
|
EXPECT_EQ(msg.getText("en"), "Filter by tag \"\"");
|
||||||
|
EXPECT_EQ(msg.getText("test"), "Filter [I18N] by [TESTING] tag \"\"");
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,9 @@ tests = [
|
||||||
'name_mapper',
|
'name_mapper',
|
||||||
'opds_catalog',
|
'opds_catalog',
|
||||||
'server_helper',
|
'server_helper',
|
||||||
'lrucache'
|
'lrucache',
|
||||||
|
'i18n',
|
||||||
|
'response'
|
||||||
]
|
]
|
||||||
|
|
||||||
if build_machine.system() != 'windows'
|
if build_machine.system() != 'windows'
|
||||||
|
|
|
@ -110,10 +110,10 @@ TEST(Suggestions, specialCharHandling)
|
||||||
CHECK_SUGGESTIONS(s.getJSON(),
|
CHECK_SUGGESTIONS(s.getJSON(),
|
||||||
R"EXPECTEDJSON([
|
R"EXPECTEDJSON([
|
||||||
{
|
{
|
||||||
"value" : "Title with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
"value" : "Title with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
||||||
"label" : "Snippet with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
"label" : "Snippet with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
||||||
"kind" : "path"
|
"kind" : "path"
|
||||||
, "path" : "Path with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?"
|
, "path" : "Path with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)EXPECTEDJSON"
|
)EXPECTEDJSON"
|
||||||
|
@ -128,10 +128,10 @@ R"EXPECTEDJSON([
|
||||||
CHECK_SUGGESTIONS(s.getJSON(),
|
CHECK_SUGGESTIONS(s.getJSON(),
|
||||||
R"EXPECTEDJSON([
|
R"EXPECTEDJSON([
|
||||||
{
|
{
|
||||||
"value" : "Snippetless title with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
"value" : "Snippetless title with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
||||||
"label" : "Snippetless title with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
"label" : "Snippetless title with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?",
|
||||||
"kind" : "path"
|
"kind" : "path"
|
||||||
, "path" : "Path with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?"
|
, "path" : "Path with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)EXPECTEDJSON"
|
)EXPECTEDJSON"
|
||||||
|
@ -145,8 +145,8 @@ R"EXPECTEDJSON([
|
||||||
CHECK_SUGGESTIONS(s.getJSON(),
|
CHECK_SUGGESTIONS(s.getJSON(),
|
||||||
R"EXPECTEDJSON([
|
R"EXPECTEDJSON([
|
||||||
{
|
{
|
||||||
"value" : "text with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.? ",
|
"value" : "text with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.? ",
|
||||||
"label" : "containing 'text with \u0009\u0010\u0013\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?'...",
|
"label" : "containing 'text with \t\n\r\\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?'...",
|
||||||
"kind" : "pattern"
|
"kind" : "pattern"
|
||||||
//EOLWHITESPACEMARKER
|
//EOLWHITESPACEMARKER
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
#include "../src/server/response.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "../src/server/request_context.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace kiwix;
|
||||||
|
|
||||||
|
RequestContext makeHttpGetRequest(const std::string& url,
|
||||||
|
const RequestContext::NameValuePairs& headers,
|
||||||
|
const RequestContext::NameValuePairs& queryArgs)
|
||||||
|
{
|
||||||
|
return RequestContext("", url, "GET", "1.1", headers, queryArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getResponseContent(const ContentResponseBlueprint& crb)
|
||||||
|
{
|
||||||
|
return crb.generateResponseObject()->getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TEST(HTTPErrorResponse, shouldBeInEnglishByDefault) {
|
||||||
|
const RequestContext req = makeHttpGetRequest("/asdf", {}, {});
|
||||||
|
HTTPErrorResponse errResp(req, MHD_HTTP_NOT_FOUND,
|
||||||
|
"404-page-title",
|
||||||
|
"404-page-heading",
|
||||||
|
"/css/error.css",
|
||||||
|
/*includeKiwixResponseData=*/true);
|
||||||
|
|
||||||
|
errResp += ParameterizedMessage("suggest-search",
|
||||||
|
{
|
||||||
|
{ "PATTERN", "asdf" },
|
||||||
|
{ "SEARCH_URL", "/search?q=asdf" }
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_EQ(getResponseContent(errResp),
|
||||||
|
R"(<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||||
|
<title>Content not found</title>
|
||||||
|
<link type="text/css" href="/css/error.css" rel="Stylesheet" />
|
||||||
|
<script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = "<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml">\n <head>\n <meta content="text/html;charset=UTF-8" http-equiv="content-type" />\n <title>{{PAGE_TITLE}}</title>\n{{#CSS_URL}}\n <link type="text/css" href="{{{CSS_URL}}}" rel="Stylesheet" />\n{{/CSS_URL}}{{#KIWIX_RESPONSE_DATA}} <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>{{/KIWIX_RESPONSE_DATA}}\n </head>\n <body>\n <h1>{{PAGE_HEADING}}</h1>\n{{#details}}\n <p>\n {{{p}}}\n </p>\n{{/details}}\n </body>\n</html>\n";
|
||||||
|
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : "/css/error.css", "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "asdf", "SEARCH_URL" : "/search?q=asdf" } } } ] };
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Not Found</h1>
|
||||||
|
<p>
|
||||||
|
Make a full text search for <a href="/search?q=asdf">asdf</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HTTPErrorResponse, shouldBeTranslatable) {
|
||||||
|
const RequestContext req = makeHttpGetRequest("/asdf",
|
||||||
|
/* headers */ {},
|
||||||
|
/* query args */ {{"userlang", "test"}}
|
||||||
|
);
|
||||||
|
|
||||||
|
HTTPErrorResponse errResp(req, MHD_HTTP_NOT_FOUND,
|
||||||
|
"404-page-title",
|
||||||
|
"404-page-heading",
|
||||||
|
"/css/error.css",
|
||||||
|
/*includeKiwixResponseData=*/true);
|
||||||
|
|
||||||
|
errResp += ParameterizedMessage("suggest-search",
|
||||||
|
{
|
||||||
|
{ "PATTERN", "asdf" },
|
||||||
|
{ "SEARCH_URL", "/search?q=asdf" }
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_EQ(getResponseContent(errResp),
|
||||||
|
R"(<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||||
|
<title>[I18N TESTING] Not Found - Try Again</title>
|
||||||
|
<link type="text/css" href="/css/error.css" rel="Stylesheet" />
|
||||||
|
<script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = "<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml">\n <head>\n <meta content="text/html;charset=UTF-8" http-equiv="content-type" />\n <title>{{PAGE_TITLE}}</title>\n{{#CSS_URL}}\n <link type="text/css" href="{{{CSS_URL}}}" rel="Stylesheet" />\n{{/CSS_URL}}{{#KIWIX_RESPONSE_DATA}} <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>{{/KIWIX_RESPONSE_DATA}}\n </head>\n <body>\n <h1>{{PAGE_HEADING}}</h1>\n{{#details}}\n <p>\n {{{p}}}\n </p>\n{{/details}}\n </body>\n</html>\n";
|
||||||
|
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : "/css/error.css", "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "asdf", "SEARCH_URL" : "/search?q=asdf" } } } ] };
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
||||||
|
<p>
|
||||||
|
[I18N TESTING] Make a full text search for <a href="/search?q=asdf">asdf</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)");
|
||||||
|
}
|
|
@ -59,7 +59,7 @@ const ResourceCollection resources200Compressible{
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=4ab55b42" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
||||||
|
@ -75,7 +75,7 @@ const ResourceCollection resources200Compressible{
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=e014a885" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=e014a885" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=948df083" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=e9c025f2" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
|
||||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
|
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
|
||||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
|
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
|
||||||
|
@ -285,7 +285,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||||
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
|
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
|
||||||
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
|
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
|
||||||
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
|
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
|
||||||
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" defer></script>
|
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=4ab55b42" defer></script>
|
||||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" defer></script>
|
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" defer></script>
|
||||||
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||||
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||||
|
@ -318,9 +318,9 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download
|
||||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
|
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
|
||||||
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
|
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
|
||||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
|
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
|
||||||
<script type="module" src="./skin/i18n.js?cacheid=6a8c6fb2" defer></script>
|
<script type="module" src="./skin/i18n.js?cacheid=4ab55b42" defer></script>
|
||||||
<script type="text/javascript" src="./skin/languages.js?cacheid=96f2cf73" defer></script>
|
<script type="text/javascript" src="./skin/languages.js?cacheid=96f2cf73" defer></script>
|
||||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=948df083" defer></script>
|
<script type="text/javascript" src="./skin/viewer.js?cacheid=e9c025f2" defer></script>
|
||||||
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||||
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
||||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||||
|
@ -337,6 +337,7 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fa
|
||||||
// a page rendered from static/templates/no_search_result_html
|
// a page rendered from static/templates/no_search_result_html
|
||||||
/* url */ "/ROOT%23%3F/search?content=poor&pattern=whatever",
|
/* url */ "/ROOT%23%3F/search?content=poor&pattern=whatever",
|
||||||
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" rel="Stylesheet" />
|
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" rel="Stylesheet" />
|
||||||
|
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : "/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84", "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "fulltext-search-unavailable", "params" : { } }, "details" : [ { "p" : { "msgid" : "no-search-results", "params" : { } } } ] };
|
||||||
)EXPECTEDRESULT"
|
)EXPECTEDRESULT"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -535,6 +536,7 @@ struct ExpectedResponseData
|
||||||
{
|
{
|
||||||
const std::string expectedPageTitle;
|
const std::string expectedPageTitle;
|
||||||
const std::string expectedCssUrl;
|
const std::string expectedCssUrl;
|
||||||
|
const std::string expectedKiwixResponseData;
|
||||||
const std::string bookName;
|
const std::string bookName;
|
||||||
const std::string bookTitle;
|
const std::string bookTitle;
|
||||||
const std::string expectedBody;
|
const std::string expectedBody;
|
||||||
|
@ -544,6 +546,7 @@ enum ExpectedResponseDataType
|
||||||
{
|
{
|
||||||
expected_page_title,
|
expected_page_title,
|
||||||
expected_css_url,
|
expected_css_url,
|
||||||
|
expected_kiwix_response_data,
|
||||||
book_name,
|
book_name,
|
||||||
book_title,
|
book_title,
|
||||||
expected_body
|
expected_body
|
||||||
|
@ -556,11 +559,13 @@ ExpectedResponseData operator==(ExpectedResponseDataType t, std::string s)
|
||||||
{
|
{
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case expected_page_title: return ExpectedResponseData{s, "", "", "", ""};
|
case expected_page_title: return ExpectedResponseData{s, "", "", "", "", ""};
|
||||||
case expected_css_url: return ExpectedResponseData{"", s, "", "", ""};
|
case expected_css_url: return ExpectedResponseData{"", s, "", "", "", ""};
|
||||||
case book_name: return ExpectedResponseData{"", "", s, "", ""};
|
case expected_kiwix_response_data:
|
||||||
case book_title: return ExpectedResponseData{"", "", "", s, ""};
|
return ExpectedResponseData{"", "", s, "", "", ""};
|
||||||
case expected_body: return ExpectedResponseData{"", "", "", "", s};
|
case book_name: return ExpectedResponseData{"", "", "", s, "", ""};
|
||||||
|
case book_title: return ExpectedResponseData{"", "", "", "", s, ""};
|
||||||
|
case expected_body: return ExpectedResponseData{"", "", "", "", "", s};
|
||||||
default: assert(false); return ExpectedResponseData{};
|
default: assert(false); return ExpectedResponseData{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,6 +584,7 @@ ExpectedResponseData operator&&(const ExpectedResponseData& a,
|
||||||
return ExpectedResponseData{
|
return ExpectedResponseData{
|
||||||
selectNonEmpty(a.expectedPageTitle, b.expectedPageTitle),
|
selectNonEmpty(a.expectedPageTitle, b.expectedPageTitle),
|
||||||
selectNonEmpty(a.expectedCssUrl, b.expectedCssUrl),
|
selectNonEmpty(a.expectedCssUrl, b.expectedCssUrl),
|
||||||
|
selectNonEmpty(a.expectedKiwixResponseData, b.expectedKiwixResponseData),
|
||||||
selectNonEmpty(a.bookName, b.bookName),
|
selectNonEmpty(a.bookName, b.bookName),
|
||||||
selectNonEmpty(a.bookTitle, b.bookTitle),
|
selectNonEmpty(a.bookTitle, b.bookTitle),
|
||||||
selectNonEmpty(a.expectedBody, b.expectedBody)
|
selectNonEmpty(a.expectedBody, b.expectedBody)
|
||||||
|
@ -607,19 +613,29 @@ private:
|
||||||
std::string TestContentIn404HtmlResponse::expectedResponse() const
|
std::string TestContentIn404HtmlResponse::expectedResponse() const
|
||||||
{
|
{
|
||||||
const std::string frag[] = {
|
const std::string frag[] = {
|
||||||
|
// frag[0]
|
||||||
R"FRAG(<!DOCTYPE html>
|
R"FRAG(<!DOCTYPE html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||||
<title>)FRAG",
|
<title>)FRAG",
|
||||||
|
|
||||||
|
// frag[1]
|
||||||
R"FRAG(</title>
|
R"FRAG(</title>
|
||||||
)FRAG",
|
)FRAG",
|
||||||
|
|
||||||
R"FRAG(
|
// frag[2]
|
||||||
|
R"( <script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = )" + ERROR_HTML_TEMPLATE_JS_STRING + R"(;
|
||||||
|
window.KIWIX_RESPONSE_DATA = )",
|
||||||
|
|
||||||
|
// frag[3]
|
||||||
|
R"FRAG(;
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>)FRAG",
|
<body>)FRAG",
|
||||||
|
|
||||||
|
// frag[4]
|
||||||
R"FRAG( </body>
|
R"FRAG( </body>
|
||||||
</html>
|
</html>
|
||||||
)FRAG"
|
)FRAG"
|
||||||
|
@ -630,8 +646,10 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const
|
||||||
+ frag[1]
|
+ frag[1]
|
||||||
+ pageCssLink()
|
+ pageCssLink()
|
||||||
+ frag[2]
|
+ frag[2]
|
||||||
|
+ expectedKiwixResponseData
|
||||||
|
+ frag[3]
|
||||||
+ expectedBody
|
+ expectedBody
|
||||||
+ frag[3];
|
+ frag[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TestContentIn404HtmlResponse::pageTitle() const
|
std::string TestContentIn404HtmlResponse::pageTitle() const
|
||||||
|
@ -648,7 +666,8 @@ std::string TestContentIn404HtmlResponse::pageCssLink() const
|
||||||
|
|
||||||
return R"( <link type="text/css" href=")"
|
return R"( <link type="text/css" href=")"
|
||||||
+ expectedCssUrl
|
+ expectedCssUrl
|
||||||
+ R"(" rel="Stylesheet" />)";
|
+ R"(" rel="Stylesheet" />)"
|
||||||
|
+ "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestContentIn400HtmlResponse : public TestContentIn404HtmlResponse
|
class TestContentIn400HtmlResponse : public TestContentIn404HtmlResponse
|
||||||
|
@ -676,6 +695,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
using namespace TestingOfHtmlResponses;
|
using namespace TestingOfHtmlResponses;
|
||||||
const std::vector<TestContentIn404HtmlResponse> testData{
|
const std::vector<TestContentIn404HtmlResponse> testData{
|
||||||
{ /* url */ "/ROOT%23%3F/random?content=non-existent-book",
|
{ /* url */ "/ROOT%23%3F/random?content=non-existent-book",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "non-existent-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -685,6 +705,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/random?content=non-existent-book&userlang=test",
|
{ /* url */ "/ROOT%23%3F/random?content=non-existent-book&userlang=test",
|
||||||
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "non-existent-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -693,6 +714,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/suggest?content=no-such-book&term=whatever",
|
{ /* url */ "/ROOT%23%3F/suggest?content=no-such-book&term=whatever",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "no-such-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -701,6 +723,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/catalog/",
|
{ /* url */ "/ROOT%23%3F/catalog/",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/catalog/" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -710,6 +733,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/catalog/?userlang=test",
|
{ /* url */ "/ROOT%23%3F/catalog/?userlang=test",
|
||||||
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/catalog/" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -718,6 +742,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/catalog/invalid_endpoint",
|
{ /* url */ "/ROOT%23%3F/catalog/invalid_endpoint",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/catalog/invalid_endpoint" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -727,6 +752,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/catalog/invalid_endpoint?userlang=test",
|
{ /* url */ "/ROOT%23%3F/catalog/invalid_endpoint?userlang=test",
|
||||||
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/catalog/invalid_endpoint" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -735,6 +761,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/content/invalid-book/whatever",
|
{ /* url */ "/ROOT%23%3F/content/invalid-book/whatever",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/invalid-book/whatever" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "whatever", "SEARCH_URL" : "/ROOT%23%3F/search?pattern=whatever" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -748,6 +775,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article",
|
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article",
|
||||||
book_name=="zimfile" &&
|
book_name=="zimfile" &&
|
||||||
book_title=="Ray Charles" &&
|
book_title=="Ray Charles" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/invalid-article" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "invalid-article", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=invalid-article" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -759,6 +787,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ R"(/ROOT%23%3F/content/"><svg onload=alert(1)>)",
|
{ /* url */ R"(/ROOT%23%3F/content/"><svg onload=alert(1)>)",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/\"><svg onload%3Dalert(1)>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "\"><svg onload=alert(1)>", "SEARCH_URL" : "/ROOT%23%3F/search?pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -772,6 +801,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
{ /* url */ R"(/ROOT%23%3F/content/zimfile/"><svg onload=alert(1)>)",
|
{ /* url */ R"(/ROOT%23%3F/content/zimfile/"><svg onload=alert(1)>)",
|
||||||
book_name=="zimfile" &&
|
book_name=="zimfile" &&
|
||||||
book_title=="Ray Charles" &&
|
book_title=="Ray Charles" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/\"><svg onload%3Dalert(1)>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "\"><svg onload=alert(1)>", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -782,10 +812,27 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
</p>
|
</p>
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
|
// XXX: This test case is against a "</script>" string appearing inside
|
||||||
|
// XXX: javascript code that will confuse the HTML parser
|
||||||
|
{ /* url */ R"(/ROOT%23%3F/content/zimfile/</script>)",
|
||||||
|
book_name=="zimfile" &&
|
||||||
|
book_title=="Ray Charles" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/</scr\ipt>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "script>", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=script%3E" } } } ] })" &&
|
||||||
|
expected_body==R"(
|
||||||
|
<h1>Not Found</h1>
|
||||||
|
<p>
|
||||||
|
The requested URL "/ROOT%23%3F/content/zimfile/</script>" was not found on this server.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Make a full text search for <a href="/ROOT%23%3F/search?content=zimfile&pattern=script%3E">script></a>
|
||||||
|
</p>
|
||||||
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
|
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
|
||||||
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
|
||||||
book_name=="zimfile" &&
|
book_name=="zimfile" &&
|
||||||
book_title=="Ray Charles" &&
|
book_title=="Ray Charles" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/invalid-article" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "invalid-article", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=invalid-article" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -797,6 +844,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/raw/no-such-book/meta/Title",
|
{ /* url */ "/ROOT%23%3F/raw/no-such-book/meta/Title",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/raw/no-such-book/meta/Title" } } }, { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "no-such-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -808,6 +856,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/raw/zimfile/XYZ",
|
{ /* url */ "/ROOT%23%3F/raw/zimfile/XYZ",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/raw/zimfile/XYZ" } } }, { "p" : { "msgid" : "invalid-raw-data-type", "params" : { "DATATYPE" : "XYZ" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -819,6 +868,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/raw/zimfile/meta/invalid-metadata",
|
{ /* url */ "/ROOT%23%3F/raw/zimfile/meta/invalid-metadata",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/raw/zimfile/meta/invalid-metadata" } } }, { "p" : { "msgid" : "raw-entry-not-found", "params" : { "DATATYPE" : "meta", "ENTRY" : "invalid-metadata" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -830,6 +880,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
)" },
|
)" },
|
||||||
|
|
||||||
{ /* url */ "/ROOT%23%3F/raw/zimfile/content/invalid-article",
|
{ /* url */ "/ROOT%23%3F/raw/zimfile/content/invalid-article",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/raw/zimfile/content/invalid-article" } } }, { "p" : { "msgid" : "raw-entry-not-found", "params" : { "DATATYPE" : "content", "ENTRY" : "invalid-article" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -845,6 +896,7 @@ TEST_F(ServerTest, Http404HtmlError)
|
||||||
expected_css_url=="/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" &&
|
expected_css_url=="/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" &&
|
||||||
book_name=="poor" &&
|
book_name=="poor" &&
|
||||||
book_title=="poor" &&
|
book_title=="poor" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : "/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84", "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "fulltext-search-unavailable", "params" : { } }, "details" : [ { "p" : { "msgid" : "no-search-results", "params" : { } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Not Found</h1>
|
<h1>Not Found</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -866,6 +918,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
using namespace TestingOfHtmlResponses;
|
using namespace TestingOfHtmlResponses;
|
||||||
const std::vector<TestContentIn400HtmlResponse> testData{
|
const std::vector<TestContentIn400HtmlResponse> testData{
|
||||||
{ /* url */ "/ROOT%23%3F/search",
|
{ /* url */ "/ROOT%23%3F/search",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search" } } }, { "p" : { "msgid" : "too-many-books", "params" : { "LIMIT" : "3", "NB_BOOKS" : "4" } } } ] })" &&
|
||||||
expected_body== R"(
|
expected_body== R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -876,6 +929,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
</p>
|
</p>
|
||||||
)" },
|
)" },
|
||||||
{ /* url */ "/ROOT%23%3F/search?content=zimfile",
|
{ /* url */ "/ROOT%23%3F/search?content=zimfile",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?content=zimfile" } } }, { "p" : { "msgid" : "no-query", "params" : { } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -886,6 +940,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
</p>
|
</p>
|
||||||
)" },
|
)" },
|
||||||
{ /* url */ "/ROOT%23%3F/search?content=non-existing-book&pattern=asdfqwerty",
|
{ /* url */ "/ROOT%23%3F/search?content=non-existing-book&pattern=asdfqwerty",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?content=non-existing-book&pattern=asdfqwerty" } } }, { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "non-existing-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -896,6 +951,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
</p>
|
</p>
|
||||||
)" },
|
)" },
|
||||||
{ /* url */ "/ROOT%23%3F/search?content=non-existing-book&pattern=a\"<script foo>",
|
{ /* url */ "/ROOT%23%3F/search?content=non-existing-book&pattern=a\"<script foo>",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?content=non-existing-book&pattern=a%22%3Cscript%20foo%3E" } } }, { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "non-existing-book" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -908,6 +964,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
// There is a flaw in our way to handle query string, we cannot differenciate
|
// There is a flaw in our way to handle query string, we cannot differenciate
|
||||||
// between `pattern` and `pattern=`
|
// between `pattern` and `pattern=`
|
||||||
{ /* url */ "/ROOT%23%3F/search?books.filter.lang=eng&pattern",
|
{ /* url */ "/ROOT%23%3F/search?books.filter.lang=eng&pattern",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?books.filter.lang=eng&pattern" } } }, { "p" : { "msgid" : "no-query", "params" : { } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -918,6 +975,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
</p>
|
</p>
|
||||||
)" },
|
)" },
|
||||||
{ /* url */ "/ROOT%23%3F/search?pattern=foo",
|
{ /* url */ "/ROOT%23%3F/search?pattern=foo",
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?pattern=foo" } } }, { "p" : { "msgid" : "too-many-books", "params" : { "LIMIT" : "3", "NB_BOOKS" : "4" } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -931,6 +989,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
||||||
// Testing of translation
|
// Testing of translation
|
||||||
{ /* url */ "/ROOT%23%3F/search?content=zimfile&userlang=test",
|
{ /* url */ "/ROOT%23%3F/search?content=zimfile&userlang=test",
|
||||||
expected_page_title=="[I18N TESTING] Invalid request ($400 fine must be paid)" &&
|
expected_page_title=="[I18N TESTING] Invalid request ($400 fine must be paid)" &&
|
||||||
|
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : "/ROOT%23%3F/search?content=zimfile&userlang=test" } } }, { "p" : { "msgid" : "no-query", "params" : { } } } ] })" &&
|
||||||
expected_body==R"(
|
expected_body==R"(
|
||||||
<h1>[I18N TESTING] -400 karma for an invalid request</h1>
|
<h1>[I18N TESTING] -400 karma for an invalid request</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -1036,7 +1095,10 @@ TEST_F(ServerTest, 500)
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||||
<title>Internal Server Error</title>
|
<title>Internal Server Error</title>
|
||||||
|
<script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = )" + ERROR_HTML_TEMPLATE_JS_STRING + R"(;
|
||||||
|
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "500-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "500-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "500-page-text", "params" : { } } }, { "p" : { "msgid" : "non-translated-text", "params" : { "MSG" : "Entry redirect_loop.html is a redirect entry." } } } ] };
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Internal Server Error</h1>
|
<h1>Internal Server Error</h1>
|
||||||
|
@ -1054,6 +1116,7 @@ TEST_F(ServerTest, 500)
|
||||||
const auto r = zfs1_->GET("/ROOT%23%3F/content/poor/A/redirect_loop.html");
|
const auto r = zfs1_->GET("/ROOT%23%3F/content/poor/A/redirect_loop.html");
|
||||||
EXPECT_EQ(r->status, 500);
|
EXPECT_EQ(r->status, 500);
|
||||||
EXPECT_EQ(r->body, expectedBody);
|
EXPECT_EQ(r->body, expectedBody);
|
||||||
|
EXPECT_EQ(r->get_header_value("Content-Type"), "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1509,7 +1509,10 @@ std::string expectedConfusionOfTonguesErrorHtml(std::string url)
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||||
<title>Invalid request</title>
|
<title>Invalid request</title>
|
||||||
|
<script>
|
||||||
|
window.KIWIX_RESPONSE_TEMPLATE = )" + ERROR_HTML_TEMPLATE_JS_STRING + R"(;
|
||||||
|
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : ")" + url + R"(" } } }, { "p" : { "msgid" : "confusion-of-tongues", "params" : { } } } ] };
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Invalid request</h1>
|
<h1>Invalid request</h1>
|
||||||
|
|
|
@ -190,3 +190,5 @@ protected:
|
||||||
zfs1_.reset();
|
zfs1_.reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const std::string ERROR_HTML_TEMPLATE_JS_STRING = R"("<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml">\n <head>\n <meta content="text/html;charset=UTF-8" http-equiv="content-type" />\n <title>{{PAGE_TITLE}}</title>\n{{#CSS_URL}}\n <link type="text/css" href="{{{CSS_URL}}}" rel="Stylesheet" />\n{{/CSS_URL}}{{#KIWIX_RESPONSE_DATA}} <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>{{/KIWIX_RESPONSE_DATA}}\n </head>\n <body>\n <h1>{{PAGE_HEADING}}</h1>\n{{#details}}\n <p>\n {{{p}}}\n </p>\n{{/details}}\n </body>\n</html>\n")";
|
||||||
|
|
Loading…
Reference in New Issue