Merge pull request #870 from kiwix/urlEncode_quickfix

This commit is contained in:
Matthieu Gautier 2023-01-25 16:41:24 +01:00 committed by GitHub
commit 76dfc03751
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 131 additions and 73 deletions

View File

@ -82,7 +82,7 @@ std::string fullEntryXML(const Book& book, const std::string& rootLocation, cons
{"title", book.getTitle()}, {"title", book.getTitle()},
{"description", book.getDescription()}, {"description", book.getDescription()},
{"language", book.getLanguage()}, {"language", book.getLanguage()},
{"content_id", urlEncode(contentId, true)}, {"content_id", urlEncode(contentId)},
{"updated", bookDate}, // XXX: this should be the entry update datetime {"updated", bookDate}, // XXX: this should be the entry update datetime
{"book_date", bookDate}, {"book_date", bookDate},
{"category", book.getCategory()}, {"category", book.getCategory()},
@ -216,7 +216,7 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
{"endpoint_root", endpointRoot}, {"endpoint_root", endpointRoot},
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)}, {"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
{"filter", onlyAsNonEmptyMustacheValue(query)}, {"filter", onlyAsNonEmptyMustacheValue(query)},
{"query", query.empty() ? "" : "?" + urlEncode(query)}, {"query", query.empty() ? "" : "?" + 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)},

View File

@ -94,7 +94,7 @@ kainjow::mustache::data buildQueryData
kainjow::mustache::data query; kainjow::mustache::data query;
query.set("pattern", kiwix::encodeDiples(pattern)); query.set("pattern", kiwix::encodeDiples(pattern));
std::ostringstream ss; std::ostringstream ss;
ss << searchProtocolPrefix << "?pattern=" << urlEncode(pattern, true); ss << searchProtocolPrefix << "?pattern=" << urlEncode(pattern);
ss << "&" << bookQuery; ss << "&" << bookQuery;
query.set("unpaginatedQuery", ss.str()); query.set("unpaginatedQuery", ss.str());
auto lang = extractValueFromQuery(bookQuery, "books.filter.lang"); auto lang = extractValueFromQuery(bookQuery, "books.filter.lang");
@ -171,9 +171,10 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
kainjow::mustache::data items{kainjow::mustache::data::type::list}; kainjow::mustache::data items{kainjow::mustache::data::type::list};
for (auto it = m_srs.begin(); it != m_srs.end(); it++) { for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
kainjow::mustache::data result; kainjow::mustache::data result;
std::string zim_id(it.getZimId()); const std::string zim_id(it.getZimId());
const auto path = mp_nameMapper->getNameForId(zim_id) + "/" + it.getPath();
result.set("title", it.getTitle()); result.set("title", it.getTitle());
result.set("absolutePath", absPathPrefix + urlEncode(mp_nameMapper->getNameForId(zim_id), true) + "/" + urlEncode(it.getPath())); result.set("absolutePath", absPathPrefix + urlEncode(path));
result.set("snippet", it.getSnippet()); result.set("snippet", it.getSnippet());
if (mp_library) { if (mp_library) {
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle()); result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());

View File

@ -1030,7 +1030,7 @@ ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::s
std::unique_ptr<Response> std::unique_ptr<Response>
InternalServer::build_redirect(const std::string& bookName, const zim::Item& item) const InternalServer::build_redirect(const std::string& bookName, const zim::Item& item) const
{ {
const auto path = kiwix::urlEncode(item.getPath(), true); const auto path = kiwix::urlEncode(item.getPath());
const auto redirectUrl = m_root + "/content/" + bookName + "/" + path; const auto redirectUrl = m_root + "/content/" + bookName + "/" + path;
return Response::build_redirect(*this, redirectUrl); return Response::build_redirect(*this, redirectUrl);
} }
@ -1055,7 +1055,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
} catch (const std::out_of_range& e) {} } catch (const std::out_of_range& e) {}
if (archive == nullptr) { if (archive == nullptr) {
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern, true); const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
return HTTP404Response(*this, request) return HTTP404Response(*this, request)
+ urlNotFoundMsg + urlNotFoundMsg
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)); + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
@ -1096,7 +1096,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
if (m_verbose.load()) if (m_verbose.load())
printf("Failed to find %s\n", urlStr.c_str()); printf("Failed to find %s\n", urlStr.c_str());
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern, true); std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
return HTTP404Response(*this, request) return HTTP404Response(*this, request)
+ urlNotFoundMsg + urlNotFoundMsg
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)); + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));

