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 | 101 | ||||
-rw-r--r-- | pyload/webui/app/cnl.py | 167 | ||||
-rw-r--r-- | pyload/webui/app/json.py | 311 | ||||
-rw-r--r-- | pyload/webui/app/pyload.py | 530 | ||||
-rw-r--r-- | pyload/webui/app/utils.py | 123 |
6 files changed, 1235 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..1ee4b1608 --- /dev/null +++ b/pyload/webui/app/api.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +from urllib import unquote +from itertools import chain +from traceback import format_exc, print_exc + +from bottle import route, request, response, HTTPError + +from pyload.utils import toDict, set_session +from pyload.webui import PYLOAD + +from pyload.utils import json +from SafeEval import const_eval as literal_eval +from pyload.api import BaseObject + +# 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 + +@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{}]*>') +@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 HTTPError(403, json.dumps("Forbidden")) + + if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}): + return HTTPError(401, json.dumps("Unauthorized")) + + args = args.split("/")[1:] + kwargs = {} + + for x, y in chain(request.GET.iteritems(), request.POST.iteritems()): + if x == "session": continue + kwargs[x] = unquote(y) + + try: + return callApi(func, *args, **kwargs) + except Exception, e: + print_exc() + return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()})) + + +def callApi(func, *args, **kwargs): + if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"): + print "Invalid API call", func + return HTTPError(404, json.dumps("Not Found")) + + result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args], + **dict((x, literal_eval(y)) for x, y in kwargs.iteritems())) + + # null is invalid json response + if result is None: result = True + + return json.dumps(result, cls=TBaseEncoder) + + +#post -> username, password +@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) + + +@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..e00e47f66 --- /dev/null +++ b/pyload/webui/app/cnl.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +from os.path import join +import re +from urllib import unquote +from base64 import standard_b64decode +from binascii import unhexlify + +from bottle import route, request, HTTPError +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 HTTPError(403, "Forbidden") + + return _view + + +@route('/flash') +@route('/flash/<id>') +@route('/flash', method='POST') +@local_check +def flash(id="0"): + return "JDownloader\r\n" + + +@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 "" + + +@route('/flash/addcrypted', method='POST') +@local_check +def addcrypted(): + package = request.forms.get('referer', 'ClickAndLoad Package') + dlc = request.forms['crypted'].replace(" ", "+") + + dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc") + dlc_file = open(dlc_path, "wb") + dlc_file.write(dlc) + dlc_file.close() + + try: + PYLOAD.addPackage(package, [dlc_path], 0) + except Exception: + return HTTPError() + else: + return "success\r\n" + + +@route('/flash/addcrypted2', method='POST') +@local_check +def addcrypted2(): + package = request.forms.get("source", None) + crypted = request.forms["crypted"] + jk = request.forms["jk"] + + crypted = standard_b64decode(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 = 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" + + +@route('/flashgot_pyload') +@route('/flashgot_pyload', method='POST') +@route('/flashgot') +@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 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 "" + + +@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 + + +@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() + + +@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..c347c25e3 --- /dev/null +++ b/pyload/webui/app/json.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- + +from os.path import join +from traceback import print_exc +from shutil import copyfileobj + +from bottle import route, request, HTTPError + +from pyload.webui import PYLOAD + +from pyload.utils import login_required, render_to_response, toDict + +from pyload.utils import decode, formatSize + + +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"] + + +@route('/json/status') +@route('/json/status', method='POST') +@login_required('LIST') +def status(): + try: + status = toDict(PYLOAD.statusServer()) + status['captcha'] = PYLOAD.isCaptchaWaiting() + return status + except Exception: + return HTTPError() + + +@route('/json/links') +@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: + print_exc() + return HTTPError() + + +@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 HTTPError() + + +@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: + print_exc() + return HTTPError() + + +@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 HTTPError() + + +@route('/json/abort_link/<id:int>') +@login_required('DELETE') +def abort_link(id): + try: + PYLOAD.stopDownloads([id]) + return {"response": "success"} + except Exception: + return HTTPError() + + +@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 HTTPError() + + +@route('/json/add_package') +@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 = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename) + destination = open(fpath, 'wb') + copyfileobj(f.file, destination) + destination.close() + 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) + + +@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 HTTPError() + + +@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 HTTPError() + + +@route('/json/set_captcha') +@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} + + +@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", {"skey": section, "section": conf[section]}) + + +@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) + + +@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) + + +@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) + +@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 HTTPError() diff --git a/pyload/webui/app/pyload.py b/pyload/webui/app/pyload.py new file mode 100644 index 000000000..2d1a8eb86 --- /dev/null +++ b/pyload/webui/app/pyload.py @@ -0,0 +1,530 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from datetime import datetime +from operator import itemgetter, attrgetter + +import time +import os +import sys +from os import listdir +from os.path import isdir, isfile, join, abspath +from sys import getfilesystemencoding +from urllib import unquote + +from bottle import route, static_file, request, response, redirect, error + +from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, SETUP, env + +from pyload.utils import render_to_response, parse_permissions, parse_userdata, \ + login_required, get_permission, set_permission, permlist, toDict, set_session + +from pyload.webui.filters import relpath, unquotepath + +from pyload.utils import formatSize, safe_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 +@error(403) +def error403(code): + return "The parameter you passed has the wrong format" + + +@error(404) +def error404(code): + return "Sorry, this page does not exist" + + +@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"]) + + +@route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>') +def server_min(theme, file): + filename = join(THEME_DIR, theme, file) + if not isfile(filename): + file = file.replace(".min.", ".") + if file.endswith(".js"): + return server_js(theme, file) + else: + return server_static(theme, file) + + +@route('/<theme>/<file_static:re:.+\.js>') +def server_js(theme, file): + response.headers['Content-Type'] = "text/javascript; charset=UTF-8" + + if "/render/" in file or ".render." in 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" + + path = join(theme, file) + return env.get_template(path).render() + else: + return server_static(theme, file) + + +@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 static_file(file, root=join(THEME_DIR, theme)) + + +@route('/favicon.ico') +def favicon(): + return static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources")) + + +@route('/login', method="GET") +def login(): + if not PYLOAD and SETUP: + redirect("/setup") + else: + return render_to_response("login.html", proc=[pre_processor]) + + +@route('/nopermission') +def nopermission(): + return base([_("You dont have permission to access this page.")]) + + +@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 redirect("/") + + +@route('/logout') +def logout(): + s = request.environ.get('beaker.session') + s.delete() + return render_to_response("logout.html", proc=[pre_processor]) + + +@route('/') +@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 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]) + + +@route('/queue') +@login_required("LIST") +def queue(): + queue = PYLOAD.getQueue() + + queue.sort(key=attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor]) + + +@route('/collector') +@login_required('LIST') +def collector(): + queue = PYLOAD.getCollector() + + queue.sort(key=attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor]) + + +@route('/downloads') +@login_required('DOWNLOAD') +def downloads(): + root = PYLOAD.getConfigValue("general", "download_folder") + + if not isdir(root): + return base([_('Download directory not found.')]) + data = { + 'folder': [], + 'files': [] + } + + items = listdir(fs_encode(root)) + + for item in sorted([fs_decode(x) for x in items]): + if isdir(safe_join(root, item)): + folder = { + 'name': item, + 'path': item, + 'files': [] + } + files = listdir(safe_join(root, item)) + for file in sorted([fs_decode(x) for x in files]): + try: + if isfile(safe_join(root, item, file)): + folder['files'].append(file) + except Exception: + pass + + data['folder'].append(folder) + elif isfile(join(root, item)): + data['files'].append(item) + + return render_to_response('downloads.html', {'files': data}, [pre_processor]) + + +@route('/downloads/get/<path:path>') +@login_required("DOWNLOAD") +def get_download(path): + path = unquote(path).decode("utf8") + #@TODO some files can not be downloaded + + root = PYLOAD.getConfigValue("general", "download_folder") + + path = path.replace("..", "") + return static_file(fs_encode(path), fs_encode(root)) + + + +@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)) + + for entry in sorted(plugin.keys()): + plugin_menu.append((entry, plugin[entry].description)) + + 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]) + + +@route('/filechooser') +@route('/pathchooser') +@route('/filechooser/<file:path>') +@route('/pathchooser/<path:path>') +@login_required('STATUS') +def path(file="", path=""): + if file: + type = "file" + else: + type = "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 = 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 = relpath(cwd) + else: + cwd = relpath(cwd) + os.path.sep + parentdir = 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(getfilesystemencoding()) + data = {'name': f, 'fullpath': join(cwd, f)} + data['sort'] = data['fullpath'].lower() + data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f)))) + data['ext'] = os.path.splitext(f)[1] + except Exception: + continue + + if os.path.isdir(join(cwd, f)): + data['type'] = 'dir' + else: + data['type'] = 'file' + + if os.path.isfile(join(cwd, f)): + data['size'] = os.path.getsize(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=itemgetter('type', 'sort')) + + return render_to_response('pathchooser.html', + {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile, + 'absolute': abs}, []) + + +@route('/logs') +@route('/logs', method='POST') +@route('/logs/<item>') +@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") + 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.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 = 0 + + 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: # we will search for 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.strptime(date + ' ' + time, '%d.%m.%Y %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 + 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.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}, + [pre_processor]) + + +@route('/admin') +@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"] = True if data["role"] is 0 else False + + + 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]) + + +@route('/setup') +def setup(): + return base([_("Run pyload.py -s to access the setup.")]) + + +@route('/info') +def info(): + conf = PYLOAD.getConfigDict() + + if hasattr(os, "uname"): + extra = os.uname() + else: + extra = tuple() + + data = {"python": sys.version, + "os": " ".join((os.name, sys.platform) + extra), + "version": PYLOAD.getServerVersion(), + "folder": abspath(PYLOAD_DIR), "config": abspath(""), + "download": abspath(conf["general"]["download_folder"]["value"]), + "freespace": formatSize(PYLOAD.freeSpace()), + "remote": conf["remote"]["port"]["value"], + "webif": conf["webinterface"]["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..fd0e14cfa --- /dev/null +++ b/pyload/webui/app/utils.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, vuolter + +from os.path import join + +from bottle import request, HTTPError, redirect, ServerAdapter + +from pyload.webui import env, THEME + +from pyload.api import has_permission, PERMS, ROLE + +def render_to_response(file, args={}, proc=[]): + for p in proc: + args.update(p()) + path = join(THEME, "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": True if session.get("role", 1) == 0 else False, + "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 HTTPError(403, "Forbidden") + else: + return redirect("/nopermission") + + return func(*args, **kwargs) + else: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return HTTPError(403, "Forbidden") + else: + return redirect("/login") + + return _view + + return _dec + + +def toDict(obj): + ret = {} + for att in obj.__slots__: + ret[att] = getattr(obj, att) + return ret + + +class CherryPyWSGI(ServerAdapter): + def run(self, handler): + from wsgiserver import CherryPyWSGIServer + + server = CherryPyWSGIServer((self.host, self.port), handler) + server.start() |