Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
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"