Compare commits

...

18 Commits

Author SHA1 Message Date
Veloman Yunkan
035667b0e1 fixup! Fixed external links in the viewer iframe 2023-05-03 16:56:55 +04:00
Veloman Yunkan
d2b014656b Fixed external links in the viewer iframe
Before this fix clicking an external link in the viewer iframe had no
effect (other than an error being reported in the browser dev tools
console) because the attempt to navigate the top browser context was
suppressed due to sandboxing.
2023-05-02 19:20:38 +04:00
Matthieu Gautier
deb02d92e2 Merge pull request #942 from kiwix/opds_response_charset_info 2023-04-25 16:56:06 +02:00
Veloman Yunkan
dc58e278c7 git mv src/server/internalServer_catalog{_v2,}.cpp 2023-04-25 12:48:49 +04:00
Veloman Yunkan
9994302312 Explicit charset in OPDS response MIME types 2023-04-25 12:48:29 +04:00
Veloman Yunkan
8c190cf34f Moved InternalServer::handle_catalog() 2023-04-25 12:48:10 +04:00
Veloman Yunkan
1273570e01 Deduplication of OPDS MIME type strings 2023-04-25 12:47:02 +04:00
Veloman Yunkan
9bd2df2327 ServerTest.MimeTypes tests all OPDS endpoints 2023-04-25 12:46:24 +04:00
Kelson
08834d6f17 Merge pull request #939 from kiwix/welcome_page_opds_api_upgrade
Got rid of legacy OPDS API usage in kiwix-serve
2023-04-21 20:19:07 +02:00
Veloman Yunkan
47950f132e Got rid of legacy OPDS API usage in kiwix-serve 2023-04-21 17:03:13 +04:00
Kelson
1a92d4a0b5 Merge pull request #934 from kiwix/mulNames
Display MUL on tile when multiple languages are available
2023-04-18 16:58:27 +02:00
Nikhil Tanwar
272dc142c5 Display MUL on tile when multiple languages are available
If a book contains multiple languages, the language label now shows "MUL".
On hover, it displays the list of all languages available in the ZIM.
2023-04-18 18:53:07 +05:30
Matthieu Gautier
bf1d207651 Merge pull request #936 from kiwix/opds_xml_fix 2023-04-18 14:07:31 +02:00
Veloman Yunkan
6f0e55d603 A slight simplification of the mustache template
Got rid of the partial vs full entries logic in the mustache template -
now it is entirely contained in `OPDSDumper::dumpOPDSFeedV2()`.
2023-04-18 14:45:51 +04:00
Veloman Yunkan
ebe16f92a5 Fixed OPDS XML output for multiple filters
In XML any & symbols acting as parameter separators in URL search
components must be HTML-escaped.
2023-04-18 14:33:40 +04:00
Veloman Yunkan
4f6a5759aa LibraryServerTest.catalog_v2_entries_multiple_filters 2023-04-18 14:24:00 +04:00
Kelson
d85eb1b747 Merge pull request #935 from kiwix/revert-macos-ci-fix
Revert "Unlink and remove some linked python3 files"
2023-04-18 07:37:59 +02:00
Emmanuel Engelhart
41a1124585 Revert "Unlink and remove some linked python3 files"
This reverts commit 95bde675ef.
2023-04-18 07:29:29 +02:00
11 changed files with 124 additions and 77 deletions

View File

@@ -18,9 +18,6 @@ jobs:
- name: Install packages
run: |
brew update
brew unlink python3
# upgrade from python@3.11.2_1 to python@3.11.3 fails to overwrite those
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.11 /usr/local/bin/idle3 /usr/local/bin/idle3.11 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.11 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.11 /usr/local/bin/python3.11-config
brew install pkg-config ninja meson
- name: Install dependencies
@@ -141,4 +138,4 @@ jobs:
if: startsWith(matrix.target, 'native_')
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -26,7 +26,7 @@ kiwix_sources = [
'server/request_context.cpp',
'server/response.cpp',
'server/internalServer.cpp',
'server/internalServer_catalog_v2.cpp',
'server/internalServer_catalog.cpp',
'server/i18n.cpp',
'opds_catalog.cpp',
'version.cpp'

View File

@@ -150,17 +150,17 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial);
const char* const endpoint = partial ? "/partial_entries" : "/entries";
const std::string url = endpoint + (query.empty() ? "" : "?" + query);
const kainjow::mustache::object template_data{
{"date", gen_date_str()},
{"endpoint_root", endpointRoot},
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
{"filter", onlyAsNonEmptyMustacheValue(query)},
{"query", query.empty() ? "" : "?" + query},
{"self_url", url},
{"totalResults", to_string(m_totalResults)},
{"startIndex", to_string(m_startIndex)},
{"itemsPerPage", to_string(m_count)},
{"books", booksData },
{"dump_partial_entries", MustacheData(partial)}
{"books", booksData }
};
return render_template(RESOURCE::templates::catalog_v2_entries_xml, template_data);

View File

