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