1
0
mirror of https://github.com/Tygs/0bin.git synced 2023-08-10 21:13:00 +03:00

New clean layout to be pip installable

This commit is contained in:
sam 2012-05-14 17:17:49 +02:00
parent 53a7b886fe
commit 78eedf8acb
34 changed files with 315 additions and 238 deletions

View File

@ -3,22 +3,38 @@
# import default settings value from src/default_settings.py
# you can refer to this file if you forgot what
# settings is for and what it is set to by default
# DO NOT ALTER THIS LINE
from src.default_settings import *
# You probably do not want to alter this line
from zerobin.default_settings import *
# debug will get you error message and auto reload
# don't set this to True in production
DEBUG = False
DEBUG = True
# Should the application serve static files on it's own ?
# 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')
# 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 = os.path.join(ROOT_DIR, 'static', 'content')
# 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
TEMPLATE_DIRS = (
os.path.join(ROOT_DIR, 'views'),
)
# Port and host the embeded python server should be using
# You can also specify them using the --host and --port script options
# which have priority on these settings
HOST = "127.0.0.1"
HOST = "0.0.0.0"
PORT = "8000"
# User and group the server should run as. Set to None if it should be the
@ -26,23 +42,16 @@ PORT = "8000"
USER = None
GROUP = None
# limit size of pasted text in bytes. Be carefull allowing too much
# size can slow down user's browser
MAX_SIZE = 1024 * 500
MAX_SIZE_KB = int(math.ceil(MAX_SIZE / 1024.0))
# Names/links to insert in the menu bar.
# Any link with "mailto:" will be escaped to prevent spam
MENU = (
('Home', '/'), # internal link
('Home', '/'), # internal link. First link will be highlited
('Download 0bin', 'https://github.com/sametmax/0bin'), # external link
('Contact', 'mailto:your@email.com') # email
)
# this import a file named settings_local.py if it exists
# you may want to create such a file to have different settings
# on each machine
try:
from settings_local import *
except ImportError:
pass
# limit size of pasted text in bytes. Be carefull allowing too much size can slow down user's
# browser
MAX_SIZE = 1024 * 500
MAX_SIZE_KB = int(math.ceil(MAX_SIZE / 1024.0))

View File

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-

View File

@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
import time
import os
import glob
import tempfile
try:
from privilege import drop_privileges_permanently, coerce_user, coerce_group
except (AttributeError):
pass # privilege does't work on several plateform
def drop_privileges(user=None, group=None, wait=5):
"""
Try to set the process user and group to another one.
If no group is provided, it's set to the same as the user.
You can wait for a certain time before doing so.
"""
if wait:
time.sleep(wait)
if user:
group = group or user
try:
user = coerce_user(user)
group = coerce_group(group)
lock_files = glob.glob(os.path.join(tempfile.gettempdir(),
'bottle.*.lock'))
for lock_file in lock_files:
os.chown(lock_file, user, group)
drop_privileges_permanently(user, group, ())
except Exception:
print "Failed to drop privileges. Running with current user."
def dmerge(*args):
"""
return new directionay being the sum of all merged dictionaries passed as arguments
"""
dictionary = {}
for arg in args:
dictionary.update(arg)
return dictionary

131
start.py
View File

