diff --git a/CHANGES/8565.bugfix.rst b/CHANGES/8565.bugfix.rst new file mode 100644 index 00000000000..35e7c4dc71a --- /dev/null +++ b/CHANGES/8565.bugfix.rst @@ -0,0 +1 @@ +Fixed server checks for circular symbolic links to be compatible with Python 3.13 -- by :user:`steverep`. diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 7fc5b3d787f..d8bbbe08993 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -191,7 +191,9 @@ async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter file_path, st, file_encoding = await loop.run_in_executor( None, self._get_file_path_stat_encoding, accept_encoding ) - except FileNotFoundError: + except OSError: + # Most likely to be FileNotFoundError or OSError for circular + # symlinks in python >= 3.13, so respond with 404. self.set_status(HTTPNotFound.status_code) return await super().prepare(request) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 688946626fd..558fb7d0c9b 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -80,9 +80,9 @@ BaseDict = dict CIRCULAR_SYMLINK_ERROR = ( - OSError + (OSError,) if sys.version_info < (3, 10) and sys.platform.startswith("win32") - else RuntimeError + else (RuntimeError,) if sys.version_info < (3, 13) else () ) YARL_VERSION: Final[Tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2])) @@ -694,8 +694,9 @@ def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse: else: file_path = unresolved_path.resolve() file_path.relative_to(self._directory) - except (ValueError, CIRCULAR_SYMLINK_ERROR) as error: - # ValueError for relative check; RuntimeError for circular symlink. + except (ValueError, *CIRCULAR_SYMLINK_ERROR) as error: + # ValueError is raised for the relative check. Circular symlinks + # raise here on resolving for python < 3.13. raise HTTPNotFound() from error # if path is a directory, return the contents if permitted. Note the