Separate http server for site content WIP

This resolves issue introduced by 14e8130acb
by having wrapper and iframe exist in different origins

Note that this does introduce minor UX issue: copying links now shows them
with a different port
This commit is contained in:
caryoscelus 2023-12-07 07:30:24 +00:00
parent 75ecd895e7
commit a4cc2eeb9f
No known key found for this signature in database
GPG key ID: 254EDDB85B66CB1F
4 changed files with 37 additions and 14 deletions

View file

@ -51,11 +51,11 @@ class SecurityError(Exception):
@PluginManager.acceptPlugins
class UiRequest:
def __init__(self, server, get, env, start_response):
def __init__(self, server, env, start_response, is_data_request=False):
if server:
self.server = server
self.log = server.log
self.get = get # Get parameters
self.get = dict(urllib.parse.parse_qsl(env.get('QUERY_STRING', '')))
self.env = env # Enviroment settings
# ['CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE', 'HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE',
# 'HTTP_COOKIE', 'HTTP_CACHE_CONTROL', 'HTTP_HOST', 'HTTP_HTTPS', 'HTTP_ORIGIN', 'HTTP_PROXY_CONNECTION', 'HTTP_REFERER',
@ -66,6 +66,7 @@ class UiRequest:
self.start_response = start_response # Start response function
self.user = None
self.script_nonce = None # Nonce for script tags in wrapper html
self.is_data_request = is_data_request
def learnHost(self, host):
self.server.allowed_hosts.add(host)
@ -142,8 +143,21 @@ class UiRequest:
we'd want something else..
"""
is_navigate = self.env.get('HTTP_SEC_FETCH_MODE') == 'navigate'
is_iframe = self.env.get('HTTP_SEC_FETCH_DEST') == 'iframe'
if is_navigate and not is_iframe and self.is_data_request:
# remove port from host
host = ':'.join(self.env['HTTP_HOST'].split(':')[:-1])
path_info = self.env['PATH_INFO']
query_string = self.env['QUERY_STRING']
protocol = self.env['wsgi.url_scheme']
return self.actionRedirect(f'{protocol}://{host}:43110{path_info}?{query_string}')
if self.isCrossOriginRequest():
return self.error404()
# we are still exposed by answering on port
self.log.warning('Cross-origin request detected. Someone might be trying to analyze your 0net usage')
return []
# Restict Ui access by ip
if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:
@ -341,13 +355,12 @@ class UiRequest:
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';"
elif script_nonce:
headers["Content-Security-Policy"] = "default-src 'none'; script-src 'nonce-{0}'; img-src 'self' blob: data:; style-src 'self' blob: 'unsafe-inline'; connect-src *; frame-src 'self' blob:".format(script_nonce)
headers["Content-Security-Policy"] = f"default-src 'none'; script-src 'nonce-{script_nonce}'; img-src 'self' blob: data:; style-src 'self' blob: 'unsafe-inline'; connect-src *; frame-src 'self' blob: http://127.0.0.1:43111"
if allow_ajax:
headers["Access-Control-Allow-Origin"] = "null"
@ -618,6 +631,7 @@ class UiRequest:
return self.render(
"src/Ui/template/wrapper.html",
site_file_server='http://127.0.0.1:43111',
server_url=server_url,
inner_path=inner_path,
file_url=xescape(file_url),

View file

@ -29,7 +29,7 @@ class UiWSGIHandler(WebSocketHandler):
import main
main.DebugHook.handleError()
else:
ui_request = UiRequest(self.server, {}, self.environ, self.start_response)
ui_request = UiRequest(self.server, self.environ, self.start_response, is_data_request=False)
block_gen = ui_request.error500("UiWSGIHandler error: %s" % Debug.formatExceptionMessage(err))
for block in block_gen:
self.write(block)
@ -96,11 +96,7 @@ class UiServer:
# Handle WSGI request
def handleRequest(self, env, start_response):
path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8")
if env.get("QUERY_STRING"):
get = dict(urllib.parse.parse_qsl(env['QUERY_STRING']))
else:
get = {}
ui_request = UiRequest(self, get, env, start_response)
ui_request = UiRequest(self, env, start_response, is_data_request=False)
if config.debug: # Let the exception catched by werkezung
return ui_request.route(path)
else: # Catch and display the error
@ -158,6 +154,19 @@ class UiServer:
main.file_server.stop()
self.log.debug("Stopped.")
def handleSiteRequest(self, env, start_response):
path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8")
ui_request = UiRequest(self, env, start_response, is_data_request=True)
try:
return ui_request.route(path)
except Exception as err:
logging.debug(f"UiRequest @ site error: {Debug.formatException(err)}")
return ui_request.error500('Error while trying to server site data')
def startSiteServer(self):
self.site_server = WSGIServer((self.ip, 43111), self.handleSiteRequest, log=self.log)
self.site_server.serve_forever()
def stop(self):
self.log.debug("Stopping...")
# Close WS sockets

View file

@ -74,11 +74,11 @@ else if (window.opener && window.opener.location.toString()) {
<!-- Site Iframe -->
<iframe src='about:blank' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups allow-modals allow-presentation allow-pointer-lock allow-popups-to-escape-sandbox allow-same-origin {sandbox_permissions}" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" oallowfullscreen="true" msallowfullscreen="true" referrerpolicy="same-origin"></iframe>
<iframe src='about:blank' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups allow-modals allow-presentation allow-pointer-lock allow-popups-to-escape-sandbox allow-same-origin {sandbox_permissions}" allowfullscreen="true"></iframe>
<!-- Site info -->
<script id="script_init" nonce="{script_nonce}">
iframe_src = "{file_url}{query_string}"
iframe_src = "{site_file_server}{file_url}{query_string}"
console.log("Changing url from " + document.getElementById("inner-iframe").src + " to " + iframe_src)
document.getElementById("inner-iframe").src = document.getElementById("inner-iframe").src // Workaround for Firefox back button bug
document.getElementById("inner-iframe").src = iframe_src

View file

@ -230,7 +230,7 @@ class Actions:
import threading
self.gevent_quit = threading.Event()
launched_greenlets = [gevent.spawn(ui_server.start), gevent.spawn(file_server.start)]
launched_greenlets = [gevent.spawn(ui_server.start), gevent.spawn(file_server.start), gevent.spawn(ui_server.startSiteServer)]
# if --repl, start ipython thread
# FIXME: Unfortunately this leads to exceptions on exit so use with care