diff --git a/pixelapi/file.go b/pixelapi/file.go index 2fd7343..f584520 100644 --- a/pixelapi/file.go +++ b/pixelapi/file.go @@ -24,6 +24,7 @@ type FileInfo struct { MimeType string `json:"mime_type"` MimeImage string `json:"mime_image"` ThumbnailHREF string `json:"thumbnail_href"` + Availability string `json:"availability"` } // GetFileInfo gets the FileInfo from the pixeldrain API diff --git a/res/static/script/Toolbar.js b/res/static/script/Toolbar.js index e6c9dda..0e73270 100644 --- a/res/static/script/Toolbar.js +++ b/res/static/script/Toolbar.js @@ -30,7 +30,56 @@ var Toolbar = { } }, download: function () { - document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile + "?download"; + var triggerDL = function(){ + document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile + "?download"; + } + + if (captchaKey === "a"){ + // If the server doesn't support captcha there's no use in checking + // availability + triggerDL(); + return; + } + + $.getJSON( + apiEndpoint + "/file/" + Viewer.currentFile + "/availability" + ).done(function(data){ + if(data.success === true){ + // Downloading is allowed, start the download + triggerDL(); + } + }).fail(function(data){ + if(data.responseJSON.success === false) { + var popupDiv = document.getElementById("captcha_popup"); + var popupTitle = document.getElementById("captcha_popup_title"); + var popupContent = document.getElementById("captcha_popup_content"); + var popupCaptcha = document.getElementById("captcha_popup_captcha"); + + if(data.responseJSON.value === "file_rate_limited_captcha_required") { + popupTitle.innerText = "Rate limiting enabled!"; + popupContent.innerText = "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(data.responseJSON.value === "virus_detected_captcha_required"){ + popupTitle.innerText = "Malware warning!"; + popupContent.innerText = "According to our scanning "+ + "systems this file may contain a virus of type '"+ + data.responseJSON.extra+"'. You can continue "+ + "downloading this file at your own risk, but you will "+ + "have to prove that you're a human first."; + } + + // Load the recaptcha script with a load function + $.getScript("https://www.google.com/recaptcha/api.js?onload=loadCaptcha&render=explicit"); + + popupDiv.style.opacity = "1"; + popupDiv.style.visibility = "visible"; + }else{ + // No JSON, try download anyway + triggerDL(); + } + }); }, downloadList: function(){ if(!Viewer.isList){ @@ -97,6 +146,23 @@ function copyText(text) { return success; } +function loadCaptcha(){ + grecaptcha.render("captcha_popup_captcha", { + sitekey: captchaKey, + theme: "dark", + callback: function(token){ + document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile + + "?download&recaptcha_response="+token; + + setTimeout(function(){ + var popupDiv = document.getElementById("captcha_popup"); + popupDiv.style.opacity = "0"; + popupDiv.style.visibility = "hidden"; + }, 1000) + } + }); +} + var DetailsWindow = { visible: false, popupDiv: document.getElementById("info_popup"), diff --git a/res/static/style/layout.css b/res/static/style/layout.css index 90ee469..236b09f 100644 --- a/res/static/style/layout.css +++ b/res/static/style/layout.css @@ -103,12 +103,13 @@ body{ font-family: "Lato Thin", sans-serif; font-weight: bold; font-size: 1.8em; - transition: box-shadow 0.5s; + transition: box-shadow 2s; } .navigation a:hover { background: linear-gradient(var(--highlight_color), var(--highlight_color_dark)); box-shadow: var(--highlight_border), 2px 2px 8px var(--shadow_color); color: var(--highlight_text_color); + transition: box-shadow 0.5s; text-decoration: none; } .navigation .icon { diff --git a/res/static/style/viewer.css b/res/static/style/viewer.css index ba2fba1..e0909fd 100644 --- a/res/static/style/viewer.css +++ b/res/static/style/viewer.css @@ -190,7 +190,6 @@ body{ /* ===================== || MISC COMPONENTS || ===================== */ - .full_popup{ position: fixed; visibility: hidden; @@ -208,6 +207,28 @@ body{ box-shadow: var(--shadow_color) 0px 0px 50px; z-index: 100; } +.captcha_popup{ + position: fixed; + visibility: hidden; + opacity: 0; + transition: visibility 1s, opacity 1s, left 1s; + background-color: var(--background_color); + border-color: var(--accent_color_dark_border); + height: auto; + width: 450px; + max-width: 100%; + top: 10%; + left: 50%; + transform: translate(-50%, -10%); + padding: 0 10px; + box-sizing: border-box; + text-align: left; + box-shadow: var(--shadow_color) 0px 0px 50px; + z-index: 101; +} +#captcha_popup_captcha > div { + display: inline-block; +} table {width: auto !important;} table > tbody > tr {border: none !important;} diff --git a/res/template/file_viewer.html b/res/template/file_viewer.html index 99e1ea1..336c779 100644 --- a/res/template/file_viewer.html +++ b/res/template/file_viewer.html @@ -29,7 +29,10 @@ - + @@ -163,6 +166,12 @@
+
+
+
+
+
+
diff --git a/res/template/home.html b/res/template/home.html index 8b3ac04..a1993fa 100644 --- a/res/template/home.html +++ b/res/template/home.html @@ -61,24 +61,36 @@ nobody will see it.

