Factor flask.make_response() calls out into helper function

This commit is contained in:
Damien Elmes 2024-06-03 13:42:03 +07:00
parent bd88d15303
commit 5082d9ae5c

View file

@ -174,15 +174,24 @@ def _mime_for_path(path: str) -> str:
return mime or "application/octet-stream" return mime or "application/octet-stream"
def _text_response(code: HTTPStatus, text: str) -> Response:
"""Return an error message.
Response is returned as text/plain, so no escaping of untrusted
input is required."""
resp = flask.make_response(text, code)
resp.headers["Content-type"] = "text/plain"
return resp
def _handle_local_file_request(request: LocalFileRequest) -> Response: def _handle_local_file_request(request: LocalFileRequest) -> Response:
directory = request.root directory = request.root
path = request.path path = request.path
try: try:
isdir = os.path.isdir(os.path.join(directory, path)) isdir = os.path.isdir(os.path.join(directory, path))
except ValueError: except ValueError:
return flask.make_response( return _text_response(
f"Path for '{directory} - {path}' is too long!", HTTPStatus.BAD_REQUEST, f"Path for '{directory} - {path}' is too long!"
HTTPStatus.BAD_REQUEST,
) )
directory = os.path.realpath(directory) directory = os.path.realpath(directory)
@ -191,15 +200,14 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
# protect against directory transversal: https://security.openstack.org/guidelines/dg_using-file-paths.html # protect against directory transversal: https://security.openstack.org/guidelines/dg_using-file-paths.html
if not fullpath.startswith(directory): if not fullpath.startswith(directory):
return flask.make_response( return _text_response(
f"Path for '{directory} - {path}' is a security leak!", HTTPStatus.FORBIDDEN, f"Path for '{directory} - {path}' is a security leak!"
HTTPStatus.FORBIDDEN,
) )
if isdir: if isdir:
return flask.make_response( return _text_response(
f"Path for '{directory} - {path}' is a directory (not supported)!",
HTTPStatus.FORBIDDEN, HTTPStatus.FORBIDDEN,
f"Path for '{directory} - {path}' is a directory (not supported)!",
) )
try: try:
@ -219,10 +227,7 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
) )
else: else:
print(f"Not found: {path}") print(f"Not found: {path}")
return flask.make_response( return _text_response(HTTPStatus.NOT_FOUND, f"Invalid path: {path}")
f"Invalid path: {path}",
HTTPStatus.NOT_FOUND,
)
except Exception as error: except Exception as error:
if dev_mode: if dev_mode:
@ -234,10 +239,7 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
# swallow it - user likely surfed away from # swallow it - user likely surfed away from
# review screen before an image had finished # review screen before an image had finished
# downloading # downloading
return flask.make_response( return _text_response(HTTPStatus.INTERNAL_SERVER_ERROR, str(error))
str(error),
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _builtin_data(path: str) -> bytes: def _builtin_data(path: str) -> bytes:
@ -270,10 +272,7 @@ def _handle_builtin_file_request(request: BundledFileRequest) -> Response:
except FileNotFoundError: except FileNotFoundError:
if dev_mode: if dev_mode:
print(f"404: {data_path}") print(f"404: {data_path}")
resp = flask.make_response( resp = _text_response(HTTPStatus.NOT_FOUND, f"Invalid path: {path}")
f"Invalid path: {path}",
HTTPStatus.NOT_FOUND,
)
# we're including the path verbatim in our response, so we need to either use # we're including the path verbatim in our response, so we need to either use
# plain text, or escape HTML characters to avoid reflecting untrusted input # plain text, or escape HTML characters to avoid reflecting untrusted input
resp.headers["Content-type"] = "text/plain" resp.headers["Content-type"] = "text/plain"
@ -288,10 +287,7 @@ def _handle_builtin_file_request(request: BundledFileRequest) -> Response:
# swallow it - user likely surfed away from # swallow it - user likely surfed away from
# review screen before an image had finished # review screen before an image had finished
# downloading # downloading
return flask.make_response( return _text_response(HTTPStatus.INTERNAL_SERVER_ERROR, str(error))
str(error),
HTTPStatus.INTERNAL_SERVER_ERROR,
)
@app.route("/<path:pathin>", methods=["GET", "POST"]) @app.route("/<path:pathin>", methods=["GET", "POST"])
@ -310,10 +306,7 @@ def handle_request(pathin: str) -> Response:
if isinstance(req, NotFound): if isinstance(req, NotFound):
print(req.message) print(req.message)
return flask.make_response( return _text_response(HTTPStatus.NOT_FOUND, f"Invalid path: {pathin}")
f"Invalid path: {pathin}",
HTTPStatus.NOT_FOUND,
)
elif callable(req): elif callable(req):
return _handle_dynamic_request(req) return _handle_dynamic_request(req)
elif isinstance(req, BundledFileRequest): elif isinstance(req, BundledFileRequest):
@ -321,10 +314,7 @@ def handle_request(pathin: str) -> Response:
elif isinstance(req, LocalFileRequest): elif isinstance(req, LocalFileRequest):
return _handle_local_file_request(req) return _handle_local_file_request(req)
else: else:
return flask.make_response( return _text_response(HTTPStatus.FORBIDDEN, f"unexpected request: {pathin}")
f"unexpected request: {pathin}",
HTTPStatus.FORBIDDEN,
)
def is_sveltekit_page(path: str) -> bool: def is_sveltekit_page(path: str) -> bool:
@ -665,12 +655,10 @@ def _extract_collection_post_request(path: str) -> DynamicRequest | NotFound:
response = flask.make_response(data) response = flask.make_response(data)
response.headers["Content-Type"] = "application/binary" response.headers["Content-Type"] = "application/binary"
else: else:
response = flask.make_response("", HTTPStatus.NO_CONTENT) response = _text_response(HTTPStatus.NO_CONTENT, "")
except Exception as exc: except Exception as exc:
print(traceback.format_exc()) print(traceback.format_exc())
response = flask.make_response( response = _text_response(HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
str(exc), HTTPStatus.INTERNAL_SERVER_ERROR
)
return response return response
return wrapped return wrapped
@ -718,7 +706,7 @@ def _handle_dynamic_request(req: DynamicRequest) -> Response:
try: try:
return req() return req()
except Exception as e: except Exception as e:
return flask.make_response(str(e), HTTPStatus.INTERNAL_SERVER_ERROR) return _text_response(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
def legacy_page_data() -> Response: def legacy_page_data() -> Response:
@ -726,7 +714,7 @@ def legacy_page_data() -> Response:
if html := aqt.mw.mediaServer.get_page_html(id): if html := aqt.mw.mediaServer.get_page_html(id):
return Response(html, mimetype="text/html") return Response(html, mimetype="text/html")
else: else:
return flask.make_response("page not found", HTTPStatus.NOT_FOUND) return _text_response(HTTPStatus.NOT_FOUND, "page not found")
def _extract_page_context() -> PageContext: def _extract_page_context() -> PageContext: