From 6843a19fa8a6071e2220913fc9a03bab3bad39fc Mon Sep 17 00:00:00 2001
From: ksamuel
Date: Tue, 11 Aug 2020 16:37:03 +0200
Subject: [PATCH] Add delete paste
---
.gitignore | 1 +
zerobin/__init__.py | 9 ++--
zerobin/cmd.py | 23 +++++----
zerobin/default_settings.py | 43 ++++++----------
zerobin/paste.py | 80 +++++++++++++----------------
zerobin/routes.py | 47 +++++++++--------
zerobin/static/js/behavior.js | 97 +++++++++++++++++++++++++----------
zerobin/utils.py | 19 ++-----
zerobin/views/404.tpl | 16 +++---
zerobin/views/base.tpl | 12 ++---
zerobin/views/home.tpl | 8 +--
zerobin/views/paste.tpl | 16 +++---
12 files changed, 202 insertions(+), 169 deletions(-)
diff --git a/.gitignore b/.gitignore
index 85b18f7..a901e11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,4 @@ content
settings_local.py
build
.vscode
+var
diff --git a/zerobin/__init__.py b/zerobin/__init__.py
index 90eaaba..5c09490 100644
--- a/zerobin/__init__.py
+++ b/zerobin/__init__.py
@@ -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
diff --git a/zerobin/cmd.py b/zerobin/cmd.py
index d7d40b1..ffaa78f 100644
--- a/zerobin/cmd.py
+++ b/zerobin/cmd.py
@@ -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
diff --git a/zerobin/default_settings.py b/zerobin/default_settings.py
index 8704632..3b4e55b 100644
--- a/zerobin/default_settings.py
+++ b/zerobin/default_settings.py
@@ -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
diff --git a/zerobin/paste.py b/zerobin/paste.py
index b5316f5..de9b640 100644
--- a/zerobin/paste.py
+++ b/zerobin/paste.py
@@ -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):
"""
diff --git a/zerobin/routes.py b/zerobin/routes.py
index 6fd5321..4bc3101 100644
--- a/zerobin/routes.py
+++ b/zerobin/routes.py
@@ -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):
diff --git a/zerobin/static/js/behavior.js b/zerobin/static/js/behavior.js
index a439ee2..f5b615c 100644
--- a/zerobin/static/js/behavior.js
+++ b/zerobin/static/js/behavior.js
@@ -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);
diff --git a/zerobin/utils.py b/zerobin/utils.py
index 0af6d17..ac92e4d 100644
--- a/zerobin/utils.py
+++ b/zerobin/utils.py
@@ -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
diff --git a/zerobin/views/404.tpl b/zerobin/views/404.tpl
index 6b6c5d3..5ed0796 100644
--- a/zerobin/views/404.tpl
+++ b/zerobin/views/404.tpl
@@ -16,8 +16,8 @@
-
diff --git a/zerobin/views/base.tpl b/zerobin/views/base.tpl
index 0e0e559..c7d3fea 100644
--- a/zerobin/views/base.tpl
+++ b/zerobin/views/base.tpl
@@ -12,11 +12,11 @@
%if settings.COMPRESSED_STATIC_FILES:
-
+
%else:
-
+
%end
@@ -109,7 +109,7 @@
- ×
+ ×
%if settings.COMPRESSED_STATIC_FILES:
-
+
%else:
-
+
%end
%if settings.COMPRESSED_STATIC_FILES:
-
+
%else:
diff --git a/zerobin/views/home.tpl b/zerobin/views/home.tpl
index cf0e80a..991bb40 100644
--- a/zerobin/views/home.tpl
+++ b/zerobin/views/home.tpl
@@ -4,7 +4,7 @@
-
diff --git a/zerobin/views/paste.tpl b/zerobin/views/paste.tpl
index b26df49..9ab808e 100644
--- a/zerobin/views/paste.tpl
+++ b/zerobin/views/paste.tpl
@@ -1,7 +1,7 @@
%if "burn_after_reading" in str(paste.expiration):
%if keep_alive:
- ×
+ ×
Ok!
This paste will be deleted the next time it is read.
@@ -9,7 +9,7 @@
%else:
- ×
+ ×
Warning!
This paste has self-destructed. If you close this window,
@@ -54,6 +54,10 @@
+
+
+
+
@@ -69,7 +73,7 @@
+ v-on:keydown.prevent.ctrl.enter="encryptAndSendPaste()">
@@ -98,7 +102,7 @@
-
+