Add paste title

This commit is contained in:
ksamuel 2020-08-14 15:08:55 +02:00
parent 6bf5333b14
commit d9a79f46dc
8 changed files with 75 additions and 55 deletions

View File

@ -5,3 +5,4 @@ bottle==0.12.18
Beaker==1.11.0
Paste==3.4.3
appdirs==1.4.4
bleach==3.1.5

View File

@ -6,9 +6,12 @@ import os
import hashlib
import base64
import lockfile
import json
from datetime import datetime, timedelta
import bleach
from zerobin.utils import settings, to_ascii, as_unicode, safe_open as open
@ -26,10 +29,13 @@ class Paste(object):
"never": 365 * 24 * 3600 * 100,
}
def __init__(self, uuid=None, uuid_length=None, content=None, expiration=None):
def __init__(
self, uuid=None, uuid_length=None, content=None, expiration=None, title=""
):
self.content = content
self.expiration = self.get_expiration(expiration)
self.title = bleach.clean(title, strip=True)[:60]
if not uuid:
# generate the uuid from the decoded content by hashing it
@ -97,6 +103,10 @@ class Paste(object):
uuid = os.path.basename(path)
expiration = next(paste).strip()
content = next(paste).strip()
try:
metadata = json.loads(next(paste).strip())
except (StopIteration, json.decoder.JSONDecodeError):
metadata = {}
if "burn_after_reading" not in expiration:
expiration = datetime.strptime(expiration, "%Y-%m-%d %H:%M:%S.%f")
@ -105,7 +115,12 @@ class Paste(object):
except (IOError, OSError):
raise ValueError("Can not open paste from file %s" % path)
return Paste(uuid=uuid, expiration=expiration, content=content)
return Paste(
uuid=uuid,
expiration=expiration,
content=content,
title=" ".join(metadata.get("title", "").split()),
)
@classmethod
def load(cls, uuid):
@ -123,9 +138,8 @@ class Paste(object):
"""
path = settings.PASTE_FILES_ROOT
counter_file = os.path.join(path, "counter")
lock = lockfile.LockFile(counter_file)
with lock:
with lockfile.LockFile(counter_file):
# Read the value from the counter
try:
with open(counter_file, "r") as fcounter:
@ -176,6 +190,8 @@ class Paste(object):
with open(self.path, "w") as f:
f.write(expiration + "\n")
f.write(self.content + "\n")
if self.title:
f.write(json.dumps({"title": self.title}) + "\n")
return self

View File

@ -3,6 +3,7 @@
"""
import os
import pdb
import sys
import _thread as thread
@ -87,7 +88,7 @@ def admin():
return {"status": "ok", "message": "Paste deleted", **GLOBAL_CONTEXT}
return {"status": "ok", "message": "" ** GLOBAL_CONTEXT}
return {"status": "ok", "message": "", **GLOBAL_CONTEXT}
@app.get(ADMIN_LOGIN_URL)
@ -121,55 +122,37 @@ def logout():
@app.post("/paste/create")
def create_paste():
try:
body = parse_qs(request.body.read(int(settings.MAX_SIZE * 1.1)))
except ValueError:
# Reject what is too small, too big, or what does not seem encrypted to
# limit a abuses
content = request.forms.get("content", "")
if '{"iv":' not in content or not (0 < len(content) < settings.MAX_SIZE):
return {"status": "error", "message": "Wrong data payload."}
try:
content = "".join(x.decode("utf8") for x in body[b"content"])
except (UnicodeDecodeError, KeyError):
return {
"status": "error",
"message": "Encoding error: the paste couldn't be saved.",
}
expiration = request.forms.get("expiration", "burn_after_reading")
title = request.forms.get("title", "")
if '{"iv":' not in content: # reject silently non encrypted content
return {"status": "error", "message": "Wrong data payload."}
paste = Paste(
expiration=expiration,
content=content,
uuid_length=settings.PASTE_ID_LENGTH,
title=title,
)
paste.save()
# check size of the paste. if more than settings return error
# without saving paste. prevent from unusual use of the
# system. need to be improved
if 0 < len(content) < settings.MAX_SIZE:
expiration = body.get(b"expiration", [b"burn_after_reading"])[0]
paste = Paste(
expiration=expiration.decode("utf8"),
content=content,
uuid_length=settings.PASTE_ID_LENGTH,
# If refresh time elapsed pick up, update the counter
if settings.DISPLAY_COUNTER:
paste.increment_counter()
now = datetime.now()
timeout = GLOBAL_CONTEXT["refresh_counter"] + timedelta(
seconds=settings.REFRESH_COUNTER
)
paste.save()
if timeout < now:
GLOBAL_CONTEXT["pastes_count"] = Paste.get_pastes_count()
GLOBAL_CONTEXT["refresh_counter"] = now
# display counter
if settings.DISPLAY_COUNTER:
# increment paste counter
paste.increment_counter()
# if refresh time elapsed pick up new counter value
now = datetime.now()
timeout = GLOBAL_CONTEXT["refresh_counter"] + timedelta(
seconds=settings.REFRESH_COUNTER
)
if timeout < now:
GLOBAL_CONTEXT["pastes_count"] = Paste.get_pastes_count()
GLOBAL_CONTEXT["refresh_counter"] = now
return {"status": "ok", "paste": paste.uuid, "owner_key": paste.owner_key}
return {
"status": "error",
"message": "Serveur error: the paste couldn't be saved. " "Please try later.",
}
return {"status": "ok", "paste": paste.uuid, "owner_key": paste.owner_key}
@app.get("/paste/:paste_id")

View File

@ -33,10 +33,12 @@ const app = new Vue({
type: '',
content: '',
downloadLink: {},
title: '',
},
newPaste: {
expiration: '1_day',
content: '',
title: '',
},
messages: [],
/** Check for browser support of the named featured. Store the result
@ -112,18 +114,21 @@ const app = new Vue({
window.location.reload();
},
handleClone: () => {
handleClone: function () {
document.querySelector('.submit-form').style.display = "inherit";
document.querySelector('.paste-form').style.display = "none";
document.querySelector('h1').style.display = "none";
let content = document.getElementById('content');
content.value = zerobin.getPasteContent();
content.dispatchEvent(new Event('change'));
this.newPaste.title = this.currentPaste.title;
},
handleCancelClone: () => {
document.querySelector('.submit-form').style.display = "none";
document.querySelector('.paste-form').style.display = "inherit";
document.querySelector('h1').style.display = "inherit";
},
handleUpload: (files) => {
@ -200,7 +205,7 @@ const app = new Vue({
newly created paste, adding the key in the hash.
*/
encryptAndSendPaste: (e) => {
encryptAndSendPaste: () => {
var paste = document.querySelector('textarea').value;
@ -235,7 +240,8 @@ const app = new Vue({
bar.set('Sending...', '95%');
var data = {
content: content,
expiration: app.newPaste.expiration
expiration: app.newPaste.expiration,
title: app.newPaste.title
};
var sizebytes = zerobin.count(JSON.stringify(data));
var oversized = sizebytes > zerobin.max_size; // 100kb - the others header information
@ -856,6 +862,11 @@ window.onload = function () {
})
}
let title = document.querySelector('h1');
if (title) {
app.currentPaste.title = title.innerText;
}
}
/* Display previous pastes */

View File

@ -1,7 +1,3 @@
import time
import os
import glob
import tempfile
import codecs
import unicodedata
import hashlib
@ -135,6 +131,8 @@ def ensure_app_context(data_dir=None, config_dir=None):
bottle.TEMPLATE_PATH.insert(0, CUSTOM_VIEWS_DIR)
bottle.BaseRequest.MEMFILE_MAX = settings.MAX_SIZE + (1024 * 100)
secret_key_file = settings.CONFIG_DIR / "secret_key"
if not secret_key_file.is_file():
secret_key_file.write_text(secrets.token_urlsafe(64))

View File

@ -67,6 +67,11 @@
<div class="container-md reader-mode" id="wrap-content">
%if defined('paste') and paste.title:
<h1>{{ paste.title }}</h1>
%end
<p :class="'alert alert-' + msg.type" v-for="msg in messages">
<a class="close" data-dismiss="alert" href="#" @click="$event.target.parentNode.remove()">×</a>
<strong class="title" v-if="msg.title" v-html="msg.title"></strong>

View File

@ -32,6 +32,9 @@
</div>
<textarea rows="10" style="width:100%;" class="form-control" id="content" name="content" autofocus
@keydown.ctrl.enter="encryptAndSendPaste()"></textarea>
<input type="text" class="paste-excerpt" name="paste-excerpt"
placeholder="Optional paste title. This part is NOT encrypted: anything you type here will be visible by anyone"
v-model="newPaste.title" maxlength="60">
</div>
<div class="form-group select-date paste-option down" v-if="displayBottomToolBar">

View File

@ -118,6 +118,9 @@
<div>
<textarea rows="10" style="width:100%;" class=" form-control" @keydown.ctrl.enter="encryptAndSendPaste()"
id="content" name="content"></textarea>
<input type="text" class="paste-excerpt" name="paste-excerpt"
placeholder="Optional paste title. This part is NOT encrypted: anything you type here will be visible by anyone"
v-model="newPaste.title" maxlength="60">
</div>
<div class="d-flex justify-content-between" v-if="displayBottomToolBar">>