-

What cookies does pixeldrain use?

+

Does pixeldrain cost any money?

- When uploading a file pixeldrain will install a cookie named - 'pduploads'. This cookie keeps a dot-separated list of all files - you have uploaded anonymously in this browser. This cookie is - only used for viewing your upload history. -

-

- When logging in to a pixeldrain account a cookie named - 'pd_auth_key' will be installed. This cookie keeps your login - session active. When you delete it you will be logged out of - your account. -

-

- When you use the style selector at the bottom of this page a - cookie called 'style' will be set. This cookie controls the - appearance of the website for you. + No, pixeldrain is completely free at the moment. While there is + an advertisement on the file downloading page, it doesn't + generate nearly enough revenue to pay for maintaining this + service. That's why I'd really appreciate it if you could spare + some coins. Possible methods for donating are:

+

Do I need to register an account?

@@ -97,25 +109,24 @@ page.

-

Does pixeldrain cost any money?

+

What cookies does pixeldrain use?

- No, pixeldrain is completely free at the moment. While there is - an advertisement on the file downloading page, it doesn't - generate nearly enough revenue to pay for maintaining this - service. That's why I'd really appreciate it if you could spare - some coins. Possible methods for donating are: + When uploading a file pixeldrain will install a cookie named + 'pduploads'. This cookie keeps a dot-separated list of all files + you have uploaded anonymously in this browser. This cookie is + only used for viewing your upload history. +

+

+ When logging in to a pixeldrain account a cookie named + 'pd_auth_key' will be installed. This cookie keeps your login + session active. When you delete it you will be logged out of + your account. +

+

+ When you use the style selector at the bottom of this page a + cookie called 'style' will be set. This cookie controls the + appearance of the website for you.

-

Legality

@@ -139,6 +150,7 @@


+
{{template "footer"}}