View File

@ -116,10 +116,10 @@ MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
if ( ! _this->queryString.empty() ) { if ( ! _this->queryString.empty() ) {
_this->queryString += "&"; _this->queryString += "&";
} }
_this->queryString += key; _this->queryString += urlEncode(key);
if ( value ) { if ( value ) {
_this->queryString += "="; _this->queryString += "=";
_this->queryString += value; _this->queryString += urlEncode(value);
} }
return MHD_YES; return MHD_YES;
} }

View File

@ -99,7 +99,7 @@ class RequestContext {
std::string get_query(F filter, bool mustEncode) const { std::string get_query(F filter, bool mustEncode) const {
std::string q; std::string q;
const char* sep = ""; const char* sep = "";
auto encode = [=](const std::string& value) { return mustEncode?urlEncode(value, true):value; }; auto encode = [=](const std::string& value) { return mustEncode?urlEncode(value):value; };
for ( const auto& a : arguments ) { for ( const auto& a : arguments ) {
if (!filter(a.first)) { if (!filter(a.first)) {
continue; continue;

View File

@ -322,9 +322,6 @@ kainjow::mustache::data kiwix::onlyAsNonEmptyMustacheValue(const std::string& 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);
kainjow::mustache::data urlencode{kainjow::mustache::lambda2{
[](const std::string& str,const kainjow::mustache::renderer& r) { return urlEncode(r(str), true); }}};
data.set("urlencoded", urlencode);
std::stringstream ss; std::stringstream ss;
tmpl.render(data, [&ss](const std::string& str) { ss << str; }); tmpl.render(data, [&ss](const std::string& str) { ss << str; });
return ss.str(); return ss.str();

View File

@ -161,15 +161,14 @@ std::string kiwix::encodeDiples(const std::string& str)
return result; return result;
} }
/* urlEncode() based on javascript encodeURI() & namespace
encodeURIComponent(). Mostly code from rstudio/httpuv (GPLv3) */ {
bool isReservedUrlChar(char c) bool isReservedUrlChar(char c)
{ {
switch (c) { switch (c) {
case ';': case ';':
case ',': case ',':
case '/':
case '?': case '?':
case ':': case ':':
case '@': case '@':
@ -177,22 +176,22 @@ bool isReservedUrlChar(char c)
case '=': case '=':
case '+': case '+':
case '$': case '$':
case '#':
return true; return true;
default: default:
return false; return false;
} }
} }
bool needsEscape(char c, bool encodeReserved) bool isHarmlessUriChar(char c)
{ {
if (c >= 'a' && c <= 'z') if (c >= 'a' && c <= 'z')
return false; return true;
if (c >= 'A' && c <= 'Z') if (c >= 'A' && c <= 'Z')
return false; return true;
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
return false; return true;
if (isReservedUrlChar(c))
return encodeReserved;
switch (c) { switch (c) {
case '-': case '-':
case '_': case '_':
@ -203,9 +202,10 @@ bool needsEscape(char c, bool encodeReserved)
case '\'': case '\'':
case '(': case '(':
case ')': case ')':
return false; case '/':
return true;
} }
return true; return false;
} }
int hexToInt(char c) { int hexToInt(char c) {
@ -230,18 +230,18 @@ int hexToInt(char c) {
} }
} }
std::string kiwix::urlEncode(const std::string& value, bool encodeReserved) } // unnamed namespace
std::string kiwix::urlEncode(const std::string& value)
{ {
std::ostringstream os; std::ostringstream os;
os << std::hex << std::uppercase; os << std::hex << std::uppercase;
for (std::string::const_iterator it = value.begin(); for (const char c : value) {
it != value.end(); if (isHarmlessUriChar(c)) {
it++) { os << c;
if (!needsEscape(*it, encodeReserved)) {
os << *it;
} else { } else {
os << '%' << std::setw(2) << static_cast<unsigned int>(static_cast<unsigned char>(*it)); const unsigned int charVal = static_cast<unsigned char>(c);
os << '%' << std::setw(2) << std::setfill('0') << charVal;
} }
} }
return os.str(); return os.str();
@ -267,15 +267,15 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
int iHi = hexToInt(hi); int iHi = hexToInt(hi);
int iLo = hexToInt(lo); int iLo = hexToInt(lo);
if (iHi < 0 || iLo < 0) { if (iHi < 0 || iLo < 0) {
// Invalid escape sequence // Invalid escape sequence
os << '%' << hi << lo; os << '%' << hi << lo;
continue; continue;
} }
char c = (char)(iHi << 4 | iLo); char c = (char)(iHi << 4 | iLo);
if (!component && isReservedUrlChar(c)) { if (!component && isReservedUrlChar(c)) {
os << '%' << hi << lo; os << '%' << hi << lo;
} else { } else {
os << c; os << c;
} }
} else { } else {
os << *it; os << *it;

View File

@ -55,7 +55,9 @@ private:
}; };
std::string urlEncode(const std::string& value, bool encodeReserved = false); /* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
* difference that the slash (/) symbol is NOT encoded. */
std::string urlEncode(const std::string& value);
std::string urlDecode(const std::string& value, bool component = false); std::string urlDecode(const std::string& value, bool component = false);
std::string join(const std::vector<std::string>& list, const std::string& sep); std::string join(const std::vector<std::string>& list, const std::string& sep);

View File

@ -193,7 +193,7 @@ TEST_F(LibraryServerTest, catalog_search_by_phrase)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (q=&quot;ray charles&quot;)</title>\n" " <title>Filtered zims (q=%22ray%20charles%22)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -212,7 +212,7 @@ TEST_F(LibraryServerTest, catalog_search_by_words)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (q=ray charles)</title>\n" " <title>Filtered zims (q=ray%20charles)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>3</totalResults>\n" " <totalResults>3</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -233,7 +233,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (q=description:ray description:charles)</title>\n" " <title>Filtered zims (q=description%3Aray%20description%3Acharles)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -250,7 +250,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (q=title:&quot;ray charles&quot;)</title>\n" " <title>Filtered zims (q=title%3A%22ray%20charles%22)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n" " <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -269,7 +269,7 @@ TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (q=ray -uncategorized)</title>\n" " <title>Filtered zims (q=ray%20-uncategorized)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -288,7 +288,7 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (tag=_category:jazz)</title>\n" " <title>Filtered zims (tag=_category%3Ajazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n" " <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -342,7 +342,7 @@ TEST_F(LibraryServerTest, catalog_search_by_language)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (lang=eng,fra)</title>\n" " <title>Filtered zims (lang=eng%2Cfra)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -694,7 +694,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
EXPECT_EQ(r->status, 200); EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22") CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
" <title>Filtered Entries (q=&quot;ray charles&quot;)</title>\n" " <title>Filtered Entries (q=%22ray%20charles%22)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -726,8 +726,8 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_language)
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?lang=eng,fra"); const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?lang=eng,fra");
EXPECT_EQ(r->status, 200); EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng,fra") CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng%2Cfra")
" <title>Filtered Entries (lang=eng,fra)</title>\n" " <title>Filtered Entries (lang=eng%2Cfra)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n" " <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"
@ -865,7 +865,7 @@ TEST_F(LibraryServerTest, no_name_mapper_returned_catalog_use_uuid_in_link)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" " <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (tag=_category:jazz)</title>\n" " <title>Filtered zims (tag=_category%3Ajazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n" " <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n" " <startIndex>0</startIndex>\n"

View File

@ -37,34 +37,34 @@ TEST(OpdsCatalog, getSearchUrl)
} }
{ {
Filter f; Filter f;
f.query("abc def"); f.query("abc def#xyz");
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%20def"); EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%20def%23xyz");
} }
{ {
Filter f; Filter f;
f.category("ted"); f.category("ted&bob");
EXPECT_SEARCH_URL("/catalog/v2/entries?category=ted"); EXPECT_SEARCH_URL("/catalog/v2/entries?category=ted%26bob");
} }
{ {
Filter f; Filter f;
f.lang("eng"); f.lang("eng,fra");
EXPECT_SEARCH_URL("/catalog/v2/entries?lang=eng"); EXPECT_SEARCH_URL("/catalog/v2/entries?lang=eng%2Cfra");
} }
{ {
Filter f; Filter f;
f.name("second"); f.name("second?");
EXPECT_SEARCH_URL("/catalog/v2/entries?name=second"); EXPECT_SEARCH_URL("/catalog/v2/entries?name=second%3F");
} }
{ {
Filter f; Filter f;
f.acceptTags({"paper", "plastic"}); f.acceptTags({"#paper", "#plastic"});
EXPECT_SEARCH_URL("/catalog/v2/entries?tag=paper;plastic"); EXPECT_SEARCH_URL("/catalog/v2/entries?tag=%23paper%3B%23plastic");
} }
{ {
Filter f; Filter f;
f.query("abc"); f.query("abc=123");
f.category("ted"); f.category("@ted");
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc&category=ted"); EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%3D123&category=%40ted");
} }
{ {
Filter f; Filter f;
@ -79,7 +79,7 @@ TEST(OpdsCatalog, getSearchUrl)
f.lang("html"); f.lang("html");
f.name("edsonarantesdonascimento"); f.name("edsonarantesdonascimento");
f.acceptTags({"body", "script"}); f.acceptTags({"body", "script"});
EXPECT_SEARCH_URL("/catalog/v2/entries?q=peru&category=scifi&lang=html&name=edsonarantesdonascimento&tag=body;script"); EXPECT_SEARCH_URL("/catalog/v2/entries?q=peru&category=scifi&lang=html&name=edsonarantesdonascimento&tag=body%3Bscript");
} }
#undef EXPECT_SEARCH_URL #undef EXPECT_SEARCH_URL
} }

