Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 221 lines 8.5 kB view raw
1import io 2import os 3import json 4import shutil 5import time 6 7from Plugin import PluginManager 8from Config import config 9from Debug import Debug 10from Translate import Translate 11from util.Flag import flag 12 13 14plugin_dir = os.path.dirname(__file__) 15 16if "_" not in locals(): 17 _ = Translate(plugin_dir + "/languages/") 18 19 20# Convert non-str,int,float values to str in a dict 21def restrictDictValues(input_dict): 22 allowed_types = (int, str, float) 23 return { 24 key: val if type(val) in allowed_types else str(val) 25 for key, val in input_dict.items() 26 } 27 28 29@PluginManager.registerTo("UiRequest") 30class UiRequestPlugin(object): 31 def actionWrapper(self, path, extra_headers=None): 32 if path.strip("/") != "Plugins": 33 return super(UiRequestPlugin, self).actionWrapper(path, extra_headers) 34 35 if not extra_headers: 36 extra_headers = {} 37 38 script_nonce = self.getScriptNonce() 39 40 self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce) 41 site = self.server.site_manager.get(config.homepage) 42 return iter([super(UiRequestPlugin, self).renderWrapper( 43 site, path, "uimedia/plugins/plugin_manager/plugin_manager.html", 44 "Plugin Manager", extra_headers, show_loadingscreen=False, script_nonce=script_nonce 45 )]) 46 47 def actionUiMedia(self, path, *args, **kwargs): 48 if path.startswith("/uimedia/plugins/plugin_manager/"): 49 file_path = path.replace("/uimedia/plugins/plugin_manager/", plugin_dir + "/media/") 50 if config.debug and (file_path.endswith("all.js") or file_path.endswith("all.css")): 51 # If debugging merge *.css to all.css and *.js to all.js 52 from Debug import DebugMedia 53 DebugMedia.merge(file_path) 54 55 if file_path.endswith("js"): 56 data = _.translateData(open(file_path).read(), mode="js").encode("utf8") 57 elif file_path.endswith("html"): 58 data = _.translateData(open(file_path).read(), mode="html").encode("utf8") 59 else: 60 data = open(file_path, "rb").read() 61 62 return self.actionFile(file_path, file_obj=io.BytesIO(data), file_size=len(data)) 63 else: 64 return super(UiRequestPlugin, self).actionUiMedia(path) 65 66 67@PluginManager.registerTo("UiWebsocket") 68class UiWebsocketPlugin(object): 69 @flag.admin 70 def actionPluginList(self, to): 71 plugins = [] 72 for plugin in PluginManager.plugin_manager.listPlugins(list_disabled=True): 73 plugin_info_path = plugin["dir_path"] + "/plugin_info.json" 74 plugin_info = {} 75 if os.path.isfile(plugin_info_path): 76 try: 77 plugin_info = json.load(open(plugin_info_path)) 78 except Exception as err: 79 self.log.error( 80 "Error loading plugin info for %s: %s" % 81 (plugin["name"], Debug.formatException(err)) 82 ) 83 if plugin_info: 84 plugin_info = restrictDictValues(plugin_info) # For security reasons don't allow complex values 85 plugin["info"] = plugin_info 86 87 if plugin["source"] != "builtin": 88 plugin_site = self.server.sites.get(plugin["source"]) 89 if plugin_site: 90 try: 91 plugin_site_info = plugin_site.storage.loadJson(plugin["inner_path"] + "/plugin_info.json") 92 plugin_site_info = restrictDictValues(plugin_site_info) 93 plugin["site_info"] = plugin_site_info 94 plugin["site_title"] = plugin_site.content_manager.contents["content.json"].get("title") 95 plugin_key = "%s/%s" % (plugin["source"], plugin["inner_path"]) 96 plugin["updated"] = plugin_key in PluginManager.plugin_manager.plugins_updated 97 except Exception: 98 pass 99 100 plugins.append(plugin) 101 102 return {"plugins": plugins} 103 104 @flag.admin 105 @flag.no_multiuser 106 def actionPluginConfigSet(self, to, source, inner_path, key, value): 107 plugin_manager = PluginManager.plugin_manager 108 plugins = plugin_manager.listPlugins(list_disabled=True) 109 plugin = None 110 for item in plugins: 111 if item["source"] == source and item["inner_path"] in (inner_path, "disabled-" + inner_path): 112 plugin = item 113 break 114 115 if not plugin: 116 return {"error": "Plugin not found"} 117 118 config_source = plugin_manager.config.setdefault(source, {}) 119 config_plugin = config_source.setdefault(inner_path, {}) 120 121 if key in config_plugin and value is None: 122 del config_plugin[key] 123 else: 124 config_plugin[key] = value 125 126 plugin_manager.saveConfig() 127 128 return "ok" 129 130 def pluginAction(self, action, address, inner_path): 131 site = self.server.sites.get(address) 132 plugin_manager = PluginManager.plugin_manager 133 134 # Install/update path should exists 135 if action in ("add", "update", "add_request"): 136 if not site: 137 raise Exception("Site not found") 138 139 if not site.storage.isDir(inner_path): 140 raise Exception("Directory not found on the site") 141 142 try: 143 plugin_info = site.storage.loadJson(inner_path + "/plugin_info.json") 144 plugin_data = (plugin_info["rev"], plugin_info["description"], plugin_info["name"]) 145 except Exception as err: 146 raise Exception("Invalid plugin_info.json: %s" % Debug.formatExceptionMessage(err)) 147 148 source_path = site.storage.getPath(inner_path) 149 150 target_path = plugin_manager.path_installed_plugins + "/" + address + "/" + inner_path 151 plugin_config = plugin_manager.config.setdefault(site.address, {}).setdefault(inner_path, {}) 152 153 # Make sure plugin (not)installed 154 if action in ("add", "add_request") and os.path.isdir(target_path): 155 raise Exception("Plugin already installed") 156 157 if action in ("update", "remove") and not os.path.isdir(target_path): 158 raise Exception("Plugin not installed") 159 160 # Do actions 161 if action == "add": 162 shutil.copytree(source_path, target_path) 163 164 plugin_config["date_added"] = int(time.time()) 165 plugin_config["rev"] = plugin_info["rev"] 166 plugin_config["enabled"] = True 167 168 if action == "update": 169 shutil.rmtree(target_path) 170 171 shutil.copytree(source_path, target_path) 172 173 plugin_config["rev"] = plugin_info["rev"] 174 plugin_config["date_updated"] = time.time() 175 176 if action == "remove": 177 del plugin_manager.config[address][inner_path] 178 shutil.rmtree(target_path) 179 180 def doPluginAdd(self, to, inner_path, res): 181 if not res: 182 return None 183 184 self.pluginAction("add", self.site.address, inner_path) 185 PluginManager.plugin_manager.saveConfig() 186 187 self.cmd( 188 "confirm", 189 ["Plugin installed!<br>You have to restart the client to load the plugin", "Restart"], 190 lambda res: self.actionServerShutdown(to, restart=True) 191 ) 192 193 self.response(to, "ok") 194 195 @flag.no_multiuser 196 def actionPluginAddRequest(self, to, inner_path): 197 self.pluginAction("add_request", self.site.address, inner_path) 198 plugin_info = self.site.storage.loadJson(inner_path + "/plugin_info.json") 199 warning = "<b>Warning!<br/>Plugins has the same permissions as the ZeroNet client.<br/>" 200 warning += "Do not install it if you don't trust the developer.</b>" 201 202 self.cmd( 203 "confirm", 204 ["Install new plugin: %s?<br>%s" % (plugin_info["name"], warning), "Trust & Install"], 205 lambda res: self.doPluginAdd(to, inner_path, res) 206 ) 207 208 @flag.admin 209 @flag.no_multiuser 210 def actionPluginRemove(self, to, address, inner_path): 211 self.pluginAction("remove", address, inner_path) 212 PluginManager.plugin_manager.saveConfig() 213 return "ok" 214 215 @flag.admin 216 @flag.no_multiuser 217 def actionPluginUpdate(self, to, address, inner_path): 218 self.pluginAction("update", address, inner_path) 219 PluginManager.plugin_manager.saveConfig() 220 PluginManager.plugin_manager.plugins_updated["%s/%s" % (address, inner_path)] = True 221 return "ok"