├── .gitignore ├── BackgroundPlugin.py ├── README.md ├── SitePlugin.py ├── UiWebsocketPlugin.py ├── __init__.py ├── crypt.py ├── sandboxer ├── __init__.py ├── runtime.py ├── scope.py └── vmbuiltins.py ├── spawner.py ├── storage.py ├── transpilers ├── __init__.py └── py │ └── __init__.py ├── util.py └── zeroframe.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /BackgroundPlugin.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloZeroNet/Plugin-BackgroundProcessing/9b96f7e7cda3931d76735d6cb82b81de8059e621/BackgroundPlugin.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZeroNet-Background 2 | 3 | ZeroNet plugin for running site code in background, when browser is closed. Safe. 4 | -------------------------------------------------------------------------------- /SitePlugin.py: -------------------------------------------------------------------------------- 1 | from Plugin import PluginManager 2 | from .spawner import Spawner 3 | from . import storage 4 | 5 | @PluginManager.registerTo("Site") 6 | class SitePlugin(object): 7 | def __init__(self, *args, **kwags): 8 | super(SitePlugin, self).__init__(*args, **kwags) 9 | 10 | # Now spawn background process if needed 11 | io = { 12 | "output": self.backgroundOutput, 13 | "input": self.backgroundInput, 14 | "readModule": self.readModule, 15 | "allowed_import": ( 16 | "json", "re", "datetime", "base64", "collections", "random" 17 | ), 18 | "modules": storage.modules, 19 | "site": self, 20 | "scope0": [], 21 | "import_cache": {} 22 | } 23 | self.spawner = Spawner(self, io=io) 24 | self.spawned_background_processes = False 25 | if "BACKGROUND" in self.settings["permissions"]: 26 | self.spawnBackgroundProcesses() 27 | 28 | self.onFileDone.append(self.reloadBackgroundProcess) 29 | 30 | 31 | def spawnBackgroundProcesses(self): 32 | if self.spawned_background_processes: 33 | return 34 | 35 | self.log.debug("Spawning background processes") 36 | self.spawned_background_processes = True 37 | files = self.storage.list("") 38 | for file in files: 39 | # Run every file that starts with 0background. 40 | if file.startswith("0background."): 41 | self.spawnBackgroundProcess(file) 42 | 43 | 44 | # Spawn background process if needed 45 | def spawnBackgroundProcess(self, file_name): 46 | ext = file_name.replace("0background.", "") 47 | # Read code 48 | code = self.storage.read(file_name) 49 | # Spawn 50 | self.spawner.spawn(ext, code) 51 | 52 | # If a background process is changed, reload it 53 | def reloadBackgroundProcess(self, inner_path): 54 | if inner_path.startswith("0background."): 55 | self.spawner.stopAll() 56 | self.spawnBackgroundProcesses() 57 | 58 | 59 | def delete(self): 60 | # First really delete 61 | super(SitePlugin, self).delete() 62 | # Now stop all threads 63 | self.spawner.stopAll() 64 | 65 | 66 | def saveSettings(self): 67 | super(SitePlugin, self).saveSettings() 68 | 69 | # Spawn if just got the permission 70 | if "BACKGROUND" in self.settings["permissions"]: 71 | self.spawnBackgroundProcesses() 72 | 73 | # IO 74 | def backgroundOutput(self, *args): 75 | print(*args) 76 | def backgroundInput(self, *args): 77 | raise NotImplementedError 78 | def readModule(self, path): 79 | if self.storage.isDir(path): 80 | path += "/__init__.py" 81 | else: 82 | path += ".py" 83 | 84 | try: 85 | with self.storage.open(path, "r") as f: 86 | return f.read(), path 87 | except IOError: 88 | return None, path -------------------------------------------------------------------------------- /UiWebsocketPlugin.py: -------------------------------------------------------------------------------- 1 | from Plugin import PluginManager 2 | 3 | @PluginManager.registerTo("UiWebsocket") 4 | class UiWebsocketPlugin(object): 5 | def actionRestartBackgroundScripts(self, to): 6 | if "BACKGROUND" in self.site.settings["permissions"]: 7 | # Stop threads 8 | self.site.spawner.stopAll() 9 | # Start them 10 | self.site.spawned_background_processes = False 11 | self.site.spawnBackgroundProcesses() 12 | # Reply 13 | self.response(to, "ok") 14 | else: 15 | self.response(to, {"error": "No BACKGROUND permission"}) -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from . import BackgroundPlugin 2 | from . import SitePlugin 3 | from . import UiWebsocketPlugin 4 | from . import storage 5 | 6 | def addModule(name, module): 7 | storage.modules[name] = module 8 | 9 | # Add ZeroFrame module 10 | from . import zeroframe 11 | addModule("ZeroFrame", zeroframe.module) 12 | 13 | # Add Crypt module 14 | from . import crypt 15 | addModule("Crypt", crypt.module) 16 | 17 | # Add Util module 18 | from . import util 19 | addModule("Util", util.module) -------------------------------------------------------------------------------- /crypt.py: -------------------------------------------------------------------------------- 1 | from Crypt import CryptBitcoin 2 | Crypt = None 3 | 4 | 5 | allowed_names = ( 6 | "newPrivatekey", "newSeed", "hdPrivatekey", "privatekeyToAddress", "sign", 7 | "verify" 8 | ) 9 | 10 | def module(io): 11 | class ExtendedCrypt: 12 | def __init__(self): 13 | for name in allowed_names: 14 | setattr(self, name, getattr(Crypt, name)) 15 | 16 | class Crypt: 17 | def __init__(self): 18 | for name in allowed_names: 19 | setattr(self, name, getattr(CryptBitcoin, name)) 20 | if Crypt is None: 21 | self.ex = None 22 | else: 23 | self.ex = ExtendedCrypt() 24 | 25 | return Crypt() 26 | -------------------------------------------------------------------------------- /sandboxer/__init__.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from . import runtime 3 | import gevent 4 | 5 | class Sandboxer(object): 6 | def __init__(self, code, filename, io): 7 | self.code = code 8 | self.filename = filename 9 | self.parsed = ast.parse(code, filename=filename) 10 | self.io = io 11 | 12 | 13 | def toSafe(self): 14 | self.handleNode(self.parsed, None, 0) 15 | ast.fix_missing_locations(self.parsed) 16 | 17 | def do(): 18 | scope0 = runtime.Scope(io=self.io) 19 | scope0.filename = self.filename 20 | self.io["scope0"].append(scope0) 21 | runtime.fillScope0(scope0) 22 | 23 | exec(compile(self.parsed, filename=self.filename, mode="exec"), {"scope0": scope0}) 24 | return scope0 25 | 26 | return do 27 | 28 | 29 | def handleNode(self, node, parent, scope): 30 | # Scope variables 31 | if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Param): 32 | return ast.Subscript( 33 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 34 | slice=ast.Index(value=ast.Str(s=node.id)), 35 | ctx=node.ctx 36 | ) 37 | 38 | # Global 39 | if isinstance(node, ast.Global): 40 | res = [] 41 | for name in node.names: 42 | res.append(ast.Expr(value=ast.Call( 43 | func=ast.Attribute( 44 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 45 | attr="inheritVariable", 46 | ctx=ast.Load() 47 | ), 48 | args=[ 49 | ast.Name(id="scope0", ctx=ast.Load()), 50 | ast.Str(s=name) 51 | ], 52 | keywords=[], starargs=None, kwargs=None 53 | ))) 54 | return res 55 | 56 | # Import 57 | if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom): 58 | none = ast.Name(id="None", ctx=ast.Load()) 59 | 60 | names = ast.List(elts=[ 61 | ast.Tuple(elts=[ 62 | ast.Str(s=name.name), 63 | ast.Str(s=name.asname) if name.asname else none 64 | ], ctx=ast.Load()) 65 | for name in node.names 66 | ], ctx=ast.Load()) 67 | 68 | if isinstance(node, ast.ImportFrom): 69 | from_ = ast.Str(node.module) if node.module else none 70 | level = ast.Num(node.level or 0) 71 | else: 72 | from_ = none 73 | level = none 74 | 75 | return ast.Expr(value=ast.Call( 76 | func=ast.Attribute( 77 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 78 | attr="import_", 79 | ctx=ast.Load() 80 | ), 81 | args=[names, from_, level], 82 | keywords=[], starargs=None, kwargs=None 83 | )) 84 | 85 | 86 | if isinstance(node, ast.FunctionDef): 87 | scope += 1 88 | 89 | if isinstance(node, ast.Lambda): 90 | # Handle arguments (default values) in parent scope 91 | args = self.handleNode(node.args, node, scope) 92 | if args is not None: 93 | node.args = args 94 | 95 | # Now increment scope and handle body 96 | scope += 1 97 | body = self.handleNode(node.body, node, scope) 98 | if body is not None: 99 | node.body = body 100 | else: 101 | for fieldname, value in ast.iter_fields(node): 102 | if isinstance(value, ast.AST): 103 | res = self.handleNode(value, node, scope) 104 | if res is not None: 105 | setattr(node, fieldname, res) 106 | elif isinstance(value, list): 107 | result = [] 108 | for child in value: 109 | val = self.handleNode(child, node, scope) 110 | if val is None: 111 | result.append(child) 112 | elif isinstance(val, list): 113 | result += val 114 | else: 115 | result.append(val) 116 | setattr(node, fieldname, result) 117 | 118 | # Add scope to functions 119 | if isinstance(node, ast.FunctionDef): 120 | node.body.insert(0, ast.Assign( 121 | targets=[ast.Name(id="scope%s" % scope, ctx=ast.Store())], 122 | value=ast.Call( 123 | func=ast.Attribute( 124 | value=ast.Name(id="scope%s" % (scope-1), ctx=ast.Load()), 125 | attr="inherit", 126 | ctx=ast.Load() 127 | ), 128 | args=[], keywords=[], 129 | starargs=None, kwargs=None 130 | ) 131 | )) 132 | 133 | # Arguments 134 | for arg in node.args.args: 135 | node.body.insert(1, ast.Assign( 136 | targets=[ast.Subscript( 137 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 138 | slice=ast.Index(value=ast.Str(s=arg.arg)), 139 | ctx=ast.Store() 140 | )], 141 | value=ast.Name(id=arg.arg, ctx=ast.Load()) 142 | )) 143 | 144 | # Kw-only arguments 145 | for arg in node.args.kwonlyargs: 146 | node.body.insert(1, ast.Assign( 147 | targets=[ast.Subscript( 148 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 149 | slice=ast.Index(value=ast.Str(s=arg.arg)), 150 | ctx=ast.Store() 151 | )], 152 | value=ast.Name(id=arg.arg, ctx=ast.Load()) 153 | )) 154 | 155 | # Vararg 156 | if node.args.vararg is not None: 157 | node.body.insert(1, ast.Assign( 158 | targets=[ast.Subscript( 159 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 160 | slice=ast.Index(value=ast.Str(s=node.args.vararg.arg)), 161 | ctx=ast.Store() 162 | )], 163 | value=ast.Name(id=node.args.vararg.arg, ctx=ast.Load()) 164 | )) 165 | 166 | # Kwarg 167 | if node.args.kwarg is not None: 168 | node.body.insert(1, ast.Assign( 169 | targets=[ast.Subscript( 170 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 171 | slice=ast.Index(value=ast.Str(s=node.args.kwarg.arg)), 172 | ctx=ast.Store() 173 | )], 174 | value=ast.Name(id=node.args.kwarg.arg, ctx=ast.Load()) 175 | )) 176 | 177 | # Save functions (not methods) to scope 178 | if isinstance(node, ast.FunctionDef) and not isinstance(parent, ast.ClassDef): 179 | oldname = node.name 180 | node.name = "_user_%s_%s_" % (oldname, scope) 181 | 182 | return [ 183 | node, 184 | ast.Assign( 185 | targets=[ast.Subscript( 186 | value=ast.Name(id="scope%s" % (scope-1), ctx=ast.Load()), 187 | slice=ast.Index(value=ast.Str(s=oldname)), 188 | ctx=ast.Store() 189 | )], 190 | value=ast.Name(id=node.name, ctx=ast.Load()) 191 | ) 192 | ] 193 | 194 | # Save classes to scope 195 | if isinstance(node, ast.ClassDef) and not isinstance(parent, ast.ClassDef): 196 | oldname = node.name 197 | node.name = "_user_%s_%s_" % (oldname, scope) 198 | 199 | return [ 200 | node, 201 | ast.Assign( 202 | targets=[ast.Subscript( 203 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 204 | slice=ast.Index(value=ast.Str(s=oldname)), 205 | ctx=ast.Store() 206 | )], 207 | value=ast.Name(id=node.name, ctx=ast.Load()) 208 | ) 209 | ] 210 | 211 | # Add scope to lambdas 212 | if isinstance(node, ast.Lambda): 213 | # lambda a: a 214 | # -> 215 | # lambda a: (lambda scope1: scope1["a"])(scope0.extend({"a": a})) 216 | 217 | # We save everything to dict, don't assign automatically 218 | dct = ast.Dict(keys=[], values=[]) 219 | 220 | # Arguments 221 | for arg in node.args.args: 222 | dct.keys.append(ast.Str(s=arg.arg)) 223 | dct.values.append(ast.Name(id=arg.arg, ctx=ast.Load())) 224 | 225 | # Vararg 226 | if node.args.vararg is not None: 227 | dct.keys.append(ast.Str(s=node.args.vararg)) 228 | dct.values.append(ast.Name(id=node.args.vararg, ctx=ast.Load())) 229 | 230 | # Kwarg 231 | if node.args.kwarg is not None: 232 | dct.keys.append(ast.Str(s=node.args.kwarg)) 233 | dct.values.append(ast.Name(id=node.args.kwarg, ctx=ast.Load())) 234 | 235 | node.body = ast.Call( 236 | func=ast.Lambda( 237 | args=ast.arguments( 238 | args=[ 239 | ast.arg( 240 | arg="scope%s" % scope, annotation=None, ctx=ast.Load() 241 | ) 242 | ], 243 | kwonlyargs=[], 244 | vararg=None, kwarg=None, 245 | defaults=[], kw_defaults=[] 246 | ), 247 | body=node.body 248 | ), 249 | args=[ 250 | ast.Call( 251 | func=ast.Attribute( 252 | value=ast.Name(id="scope%s" % (scope - 1), ctx=ast.Load()), 253 | attr="extend", 254 | ctx=ast.Load() 255 | ), 256 | args=[dct], keywords=[], 257 | starargs=None, kwargs=None 258 | ) 259 | ], 260 | keywords=[], starargs=None, kwargs=None 261 | ) 262 | 263 | # Add except handler 264 | if isinstance(node, ast.ExceptHandler): 265 | if node.name is not None: 266 | node.name = "_exc" 267 | node.body.insert(0, ast.Assign( 268 | targets=[ast.Subscript( 269 | value=ast.Name(id="scope%s" % scope, ctx=ast.Load()), 270 | slice=ast.Index(value=ast.Str(s=node.name)), 271 | ctx=ast.Store() 272 | )], 273 | value=ast.Name(id="_exc", ctx=ast.Load()) 274 | )) 275 | 276 | # Now do something to prevent object.__subclasses__() hacks and others 277 | if ( 278 | isinstance(node, ast.Attribute) and 279 | (( 280 | node.attr.startswith("__") and 281 | node.attr.endswith("__") 282 | ) or ( 283 | node.attr.startswith("func_") 284 | )) 285 | ): 286 | return ast.Subscript( 287 | value=ast.Call( 288 | func=ast.Attribute( 289 | value=ast.Name(id="scope0", ctx=ast.Load()), 290 | attr="safeAttr", 291 | ctx=ast.Load() 292 | ), 293 | args=[node.value], 294 | keywords=[], starargs=None, kwargs=None 295 | ), 296 | slice=ast.Index(value=ast.Str(node.attr)), 297 | ctx=node.ctx 298 | ) -------------------------------------------------------------------------------- /sandboxer/runtime.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import builtins 3 | from .scope import Scope, allowed_classes 4 | from .vmbuiltins import setBuiltins 5 | 6 | 7 | # Return scope-before-scope0 8 | def fillScope0(scope0): 9 | scope0.inherits = {} 10 | 11 | # Built-in constants 12 | scope0.inherits["False"] = False 13 | scope0.inherits["True"] = True 14 | scope0.inherits["None"] = None 15 | scope0.inherits["NotImplemented"] = NotImplemented 16 | scope0.inherits["Ellipsis"] = Ellipsis 17 | 18 | # Types and exceptions 19 | for type_object in allowed_classes: 20 | type_name = type_object.__name__ 21 | if hasattr(builtins, type_name): 22 | scope0.inherits[type_name] = getattr(builtins, type_name) 23 | 24 | # Secure default functions 25 | funcs = [ 26 | "abs", "all", "any", "ascii", "bin", "callable", "chr", "delattr", 27 | "dir", "divmod", "format", "getattr", "hasattr", "hash", "hex", "id", 28 | "isinstance", "issubclass", "iter", "len", "max", "min", "next", "oct", 29 | "ord", "pow", "repr", "round", "setattr", "sorted", "sum" 30 | ] 31 | for func_name in funcs: 32 | scope0.inherits[func_name] = getattr(builtins, func_name) 33 | 34 | # Now add more builtins 35 | setBuiltins(scope0) -------------------------------------------------------------------------------- /sandboxer/scope.py: -------------------------------------------------------------------------------- 1 | allowed_classes = [ 2 | # Exceptions 3 | ArithmeticError, AssertionError, AttributeError, BaseException, 4 | BlockingIOError, BrokenPipeError, BufferError, BytesWarning, 5 | ChildProcessError, ConnectionAbortedError, ConnectionError, 6 | ConnectionRefusedError, ConnectionResetError, DeprecationWarning, EOFError, 7 | EnvironmentError, Exception, FileExistsError, FileNotFoundError, 8 | FloatingPointError, FutureWarning, GeneratorExit, IOError, ImportError, 9 | ImportWarning, IndentationError, IndexError, InterruptedError, 10 | IsADirectoryError, KeyError, KeyboardInterrupt, LookupError, MemoryError, 11 | ModuleNotFoundError, NameError, NotADirectoryError, NotImplementedError, 12 | OSError, OverflowError, PendingDeprecationWarning, PermissionError, 13 | ProcessLookupError, RecursionError, ReferenceError, ResourceWarning, 14 | RuntimeError, RuntimeWarning, StopAsyncIteration, StopIteration, 15 | SyntaxError, SyntaxWarning, SystemError, SystemExit, TabError, TimeoutError, 16 | TypeError, UnboundLocalError, UnicodeDecodeError, UnicodeEncodeError, 17 | UnicodeError, UnicodeTranslateError, UnicodeWarning, UserWarning, 18 | ValueError, Warning, ZeroDivisionError, 19 | 20 | # Primitives 21 | bool, float, int, bytes, str, complex, 22 | 23 | # Objects 24 | bytearray, dict, frozenset, list, memoryview, object, set, slice, tuple, 25 | 26 | # Transformations 27 | filter, map, enumerate, zip, reversed, 28 | 29 | # Other 30 | classmethod, property, range, staticmethod, type, type(None), 31 | type(NotImplemented), type(Ellipsis), type({}.keys()), type({}.values()), 32 | type({}.items()) 33 | ] 34 | 35 | 36 | class Scope(object): 37 | def __init__(self, inherits=None, io=None): 38 | self.vars = {} 39 | self.inheritsVariable = {} 40 | self.inherits = inherits 41 | if inherits is None: 42 | self.to_close = [] 43 | self.filename = None # will be set by spawner 44 | self.io = io 45 | else: 46 | self.to_close = inherits.to_close 47 | self.filename = inherits.filename 48 | self.io = inherits.io 49 | 50 | def import_(self, names, from_, level): 51 | if level is not None and level > 0: 52 | # Import local file 53 | file_parts = self.filename.split("/") 54 | if level > len(file_parts): 55 | if from_ is None: 56 | from_ = "." 57 | raise ImportError("Import of %s outside site root" % from_) 58 | 59 | if from_ is None: 60 | from_ = [] 61 | else: 62 | from_ = from_.split(".") 63 | new_path = "/".join(file_parts[:-level] + from_) 64 | 65 | if new_path not in self.io["import_cache"]: 66 | # Handle modules 67 | code, new_path = self.io["readModule"](new_path) 68 | if code is None: 69 | raise ImportError("Cannot read %s" % new_path) 70 | 71 | # Execute code 72 | from . import Sandboxer 73 | sandboxer = Sandboxer(code, new_path, io=self.io) 74 | safe_code = sandboxer.toSafe() 75 | self.io["import_cache"] = safe_code() 76 | 77 | 78 | result_scope = self.io["import_cache"] 79 | 80 | # Import the result 81 | for name, asname in names: 82 | if asname is None: 83 | asname = name 84 | if name not in result_scope: 85 | raise ImportError("Cannot import %s from %s" % (name, new_path)) 86 | self[asname] = result_scope[name] 87 | 88 | return 89 | 90 | 91 | for name, asname in names: 92 | if asname is None: 93 | asname = name 94 | 95 | if from_ is not None: 96 | if from_ not in self.io["allowed_import"]: 97 | raise ImportError("%s is not allowed to be imported" % from_) 98 | 99 | scope = {} 100 | exec(compile("from %s import %s as import_module" % (from_, name), "", "single"), scope, scope) 101 | import_module = scope["import_module"] 102 | elif name in self.io["modules"]: 103 | import_module = self.io["modules"][name](self.io) 104 | if hasattr(self.io["modules"][name], "close"): 105 | self.to_close.append(self.io["modules"][name].close) 106 | else: 107 | if name not in self.io["allowed_import"]: 108 | raise ImportError("%s is not allowed to be imported" % name) 109 | 110 | scope = {} 111 | exec(compile("import %s as import_module" % name, "", "single"), scope, scope) 112 | import_module = scope["import_module"] 113 | 114 | self[asname] = import_module 115 | del import_module 116 | 117 | 118 | def __getitem__(self, name): 119 | if name in self.inheritsVariable: 120 | scope = self.inheritsVariable[name] 121 | return scope[name] 122 | if name in self.vars: 123 | return self.vars[name] 124 | 125 | # Vars 126 | if name == "vars": 127 | return self.getVars() 128 | elif name == "locals": 129 | return self.getLocals() 130 | 131 | 132 | if self.inherits is not None: 133 | if isinstance(self.inherits, Scope): 134 | return self.inherits[name] # Recursive: type(inherits)==Scope 135 | elif name in self.inherits: 136 | return self.inherits[name] 137 | 138 | raise NameError(name) 139 | 140 | def __contains__(self, name): 141 | try: 142 | self[name] 143 | return True 144 | except NameError: 145 | return False 146 | 147 | def __setitem__(self, name, value): 148 | if name in self.inheritsVariable: 149 | scope = self.inheritsVariable[name] 150 | scope[name] = value 151 | return 152 | 153 | if isinstance(value, type): 154 | # User-defined class 155 | allowed_classes.append(value) 156 | 157 | self.vars[name] = value 158 | 159 | 160 | def inherit(self): 161 | return Scope(self) 162 | def inheritVariable(self, scope, name): 163 | self.inheritsVariable[name] = scope 164 | 165 | def extend(self, dct): 166 | scope1 = Scope(self) 167 | for name, value in dct.items(): 168 | scope1[name] = value 169 | return scope1 170 | 171 | 172 | def getVars(self): 173 | class ThisNone(object): 174 | pass 175 | def vars(object=ThisNone): 176 | if object is ThisNone: 177 | return self["locals"]() 178 | return object.__dict__ 179 | return vars 180 | 181 | def getLocals(self): 182 | def locals(): 183 | return self.vars 184 | return locals 185 | 186 | 187 | def safeAttr(self, obj): 188 | return SafeAttr(obj) 189 | 190 | 191 | class SafeAttr(object): 192 | def __init__(self, obj): 193 | self.obj = obj 194 | 195 | def __getitem__(self, name): 196 | if name == "__subclasses__": 197 | def subclasses(): 198 | return [ 199 | subclass for subclass 200 | in self.obj.__subclasses__() 201 | if subclass in allowed_classes 202 | ] 203 | 204 | return subclasses 205 | elif name in ("__globals__", "func_globals"): 206 | return self["globals"]() 207 | elif name in ("__code__", "func_code"): 208 | raise TypeError("%s is unsafe" % name) 209 | 210 | return getattr(self.obj, name) 211 | 212 | def __setitem__(self, name, value): 213 | if name == "__subclasses__": 214 | raise TypeError("__subclasses__ is read-only") 215 | elif name == "__globals__": 216 | raise TypeError("__globals__ is read-only") 217 | elif name in ("__code__", "func_code"): 218 | raise TypeError("%s is unsafe" % name) 219 | 220 | setattr(self.obj, name, value) -------------------------------------------------------------------------------- /sandboxer/vmbuiltins.py: -------------------------------------------------------------------------------- 1 | class BuiltinNone(object): 2 | pass 3 | 4 | old_super = super 5 | 6 | def setBuiltins(scope0): 7 | scope0.inherits["help"] = lambda: None 8 | scope0.inherits["copyright"] = lambda: None 9 | scope0.inherits["credits"] = lambda: None 10 | scope0.inherits["license"] = lambda: None 11 | 12 | scope0.inherits["__package__"] = "0background" 13 | scope0.inherits["__name__"] = "0background" 14 | scope0.inherits["__doc__"] = "" 15 | scope0.inherits["__debug__"] = False 16 | 17 | # globals() 18 | def globals(): 19 | return scope0["locals"]() 20 | scope0.inherits["globals"] = globals 21 | 22 | # reload() 23 | def reload(): 24 | raise NotImplementedError("reload() is not supported") 25 | scope0.inherits["reload"] = reload 26 | 27 | # print and print() 28 | def print_(*args, **kwargs): 29 | file = kwargs.get("file", None) 30 | if file is None: 31 | # Communication with hosting process 32 | scope0.io["output"](*args) 33 | else: 34 | import builtins 35 | getattr(builtins, "print")(*args, **kwargs) 36 | 37 | scope0.inherits["print"] = print_ 38 | 39 | # input() and raw_input() 40 | def input_(prompt=None): 41 | return scope0.io["input"](prompt) 42 | 43 | scope0.inherits["input"] = input_ 44 | scope0.inherits["raw_input"] = input_ 45 | 46 | # Attributes 47 | def getattr_(obj, name): 48 | return scope0.safeAttr(obj)[name] 49 | scope0.inherits["getattr"] = getattr_ 50 | 51 | def setattr_(obj, name, value): 52 | scope0.safeAttr(obj)[name] = value 53 | scope0.inherits["setattr"] = setattr_ 54 | 55 | # super 56 | def super(*args): 57 | if args == (): 58 | import sys 59 | frame = sys._getframe(1) 60 | self_name = frame.f_code.co_varnames[0] # usually "self" 61 | self = frame.f_locals[self_name] 62 | return old_super(type(self), self) 63 | else: 64 | return old_super(*args) 65 | scope0.inherits["super"] = super 66 | 67 | #arr = ['open', 'compile', '__import__', 'file', 'execfile', 'eval'] -------------------------------------------------------------------------------- /spawner.py: -------------------------------------------------------------------------------- 1 | import gevent 2 | import logging 3 | import os 4 | import importlib 5 | from .sandboxer import Sandboxer 6 | 7 | class Spawner(object): 8 | def __init__(self, site, io): 9 | self.site = site 10 | self.log = logging.getLogger("Spawner:%s" % self.site.address_short) 11 | self.threads = [] 12 | self.io = io 13 | io["spawner"] = self 14 | 15 | 16 | def spawn(self, ext, code): 17 | # Find correct transpiler for this file, based on extension 18 | transpiler = self.findTranspiler(ext) 19 | if transpiler is None: 20 | self.log.debug("No transpiler for 0background.%s" % ext) 21 | return False 22 | 23 | # Transpile 24 | self.log.debug("Transpiling 0background.%s" % ext) 25 | try: 26 | transpiled = transpiler.transpile(code) 27 | except Exception as e: 28 | self.log.exception("Error transpiling 0background.%s" % ext) 29 | return False 30 | 31 | # Sandbox 32 | sandboxer = Sandboxer(code, "0background.%s" % ext, io=self.io) 33 | safe_code = sandboxer.toSafe() 34 | 35 | self.log.debug("Running 0background.%s" % ext) 36 | self.threads.append(gevent.spawn(safe_code)) 37 | 38 | 39 | def findTranspiler(self, ext): 40 | try: 41 | return importlib.import_module("BackgroundProcessing.transpilers.%s" % ext) 42 | except ImportError: 43 | try: 44 | return importlib.import_module("transpilers.%s" % ext) 45 | except ImportError: 46 | return None 47 | 48 | 49 | # Stop all threads 50 | def stopAll(self): 51 | for scope0 in self.io["scope0"]: 52 | for f in scope0.to_close: 53 | f(self.io) 54 | for thread in self.threads: 55 | thread.kill(block=False) 56 | self.threads = [] 57 | self.io["scope0"] = [] -------------------------------------------------------------------------------- /storage.py: -------------------------------------------------------------------------------- 1 | modules = {} -------------------------------------------------------------------------------- /transpilers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloZeroNet/Plugin-BackgroundProcessing/9b96f7e7cda3931d76735d6cb82b81de8059e621/transpilers/__init__.py -------------------------------------------------------------------------------- /transpilers/py/__init__.py: -------------------------------------------------------------------------------- 1 | def transpile(code): 2 | return code -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import gevent 2 | 3 | def module(io): 4 | class Util: 5 | def sleep(self, sec): 6 | gevent.sleep(sec) 7 | def parallel(self, f): 8 | io["spawner"].threads.append(gevent.spawn(f)) 9 | 10 | return Util() -------------------------------------------------------------------------------- /zeroframe.py: -------------------------------------------------------------------------------- 1 | _cache = {} 2 | 3 | def module(io): 4 | if io["site"].address in _cache: 5 | return _cache[io["site"].address] 6 | 7 | 8 | import sys 9 | import gevent 10 | from User import UserManager 11 | from Ui import UiWebsocket 12 | 13 | # Create a fake WebSocket 14 | class WS(object): 15 | def send(self, *args, **kwargs): 16 | pass 17 | ws = WS() 18 | 19 | # Create a fake UiRequest 20 | class FakeUiRequest(object): 21 | def __init__(self): 22 | self.env = { 23 | "REMOTE_ADDR": "0.0.0.0" 24 | } 25 | def getWrapperNonce(self): 26 | return "" 27 | ui_request = FakeUiRequest() 28 | 29 | # Create fake UiWebsocket 30 | waiting_ids = {} 31 | class FakeUiWebsocket(UiWebsocket): 32 | # Responses 33 | def response(self, to, result): 34 | if to in waiting_ids: 35 | waiting_ids[to].set_result(result) 36 | 37 | # Callbacks 38 | def cmd(self, cmd, params={}, cb=None): 39 | attr_name = "on" + cmd[0].upper() + cmd[1:] 40 | if hasattr(zeroframe, attr_name): 41 | getattr(zeroframe, attr_name)(params) 42 | 43 | site = io["site"] 44 | user = UserManager.user_manager.get() 45 | 46 | # Create a fake UiWebsocket 47 | ui_server = sys.modules["main"].ui_server 48 | 49 | ui_websocket = FakeUiWebsocket(ws, site, ui_server, user, ui_request) 50 | site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events 51 | 52 | last_req_id = [0] 53 | 54 | class ZeroFrame(object): 55 | def cmd(self, cmd, *args, **kwargs): 56 | wait = kwargs.pop("wait", False) 57 | 58 | # Check params 59 | if len(args) == 0: 60 | params = kwargs 61 | elif len(kwargs) == 0: 62 | params = list(args) 63 | else: 64 | raise TypeError("ZeroFrame.cmd() accepts either *vararg or **kwarg, not both") 65 | 66 | # Generate ID 67 | req_id = last_req_id[0] 68 | last_req_id[0] += 1 69 | 70 | if wait: 71 | # Set callback 72 | waiting_ids[req_id] = gevent.event.AsyncResult() 73 | 74 | # Send 75 | ui_websocket.handleRequest({ 76 | "cmd": cmd, 77 | "params": params, 78 | "id": req_id 79 | }) 80 | 81 | if wait: 82 | # Wait 83 | result = waiting_ids[req_id].get() 84 | 85 | # Reply 86 | del waiting_ids[req_id] 87 | if isinstance(result, dict) and "error" in result: 88 | raise ValueError(result["error"]) 89 | else: 90 | return result 91 | 92 | # Same as settings .on... attribute, but can set several handlers 93 | def on(self, event_name, func): 94 | attr_name = "on" + event_name[0].upper() + event_name[1:] 95 | old_handler = getattr(self, attr_name, None) 96 | def handler(*args, **kwargs): 97 | if old_handler is not None: 98 | try: 99 | old_handler(*args, **kwargs) 100 | except: 101 | pass 102 | func(*args, **kwargs) 103 | setattr(self, attr_name, handler) 104 | 105 | # A simplier way to call API, e.g.: 106 | # ZeroFrame.fileGet("content.json") 107 | def __getattr__(self, name): 108 | def call(*args, **kwargs): 109 | return self.cmd(name, *args, **kwargs) 110 | return call 111 | 112 | zeroframe = ZeroFrame() 113 | _cache[io["site"].address] = zeroframe 114 | return zeroframe 115 | 116 | def close(io): 117 | zeroframe = _cache[io["site"].address] 118 | for key in list(zeroframe.__dict__.keys()): 119 | if key.startswith("on"): 120 | delattr(zeroframe, key) 121 | module.close = close --------------------------------------------------------------------------------