From 35e694e93b178442a1a55bfef1c8daa001316586 Mon Sep 17 00:00:00 2001 From: Lukas Senionis Date: Fri, 4 Oct 2024 20:15:58 +0300 Subject: [PATCH] fix(http_request): remove conflicting CORS headers (#708) --- backend/decky_loader/utilities.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/decky_loader/utilities.py b/backend/decky_loader/utilities.py index 4962da32..69c69fe6 100644 --- a/backend/decky_loader/utilities.py +++ b/backend/decky_loader/utilities.py @@ -9,7 +9,7 @@ from stat import FILE_ATTRIBUTE_HIDDEN # pyright: ignore [reportAttributeAccessIssue, reportUnknownVariableType] from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection -from aiohttp import ClientSession +from aiohttp import ClientSession, hdrs from aiohttp.web import Request, StreamResponse, Response, json_response, post from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict @@ -153,14 +153,14 @@ async def http_request(self, req: Request) -> StreamResponse: headers["User-Agent"] = helpers.user_agent for excluded_header in excluded_default_headers: - self.logger.debug(f"Excluding default header {excluded_header}") if excluded_header in headers: + self.logger.debug(f"Excluding default header {excluded_header}: {headers[excluded_header]}") del headers[excluded_header] if "X-Decky-Fetch-Excluded-Headers" in req.headers: for excluded_header in req.headers["X-Decky-Fetch-Excluded-Headers"].split(", "): - self.logger.debug(f"Excluding header {excluded_header}") if excluded_header in headers: + self.logger.debug(f"Excluding header {excluded_header}: {headers[excluded_header]}") del headers[excluded_header] for header in req.headers: @@ -187,7 +187,21 @@ async def http_request(self, req: Request) -> StreamResponse: # defeat the point of this proxy. async with ClientSession(auto_decompress=False) as web: async with web.request(req.method, url, headers=headers, data=body, ssl=helpers.get_ssl_context()) as web_res: - res = StreamResponse(headers=web_res.headers, status=web_res.status) + # Whenever the aiohttp_cors is used, it expects a near complete control over whatever headers are needed + # for `aiohttp_cors.ResourceOptions`. As a server, if you delegate CORS handling to aiohttp_cors, + # the headers below must NOT be set. Otherwise they would be overwritten by aiohttp_cors and there would be + # logic bugs, so it was probably a smart choice to assert if the headers are present. + # + # However, this request handler method does not act like our own local server, it always acts like a proxy + # where we do not have control over the response headers. For responses that do not allow CORS, we add the support + # via aiohttp_cors. For responses that allow CORS, we have to remove the conflicting headers to allow + # aiohttp_cors handle it for us as if there was no CORS support. + aiohttp_cors_compatible_headers = web_res.headers.copy() + aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_ORIGIN, default=None) + aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS, default=None) + aiohttp_cors_compatible_headers.popall(hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, default=None) + + res = StreamResponse(headers=aiohttp_cors_compatible_headers, status=web_res.status) if web_res.headers.get('Transfer-Encoding', '').lower() == 'chunked': res.enable_chunked_encoding()