mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #330 from kiwix/block-external-links
Add external links blocking in serve
This commit is contained in:
commit
507002d34e
|
@ -57,6 +57,8 @@ namespace kiwix
|
||||||
void setVerbose(bool verbose) { m_verbose = verbose; }
|
void setVerbose(bool verbose) { m_verbose = verbose; }
|
||||||
void setTaskbar(bool withTaskbar, bool withLibraryButton)
|
void setTaskbar(bool withTaskbar, bool withLibraryButton)
|
||||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||||
|
void setBlockExternalLinks(bool blockExternalLinks)
|
||||||
|
{ m_blockExternalLinks = blockExternalLinks; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Library* mp_library;
|
Library* mp_library;
|
||||||
|
@ -68,6 +70,7 @@ namespace kiwix
|
||||||
bool m_verbose = false;
|
bool m_verbose = false;
|
||||||
bool m_withTaskbar = true;
|
bool m_withTaskbar = true;
|
||||||
bool m_withLibraryButton = true;
|
bool m_withLibraryButton = true;
|
||||||
|
bool m_blockExternalLinks = false;
|
||||||
std::unique_ptr<InternalServer> mp_server;
|
std::unique_ptr<InternalServer> mp_server;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,8 @@ class InternalServer {
|
||||||
int nbThreads,
|
int nbThreads,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool withTaskbar,
|
bool withTaskbar,
|
||||||
bool withLibraryButton);
|
bool withLibraryButton,
|
||||||
|
bool blockExternalLinks);
|
||||||
virtual ~InternalServer() = default;
|
virtual ~InternalServer() = default;
|
||||||
|
|
||||||
int handlerCallback(struct MHD_Connection* connection,
|
int handlerCallback(struct MHD_Connection* connection,
|
||||||
|
@ -119,6 +120,7 @@ class InternalServer {
|
||||||
Response handle_search(const RequestContext& request);
|
Response handle_search(const RequestContext& request);
|
||||||
Response handle_suggest(const RequestContext& request);
|
Response handle_suggest(const RequestContext& request);
|
||||||
Response handle_random(const RequestContext& request);
|
Response handle_random(const RequestContext& request);
|
||||||
|
Response handle_captured_external(const RequestContext& request);
|
||||||
Response handle_content(const RequestContext& request);
|
Response handle_content(const RequestContext& request);
|
||||||
|
|
||||||
kainjow::mustache::data get_default_data();
|
kainjow::mustache::data get_default_data();
|
||||||
|
@ -131,6 +133,7 @@ class InternalServer {
|
||||||
std::atomic_bool m_verbose;
|
std::atomic_bool m_verbose;
|
||||||
bool m_withTaskbar;
|
bool m_withTaskbar;
|
||||||
bool m_withLibraryButton;
|
bool m_withLibraryButton;
|
||||||
|
bool m_blockExternalLinks;
|
||||||
struct MHD_Daemon* mp_daemon;
|
struct MHD_Daemon* mp_daemon;
|
||||||
|
|
||||||
Library* mp_library;
|
Library* mp_library;
|
||||||
|
@ -157,7 +160,8 @@ bool Server::start() {
|
||||||
m_nbThreads,
|
m_nbThreads,
|
||||||
m_verbose,
|
m_verbose,
|
||||||
m_withTaskbar,
|
m_withTaskbar,
|
||||||
m_withLibraryButton));
|
m_withLibraryButton,
|
||||||
|
m_blockExternalLinks));
|
||||||
return mp_server->start();
|
return mp_server->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +190,8 @@ InternalServer::InternalServer(Library* library,
|
||||||
int nbThreads,
|
int nbThreads,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool withTaskbar,
|
bool withTaskbar,
|
||||||
bool withLibraryButton) :
|
bool withLibraryButton,
|
||||||
|
bool blockExternalLinks) :
|
||||||
m_addr(addr),
|
m_addr(addr),
|
||||||
m_port(port),
|
m_port(port),
|
||||||
m_root(root),
|
m_root(root),
|
||||||
|
@ -194,6 +199,7 @@ InternalServer::InternalServer(Library* library,
|
||||||
m_verbose(verbose),
|
m_verbose(verbose),
|
||||||
m_withTaskbar(withTaskbar),
|
m_withTaskbar(withTaskbar),
|
||||||
m_withLibraryButton(withLibraryButton),
|
m_withLibraryButton(withLibraryButton),
|
||||||
|
m_blockExternalLinks(blockExternalLinks),
|
||||||
mp_daemon(nullptr),
|
mp_daemon(nullptr),
|
||||||
mp_library(library),
|
mp_library(library),
|
||||||
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper)
|
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper)
|
||||||
|
@ -340,6 +346,9 @@ Response InternalServer::handle_request(const RequestContext& request)
|
||||||
if (request.get_url() == "/random")
|
if (request.get_url() == "/random")
|
||||||
return handle_random(request);
|
return handle_random(request);
|
||||||
|
|
||||||
|
if (request.get_url() == "/catch/external")
|
||||||
|
return handle_captured_external(request);
|
||||||
|
|
||||||
return handle_content(request);
|
return handle_content(request);
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
||||||
|
@ -359,7 +368,7 @@ kainjow::mustache::data InternalServer::get_default_data()
|
||||||
|
|
||||||
Response InternalServer::get_default_response()
|
Response InternalServer::get_default_response()
|
||||||
{
|
{
|
||||||
return Response(m_root, m_verbose.load(), m_withTaskbar, m_withLibraryButton);
|
return Response(m_root, m_verbose.load(), m_withTaskbar, m_withLibraryButton, m_blockExternalLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -383,7 +392,7 @@ Response InternalServer::build_500(const std::string& msg)
|
||||||
{
|
{
|
||||||
kainjow::mustache::data data;
|
kainjow::mustache::data data;
|
||||||
data.set("error", msg);
|
data.set("error", msg);
|
||||||
Response response(m_root, true, false, false);
|
Response response(m_root, true, false, false, false);
|
||||||
response.set_template(RESOURCE::templates::_500_html, data);
|
response.set_template(RESOURCE::templates::_500_html, data);
|
||||||
response.set_mimeType("text/html");
|
response.set_mimeType("text/html");
|
||||||
response.set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
response.set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
@ -711,6 +720,26 @@ Response InternalServer::handle_random(const RequestContext& request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response InternalServer::handle_captured_external(const RequestContext& request)
|
||||||
|
{
|
||||||
|
std::string source = "";
|
||||||
|
try {
|
||||||
|
source = kiwix::urlDecode(request.get_argument("source"));
|
||||||
|
} catch (const std::out_of_range& e) {}
|
||||||
|
|
||||||
|
if (source.empty())
|
||||||
|
return build_404(request, "");
|
||||||
|
|
||||||
|
auto data = get_default_data();
|
||||||
|
data.set("source", source);
|
||||||
|
auto response = get_default_response();
|
||||||
|
response.set_template(RESOURCE::templates::captured_external_html, data);
|
||||||
|
response.set_mimeType("text/html; charset=utf-8");
|
||||||
|
response.set_compress(true);
|
||||||
|
response.set_taskbar("", "");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
Response InternalServer::handle_catalog(const RequestContext& request)
|
Response InternalServer::handle_catalog(const RequestContext& request)
|
||||||
{
|
{
|
||||||
if (m_verbose.load()) {
|
if (m_verbose.load()) {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton)
|
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks)
|
||||||
: m_verbose(verbose),
|
: m_verbose(verbose),
|
||||||
m_root(root),
|
m_root(root),
|
||||||
m_content(""),
|
m_content(""),
|
||||||
|
@ -25,6 +25,7 @@ Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool
|
||||||
m_returnCode(MHD_HTTP_OK),
|
m_returnCode(MHD_HTTP_OK),
|
||||||
m_withTaskbar(withTaskbar),
|
m_withTaskbar(withTaskbar),
|
||||||
m_withLibraryButton(withLibraryButton),
|
m_withLibraryButton(withLibraryButton),
|
||||||
|
m_blockExternalLinks(blockExternalLinks),
|
||||||
m_useCache(false),
|
m_useCache(false),
|
||||||
m_addTaskbar(false),
|
m_addTaskbar(false),
|
||||||
m_bookName(""),
|
m_bookName(""),
|
||||||
|
@ -116,7 +117,7 @@ void Response::introduce_taskbar()
|
||||||
auto head_content = render_template(RESOURCE::templates::head_part_html, data);
|
auto head_content = render_template(RESOURCE::templates::head_part_html, data);
|
||||||
m_content = appendToFirstOccurence(
|
m_content = appendToFirstOccurence(
|
||||||
m_content,
|
m_content,
|
||||||
"<head>",
|
"<head>\n",
|
||||||
head_content);
|
head_content);
|
||||||
|
|
||||||
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
|
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
|
||||||
|
@ -127,6 +128,17 @@ void Response::introduce_taskbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Response::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 = appendToFirstOccurence(
|
||||||
|
m_content,
|
||||||
|
"<head>\n",
|
||||||
|
script_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int Response::send(const RequestContext& request, MHD_Connection* connection)
|
int Response::send(const RequestContext& request, MHD_Connection* connection)
|
||||||
{
|
{
|
||||||
|
@ -136,6 +148,9 @@ int Response::send(const RequestContext& request, MHD_Connection* connection)
|
||||||
if (m_addTaskbar) {
|
if (m_addTaskbar) {
|
||||||
introduce_taskbar();
|
introduce_taskbar();
|
||||||
}
|
}
|
||||||
|
if ( m_blockExternalLinks ) {
|
||||||
|
inject_externallinks_blocker();
|
||||||
|
}
|
||||||
|
|
||||||
bool shouldCompress = m_compress && request.can_compress();
|
bool shouldCompress = m_compress && request.can_compress();
|
||||||
shouldCompress &= m_mimeType.find("text/") != string::npos
|
shouldCompress &= m_mimeType.find("text/") != string::npos
|
||||||
|
|
|
@ -42,7 +42,7 @@ class RequestContext;
|
||||||
|
|
||||||
class Response {
|
class Response {
|
||||||
public:
|
public:
|
||||||
Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton);
|
Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks);
|
||||||
~Response() = default;
|
~Response() = default;
|
||||||
|
|
||||||
int send(const RequestContext& request, MHD_Connection* connection);
|
int send(const RequestContext& request, MHD_Connection* connection);
|
||||||
|
@ -64,6 +64,7 @@ class Response {
|
||||||
int getReturnCode() { return m_returnCode; }
|
int getReturnCode() { return m_returnCode; }
|
||||||
|
|
||||||
void introduce_taskbar();
|
void introduce_taskbar();
|
||||||
|
void inject_externallinks_blocker();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_verbose;
|
bool m_verbose;
|
||||||
|
@ -75,6 +76,7 @@ class Response {
|
||||||
int m_returnCode;
|
int m_returnCode;
|
||||||
bool m_withTaskbar;
|
bool m_withTaskbar;
|
||||||
bool m_withLibraryButton;
|
bool m_withLibraryButton;
|
||||||
|
bool m_blockExternalLinks;
|
||||||
bool m_useCache;
|
bool m_useCache;
|
||||||
bool m_compress;
|
bool m_compress;
|
||||||
bool m_addTaskbar;
|
bool m_addTaskbar;
|
||||||
|
|
|
@ -85,6 +85,12 @@ Java_org_kiwix_kiwixlib_JNIKiwixServer_setTaskbar(JNIEnv* env, jobject obj, jboo
|
||||||
SERVER->setTaskbar(withTaskbar, withLibraryButton);
|
SERVER->setTaskbar(withTaskbar, withLibraryButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_kiwix_kiwixlib_JNIKiwixServer_setBlockExternalLinks(JNIEnv* env, jobject obj, jboolean blockExternalLinks)
|
||||||
|
{
|
||||||
|
SERVER->setBlockExternalLinks(blockExternalLinks);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_start(JNIEnv* env, jobject obj)
|
Java_org_kiwix_kiwixlib_JNIKiwixServer_start(JNIEnv* env, jobject obj)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,6 +34,8 @@ public class JNIKiwixServer
|
||||||
|
|
||||||
public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton);
|
public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton);
|
||||||
|
|
||||||
|
public native void setBlockExternalLinks(boolean blockExternalLinks);
|
||||||
|
|
||||||
public native boolean start();
|
public native boolean start();
|
||||||
|
|
||||||
public native void stop();
|
public native void stop();
|
||||||
|
|
|
@ -20,6 +20,7 @@ skin/jquery-ui/jquery-ui.min.css
|
||||||
skin/caret.png
|
skin/caret.png
|
||||||
skin/taskbar.js
|
skin/taskbar.js
|
||||||
skin/taskbar.css
|
skin/taskbar.css
|
||||||
|
skin/block_external.js
|
||||||
templates/search_result.html
|
templates/search_result.html
|
||||||
templates/no_search_result.html
|
templates/no_search_result.html
|
||||||
templates/404.html
|
templates/404.html
|
||||||
|
@ -28,4 +29,6 @@ templates/index.html
|
||||||
templates/suggestion.json
|
templates/suggestion.json
|
||||||
templates/head_part.html
|
templates/head_part.html
|
||||||
templates/taskbar_part.html
|
templates/taskbar_part.html
|
||||||
|
templates/external_blocker_part.html
|
||||||
|
templates/captured_external.html
|
||||||
opensearchdescription.xml
|
opensearchdescription.xml
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
var block_path = "/catch/external";
|
||||||
|
// called only on external links
|
||||||
|
function capture_event(e) { e.target.setAttribute("href", encodeURI(block_path + "?source=" + e.target.href)); }
|
||||||
|
|
||||||
|
// called on all link clicks. filters external and call capture_event
|
||||||
|
function on_click_event(e) {
|
||||||
|
if ("target" in e && "href" in e.target) {
|
||||||
|
var href = e.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);
|
||||||
|
if (href.substr(0, 5) == "http:")
|
||||||
|
return capture_event(e);
|
||||||
|
if (href.substr(0, 6) == "https:")
|
||||||
|
return capture_event(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// script entrypoint (called on document ready)
|
||||||
|
function run() { live('a', 'click', on_click_event); }
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>External link blocked</title>
|
||||||
|
</head>
|
||||||
|
<body class="kiwix">
|
||||||
|
<h1>External link blocked</h1>
|
||||||
|
<p>This instance of Kiwix protects you from accidentaly going to external (out-of ZIM) links.</p>
|
||||||
|
<p>If you intend to go to such locations, please click the link below.</p>
|
||||||
|
<p><a href="{{ source }}">Go to {{ source }}</a></p>
|
||||||
|
<div id="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
<script type="text/javascript" src="{{root}}/skin/block_external.js"></script>
|
Loading…
Reference in New Issue