Add option to force random filenames (fixes #86) (#159)

This commit is contained in:
mutantmonkey 2019-01-26 10:04:32 +00:00 committed by Andrei Marcu
parent a79bc1898a
commit 8f3108148b
9 changed files with 209 additions and 103 deletions

View file

@ -52,6 +52,7 @@ allowhotlink = true
- ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN")
- ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...)
- ```-nologs``` -- (optionally) disable request logs in stdout - ```-nologs``` -- (optionally) disable request logs in stdout
- ```-force-random-filename``` -- (optionally) force the use of random filenames
#### Storage backends #### Storage backends
The following storage backends are available: The following storage backends are available:

View file

@ -23,6 +23,7 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["index.html"], pongo2.Context{ err := renderTemplate(Templates["index.html"], pongo2.Context{
"maxsize": Config.maxSize, "maxsize": Config.maxSize,
"expirylist": listExpirationTimes(), "expirylist": listExpirationTimes(),
"forcerandom": Config.forceRandomFilename,
}, r, w) }, r, w)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -32,6 +33,7 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
func pasteHandler(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{ err := renderTemplate(Templates["paste.html"], pongo2.Context{
"expirylist": listExpirationTimes(), "expirylist": listExpirationTimes(),
"forcerandom": Config.forceRandomFilename,
}, r, w) }, r, w)
if err != nil { if err != nil {
oopsHandler(c, w, r, RespHTML, "") oopsHandler(c, w, r, RespHTML, "")
@ -41,6 +43,7 @@ func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) {
func apiDocHandler(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{ err := renderTemplate(Templates["API.html"], pongo2.Context{
"siteurl": getSiteURL(r), "siteurl": getSiteURL(r),
"forcerandom": Config.forceRandomFilename,
}, r, w) }, r, w)
if err != nil { if err != nil {
oopsHandler(c, w, r, RespHTML, "") oopsHandler(c, w, r, RespHTML, "")

View file

@ -65,6 +65,7 @@ var Config struct {
s3Region string s3Region string
s3Bucket string s3Bucket string
s3ForcePathStyle bool s3ForcePathStyle bool
forceRandomFilename bool
} }
var Templates = make(map[string]*pongo2.Template) var Templates = make(map[string]*pongo2.Template)
@ -268,6 +269,8 @@ func main() {
"S3 bucket to use for files and metadata") "S3 bucket to use for files and metadata")
flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false, flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false,
"Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)") "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() iniflags.Parse()

View file

@ -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) { func TestPutNoExtensionUpload(t *testing.T) {
mux := setup() mux := setup()
w := httptest.NewRecorder() 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) { func TestPutAndSpecificDelete(t *testing.T) {
var myjson RespOkJSON var myjson RespOkJSON

View file

@ -42,7 +42,10 @@ Dropzone.options.dropzone = {
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 + '%)'); 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) { sending: function(file, xhr, formData) {
formData.append("randomize", document.getElementById("randomize").checked); var randomize = document.getElementById("randomize");
if(randomize != null) {
formData.append("randomize", randomize.checked);
}
formData.append("expires", document.getElementById("expires").value); formData.append("expires", document.getElementById("expires").value);
}, },
success: function(file, resp) { success: function(file, resp) {

View file

@ -25,8 +25,10 @@
<p><strong>Optional headers with the request</strong></p> <p><strong>Optional headers with the request</strong></p>
{% if not forcerandom %}
<p>Randomize the filename<br/> <p>Randomize the filename<br/>
<code>Linx-Randomize: yes</code></p> <code>Linx-Randomize: yes</code></p>
{% endif %}
<p>Specify a custom deletion key<br/> <p>Specify a custom deletion key<br/>
<code>Linx-Delete-Key: mysecret</code></p> <code>Linx-Delete-Key: mysecret</code></p>
@ -56,30 +58,30 @@
{% if using_auth %} {% if using_auth %}
<pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -T myphoto.jpg {{ siteurl }}upload/ <pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -T myphoto.jpg {{ siteurl }}upload/
{{ siteurl }}myphoto.jpg</code></pre> {{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %}</code></pre>
{% else %} {% else %}
<pre><code>$ curl -T myphoto.jpg {{ siteurl }}upload/ <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 %} {% endif %}
<p>Uploading myphoto.jpg with an expiry of 20 minutes</p> <p>Uploading myphoto.jpg with an expiry of 20 minutes</p>
{% if using_auth %} {% if using_auth %}
<pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -H &#34;Linx-Expiry: 1200&#34; -T myphoto.jpg {{ siteurl }}upload/ <pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -H &#34;Linx-Expiry: 1200&#34; -T myphoto.jpg {{ siteurl }}upload/
{{ siteurl }}myphoto.jpg</code></pre> {{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}jm295snf.jpg{% endif %}</code></pre>
{% else %} {% else %}
<pre><code>$ curl -H &#34;Linx-Expiry: 1200&#34; -T myphoto.jpg {{ siteurl }}upload/ <pre><code>$ curl -H &#34;Linx-Expiry: 1200&#34; -T myphoto.jpg {{ siteurl }}upload/
{{ siteurl }}myphoto.jpg</code></pre> {{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}1doym9u2.jpg{% endif %}</code></pre>
{% endif %} {% endif %}
<p>Uploading myphoto.jpg with a random filename and getting a json response:</p> <p>Uploading myphoto.jpg with a random filename and getting a json response:</p>
{% if using_auth %} {% if using_auth %}
<pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -H &#34;Accept: application/json&#34; -H &#34;Linx-Randomize: yes&#34; -T myphoto.jpg {{ siteurl }}upload/ <pre><code>$ curl -H &#34;Linx-Api-Key: mysecretkey&#34; -H &#34;Accept: application/json&#34;{% if not forcerandom %} -H &#34;Linx-Randomize: yes&#34;{% endif %} -T myphoto.jpg {{ siteurl }}upload/
{&#34;delete_key&#34;:&#34;...&#34;,&#34;expiry&#34;:&#34;0&#34;,&#34;filename&#34;:&#34;f34h4iu.jpg&#34;,&#34;mimetype&#34;:&#34;image/jpeg&#34;, {&#34;delete_key&#34;:&#34;...&#34;,&#34;expiry&#34;:&#34;0&#34;,&#34;filename&#34;:&#34;f34h4iu.jpg&#34;,&#34;mimetype&#34;:&#34;image/jpeg&#34;,
&#34;sha256sum&#34;:&#34;...&#34;,&#34;size&#34;:&#34;...&#34;,&#34;url&#34;:&#34;{{ siteurl }}f34h4iu.jpg&#34;}</code></pre> &#34;sha256sum&#34;:&#34;...&#34;,&#34;size&#34;:&#34;...&#34;,&#34;url&#34;:&#34;{{ siteurl }}f34h4iu.jpg&#34;}</code></pre>
{% else %} {% else %}
<pre><code>$ curl -H &#34;Accept: application/json&#34; -H &#34;Linx-Randomize: yes&#34; -T myphoto.jpg {{ siteurl }}upload/ <pre><code>$ curl -H &#34;Accept: application/json&#34;{% if not forcerandom %} -H &#34;Linx-Randomize: yes&#34;{% endif %} -T myphoto.jpg {{ siteurl }}upload/
{&#34;delete_key&#34;:&#34;...&#34;,&#34;expiry&#34;:&#34;0&#34;,&#34;filename&#34;:&#34;f34h4iu.jpg&#34;,&#34;mimetype&#34;:&#34;image/jpeg&#34;, {&#34;delete_key&#34;:&#34;...&#34;,&#34;expiry&#34;:&#34;0&#34;,&#34;filename&#34;:&#34;f34h4iu.jpg&#34;,&#34;mimetype&#34;:&#34;image/jpeg&#34;,
&#34;sha256sum&#34;:&#34;...&#34;,&#34;size&#34;:&#34;...&#34;,&#34;url&#34;:&#34;{{ siteurl }}f34h4iu.jpg&#34;}</code></pre> &#34;sha256sum&#34;:&#34;...&#34;,&#34;size&#34;:&#34;...&#34;,&#34;url&#34;:&#34;{{ siteurl }}f34h4iu.jpg&#34;}</code></pre>
{% endif %} {% endif %}

View file

@ -17,7 +17,7 @@
</div> </div>
<div id="choices"> <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"> <div id="expiry">
<label>File expiry: <label>File expiry:
<select name="expires" id="expires"> <select name="expires" id="expires">

View file

@ -5,7 +5,7 @@
<div id="main" class="paste"> <div id="main" class="paste">
<div id="info"> <div id="info">
<div> <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>
<div> <div>
<input type="submit" value="Paste"> <input type="submit" value="Paste">

View file

@ -222,11 +222,14 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) {
return upload, FileTooLargeError return upload, FileTooLargeError
} }
// Determine the appropriate filename, then write to disk // Determine the appropriate filename
barename, extension := barePlusExt(upReq.filename) barename, extension := barePlusExt(upReq.filename)
randomize := false
// Randomize the "barename" (filename without extension) if needed
if upReq.randomBarename || len(barename) == 0 { if upReq.randomBarename || len(barename) == 0 {
barename = generateBarename() barename = generateBarename()
randomize = true
} }
var header []byte var header []byte
@ -259,17 +262,33 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) {
if merr == nil { if merr == nil {
if upReq.deleteKey == metad.DeleteKey { if upReq.deleteKey == metad.DeleteKey {
fileexists = false 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 { for fileexists {
if randomize {
barename = generateBarename()
} else {
counter, err := strconv.Atoi(string(barename[len(barename)-1])) counter, err := strconv.Atoi(string(barename[len(barename)-1]))
if err != nil { if err != nil {
barename = barename + "1" barename = barename + "1"
} else { } else {
barename = barename[:len(barename)-1] + strconv.Itoa(counter+1) barename = barename[:len(barename)-1] + strconv.Itoa(counter+1)
} }
}
upload.Filename = strings.Join([]string{barename, extension}, ".") upload.Filename = strings.Join([]string{barename, extension}, ".")
fileexists, err = storageBackend.Exists(upload.Filename) fileexists, err = storageBackend.Exists(upload.Filename)