summaryrefslogtreecommitdiffstats
path: root/pyload/web/api_app.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyload/web/api_app.py')
-rw-r--r--pyload/web/api_app.py154
1 files changed, 154 insertions, 0 deletions
diff --git a/pyload/web/api_app.py b/pyload/web/api_app.py
new file mode 100644
index 000000000..39747d5ea
--- /dev/null
+++ b/pyload/web/api_app.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from traceback import format_exc, print_exc
+
+from bottle import route, request, response, HTTPError, parse_auth
+
+from utils import set_session, get_user_api, add_json_header
+from webinterface import PYLOAD, session
+
+from pyload.Api import ExceptionObject
+from pyload.remote.json_converter import loads, dumps, BaseEncoder
+from pyload.utils import remove_chars
+
+# used for gzip compression
+try:
+ import gzip
+ from cStringIO import StringIO
+except ImportError:
+ gzip = None
+ StringIO = None
+
+# gzips response if supported
+def json_response(obj):
+ accept = 'gzip' in request.headers.get('Accept-Encoding', '')
+ result = dumps(obj)
+ # don't compress small string
+ if gzip and accept and len(result) > 500:
+ response.headers['Vary'] = 'Accept-Encoding'
+ response.headers['Content-Encoding'] = 'gzip'
+ zbuf = StringIO()
+ zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
+ zfile.write(result)
+ zfile.close()
+ return zbuf.getvalue()
+
+ return result
+
+
+# returns http error
+def error(code, msg):
+ return HTTPError(code, dumps(msg), **dict(response.headers))
+
+# accepting positional arguments, as well as kwargs via post and get
+# only forbidden path symbol are "?", which is used to separate GET data and #
+@route("/api/<func><args:re:[^#?]*>")
+@route("/api/<func><args:re:[^#?]*>", method="POST")
+def call_api(func, args=""):
+ add_json_header(response)
+
+ s = request.environ.get('beaker.session')
+ # Accepts standard http auth
+ auth = parse_auth(request.get_header('Authorization', ''))
+ if 'session' in request.POST or 'session' in request.GET:
+ # removes "' so it works on json strings
+ s = s.get_by_id(remove_chars(request.params.get('session'), "'\""))
+ elif auth:
+ user = PYLOAD.checkAuth(auth[0], auth[1], request.environ.get('REMOTE_ADDR', None))
+ # if auth is correct create a pseudo session
+ if user: s = {'uid': user.uid}
+
+ api = get_user_api(s)
+ if not api:
+ return error(401, "Unauthorized")
+
+ if not PYLOAD.isAuthorized(func, api.user):
+ return error(403, "Forbidden")
+
+ if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
+ print "Invalid API call", func
+ return error(404, "Not Found")
+
+ # TODO: possible encoding
+ # TODO Better error codes on invalid input
+
+ args = [loads(unquote(arg)) for arg in args.split("/")[1:]]
+ kwargs = {}
+
+ # accepts body as json dict
+ if request.json:
+ kwargs = request.json
+
+ # file upload, reads whole file into memory
+ for name, f in request.files.iteritems():
+ kwargs["filename"] = f.filename
+ kwargs[name] = f.value
+
+ # convert arguments from json to obj separately
+ for x, y in request.params.iteritems():
+ try:
+ if not x or not y or x == "session": continue
+ kwargs[x] = loads(unquote(y))
+ except Exception, e:
+ # Unsupported input
+ msg = "Invalid Input %s, %s : %s" % (x, y, e.message)
+ print_exc()
+ print msg
+ return error(415, msg)
+
+ try:
+ result = getattr(api, func)(*args, **kwargs)
+ # null is invalid json response
+ if result is None: result = True
+ return json_response(result)
+
+ except ExceptionObject, e:
+ return error(400, e.message)
+ except Exception, e:
+ print_exc()
+ return error(500, {"error": e.message, "traceback": format_exc()})
+
+
+@route("/api/login")
+@route("/api/login", method="POST")
+def login():
+ add_json_header(response)
+
+ username = request.params.get("username")
+ password = request.params.get("password")
+
+ user = PYLOAD.checkAuth(username, password, request.environ.get('REMOTE_ADDR', None))
+
+ if not user:
+ return json_response(False)
+
+ s = set_session(request, user)
+
+ # get the session id by dirty way, documentations seems wrong
+ try:
+ sid = s._headers["cookie_out"].split("=")[1].split(";")[0]
+ # reuse old session id
+ except:
+ sid = request.get_header(session.options['key'])
+
+ result = BaseEncoder().default(user)
+ result["session"] = sid
+
+ # Return full user information if needed
+ if request.params.get('user', None):
+ return dumps(result)
+
+ return json_response(sid)
+
+
+@route("/api/logout")
+@route("/api/logout", method="POST")
+def logout():
+ add_json_header(response)
+
+ s = request.environ.get('beaker.session')
+ s.delete()
+
+ return json_response(True)