From 16ebc6611b2116128e1ddbe6a72ca6b97ae4ff2b Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Thu, 5 Sep 2024 18:20:15 +0400 Subject: [PATCH 1/2] Handling of external app links in the viewer Links that should be handled/opened by external applications - such as email addresses (mailto:), phone numbers (tel:), etc - are opened by the viewer in a new tab/window, thus avoiding any issues with content security policy. --- static/skin/viewer.js | 15 ++++++++++++++- test/server.cpp | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 8beec989e..4da7a8952 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -310,6 +310,12 @@ function blockLink(url) { : url; } +function urlMustBeHandledByAnExternalApp(url) { + const WHITELISTED_URL_SCHEMATA = ['http:', 'https:', 'about:', 'javascript:']; + + return WHITELISTED_URL_SCHEMATA.indexOf(url.protocol) == -1; +} + function isExternalUrl(url) { if ( url.startsWith(window.location.origin) ) return false; @@ -334,7 +340,13 @@ function onClickEvent(e) { const target = matchingAncestorElement(e.target, iframeDocument, "a"); if (target !== null && "href" in target) { const target_href = getRealHref(target); - if (isExternalUrl(target_href)) { + const target_url = new URL(target_href, iframeDocument.location); + const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url); + if ( isExternalAppUrl && !viewerSettings.linkBlockingEnabled ) { + target.setAttribute("target", "_blank"); + } + + if (isExternalAppUrl || isExternalUrl(target_href)) { const possiblyBlockedLink = blockLink(target_href); if ( e.ctrlKey || e.shiftKey ) { // The link will be loaded in a new tab/window - update the link @@ -343,6 +355,7 @@ function onClickEvent(e) { } else { // Load the external URL in the viewer window (rather than iframe) contentIframe.contentWindow.parent.location = possiblyBlockedLink; + e.preventDefault(); } } } diff --git a/test/server.cpp b/test/server.cpp index e7b5714f6..555ba3c14 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -75,7 +75,7 @@ const ResourceCollection resources200Compressible{ { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" }, - { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=5fc4badf" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=215635fd" }, { 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" }, @@ -324,7 +324,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032"; From 2da9801bac2ee843b58ac5cf07ee587e34ab7a76 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Tue, 10 Sep 2024 17:03:04 +0400 Subject: [PATCH 2/2] Fixed ctrl-clicking of links for Zimit2 ZIMs Zimit2 ZIMs employ Wombat for client-side rewriting of URLs. The latter interferes with our approach of handling in the viewer CTRL/SHIFT-clicks on links inside articles. This commit disables Wombat temporarily while changing the href attribute of the clicked link. --- static/skin/viewer.js | 9 ++++++++- test/server.cpp | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 4da7a8952..baa620ef4 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -335,6 +335,13 @@ function getRealHref(target) { return target_href; } +function setHrefAvoidingWombatRewriting(target, url) { + const old_no_rewrite = target._no_rewrite; + target._no_rewrite = true; + target.setAttribute("href", url); + target._no_rewrite = old_no_rewrite; +} + function onClickEvent(e) { const iframeDocument = contentIframe.contentDocument; const target = matchingAncestorElement(e.target, iframeDocument, "a"); @@ -351,7 +358,7 @@ function onClickEvent(e) { if ( e.ctrlKey || e.shiftKey ) { // The link will be loaded in a new tab/window - update the link // and let the browser handle the rest. - target.setAttribute("href", possiblyBlockedLink); + setHrefAvoidingWombatRewriting(target, possiblyBlockedLink); } else { // Load the external URL in the viewer window (rather than iframe) contentIframe.contentWindow.parent.location = possiblyBlockedLink; diff --git a/test/server.cpp b/test/server.cpp index 555ba3c14..3e0646f06 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -75,7 +75,7 @@ const ResourceCollection resources200Compressible{ { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" }, { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" }, - { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=215635fd" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=aca897b0" }, { 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" }, @@ -324,7 +324,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";