From 0ad8bf45fc9237a4ba4f199943b25f5925736dee Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Thu, 26 Mar 2020 12:06:36 +0000 Subject: [PATCH 1/5] Add external links blocking in serve In many use cases, it is not wanted to have user accidentaly click on external links and leave the served ZIM content. This could be because the result is unpredictible (reader not implementing this properly) or because the serve user knows there's no backup internet connexion or because there is an induced cost behind external links that doesn't affect served content. using a new flag (`blockExternalLinks`) on `Response`/`setTaskBar`, a piece of JS code is injected into the taskbar code. This code adds a JS handler on all link click events and verifies the destination. If the destination appears to be an external link (1), the link target is changed to a specific URL: ``` /external?source= ``` (1) external is a link that's not on the same origin and starts with either `http:` `https:` or `//`. Server implements a new handler on `/external` that displays a new page (`captured_external.html`) which returns a generic message explaining the situation and offering to click on the link again should the user really want to. This is done by specifically asking `set_taskbar` to not block external requests on that page. This approach allows integrators using a reverse proxy to handle that endpoint differently (rebrand it) 1. `Server` now has an `m_blockExternalLinks` defaulting to `false` 1. `Server.setTaskbar` is extended to support an additional bool to set the variable. 1. `Response` now has an `m_blockExternalLinks` 1. `Response` constr expects an additional bool for `blockExternalLinks`. 1. `Response.set_taskbar` is extended to support an additional bool to set the variable. 1. JNI/Java Wrapper reflects the extensions. 1. New resource file `templates/block_external.js` (included in head_part). Should it be in skin? 1. New resource file `templates/captured_external.html` for `handle_captured_external()` 1. Added a comment on `head_part.html` to help with JS insertion at the right place 1. `introduce_taskbar()` conditionnaly inserts the JS inside the taskbar --- include/server.h | 5 ++- src/server.cpp | 39 ++++++++++++++++--- src/server/response.cpp | 14 ++++++- src/server/response.h | 6 ++- src/wrapper/java/kiwixserver.cpp | 6 +++ .../org/kiwix/kiwixlib/JNIKiwixServer.java | 1 + static/resources_list.txt | 2 + static/templates/block_external.js | 17 ++++++++ static/templates/captured_external.html | 23 +++++++++++ static/templates/head_part.html | 1 + 10 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 static/templates/block_external.js create mode 100644 static/templates/captured_external.html diff --git a/include/server.h b/include/server.h index 52f014543..5b439c309 100644 --- a/include/server.h +++ b/include/server.h @@ -56,7 +56,9 @@ namespace kiwix void setNbThreads(int threads) { m_nbThreads = threads; } void setVerbose(bool verbose) { m_verbose = verbose; } void setTaskbar(bool withTaskbar, bool withLibraryButton) - { m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; } + { setTaskbar(withTaskbar, withLibraryButton, m_blockExternalLinks); } + void setTaskbar(bool withTaskbar, bool withLibraryButton, bool blockExternalLinks) + { m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; m_blockExternalLinks = blockExternalLinks; } protected: Library* mp_library; @@ -68,6 +70,7 @@ namespace kiwix bool m_verbose = false; bool m_withTaskbar = true; bool m_withLibraryButton = true; + bool m_blockExternalLinks = false; std::unique_ptr mp_server; }; } diff --git a/src/server.cpp b/src/server.cpp index 4af15b92b..50a30748c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -95,7 +95,8 @@ class InternalServer { int nbThreads, bool verbose, bool withTaskbar, - bool withLibraryButton); + bool withLibraryButton, + bool blockExternalLinks); virtual ~InternalServer() = default; int handlerCallback(struct MHD_Connection* connection, @@ -119,6 +120,7 @@ class InternalServer { Response handle_search(const RequestContext& request); Response handle_suggest(const RequestContext& request); Response handle_random(const RequestContext& request); + Response handle_captured_external(const RequestContext& request); Response handle_content(const RequestContext& request); kainjow::mustache::data get_default_data(); @@ -131,6 +133,7 @@ class InternalServer { std::atomic_bool m_verbose; bool m_withTaskbar; bool m_withLibraryButton; + bool m_blockExternalLinks; struct MHD_Daemon* mp_daemon; Library* mp_library; @@ -157,7 +160,8 @@ bool Server::start() { m_nbThreads, m_verbose, m_withTaskbar, - m_withLibraryButton)); + m_withLibraryButton, + m_blockExternalLinks)); return mp_server->start(); } @@ -186,7 +190,8 @@ InternalServer::InternalServer(Library* library, int nbThreads, bool verbose, bool withTaskbar, - bool withLibraryButton) : + bool withLibraryButton, + bool blockExternalLinks) : m_addr(addr), m_port(port), m_root(root), @@ -194,6 +199,7 @@ InternalServer::InternalServer(Library* library, m_verbose(verbose), m_withTaskbar(withTaskbar), m_withLibraryButton(withLibraryButton), + m_blockExternalLinks(blockExternalLinks), mp_daemon(nullptr), mp_library(library), mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper) @@ -340,6 +346,9 @@ Response InternalServer::handle_request(const RequestContext& request) if (request.get_url() == "/random") return handle_random(request); + if (request.get_url() == "/external") + return handle_captured_external(request); + return handle_content(request); } catch (std::exception& e) { fprintf(stderr, "===== Unhandled error : %s\n", e.what()); @@ -359,7 +368,7 @@ kainjow::mustache::data InternalServer::get_default_data() 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; 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_mimeType("text/html"); 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("", "", false); + return response; +} + Response InternalServer::handle_catalog(const RequestContext& request) { if (m_verbose.load()) { diff --git a/src/server/response.cpp b/src/server/response.cpp index c55fd63bb..4344de7e1 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -17,7 +17,7 @@ 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_root(root), m_content(""), @@ -25,6 +25,7 @@ Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool m_returnCode(MHD_HTTP_OK), m_withTaskbar(withTaskbar), m_withLibraryButton(withLibraryButton), + m_blockExternalLinks(blockExternalLinks), m_useCache(false), m_addTaskbar(false), m_bookName(""), @@ -124,6 +125,14 @@ void Response::introduce_taskbar() m_content, "]*>", taskbar_part); + + if ( m_blockExternalLinks ) { + const std::string capture_external_part = getResource("templates/block_external.js"); + m_content = appendToFirstOccurence( + m_content, + "block external links\n", + capture_external_part); + } } @@ -239,11 +248,12 @@ void Response::set_entry(const Entry& entry) { m_mode = ResponseMode::ENTRY; } -void Response::set_taskbar(const std::string& bookName, const std::string& bookTitle) +void Response::set_taskbar(const std::string& bookName, const std::string& bookTitle, bool blockExternalLinks) { m_addTaskbar = true; m_bookName = bookName; m_bookTitle = bookTitle; + m_blockExternalLinks = blockExternalLinks; } diff --git a/src/server/response.h b/src/server/response.h index df372c25f..0340b013a 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -42,7 +42,7 @@ class RequestContext; class Response { 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; int send(const RequestContext& request, MHD_Connection* connection); @@ -57,7 +57,8 @@ class Response { void set_code(int code) { m_returnCode = code; } void set_cache(bool cache) { m_useCache = cache; } void set_compress(bool compress) { m_compress = compress; } - void set_taskbar(const std::string& bookName, const std::string& bookTitle); + void set_taskbar(const std::string& bookName, const std::string& bookTitle) { return set_taskbar(bookName, bookTitle, m_blockExternalLinks); } + void set_taskbar(const std::string& bookName, const std::string& bookTitle, bool blockExternalLinks); void set_range_first(uint64_t start) { m_startRange = start; } void set_range_len(uint64_t len) { m_lenRange = len; } @@ -75,6 +76,7 @@ class Response { int m_returnCode; bool m_withTaskbar; bool m_withLibraryButton; + bool m_blockExternalLinks; bool m_useCache; bool m_compress; bool m_addTaskbar; diff --git a/src/wrapper/java/kiwixserver.cpp b/src/wrapper/java/kiwixserver.cpp index 7242c4cd4..1fb2f360f 100644 --- a/src/wrapper/java/kiwixserver.cpp +++ b/src/wrapper/java/kiwixserver.cpp @@ -85,6 +85,12 @@ Java_org_kiwix_kiwixlib_JNIKiwixServer_setTaskbar(JNIEnv* env, jobject obj, jboo SERVER->setTaskbar(withTaskbar, withLibraryButton); } +JNIEXPORT void JNICALL +Java_org_kiwix_kiwixlib_JNIKiwixServer_setTaskbar(JNIEnv* env, jobject obj, jboolean withTaskbar, jboolean withLibraryButton, jboolean blockExternalLinks) +{ + SERVER->setTaskbar(withTaskbar, withLibraryButton, blockExternalLinks); +} + JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwixServer_start(JNIEnv* env, jobject obj) { diff --git a/src/wrapper/java/org/kiwix/kiwixlib/JNIKiwixServer.java b/src/wrapper/java/org/kiwix/kiwixlib/JNIKiwixServer.java index 578e2f4de..2fc49e449 100644 --- a/src/wrapper/java/org/kiwix/kiwixlib/JNIKiwixServer.java +++ b/src/wrapper/java/org/kiwix/kiwixlib/JNIKiwixServer.java @@ -33,6 +33,7 @@ public class JNIKiwixServer public native void setNbThreads(int nbTreads); public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton); + public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton, boolean blockExternalLinks); public native boolean start(); diff --git a/static/resources_list.txt b/static/resources_list.txt index 607f30fe9..466e2d39d 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -28,4 +28,6 @@ templates/index.html templates/suggestion.json templates/head_part.html templates/taskbar_part.html +templates/captured_external.html +templates/block_external.js opensearchdescription.xml diff --git a/static/templates/block_external.js b/static/templates/block_external.js new file mode 100644 index 000000000..21b005084 --- /dev/null +++ b/static/templates/block_external.js @@ -0,0 +1,17 @@ + function capture(e) { $(e.target).attr("href", encodeURI("/external?source=" + e.target.href)); } + jk( document ).ready(function() { + jk("a").on({click: function(e) { + if ("target" in e && "href" in e.target) { + var href = e.target.href; + if (href.indexOf(window.location.origin) == 0) + return; + if (href.substr(0, 2) == "//") + return capture(e); + if (href.substr(0, 5) == "http:") + return capture(e); + if (href.substr(0, 6) == "https:") + return capture(e); + return; + } + }}); + }); diff --git a/static/templates/captured_external.html b/static/templates/captured_external.html new file mode 100644 index 000000000..016172316 --- /dev/null +++ b/static/templates/captured_external.html @@ -0,0 +1,23 @@ + + + + + Welcome to Kiwix Server + + + + + + + + +

External link blocked

+

This instance of Kiwix protects you from accidentaly going to external (out-of ZIM) links.

+

If you intend to go to such locations, please click the link bellow.

+

Go to {{ source }}

+
+ Powered by Kiwix +
+ + + diff --git a/static/templates/head_part.html b/static/templates/head_part.html index 47b7c7921..1dcebceca 100644 --- a/static/templates/head_part.html +++ b/static/templates/head_part.html @@ -5,6 +5,7 @@ diff --git a/static/templates/head_part.html b/static/templates/head_part.html index 1dcebceca..47b7c7921 100644 --- a/static/templates/head_part.html +++ b/static/templates/head_part.html @@ -5,7 +5,6 @@ - - - - + External link blocked -

External link blocked

This instance of Kiwix protects you from accidentaly going to external (out-of ZIM) links.

If you intend to go to such locations, please click the link below.

Go to {{ source }}

-
- Powered by Kiwix -
- +
Powered by Kiwix