@ -2,133 +2,6 @@
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
"""
Main script including controller, rooting, dependancy management, and
server run.
"""
from zerobin.routes import main
import sys
import os
import thread
from datetime import datetime, timedelta
# add project dir and libs dir to the PYTHON PATH to ensure they are
# importable
import settings
sys.path.insert(0, os.path.dirname(settings.ROOT_DIR))
sys.path.append(os.path.join(settings.ROOT_DIR, 'libs'))
import bottle
from bottle import (Bottle, run, static_file, view, request)
import clize
from src.paste import Paste
from src.utils import drop_privileges, dmerge
app = Bottle()
global_vars = {
'settings': settings
}
@app.route('/')
@view('home')
def index():
return global_vars
@app.route('/paste/create', method='POST')
def create_paste():
try:
content = unicode(request.forms.get('content', ''), 'utf8')
except UnicodeDecodeError:
return {'status': 'error',
'message': u"Encoding error: the paste couldn't be saved."}
if '{"iv":' not in content: # reject silently non encrypted content
return ''
if content:
# check size of the paste. if more than settings return error without saving paste.
# prevent from unusual use of the system.
# need to be improved
if len(content) < settings.MAX_SIZE:
expiration = request.forms.get('expiration', u'burn_after_reading')
paste = Paste(expiration=expiration, content=content)
paste.save()
return {'status': 'ok',
'paste': paste.uuid}
return {'status': 'error',
'message': u"Serveur error: the paste couldn't be saved. Please try later."}
@app.route('/paste/:paste_id')
@view('paste')
def display_paste(paste_id):
now = datetime.now()
keep_alive = False
try:
paste = Paste.load(paste_id)
# Delete the paste if it expired:
if 'burn_after_reading' in str(paste.expiration):
# burn_after_reading contains the paste creation date
# if this read appends 10 seconds after the creation date
# we don't delete the paste because it means it's the redirection
# to the paste that happens during the paste creation
try:
keep_alive = paste.expiration.split('#')[1]
keep_alive = datetime.strptime(keep_alive, '%Y-%m-%d %H:%M:%S.%f')
keep_alive = now < keep_alive + timedelta(seconds=10)
except IndexError:
keep_alive = False
if not keep_alive:
paste.delete()
elif paste.expiration < now:
paste.delete()
raise ValueError()
except (TypeError, ValueError):
#abort(404, u"This paste doesn't exist or has expired")
return error404(ValueError)
context = {'paste': paste, 'keep_alive': keep_alive}
return dmerge(context, global_vars)
@app.error(404)
@view('404')
def error404(code):
return global_vars
@clize.clize
def runserver(host=settings.HOST, port=settings.PORT, debug=settings.DEBUG,
serve_static=settings.DEBUG, user=settings.USER,
group=settings.GROUP):
if serve_static:
@app.route('/static/<filename:path>')
def server_static(filename):
return static_file(filename, root=settings.STATIC_FILES_ROOT)
thread.start_new_thread(drop_privileges, (user, group))
if debug:
bottle.debug(True)
run(app, host=host, port=port, reloader=True, server="cherrypy")
else:
run(app, host=host, port=port, server="cherrypy")
if __name__ == "__main__":
clize.run(runserver)
main()

View File

@ -2,21 +2,41 @@
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
######## NOT SETTINGS, JUST BOILER PLATE ##############
import os
import math
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_FILES_ROOT = os.path.join(ROOT_DIR, 'static')
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
DEBUG = False
# Should the application serve static files on it's own ?
# 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')
# 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')
# 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
TEMPLATE_DIRS = (
os.path.join(ROOT_DIR, 'views'),
)
# Port and host the embeded python server should be using
# You can also specify them using the --host and --port script options
# which have priority on these settings

View File

