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
|
Beaker==1.11.0
|
||||||
Paste==3.4.3
|
Paste==3.4.3
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
|
bleach==3.1.5
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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 */
|
||||||
|
@ -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))
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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">>
|
||||||
|
Loading…
Reference in New Issue
Block a user