parent
a79bc1898a
commit
8f3108148b
|
@ -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:
|
||||
|
|
11
pages.go
11
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, "")
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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("<html")) {
|
||||
file.fileLabel.innerHTML = file.name + ": Server Error";
|
||||
}
|
||||
else {
|
||||
file.fileLabel.innerHTML = file.name + ": " + resp;
|
||||
}
|
||||
}
|
||||
file.fileLabel.className = "error";
|
||||
},
|
||||
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("<html")) {
|
||||
file.fileLabel.innerHTML = file.name + ": Server Error";
|
||||
}
|
||||
else {
|
||||
file.fileLabel.innerHTML = file.name + ": " + resp;
|
||||
}
|
||||
}
|
||||
file.fileLabel.className = "error";
|
||||
},
|
||||
|
||||
maxFilesize: Math.round(parseInt(document.getElementById("dropzone").getAttribute("data-maxsize"), 10) / 1024 / 1024),
|
||||
previewsContainer: "#uploads",
|
||||
parallelUploads: 5,
|
||||
headers: {"Accept": "application/json"},
|
||||
dictDefaultMessage: "Click or Drop file(s) or Paste image",
|
||||
dictFallbackMessage: ""
|
||||
previewsContainer: "#uploads",
|
||||
parallelUploads: 5,
|
||||
headers: {"Accept": "application/json"},
|
||||
dictDefaultMessage: "Click or Drop file(s) or Paste image",
|
||||
dictFallbackMessage: ""
|
||||
};
|
||||
|
||||
document.onpaste = function(event) {
|
||||
var items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||
for (index in items) {
|
||||
var item = items[index];
|
||||
if (item.kind === "file") {
|
||||
Dropzone.forElement("#dropzone").addFile(item.getAsFile());
|
||||
}
|
||||
}
|
||||
var items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||
for (index in items) {
|
||||
var item = items[index];
|
||||
if (item.kind === "file") {
|
||||
Dropzone.forElement("#dropzone").addFile(item.getAsFile());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// @end-license
|
||||
|
|
|
@ -25,8 +25,10 @@
|
|||
|
||||
<p><strong>Optional headers with the request</strong></p>
|
||||
|
||||
{% if not forcerandom %}
|
||||
<p>Randomize the filename<br/>
|
||||
<code>Linx-Randomize: yes</code></p>
|
||||
{% endif %}
|
||||
|
||||
<p>Specify a custom deletion key<br/>
|
||||
<code>Linx-Delete-Key: mysecret</code></p>
|
||||
|
@ -56,30 +58,30 @@
|
|||
|
||||
{% if using_auth %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}myphoto.jpg</code></pre>
|
||||
{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %}</code></pre>
|
||||
{% else %}
|
||||
<pre><code>$ curl -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}myphoto.jpg</code></pre>
|
||||
{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}wtq7pan.jpg{% endif %}</code></pre>
|
||||
{% endif %}
|
||||
|
||||
<p>Uploading myphoto.jpg with an expiry of 20 minutes</p>
|
||||
|
||||
{% if using_auth %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}myphoto.jpg</code></pre>
|
||||
{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}jm295snf.jpg{% endif %}</code></pre>
|
||||
{% else %}
|
||||
<pre><code>$ curl -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
|
||||
{{ siteurl }}myphoto.jpg</code></pre>
|
||||
{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}1doym9u2.jpg{% endif %}</code></pre>
|
||||
{% endif %}
|
||||
|
||||
<p>Uploading myphoto.jpg with a random filename and getting a json response:</p>
|
||||
|
||||
{% if using_auth %}
|
||||
<pre><code>$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/
|
||||
<pre><code>$ 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"}</code></pre>
|
||||
{% else %}
|
||||
<pre><code>$ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/
|
||||
<pre><code>$ 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"}</code></pre>
|
||||
{% endif %}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
|
||||
<div id="choices">
|
||||
<label><input name="randomize" id="randomize" type="checkbox" checked /> Randomize filename</label>
|
||||
<label>{% if not forcerandom %}<input name="randomize" id="randomize" type="checkbox" checked /> Randomize filename{% endif %}</label>
|
||||
<div id="expiry">
|
||||
<label>File expiry:
|
||||
<select name="expires" id="expires">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div id="main" class="paste">
|
||||
<div id="info">
|
||||
<div>
|
||||
<span class="hint--top hint--bounce" data-hint="Leave empty for random filename"><input class="codebox" name='filename' id="filename" type='text' value="" placeholder="filename" /></span>.<span class="hint--top hint--bounce" data-hint="Enable syntax highlighting by adding the extension"><input id="extension" class="codebox" name='extension' type='text' value="" placeholder="txt" /></span>
|
||||
{% if not forcerandom %}<span class="hint--top hint--bounce" data-hint="Leave empty for random filename"><input class="codebox" name='filename' id="filename" type='text' value="" placeholder="filename" /></span>{% endif %}.<span class="hint--top hint--bounce" data-hint="Enable syntax highlighting by adding the extension"><input id="extension" class="codebox" name='extension' type='text' value="" placeholder="txt" /></span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Paste">
|
||||
|
|
29
upload.go
29
upload.go
|
@ -222,11 +222,14 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) {
|
|||
return upload, FileTooLargeError
|
||||
}
|
||||
|
||||
// Determine the appropriate filename, then write to disk
|
||||
// Determine the appropriate filename
|
||||
barename, extension := barePlusExt(upReq.filename)
|
||||
randomize := false
|
||||
|
||||
// Randomize the "barename" (filename without extension) if needed
|
||||
if upReq.randomBarename || len(barename) == 0 {
|
||||
barename = generateBarename()
|
||||
randomize = true
|
||||
}
|
||||
|
||||
var header []byte
|
||||
|
@ -259,16 +262,32 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) {
|
|||
if merr == nil {
|
||||
if upReq.deleteKey == metad.DeleteKey {
|
||||
fileexists = false
|
||||
} else if Config.forceRandomFilename == true {
|
||||
// the file exists
|
||||
// the delete key doesn't match
|
||||
// force random filenames is enabled
|
||||
randomize = true
|
||||
}
|
||||
}
|
||||
} else if Config.forceRandomFilename == true {
|
||||
// the file doesn't exist
|
||||
// force random filenames is enabled
|
||||
randomize = true
|
||||
|
||||
// set fileexists to true to generate a new barename
|
||||
fileexists = true
|
||||
}
|
||||
|
||||
for fileexists {
|
||||
counter, err := strconv.Atoi(string(barename[len(barename)-1]))
|
||||
if err != nil {
|
||||
barename = barename + "1"
|
||||
if randomize {
|
||||
barename = generateBarename()
|
||||
} else {
|
||||
barename = barename[:len(barename)-1] + strconv.Itoa(counter+1)
|
||||
counter, err := strconv.Atoi(string(barename[len(barename)-1]))
|
||||
if err != nil {
|
||||
barename = barename + "1"
|
||||
} else {
|
||||
barename = barename[:len(barename)-1] + strconv.Itoa(counter+1)
|
||||
}
|
||||
}
|
||||
upload.Filename = strings.Join([]string{barename, extension}, ".")
|
||||
|
||||
|
|
Loading…
Reference in New Issue