View File

@ -827,7 +827,7 @@ TEST_F(ServerTest, Http400HtmlError)
expected_body==R"( expected_body==R"(
<h1>Invalid request</h1> <h1>Invalid request</h1>
<p> <p>
The requested URL "/ROOT/search?content=non-existing-book&pattern=a"&lt;script foo&gt;" is not a valid request. The requested URL "/ROOT/search?content=non-existing-book&pattern=a%22%3Cscript%20foo%3E" is not a valid request.
</p> </p>
<p> <p>
No such book: non-existing-book No such book: non-existing-book
@ -910,7 +910,7 @@ TEST_F(ServerTest, HttpXmlError)
/* HTTP status code */ 400, /* HTTP status code */ 400,
/* expected response XML */ R"( /* expected response XML */ R"(
<error>Invalid request</error> <error>Invalid request</error>
<detail>The requested URL "/ROOT/search?format=xml&content=non-existing-book&pattern=a"&lt;script foo&gt;" is not a valid request.</detail> <detail>The requested URL "/ROOT/search?format=xml&content=non-existing-book&pattern=a%22%3Cscript%20foo%3E" is not a valid request.</detail>
<detail>No such book: non-existing-book</detail> <detail>No such book: non-existing-book</detail>
)" }, )" },
// 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
@ -1156,7 +1156,7 @@ TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
auto g = zfs1_->GET("/ROOT/random?content=zimfile"); auto g = zfs1_->GET("/ROOT/random?content=zimfile");
ASSERT_EQ(302, g->status); ASSERT_EQ(302, g->status);
ASSERT_TRUE(g->has_header("Location")); ASSERT_TRUE(g->has_header("Location"));
ASSERT_TRUE(kiwix::startsWith(g->get_header_value("Location"), "/ROOT/content/zimfile/A%2F")); ASSERT_TRUE(kiwix::startsWith(g->get_header_value("Location"), "/ROOT/content/zimfile/A/"));
ASSERT_EQ(getCacheControlHeader(*g), "max-age=0, must-revalidate"); ASSERT_EQ(getCacheControlHeader(*g), "max-age=0, must-revalidate");
ASSERT_FALSE(g->has_header("ETag")); ASSERT_FALSE(g->has_header("ETag"));
} }
@ -1224,7 +1224,7 @@ TEST_F(ServerTest, BookMainPageIsRedirectedToArticleIndex)
auto g = zfs1_->GET("/ROOT/content/zimfile"); auto g = zfs1_->GET("/ROOT/content/zimfile");
ASSERT_EQ(302, g->status); ASSERT_EQ(302, g->status);
ASSERT_TRUE(g->has_header("Location")); ASSERT_TRUE(g->has_header("Location"));
ASSERT_EQ("/ROOT/content/zimfile/A%2Findex", g->get_header_value("Location")); ASSERT_EQ("/ROOT/content/zimfile/A/index", g->get_header_value("Location"));
} }
} }

