2012-05-14 19:17:49 +04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vim: ai ts=4 sts=4 et sw=4
|
|
|
|
|
|
|
|
"""
|
2013-03-03 20:27:39 +04:00
|
|
|
Main script including controller, rooting, dependency management, and
|
2012-05-14 19:17:49 +04:00
|
|
|
server run.
|
|
|
|
"""
|
|
|
|
|
2012-05-18 16:29:52 +04:00
|
|
|
import os
|
|
|
|
import sys
|
2012-05-14 19:17:49 +04:00
|
|
|
import thread
|
2013-01-19 21:50:07 +04:00
|
|
|
import urlparse
|
2012-05-14 19:17:49 +04:00
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
# add project dir and libs dir to the PYTHON PATH to ensure they are
|
|
|
|
# importable
|
2013-04-29 22:17:37 +04:00
|
|
|
from utils import settings, SettingsValidationError
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
import bottle
|
|
|
|
from bottle import (Bottle, run, static_file, view, request)
|
|
|
|
|
|
|
|
import clize
|
|
|
|
|
|
|
|
from paste import Paste
|
2012-05-22 16:39:34 +04:00
|
|
|
from utils import drop_privileges, dmerge
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
|
|
|
|
app = Bottle()
|
|
|
|
GLOBAL_CONTEXT = {
|
2012-05-21 19:14:01 +04:00
|
|
|
'settings': settings,
|
2012-05-22 16:39:34 +04:00
|
|
|
'pastes_count': Paste.get_pastes_count(),
|
2012-05-21 20:21:06 +04:00
|
|
|
'refresh_counter': datetime.now()
|
2012-05-14 19:17:49 +04:00
|
|
|
}
|
2012-05-22 16:39:34 +04:00
|
|
|
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
@app.route('/')
|
|
|
|
@view('home')
|
|
|
|
def index():
|
|
|
|
return GLOBAL_CONTEXT
|
|
|
|
|
|
|
|
|
2012-05-21 14:25:24 +04:00
|
|
|
@app.route('/faq/')
|
|
|
|
@view('faq')
|
2012-05-21 19:14:01 +04:00
|
|
|
def faq():
|
2012-05-21 14:25:24 +04:00
|
|
|
return GLOBAL_CONTEXT
|
|
|
|
|
|
|
|
|
2012-05-14 19:17:49 +04:00
|
|
|
@app.route('/paste/create', method='POST')
|
|
|
|
def create_paste():
|
2013-01-19 21:50:07 +04:00
|
|
|
try:
|
2013-01-19 21:54:51 +04:00
|
|
|
body = urlparse.parse_qs(request.body.read(int(settings.MAX_SIZE * 1.1)))
|
2013-01-19 21:50:07 +04:00
|
|
|
except ValueError:
|
|
|
|
return {'status': 'error',
|
|
|
|
'message': u"Wrong data payload."}
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
try:
|
2013-01-19 21:50:07 +04:00
|
|
|
content = unicode(''.join(body['content']), 'utf8')
|
|
|
|
except (UnicodeDecodeError, KeyError):
|
2012-05-14 19:17:49 +04:00
|
|
|
return {'status': 'error',
|
|
|
|
'message': u"Encoding error: the paste couldn't be saved."}
|
|
|
|
|
2013-01-19 20:05:37 +04:00
|
|
|
if '{"iv":' not in content: # reject silently non encrypted content
|
2013-01-19 21:50:07 +04:00
|
|
|
return {'status': 'error',
|
|
|
|
'message': u"Wrong data payload."}
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
if content:
|
2013-01-19 20:05:37 +04:00
|
|
|
# check size of the paste. if more than settings return error
|
2013-01-19 21:50:07 +04:00
|
|
|
# without saving paste. prevent from unusual use of the
|
2013-01-19 20:05:37 +04:00
|
|
|
# system. need to be improved
|
2012-05-14 19:17:49 +04:00
|
|
|
if len(content) < settings.MAX_SIZE:
|
2013-01-19 21:50:07 +04:00
|
|
|
expiration = body.get('expiration', [u'burn_after_reading'])[0]
|
2013-04-29 22:17:37 +04:00
|
|
|
paste = Paste(expiration=expiration, content=content,
|
|
|
|
uuid_length=settings.PASTE_ID_LENGTH)
|
2012-05-14 19:17:49 +04:00
|
|
|
paste.save()
|
2012-05-22 16:39:34 +04:00
|
|
|
|
2012-05-21 20:21:06 +04:00
|
|
|
# display counter
|
2012-05-21 19:14:01 +04:00
|
|
|
if settings.DISPLAY_COUNTER:
|
2012-05-21 20:21:06 +04:00
|
|
|
|
|
|
|
#increment paste counter
|
2012-05-21 19:14:01 +04:00
|
|
|
paste.increment_counter()
|
2012-05-22 16:39:34 +04:00
|
|
|
|
2012-05-21 20:21:06 +04:00
|
|
|
# if refresh time elapsed pick up new counter value
|
2012-05-22 16:39:34 +04:00
|
|
|
now = datetime.now()
|
|
|
|
timeout = (GLOBAL_CONTEXT['refresh_counter']
|
|
|
|
+ timedelta(seconds=settings.REFRESH_COUNTER))
|
|
|
|
if timeout < now:
|
|
|
|
GLOBAL_CONTEXT['pastes_count'] = Paste.get_pastes_count()
|
|
|
|
GLOBAL_CONTEXT['refresh_counter'] = now
|
2012-05-21 20:21:06 +04:00
|
|
|
|
2012-05-14 19:17:49 +04:00
|
|
|
return {'status': 'ok',
|
|
|
|
'paste': paste.uuid}
|
|
|
|
|
|
|
|
return {'status': 'error',
|
2013-01-19 20:05:37 +04:00
|
|
|
'message': u"Serveur error: the paste couldn't be saved. "
|
|
|
|
u"Please try later."}
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
|
|
|
|
@app.route('/paste/:paste_id')
|
|
|
|
@view('paste')
|
|
|
|
def display_paste(paste_id):
|
|
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
keep_alive = False
|
|
|
|
try:
|
|
|
|
paste = Paste.load(paste_id)
|
2014-07-07 03:23:56 +04:00
|
|
|
if not paste.is_alive():
|
2012-05-14 19:17:49 +04:00
|
|
|
paste.delete()
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2012-05-18 16:29:52 +04:00
|
|
|
@app.route('/static/<filename:path>')
|
|
|
|
def server_static(filename):
|
|
|
|
return static_file(filename, root=settings.STATIC_FILES_ROOT)
|
|
|
|
|
|
|
|
|
2013-04-29 22:17:37 +04:00
|
|
|
def get_app(debug=None, settings_file='',
|
|
|
|
compressed_static=None, settings=settings):
|
2012-05-19 00:18:40 +04:00
|
|
|
"""
|
2013-01-19 20:05:37 +04:00
|
|
|
Return a tuple (settings, app) configured using passed
|
|
|
|
parameters and/or a setting file.
|
2012-05-19 00:18:40 +04:00
|
|
|
"""
|
2013-07-03 13:12:12 +04:00
|
|
|
|
|
|
|
settings_file = settings_file or os.environ.get('ZEROBIN_SETTINGS_FILE')
|
|
|
|
|
2012-05-14 19:17:49 +04:00
|
|
|
if settings_file:
|
2013-07-03 13:12:12 +04:00
|
|
|
settings.update_with_file(os.path.realpath(settings_file))
|
2012-05-14 19:17:49 +04:00
|
|
|
|
2013-04-29 22:17:37 +04:00
|
|
|
if settings.PASTE_ID_LENGTH < 4:
|
|
|
|
raise SettingsValidationError('PASTE_ID_LENGTH cannot be lower than 4')
|
|
|
|
|
2012-05-17 13:13:40 +04:00
|
|
|
if compressed_static is not None:
|
|
|
|
settings.COMPRESSED_STATIC_FILES = compressed_static
|
|
|
|
|
|
|
|
if debug is not None:
|
|
|
|
settings.DEBUG = debug
|
|
|
|
|
2012-05-14 19:17:49 +04:00
|
|
|
# make sure the templates can be loaded
|
|
|
|
for d in reversed(settings.TEMPLATE_DIRS):
|
|
|
|
bottle.TEMPLATE_PATH.insert(0, d)
|
|
|
|
|
2012-05-19 00:18:40 +04:00
|
|
|
if settings.DEBUG:
|
|
|
|
bottle.debug(True)
|
|
|
|
|
|
|
|
return settings, app
|
|
|
|
|
|
|
|
|
|
|
|
@clize.clize(coerce={'debug': bool, 'compressed_static': bool})
|
2013-01-19 20:05:37 +04:00
|
|
|
def runserver(host='', port='', debug=None, user='', group='',
|
2013-04-29 22:17:37 +04:00
|
|
|
settings_file='', compressed_static=None,
|
2014-07-07 03:23:56 +04:00
|
|
|
version=False, paste_id_length=None, server="cherrypy", purge=False):
|
2012-05-19 00:18:40 +04:00
|
|
|
|
|
|
|
if version:
|
|
|
|
print '0bin V%s' % settings.VERSION
|
|
|
|
sys.exit(0)
|
|
|
|
|
2014-07-07 03:23:56 +04:00
|
|
|
if purge:
|
|
|
|
print 'Purging expired pastes excluding burn notices'
|
|
|
|
purged_pastes = Paste.purge()
|
|
|
|
print 'Done (%s pastes removed)' % (purged_pastes)
|
|
|
|
sys.exit(0)
|
|
|
|
|
2012-05-19 00:18:40 +04:00
|
|
|
settings.HOST = host or settings.HOST
|
|
|
|
settings.PORT = port or settings.PORT
|
|
|
|
settings.USER = user or settings.USER
|
|
|
|
settings.GROUP = group or settings.GROUP
|
2013-04-29 22:17:37 +04:00
|
|
|
settings.PASTE_ID_LENGTH = paste_id_length or settings.PASTE_ID_LENGTH
|
|
|
|
|
|
|
|
try:
|
|
|
|
_, app = get_app(debug, settings_file, compressed_static, settings=settings)
|
|
|
|
except SettingsValidationError as err:
|
|
|
|
print >>sys.stderr, 'Configuration error: %s' % err.message
|
|
|
|
sys.exit(1)
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
thread.start_new_thread(drop_privileges, (settings.USER, settings.GROUP))
|
|
|
|
|
|
|
|
if settings.DEBUG:
|
2012-05-19 00:18:40 +04:00
|
|
|
run(app, host=settings.HOST, port=settings.PORT, reloader=True,
|
|
|
|
server="cherrypy")
|
2012-05-14 19:17:49 +04:00
|
|
|
else:
|
2013-01-19 20:05:37 +04:00
|
|
|
run(app, host=settings.HOST, port=settings.PORT, server="cherrypy")
|
2012-05-14 19:17:49 +04:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
clize.run(runserver)
|