@@ -1046,56 +1046,6 @@ std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& req
+ urlNotFoundMsg;
}
std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_catalog");
}
std::string host;
std::string url;
try {
host = request.get_header("Host");
url = request.get_url_part(1);
} catch (const std::out_of_range&) {
return HTTP404Response(*this, request)
+ urlNotFoundMsg;
}
if (url == "v2") {
return handle_catalog_v2(request);
}
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
return HTTP404Response(*this, request)
+ urlNotFoundMsg;
}
if (url == "searchdescription.xml") {
auto response = ContentResponse::build(*this, RESOURCE::opensearchdescription_xml, get_default_data(), "application/opensearchdescription+xml");
return std::move(response);
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
std::vector<std::string> bookIdsToDump;
if (url == "root.xml") {
uuid = zim::Uuid::generate(host);
bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
} else if (url == "search") {
bookIdsToDump = search_catalog(request, opdsDumper);
uuid = zim::Uuid::generate();
}
auto response = ContentResponse::build(
*this,
opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()),
"application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8");
return std::move(response);
}
std::vector<std::string>
InternalServer::search_catalog(const RequestContext& request,
kiwix::OPDSDumper& opdsDumper)
@@ -1138,6 +1088,7 @@ const std::string CONTENT_CSP_HEADER =
"sandbox allow-scripts "
"allow-same-origin "
"allow-top-navigation-by-user-activation "
"allow-modals "
"allow-popups "
"allow-forms "

View File

@@ -33,6 +33,74 @@
namespace kiwix {
namespace
{
enum OPDSResponseKind
{
OPDS_ENTRY,
OPDS_NAVIGATION_FEED,
OPDS_ACQUISITION_FEED
};
const std::string opdsMimeType[] = {
"application/atom+xml;type=entry;profile=opds-catalog;charset=utf-8",
"application/atom+xml;profile=opds-catalog;kind=navigation;charset=utf-8",
"application/atom+xml;profile=opds-catalog;kind=acquisition;charset=utf-8"
};
} // unnamed namespace
std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_catalog");
}
std::string host;
std::string url;
try {
host = request.get_header("Host");
url = request.get_url_part(1);
} catch (const std::out_of_range&) {
return HTTP404Response(*this, request)
+ urlNotFoundMsg;
}
if (url == "v2") {
return handle_catalog_v2(request);
}
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
return HTTP404Response(*this, request)
+ urlNotFoundMsg;
}
if (url == "searchdescription.xml") {
auto response = ContentResponse::build(*this, RESOURCE::opensearchdescription_xml, get_default_data(), "application/opensearchdescription+xml");
return std::move(response);
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
std::vector<std::string> bookIdsToDump;
if (url == "root.xml") {
uuid = zim::Uuid::generate(host);
bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
} else if (url == "search") {
bookIdsToDump = search_catalog(request, opdsDumper);
uuid = zim::Uuid::generate();
}
auto response = ContentResponse::build(
*this,
opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()),
opdsMimeType[OPDS_ACQUISITION_FEED]);
return std::move(response);
}
std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext& request)
{
if (m_verbose.load()) {
@@ -90,7 +158,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
{"category_list_feed_id", gen_uuid(libraryId + "/categories")},
{"language_list_feed_id", gen_uuid(libraryId + "/languages")}
},
"application/atom+xml;profile=opds-catalog;kind=navigation"
opdsMimeType[OPDS_NAVIGATION_FEED]
);
}
@@ -104,7 +172,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const Reques
return ContentResponse::build(
*this,
opdsFeed,
"application/atom+xml;profile=opds-catalog;kind=acquisition"
opdsMimeType[OPDS_ACQUISITION_FEED]
);
}
@@ -124,7 +192,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
return ContentResponse::build(
*this,
opdsFeed,
"application/atom+xml;type=entry;profile=opds-catalog"
opdsMimeType[OPDS_ENTRY]
);
}
@@ -136,7 +204,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
return ContentResponse::build(
*this,
opdsDumper.categoriesOPDSFeed(),
"application/atom+xml;profile=opds-catalog;kind=navigation"
opdsMimeType[OPDS_NAVIGATION_FEED]
);
}
@@ -148,7 +216,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const Requ
return ContentResponse::build(
*this,
opdsDumper.languagesOPDSFeed(),
"application/atom+xml;profile=opds-catalog;kind=navigation"
opdsMimeType[OPDS_NAVIGATION_FEED]
);
}

View File