View File

@ -196,7 +196,7 @@ struct SearchResult
const std::vector<SearchResult> LARGE_SEARCH_RESULTS = { const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
SEARCH_RESULT( SEARCH_RESULT(
/*link*/ "/ROOT/content/zimfile/A/Genius_+_Soul_=_Jazz", /*link*/ "/ROOT/content/zimfile/A/Genius_%2B_Soul_%3D_Jazz",
/*title*/ "Genius + Soul = Jazz", /*title*/ "Genius + Soul = Jazz",
/*snippet*/ R"SNIPPET(...Grammy Hall of Fame in 2011. It was re-issued in the UK, first in 1989 on the Castle Communications "Essential Records" label, and by Rhino Records in 1997 on a single CD together with Charles' 1970 My Kind of <b>Jazz</b>. In 2010, Concord Records released a deluxe edition comprising digitally remastered versions of Genius + Soul = <b>Jazz</b>, My Kind of <b>Jazz</b>, <b>Jazz</b> Number II, and My Kind of <b>Jazz</b> Part 3. Professional ratings Review scores Source Rating Allmusic link Warr.org link Encyclopedia of Popular Music...)SNIPPET", /*snippet*/ R"SNIPPET(...Grammy Hall of Fame in 2011. It was re-issued in the UK, first in 1989 on the Castle Communications "Essential Records" label, and by Rhino Records in 1997 on a single CD together with Charles' 1970 My Kind of <b>Jazz</b>. In 2010, Concord Records released a deluxe edition comprising digitally remastered versions of Genius + Soul = <b>Jazz</b>, My Kind of <b>Jazz</b>, <b>Jazz</b> Number II, and My Kind of <b>Jazz</b> Part 3. Professional ratings Review scores Source Rating Allmusic link Warr.org link Encyclopedia of Popular Music...)SNIPPET",
/*bookTitle*/ "Ray Charles", /*bookTitle*/ "Ray Charles",
@ -236,7 +236,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
), ),
SEARCH_RESULT( SEARCH_RESULT(
/*link*/ "/ROOT/content/zimfile/A/Catchin'_Some_Rays:_The_Music_of_Ray_Charles", /*link*/ "/ROOT/content/zimfile/A/Catchin'_Some_Rays%3A_The_Music_of_Ray_Charles",
/*title*/ "Catchin&apos; Some Rays: The Music of Ray Charles", /*title*/ "Catchin&apos; Some Rays: The Music of Ray Charles",
/*snippet*/ R"SNIPPET(...<b>jazz</b> singer Roseanna Vitro, released in August 1997 on the Telarc <b>Jazz</b> label. Catchin' Some Rays: The Music of Ray Charles Studio album by Roseanna Vitro Released August 1997 Recorded March 26, 1997 at Sound on Sound, NYC April 4,1997 at Quad Recording Studios, NYC Genre Vocal <b>jazz</b> Length 61:00 Label Telarc <b>Jazz</b> CD-83419 Producer Paul Wickliffe Roseanna Vitro chronology Passion Dance (1996) Catchin' Some Rays: The Music of Ray Charles (1997) The Time of My Life: Roseanna Vitro Sings the Songs of......)SNIPPET", /*snippet*/ R"SNIPPET(...<b>jazz</b> singer Roseanna Vitro, released in August 1997 on the Telarc <b>Jazz</b> label. Catchin' Some Rays: The Music of Ray Charles Studio album by Roseanna Vitro Released August 1997 Recorded March 26, 1997 at Sound on Sound, NYC April 4,1997 at Quad Recording Studios, NYC Genre Vocal <b>jazz</b> Length 61:00 Label Telarc <b>Jazz</b> CD-83419 Producer Paul Wickliffe Roseanna Vitro chronology Passion Dance (1996) Catchin' Some Rays: The Music of Ray Charles (1997) The Time of My Life: Roseanna Vitro Sings the Songs of......)SNIPPET",
/*bookTitle*/ "Ray Charles", /*bookTitle*/ "Ray Charles",
@ -244,7 +244,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
), ),
SEARCH_RESULT( SEARCH_RESULT(
/*link*/ "/ROOT/content/zimfile/A/That's_What_I_Say:_John_Scofield_Plays_the_Music_of_Ray_Charles", /*link*/ "/ROOT/content/zimfile/A/That's_What_I_Say%3A_John_Scofield_Plays_the_Music_of_Ray_Charles",
/*title*/ "That&apos;s What I Say: John Scofield Plays the Music of Ray Charles", /*title*/ "That&apos;s What I Say: John Scofield Plays the Music of Ray Charles",
/*snippet*/ R"SNIPPET(That's What I Say: John Scofield Plays the Music of Ray Charles Studio album by John Scofield Released June 7, 2005 (2005-06-07) Recorded December 2004 Studio Avatar Studios, New York City Genre <b>Jazz</b> Length 65:21 Label Verve Producer Steve Jordan John Scofield chronology EnRoute: John Scofield Trio LIVE (2004) That's What I Say: John Scofield Plays the Music of Ray Charles (2005) Out Louder (2006) Professional ratings Review scores Source Rating Allmusic All About <b>Jazz</b> All About <b>Jazz</b>...)SNIPPET", /*snippet*/ R"SNIPPET(That's What I Say: John Scofield Plays the Music of Ray Charles Studio album by John Scofield Released June 7, 2005 (2005-06-07) Recorded December 2004 Studio Avatar Studios, New York City Genre <b>Jazz</b> Length 65:21 Label Verve Producer Steve Jordan John Scofield chronology EnRoute: John Scofield Trio LIVE (2004) That's What I Say: John Scofield Plays the Music of Ray Charles (2005) Out Louder (2006) Professional ratings Review scores Source Rating Allmusic All About <b>Jazz</b> All About <b>Jazz</b>...)SNIPPET",
/*bookTitle*/ "Ray Charles", /*bookTitle*/ "Ray Charles",
@ -284,7 +284,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
), ),
SEARCH_RESULT( SEARCH_RESULT(
/*link*/ "/ROOT/content/zimfile/A/Here_We_Go_Again:_Celebrating_the_Genius_of_Ray_Charles", /*link*/ "/ROOT/content/zimfile/A/Here_We_Go_Again%3A_Celebrating_the_Genius_of_Ray_Charles",
/*title*/ "Here We Go Again: Celebrating the Genius of Ray Charles", /*title*/ "Here We Go Again: Celebrating the Genius of Ray Charles",
/*snippet*/ R"SNIPPET(...and <b>jazz</b> trumpeter Wynton Marsalis. It was recorded during concerts at the Rose Theater in New York City, on February 9 and 10, 2009. The album received mixed reviews, in which the instrumentation of Marsalis' orchestra was praised by the critics. Here We Go Again: Celebrating the Genius of Ray Charles Live album by Willie Nelson and Wynton Marsalis Released March 29, 2011 (2011-03-29) Recorded February 9 10 2009 Venue Rose Theater, New York Genre <b>Jazz</b>, country Length 61:49 Label Blue Note......)SNIPPET", /*snippet*/ R"SNIPPET(...and <b>jazz</b> trumpeter Wynton Marsalis. It was recorded during concerts at the Rose Theater in New York City, on February 9 and 10, 2009. The album received mixed reviews, in which the instrumentation of Marsalis' orchestra was praised by the critics. Here We Go Again: Celebrating the Genius of Ray Charles Live album by Willie Nelson and Wynton Marsalis Released March 29, 2011 (2011-03-29) Recorded February 9 10 2009 Venue Rose Theater, New York Genre <b>Jazz</b>, country Length 61:49 Label Blue Note......)SNIPPET",
/*bookTitle*/ "Ray Charles", /*bookTitle*/ "Ray Charles",
@ -356,7 +356,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
), ),
SEARCH_RESULT( SEARCH_RESULT(
/*link*/ "/ROOT/content/zimfile/A/Ray_Sings,_Basie_Swings", /*link*/ "/ROOT/content/zimfile/A/Ray_Sings%2C_Basie_Swings",
/*title*/ "Ray Sings, Basie Swings", /*title*/ "Ray Sings, Basie Swings",
/*snippet*/ R"SNIPPET(...from 1973 with newly recorded instrumental tracks by the contemporary Count Basie Orchestra. Professional ratings Review scores Source Rating AllMusic Ray Sings, Basie Swings Compilation album by Ray Charles, Count Basie Orchestra Released October 3, 2006 (2006-10-03) Recorded Mid-1970s, February - May 2006 Studio Los Angeles Genre Soul, <b>jazz</b>, Swing Label Concord/Hear Music Producer Gregg Field Ray Charles chronology Genius &amp; Friends (2005) Ray Sings, Basie Swings (2006) Rare Genius: The Undiscovered Masters (2010)...)SNIPPET", /*snippet*/ R"SNIPPET(...from 1973 with newly recorded instrumental tracks by the contemporary Count Basie Orchestra. Professional ratings Review scores Source Rating AllMusic Ray Sings, Basie Swings Compilation album by Ray Charles, Count Basie Orchestra Released October 3, 2006 (2006-10-03) Recorded Mid-1970s, February - May 2006 Studio Los Angeles Genre Soul, <b>jazz</b>, Swing Label Concord/Hear Music Producer Gregg Field Ray Charles chronology Genius &amp; Friends (2005) Ray Sings, Basie Swings (2006) Rare Genius: The Undiscovered Masters (2010)...)SNIPPET",
/*bookTitle*/ "Ray Charles", /*bookTitle*/ "Ray Charles",

View File

@ -105,4 +105,62 @@ TEST(stringTools, extractFromString)
ASSERT_THROW(extractFromString<float>("3.14.5"), std::invalid_argument); ASSERT_THROW(extractFromString<float>("3.14.5"), std::invalid_argument);
} }
namespace URLEncoding
{
const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char digits[] = "0123456789";
const char nonEncodableSymbols[] = ".-_~()*!/";
const char uriDelimSymbols[] = ":@?=+&#$;,";
const char otherSymbols[] = R"(`%^[]{}\|"<>)";
const char whitespace[] = " \n\t\r";
const char someNonASCIIChars[] = "Σ♂♀ツ";
}
TEST(stringTools, urlEncode)
{
using namespace URLEncoding;
EXPECT_EQ(urlEncode(letters), letters);
EXPECT_EQ(urlEncode(digits), digits);
EXPECT_EQ(urlEncode(nonEncodableSymbols), nonEncodableSymbols);
EXPECT_EQ(urlEncode(uriDelimSymbols), "%3A%40%3F%3D%2B%26%23%24%3B%2C");
EXPECT_EQ(urlEncode(otherSymbols), "%60%25%5E%5B%5D%7B%7D%5C%7C%22%3C%3E");
EXPECT_EQ(urlEncode(whitespace), "%20%0A%09%0D");
EXPECT_EQ(urlEncode(someNonASCIIChars), "%CE%A3%E2%99%82%E2%99%80%E3%83%84");
}
TEST(stringTools, urlDecode)
{
using namespace URLEncoding;
const std::string allTestChars = std::string(letters)
+ digits
+ nonEncodableSymbols
+ uriDelimSymbols
+ otherSymbols
+ whitespace
+ someNonASCIIChars;
for ( const char c : allTestChars ) {
const std::string str(1, c);
EXPECT_EQ(urlDecode(urlEncode(str), true), str);
}
EXPECT_EQ(urlDecode(urlEncode(allTestChars), true), allTestChars);
const std::string encodedUriDelimSymbols = urlEncode(uriDelimSymbols);
EXPECT_EQ(urlDecode(encodedUriDelimSymbols, false), encodedUriDelimSymbols);
}
}; };