Repository: encode/starlette

Issue:
When the directory itself being a symlink does not work.
My test code:

```python
from fastapi import FastAPI
import uvicorn

from staticfiles import StaticFiles

app = FastAPI()
app.mount("/music", StaticFiles(directory="/home/hanxi/work/xiaomusic/music", follow_symlink=True), name="music")

uvicorn.run(
    app,
    host=["::", "0.0.0.0"],
    port=3333,
)
```

The directory /home/hanxi/work/xiaomusic/music is a symlink.

```
> ls -l /home/hanxi/work/xiaomusic/music
lrwxrwxrwx 1 hanxi hanxi 26 Apr 30 12:10 /home/hanxi/work/xiaomusic/music -> /home/hanxi/nas/data/music
```

I fix this in https://github.com/hanxi/starlette/commit/831cb5d255832d9f704ffd7be49b4184cd301dd6

Need pr?

<!-- POLAR PLEDGE BADGE START -->
> [!IMPORTANT]
> - We're using [Polar.sh](https://polar.sh/encode) so you can upvote and help fund this issue.
> - We receive the funding once the issue is completed & confirmed by you.
> - Thank you in advance for helping prioritize & fund our backlog.

<a href="https://polar.sh/encode/starlette/issues/2646">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/encode/starlette/issues/2646/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/encode/starlette/issues/2646/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->


Discussion:
Kludex: A test case is needed here.
hanxi: Okay, I'll add the test cases.
hanxi: done.

Patch:
diff --git a/starlette/staticfiles.py b/starlette/staticfiles.py
index 7498c30..746e740 100644
--- a/starlette/staticfiles.py
+++ b/starlette/staticfiles.py
@@ -155,7 +155,7 @@ class StaticFiles:
                 full_path = os.path.abspath(joined_path)
             else:
                 full_path = os.path.realpath(joined_path)
-            directory = os.path.realpath(directory)
+                directory = os.path.realpath(directory)
             if os.path.commonpath([full_path, directory]) != directory:
                 # Don't allow misbehaving clients to break out of the static files
                 # directory.


Test patch:
diff --git a/tests/test_staticfiles.py b/tests/test_staticfiles.py
index 8beb3cd..8f74235 100644
--- a/tests/test_staticfiles.py
+++ b/tests/test_staticfiles.py
@@ -559,3 +559,23 @@ def test_staticfiles_avoids_path_traversal(tmp_path: Path) -> None:
 
     assert exc_info.value.status_code == 404
     assert exc_info.value.detail == "Not Found"
+
+
+def test_staticfiles_self_symlinks(tmpdir: Path, test_client_factory: TestClientFactory) -> None:
+    statics_path = os.path.join(tmpdir, "statics")
+    os.mkdir(statics_path)
+
+    source_file_path = os.path.join(statics_path, "index.html")
+    with open(source_file_path, "w") as file:
+        file.write("<h1>Hello</h1>")
+
+    statics_symlink_path = os.path.join(tmpdir, "statics_symlink")
+    os.symlink(statics_path, statics_symlink_path)
+
+    app = StaticFiles(directory=statics_symlink_path, follow_symlink=True)
+    client = test_client_factory(app)
+
+    response = client.get("/index.html")
+    assert response.url == "http://testserver/index.html"
+    assert response.status_code == 200
+    assert response.text == "<h1>Hello</h1>"