UiPluginManagerPlugin.py 8.3 KB

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