diff --git a/webcontroller/file_preview.go b/webcontroller/file_preview.go index ca442da..f473b7e 100644 --- a/webcontroller/file_preview.go +++ b/webcontroller/file_preview.go @@ -187,11 +187,10 @@ seamless="seamless" frameborder="0" allowtransparency="true" func (f filePreview) def() string { return fmt.Sprintf( - `%s
%s
`, + `



%s
Type: '%s'`, + f.APIURL+f.FileInfo.ThumbnailHREF, f.FileInfo.Name, f.FileInfo.MimeType, - f.DownloadURL, - f.APIURL+f.FileInfo.ThumbnailHREF, ) } diff --git a/webcontroller/file_viewer.go b/webcontroller/file_viewer.go index 887816e..7c9b848 100644 --- a/webcontroller/file_viewer.go +++ b/webcontroller/file_viewer.go @@ -12,6 +12,7 @@ import ( type viewerData struct { Type string // file or list + CaptchaKey string APIResponse interface{} } @@ -52,7 +53,8 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request, if list { templateData.Title = fmt.Sprintf("%d files in Pixeldrain", len(finfo)) templateData.Other = viewerData{ - Type: "list", + Type: "list", + CaptchaKey: wc.captchaKey(), APIResponse: map[string]interface{}{ "data": finfo, "date_created": "now", @@ -65,6 +67,7 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request, templateData.Title = fmt.Sprintf("%s ~ Pixeldrain file", finfo[0].Name) templateData.Other = viewerData{ Type: "file", + CaptchaKey: wc.captchaKey(), APIResponse: finfo[0], } } @@ -80,7 +83,8 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request, func (wc *WebController) serveFileViewerDemo(w http.ResponseWriter, r *http.Request) { templateData := wc.newTemplateData(w, r) templateData.Other = viewerData{ - Type: "file", + Type: "file", + CaptchaKey: wc.captchaSiteKey, APIResponse: map[string]interface{}{ "id": "demo", "name": "Demo file", diff --git a/webcontroller/list_viewer.go b/webcontroller/list_viewer.go index c63991c..330bb5f 100644 --- a/webcontroller/list_viewer.go +++ b/webcontroller/list_viewer.go @@ -26,7 +26,8 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request, templateData.Title = fmt.Sprintf("%s ~ Pixeldrain list", list.Title) templateData.OGData = OpenGraphFromList(*list) templateData.Other = viewerData{ - Type: "list", + Type: "list", + CaptchaKey: wc.captchaSiteKey, APIResponse: map[string]interface{}{ "id": list.ID, "data": list.Files, diff --git a/webcontroller/template_data.go b/webcontroller/template_data.go index d89de2f..e60c614 100644 --- a/webcontroller/template_data.go +++ b/webcontroller/template_data.go @@ -44,14 +44,20 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, key) uinf, err := t.PixelAPI.UserInfo() if err != nil { - // This session key doesn't work, delete it + // This session key doesn't work, or the backend is down, user + // cannot be authenticated log.Debug("Session check for key '%s' failed: %s", key, err) - http.SetCookie(w, &http.Cookie{ - Name: "pd_auth_key", - Value: "", - Path: "/", - Expires: time.Unix(0, 0), - }) + + if err.Error() == "authentication_required" || err.Error() == "authentication_failed" { + // This key is invalid, delete it + log.Debug("Deleting invalid API key") + http.SetCookie(w, &http.Cookie{ + Name: "pd_auth_key", + Value: "", + Path: "/", + Expires: time.Unix(0, 0), + }) + } return t } diff --git a/webcontroller/user_account.go b/webcontroller/user_account.go index a76a76b..24b94e4 100644 --- a/webcontroller/user_account.go +++ b/webcontroller/user_account.go @@ -16,23 +16,7 @@ func (wc *WebController) serveRegister( p httprouter.Params, ) { var tpld = wc.newTemplateData(w, r) - - // This only runs on the first request - if wc.captchaSiteKey == "" { - capt, err := tpld.PixelAPI.GetRecaptcha() - if err != nil { - log.Error("Error getting recaptcha key: %s", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - if capt.SiteKey == "" { - wc.captchaSiteKey = "none" - } else { - wc.captchaSiteKey = capt.SiteKey - } - } - - tpld.Other = wc.captchaSiteKey + tpld.Other = wc.captchaKey() err := wc.templates.Get().ExecuteTemplate(w, "register", tpld) if err != nil { diff --git a/webcontroller/user_style.go b/webcontroller/user_style.go index b29e722..afec7ef 100644 --- a/webcontroller/user_style.go +++ b/webcontroller/user_style.go @@ -15,15 +15,14 @@ func userStyle(r *http.Request) (style template.CSS) { switch cookie.Value { case "solarized_dark": selectedStyle = solarizedDarkStyle - break case "maroon": selectedStyle = maroonStyle - break + case "hacker": + selectedStyle = hackerStyle case "default": fallthrough // use default case default: selectedStyle = defaultPixeldrainStyle - break } } @@ -205,3 +204,24 @@ var maroonStyle = pixeldrainStyleSheet{ ShadowSpread: 50, ShadowIntensity: 5, } + +var hackerStyle = pixeldrainStyleSheet{ + TextColor: hsl{0, 0, 1}, + InputColor: hsl{0, 0, .25}, + InputTextColor: hsl{0, 0, 1}, + HighlightColor: hsl{120, 1, .6}, + HighlightTextColor: hsl{0, 0, 0}, + DangerColor: hsl{0, .65, .31}, + DangerColorDark: hsl{0, .64, .23}, + FileBackgroundColor: hsl{120, .8, .06}, + + BackgroundColor: hsl{0, 0, 0}, + BodyColor: hsl{0, 0, 0}, + AccentColorDark: hsl{0, 0, .05}, + AccentColorMedium: hsl{0, 0, .10}, + AccentColorLight: hsl{0, 0, .15}, + + ShadowColor: hsl{120, 1, .1}, + ShadowSpread: 50, + ShadowIntensity: 5, +} diff --git a/webcontroller/web_controller.go b/webcontroller/web_controller.go index 693ba72..3eb69c9 100644 --- a/webcontroller/web_controller.go +++ b/webcontroller/web_controller.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "fornaxian.com/pixeldrain-web/init/conf" + "fornaxian.com/pixeldrain-web/pixelapi" "fornaxian.com/pixeldrain-web/webcontroller/forms" "github.com/Fornaxian/log" "github.com/julienschmidt/httprouter" @@ -176,3 +177,22 @@ func (wc *WebController) getAPIKey(r *http.Request) (key string, err error) { } return "", errors.New("not a valid pixeldrain authentication cookie") } + +func (wc *WebController) captchaKey() string { + // This only runs on the first request + if wc.captchaSiteKey == "" { + var api = pixelapi.New(wc.conf.APIURLInternal, "") + capt, err := api.GetRecaptcha() + if err != nil { + log.Error("Error getting recaptcha key: %s", err) + return "" + } + if capt.SiteKey == "" { + wc.captchaSiteKey = "none" + } else { + wc.captchaSiteKey = capt.SiteKey + } + } + + return wc.captchaSiteKey +}