summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2017-04-09 09:02:09 +0200
committerJon Bergli Heier <snakebite@jvnv.net>2017-04-09 09:02:09 +0200
commitb36f9c05071ea549ed59e703270fcf223b60df03 (patch)
tree8992c6bcaa5b0d64cbd589588b2539523125548c
parentaf750a6598d53b8a5cb58092dd5b523ea7e967ca (diff)
Major rewrite to use jab/oauth.
Highlights: - Uses the oauth branch of jab. - Changed design to use bootstrap. - Some minor changes to functionality in file uploading and listing. - API is currently disabled and incomplete.
-rw-r--r--.gitignore6
-rw-r--r--db.py80
-rwxr-xr-xfbin.py626
-rw-r--r--fbin/__init__.py39
-rw-r--r--fbin/api.py62
-rw-r--r--fbin/db.py121
-rwxr-xr-xfbin/fbin.py353
-rw-r--r--fbin/login.py92
-rw-r--r--fbin/monkey.py25
-rw-r--r--fbin/static/css/bootstrap.min.css6
-rw-r--r--fbin/static/css/style.css18
-rw-r--r--fbin/static/fonts/glyphicons-halflings-regular.eotbin0 -> 20127 bytes
-rw-r--r--fbin/static/fonts/glyphicons-halflings-regular.svg288
-rw-r--r--fbin/static/fonts/glyphicons-halflings-regular.ttfbin0 -> 45404 bytes
-rw-r--r--fbin/static/fonts/glyphicons-halflings-regular.woffbin0 -> 23424 bytes
-rw-r--r--fbin/static/fonts/glyphicons-halflings-regular.woff2bin0 -> 18028 bytes
-rw-r--r--fbin/static/img/no-thumbnail.png (renamed from static/no-thumbnail.png)bin1546 -> 1546 bytes
-rw-r--r--fbin/static/js/bootstrap-filestyle.min.js1
-rw-r--r--fbin/static/js/bootstrap.min.js7
-rw-r--r--fbin/static/js/jquery.lazy.min.js2
-rw-r--r--fbin/static/js/jquery.min.js4
-rw-r--r--fbin/templates/base.html70
-rw-r--r--fbin/templates/file-modals.html59
-rw-r--r--fbin/templates/files.html73
-rw-r--r--fbin/templates/help.html30
-rw-r--r--fbin/templates/images.html29
-rw-r--r--fbin/templates/modal.html17
-rw-r--r--fbin/templates/upload.html25
-rw-r--r--fbin/templates/uploaded.html14
-rw-r--r--run.py4
-rw-r--r--static/jquery-2.1.0.min.js4
-rw-r--r--static/jquery.lazy.min.js2
-rw-r--r--static/style.css28
-rw-r--r--templates/__init__.py9
-rw-r--r--templates/base.tmpl48
-rw-r--r--templates/changepass.tmpl16
-rw-r--r--templates/delete.tmpl9
-rw-r--r--templates/help.tmpl20
-rw-r--r--templates/images.tmpl28
-rw-r--r--templates/login.tmpl24
-rw-r--r--templates/my.tmpl14
-rw-r--r--templates/register.tmpl16
-rw-r--r--templates/upload.tmpl23
-rw-r--r--templates/uploaded.tmpl16
44 files changed, 1342 insertions, 966 deletions
diff --git a/.gitignore b/.gitignore
index 268ebe8..1a01755 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
*.swp
*.pyc
-settings.py
+*.cfg
+*.pem
+__pycache__
/files
/thumbs
-/templates/*.py
-!/templates/__init__.py
diff --git a/db.py b/db.py
deleted file mode 100644
index dd24204..0000000
--- a/db.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Index, ForeignKey, Boolean
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import sessionmaker, relation, backref
-from sqlalchemy.orm.exc import NoResultFound
-from sqlalchemy.exc import IntegrityError
-from sqlalchemy.sql import and_
-import settings, os, mimetypes
-
-engine = create_engine(settings.db_path)
-
-Base = declarative_base(bind = engine)
-
-class User(Base):
- __tablename__ = 'users'
-
- id = Column(Integer, primary_key = True)
- username = Column(String, unique = True, index = True)
- jab_id = Column(String(12), unique = True, index = True)
- files = relation('File', backref = 'user', order_by = 'File.date.desc()')
-
- def __init__(self, username, jab_id):
- self.username = username
- self.jab_id = jab_id
-
-class File(Base):
- __tablename__ = 'files'
-
- id = Column(Integer, primary_key = True)
- hash = Column(String, unique = True, index = True)
- file_hash = Column(String, unique = True, index = True)
- filename = Column(String)
- date = Column(DateTime)
- user_id = Column(Integer, ForeignKey('users.id'), nullable = True)
- ip = Column(String)
- accessed = Column(DateTime)
-
- def __init__(self, hash, file_hash, filename, date, user_id = None, ip = None):
- self.hash = hash
- self.file_hash = file_hash
- self.filename = filename
- self.date = date
- self.user_id = user_id
- self.ip = ip
-
- @staticmethod
- def pretty_size(size):
- suffixes = (('B', 2**10), ('KiB', 2**20), ('MiB', 2**30), ('GiB', 2**40), ('TiB', 2**50))
- for suf, lim in suffixes:
- if size > lim:
- continue
- else:
- return '%s %s' % (str(round(size/float(lim/2**10), 2)), suf)
-
- def get_path(self):
- return os.path.join(settings.file_directory, self.hash + os.path.splitext(self.filename)[1])
-
- def get_size(self):
- return os.path.getsize(self.get_path())
-
- def html(self):
- return u'<a href="{root}f/{hash}/{filename}">{filename}</a> ' \
- '<sup><a href="{root}f/{hash}">1</a> <a href="{root}f/{hash}{ext}">2</a> <a href="{root}d/{hash}">del</a></sup> ' \
- '<span class="file-info">({size}) on {date}'.format(
- root = settings.virtual_root, hash = self.hash, filename = self.filename, ext = os.path.splitext(self.filename)[1],
- size = self.pretty_size(self.get_size()), date = self.date.strftime('%Y-%m-%d %H:%M:%S UTC'))
-
- def get_mime_type(self):
- return mimetypes.guess_type(self.filename, strict = False)[0] or 'application/octet-stream'
-
- def is_image(self):
- return self.get_mime_type().startswith('image')
-
- @property
- def ext(self):
- return os.path.splitext(self.filename)[1]
-
-Base.metadata.create_all()
-Session = sessionmaker(bind = engine, autoflush = True, autocommit = False)
-
-# vim: noet ts=4
diff --git a/fbin.py b/fbin.py
deleted file mode 100755
index 0f91330..0000000
--- a/fbin.py
+++ /dev/null
@@ -1,626 +0,0 @@
-#!/usr/bin/env python2
-
-import templates
-import settings, db, os, random, datetime, mimetypes, cgi, tempfile, hashlib, Cookie, urllib, subprocess, json
-from PIL import Image
-import jab.client
-
-base62_alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
-rfc1123_format = '%a, %d %b %Y %H:%M:%S +0000'
-rfc1123_format_tzname = '%a, %d %b %Y %H:%M:%S %Z'
-
-if not os.path.isdir(settings.file_directory):
- os.mkdir(settings.file_directory)
-
-if not os.path.isdir(settings.thumb_directory):
- os.mkdir(settings.thumb_directory)
-
-try:
- # Throws OSError if mogrify doesn't exist.
- subprocess.call(['mogrify', '-quiet'])
-except OSError:
- has_mogrify = False
-else:
- has_mogrify = True
-
-class BinError(Exception): pass
-class InvalidCookieError(BinError): pass
-class InactiveLoginError(BinError): pass
-
-class FileUploadFieldStorage(cgi.FieldStorage):
- def make_file(self, binary = None):
- # Make a temporary file in the destination directory, which will be renamed on completion.
- return tempfile.NamedTemporaryFile(prefix = 'upload_', dir = settings.file_directory, delete = True)
-
-class Application(object):
- def get_or_create_user(self, username, jab_id):
- session = db.Session()
- try:
- return session.query(db.User).filter(db.User.jab_id == jab_id).one()
- except db.NoResultFound:
- try:
- user = db.User(username, jab_id)
- session.add(user)
- session.commit()
- session.refresh(user)
- return user
- except db.IntegrityError:
- return None
- finally:
- session.close()
-
- def get_user_by_jab_id(self, jab_id):
- session = db.Session()
- try:
- return session.query(db.User).filter(db.User.jab_id == jab_id).one()
- except db.NoResultFound:
- return None
- finally:
- session.close()
-
- def get_user_by_id(self, uid):
- session = db.Session()
- try:
- return session.query(db.User).filter(db.User.id == uid).one()
- except db.NoResultFound:
- return None
- finally:
- session.close()
-
- def get_file(self, hash, update_accessed = False):
- session = db.Session()
- try:
- f = session.query(db.File).filter(db.File.hash == hash).one()
- if update_accessed:
- f.accessed = datetime.datetime.utcnow()
- session.add(f)
- session.commit()
- # Refresh after field update.
- session.refresh(f)
- return f
- except db.NoResultFound:
- return None
- finally:
- session.close()
-
- def add_file(self, path, filename, file_hash, user = None, ip = None):
- hash = ''.join(random.choice(base62_alphabet) for x in xrange(5))
- new_path = os.path.join(settings.file_directory, hash + os.path.splitext(filename)[1])
- os.rename(path, new_path)
- if hasattr(settings, 'destination_mode'):
- os.chmod(new_path, settings.destination_mode)
-
- session = db.Session()
- try:
- file = db.File(hash, file_hash, filename, datetime.datetime.utcnow(), user.id if user else None, ip)
- session.add(file)
- session.commit()
- finally:
- session.close()
-
- return hash
-
- def get_files(self, user):
- session = db.Session()
- try:
- session.add(user)
- files = user.files
- except db.NoResultFound:
- return []
- finally:
- session.close()
-
- return files
-
- def validate_cookie(self, environ):
- cookie = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None)
- if not cookie or not 'token' in cookie:
- return
-
- token = cookie['token'].value
- try:
- user = self.jab.get_user_by_token(token, settings.jab_identifier, environ['REMOTE_ADDR'])
- user = self.get_user_by_id(user['token_data']['user_id'])
- except jab.client.InvalidCredentialsError:
- user = None
- return user
-
- def get_file_by_file_hash(self, file_hash):
- session = db.Session()
- try:
- return session.query(db.File).filter(db.File.file_hash == file_hash).one()
- except db.NoResultFound:
- return None
- finally:
- session.close()
-
- def delete_file(self, file):
- session = db.Session()
- try:
- session.delete(file)
- session.commit()
- os.unlink(file.get_path())
- thumbfile = os.path.join(settings.thumb_directory, file.hash + '.jpg')
- if os.path.exists(thumbfile):
- os.unlink(thumbfile)
- except:
- raise
- finally:
- session.close()
-
- def redirect(self, environ, start_response, dest):
- start_response('302 Found', [('Location', settings.virtual_root + dest)])
- return []
-
- def not_modified(self, environ, date):
- if not 'HTTP_IF_MODIFIED_SINCE' in environ:
- return False
- try:
- mod_since_date = datetime.datetime.strptime(environ['HTTP_IF_MODIFIED_SINCE'], rfc1123_format)
- except ValueError:
- # some clients use timezone names (eg. GMT) instead of numeric timezones
- mod_since_date = datetime.datetime.strptime(environ['HTTP_IF_MODIFIED_SINCE'], rfc1123_format_tzname)
- return date == mod_since_date
-
- def file(self, environ, start_response, path):
- hash = path[1]
- if '.' in hash:
- hash = hash.split('.')[0]
- file = self.get_file(hash, True)
- filename = file.get_path() if file else None
- if file == None or filename == None:
- start_response('404 Not Found', [('Content-Type', 'text/html')])
- return ['<h1>Not Found</h1><p>The file you requested does not exist.</p>']
-
- # strip microseconds
- if self.not_modified(environ, file.date - datetime.timedelta(microseconds = file.date.microsecond)):
- start_response('304 Not Modified', [('Last-Modified', file.date.strftime(rfc1123_format))])
- return []
-
- do_range = 'HTTP_RANGE' in environ
- if do_range:
- file_range = environ['HTTP_RANGE'].split('bytes=')[1]
-
- mime = mimetypes.guess_type(file.filename, strict = False)[0] or 'application/octet-stream'
-
- # X-Sendfile handling
- if settings.use_xsendfile:
- headers = [('Content-Type', mime), ('Last-Modified', file.date.strftime(rfc1123_format))]
- if do_range:
- headers.append(('X-Sendfile2', '{filename} {range}'.format(filename = urllib.quote(filename.encode('utf8')), range = file_range)))
- if file_range.endswith('-'):
- file_range += str(os.path.getsize(filename)-1)
- headers.append(('Content-Range', 'bytes {range}/{size}'.format(range = file_range, size = os.path.getsize(filename))))
- status = '206 Partial Content'
- else:
- headers.append(('X-Sendfile', filename.encode('utf8')))
- status = '200 OK'
- start_response(status, headers)
- return []
-
- # Range handling
- if do_range:
- start, end = [int(x or 0) for x in file_range.split('-')]
- size = os.path.getsize(filename)
-
- if end == 0:
- end = size-1
-
- write_out = start_response('206 Partial Content', [('Content-Type', mime),
- ('Content-Range', 'bytes {start}-{end}/{size}'.format(start = start, end = end, size = size)),
- ('Content-Length', str(end - start + 1)), ('Last-Modified', file.date.strftime(rfc1123_format))])
-
- f = open(filename, 'rb')
- f.seek(start)
- remaining = end-start+1
- s = f.read(min(remaining, 1024))
- while s:
- write_out(s)
- remaining -= len(s)
- s = f.read(min(remaining, 1024))
- return []
-
- start_response('200 OK', [('Content-Type', mime), ('Content-Length', str(os.path.getsize(filename))),
- ('Last-Modified', file.date.strftime(rfc1123_format))])
- return open(filename, 'rb')
-
- def upload(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- tempfile.tempdir = settings.file_directory
- form = FileUploadFieldStorage(fp = environ['wsgi.input'], environ = environ)
- if environ['REQUEST_METHOD'] != 'POST' or not 'file' in form or not 'filename' in form:
- if user or settings.allow_anonymous_uploads:
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.upload(searchList = {'settings': settings, 'user': user}))]
- else:
- return self.redirect(environ, start_response, 'l')
-
- if not user and not settings.allow_anonymous_uploads:
- if hasattr(form['file'].file, 'delete'):
- form['file'].file.delete = True
- start_response('403 Forbidden', [('Content-Type', 'text/plain')])
- return ['Anonymous uploads are disabled by the administrator.']
-
- filename = form.getvalue('filename')
- temp = form['file'].file
- if hasattr(temp, 'delete'):
- temp.delete = False
-
- # If the name attribute is missing, assume this is a StringIO object, then create a new temporary file and copy the contents.
- if not hasattr(temp, 'name'):
- new_temp = tempfile.NamedTemporaryFile(prefix = 'upload_', dir = settings.file_directory, delete = False)
- new_temp.write(temp.getvalue())
- new_temp.seek(0)
- temp = new_temp
-
- m = hashlib.md5()
- s = temp.read(128)
- while len(s):
- m.update(s)
- s = temp.read(128)
- temp.close()
- file_hash = m.hexdigest()
-
- f = self.get_file_by_file_hash(file_hash)
- # TODO: Currently users uploading existing files won't get their files added to their account.
- if f:
- hash = f.hash
- else:
- # temp.name will be moved to the destination filename
- hash = self.add_file(temp.name, filename, file_hash, user, environ['REMOTE_ADDR'])
- # This avoids silly "not bound to a Session" errors when trying to use a newly added file object.
- f = self.get_file(hash)
-
- # If temp.name still exists, we most likely uploaded a file whose file hash already exists, so just delete the file.
- if os.path.exists(temp.name):
- os.unlink(temp.name)
-
- mime = f.get_mime_type()
- # TODO: Apparently TIFF also supports EXIF, test this.
- if has_mogrify and mime == 'image/jpeg':
- # NOTE: PIL doesn't support lossless rotation, so we call mogrify to do this.
- # NOTE: This changes the file, so the file_hash applies to the ORIGINAL file contents only.
- # NOTE: The file hash is only used to detect duplicates when uploading, so this should not be a problem.
- subprocess.call(['mogrify', '-auto-orient', f.get_path()])
-
- if 'api' in form:
- start_response('200 OK', [('Content-Type', 'text/plain')])
- return ['OK {hash}'.format(hash = hash)]
- else:
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.uploaded(searchList = {
- 'settings': settings,
- 'user': user,
- 'hash': hash,
- 'filename': filename,
- 'ext': os.path.splitext(filename)[1],
- 'scheme': environ['wsgi.url_scheme'],
- 'host': environ['HTTP_HOST'],
- }))]
-
- def login(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ)
- if not user and not 'request_token' in form and not 'verified' in form:
- from urlparse import urljoin
- request_token = self.jab.generate_request_token(settings.jab_identifier, settings.jab_name,
- urljoin('%s://%s' % (environ['wsgi.url_scheme'], environ['HTTP_HOST']), environ['PATH_INFO']))
- if isinstance(request_token, unicode):
- request_token = request_token.encode('utf-8')
- start_response('302 Found', [('Location', urljoin(settings.jab_web_url, 'verify/' + request_token))])
- return []
- if 'request_token' in form and 'verified' in form:
- if form.getvalue('verified') == '0':
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.login(searchList = {
- 'settings': settings,
- 'user': None,
- 'error': 'Login was declined.',
- 'loggedin': False,
- 'next': form.getvalue('next'),
- }))]
- request_token = form.getvalue('request_token')
- try:
- token = self.jab.request_user_token(request_token, settings.jab_identifier, settings.jab_name)
- jab_user = self.jab.get_user_by_token(token, settings.jab_identifier, environ['REMOTE_ADDR'])
- except jab.client.InvalidCredentialsError:
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.login(searchList = {
- 'settings': settings,
- 'user': None,
- 'error': 'Failed to request login: invalid token or user.',
- 'loggedin': False,
- 'next': form.getvalue('next'),
- }))]
- user = self.get_or_create_user(jab_user['username'], jab_user['_id'])
- if not user:
- start_response('500 Internal Server Error', [])
- return []
- self.jab.set_token_data(token, settings.jab_identifier, {'user_id': user.id})
- c = Cookie.SimpleCookie()
- c['token'] = token
- start_response('200 OK', [
- ('Content-Type', 'text/html'),
- ('Set-Cookie', c['token'].OutputString())
- ])
- return [str(templates.login(searchList = {
- 'settings': settings,
- 'user': user,
- 'error': None,
- 'loggedin': True,
- 'next': form.getvalue('next'),
- }))]
-
- if user:
- rememberme = 'rememberme' in form
- forever = 'forever' in form
-
- cookie = Cookie.SimpleCookie(environ['HTTP_COOKIE'])
- token = cookie['token'].value
- c = Cookie.SimpleCookie()
- c['token'] = token
-
- dt = datetime.datetime.utcnow() + datetime.timedelta(days = 30)
- expires = dt.strftime('%a, %d-%b-%y %H:%M:%S GMT')
- if rememberme:
- c['token']['expires'] = expires
- if forever:
- c['forever'] = 1
- c['forever']['expires'] = expires
-
- # FIXME: This field is lost when we redirect to jab.
- next = form.getvalue('next')
- headers = [
- ('Location', next if next else (settings.virtual_root + 'u')),
- ('Set-Cookie', c['token'].OutputString())]
- if 'forever' in c:
- headers.append(('Set-Cookie', c['forever'].OutputString()))
- start_response('302 Found', headers)
- return []
-
- start_response('404 Not Found', [])
- return []
-
- def register(self, environ, start_response, path):
- start_response('302 Found', [('Location', settings.jab_web_url + 'register')])
- return []
-
- def logout(self, environ, start_response, path):
- c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None)
- if c and 'token' in c:
- try:
- self.jab.expire_user_token(c['token'].value, settings.jab_identifier)
- except:
- pass
-
- c = Cookie.SimpleCookie()
- expires = datetime.datetime.utcfromtimestamp(0).strftime('%a, %d-%b-%y %H:%M:%S GMT')
- c['forever'] = 0
- c['forever']['expires'] = expires
- c['token'] = 0
- c['token']['expires'] = expires
- start_response('302 Found', [
- ('Set-Cookie', c['forever'].OutputString()),
- ('Set-Cookie', c['token'].OutputString()),
- ('Location', settings.virtual_root)])
- return []
-
- def changepass(self, environ, start_response, path):
- start_response('302 Found', [('Location', settings.jab_web_url + 'changepass')])
- return []
-
- def static(self, environ, start_response, path):
- filename = path[1]
- if not filename in ('style.css', 'no-thumbnail.png', 'favicon.ico', 'jquery-2.1.0.min.js', 'jquery.lazy.min.js'):
- start_response('404 Not Found', [])
- return []
-
- filepath = os.path.join(settings.static_root, filename)
- if not os.path.exists(filepath):
- start_response('404 Not Found', [])
- return []
- mime = mimetypes.guess_type(filename, strict = False)[0] or 'application/octet-stream'
- start_response('200 OK', [('Content-Type', mime)])
- return open(filepath, 'rb')
-
- def help(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.help(searchList = {
- 'settings': settings,
- 'user': user,
- 'scheme': environ['wsgi.url_scheme'],
- 'host': environ['HTTP_HOST'],
- }))]
-
- def my_files(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- if user == None:
- start_response('302 Found', [('Location', settings.virtual_root + 'l?' + urllib.urlencode({'next': settings.virtual_root + 'm'}))])
- return []
- files = self.get_files(user)
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.my(searchList = {
- 'settings': settings,
- 'user': user,
- 'files': files,
- 'total_size': db.File.pretty_size(sum([f.get_size() for f in files])),
- }))]
-
- def images(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- if user == None:
- start_response('302 Found', [('Location', settings.virtual_root + 'l?' + urllib.urlencode({'next': settings.virtual_root + 'i'}))])
- return []
- files = [f for f in self.get_files(user) if f.is_image()]
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.images(searchList = {
- 'settings': settings,
- 'user': user,
- 'files': files,
- 'total_size': db.File.pretty_size(sum([f.get_size() for f in files])),
- }))]
-
- def thumb(self, environ, start_response, path):
- hash = path[1]
- thumbfile = os.path.join(settings.thumb_directory, hash + '.jpg')
- if not os.access(thumbfile, os.F_OK):
- file = self.get_file(hash)
- try:
- im = Image.open(file.get_path())
- except IOError:
- # We can't generate a thumbnail for this file, just say it doesn't exist.
- start_response('404 Not Found', [])
- return []
- # Check for valid JPEG modes.
- if im.mode not in ('1', 'L', 'RGB', 'RGBA', 'RGBX', 'CMYK', 'YCbCr'):
- im = im.convert('RGB')
- im.thumbnail(settings.thumb_size, Image.ANTIALIAS)
- im.save(thumbfile)
-
- date = datetime.datetime.utcfromtimestamp(os.path.getmtime(thumbfile))
- if self.not_modified(environ, date):
- start_response('304 Not Modified', [('Last-Modified', date.strftime(rfc1123_format))])
- return []
-
- start_response('200 OK', [('Content-Type', 'image/jpeg'), ('Last-Modified', date.strftime(rfc1123_format))])
- return open(thumbfile, 'rb')
-
- def delete(self, environ, start_response, path):
- user = self.validate_cookie(environ)
- if user == None:
- start_response('200 OK', [('Content-Type', 'text/html')])
- return ['Not logged in.']
- hash = path[1]
- file = self.get_file(hash)
- if file == None:
- start_response('404 Not Found', [('Content-Type', 'text/html')])
- return ['<h1>Not Found</h1><p>The file you requested does not exist.</p>']
- if file.user_id != user.id:
- start_response('403 Forbidden', [('Content-Type', 'text/html')])
- return ['<h1>Forbidden</h1><p>You are not allowed to delete this file.</p>']
- if environ['REQUEST_METHOD'] == 'POST':
- try:
- self.delete_file(file)
- except Exception as e:
- start_response('500 Internal Error', [('Content-Type', 'text/html')])
- return ['Failed to delete file {filename} ({error}).'.format(filename = file.filename, error = str(e))]
- else:
- start_response('302 Found', [('Location', settings.virtual_root + 'u')])
- return []
- else:
- start_response('200 OK', [('Content-Type', 'text/html')])
- return [str(templates.delete(searchList = {
- 'settings': settings,
- 'user': user,
- 'hash': hash,
- 'filename': file.filename,
- }))]
-
- def api(self, environ, start_response, path):
- def error(msg):
- start_response('200 OK', [('Content-Type', 'application/json')])
- return [json.dumps({'status': False, 'message': msg})]
- user = self.validate_cookie(environ)
- form = cgi.FieldStorage(fp = environ['wsgi.input'], environ = environ)
- method = form.getvalue('method')
- data = {'status': False, 'method': method, 'message': None}
- if method in ('list', 'images'):
- if user == None:
- return error('Not logged in')
- files = self.get_files(user)
- data['files'] = [
- {
- 'name': f.filename,
- 'hash': f.hash,
- 'date': int(f.date.strftime('%s')),
- 'size': f.get_size(),
- }
- for f in files if method == 'list' or (method == 'images' and f.is_image())
- ]
- data['status'] = True
- elif method == 'get_token':
- try:
- token = self.jab.generate_user_token(form['username'].value, form['password'].value, settings.jab_identifier, '%s (API)' % settings.jab_name)
- jab_user = self.jab.get_user_by_token(token, settings.jab_identifier, environ['REMOTE_ADDR'])
- user = self.get_or_create_user(jab_user['username'], jab_user['_id'])
- if not user:
- return error('Error fetching user data')
- self.jab.set_token_data(token, settings.jab_identifier, {'user_id': user.id})
- except:
- return error('Invalid credentials')
- data['token'] = token
- data['status'] = True
- elif method == 'expire_token':
- try:
- self.jab.expire_token(form['token'].value, settings.jab_identifier)
- except:
- pass
- data['status'] = True
- elif method == 'test_token':
- try:
- user = self.jab.get_user_by_token(form['token'].value, settings.jab_identifier, environ['REMOTE_ADDR'])
- except jab.client.InvalidCredentialsError:
- return error('Invalid token')
- except:
- return error('Error fetching user data')
- data['status'] = True
- else:
- data['message'] = 'Unknown method "%s"' % method
- start_response('200 OK', [('Content-Type', 'application/json')])
- return [json.dumps(data)]
-
- f = file
- u = upload
- l = login
- s = static
- h = help
- m = my_files
- i = images
- t = thumb
- o = logout
- r = register
- c = changepass
- d = delete
- a = api
-
- def __init__(self):
- self.jab = jab.client.JabClient(settings.jab_socket)
-
- def __call__(self, environ, start_response):
- def never_forget_wrapper(status, headers, exc_info = None):
- c = Cookie.SimpleCookie(environ['HTTP_COOKIE'] if 'HTTP_COOKIE' in environ else None)
- if 'forever' in c and c['forever'].value == '1' and not any(x[0] == 'Set-Cookie' for x in headers):
- dt = datetime.datetime.utcnow() + datetime.timedelta(days = 30)
- expires = dt.strftime('%a, %d-%b-%y %H:%M:%S GMT')
- for k in c.keys():
- c[k]['path'] = settings.virtual_root
- c[k]['expires'] = expires
- headers.append(('Set-Cookie', c[k].OutputString()))
- return start_response(status, headers, exc_info)
- try:
- path = environ['PATH_INFO'].split('/')[1:]
- module = path[0] if len(path) else ''
- if len(module) and module in 'fulshmitorcda':
- return getattr(self, module)(environ, never_forget_wrapper, path)
- elif path == ['favicon.ico']:
- return self.static(environ, start_response, ['s'] + path)
- else:
- start_response('302 Found', [('Location', settings.virtual_root + 'u')])
- return []
- except (InactiveLoginError, InvalidCookieError):
- return self.logout(environ, start_response, path)
-
-if __name__ == '__main__':
- import sys
- if len(sys.argv) == 3:
- from flup.server.fcgi import WSGIServer
- WSGIServer(Application(), bindAddress = (sys.argv[1], int(sys.argv[2]))).run()
- else:
- from wsgiref.simple_server import make_server, WSGIServer
- # enable IPv6
- WSGIServer.address_family |= 10
- http = make_server('', 8000, Application())
- http.serve_forever()
-
-# vim: noet ts=4
diff --git a/fbin/__init__.py b/fbin/__init__.py
new file mode 100644
index 0000000..bdeaa5d
--- /dev/null
+++ b/fbin/__init__.py
@@ -0,0 +1,39 @@
+from flask import Flask, url_for, Markup, request
+from flask_login import current_user
+from werkzeug.routing import BaseConverter
+
+app = Flask(__name__)
+app.config.from_pyfile('fbin.cfg')
+
+# Set up some custom converters. These are needed for file URLs to be properly parsed.
+
+clas