From 8f3108148b53f078fb19e0d2c9ac04acba8f7c8d Mon Sep 17 00:00:00 2001
From: mutantmonkey
Date: Sat, 26 Jan 2019 10:04:32 +0000
Subject: [PATCH] Add option to force random filenames (fixes #86) (#159)
---
README.md | 1 +
pages.go | 11 ++-
server.go | 3 +
server_test.go | 75 +++++++++++++++++++
static/js/upload.js | 175 ++++++++++++++++++++++---------------------
templates/API.html | 14 ++--
templates/index.html | 2 +-
templates/paste.html | 2 +-
upload.go | 29 +++++--
9 files changed, 209 insertions(+), 103 deletions(-)
diff --git a/README.md b/README.md
index 8b55687..bd1d9eb 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ allowhotlink = true
- ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN")
- ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...)
- ```-nologs``` -- (optionally) disable request logs in stdout
+- ```-force-random-filename``` -- (optionally) force the use of random filenames
#### Storage backends
The following storage backends are available:
diff --git a/pages.go b/pages.go
index bb38f37..6fcc934 100644
--- a/pages.go
+++ b/pages.go
@@ -21,8 +21,9 @@ const (
func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["index.html"], pongo2.Context{
- "maxsize": Config.maxSize,
- "expirylist": listExpirationTimes(),
+ "maxsize": Config.maxSize,
+ "expirylist": listExpirationTimes(),
+ "forcerandom": Config.forceRandomFilename,
}, r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -31,7 +32,8 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["paste.html"], pongo2.Context{
- "expirylist": listExpirationTimes(),
+ "expirylist": listExpirationTimes(),
+ "forcerandom": Config.forceRandomFilename,
}, r, w)
if err != nil {
oopsHandler(c, w, r, RespHTML, "")
@@ -40,7 +42,8 @@ func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) {
func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["API.html"], pongo2.Context{
- "siteurl": getSiteURL(r),
+ "siteurl": getSiteURL(r),
+ "forcerandom": Config.forceRandomFilename,
}, r, w)
if err != nil {
oopsHandler(c, w, r, RespHTML, "")
diff --git a/server.go b/server.go
index 7883a07..e4e1661 100644
--- a/server.go
+++ b/server.go
@@ -65,6 +65,7 @@ var Config struct {
s3Region string
s3Bucket string
s3ForcePathStyle bool
+ forceRandomFilename bool
}
var Templates = make(map[string]*pongo2.Template)
@@ -268,6 +269,8 @@ func main() {
"S3 bucket to use for files and metadata")
flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false,
"Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)")
+ flag.BoolVar(&Config.forceRandomFilename, "force-random-filename", false,
+ "Force all uploads to use a random filename")
iniflags.Parse()
diff --git a/server_test.go b/server_test.go
index a1ec853..fc225ce 100644
--- a/server_test.go
+++ b/server_test.go
@@ -763,6 +763,32 @@ func TestPutRandomizedUpload(t *testing.T) {
}
}
+func TestPutForceRandomUpload(t *testing.T) {
+ mux := setup()
+ w := httptest.NewRecorder()
+
+ oldFRF := Config.forceRandomFilename
+ Config.forceRandomFilename = true
+ filename := "randomizeme.file"
+
+ req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // while this should also work without this header, let's try to force
+ // the randomized filename off to be sure
+ req.Header.Set("Linx-Randomize", "no")
+
+ mux.ServeHTTP(w, req)
+
+ if w.Body.String() == Config.siteURL+filename {
+ t.Fatal("Filename was not random")
+ }
+
+ Config.forceRandomFilename = oldFRF
+}
+
func TestPutNoExtensionUpload(t *testing.T) {
mux := setup()
w := httptest.NewRecorder()
@@ -1013,6 +1039,55 @@ func TestPutAndOverwrite(t *testing.T) {
}
}
+func TestPutAndOverwriteForceRandom(t *testing.T) {
+ var myjson RespOkJSON
+
+ mux := setup()
+ w := httptest.NewRecorder()
+
+ oldFRF := Config.forceRandomFilename
+ Config.forceRandomFilename = true
+
+ req, err := http.NewRequest("PUT", "/upload", strings.NewReader("File content"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ req.Header.Set("Accept", "application/json")
+
+ mux.ServeHTTP(w, req)
+
+ err = json.Unmarshal([]byte(w.Body.String()), &myjson)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Overwrite it
+ w = httptest.NewRecorder()
+ req, err = http.NewRequest("PUT", "/upload/"+myjson.Filename, strings.NewReader("New file content"))
+ req.Header.Set("Linx-Delete-Key", myjson.Delete_Key)
+ mux.ServeHTTP(w, req)
+
+ if w.Code != 200 {
+ t.Fatal("Status code was not 200, but " + strconv.Itoa(w.Code))
+ }
+
+ // Make sure it's the new file
+ w = httptest.NewRecorder()
+ req, err = http.NewRequest("GET", "/"+Config.selifPath+myjson.Filename, nil)
+ mux.ServeHTTP(w, req)
+
+ if w.Code == 404 {
+ t.Fatal("Status code was 404")
+ }
+
+ if w.Body.String() != "New file content" {
+ t.Fatal("File did not contain 'New file content")
+ }
+
+ Config.forceRandomFilename = oldFRF
+}
+
func TestPutAndSpecificDelete(t *testing.T) {
var myjson RespOkJSON
diff --git a/static/js/upload.js b/static/js/upload.js
index 159bad2..125123c 100644
--- a/static/js/upload.js
+++ b/static/js/upload.js
@@ -1,51 +1,54 @@
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
Dropzone.options.dropzone = {
- init: function() {
- var dzone = document.getElementById("dzone");
- dzone.style.display = "block";
- },
- addedfile: function(file) {
- var upload = document.createElement("div");
- upload.className = "upload";
+ init: function() {
+ var dzone = document.getElementById("dzone");
+ dzone.style.display = "block";
+ },
+ addedfile: function(file) {
+ var upload = document.createElement("div");
+ upload.className = "upload";
- var fileLabel = document.createElement("span");
- fileLabel.innerHTML = file.name;
- file.fileLabel = fileLabel;
- upload.appendChild(fileLabel);
+ var fileLabel = document.createElement("span");
+ fileLabel.innerHTML = file.name;
+ file.fileLabel = fileLabel;
+ upload.appendChild(fileLabel);
- var fileActions = document.createElement("div");
- fileActions.className = "right";
- file.fileActions = fileActions;
- upload.appendChild(fileActions);
+ var fileActions = document.createElement("div");
+ fileActions.className = "right";
+ file.fileActions = fileActions;
+ upload.appendChild(fileActions);
- var cancelAction = document.createElement("span");
- cancelAction.className = "cancel";
- cancelAction.innerHTML = "Cancel";
- cancelAction.addEventListener('click', function(ev) {
- this.removeFile(file);
- }.bind(this));
- file.cancelActionElement = cancelAction;
- fileActions.appendChild(cancelAction);
+ var cancelAction = document.createElement("span");
+ cancelAction.className = "cancel";
+ cancelAction.innerHTML = "Cancel";
+ cancelAction.addEventListener('click', function(ev) {
+ this.removeFile(file);
+ }.bind(this));
+ file.cancelActionElement = cancelAction;
+ fileActions.appendChild(cancelAction);
- var progress = document.createElement("span");
- file.progressElement = progress;
- fileActions.appendChild(progress);
+ var progress = document.createElement("span");
+ file.progressElement = progress;
+ fileActions.appendChild(progress);
- file.uploadElement = upload;
+ file.uploadElement = upload;
- document.getElementById("uploads").appendChild(upload);
- },
- uploadprogress: function(file, p, bytesSent) {
- p = parseInt(p);
- file.progressElement.innerHTML = p + "%";
- file.uploadElement.setAttribute("style", 'background-image: -webkit-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -moz-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -ms-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -o-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%)');
- },
- sending: function(file, xhr, formData) {
- formData.append("randomize", document.getElementById("randomize").checked);
- formData.append("expires", document.getElementById("expires").value);
- },
- success: function(file, resp) {
+ document.getElementById("uploads").appendChild(upload);
+ },
+ uploadprogress: function(file, p, bytesSent) {
+ p = parseInt(p);
+ file.progressElement.innerHTML = p + "%";
+ file.uploadElement.setAttribute("style", 'background-image: -webkit-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -moz-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -ms-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -o-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%)');
+ },
+ sending: function(file, xhr, formData) {
+ var randomize = document.getElementById("randomize");
+ if(randomize != null) {
+ formData.append("randomize", randomize.checked);
+ }
+ formData.append("expires", document.getElementById("expires").value);
+ },
+ success: function(file, resp) {
file.fileActions.removeChild(file.progressElement);
var fileLabelLink = document.createElement("a");
@@ -59,61 +62,61 @@ Dropzone.options.dropzone = {
var deleteAction = document.createElement("span");
deleteAction.innerHTML = "Delete";
deleteAction.className = "cancel";
- deleteAction.addEventListener('click', function(ev) {
- xhr = new XMLHttpRequest();
- xhr.open("DELETE", resp.url, true);
- xhr.setRequestHeader("Linx-Delete-Key", resp.delete_key);
- xhr.onreadystatechange = function(file) {
- if (xhr.readyState == 4 && xhr.status === 200) {
- var text = document.createTextNode("Deleted ");
- file.fileLabel.insertBefore(text, file.fileLabelLink);
- file.fileLabel.className = "deleted";
- file.fileActions.removeChild(file.cancelActionElement);
- }
- }.bind(this, file);
- xhr.send();
- });
- file.fileActions.removeChild(file.cancelActionElement);
- file.cancelActionElement = deleteAction;
- file.fileActions.appendChild(deleteAction);
- },
- error: function(file, resp, xhrO) {
+ deleteAction.addEventListener('click', function(ev) {
+ xhr = new XMLHttpRequest();
+ xhr.open("DELETE", resp.url, true);
+ xhr.setRequestHeader("Linx-Delete-Key", resp.delete_key);
+ xhr.onreadystatechange = function(file) {
+ if (xhr.readyState == 4 && xhr.status === 200) {
+ var text = document.createTextNode("Deleted ");
+ file.fileLabel.insertBefore(text, file.fileLabelLink);
+ file.fileLabel.className = "deleted";
+ file.fileActions.removeChild(file.cancelActionElement);
+ }
+ }.bind(this, file);
+ xhr.send();
+ });
+ file.fileActions.removeChild(file.cancelActionElement);
+ file.cancelActionElement = deleteAction;
+ file.fileActions.appendChild(deleteAction);
+ },
+ error: function(file, resp, xhrO) {
file.fileActions.removeChild(file.cancelActionElement);
file.fileActions.removeChild(file.progressElement);
- if (file.status === "canceled") {
- file.fileLabel.innerHTML = file.name + ": Canceled ";
- }
- else {
- if (resp.error) {
- file.fileLabel.innerHTML = file.name + ": " + resp.error;
- }
- else if (resp.includes("Optional headers with the request
+{% if not forcerandom %}
Randomize the filename
Linx-Randomize: yes
+{% endif %}
Specify a custom deletion key
Linx-Delete-Key: mysecret
@@ -56,30 +58,30 @@
{% if using_auth %}
$ curl -H "Linx-Api-Key: mysecretkey" -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %}
{% else %}
$ curl -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}wtq7pan.jpg{% endif %}
{% endif %}
Uploading myphoto.jpg with an expiry of 20 minutes
{% if using_auth %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}jm295snf.jpg{% endif %}
{% else %}
$ curl -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}1doym9u2.jpg{% endif %}
{% endif %}
Uploading myphoto.jpg with a random filename and getting a json response:
{% if using_auth %}
- $ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/
+ $ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json"{% if not forcerandom %} -H "Linx-Randomize: yes"{% endif %} -T myphoto.jpg {{ siteurl }}upload/
{"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
"sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% else %}
- $ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/
+ $ curl -H "Accept: application/json"{% if not forcerandom %} -H "Linx-Randomize: yes"{% endif %} -T myphoto.jpg {{ siteurl }}upload/
{"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
"sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% endif %}
diff --git a/templates/index.html b/templates/index.html
index d423879..2843109 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -17,7 +17,7 @@
-
+