mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #870 from kiwix/urlEncode_quickfix
This commit is contained in:
commit
76dfc03751
|
@ -82,7 +82,7 @@ std::string fullEntryXML(const Book& book, const std::string& rootLocation, cons
|
|||
{"title", book.getTitle()},
|
||||
{"description", book.getDescription()},
|
||||
{"language", book.getLanguage()},
|
||||
{"content_id", urlEncode(contentId, true)},
|
||||
{"content_id", urlEncode(contentId)},
|
||||
{"updated", bookDate}, // XXX: this should be the entry update datetime
|
||||
{"book_date", bookDate},
|
||||
{"category", book.getCategory()},
|
||||
|
@ -216,7 +216,7 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
|
|||
{"endpoint_root", endpointRoot},
|
||||
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
|
||||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||||
{"query", query.empty() ? "" : "?" + urlEncode(query)},
|
||||
{"query", query.empty() ? "" : "?" + query},
|
||||
{"totalResults", to_string(m_totalResults)},
|
||||
{"startIndex", to_string(m_startIndex)},
|
||||
{"itemsPerPage", to_string(m_count)},
|
||||
|
|
|
@ -94,7 +94,7 @@ kainjow::mustache::data buildQueryData
|
|||
kainjow::mustache::data query;
|
||||
query.set("pattern", kiwix::encodeDiples(pattern));
|
||||
std::ostringstream ss;
|
||||
ss << searchProtocolPrefix << "?pattern=" << urlEncode(pattern, true);
|
||||
ss << searchProtocolPrefix << "?pattern=" << urlEncode(pattern);
|
||||
ss << "&" << bookQuery;
|
||||
query.set("unpaginatedQuery", ss.str());
|
||||
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};
|
||||
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
|
||||
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("absolutePath", absPathPrefix + urlEncode(mp_nameMapper->getNameForId(zim_id), true) + "/" + urlEncode(it.getPath()));
|
||||
result.set("absolutePath", absPathPrefix + urlEncode(path));
|
||||
result.set("snippet", it.getSnippet());
|
||||
if (mp_library) {
|
||||
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
|
||||
|
|
|
@ -1030,7 +1030,7 @@ ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::s
|
|||
std::unique_ptr<Response>
|
||||
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;
|
||||
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) {}
|
||||
|
||||
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)
|
||||
+ urlNotFoundMsg
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||
|
@ -1096,7 +1096,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
|||
if (m_verbose.load())
|
||||
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)
|
||||
+ urlNotFoundMsg
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||
|
|
|
@ -116,10 +116,10 @@ MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
|||
if ( ! _this->queryString.empty() ) {
|
||||
_this->queryString += "&";
|
||||
}
|
||||
_this->queryString += key;
|
||||
_this->queryString += urlEncode(key);
|
||||
if ( value ) {
|
||||
_this->queryString += "=";
|
||||
_this->queryString += value;
|
||||
_this->queryString += urlEncode(value);
|
||||
}
|
||||
return MHD_YES;
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class RequestContext {
|
|||
std::string get_query(F filter, bool mustEncode) const {
|
||||
std::string q;
|
||||
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 ) {
|
||||
if (!filter(a.first)) {
|
||||
continue;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
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;
|
||||
tmpl.render(data, [&ss](const std::string& str) { ss << str; });
|
||||
return ss.str();
|
||||
|
|
|
@ -161,15 +161,14 @@ std::string kiwix::encodeDiples(const std::string& str)
|
|||
return result;
|
||||
}
|
||||
|
||||
/* urlEncode() based on javascript encodeURI() &
|
||||
encodeURIComponent(). Mostly code from rstudio/httpuv (GPLv3) */
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isReservedUrlChar(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case ';':
|
||||
case ',':
|
||||
case '/':
|
||||
case '?':
|
||||
case ':':
|
||||
case '@':
|
||||
|
@ -177,22 +176,22 @@ bool isReservedUrlChar(char c)
|
|||
case '=':
|
||||
case '+':
|
||||
case '$':
|
||||
case '#':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsEscape(char c, bool encodeReserved)
|
||||
bool isHarmlessUriChar(char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return false;
|
||||
return true;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return false;
|
||||
return true;
|
||||
if (c >= '0' && c <= '9')
|
||||
return false;
|
||||
if (isReservedUrlChar(c))
|
||||
return encodeReserved;
|
||||
return true;
|
||||
|
||||
switch (c) {
|
||||
case '-':
|
||||
case '_':
|
||||
|
@ -203,9 +202,10 @@ bool needsEscape(char c, bool encodeReserved)
|
|||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
return false;
|
||||
}
|
||||
case '/':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
os << std::hex << std::uppercase;
|
||||
for (std::string::const_iterator it = value.begin();
|
||||
it != value.end();
|
||||
it++) {
|
||||
|
||||
if (!needsEscape(*it, encodeReserved)) {
|
||||
os << *it;
|
||||
for (const char c : value) {
|
||||
if (isHarmlessUriChar(c)) {
|
||||
os << c;
|
||||
} 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();
|
||||
|
|
|
@ -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 join(const std::vector<std::string>& list, const std::string& sep);
|
||||
|
|
|
@ -193,7 +193,7 @@ TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Filtered zims (q="ray charles")</title>\n"
|
||||
" <title>Filtered zims (q=%22ray%20charles%22)</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>2</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -212,7 +212,7 @@ TEST_F(LibraryServerTest, catalog_search_by_words)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>3</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -233,7 +233,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>2</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -250,7 +250,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Filtered zims (q=title:"ray charles")</title>\n"
|
||||
" <title>Filtered zims (q=title%3A%22ray%20charles%22)</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>1</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -269,7 +269,7 @@ TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>2</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -288,7 +288,7 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>1</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
@ -342,7 +342,7 @@ TEST_F(LibraryServerTest, catalog_search_by_language)
|
|||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>2</totalResults>\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(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
||||
" <title>Filtered Entries (q="ray charles")</title>\n"
|
||||
" <title>Filtered Entries (q=%22ray%20charles%22)</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>2</totalResults>\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");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng,fra")
|
||||
" <title>Filtered Entries (lang=eng,fra)</title>\n"
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng%2Cfra")
|
||||
" <title>Filtered Entries (lang=eng%2Cfra)</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>2</totalResults>\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),
|
||||
OPDS_FEED_TAG
|
||||
" <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"
|
||||
" <totalResults>1</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
|
|
|
@ -37,34 +37,34 @@ TEST(OpdsCatalog, getSearchUrl)
|
|||
}
|
||||
{
|
||||
Filter f;
|
||||
f.query("abc def");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%20def");
|
||||
f.query("abc def#xyz");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%20def%23xyz");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
f.category("ted");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?category=ted");
|
||||
f.category("ted&bob");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?category=ted%26bob");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
f.lang("eng");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?lang=eng");
|
||||
f.lang("eng,fra");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?lang=eng%2Cfra");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
f.name("second");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?name=second");
|
||||
f.name("second?");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?name=second%3F");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
f.acceptTags({"paper", "plastic"});
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?tag=paper;plastic");
|
||||
f.acceptTags({"#paper", "#plastic"});
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?tag=%23paper%3B%23plastic");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
f.query("abc");
|
||||
f.category("ted");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc&category=ted");
|
||||
f.query("abc=123");
|
||||
f.category("@ted");
|
||||
EXPECT_SEARCH_URL("/catalog/v2/entries?q=abc%3D123&category=%40ted");
|
||||
}
|
||||
{
|
||||
Filter f;
|
||||
|
@ -79,7 +79,7 @@ TEST(OpdsCatalog, getSearchUrl)
|
|||
f.lang("html");
|
||||
f.name("edsonarantesdonascimento");
|
||||
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
|
||||
}
|
||||
|
|
|
@ -827,7 +827,7 @@ TEST_F(ServerTest, Http400HtmlError)
|
|||
expected_body==R"(
|
||||
<h1>Invalid request</h1>
|
||||
<p>
|
||||
The requested URL "/ROOT/search?content=non-existing-book&pattern=a"<script foo>" 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>
|
||||
No such book: non-existing-book
|
||||
|
@ -910,7 +910,7 @@ TEST_F(ServerTest, HttpXmlError)
|
|||
/* HTTP status code */ 400,
|
||||
/* expected response XML */ R"(
|
||||
<error>Invalid request</error>
|
||||
<detail>The requested URL "/ROOT/search?format=xml&content=non-existing-book&pattern=a"<script foo>" 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>
|
||||
)" },
|
||||
// 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");
|
||||
ASSERT_EQ(302, g->status);
|
||||
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_FALSE(g->has_header("ETag"));
|
||||
}
|
||||
|
@ -1224,7 +1224,7 @@ TEST_F(ServerTest, BookMainPageIsRedirectedToArticleIndex)
|
|||
auto g = zfs1_->GET("/ROOT/content/zimfile");
|
||||
ASSERT_EQ(302, g->status);
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ struct SearchResult
|
|||
|
||||
const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT/content/zimfile/A/Genius_+_Soul_=_Jazz",
|
||||
/*link*/ "/ROOT/content/zimfile/A/Genius_%2B_Soul_%3D_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",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
|
@ -236,7 +236,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
|||
),
|
||||
|
||||
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' 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",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
|
@ -244,7 +244,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
|||
),
|
||||
|
||||
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'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",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
|
@ -284,7 +284,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
|||
),
|
||||
|
||||
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",
|
||||
/*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",
|
||||
|
@ -356,7 +356,7 @@ const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
|||
),
|
||||
|
||||
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",
|
||||
/*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 & Friends (2005) Ray Sings, Basie Swings (2006) Rare Genius: The Undiscovered Masters (2010)...)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
|
|
|
@ -105,4 +105,62 @@ TEST(stringTools, extractFromString)
|
|||
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);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue