mirror of https://github.com/kiwix/libkiwix.git
Unconditional blocking of external links
This commit is contained in:
parent
0ce36e6246
commit
685e7f8ad4
|
@ -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(){
|
void ContentResponse::inject_root_link(){
|
||||||
m_content = prependToFirstOccurence(
|
m_content = prependToFirstOccurence(
|
||||||
m_content,
|
m_content,
|
||||||
|
@ -369,10 +358,6 @@ ContentResponse::create_mhd_response(const RequestContext& request)
|
||||||
{
|
{
|
||||||
if (contentDecorationAllowed()) {
|
if (contentDecorationAllowed()) {
|
||||||
inject_root_link();
|
inject_root_link();
|
||||||
|
|
||||||
if (m_blockExternalLinks) {
|
|
||||||
inject_externallinks_blocker();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool isCompressed = can_compress(request) && compress(m_content);
|
const bool isCompressed = can_compress(request) && compress(m_content);
|
||||||
|
|
|
@ -103,7 +103,6 @@ class ContentResponse : public Response {
|
||||||
private:
|
private:
|
||||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||||
|
|
||||||
void inject_externallinks_blocker();
|
|
||||||
void inject_root_link();
|
void inject_root_link();
|
||||||
bool can_compress(const RequestContext& request) const;
|
bool can_compress(const RequestContext& request) const;
|
||||||
bool contentDecorationAllowed() const;
|
bool contentDecorationAllowed() const;
|
||||||
|
|
|
@ -12,7 +12,6 @@ skin/taskbar.css
|
||||||
skin/index.css
|
skin/index.css
|
||||||
skin/fonts/Poppins.ttf
|
skin/fonts/Poppins.ttf
|
||||||
skin/fonts/Roboto.ttf
|
skin/fonts/Roboto.ttf
|
||||||
skin/block_external.js
|
|
||||||
skin/search_results.css
|
skin/search_results.css
|
||||||
skin/blank.html
|
skin/blank.html
|
||||||
skin/viewer.js
|
skin/viewer.js
|
||||||
|
@ -23,7 +22,6 @@ templates/error.html
|
||||||
templates/error.xml
|
templates/error.xml
|
||||||
templates/index.html
|
templates/index.html
|
||||||
templates/suggestion.json
|
templates/suggestion.json
|
||||||
templates/external_blocker_part.html
|
|
||||||
templates/captured_external.html
|
templates/captured_external.html
|
||||||
templates/catalog_entries.xml
|
templates/catalog_entries.xml
|
||||||
templates/catalog_v2_root.xml
|
templates/catalog_v2_root.xml
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
|
@ -179,6 +179,88 @@ function handle_content_url_change() {
|
||||||
updateCurrentBookIfNeeded(newHash);
|
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.onresize = handle_visual_viewport_change;
|
||||||
window.onhashchange = handle_location_hash_change;
|
window.onhashchange = handle_location_hash_change;
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<script type="text/javascript" src="{{root}}/skin/block_external.js"></script>
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
<iframe id="content_iframe"
|
<iframe id="content_iframe"
|
||||||
referrerpolicy="same-origin"
|
referrerpolicy="same-origin"
|
||||||
onload="handle_content_url_change()"
|
onload="on_content_load()"
|
||||||
src="skin/blank.html" title="ZIM content" width="100%"
|
src="skin/blank.html" title="ZIM content" width="100%"
|
||||||
style="border:0px">
|
style="border:0px">
|
||||||
</iframe>
|
</iframe>
|
||||||
|
|
|
@ -46,7 +46,6 @@ const ResourceCollection resources200Compressible{
|
||||||
{ WITH_ETAG, "/ROOT/skin/autoComplete.min.js" },
|
{ WITH_ETAG, "/ROOT/skin/autoComplete.min.js" },
|
||||||
{ WITH_ETAG, "/ROOT/skin/css/autoComplete.css" },
|
{ WITH_ETAG, "/ROOT/skin/css/autoComplete.css" },
|
||||||
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
|
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
|
||||||
{ WITH_ETAG, "/ROOT/skin/block_external.js" },
|
|
||||||
|
|
||||||
{ NO_ETAG, "/ROOT/catalog/search" },
|
{ NO_ETAG, "/ROOT/catalog/search" },
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue