diff --git a/plugins/UiPluginManager/UiPluginManagerPlugin.py b/plugins/UiPluginManager/UiPluginManagerPlugin.py
index 1ab80f53..0bfa8ec5 100644
--- a/plugins/UiPluginManager/UiPluginManagerPlugin.py
+++ b/plugins/UiPluginManager/UiPluginManagerPlugin.py
@@ -126,96 +126,3 @@ class UiWebsocketPlugin(object):
plugin_manager.saveConfig()
return "ok"
-
- def pluginAction(self, action, address, inner_path):
- site = self.server.sites.get(address)
- plugin_manager = PluginManager.plugin_manager
-
- # Install/update path should exists
- if action in ("add", "update", "add_request"):
- if not site:
- raise Exception("Site not found")
-
- if not site.storage.isDir(inner_path):
- raise Exception("Directory not found on the site")
-
- try:
- plugin_info = site.storage.loadJson(inner_path + "/plugin_info.json")
- plugin_data = (plugin_info["rev"], plugin_info["description"], plugin_info["name"])
- except Exception as err:
- raise Exception("Invalid plugin_info.json: %s" % Debug.formatExceptionMessage(err))
-
- source_path = site.storage.getPath(inner_path)
-
- target_path = plugin_manager.path_installed_plugins + "/" + address + "/" + inner_path
- plugin_config = plugin_manager.config.setdefault(site.address, {}).setdefault(inner_path, {})
-
- # Make sure plugin (not)installed
- if action in ("add", "add_request") and os.path.isdir(target_path):
- raise Exception("Plugin already installed")
-
- if action in ("update", "remove") and not os.path.isdir(target_path):
- raise Exception("Plugin not installed")
-
- # Do actions
- if action == "add":
- shutil.copytree(source_path, target_path)
-
- plugin_config["date_added"] = int(time.time())
- plugin_config["rev"] = plugin_info["rev"]
- plugin_config["enabled"] = True
-
- if action == "update":
- shutil.rmtree(target_path)
-
- shutil.copytree(source_path, target_path)
-
- plugin_config["rev"] = plugin_info["rev"]
- plugin_config["date_updated"] = time.time()
-
- if action == "remove":
- del plugin_manager.config[address][inner_path]
- shutil.rmtree(target_path)
-
- def doPluginAdd(self, to, inner_path, res):
- if not res:
- return None
-
- self.pluginAction("add", self.site.address, inner_path)
- PluginManager.plugin_manager.saveConfig()
-
- self.cmd(
- "confirm",
- ["Plugin installed!
You have to restart the client to load the plugin", "Restart"],
- lambda res: self.actionServerShutdown(to, restart=True)
- )
-
- self.response(to, "ok")
-
- @flag.no_multiuser
- def actionPluginAddRequest(self, to, inner_path):
- self.pluginAction("add_request", self.site.address, inner_path)
- plugin_info = self.site.storage.loadJson(inner_path + "/plugin_info.json")
- warning = "Warning!
Plugins has the same permissions as the ZeroNet client.
"
- warning += "Do not install it if you don't trust the developer."
-
- self.cmd(
- "confirm",
- ["Install new plugin: %s?
%s" % (plugin_info["name"], warning), "Trust & Install"],
- lambda res: self.doPluginAdd(to, inner_path, res)
- )
-
- @flag.admin
- @flag.no_multiuser
- def actionPluginRemove(self, to, address, inner_path):
- self.pluginAction("remove", address, inner_path)
- PluginManager.plugin_manager.saveConfig()
- return "ok"
-
- @flag.admin
- @flag.no_multiuser
- def actionPluginUpdate(self, to, address, inner_path):
- self.pluginAction("update", address, inner_path)
- PluginManager.plugin_manager.saveConfig()
- PluginManager.plugin_manager.plugins_updated["%s/%s" % (address, inner_path)] = True
- return "ok"
diff --git a/src/Plugin/PluginManager.py b/src/Plugin/PluginManager.py
index dbafa98f..5855c842 100644
--- a/src/Plugin/PluginManager.py
+++ b/src/Plugin/PluginManager.py
@@ -17,7 +17,6 @@ class PluginManager:
def __init__(self):
self.log = logging.getLogger("PluginManager")
self.path_plugins = os.path.abspath(os.path.dirname(plugins.__file__))
- self.path_installed_plugins = config.data_dir + "/__plugins__"
self.plugins = defaultdict(list) # Registered plugins (key: class name, value: list of plugins for class)
self.subclass_order = {} # Record the load order of the plugins, to keep it after reload
self.pluggable = {}
@@ -93,34 +92,6 @@ class PluginManager:
plugin["loaded"] = plugin_name in self.plugin_names
plugins.append(plugin)
- plugins += self.listInstalledPlugins(list_disabled)
- return plugins
-
- def listInstalledPlugins(self, list_disabled=False):
- plugins = []
-
- for address, site_plugins in sorted(self.config.items()):
- if address == "builtin":
- continue
- for plugin_inner_path, plugin_config in sorted(site_plugins.items()):
- is_enabled = plugin_config.get("enabled", False)
- if not is_enabled and not list_disabled:
- continue
- plugin_name = os.path.basename(plugin_inner_path)
-
- dir_path = "%s/%s/%s" % (self.path_installed_plugins, address, plugin_inner_path)
-
- plugin = {}
- plugin["source"] = address
- plugin["name"] = plugin_name
- plugin["dir_name"] = plugin_name
- plugin["dir_path"] = dir_path
- plugin["inner_path"] = plugin_inner_path
- plugin["enabled"] = is_enabled
- plugin["rev"] = plugin_config.get("rev", 0)
- plugin["loaded"] = plugin_name in self.plugin_names
- plugins.append(plugin)
-
return plugins
# Load all plugin
@@ -156,10 +127,10 @@ class PluginManager:
for module_name, module in list(sys.modules.items()):
if not module or not getattr(module, "__file__", None):
continue
- if self.path_plugins not in module.__file__ and self.path_installed_plugins not in module.__file__:
+ if self.path_plugins not in module.__file__:
continue
- if "allow_reload" in dir(module) and not module.allow_reload: # Reload disabled
+ if not getattr(module, 'allow_reload', True): # Reload disabled
# Re-add non-reloadable plugins
for class_name, classes in self.plugins_before.items():
for c in classes:
diff --git a/src/Ui/UiRequest.py b/src/Ui/UiRequest.py
index fb4e6e32..b55fa78d 100644
--- a/src/Ui/UiRequest.py
+++ b/src/Ui/UiRequest.py
@@ -49,7 +49,7 @@ class SecurityError(Exception):
@PluginManager.acceptPlugins
-class UiRequest(object):
+class UiRequest:
def __init__(self, server, get, env, start_response):
if server:
@@ -99,8 +99,52 @@ class UiRequest(object):
def resolveDomain(self, domain):
return self.server.site_manager.resolveDomainCached(domain)
- # Call the request handler function base on path
+ def isCrossOriginRequest(self):
+ """Prevent detecting sites on this 0net instance
+
+ In particular, we block non-user requests from other hosts as well as
+ cross-site
+ """
+
+ url = self.getRequestUrl()
+ fetch_mode = self.env.get('HTTP_SEC_FETCH_MODE')
+ origin = self.env.get('HTTP_ORIGIN')
+ referer = self.env.get('HTTP_REFERER')
+
+ # Allow all user-initiated requests
+ if fetch_mode == 'navigate':
+ return False
+
+ # Deny requests that cannot be traced
+ if not origin and not referer:
+ return True
+
+ # Deny requests from non-0net origins
+ if origin and not self.isSameHost(origin, url):
+ return True
+
+ # Allow non-site specific requests
+ if self.getRequestSite() == '/':
+ return False
+
+ # Deny cross site requests
+ if not self.isSameOrigin(referer, url):
+ return True
+
+ return False
+
def route(self, path):
+ """Main routing
+
+ If no internal action is performed, calls action[Path] from plugins
+
+ This behaviour is not very flexible or easy to follow, so perhaps
+ we'd want something else..
+ """
+
+ if self.isCrossOriginRequest():
+ return self.error404()
+
# Restict Ui access by ip
if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:
return self.error403(details=False)
@@ -274,25 +318,31 @@ class UiRequest(object):
else:
return referer
+ def getRequestSite(self):
+ """Return 0net site addr associated with current request
+
+ If request is site-agnostic, returns /
+ """
+ path = self.env["PATH_INFO"]
+ match = re.match(r'(/raw)?(?P/1[a-zA-Z0-9]*)', path)
+ if not match:
+ match = re.match(r'(/raw)?/(?P[a-zA-Z0-9\.\-_]*)', path)
+ if match:
+ domain = match.group('domain')
+ if self.isDomain(domain):
+ addr = self.resolveDomain(domain)
+ return '/'+addr
+ return '/'
+ return match.group('site')
+
# Send response headers
def sendHeader(self, status=200, content_type="text/html", noscript=False, allow_ajax=False, script_nonce=None, extra_headers=[]):
- url = self.getRequestUrl()
- referer = self.env.get('HTTP_REFERER')
- origin = self.env.get('HTTP_ORIGIN')
- fetch_site = self.env.get('HTTP_SEC_FETCH_SITE')
- fetch_mode = self.env.get('HTTP_SEC_FETCH_MODE')
- not_same_ref = referer and not self.isSameHost(referer, url)
- not_same_origin = origin and not self.isSameHost(origin, url)
- cross_site_not_navigate = not referer and fetch_site == 'cross-site' and not fetch_mode == 'navigate'
- if status != 404 and (not_same_ref or not_same_origin or cross_site_not_navigate):
- # pretend nothing is here for third-party access
- return self.error404()
-
headers = {}
headers["Version"] = "HTTP/1.1"
headers["Connection"] = "Keep-Alive"
headers["Keep-Alive"] = "max=25, timeout=30"
headers["X-Frame-Options"] = "SAMEORIGIN"
+ headers["Referrer-Policy"] = "same-origin"
if noscript:
headers["Content-Security-Policy"] = "default-src 'none'; sandbox allow-top-navigation allow-forms; img-src *; font-src * data:; media-src *; style-src * 'unsafe-inline';"
@@ -304,7 +354,7 @@ class UiRequest(object):
if self.env["REQUEST_METHOD"] == "OPTIONS":
# Allow json access
- headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Cookie, Range"
+ headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Cookie, Range, Referer"
headers["Access-Control-Allow-Credentials"] = "true"
# Download instead of display file types that can be dangerous
@@ -614,15 +664,18 @@ class UiRequest(object):
if not url_a or not url_b:
return False
- url_a = url_a.replace("/raw/", "/")
- url_b = url_b.replace("/raw/", "/")
+ host_pattern = r'(?Phttp[s]?://.*?)(/|$)'
- origin_pattern = "http[s]{0,1}://(.*?/).*"
+ match_a = re.match(host_pattern, url_a)
+ match_b = re.match(host_pattern, url_b)
- origin_a = re.sub(origin_pattern, "\\1", url_a)
- origin_b = re.sub(origin_pattern, "\\1", url_b)
+ if not match_a or not match_b:
+ return False
- return origin_a == origin_b
+ host_a = match_a.group('host')
+ host_b = match_b.group('host')
+
+ return host_a == host_b
def isSameOrigin(self, url_a, url_b):
"""Check if 0net origin is the same"""
diff --git a/src/Ui/template/wrapper.html b/src/Ui/template/wrapper.html
index f65c5066..2cce69cf 100644
--- a/src/Ui/template/wrapper.html
+++ b/src/Ui/template/wrapper.html
@@ -74,7 +74,7 @@ else if (window.opener && window.opener.location.toString()) {
-
+