@ -2,11 +2,10 @@
import os
import hashlib
import json
from datetime import datetime, timedelta
import settings
from utils import settings
class Paste(object):
@ -25,12 +24,10 @@ class Paste(object):
def __init__(self, uuid=None, content=None,
expiration=None,
comments=None):
expiration=None):
self.content = content
self.expiration = expiration
self.comments = comments
if isinstance(self.content, unicode):
self.content = self.content.encode('utf8')
@ -95,7 +92,6 @@ class Paste(object):
uuid = os.path.basename(path)
expiration = paste.next().strip()
content = paste.next().strip()
comments = paste.read()[:-1] # remove the last coma
if "burn_after_reading" not in str(expiration):
expiration = datetime.strptime(expiration, '%Y-%m-%d %H:%M:%S.%f')
@ -104,8 +100,7 @@ class Paste(object):
except (IOError, OSError):
raise ValueError(u'Can not open paste from file %s' % path)
return Paste(uuid=uuid, comments=comments,
expiration=expiration, content=content)
return Paste(uuid=uuid, expiration=expiration, content=content)
@classmethod
@ -120,9 +115,6 @@ class Paste(object):
def save(self):
"""
Save the content of this paste to a file.
If comments are passed, they are expected to be serialized
already.
"""
head, tail = self.uuid[:2], self.uuid[2:4]
@ -157,8 +149,6 @@ class Paste(object):
with open(self.path, 'w') as f:
f.write(unicode(self.expiration) + '\n')
f.write(self.content + '\n')
if self.comments:
f.write(self.comments)
return self
@ -169,26 +159,3 @@ class Paste(object):
"""
os.remove(self.path)
@classmethod
def add_comment_to(cls, uuid, **comment):
"""
Append a comment to the file of the paste with the given uuid.
The comment is serialized to json, and a comma is added at the
end of it. Then the result is appended to the paste file.
This way we can add sequencially all comments to the file by just
appending to it, and then extracting the comment by selecting
this big blob of text, adding [] around it and use it as a json list
with no extra processing.
"""
with open(cls.get_path(uuid), 'a') as f:
f.write(json.dumps(comment) + u',\n')
def add_comment(self, **comment):
"""
Append a comment to the file of this paste.
Use add_comment_to()
"""
self.add_comment_to(self.uuid, **comment)

141
zerobin/routes.py Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
"""
Main script including controller, rooting, dependancy management, and
server run.
"""
import thread
from datetime import datetime, timedelta
# add project dir and libs dir to the PYTHON PATH to ensure they are
# importable
from utils import settings
import bottle
from bottle import (Bottle, run, static_file, view, request)
import clize
from paste import Paste
from utils import drop_privileges, dmerge
app = Bottle()
GLOBAL_CONTEXT = {
'settings': settings
}
@app.route('/')
@view('home')
def index():
return GLOBAL_CONTEXT
@app.route('/paste/create', method='POST')
def create_paste():
try:
content = unicode(request.forms.get('content', ''), 'utf8')
except UnicodeDecodeError:
return {'status': 'error',
'message': u"Encoding error: the paste couldn't be saved."}
if '{"iv":' not in content: # reject silently non encrypted content
return ''
if content:
# check size of the paste. if more than settings return error without saving paste.
# prevent from unusual use of the system.
# need to be improved
if len(content) < settings.MAX_SIZE:
expiration = request.forms.get('expiration', u'burn_after_reading')
paste = Paste(expiration=expiration, content=content)
paste.save()
return {'status': 'ok',
'paste': paste.uuid}
return {'status': 'error',
'message': u"Serveur error: the paste couldn't be saved. Please try later."}
@app.route('/paste/:paste_id')
@view('paste')
def display_paste(paste_id):
now = datetime.now()
keep_alive = False
try:
paste = Paste.load(paste_id)
# Delete the paste if it expired:
if 'burn_after_reading' in str(paste.expiration):
# burn_after_reading contains the paste creation date
# if this read appends 10 seconds after the creation date
# we don't delete the paste because it means it's the redirection
# to the paste that happens during the paste creation
try:
keep_alive = paste.expiration.split('#')[1]
keep_alive = datetime.strptime(keep_alive, '%Y-%m-%d %H:%M:%S.%f')
keep_alive = now < keep_alive + timedelta(seconds=10)
except IndexError:
keep_alive = False
if not keep_alive:
paste.delete()
elif paste.expiration < now:
paste.delete()
raise ValueError()
except (TypeError, ValueError):
#abort(404, u"This paste doesn't exist or has expired")
return error404(ValueError)
context = {'paste': paste, 'keep_alive': keep_alive}
return dmerge(context, GLOBAL_CONTEXT)
@app.error(404)
@view('404')
def error404(code):
return GLOBAL_CONTEXT
@clize.clize
def runserver(host='', port='', debug=None, serve_static='', user='',
group='', settings_file=''):
# merge the settings
if settings_file:
settings.update_with_file(settings_file)
settings.HOST = host or settings.HOST
settings.PORT = port or settings.PORT
settings.DEBUG = debug if debug is not None else settings.DEBUG
settings.STATIC_FILES_ROOT = serve_static or settings.STATIC_FILES_ROOT
settings.USER = user or settings.USER
settings.GROUP = group or settings.GROUP
# make sure the templates can be loaded
for d in reversed(settings.TEMPLATE_DIRS):
bottle.TEMPLATE_PATH.insert(0, d)
if serve_static:
@app.route('/static/<filename:path>')
def server_static(filename):
return static_file(filename, root=settings.STATIC_FILES_ROOT)
thread.start_new_thread(drop_privileges, (settings.USER, settings.GROUP))
if settings.DEBUG:
bottle.debug(True)
run(app, host=settings.HOST, port=settings.PORT, reloader=True, server="cherrypy")
else:
run(app, host=settings.HOST, port=settings.PORT, server="cherrypy")
def main():
clize.run(runserver)

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 968 B

After

Width:  |  Height:  |  Size: 968 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

106
zerobin/utils.py Normal file
View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
import time
import os
import glob
import tempfile
import sys
import default_settings
sys.path.append(default_settings.LIBS_DIR)
try:
from privilege import drop_privileges_permanently, coerce_user, coerce_group
except (AttributeError):
pass # privilege does't work on several plateform
def drop_privileges(user=None, group=None, wait=5):
"""
Try to set the process user and group to another one.
If no group is provided, it's set to the same as the user.
You can wait for a certain time before doing so.
"""
if wait:
time.sleep(wait)
if user:
group = group or user
try:
user = coerce_user(user)
group = coerce_group(group)
lock_files = glob.glob(os.path.join(tempfile.gettempdir(),
'bottle.*.lock'))
for lock_file in lock_files:
os.chown(lock_file, user, group)
drop_privileges_permanently(user, group, ())
except Exception:
print "Failed to drop privileges. Running with current user."
def dmerge(*args):
"""
Return new directionay being the sum of all merged dictionaries passed
as arguments
"""
dictionary = {}
for arg in args:
dictionary.update(arg)
return dictionary
class SettingsContainer(object):
"""
Singleton containing the settings for the whole app
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(SettingsContainer, cls).__new__(cls, *args,
**kwargs)
cls._instance.update_with_module(default_settings)
return cls._instance
def update_with_module(self, module):
"""
Update settings with values from the given module.
(Taking only variable with uppercased name)
"""
for name, value in module.__dict__.iteritems():
if name.isupper():
setattr(self, name, value)
return self
@classmethod
def from_module(cls, module):
"""
Create an instance of SettingsContainer with values based
on the one in the passed module.
"""
settings = cls()
settings.update_with_module(module)
return settings
def update_with_file(self, filepath):
"""
Update settings with values from the given module file.
User update_with_module() behind the scene
"""
sys.path.insert(0, os.path.dirname(filepath))
module_name = os.path.splitext(os.path.basename(filepath))[0]
return self.update_with_module(__import__(module_name))
settings = SettingsContainer()

View File

@ -14,11 +14,24 @@
<link href="/static/css/bootstrap.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!-- Le HTML5 shim, for IE7-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Prompt IE 6 users to tell them to install chrome frame -->
<!--[if gte IE 0 ]>
<script defer src="//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js"></script>
<script defer>window.attachEvent('onload',
function() {
CFInstall.check({
mode: 'overlay',
cssText: 'margin-top: 400px;'
})
})
</script>
<![endif]-->
<script src="/static/js/jquery-1.7.2.min.js"></script>
<script src="/static/js/sjcl.js"></script>
<script src="/static/js/behavior.js"></script>