From 1f92213c3e0be8111a55391a86d03710c518f352 Mon Sep 17 00:00:00 2001
From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
Date: Thu, 8 Aug 2024 15:39:30 +0000
Subject: [PATCH] [PR #8642/e4942771 backport][3.10] Fix response to circular
 symlinks with Python v3.13 (#8648)

Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
---
 CHANGES/8565.bugfix.rst      | 1 +
 aiohttp/web_fileresponse.py  | 4 +++-
 aiohttp/web_urldispatcher.py | 9 +++++----
 3 files changed, 9 insertions(+), 5 deletions(-)
 create mode 100644 CHANGES/8565.bugfix.rst

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