@@ -51,7 +51,7 @@
}
function queryUrlBuilder() {
let url = `${root}/catalog/search?`;
let url = `${root}/catalog/v2/entries?`;
url += Object.keys(incrementalLoadingParams).map(key => `${key}=${incrementalLoadingParams[key]}`).join("&");
params.forEach((value, key) => {url+= value ? `&${key}=${value}` : ''});
return (url);
@@ -131,8 +131,13 @@
const title = getInnerHtml(book, 'title');
const description = getInnerHtml(book, 'summary');
const id = getInnerHtml(book, 'id');
const langCode = getInnerHtml(book, 'language');
const language = languages[langCode];
const langCodesList = getInnerHtml(book, 'language').split(',');
const langCode = langCodesList.length == 1 ? langCodesList[0] : 'mul';
let language = languages[langCode];
if (langCode == 'mul') {
const mulLangList = langCodesList.filter(x => languages.hasOwnProperty(x)).map(x => languages[x]);
language = mulLangList.join(', ');
}
const tags = getInnerHtml(book, 'tags');
const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
@@ -262,7 +267,7 @@
}
async function getBookCount(query) {
const url = `${root}/catalog/search?${query}`;
const url = `${root}/catalog/v2/entries?${query}&count=0`;
return await fetch(url).then(async (resp) => {
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
return parseInt(data.querySelector('totalResults').innerHTML);

View File

@@ -257,7 +257,7 @@ function isExternalUrl(url) {
|| url.startsWith("https:");
}
function onClickEvent(e) {
function handleLinkClick(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) {
@@ -266,6 +266,8 @@ function onClickEvent(e) {
if ( viewerSettings.linkBlockingEnabled ) {
return blockLink(target);
}
} else {
target.setAttribute("target", "content_iframe");
}
}
}
@@ -301,7 +303,7 @@ this.Element && function(ElementPrototype) {
}(Element.prototype);
function setup_external_link_blocker() {
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
setupEventHandler(contentIframe.contentDocument, 'a', 'click', handleLinkClick);
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -6,7 +6,7 @@
<id>{{feed_id}}</id>
<link rel="self"
href="{{endpoint_root}}/{{#dump_partial_entries}}partial_{{/dump_partial_entries}}entries{{{query}}}"
href="{{endpoint_root}}{{self_url}}"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link rel="start"
href="{{endpoint_root}}/root.xml"

View File

@@ -69,6 +69,7 @@
</div>
<iframe id="content_iframe"
name="content_iframe"
referrerpolicy="no-referrer"
onload="on_content_load()"
src="./skin/blank.html?KIWIXCACHEID" title="ZIM content" width="100%"

View File

@@ -728,7 +728,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?start=1&count=1");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?start=1&count=1")
CATALOG_V2_ENTRIES_PREAMBLE("?start=1&amp;count=1")
" <title>Filtered Entries (start=1&amp;count=1)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>3</totalResults>\n"
@@ -793,6 +793,24 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_language)
}
}
TEST_F(LibraryServerTest, catalog_v2_entries_multiple_filters)
{
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?lang=fra&category=jazz");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?lang=fra&amp;category=jazz")
" <title>Filtered Entries (lang=fra&amp;category=jazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>1</itemsPerPage>\n"
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
}
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entry/raycharles");
@@ -1193,7 +1211,7 @@ TEST_F(LibraryServerTest, noJS) {
FILTERS_HTML("")
HOME_BODY_0_RESULTS
FINAL_HTML_TEXT);
// no_js_download
r = zfs1_->GET("/ROOT%23%3F/nojs/download/zimfile");
EXPECT_EQ(r->status, 200);

View File

@@ -63,7 +63,7 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=d38d9ef1" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=07b06fca" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
@@ -73,7 +73,7 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=bbdaf425" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=b9a574d4" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=b548ad94" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@@ -288,7 +288,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=b00b12db" 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 type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=d38d9ef1" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=07b06fca" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
@@ -312,7 +312,7 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/taskbar.css?cacheid=bbda
<link type="text/css" href="./skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=b00b12db" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=b9a574d4" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=b548ad94" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<img src="./skin/langSelector.svg?cacheid=00b59961">
@@ -497,8 +497,13 @@ TEST_F(ServerTest, MimeTypes)
{ "/skin/blank.html", "text/html" },
{ "/skin/index.css", "text/css" },
{ "/skin/index.js", "application/javascript" },
{ "/catalog/root.xml", "application/atom+xml;profile=opds-catalog;kind=acquisition;charset=utf-8" },
{ "/catalog/v2/searchdescription.xml", "application/opensearchdescription+xml" },
{ "/catalog/v2/root.xml", "application/atom+xml;profile=opds-catalog;kind=navigation" },
{ "/catalog/v2/root.xml", "application/atom+xml;profile=opds-catalog;kind=navigation;charset=utf-8" },
{ "/catalog/v2/languages", "application/atom+xml;profile=opds-catalog;kind=navigation;charset=utf-8" },
{ "/catalog/v2/categories", "application/atom+xml;profile=opds-catalog;kind=navigation;charset=utf-8" },
{ "/catalog/v2/entries", "application/atom+xml;profile=opds-catalog;kind=acquisition;charset=utf-8" },
{ "/catalog/v2/entry/6f1d19d0-633f-087b-fb55-7ac324ff9baf", "application/atom+xml;type=entry;profile=opds-catalog;charset=utf-8" },
{ "/skin/search-icon.svg", "image/svg+xml" },
{ "/skin/bittorrent.png", "image/png" },
{ "/skin/favicon/favicon.ico", "image/x-icon" },