1
0
mirror of https://github.com/Tygs/0bin.git synced 2023-08-10 21:13:00 +03:00

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 Beaker==1.11.0
Paste==3.4.3 Paste==3.4.3
appdirs==1.4.4 appdirs==1.4.4
bleach==3.1.5

View File

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

View File

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

View File

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

View File

@ -1,7 +1,3 @@
import time
import os
import glob
import tempfile
import codecs import codecs
import unicodedata import unicodedata
import hashlib 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.TEMPLATE_PATH.insert(0, CUSTOM_VIEWS_DIR)
bottle.BaseRequest.MEMFILE_MAX = settings.MAX_SIZE + (1024 * 100)
secret_key_file = settings.CONFIG_DIR / "secret_key" secret_key_file = settings.CONFIG_DIR / "secret_key"
if not secret_key_file.is_file(): if not secret_key_file.is_file():
secret_key_file.write_text(secrets.token_urlsafe(64)) secret_key_file.write_text(secrets.token_urlsafe(64))

View File

@ -67,6 +67,11 @@
<div class="container-md reader-mode" id="wrap-content"> <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"> <p :class="'alert alert-' + msg.type" v-for="msg in messages">
<a class="close" data-dismiss="alert" href="#" @click="$event.target.parentNode.remove()">×</a> <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> <strong class="title" v-if="msg.title" v-html="msg.title"></strong>

View File

@ -32,6 +32,9 @@
</div> </div>
<textarea rows="10" style="width:100%;" class="form-control" id="content" name="content" autofocus <textarea rows="10" style="width:100%;" class="form-control" id="content" name="content" autofocus
@keydown.ctrl.enter="encryptAndSendPaste()"></textarea> @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>
<div class="form-group select-date paste-option down" v-if="displayBottomToolBar"> <div class="form-group select-date paste-option down" v-if="displayBottomToolBar">

View File

@ -118,6 +118,9 @@
<div> <div>
<textarea rows="10" style="width:100%;" class=" form-control" @keydown.ctrl.enter="encryptAndSendPaste()" <textarea rows="10" style="width:100%;" class=" form-control" @keydown.ctrl.enter="encryptAndSendPaste()"
id="content" name="content"></textarea> 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>
<div class="d-flex justify-content-between" v-if="displayBottomToolBar">> <div class="d-flex justify-content-between" v-if="displayBottomToolBar">>