Unconditional blocking of external links

This commit is contained in:
Veloman Yunkan 2022-06-29 11:21:47 +04:00
parent 0ce36e6246
commit 685e7f8ad4
8 changed files with 83 additions and 95 deletions

View File

@ -321,17 +321,6 @@ void print_response_info(int retCode, MHD_Response* response)
}
void ContentResponse::inject_externallinks_blocker()
{
kainjow::mustache::data data;
data.set("root", m_root);
auto script_tag = render_template(RESOURCE::templates::external_blocker_part_html, data);
m_content = prependToFirstOccurence(
m_content,
"</head[ \\t]*>",
script_tag);
}
void ContentResponse::inject_root_link(){
m_content = prependToFirstOccurence(
m_content,
@ -369,10 +358,6 @@ ContentResponse::create_mhd_response(const RequestContext& request)
{
if (contentDecorationAllowed()) {
inject_root_link();
if (m_blockExternalLinks) {
inject_externallinks_blocker();
}
}
const bool isCompressed = can_compress(request) && compress(m_content);

View File

@ -103,7 +103,6 @@ class ContentResponse : public Response {
private:
MHD_Response* create_mhd_response(const RequestContext& request);
void inject_externallinks_blocker();
void inject_root_link();
bool can_compress(const RequestContext& request) const;
bool contentDecorationAllowed() const;

View File

@ -12,7 +12,6 @@ skin/taskbar.css
skin/index.css
skin/fonts/Poppins.ttf
skin/fonts/Roboto.ttf
skin/block_external.js
skin/search_results.css
skin/blank.html
skin/viewer.js
@ -23,7 +22,6 @@ templates/error.html
templates/error.xml
templates/index.html
templates/suggestion.json
templates/external_blocker_part.html
templates/captured_external.html
templates/catalog_entries.xml
templates/catalog_v2_root.xml

View File

@ -1,74 +0,0 @@
const root = document.querySelector( `link[type='root']` ).getAttribute("href");
// `block_path` variable used by openzim/warc2zim to detect whether URL blocking is enabled or not
var block_path = `${root}/catch/external`;
// called only on external links
function capture_event(e, target) { target.setAttribute("href", encodeURI(block_path + "?source=" + target.href)); }
// called on all link clicks. filters external and call capture_event
function on_click_event(e) {
var target = findParent("a", e.target);
if (target !== null && "href" in target) {
var href = target.href;
if (window.location.pathname.indexOf(block_path) == 0) // already in catch page
return;
if (href.indexOf(window.location.origin) == 0)
return;
if (href.substr(0, 2) == "//")
return capture_event(e, target);
if (href.substr(0, 5) == "http:")
return capture_event(e, target);
if (href.substr(0, 6) == "https:")
return capture_event(e, target);
return;
}
}
// script entrypoint (called on document ready)
function run() { live('a', 'click', on_click_event); }
// find first parent with tagname
function findParent(tagname, el) {
while (el) {
if ((el.nodeName || el.tagName).toLowerCase() === tagname.toLowerCase()) {
return el;
}
el = el.parentNode;
}
return null;
}
// matches polyfill
this.Element && function(ElementPrototype) {
ElementPrototype.matches = ElementPrototype.matches ||
ElementPrototype.matchesSelector ||
ElementPrototype.webkitMatchesSelector ||
ElementPrototype.msMatchesSelector ||
function(selector) {
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
while (nodes[++i] && nodes[i] != node);
return !!nodes[i];
}
}(Element.prototype);
// helper for enabling IE 8 event bindings
function addEvent(el, type, handler) {
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
}
// live binding helper using matchesSelector
function live(selector, event, callback, context) {
addEvent(context || document, event, function(e) {
var found, el = e.target || e.srcElement;
while (el && el.matches && el !== context && !(found = el.matches(selector))) el = el.parentElement;
if (found) callback.call(el, e);
});
}
// in case the document is already rendered
if (document.readyState!='loading') run();
// modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', run);
// IE <= 8
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') run();
});

View File

@ -179,6 +179,88 @@ function handle_content_url_change() {
updateCurrentBookIfNeeded(newHash);
};
////////////////////////////////////////////////////////////////////////////////
// External link blocking
////////////////////////////////////////////////////////////////////////////////
function matchingAncestorElement(el, context, selector) {
while (el && el.matches && el !== context) {
if ( el.matches(selector) )
return el;
el = el.parentElement;
}
return null;
}
const block_path = `${root}/catch/external`;
function blockLink(target) {
const encodedHref = encodeURIComponent(target.href);
target.setAttribute("href", block_path + "?source=" + encodedHref);
target.setAttribute("target", "_top");
}
function isExternalUrl(url) {
if ( url.startsWith(window.location.origin) )
return false;
return url.startsWith("//")
|| url.startsWith("http:")
|| url.startsWith("https:");
}
function onClickEvent(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) {
if ( isExternalUrl(target.href) )
return blockLink(target);
}
}
// helper for enabling IE 8 event bindings
function addEventHandler(el, eventType, handler) {
if (el.attachEvent)
el.attachEvent('on'+eventType, handler);
else
el.addEventListener(eventType, handler);
}
function setupEventHandler(context, selector, eventType, callback) {
addEventHandler(context, eventType, function(e) {
const eventElement = e.target || e.srcElement;
const el = matchingAncestorElement(eventElement, context, selector);
if (el)
callback.call(el, e);
});
}
// matches polyfill
this.Element && function(ElementPrototype) {
ElementPrototype.matches = ElementPrototype.matches ||
ElementPrototype.matchesSelector ||
ElementPrototype.webkitMatchesSelector ||
ElementPrototype.msMatchesSelector ||
function(selector) {
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
while (nodes[++i] && nodes[i] != node);
return !!nodes[i];
}
}(Element.prototype);
function setup_external_link_blocker() {
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
}
////////////////////////////////////////////////////////////////////////////////
// End of external link blocking
////////////////////////////////////////////////////////////////////////////////
function on_content_load() {
handle_content_url_change();
setup_external_link_blocker();
}
window.onresize = handle_visual_viewport_change;
window.onhashchange = handle_location_hash_change;

View File

@ -1 +0,0 @@
<script type="text/javascript" src="{{root}}/skin/block_external.js"></script>

View File

@ -56,7 +56,7 @@
<iframe id="content_iframe"
referrerpolicy="same-origin"
onload="handle_content_url_change()"
onload="on_content_load()"
src="skin/blank.html" title="ZIM content" width="100%"
style="border:0px">
</iframe>

View File

@ -46,7 +46,6 @@ const ResourceCollection resources200Compressible{
{ WITH_ETAG, "/ROOT/skin/autoComplete.min.js" },
{ WITH_ETAG, "/ROOT/skin/css/autoComplete.css" },
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
{ WITH_ETAG, "/ROOT/skin/block_external.js" },
{ NO_ETAG, "/ROOT/catalog/search" },