mirror of
https://github.com/Tygs/0bin.git
synced 2023-08-10 21:13:00 +03:00
Add delete paste
This commit is contained in:
parent
e13cead89b
commit
6843a19fa8
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,3 +37,4 @@ content
|
||||
settings_local.py
|
||||
build
|
||||
.vscode
|
||||
var
|
||||
|
@ -1,8 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
from pathlib import Path
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = "1.0.0"
|
||||
|
||||
from zerobin.default_settings import VERSION
|
||||
|
||||
__version__ = VERSION
|
||||
ROOT_DIR = Path(__file__).absolute().parent
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import sys
|
||||
import re
|
||||
import secrets
|
||||
import _thread as thread
|
||||
|
||||
from zerobin.utils import settings, SettingsValidationError, drop_privileges
|
||||
@ -19,6 +20,7 @@ import clize
|
||||
|
||||
|
||||
def runserver(
|
||||
*,
|
||||
host="",
|
||||
port="",
|
||||
debug=None,
|
||||
@ -30,8 +32,6 @@ def runserver(
|
||||
paste_id_length=None,
|
||||
server="cherrypy",
|
||||
):
|
||||
|
||||
debug = True
|
||||
if version:
|
||||
print("0bin V%s" % settings.VERSION)
|
||||
sys.exit(0)
|
||||
@ -41,6 +41,15 @@ def runserver(
|
||||
settings.USER = user or settings.USER
|
||||
settings.GROUP = group or settings.GROUP
|
||||
settings.PASTE_ID_LENGTH = paste_id_length or settings.PASTE_ID_LENGTH
|
||||
settings.DEBUG = bool(debug) if debug is not None else settings.DEBUG
|
||||
|
||||
settings.VAR_DIR.mkdir(exist_ok=True, parents=True)
|
||||
settings.PASTE_FILES_ROOT.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
secret_key_file = settings.VAR_DIR / "secret_key"
|
||||
if not secret_key_file.is_file():
|
||||
secret_key_file.write_text(secrets.token_urlsafe(64))
|
||||
settings.SECRET_KEY = secret_key_file.read_text()
|
||||
|
||||
try:
|
||||
_, app = get_app(debug, settings_file, compressed_static, settings=settings)
|
||||
@ -52,14 +61,10 @@ def runserver(
|
||||
|
||||
if settings.DEBUG:
|
||||
run(
|
||||
app,
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
reloader=True,
|
||||
server="cherrypy",
|
||||
app, host=settings.HOST, port=settings.PORT, reloader=True, server=server,
|
||||
)
|
||||
else:
|
||||
run(app, host=settings.HOST, port=settings.PORT, server="cherrypy")
|
||||
run(app, host=settings.HOST, port=settings.PORT, server=server)
|
||||
|
||||
|
||||
# The regex parse the url and separate the paste's id from the decription key
|
||||
@ -78,7 +83,7 @@ def unpack_paste(paste):
|
||||
return paste
|
||||
|
||||
|
||||
def delete_paste(quiet=False, *pastes):
|
||||
def delete_paste(*pastes, quiet=False):
|
||||
"""
|
||||
Remove pastes, given its ID or its URL
|
||||
|
||||
|
@ -1,28 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
from zerobin import ROOT_DIR
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
# Path to the directory that will contains all variable content, such
|
||||
# as pastes, the secret key, etc
|
||||
VAR_DIR = ROOT_DIR.parent / "var"
|
||||
|
||||
|
||||
######## NOT SETTINGS, JUST BOILER PLATE ##############
|
||||
import os
|
||||
|
||||
VERSION = '0.5'
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LIBS_DIR = os.path.join(os.path.dirname(ROOT_DIR), 'libs')
|
||||
|
||||
######## END OF BOILER PLATE ##############
|
||||
|
||||
# debug will get you error message and auto reload
|
||||
# debug will get you error messages and auto reload
|
||||
# don't set this to True in production
|
||||
DEBUG = False
|
||||
|
||||
# Should the application serve static files on it's own ?
|
||||
# IF yes, set the absolute path to the static files.
|
||||
# If yes, set the absolute path to the static files.
|
||||
# If no, set it to None
|
||||
# In dev this is handy, in prod you probably want the HTTP servers
|
||||
# to serve it, but it's OK for small traffic to set it to True in prod too.
|
||||
STATIC_FILES_ROOT = os.path.join(ROOT_DIR, 'static')
|
||||
STATIC_FILES_ROOT = ROOT_DIR / "static"
|
||||
|
||||
# If True, will link the compressed verion of the js and css files,
|
||||
# otherwise, will use the ordinary files
|
||||
@ -31,15 +22,15 @@ COMPRESSED_STATIC_FILES = False
|
||||
# absolute path where the paste files should be store
|
||||
# default in projectdirectory/static/content/
|
||||
# use "/" even under Windows
|
||||
PASTE_FILES_ROOT = os.path.join(STATIC_FILES_ROOT, 'content')
|
||||
PASTE_FILES_ROOT = VAR_DIR / "content"
|
||||
|
||||
# a tuple of absolute paths of directory where to look the template for
|
||||
# A tuple of absolute paths of directory where to look the template for
|
||||
# the first one will be the first to be looked into
|
||||
# if you want to override a template, create a new dir, write the
|
||||
# template with the same name as the one you want to override in it
|
||||
# then add the dir path at the top of this tuple
|
||||
# if you want to override, it needs to be it a directory at the begining of
|
||||
# this tuple. By default, custom_views is meant for that purpose.
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(ROOT_DIR, 'views'),
|
||||
VAR_DIR / "custom_views",
|
||||
ROOT_DIR / "views",
|
||||
)
|
||||
|
||||
# Port and host the embeded python server should be using
|
||||
@ -62,10 +53,10 @@ REFRESH_COUNTER = 60 * 1
|
||||
# Names/links to insert in the menu bar.
|
||||
# Any link with "mailto:" will be escaped to prevent spam
|
||||
MENU = (
|
||||
('Home', '/'), # internal link. First link will be highlited
|
||||
('Download 0bin', 'https://github.com/sametmax/0bin'), # external link
|
||||
('Faq', '/faq/'), # faq
|
||||
('Contact', 'mailto:your@email.com') # email
|
||||
("Home", "/"), # internal link. First link will be highlited
|
||||
("Download 0bin", "https://github.com/sametmax/0bin"), # external link
|
||||
("Faq", "/faq/"), # faq
|
||||
("Contact", "mailto:your@email.com"), # email
|
||||
)
|
||||
|
||||
# limit size of pasted text in bytes. Be careful allowing too much size can
|
||||
|
@ -12,7 +12,6 @@ from datetime import datetime, timedelta
|
||||
from zerobin.utils import settings, to_ascii, as_unicode, safe_open as open
|
||||
|
||||
|
||||
|
||||
class Paste(object):
|
||||
"""
|
||||
A paste object to deal with the file opening/parsing/saving and the
|
||||
@ -22,14 +21,12 @@ class Paste(object):
|
||||
DIR_CACHE = set()
|
||||
|
||||
DURATIONS = {
|
||||
'1_day': 24 * 3600,
|
||||
'1_month': 30 * 24 * 3600,
|
||||
'never': 365 * 24 * 3600 * 100,
|
||||
"1_day": 24 * 3600,
|
||||
"1_month": 30 * 24 * 3600,
|
||||
"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):
|
||||
|
||||
self.content = content
|
||||
self.expiration = self.get_expiration(expiration)
|
||||
@ -37,15 +34,14 @@ class Paste(object):
|
||||
if not uuid:
|
||||
# generate the uuid from the decoded content by hashing it
|
||||
# and turning it into base64, with some characters strippped
|
||||
uuid = hashlib.sha1(self.content.encode('utf8'))
|
||||
uuid = hashlib.sha1(self.content.encode("utf8"))
|
||||
uuid = base64.b64encode(uuid.digest()).decode()
|
||||
uuid = uuid.rstrip('=\n').replace('/', '-')
|
||||
uuid = uuid.rstrip("=\n").replace("/", "-")
|
||||
|
||||
if uuid_length:
|
||||
uuid = uuid[:uuid_length]
|
||||
self.uuid = uuid
|
||||
|
||||
|
||||
def get_expiration(self, expiration):
|
||||
"""
|
||||
Return a date at which the Paste will expire
|
||||
@ -60,7 +56,6 @@ class Paste(object):
|
||||
except KeyError:
|
||||
return expiration
|
||||
|
||||
|
||||
@classmethod
|
||||
def build_path(cls, *dirs):
|
||||
"""
|
||||
@ -69,7 +64,6 @@ class Paste(object):
|
||||
"""
|
||||
return os.path.join(settings.PASTE_FILES_ROOT, *dirs)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_path(cls, uuid):
|
||||
"""
|
||||
@ -77,7 +71,6 @@ class Paste(object):
|
||||
"""
|
||||
return cls.build_path(uuid[:2], uuid[2:4], uuid)
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
@ -85,6 +78,13 @@ class Paste(object):
|
||||
"""
|
||||
return self.get_path(self.uuid)
|
||||
|
||||
@property
|
||||
def owner_key(self):
|
||||
"""
|
||||
Return a key that gives you admin rights on this paste
|
||||
"""
|
||||
payload = (settings.SECRET_KEY + self.uuid).encode("ascii")
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, path):
|
||||
@ -98,16 +98,15 @@ class Paste(object):
|
||||
expiration = next(paste).strip()
|
||||
content = next(paste).strip()
|
||||
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")
|
||||
|
||||
except StopIteration:
|
||||
raise TypeError(to_ascii('File %s is malformed' % path))
|
||||
raise TypeError(to_ascii("File %s is malformed" % path))
|
||||
except (IOError, OSError):
|
||||
raise ValueError(to_ascii('Can not open paste from file %s' % path))
|
||||
raise ValueError(to_ascii("Can not open paste from file %s" % path))
|
||||
|
||||
return Paste(uuid=uuid, expiration=expiration, content=content)
|
||||
|
||||
|
||||
@classmethod
|
||||
def load(cls, uuid):
|
||||
"""
|
||||
@ -116,7 +115,6 @@ class Paste(object):
|
||||
"""
|
||||
return cls.load_from_file(cls.get_path(uuid))
|
||||
|
||||
|
||||
def increment_counter(self):
|
||||
"""
|
||||
Increment pastes counter.
|
||||
@ -124,21 +122,20 @@ class Paste(object):
|
||||
It uses a lock file to prevent multi access to the file.
|
||||
"""
|
||||
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:
|
||||
# Read the value from the counter
|
||||
try:
|
||||
with open(counter_file, "r") as fcounter:
|
||||
counter_value = int(fcounter.read(50)) + 1
|
||||
except (ValueError, IOError, OSError):
|
||||
counter_value = 1
|
||||
|
||||
# write new value to counter
|
||||
with open(counter_file, "w") as fcounter:
|
||||
fcounter.write(str(counter_value))
|
||||
# Read the value from the counter
|
||||
try:
|
||||
with open(counter_file, "r") as fcounter:
|
||||
counter_value = int(fcounter.read(50)) + 1
|
||||
except (ValueError, IOError, OSError):
|
||||
counter_value = 1
|
||||
|
||||
# write new value to counter
|
||||
with open(counter_file, "w") as fcounter:
|
||||
fcounter.write(str(counter_value))
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
@ -171,18 +168,17 @@ class Paste(object):
|
||||
# a quick period of time where you can redirect to the page without
|
||||
# deleting the paste
|
||||
if "burn_after_reading" == self.expiration:
|
||||
expiration = self.expiration + '#%s' % datetime.now() # TODO: use UTC dates
|
||||
expiration = self.expiration + "#%s" % datetime.now() # TODO: use UTC dates
|
||||
else:
|
||||
expiration = as_unicode(self.expiration)
|
||||
|
||||
# write the paste
|
||||
with open(self.path, 'w') as f:
|
||||
f.write(expiration + '\n')
|
||||
f.write(self.content + '\n')
|
||||
with open(self.path, "w") as f:
|
||||
f.write(expiration + "\n")
|
||||
f.write(self.content + "\n")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_pastes_count(cls):
|
||||
"""
|
||||
@ -190,14 +186,13 @@ class Paste(object):
|
||||
(must have option DISPLAY_COUNTER enabled for the pastes to be
|
||||
be counted)
|
||||
"""
|
||||
counter_file = os.path.join(settings.PASTE_FILES_ROOT, 'counter')
|
||||
counter_file = os.path.join(settings.PASTE_FILES_ROOT, "counter")
|
||||
try:
|
||||
count = int(open(counter_file).read(50))
|
||||
except (IOError, OSError):
|
||||
count = 0
|
||||
|
||||
return '{0:,}'.format(count)
|
||||
|
||||
return "{0:,}".format(count)
|
||||
|
||||
@property
|
||||
def humanized_expiration(self):
|
||||
@ -215,19 +210,18 @@ class Paste(object):
|
||||
return None
|
||||
|
||||
if expiration < 60:
|
||||
return 'in %s s' % expiration
|
||||
return "in %s s" % expiration
|
||||
|
||||
if expiration < 60 * 60:
|
||||
return 'in %s m' % int(expiration / 60)
|
||||
return "in %s m" % int(expiration / 60)
|
||||
|
||||
if expiration < 60 * 60 * 24:
|
||||
return 'in %s h' % int(expiration / (60 * 60))
|
||||
return "in %s h" % int(expiration / (60 * 60))
|
||||
|
||||
if expiration < 60 * 60 * 24 * 10:
|
||||
return 'in %s days(s)' % int(expiration / (60 * 60 * 24))
|
||||
|
||||
return 'the %s' % self.expiration.strftime('%m/%d/%Y')
|
||||
return "in %s days(s)" % int(expiration / (60 * 60 * 24))
|
||||
|
||||
return "the %s" % self.expiration.strftime("%m/%d/%Y")
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
|
@ -1,9 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals, absolute_import, print_function
|
||||
import pdb
|
||||
|
||||
"""
|
||||
Script including controller, rooting, and dependency management.
|
||||
"""
|
||||
@ -11,24 +5,17 @@ import pdb
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import thread
|
||||
except ImportError:
|
||||
import _thread as thread
|
||||
import _thread as thread
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# add project dir and libs dir to the PYTHON PATH to ensure they are
|
||||
# importable
|
||||
from zerobin.utils import settings, SettingsValidationError, drop_privileges, dmerge
|
||||
from zerobin import __version__
|
||||
from zerobin.utils import settings, SettingsValidationError, dmerge
|
||||
|
||||
import bottle
|
||||
from bottle import Bottle, run, static_file, view, request
|
||||
from bottle import Bottle, static_file, view, request, HTTPResponse
|
||||
|
||||
from zerobin.paste import Paste
|
||||
|
||||
@ -36,6 +23,7 @@ from zerobin.paste import Paste
|
||||
app = Bottle()
|
||||
GLOBAL_CONTEXT = {
|
||||
"settings": settings,
|
||||
"VERSION": __version__,
|
||||
"pastes_count": Paste.get_pastes_count(),
|
||||
"refresh_counter": datetime.now(),
|
||||
}
|
||||
@ -99,7 +87,7 @@ def create_paste():
|
||||
GLOBAL_CONTEXT["pastes_count"] = Paste.get_pastes_count()
|
||||
GLOBAL_CONTEXT["refresh_counter"] = now
|
||||
|
||||
return {"status": "ok", "paste": paste.uuid}
|
||||
return {"status": "ok", "paste": paste.uuid, "owner_key": paste.owner_key}
|
||||
|
||||
return {
|
||||
"status": "error",
|
||||
@ -107,7 +95,7 @@ def create_paste():
|
||||
}
|
||||
|
||||
|
||||
@app.route("/paste/:paste_id")
|
||||
@app.route("/paste/:paste_id", method="GET")
|
||||
@view("paste")
|
||||
def display_paste(paste_id):
|
||||
|
||||
@ -141,6 +129,25 @@ def display_paste(paste_id):
|
||||
return dmerge(context, GLOBAL_CONTEXT)
|
||||
|
||||
|
||||
@app.route("/paste/:paste_id", method="DELETE")
|
||||
def delete_paste(paste_id):
|
||||
|
||||
try:
|
||||
paste = Paste.load(paste_id)
|
||||
except (TypeError, ValueError):
|
||||
return error404(ValueError)
|
||||
|
||||
if paste.owner_key != request.forms.get("owner_key", None):
|
||||
return HTTPResponse(status=403, body="Wrong owner key")
|
||||
|
||||
paste.delete()
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"message": "Paste deleted",
|
||||
}
|
||||
|
||||
|
||||
@app.error(404)
|
||||
@view("404")
|
||||
def error404(code):
|
||||
|
@ -1,12 +1,16 @@
|
||||
/*global sjcl:true, jQuery:true, lzw:true, zerobin:true, prettyPrint:true, confirm:true */
|
||||
/*global sjcl:true, jQuery:true, lzw:true, zerobin:true, prettyPrint:true */
|
||||
|
||||
/*
|
||||
This file has been migrated away from jQuery, to Vue. Because of the way the code base used to be, a lot of the operation are still using imperative DOM manipulation
|
||||
instead of the Vue declarative style. We haven't had the time to rewrite it completly and it's a bit of a mixed bag at the moment
|
||||
This file has been migrated away from jQuery, to Vue. Because of the way
|
||||
the code base used to be, a lot of the operations are still using imperative
|
||||
DOM manipulation instead of the Vue declarative style. We haven't had the
|
||||
time to rewrite it completly and it's a bit of a mixed bag at the moment.
|
||||
*/
|
||||
|
||||
/* Start random number generator seeding ASAP */
|
||||
sjcl.random.startCollectors();
|
||||
|
||||
// Vue template syntax conflicts with bottle template syntax
|
||||
Vue.options.delimiters = ['{%', '%}'];
|
||||
|
||||
// Force focus for textarea (firefox hack)
|
||||
@ -14,11 +18,11 @@ setTimeout(function () {
|
||||
document.querySelector('textarea').focus()
|
||||
}, 100)
|
||||
|
||||
// Parse obfuscaded emails and make them usable
|
||||
const menu = new Vue({
|
||||
el: "#menu-top",
|
||||
methods: {
|
||||
formatEmail: (email) => {
|
||||
/* Parse obfuscaded emails and make them usable */
|
||||
return "mailto:" + email.replace('__AT__', '@');
|
||||
},
|
||||
}
|
||||
@ -32,6 +36,10 @@ const app = new Vue({
|
||||
downloadLink: {},
|
||||
displayBottomToolBar: false,
|
||||
isUploading: false,
|
||||
currentPaste: {
|
||||
ownerKey: '',
|
||||
id: ''
|
||||
},
|
||||
newPaste: {
|
||||
expiration: '1_day',
|
||||
content: '',
|
||||
@ -89,32 +97,60 @@ const app = new Vue({
|
||||
},
|
||||
|
||||
handleUpload: (files) => {
|
||||
|
||||
try {
|
||||
app.isUploading = true;
|
||||
zerobin.upload(files);
|
||||
} catch (e) {
|
||||
zerobin.message('error', 'Could no upload the file', 'Error');
|
||||
}
|
||||
|
||||
app.isUploading = false;
|
||||
},
|
||||
|
||||
handleForceColoration: (e) => {
|
||||
/* Force text coloration when clickin on link */
|
||||
|
||||
let content = document.getElementById('paste-content');
|
||||
content.classList.add('linenums');
|
||||
e.target.innerHTML = 'Applying coloration';
|
||||
prettyPrint();
|
||||
e.target.remove()
|
||||
|
||||
e.target.parentNode.remove()
|
||||
},
|
||||
|
||||
handleSendByEmail: (e) => {
|
||||
e.target.href = 'mailto:friend@example.com?body=' + window.location.toString();
|
||||
},
|
||||
|
||||
handleDeletePaste: () => {
|
||||
if (window.confirm("Delete this paste?")) {
|
||||
app.isLoading = true;
|
||||
bar.set('Deleting paste...', '50%');
|
||||
|
||||
fetch('/paste/' + app.currentPaste.id, {
|
||||
method: "DELETE",
|
||||
body: new URLSearchParams({
|
||||
"owner_key": app.currentPaste.ownerKey
|
||||
})
|
||||
}).then(function (response) {
|
||||
if (response.ok) {
|
||||
window.location = "/";
|
||||
window.reload()
|
||||
} else {
|
||||
form.forEach((node) => node.disabled = false);
|
||||
app.isLoading = false
|
||||
zerobin.message(
|
||||
'error',
|
||||
'Paste could not be deleted. Please try again later.',
|
||||
'Error');
|
||||
}
|
||||
app.isLoading = false;
|
||||
}).catch(function (error) {
|
||||
zerobin.message(
|
||||
'error',
|
||||
'Paste could not be delete. Please try again later.',
|
||||
'Error');
|
||||
app.isLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
copyToClipboard: () => {
|
||||
|
||||
var pasteContent = zerobin.getPasteContent();
|
||||
@ -153,7 +189,6 @@ const app = new Vue({
|
||||
|
||||
encryptAndSendPaste: (e) => {
|
||||
|
||||
e.preventDefault();
|
||||
var paste = document.querySelector('textarea').value;
|
||||
|
||||
if (paste.trim()) {
|
||||
@ -216,11 +251,10 @@ const app = new Vue({
|
||||
form.forEach((node) => node.disabled = false);
|
||||
app.isLoading = false
|
||||
} else {
|
||||
var paste_url = '/paste/' + data.paste + '#' + key;
|
||||
if (app.support.localStorage) {
|
||||
zerobin.storePaste(paste_url);
|
||||
zerobin.storePaste('/paste/' + data.paste + "?owner_key=" + data.owner_key + '#' + key);
|
||||
}
|
||||
window.location = (paste_url);
|
||||
window.location = ('/paste/' + data.paste + '#' + key);
|
||||
}
|
||||
})
|
||||
|
||||
@ -253,9 +287,9 @@ const app = new Vue({
|
||||
}
|
||||
})
|
||||
|
||||
/***************************
|
||||
**** 0bin utilities ***
|
||||
***************************/
|
||||
/****************************
|
||||
**** 0bin utilities ****
|
||||
****************************/
|
||||
|
||||
window.zerobin = {
|
||||
/** Base64 + compress + encrypt, with callbacks before each operation,
|
||||
@ -416,11 +450,11 @@ window.zerobin = {
|
||||
/** Return a reverse sorted list of all the keys in local storage that
|
||||
are prefixed with with the passed version (default being this lib
|
||||
version) */
|
||||
getLocalStorageKeys: function () {
|
||||
var version = 'zerobinV0.1';
|
||||
getLocalStorageURLKeys: function () {
|
||||
var version = 'zerobinV' + zerobin.version;
|
||||
var keys = [];
|
||||
for (var key in localStorage) {
|
||||
if (key.indexOf(version) !== -1) {
|
||||
if (key.indexOf(version) !== -1 && key.indexOf("owner_key") === -1) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
@ -436,13 +470,14 @@ window.zerobin = {
|
||||
date = date || new Date();
|
||||
date = (date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + zerobin.getFormatedTime(date));
|
||||
|
||||
var keys = zerobin.getLocalStorageKeys();
|
||||
var keys = zerobin.getLocalStorageURLKeys();
|
||||
|
||||
if (localStorage.length > 19) {
|
||||
void localStorage.removeItem(keys[19]);
|
||||
}
|
||||
|
||||
localStorage.setItem('zerobinV' + zerobin.version + "#" + date, url);
|
||||
localStorage.setItem('zerobinV' + zerobin.version + "#" + zerobin.getPasteId(url) + "#owner_key", zerobin.getPasteOwnerKey(url));
|
||||
},
|
||||
|
||||
/** Return a list of the previous paste url with the creation date
|
||||
@ -450,7 +485,7 @@ window.zerobin = {
|
||||
else it should be "the mm-dd-yyy"
|
||||
*/
|
||||
getPreviousPastes: function () {
|
||||
var keys = zerobin.getLocalStorageKeys(),
|
||||
var keys = zerobin.getLocalStorageURLKeys(),
|
||||
today = zerobin.getFormatedDate();
|
||||
|
||||
return keys.map(function (key, i) {
|
||||
@ -463,11 +498,14 @@ window.zerobin = {
|
||||
prefix = 'at ';
|
||||
}
|
||||
let link = localStorage.getItem(key);
|
||||
|
||||
return {
|
||||
displayDate: displayDate,
|
||||
prefix: prefix,
|
||||
link: link,
|
||||
isCurrent: link.replace(/#[^#]+/, '') === window.location.pathname
|
||||
// The owner key is stored in the URL, but we don't want the user
|
||||
// to see it
|
||||
link: link.replace(/\?[^#]+#/, '#'),
|
||||
isCurrent: link.replace(/\?[^?]+/, '') === window.location.pathname
|
||||
};
|
||||
});
|
||||
|
||||
@ -497,6 +535,11 @@ window.zerobin = {
|
||||
return loc.pathname.replace(/\/|paste/g, '');
|
||||
},
|
||||
|
||||
getPasteOwnerKey: function (url) {
|
||||
var loc = url ? zerobin.parseUrl(url) : window.location;
|
||||
return (new URLSearchParams(loc.search)).get("owner_key");
|
||||
},
|
||||
|
||||
getPasteKey: function (url) {
|
||||
var loc = url ? zerobin.parseUrl(url) : window.location;
|
||||
return loc.hash.replace('#', '').replace(/(\?|&).*$/, '');
|
||||
@ -541,7 +584,7 @@ window.zerobin = {
|
||||
title: title,
|
||||
content: message,
|
||||
type: type,
|
||||
action: action,
|
||||
action: action || {},
|
||||
});
|
||||
callback && callback()
|
||||
},
|
||||
@ -681,6 +724,7 @@ let content = '';
|
||||
|
||||
if (pasteContent) {
|
||||
content = pasteContent.textContent.trim();
|
||||
app.currentPaste.id = zerobin.getPasteId(window.location);
|
||||
}
|
||||
|
||||
var key = zerobin.getPasteKey();
|
||||
@ -788,6 +832,7 @@ window.onload = function () {
|
||||
/* Display previous pastes */
|
||||
if (app.support.localStorage) {
|
||||
app.previousPastes = zerobin.getPreviousPastes();
|
||||
app.currentPaste.ownerKey = localStorage.getItem('zerobinV' + zerobin.version + "#" + zerobin.getPasteId(window.location) + "#owner_key");
|
||||
}
|
||||
|
||||
/* Upload file using HTML5 File API */
|
||||
@ -804,7 +849,7 @@ if (app.support.fileUpload) {
|
||||
/* Remove expired pasted from history */
|
||||
if (app.support.history && zerobin.paste_not_found) {
|
||||
var paste_id = zerobin.getPasteId();
|
||||
var keys = zerobin.getLocalStorageKeys();
|
||||
var keys = zerobin.getLocalStorageURLKeys();
|
||||
keys.forEach((key, i) => {
|
||||
if (localStorage[key].indexOf(paste_id) !== -1) {
|
||||
localStorage.removeItem(key);
|
||||
|
@ -1,7 +1,3 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import print_function, unicode_literals, absolute_import
|
||||
|
||||
import time
|
||||
import os
|
||||
import glob
|
||||
@ -12,7 +8,6 @@ import unicodedata
|
||||
from functools import partial
|
||||
|
||||
from zerobin import default_settings
|
||||
sys.path.append(default_settings.LIBS_DIR)
|
||||
|
||||
try:
|
||||
from zerobin.privilege import drop_privileges_permanently, coerce_user, coerce_group
|
||||
@ -24,7 +19,7 @@ try:
|
||||
except ImportError:
|
||||
# python-2.6 or earlier - use simplier less-optimized execfile()
|
||||
def run_path(file_path):
|
||||
mod_globals = {'__file__': file_path}
|
||||
mod_globals = {"__file__": file_path}
|
||||
execfile(file_path, mod_globals)
|
||||
return mod_globals
|
||||
|
||||
@ -43,8 +38,7 @@ def drop_privileges(user=None, group=None, wait=5):
|
||||
user = coerce_user(user)
|
||||
group = coerce_group(group)
|
||||
|
||||
lock_files = glob.glob(os.path.join(tempfile.gettempdir(),
|
||||
'bottle.*.lock'))
|
||||
lock_files = glob.glob(os.path.join(tempfile.gettempdir(), "bottle.*.lock"))
|
||||
for lock_file in lock_files:
|
||||
os.chown(lock_file, user, group)
|
||||
|
||||
@ -78,12 +72,10 @@ class SettingsContainer(object):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
|
||||
if not cls._instance:
|
||||
cls._instance = super(SettingsContainer, cls).__new__(cls, *args,
|
||||
**kwargs)
|
||||
cls._instance = super(SettingsContainer, cls).__new__(cls, *args, **kwargs)
|
||||
cls._instance.update_with_module(default_settings)
|
||||
return cls._instance
|
||||
|
||||
|
||||
def update_with_dict(self, dict):
|
||||
"""
|
||||
Update settings with values from the given mapping object.
|
||||
@ -94,7 +86,6 @@ class SettingsContainer(object):
|
||||
setattr(self, name, value)
|
||||
return self
|
||||
|
||||
|
||||
def update_with_module(self, module):
|
||||
"""
|
||||
Update settings with values from the given module.
|
||||
@ -102,7 +93,6 @@ class SettingsContainer(object):
|
||||
"""
|
||||
return self.update_with_dict(module.__dict__)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_module(cls, module):
|
||||
"""
|
||||
@ -113,7 +103,6 @@ class SettingsContainer(object):
|
||||
settings.update_with_module(module)
|
||||
return settings
|
||||
|
||||
|
||||
def update_with_file(self, filepath):
|
||||
"""
|
||||
Update settings with values from the given module file.
|
||||
@ -132,7 +121,7 @@ def to_ascii(utext):
|
||||
Try to replace non ASCII char by similar ASCII char. If it can't,
|
||||
replace it with "?".
|
||||
"""
|
||||
return unicodedata.normalize('NFKD', utext).encode('ascii', "replace")
|
||||
return unicodedata.normalize("NFKD", utext).encode("ascii", "replace")
|
||||
|
||||
|
||||
# Make sure to always specify encoding when using open in Python 2 or 3
|
||||
|
@ -16,8 +16,8 @@
|
||||
<input type="file" class="hide-upload" id="file-upload" @change="handleUpload($event.target.files)">
|
||||
</p>
|
||||
|
||||
<form class="well" method="post" action="/paste/create">
|
||||
<p class="paste-option">
|
||||
<form class="well" method="post" action="/paste/create" @submit.prevent="encryptAndSendPaste()">
|
||||
<p class=" paste-option">
|
||||
<label for="expiration">Expiration:</label>
|
||||
<select id="expiration" name="expiration" v-model="newPaste.expiration">
|
||||
<option value="burn_after_reading">Burn after reading</option>
|
||||
@ -25,14 +25,14 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary" ">Submit</button>
|
||||
</p>
|
||||
<p>
|
||||
<div class="progress progress-striped active" v-show="isLoading">
|
||||
<div class=" progress progress-striped active" v-show="isLoading">
|
||||
<div class="bar"></div>
|
||||
</div>
|
||||
<textarea rows="10" style="width:100%;" class="input-xlarge" id="content" name="content" autofocus
|
||||
v-on:keydown.ctrl.enter="encryptAndSendPaste($event)"></textarea>
|
||||
</div>
|
||||
<textarea rows="10" style="width:100%;" class="input-xlarge" id="content" name="content" autofocus
|
||||
v-on:keydown.prevent.ctrl.enter="encryptAndSendPaste()"></textarea>
|
||||
</p>
|
||||
<p class="paste-option down" v-if="displayBottomToolBar">
|
||||
<label for="expiration">Expiration:</label>
|
||||
@ -42,7 +42,7 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
|
@ -12,11 +12,11 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
|
||||
%if settings.COMPRESSED_STATIC_FILES:
|
||||
<link href="/static/css/style.min.css?{{ settings.VERSION }}" rel="stylesheet" />
|
||||
<link href="/static/css/style.min.css?{{ VERSION }}" rel="stylesheet" />
|
||||
%else:
|
||||
<link href="/static/css/prettify.css" rel="stylesheet" />
|
||||
<link href="/static/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="/static/css/style.css?{{ settings.VERSION }}" rel="stylesheet">
|
||||
<link href="/static/css/style.css?{{ VERSION }}" rel="stylesheet">
|
||||
%end
|
||||
|
||||
<!-- Le HTML5 shim, for IE7-8 support of HTML5 elements -->
|
||||
@ -109,7 +109,7 @@
|
||||
|
||||
|
||||
<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.prevent="$event.target.parentNode.remove()">×</a>
|
||||
<strong class="title" v-if="msg.title" v-html="msg.title"></strong>
|
||||
<span class="message" v-html="msg.content"></span>
|
||||
<a v-if="msg.action.message" href="#"
|
||||
@ -156,10 +156,10 @@
|
||||
|
||||
<script src="/static/js/vue.js"></script>
|
||||
%if settings.COMPRESSED_STATIC_FILES:
|
||||
<script src="/static/js/main.min.js?{{ settings.VERSION }}"></script>
|
||||
<script src="/static/js/main.min.js?{{ VERSION }}"></script>
|
||||
%else:
|
||||
<script src="/static/js/sjcl.js"></script>
|
||||
<script src="/static/js/behavior.js?{{ settings.VERSION }}"></script>
|
||||
<script src="/static/js/behavior.js?{{ VERSION }}"></script>
|
||||
%end
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -168,7 +168,7 @@
|
||||
</script>
|
||||
|
||||
%if settings.COMPRESSED_STATIC_FILES:
|
||||
<script src="/static/js/additional.min.js?{{ settings.VERSION }}"></script>
|
||||
<script src="/static/js/additional.min.js?{{ VERSION }}"></script>
|
||||
%else:
|
||||
|
||||
<script src="/static/js/lzw.js"></script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<input type="file" class="hide-upload" id="file-upload" @change="handleUpload($event.target.files)">
|
||||
</p>
|
||||
|
||||
<form class="well" method="post" action="/paste/create">
|
||||
<form class="well" method="post" action="/paste/create" @submit.prevent="encryptAndSendPaste()">
|
||||
<p class="paste-option">
|
||||
<label for="expiration">Expiration:</label>
|
||||
<select id="expiration" name="expiration" v-model="newPaste.expiration">
|
||||
@ -13,14 +13,14 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</p>
|
||||
<p>
|
||||
<div class="progress progress-striped active" v-show="isLoading">
|
||||
<div class="bar"></div>
|
||||
</div>
|
||||
<textarea rows="10" style="width:100%" class="input-xlarge" id="content" name="content" autofocus
|
||||
v-on:keydown.ctrl.enter="encryptAndSendPaste($event)"></textarea>
|
||||
v-on:keydown.prevent.ctrl.enter="encryptAndSendPaste()"></textarea>
|
||||
</p>
|
||||
|
||||
<p class="paste-option down" v-if="displayBottomToolBar">
|
||||
@ -31,7 +31,7 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
%if "burn_after_reading" in str(paste.expiration):
|
||||
%if keep_alive:
|
||||
<p class="alert alert-info">
|
||||
<a class="close" data-dismiss="alert" href="#" @click="$event.target.parentNode.remove()">×</a>
|
||||
<a class="close" data-dismiss="alert" href="#" @click.prevent="$event.target.parentNode.remove()">×</a>
|
||||
<strong class="title">Ok!</strong>
|
||||
<span class="message">
|
||||
This paste will be deleted the next time it is read.
|
||||
@ -9,7 +9,7 @@
|
||||
</p>
|
||||
%else:
|
||||
<p class="alert">
|
||||
<a class="close" data-dismiss="alert" href="#" @click="$event.target.parentNode.remove()">×</a>
|
||||
<a class="close" data-dismiss="alert" href="#" @click.prevent="$event.target.parentNode.remove()">×</a>
|
||||
<strong class="title">Warning!</strong>
|
||||
<span class="message">
|
||||
This paste has self-destructed. If you close this window,
|
||||
@ -54,6 +54,10 @@
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p v-if="currentPaste.ownerKey">
|
||||
<button type="button" class="btn btn-danger" @click="handleDeletePaste()">Delete this paste</button>
|
||||
</p>
|
||||
|
||||
<p class="paste-option btn-group bottom">
|
||||
<button class="btn btn-clone" @click.prevent="handleClone()"><i class="icon-camera"></i> Clone</button>
|
||||
|
||||
@ -69,7 +73,7 @@
|
||||
|
||||
<!-- For cloning -->
|
||||
<div class="submit-form clone">
|
||||
<form class="well" method="post" action="/paste/create">
|
||||
<form class="well" method="post" action="/paste/create" @submit.prevent="encryptAndSendPaste()">
|
||||
<p class="paste-option">
|
||||
<label for="expiration">Expiration:</label>
|
||||
<select id="expiration" name="expiration" v-model="newPaste.expiration">
|
||||
@ -78,7 +82,7 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<button class="btn btn-danger" @click.prevent="handleCancelClone()">Cancel clone</button>
|
||||
</p>
|
||||
|
||||
@ -87,7 +91,7 @@
|
||||
<div class="bar"></div>
|
||||
</div>
|
||||
<textarea rows="10" style="width:100%;" class="input-xlarge" id="content" name="content" autofocus
|
||||
v-on:keydown.ctrl.enter="encryptAndSendPaste($event)"></textarea>
|
||||
v-on:keydown.prevent.ctrl.enter="encryptAndSendPaste()"></textarea>
|
||||
</div>
|
||||
|
||||
<p class="paste-option down" v-if="displayBottomToolBar">
|
||||
@ -98,7 +102,7 @@
|
||||
<option value="1_month">1 month</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary" @click="encryptAndSendPaste($event)">Submit</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user