import os, json, inspect import mimetypes from html2text import html2text from RestrictedPython import compile_restricted, safe_globals import frappe import frappe.utils import frappe.utils.data from frappe.website.utils import (get_shade, get_toc, get_next_link) from frappe.modules import scrub from frappe.www.printview import get_visible_columns import frappe.exceptions class ServerScriptNotEnabled(frappe.PermissionError): pass def safe_exec(script, _globals=None, _locals=None): # script reports must be enabled via site_config.json if not frappe.conf.server_script_enabled: frappe.msgprint('Please Enable Server Scripts') raise ServerScriptNotEnabled # build globals exec_globals = get_safe_globals() if _globals: exec_globals.update(_globals) # execute script compiled by RestrictedPython exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used def get_safe_globals(): datautils = {} if frappe.db: date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd" else: date_format = 'yyyy-mm-dd' add_module_properties(frappe.utils.data, datautils, lambda obj: hasattr(obj, "__call__")) if "_" in getattr(frappe.local, 'form_dict', {}): del frappe.local.form_dict["_"] user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest" out = frappe._dict( # make available limited methods of frappe json = json, dict = dict, frappe = frappe._dict( _ = frappe._, _dict = frappe._dict, flags = frappe.flags, format = frappe.format_value, format_value = frappe.format_value, date_format = date_format, format_date = frappe.utils.data.global_date_format, form_dict = getattr(frappe.local, 'form_dict', {}), get_meta = frappe.get_meta, get_doc = frappe.get_doc, get_cached_doc = frappe.get_cached_doc, get_list = frappe.get_list, get_all = frappe.get_all, get_system_settings = frappe.get_system_settings, utils = datautils, get_url = frappe.utils.get_url, render_template = frappe.render_template, msgprint = frappe.msgprint, user = user, get_fullname = frappe.utils.get_fullname, get_gravatar = frappe.utils.get_gravatar_url, full_name = frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest", request = getattr(frappe.local, 'request', {}), session = frappe._dict( user = user, csrf_token = frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else '' ), socketio_port = frappe.conf.socketio_port, get_hooks = frappe.get_hooks, ), style = frappe._dict( border_color = '#d1d8dd' ), get_toc = get_toc, get_next_link = get_next_link, _ = frappe._, get_shade = get_shade, scrub = scrub, guess_mimetype = mimetypes.guess_type, html2text = html2text, dev_server = 1 if os.environ.get('DEV_SERVER', False) else 0 ) add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception)) if not frappe.flags.in_setup_help: out.get_visible_columns = get_visible_columns out.frappe.date_format = date_format out.frappe.db = frappe._dict( get_list = frappe.get_list, get_all = frappe.get_all, get_value = frappe.db.get_value, get_single_value = frappe.db.get_single_value, get_default = frappe.db.get_default, escape = frappe.db.escape, ) if frappe.response: out.frappe.response = frappe.response out.update(safe_globals) # default writer allows write access out._write_ = _write out._getitem_ = _getitem return out def _getitem(obj, key): # guard function for RestrictedPython # allow any key to be accessed as long as it does not start with underscore if isinstance(key, str) and key.startswith('_'): raise SyntaxError('Key starts with _') return obj[key] def _write(obj): # guard function for RestrictedPython # allow writing to any object return obj def add_module_properties(module, data, filter_method): for key, obj in module.__dict__.items(): if key.startswith("_"): # ignore continue if filter_method(obj): # only allow functions data[key] = obj