From f3f101d8049607b7756a1555b9ae811b4393c5d5 Mon Sep 17 00:00:00 2001 From: Wim Brand Date: Thu, 4 Mar 2021 17:10:59 +0100 Subject: [PATCH] Add reverse proxy for local debugging --- init/init.go | 25 +++++-- res/include/script/file_viewer/Toolbar.js | 86 +++++++++++------------ webcontroller/file_viewer.go | 2 +- webcontroller/web_controller.go | 29 ++++++-- 4 files changed, 89 insertions(+), 53 deletions(-) diff --git a/init/init.go b/init/init.go index 7c0964f..e3fb945 100644 --- a/init/init.go +++ b/init/init.go @@ -19,18 +19,32 @@ type PixelWebConfig struct { SessionCookieDomain string `toml:"session_cookie_domain"` ResourceDir string `toml:"resource_dir"` DebugMode bool `toml:"debug_mode"` + ProxyAPIRequests bool `toml:"proxy_api_requests"` MaintenanceMode bool `toml:"maintenance_mode"` } // DefaultConfig is the default configuration for Pixeldrain Web -const DefaultConfig = `# Pixeldrain Web UI server configuration +const DefaultConfig = `## Pixeldrain Web UI server configuration + +# Address used in the browser for making requests directly to the API. Can be +# relative to the current domain name +api_url_external = "/api" + +# Address used to make internal API requests to the backend +api_url_internal = "https://pixeldrain.com/api" -api_url_external = "/api" # Used in the web browser -api_url_internal = "http://127.0.0.1:8080" # Used for internal API requests to the pixeldrain server, not visible to users website_address = "https://pixeldrain.com" -session_cookie_domain = ".pixeldrain.com" +session_cookie_domain = "" resource_dir = "res" -debug_mode = false + +# Parse all the templates every time a request comes in +debug_mode = true + +# Create proxy listeners to forward all requests made to /api to +# api_url_internal +proxy_api_requests = true + +# When this is true every request will return a maintainance HTML page maintenance_mode = false ` @@ -68,5 +82,6 @@ func Init(r *httprouter.Router, prefix string, setLogLevel bool) { webconf.SessionCookieDomain, webconf.MaintenanceMode, webconf.DebugMode, + webconf.ProxyAPIRequests, ) } diff --git a/res/include/script/file_viewer/Toolbar.js b/res/include/script/file_viewer/Toolbar.js index ea547c1..d57a715 100644 --- a/res/include/script/file_viewer/Toolbar.js +++ b/res/include/script/file_viewer/Toolbar.js @@ -1,42 +1,42 @@ function Toolbar(viewer) { - this.viewer = viewer - this.visible = false - this.sharebarVisible = false - this.currentFile = null - this.editWindow = null + this.viewer = viewer + this.visible = false + this.sharebarVisible = false + this.currentFile = null + this.editWindow = null - this.views = 0 - this.downloads = 0 - this.statsWebsocket = null + this.views = 0 + this.downloads = 0 + this.statsWebsocket = null - this.divToolbar = document.getElementById("toolbar") - this.divFilePreview = document.getElementById("filepreview") - this.downloadFrame = document.getElementById("download_frame") - this.spanViews = document.getElementById("stat_views") - this.spanDownloads = document.getElementById("stat_downloads") - this.spanSize = document.getElementById("stat_size") + this.divToolbar = document.getElementById("toolbar") + this.divFilePreview = document.getElementById("filepreview") + this.downloadFrame = document.getElementById("download_frame") + this.spanViews = document.getElementById("stat_views") + this.spanDownloads = document.getElementById("stat_downloads") + this.spanSize = document.getElementById("stat_size") this.btnToggleToolbar = document.getElementById("btn_toggle_toolbar") - this.btnDownload = document.getElementById("btn_download") - this.btnCopyLink = document.getElementById("btn_copy") - this.spanCopyLink = document.querySelector("#btn_copy > span") - this.btnShare = document.getElementById("btn_share") - this.divSharebar = document.getElementById("sharebar") + this.btnDownload = document.getElementById("btn_download") + this.btnCopyLink = document.getElementById("btn_copy") + this.spanCopyLink = document.querySelector("#btn_copy > span") + this.btnShare = document.getElementById("btn_share") + this.divSharebar = document.getElementById("sharebar") this.btnToggleToolbar.addEventListener("click", () => { this.toggle() }) - this.btnDownload.addEventListener("click", () => { this.download() }) - this.btnCopyLink.addEventListener("click", () => { this.copyUrl() }) - this.btnShare.addEventListener("click", () => { this.toggleSharebar() }) + this.btnDownload.addEventListener("click", () => { this.download() }) + this.btnCopyLink.addEventListener("click", () => { this.copyUrl() }) + this.btnShare.addEventListener("click", () => { this.toggleSharebar() }) } -Toolbar.prototype.setFile = function(file) { +Toolbar.prototype.setFile = function (file) { this.currentFile = file this.spanSize.innerText = formatDataVolume(file.size, 3) this.setStats() } -Toolbar.prototype.setStats = function() { +Toolbar.prototype.setStats = function () { let size = this.currentFile.size this.spanViews.innerText = "loading..." @@ -47,19 +47,19 @@ Toolbar.prototype.setStats = function() { } this.statsWebsocket = new WebSocket( - location.origin.replace(/^http/, 'ws')+"/api/file/"+this.currentFile.id+"/stats" + location.origin.replace(/^http/, 'ws') + "/api/file/" + this.currentFile.id + "/stats" ) this.statsWebsocket.onmessage = (msg) => { let j = JSON.parse(msg.data) console.debug("WS update", j) this.views = j.views - this.downloads = Math.round(j.bandwidth/size) + this.downloads = Math.round(j.bandwidth / size) this.spanViews.innerText = formatThousands(this.views) this.spanDownloads.innerText = formatThousands(this.downloads) } this.statsWebsocket.onerror = (err) => { - log.error("WS error", err) + console.error("WS error", err) this.statsWebsocket.close() this.statsWebsocket = null @@ -74,7 +74,7 @@ Toolbar.prototype.setStats = function() { } } -Toolbar.prototype.toggle = function() { +Toolbar.prototype.toggle = function () { if (this.visible) { if (this.sharebarVisible) { this.toggleSharebar() } @@ -90,7 +90,7 @@ Toolbar.prototype.toggle = function() { } } -Toolbar.prototype.toggleSharebar = function() { +Toolbar.prototype.toggleSharebar = function () { if (navigator.share) { navigator.share({ title: this.viewer.title, @@ -100,19 +100,19 @@ Toolbar.prototype.toggleSharebar = function() { return } - if(this.sharebarVisible){ + if (this.sharebarVisible) { this.divSharebar.style.left = "-8em" this.btnShare.classList.remove("button_highlight") this.sharebarVisible = false - }else{ + } else { this.divSharebar.style.left = "8em" this.btnShare.classList.add("button_highlight") this.sharebarVisible = true } } -Toolbar.prototype.download = function() { - if (captchaKey === "none" || captchaKey === ""){ +Toolbar.prototype.download = function () { + if (captchaKey === "none" || captchaKey === "") { console.debug("Server doesn't support captcha, starting download") this.downloadFrame.src = this.currentFile.download_href return @@ -140,7 +140,7 @@ Toolbar.prototype.download = function() { // Set the callback function recaptchaCallback = token => { // Download the file using the recaptcha token - this.downloadFrame.src = this.currentFile.download_href+"&recaptcha_response="+token + this.downloadFrame.src = this.currentFile.download_href + "&recaptcha_response=" + token this.captchaModal.close() } @@ -158,25 +158,25 @@ Toolbar.prototype.download = function() { console.debug("Showing rate limiting captcha") showCaptcha( "Rate limiting enabled!", - "This file is using a suspicious amount of bandwidth relative "+ - "to its popularity. To continue downloading this file you "+ + "This file is using a suspicious amount of bandwidth relative " + + "to its popularity. To continue downloading this file you " + "will have to prove that you're a human first.", ) } else if (this.currentFile.availability === "virus_detected_captcha_required") { console.debug("Showing virus captcha") showCaptcha( "Malware warning!", - "According to our scanning systems this file may contain a "+ - "virus of type '"+this.currentFile.availability_name+"'. You "+ - "can continue downloading this file at your own risk, but you "+ + "According to our scanning systems this file may contain a " + + "virus of type '" + this.currentFile.availability_name + "'. You " + + "can continue downloading this file at your own risk, but you " + "will have to prove that you're a human first.", ) } } } -Toolbar.prototype.copyUrl = function() { - if(copyText(window.location.href)) { +Toolbar.prototype.copyUrl = function () { + if (copyText(window.location.href)) { console.log('Text copied') this.spanCopyLink.innerText = "Copied!" this.btnCopyLink.classList.add("button_highlight") @@ -194,9 +194,9 @@ Toolbar.prototype.copyUrl = function() { } // Called by the google recaptcha script -let recaptchaElement = null +let recaptchaElement = null let recaptchaCallback = null -function loadCaptcha(){ +function loadCaptcha() { grecaptcha.render(recaptchaElement, { sitekey: captchaKey, theme: "dark", diff --git a/webcontroller/file_viewer.go b/webcontroller/file_viewer.go index bf08685..5e1aa50 100644 --- a/webcontroller/file_viewer.go +++ b/webcontroller/file_viewer.go @@ -19,7 +19,7 @@ import ( func (wc *WebController) viewTokenOrBust() (t string) { var err error - if t, err = wc.api.GetMiscViewToken(); err != nil { + if t, err = wc.api.GetMiscViewToken(); err != nil && !wc.proxyAPIRequests { log.Error("Could not get viewtoken: %s", err) } return t diff --git a/webcontroller/web_controller.go b/webcontroller/web_controller.go index d2f3781..a098c11 100644 --- a/webcontroller/web_controller.go +++ b/webcontroller/web_controller.go @@ -6,6 +6,8 @@ import ( "fmt" "html/template" "net/http" + "net/http/httputil" + "net/url" "os" "strings" "time" @@ -26,11 +28,11 @@ type WebController struct { hostname string - apiURLInternal string - apiURLExternal string - websiteAddress string - + apiURLInternal string + apiURLExternal string + websiteAddress string sessionCookieDomain string + proxyAPIRequests bool // page-specific variables captchaSiteKey string @@ -55,6 +57,7 @@ func New( sessionCookieDomain string, maintenanceMode bool, debugMode bool, + proxyAPIRequests bool, ) (wc *WebController) { var err error wc = &WebController{ @@ -63,6 +66,7 @@ func New( apiURLExternal: apiURLExternal, websiteAddress: websiteAddress, sessionCookieDomain: sessionCookieDomain, + proxyAPIRequests: proxyAPIRequests, httpClient: &http.Client{Timeout: time.Minute * 10}, api: apiclient.New(apiURLInternal), } @@ -96,6 +100,23 @@ func New( return wc } + if proxyAPIRequests { + proxPath := strings.TrimSuffix(apiURLInternal, "/api") + proxURL, err := url.Parse(proxPath) + if err != nil { + panic(fmt.Errorf("failed to parse reverse proxy URL '%s': %w", proxPath, err)) + } + + var prox = httputil.NewSingleHostReverseProxy(proxURL) + prox.Transport = wc.httpClient.Transport + r.Handler("OPTIONS", "/api/*p", prox) + r.Handler("POST", "/api/*p", prox) + r.Handler("GET", "/api/*p", prox) + r.Handler("PUT", "/api/*p", prox) + r.Handler("PATCH", "/api/*p", prox) + r.Handler("DELETE", "/api/*p", prox) + } + r.NotFound = http.HandlerFunc(wc.serveNotFound) // Request method shorthands. These help keep the array of handlers aligned