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:
parent
6bf5333b14
commit
d9a79f46dc
@ -5,3 +5,4 @@ bottle==0.12.18
|
||||
Beaker==1.11.0
|
||||
Paste==3.4.3
|
||||
appdirs==1.4.4
|
||||
bleach==3.1.5
|
||||
|
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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 */
|
||||
|
@ -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))
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">>
|
||||
|
Loading…
Reference in New Issue
Block a user