From 95482c78a51ca0940c4c5fbee4e9aef157397b7f Mon Sep 17 00:00:00 2001 From: Oliver Giles Date: Fri, 29 Mar 2019 22:43:16 +0300 Subject: [PATCH] resolves #80: reverse-proxy with custom base URL Fix all hrefs and vue routes to correctly operate against the tag. Add a configuration parameter to override the content of the href attribute, and describe its use. --- CMakeLists.txt | 9 ++++++-- UserManual.md | 2 ++ etc/laminar.conf | 10 +++++++++ src/resources.cpp | 47 +++++++++++++++++++++++++++++++++++++++- src/resources.h | 5 +++-- src/resources/index.html | 19 ++++++++-------- src/resources/js/app.js | 11 ++++------ 7 files changed, 82 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 159da3a..cf73a1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ ### -### Copyright 2015-2018 Oliver Giles +### Copyright 2015-2019 Oliver Giles ### ### This file is part of Laminar ### @@ -64,6 +64,11 @@ add_custom_command(OUTPUT laminar.capnp.c++ laminar.capnp.h generate_compressed_bins(${CMAKE_SOURCE_DIR}/src/resources index.html js/app.js favicon.ico favicon-152.png icon.png) +# The code that allows dynamic modifying of index.html requires knowing its original size +add_custom_command(OUTPUT index_html_size.h + COMMAND sh -c '( echo -n "\\#define INDEX_HTML_UNCOMPRESSED_SIZE " && wc -c < "${CMAKE_SOURCE_DIR}/src/resources/index.html" ) > index_html_size.h' + DEPENDS src/resources/index.html) + # Download 3rd-party frontend JS libs... file(DOWNLOAD https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js js/vue.min.js EXPECTED_MD5 ae2fca1cfa0e31377819b1b0ffef704c) @@ -82,7 +87,7 @@ generate_compressed_bins(${CMAKE_BINARY_DIR} js/vue-router.min.js js/vue.min.js ## Server add_executable(laminard src/database.cpp src/main.cpp src/server.cpp src/laminar.cpp - src/conf.cpp src/resources.cpp src/run.cpp laminar.capnp.c++ ${COMPRESSED_BINS}) + src/conf.cpp src/resources.cpp src/run.cpp laminar.capnp.c++ ${COMPRESSED_BINS} index_html_size.h) target_link_libraries(laminard capnp-rpc capnp kj-http kj-async kj pthread sqlite3 z) ## Client diff --git a/UserManual.md b/UserManual.md index 79580ff..39b6f7b 100644 --- a/UserManual.md +++ b/UserManual.md @@ -69,6 +69,8 @@ For Apache, see [Apache Reverse Proxy](https://httpd.apache.org/docs/2.4/howto/r If you use [artefacts](#Archiving-artefacts), note that Laminar is not designed as a file server, and better performance will be achieved by allowing the frontend web server to directly serve the archive directory directly (e.g. using a `Location` directive). +If you use a reverse proxy to host Laminar at a subfolder instead of a subdomain root, the `` needs to be updated to ensure all links point to their proper targets. This can be done by setting `LAMINAR_BASE_URL` in `/etc/laminar.conf`. + ## Set the page title Change `LAMINAR_TITLE` in `/etc/laminar.conf` to your preferred page title. For further WebUI customization, consider using a [custom style sheet](#Customizing-the-WebUI). diff --git a/etc/laminar.conf b/etc/laminar.conf index 9aa2701..2634d94 100644 --- a/etc/laminar.conf +++ b/etc/laminar.conf @@ -43,6 +43,16 @@ ### #LAMINAR_KEEP_RUNDIRS=0 + +### +### LAMINAR_BASE_URL +### +### Base url for the frontend. This affects the tag and needs +### to be set if Laminar runs behind a reverse-proxy that hosts Laminar +### within a subfolder (rather than at a subdomain root) +### +#LAMINAR_BASE_URL=/ + ### ### LAMINAR_ARCHIVE_URL ### diff --git a/src/resources.cpp b/src/resources.cpp index e7bd45d..60c1c2c 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -1,5 +1,5 @@ /// -/// Copyright 2015-2017 Oliver Giles +/// Copyright 2015-2019 Oliver Giles /// /// This file is part of Laminar /// @@ -17,7 +17,10 @@ /// along with Laminar. If not, see /// #include "resources.h" +#include "log.h" +#include "index_html_size.h" #include +#include #define INIT_RESOURCE(route, name, content_type) \ extern const char _binary_##name##_z_start[];\ @@ -30,6 +33,8 @@ #define CONTENT_TYPE_JS "application/javascript; charset=utf-8" #define CONTENT_TYPE_CSS "text/css; charset=utf-8" +#define GZIP_FORMAT 16 + Resources::Resources() { INIT_RESOURCE("/", index_html, CONTENT_TYPE_HTML); @@ -43,6 +48,46 @@ Resources::Resources() INIT_RESOURCE("/js/ansi_up.js", js_ansi_up_js, CONTENT_TYPE_JS); INIT_RESOURCE("/js/Chart.min.js", js_Chart_min_js, CONTENT_TYPE_JS); INIT_RESOURCE("/css/bootstrap.min.css", css_bootstrap_min_css, CONTENT_TYPE_CSS); + + if(const char* baseUrl = getenv("LAMINAR_BASE_URL")) { + // The administrator needs to customize the . Unfortunately this seems + // to be the only thing that needs to be customizable but cannot be done via dynamic + // DOM manipulation without heavy compromises. So replace the static char array with + // a modified buffer accordingly. + z_stream strm; + memset(&strm, 0, sizeof(z_stream)); + std::string tmp; + tmp.resize(INDEX_HTML_UNCOMPRESSED_SIZE); + // inflate + inflateInit2(&strm, MAX_WBITS|GZIP_FORMAT); + strm.next_in = (unsigned char*) _binary_index_html_z_start; + strm.avail_in = _binary_index_html_z_end - _binary_index_html_z_start; + strm.next_out = (unsigned char*) tmp.data(); + strm.avail_out = INDEX_HTML_UNCOMPRESSED_SIZE; + if(inflate(&strm, Z_FINISH) != Z_STREAM_END) { + LLOG(FATAL, "Failed to uncompress index_html"); + } + // replace + // There's no validation on the replacement string, so you can completely mangle + // the html if you like. This isn't really an issue because if you can modify laminar's + // environment you already have elevated permissions + if(auto it = tmp.find("base href=\"/")) + tmp.replace(it+11, 1, baseUrl); + // deflate + index_html.resize(tmp.size()); + deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS|GZIP_FORMAT, 8, Z_DEFAULT_STRATEGY); + strm.next_in = (unsigned char*) tmp.data(); + strm.avail_in = tmp.size(); + strm.next_out = (unsigned char*) index_html.data(); + strm.avail_out = tmp.size(); + if(deflate(&strm, Z_FINISH) != Z_STREAM_END) { + LLOG(FATAL, "Failed to compress index.html"); + } + index_html.resize(strm.total_out); + // update resource map + resources["/"].start = index_html.data(); + resources["/"].end = index_html.data() + index_html.size(); + } } inline bool beginsWith(std::string haystack, const char* needle) { diff --git a/src/resources.h b/src/resources.h index 969a228..14ef6de 100644 --- a/src/resources.h +++ b/src/resources.h @@ -1,5 +1,5 @@ /// -/// Copyright 2015-2017 Oliver Giles +/// Copyright 2015-2019 Oliver Giles /// /// This file is part of Laminar /// @@ -40,7 +40,8 @@ private: const char* end; const char* content_type; }; - std::unordered_map resources; + std::unordered_map resources; + std::string index_html; }; #endif // LAMINAR_RESOURCES_H_ diff --git a/src/resources/index.html b/src/resources/index.html index 8b211fd..5041bd2 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -6,15 +6,16 @@ - + + Laminar - - - - - - - + + + + + + +