diff options
Diffstat (limited to 'pyload/webui/app')
-rw-r--r-- | pyload/webui/app/__init__.py | 3 | ||||
-rw-r--r-- | pyload/webui/app/api.py | 99 | ||||
-rw-r--r-- | pyload/webui/app/cnl.py | 172 | ||||
-rw-r--r-- | pyload/webui/app/json.py | 314 | ||||
-rw-r--r-- | pyload/webui/app/pyload.py | 526 | ||||
-rw-r--r-- | pyload/webui/app/utils.py | 124 |
6 files changed, 1238 insertions, 0 deletions
diff --git a/pyload/webui/app/__init__.py b/pyload/webui/app/__init__.py new file mode 100644 index 000000000..39d0fadd5 --- /dev/null +++ b/pyload/webui/app/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from pyload.webui.app import api, cnl, json, pyload diff --git a/pyload/webui/app/api.py b/pyload/webui/app/api.py new file mode 100644 index 000000000..99a7c2998 --- /dev/null +++ b/pyload/webui/app/api.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +import itertools +import traceback +import urllib + +import SafeEval +import bottle + +from pyload.Api import BaseObject +from pyload.utils import json +from pyload.webui import PYLOAD +from pyload.webui.app.utils import toDict, set_session + + +# json encoder that accepts TBase objects +class TBaseEncoder(json.JSONEncoder): + + def default(self, o): + if isinstance(o, BaseObject): + return toDict(o) + return json.JSONEncoder.default(self, o) + + +# accepting positional arguments, as well as kwargs via post and get +@bottle.route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>') +@bottle.route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>', method='POST') +def call_api(func, args=""): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + s = request.environ.get('beaker.session') + if 'session' in request.POST: + s = s.get_by_id(request.POST['session']) + + if not s or not s.get("authenticated", False): + return bottle.HTTPError(403, json.dumps("Forbidden")) + + if not PYLOAD.isAuthorized(func, {"role": s['role'], "permission": s['perms']}): + return bottle.HTTPError(401, json.dumps("Unauthorized")) + + args = args.split("/")[1:] + kwargs = {} + + for x, y in itertools.chain(request.GET.iteritems(), request.POST.iteritems()): + if x == "session": + continue + kwargs[x] = urllib.unquote(y) + + try: + return callApi(func, *args, **kwargs) + except Exception, e: + traceback.print_exc() + return bottle.HTTPError(500, json.dumps({"error": e.message, "traceback": traceback.format_exc()})) + + +def callApi(func, *args, **kwargs): + if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"): + print "Invalid API call", func + return bottle.HTTPError(404, json.dumps("Not Found")) + + result = getattr(PYLOAD, func)(*[SafeEval.const_eval(x) for x in args], + **dict((x, SafeEval.const_eval(y)) for x, y in kwargs.iteritems())) + + # null is invalid json response + return json.dumps(result or True, cls=TBaseEncoder) + + +# post -> username, password +@bottle.route('/api/login', method='POST') +def login(): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + user = request.forms.get("username") + password = request.forms.get("password") + + info = PYLOAD.checkAuth(user, password) + + if not info: + return json.dumps(False) + + s = set_session(request, info) + + # get the session id by dirty way, documentations seems wrong + try: + sid = s._headers['cookie_out'].split("=")[1].split(";")[0] + return json.dumps(sid) + except Exception: + return json.dumps(True) + + +@bottle.route('/api/logout') +def logout(): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + s = request.environ.get('beaker.session') + s.delete() diff --git a/pyload/webui/app/cnl.py b/pyload/webui/app/cnl.py new file mode 100644 index 000000000..465e087e5 --- /dev/null +++ b/pyload/webui/app/cnl.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import base64 +import binascii +import os +import re +import urllib + +import bottle + +from pyload.webui import PYLOAD, DL_ROOT, JS + + +try: + from Crypto.Cipher import AES +except Exception: + pass + + +def local_check(function): + + + def _view(*args, **kwargs): + if request.environ.get("REMOTE_ADDR", "0") in ("127.0.0.1", "localhost") \ + or request.environ.get("HTTP_HOST", "0") in ("127.0.0.1:9666", "localhost:9666"): + return function(*args, **kwargs) + else: + return bottle.HTTPError(403, "Forbidden") + + return _view + + +@bottle.route('/flash') +@bottle.route('/flash/<id>') +@bottle.route('/flash', method='POST') +@local_check +def flash(id="0"): + return "JDownloader\r\n" + + +@bottle.route('/flash/add', method='POST') +@local_check +def add(request): + package = request.POST.get('referer', None) + urls = filter(lambda x: x != "", request.POST['urls'].split("\n")) + + if package: + PYLOAD.addPackage(package, urls, 0) + else: + PYLOAD.generateAndAddPackages(urls, 0) + + return "" + + +@bottle.route('/flash/addcrypted', method='POST') +@local_check +def addcrypted(): + package = request.forms.get('referer', 'ClickNLoad Package') + dlc = request.forms['crypted'].replace(" ", "+") + + dlc_path = os.path.join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc") + with open(dlc_path, "wb") as dlc_file: + dlc_file.write(dlc) + + try: + PYLOAD.addPackage(package, [dlc_path], 0) + except Exception: + return bottle.HTTPError() + else: + return "success\r\n" + + +@bottle.route('/flash/addcrypted2', method='POST') +@local_check +def addcrypted2(): + package = request.forms.get("source", None) + crypted = request.forms['crypted'] + jk = request.forms['jk'] + + crypted = base64.standard_b64decode(urllib.unquote(crypted.replace(" ", "+"))) + if JS: + jk = "%s f()" % jk + jk = JS.eval(jk) + + else: + try: + jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1] + except Exception: + # Test for some known js functions to decode + if jk.find("dec") > -1 and jk.find("org") > -1: + org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1] + jk = list(org) + jk.reverse() + jk = "".join(jk) + else: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + + try: + Key = binascii.unhexlify(jk) + except Exception: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + return "failed" + + IV = Key + + obj = AES.new(Key, AES.MODE_CBC, IV) + result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n") + + result = filter(lambda x: x != "", result) + + try: + if package: + PYLOAD.addPackage(package, result, 0) + else: + PYLOAD.generateAndAddPackages(result, 0) + except Exception: + return "failed can't add" + else: + return "success\r\n" + + +@bottle.route('/flashgot_pyload') +@bottle.route('/flashgot_pyload', method='POST') +@bottle.route('/flashgot') +@bottle.route('/flashgot', method='POST') +@local_check +def flashgot(): + if request.environ['HTTP_REFERER'] not in ("http://localhost:9666/flashgot", "http://127.0.0.1:9666/flashgot"): + return bottle.HTTPError() + + autostart = int(request.forms.get('autostart', 0)) + package = request.forms.get('package', None) + urls = filter(lambda x: x != "", request.forms['urls'].split("\n")) + folder = request.forms.get('dir', None) + + if package: + PYLOAD.addPackage(package, urls, autostart) + else: + PYLOAD.generateAndAddPackages(urls, autostart) + + return "" + + +@bottle.route('/crossdomain.xml') +@local_check +def crossdomain(): + rep = "<?xml version=\"1.0\"?>\n" + rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" + rep += "<cross-domain-policy>\n" + rep += "<allow-access-from domain=\"*\" />\n" + rep += "</cross-domain-policy>" + return rep + + +@bottle.route('/flash/checkSupportForUrl') +@local_check +def checksupport(): + url = request.GET.get("url") + res = PYLOAD.checkURLs([url]) + supported = (not res[0][1] is None) + + return str(supported).lower() + + +@bottle.route('/jdcheck.js') +@local_check +def jdcheck(): + rep = "jdownloader=true;\n" + rep += "var version='9.581;'" + return rep diff --git a/pyload/webui/app/json.py b/pyload/webui/app/json.py new file mode 100644 index 000000000..a7fe8dcfb --- /dev/null +++ b/pyload/webui/app/json.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import shutil +import traceback + +import bottle + +from pyload.utils import decode, formatSize +from pyload.webui import PYLOAD +from pyload.webui.app.utils import login_required, render_to_response, toDict + + +def format_time(seconds): + seconds = int(seconds) + + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) + + +def get_sort_key(item): + return item['order'] + + +@bottle.route('/json/status') +@bottle.route('/json/status', method='POST') +@login_required('LIST') +def status(): + try: + status = toDict(PYLOAD.statusServer()) + status['captcha'] = PYLOAD.isCaptchaWaiting() + return status + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/links') +@bottle.route('/json/links', method='POST') +@login_required('LIST') +def links(): + try: + links = [toDict(x) for x in PYLOAD.statusDownloads()] + ids = [] + for link in links: + ids.append(link['fid']) + + if link['status'] == 12: + link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed'])) + elif link['status'] == 5: + link['percent'] = 0 + link['size'] = 0 + link['bleft'] = 0 + link['info'] = _("waiting %s") % link['format_wait'] + else: + link['info'] = "" + + data = {'links': links, 'ids': ids} + return data + except Exception, e: + traceback.print_exc() + return bottle.HTTPError() + + +@bottle.route('/json/packages') +@login_required('LIST') +def packages(): + print "/json/packages" + try: + data = PYLOAD.getQueue() + + for package in data: + package['links'] = [] + for file in PYLOAD.get_package_files(package['id']): + package['links'].append(PYLOAD.get_file_info(file)) + + return data + + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/package/<id:int>') +@login_required('LIST') +def package(id): + try: + data = toDict(PYLOAD.getPackageData(id)) + data['links'] = [toDict(x) for x in data['links']] + + for pyfile in data['links']: + if pyfile['status'] == 0: + pyfile['icon'] = "status_finished.png" + elif pyfile['status'] in (2, 3): + pyfile['icon'] = "status_queue.png" + elif pyfile['status'] in (9, 1): + pyfile['icon'] = "status_offline.png" + elif pyfile['status'] == 5: + pyfile['icon'] = "status_waiting.png" + elif pyfile['status'] == 8: + pyfile['icon'] = "status_failed.png" + elif pyfile['status'] == 4: + pyfile['icon'] = "arrow_right.png" + elif pyfile['status'] in (11, 13): + pyfile['icon'] = "status_proc.png" + else: + pyfile['icon'] = "status_downloading.png" + + tmp = data['links'] + tmp.sort(key=get_sort_key) + data['links'] = tmp + return data + + except Exception: + traceback.print_exc() + return bottle.HTTPError() + + +@bottle.route('/json/package_order/<ids>') +@login_required('ADD') +def package_order(ids): + try: + pid, pos = ids.split("|") + PYLOAD.orderPackage(int(pid), int(pos)) + return {"response": "success"} + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/abort_link/<id:int>') +@login_required('DELETE') +def abort_link(id): + try: + PYLOAD.stopDownloads([id]) + return {"response": "success"} + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/link_order/<ids>') +@login_required('ADD') +def link_order(ids): + try: + pid, pos = ids.split("|") + PYLOAD.orderFile(int(pid), int(pos)) + return {"response": "success"} + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/add_package') +@bottle.route('/json/add_package', method='POST') +@login_required('ADD') +def add_package(): + name = request.forms.get("add_name", "New Package").strip() + queue = int(request.forms['add_dest']) + links = decode(request.forms['add_links']) + links = links.split("\n") + pw = request.forms.get("add_password", "").strip("\n\r") + + try: + f = request.files['add_file'] + + if not name or name == "New Package": + name = f.name + + fpath = os.path.join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename) + with open(fpath, 'wb') as destination: + shutil.copyfileobj(f.file, destination) + links.insert(0, fpath) + except Exception: + pass + + name = name.decode("utf8", "ignore") + + links = map(lambda x: x.strip(), links) + links = filter(lambda x: x != "", links) + + pack = PYLOAD.addPackage(name, links, queue) + if pw: + pw = pw.decode("utf8", "ignore") + data = {"password": pw} + PYLOAD.setPackageData(pack, data) + + +@bottle.route('/json/move_package/<dest:int>/<id:int>') +@login_required('MODIFY') +def move_package(dest, id): + try: + PYLOAD.movePackage(dest, id) + return {"response": "success"} + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/edit_package', method='POST') +@login_required('MODIFY') +def edit_package(): + try: + id = int(request.forms.get("pack_id")) + data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"), + "folder": request.forms.get("pack_folder").decode("utf8", "ignore"), + "password": request.forms.get("pack_pws").decode("utf8", "ignore")} + + PYLOAD.setPackageData(id, data) + return {"response": "success"} + + except Exception: + return bottle.HTTPError() + + +@bottle.route('/json/set_captcha') +@bottle.route('/json/set_captcha', method='POST') +@login_required('ADD') +def set_captcha(): + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + try: + PYLOAD.setCaptchaResult(request.forms['cap_id'], request.forms['cap_result']) + except Exception: + pass + + task = PYLOAD.getCaptchaTask() + + if task.tid >= 0: + src = "data:image/%s;base64,%s" % (task.type, task.data) + + return {'captcha': True, 'id': task.tid, 'src': src, 'result_type': task.resultType} + else: + return {'captcha': False} + + +@bottle.route('/json/load_config/<category>/<section>') +@login_required("SETTINGS") +def load_config(category, section): + conf = None + if category == "general": + conf = PYLOAD.getConfigDict() + elif category == "plugin": + conf = PYLOAD.getPluginConfigDict() + + for key, option in conf[section].iteritems(): + if key in ("desc", "outline"): + continue + + if ";" in option['type']: + option['list'] = option['type'].split(";") + + option['value'] = decode(option['value']) + + return render_to_response("settings_item.html", {"sorted_conf": lambda c: sorted(c.items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0), + "skey": section, "section": conf[section]}) + + +@bottle.route('/json/save_config/<category>', method='POST') +@login_required("SETTINGS") +def save_config(category): + for key, value in request.POST.iteritems(): + try: + section, option = key.split("|") + except Exception: + continue + + if category == "general": category = "core" + + PYLOAD.setConfigValue(section, option, decode(value), category) + + +@bottle.route('/json/add_account', method='POST') +@login_required("ACCOUNTS") +def add_account(): + login = request.POST['account_login'] + password = request.POST['account_password'] + type = request.POST['account_type'] + + PYLOAD.updateAccount(type, login, password) + + +@bottle.route('/json/update_accounts', method='POST') +@login_required("ACCOUNTS") +def update_accounts(): + deleted = [] #: dont update deleted accs or they will be created again + + for name, value in request.POST.iteritems(): + value = value.strip() + if not value: + continue + + tmp, user = name.split(";") + plugin, action = tmp.split("|") + + if (plugin, user) in deleted: + continue + + if action == "password": + PYLOAD.updateAccount(plugin, user, value) + elif action == "time" and "-" in value: + PYLOAD.updateAccount(plugin, user, options={"time": [value]}) + elif action == "limitdl" and value.isdigit(): + PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]}) + elif action == "delete": + deleted.append((plugin, user)) + PYLOAD.removeAccount(plugin, user) + + +@bottle.route('/json/change_password', method='POST') +def change_password(): + user = request.POST['user_login'] + oldpw = request.POST['login_current_password'] + newpw = request.POST['login_new_password'] + + if not PYLOAD.changePassword(user, oldpw, newpw): + print "Wrong password" + return bottle.HTTPError() diff --git a/pyload/webui/app/pyload.py b/pyload/webui/app/pyload.py new file mode 100644 index 000000000..58acdf12c --- /dev/null +++ b/pyload/webui/app/pyload.py @@ -0,0 +1,526 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import datetime +import operator +import os +import sys +import time +import urllib + +import bottle + +from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, THEME, SETUP, env + +from pyload.webui.app.utils import render_to_response, parse_permissions, parse_userdata, \ + login_required, get_permission, set_permission, permlist, toDict, set_session + +from pyload.utils.filters import relpath, unquotepath + +from pyload.utils import formatSize, fs_join, fs_encode, fs_decode + +# Helper + + +def pre_processor(): + s = request.environ.get('beaker.session') + user = parse_userdata(s) + perms = parse_permissions(s) + status = {} + captcha = False + update = False + plugins = False + if user['is_authenticated']: + status = PYLOAD.statusServer() + info = PYLOAD.getInfoByPlugin("UpdateManager") + captcha = PYLOAD.isCaptchaWaiting() + + # check if update check is available + if info: + if info['pyload'] == "True": + update = info['version'] + if info['plugins'] == "True": + plugins = True + + return {"user": user, + 'status': status, + 'captcha': captcha, + 'perms': perms, + 'url': request.url, + 'update': update, + 'plugins': plugins} + + +def base(messages): + return render_to_response('base.html', {'messages': messages}, [pre_processor]) + + +# Views +@bottle.error(403) +def error403(code): + return "The parameter you passed has the wrong format" + + +@bottle.error(404) +def error404(code): + return "Sorry, this page does not exist" + + +@bottle.error(500) +def error500(error): + traceback = error.traceback + if traceback: + print traceback + return base(["An Error occured, please enable debug mode to get more details.", error, + traceback.replace("\n", "<br>") if traceback else "No Traceback"]) + + +@bottle.route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>') +def server_min(theme, file): + filename = os.path.join(THEME_DIR, THEME, theme, file) + if not os.path.isfile(filename): + file = file.replace(".min.", ".") + if file.endswith(".js"): + return server_js(theme, file) + else: + return server_static(theme, file) + + +@bottle.route('/<theme>/<file:re:.+\.js>') +def server_js(theme, file): + response.headers['Content-Type'] = "text/javascript; charset=UTF-8" + + if "/render/" in file or ".render." in file or True: + response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(time.time() + 24 * 7 * 60 * 60)) + response.headers['Cache-control'] = "public" + + path = "/".join((theme, file)) + return env.get_template(path).render() + else: + return server_static(theme, file) + + +@bottle.route('/<theme>/<file:path>') +def server_static(theme, file): + response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(time.time() + 24 * 7 * 60 * 60)) + response.headers['Cache-control'] = "public" + + return bottle.static_file(file, root=join(THEME_DIR, THEME, theme)) + + +@bottle.route('/favicon.ico') +def favicon(): + return bottle.static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources")) + + +@bottle.route('/login', method="GET") +def login(): + if not PYLOAD and SETUP: + bottle.redirect("/setup") + else: + return render_to_response("login.html", proc=[pre_processor]) + + +@bottle.route('/nopermission') +def nopermission(): + return base([_("You dont have permission to access this page.")]) + + +@bottle.route('/login', method='POST') +def login_post(): + user = request.forms.get("username") + password = request.forms.get("password") + + info = PYLOAD.checkAuth(user, password) + + if not info: + return render_to_response("login.html", {"errors": True}, [pre_processor]) + + set_session(request, info) + return bottle.redirect("/") + + +@bottle.route('/logout') +def logout(): + s = request.environ.get('beaker.session') + s.delete() + return render_to_response("logout.html", proc=[pre_processor]) + + +@bottle.route('/') +@bottle.route('/home') +@login_required("LIST") +def home(): + try: + res = [toDict(x) for x in PYLOAD.statusDownloads()] + except Exception: + s = request.environ.get('beaker.session') + s.delete() + return bottle.redirect("/login") + + for link in res: + if link['status'] == 12: + link['information'] = "%s kB @ %s kB/s" % (link['size'] - link['bleft'], link['speed']) + + return render_to_response("home.html", {"res": res}, [pre_processor]) + + +@bottle.route('/queue') +@login_required("LIST") +def queue(): + queue = PYLOAD.getQueue() + + queue.sort(key=operator.attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor]) + + +@bottle.route('/collector') +@login_required('LIST') +def collector(): + queue = PYLOAD.getCollector() + + queue.sort(key=operator.attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor]) + + +@bottle.route('/downloads') +@login_required('DOWNLOAD') +def downloads(): + root = PYLOAD.getConfigValue("general", "download_folder") + + if not os.path.isdir(root): + return base([_('Download directory not found.')]) + data = { + 'folder': [], + 'files': [] + } + + items = os.listdir(fs_encode(root)) + + for item in sorted([fs_decode(x) for x in items]): + if os.path.isdir(fs_join(root, item)): + folder = { + 'name': item, + 'path': item, + 'files': [] + } + files = os.listdir(fs_join(root, item)) + for file in sorted([fs_decode(x) for x in files]): + try: + if os.path.isfile(fs_join(root, item, file)): + folder['files'].append(file) + except Exception: + pass + + data['folder'].append(folder) + elif os.path.isfile(os.path.join(root, item)): + data['files'].append(item) + + return render_to_response('downloads.html', {'files': data}, [pre_processor]) + + +@bottle.route('/downloads/get/<path:path>') +@login_required("DOWNLOAD") +def get_download(path): + path = urllib.unquote(path).decode("utf8") + #@TODO some files can not be downloaded + + root = PYLOAD.getConfigValue("general", "download_folder") + + path = path.replace("..", "") + return bottle.static_file(fs_encode(path), fs_encode(root)) + + +@bottle.route('/settings') +@login_required('SETTINGS') +def config(): + conf = PYLOAD.getConfig() + plugin = PYLOAD.getPluginConfig() + conf_menu = [] + plugin_menu = [] + + for entry in sorted(conf.keys()): + conf_menu.append((entry, conf[entry].description)) + + last_name = None + for entry in sorted(plugin.keys()): + desc = plugin[entry].description + name, none, type = desc.partition("_") + + if type in PYLOAD.core.pluginManager.TYPES: + if name == last_name or len([a for a, b in plugin.iteritems() if b.description.startswith(name + "_")]) > 1: + desc = name + " (" + type.title() + ")" + else: + desc = name + last_name = name + plugin_menu.append((entry, desc)) + + accs = PYLOAD.getAccounts(False) + + for data in accs: + if data.trafficleft == -1: + data.trafficleft = _("unlimited") + elif not data.trafficleft: + data.trafficleft = _("not available") + else: + data.trafficleft = formatSize(data.trafficleft) + + if data.validuntil == -1: + data.validuntil = _("unlimited") + elif not data.validuntil: + data.validuntil = _("not available") + else: + t = time.localtime(data.validuntil) + data.validuntil = time.strftime("%d.%m.%Y - %H:%M:%S", t) + + try: + data.options['time'] = data.options['time'][0] + except Exception: + data.options['time'] = "0:00-0:00" + + if "limitDL" in data.options: + data.options['limitdl'] = data.options['limitDL'][0] + else: + data.options['limitdl'] = "0" + + return render_to_response('settings.html', + {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, + 'types': PYLOAD.getAccountTypes()}, + [pre_processor]) + + +@bottle.route('/filechooser') +@bottle.route('/pathchooser') +@bottle.route('/filechooser/<file:path>') +@bottle.route('/pathchooser/<path:path>') +@login_required('STATUS') +def os.path(file="", path=""): + type = "file" if file else "folder" + + path = os.path.normpath(unquotepath(path)) + + if os.path.isfile(path): + oldfile = path + path = os.path.dirname(path) + else: + oldfile = '' + + abs = False + + if os.path.isdir(path): + if os.path.isabs(path): + cwd = os.path.abspath(path) + abs = True + else: + cwd = os.relpath(path) + else: + cwd = os.getcwd() + + try: + cwd = cwd.encode("utf8") + except Exception: + pass + + cwd = os.path.normpath(os.path.abspath(cwd)) + parentdir = os.path.dirname(cwd) + if not abs: + if os.path.abspath(cwd) == "/": + cwd = os.relpath(cwd) + else: + cwd = os.relpath(cwd) + os.path.sep + parentdir = os.relpath(parentdir) + os.path.sep + + if os.path.abspath(cwd) == "/": + parentdir = "" + + try: + folders = os.listdir(cwd) + except Exception: + folders = [] + + files = [] + + for f in folders: + try: + f = f.decode(sys.getfilesystemencoding()) + data = {'name': f, 'fullpath': os.path.join(cwd, f)} + data['sort'] = data['fullpath'].lower() + data['modified'] = datetime.datetime.fromtimestamp(int(os.path.getmtime(os.path.join(cwd, f)))) + data['ext'] = os.path.splitext(f)[1] + except Exception: + continue + + data['type'] = 'dir' if os.path.isdir(os.path.join(cwd, f)) else 'file' + + if os.path.isfile(os.path.join(cwd, f)): + data['size'] = os.path.getsize(os.path.join(cwd, f)) + + power = 0 + while (data['size'] / 1024) > 0.3: + power += 1 + data['size'] /= 1024. + units = ('', 'K', 'M', 'G', 'T') + data['unit'] = units[power] + 'Byte' + else: + data['size'] = '' + + files.append(data) + + files = sorted(files, key=operator.itemgetter('type', 'sort')) + + return render_to_response('pathchooser.html', + {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile, + 'absolute': abs}, []) + + +@bottle.route('/logs') +@bottle.route('/logs', method='POST') +@bottle.route('/logs/<item>') +@bottle.route('/logs/<item>', method='POST') +@login_required('LOGS') +def logs(item=-1): + s = request.environ.get('beaker.session') + + perpage = s.get('perpage', 34) + reversed = s.get('reversed', False) + + warning = "" + conf = PYLOAD.getConfigValue("log", "file_log") + color_template = PYLOAD.getConfigValue("log", "color_template") if PYLOAD.getConfigValue("log", "color_console") else "" + if not conf: + warning = "Warning: File log is disabled, see settings page." + + perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all')) + fro = None + + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + try: + fro = datetime.datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S') + except Exception: + pass + try: + perpage = int(request.forms['perpage']) + s['perpage'] = perpage + + reversed = bool(request.forms.get('reversed', False)) + s['reversed'] = reversed + except Exception: + pass + + s.save() + + try: + item = int(item) + except Exception: + pass + + log = PYLOAD.getLog() + if not perpage: + item = 1 + + if item < 1 or type(item) is not int: + item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1 + + if type(fro) is datetime.datetime: #: we will search for datetime.datetime + item = -1 + + data = [] + counter = 0 + perpagecheck = 0 + for l in log: + counter += 1 + + if counter >= item: + try: + date, time, level, message = l.decode("utf8", "ignore").split(" ", 3) + dtime = datetime.datetime.strptime(date + ' ' + time, '%Y-%m-%d %H:%M:%S') + except Exception: + dtime = None + date = '?' + time = ' ' + level = '?' + message = l + if item == -1 and dtime is not None and fro <= dtime: + item = counter #: found our datetime.datetime + if item >= 0: + data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message}) + perpagecheck += 1 + if fro is None and dtime is not None: #: if fro not set set it to first showed line + fro = dtime + if perpagecheck >= perpage > 0: + break + + if fro is None: #: still not set, empty log? + fro = datetime.datetime.now() + if reversed: + data.reverse() + return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'), + 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p), + 'iprev': 1 if item - perpage < 1 else item - perpage, + 'inext': (item + perpage) if item + perpage < len(log) else item, + 'color_template': color_template.title()}, + [pre_processor]) + + +@bottle.route('/admin') +@bottle.route('/admin', method='POST') +@login_required("ADMIN") +def admin(): + # convert to dict + user = dict((name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()) + perms = permlist() + + for data in user.itervalues(): + data['perms'] = {} + get_permission(data['perms'], data['permission']) + data['perms']['admin'] = data['role'] is 0 + + s = request.environ.get('beaker.session') + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + for name in user: + if request.POST.get("%s|admin" % name, False): + user[name]['role'] = 0 + user[name]['perms']['admin'] = True + elif name != s['name']: + user[name]['role'] = 1 + user[name]['perms']['admin'] = False + + # set all perms to false + for perm in perms: + user[name]['perms'][perm] = False + + for perm in request.POST.getall("%s|perms" % name): + user[name]['perms'][perm] = True + + user[name]['permission'] = set_permission(user[name]['perms']) + + PYLOAD.setUserPermission(name, user[name]['permission'], user[name]['role']) + + return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor]) + + +@bottle.route('/setup') +def setup(): + return base([_("Run pyload.py -s to access the setup.")]) + + +@bottle.route('/info') +def info(): + conf = PYLOAD.getConfigDict() + extra = os.uname() if hasattr(os, "uname") else tuple() + + data = {"python" : sys.version, + "os" : " ".join((os.name, sys.platform) + extra), + "version" : PYLOAD.getServerVersion(), + "folder" : os.path.abspath(PYLOAD_DIR), "config": os.path.abspath(""), + "download" : os.path.abspath(conf['general']['download_folder']['value']), + "freespace": formatSize(PYLOAD.freeSpace()), + "remote" : conf['remote']['port']['value'], + "webif" : conf['webui']['port']['value'], + "language" : conf['general']['language']['value']} + + return render_to_response("info.html", data, [pre_processor]) diff --git a/pyload/webui/app/utils.py b/pyload/webui/app/utils.py new file mode 100644 index 000000000..2e7cf76c5 --- /dev/null +++ b/pyload/webui/app/utils.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, vuolter + +import os + +import bottle + +from pyload.Api import has_permission, PERMS, ROLE +from pyload.webui import env, THEME + + +def render_to_response(file, args={}, proc=[]): + for p in proc: + args.update(p()) + path = "tml/" + file + return env.get_template(path).render(**args) + + +def parse_permissions(session): + perms = dict((x, False) for x in dir(PERMS) if not x.startswith("_")) + perms['ADMIN'] = False + perms['is_admin'] = False + + if not session.get("authenticated", False): + return perms + + if session.get("role") == ROLE.ADMIN: + for k in perms.iterkeys(): + perms[k] = True + + elif session.get("perms"): + p = session.get("perms") + get_permission(perms, p) + + return perms + + +def permlist(): + return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"] + + +def get_permission(perms, p): + """Returns a dict with permission key + + :param perms: dictionary + :param p: bits + """ + for name in permlist(): + perms[name] = has_permission(p, getattr(PERMS, name)) + + +def set_permission(perms): + """generates permission bits from dictionary + + :param perms: dict + """ + permission = 0 + for name in dir(PERMS): + if name.startswith("_"): + continue + + if name in perms and perms[name]: + permission |= getattr(PERMS, name) + + return permission + + +def set_session(request, info): + s = request.environ.get('beaker.session') + s['authenticated'] = True + s['user_id'] = info['id'] + s['name'] = info['name'] + s['role'] = info['role'] + s['perms'] = info['permission'] + s['template'] = info['template'] + s.save() + + return s + + +def parse_userdata(session): + return {"name" : session.get("name", "Anonymous"), + "is_admin" : session.get("role", 1) == 0, + "is_authenticated": session.get("authenticated", False)} + + +def login_required(perm=None): + + def _dec(func): + + def _view(*args, **kwargs): + s = request.environ.get('beaker.session') + if s.get("name", None) and s.get("authenticated", False): + if perm: + perms = parse_permissions(s) + if perm not in perms or not perms[perm]: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return bottle.HTTPError(403, "Forbidden") + else: + return bottle.redirect("/nopermission") + + return func(*args, **kwargs) + else: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return bottle.HTTPError(403, "Forbidden") + else: + return bottle.redirect("/login") + + return _view + + return _dec + + +def toDict(obj): + return {att: getattr(obj, att) for att in obj.__slots__} + + +class CherryPyWSGI(ServerAdapter): + + def run(self, handler): + from wsgiserver import CherryPyWSGIServer + + server = CherryPyWSGIServer((self.host, self.port), handler) + server.start() |