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.

This commit works around that limitation by providing message-based
API for top context navigation. Now malicious pages can break out of
the viewer iframe as soon as they learn about that API :)
This commit is contained in:
Veloman Yunkan 2023-05-16 18:17:25 +04:00
parent deb02d92e2
commit f674336cc1
2 changed files with 16 additions and 9 deletions

View File

@ -243,9 +243,10 @@ function matchingAncestorElement(el, context, selector) {
const block_path = `${root}/catch/external`; const block_path = `${root}/catch/external`;
function blockLink(target) { function blockLink(url) {
const encodedHref = encodeURIComponent(target.href); return viewerSettings.linkBlockingEnabled
target.setAttribute("href", block_path + "?source=" + encodedHref); ? block_path + "?source=" + encodeURIComponent(url)
: url;
} }
function isExternalUrl(url) { function isExternalUrl(url) {
@ -262,10 +263,8 @@ function onClickEvent(e) {
const target = matchingAncestorElement(e.target, iframeDocument, "a"); const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) { if (target !== null && "href" in target) {
if ( isExternalUrl(target.href) ) { if ( isExternalUrl(target.href) ) {
target.setAttribute("target", "_top"); target.setAttribute("href", blockLink(target.href));
if ( viewerSettings.linkBlockingEnabled ) { contentIframe.contentWindow.parent.postMessage({ externalURL : target.href }, "*");
return blockLink(target);
}
} }
} }
} }
@ -446,6 +445,13 @@ function changeUILanguage() {
}); });
} }
function handleMessage(event) {
console.log("handleMessage");
if ( event.data.externalURL ) {
window.location = event.data.externalURL;
}
}
function setupViewer() { function setupViewer() {
// Defer the call of handle_visual_viewport_change() until after the // Defer the call of handle_visual_viewport_change() until after the
// presence or absence of the taskbar as determined by this function // presence or absence of the taskbar as determined by this function
@ -453,6 +459,7 @@ function setupViewer() {
setTimeout(handle_visual_viewport_change, 0); setTimeout(handle_visual_viewport_change, 0);
window.onresize = handle_visual_viewport_change; window.onresize = handle_visual_viewport_change;
window.addEventListener("message", handleMessage);
const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper'); const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper');
if ( ! viewerSettings.toolbarEnabled ) { if ( ! viewerSettings.toolbarEnabled ) {

View File

@ -73,7 +73,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=bbdaf425" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=bbdaf425" },
{ 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=b9a574d4" }, { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=d575e81a" },
{ 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" },
@ -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" /> <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="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/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=d575e81a" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script> <script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032"; const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<img src="./skin/langSelector.svg?cacheid=00b59961"> <img src="./skin/langSelector.svg?cacheid=00b59961">