Skip to content

Commit

Permalink
Resolved #1299 - Allow StaticRoute to follow symlinks (#1327)
Browse files Browse the repository at this point in the history
* Resolved #1299 - Allow StaticRoute to follow symlinks

* Updated CHANGES with issue no

* added symlinks to spelling

* also added symlink in spelling

* Added test for follow_symlinks

* Removed print and changed doc
  • Loading branch information
alexm92 authored and asvetlov committed Oct 23, 2016
1 parent 3062e4c commit 1ae3ae6
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ CHANGES

- Added save and load functionality for `CookieJar` #1219

-
- Added option on `StaticRoute` to follow symlinks #1299

-

Expand Down
11 changes: 7 additions & 4 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ class StaticResource(PrefixResource):
def __init__(self, prefix, directory, *, name=None,
expect_handler=None, chunk_size=256*1024,
response_factory=StreamResponse,
show_index=False):
show_index=False, follow_symlinks=False):
super().__init__(prefix, name=name)
try:
directory = Path(directory)
Expand All @@ -351,6 +351,7 @@ def __init__(self, prefix, directory, *, name=None,
self._file_sender = FileSender(resp_factory=response_factory,
chunk_size=chunk_size)
self._show_index = show_index
self._follow_symlinks = follow_symlinks

self._routes = {'GET': ResourceRoute('GET', self._handle, self,
expect_handler=expect_handler),
Expand Down Expand Up @@ -397,7 +398,8 @@ def _handle(self, request):
filename = unquote(request.match_info['filename'])
try:
filepath = self._directory.joinpath(filename).resolve()
filepath.relative_to(self._directory)
if not self._follow_symlinks:
filepath.relative_to(self._directory)
except (ValueError, FileNotFoundError) as error:
# relatively safe
raise HTTPNotFound() from error
Expand Down Expand Up @@ -709,7 +711,7 @@ def add_route(self, method, path, handler,

def add_static(self, prefix, path, *, name=None, expect_handler=None,
chunk_size=256*1024, response_factory=StreamResponse,
show_index=False):
show_index=False, follow_symlinks=False):
"""Add static files view.
prefix - url prefix
Expand All @@ -725,7 +727,8 @@ def add_static(self, prefix, path, *, name=None, expect_handler=None,
expect_handler=expect_handler,
chunk_size=chunk_size,
response_factory=response_factory,
show_index=show_index)
show_index=show_index,
follow_symlinks=follow_symlinks)
self._reg_resource(resource)
return resource

Expand Down
2 changes: 2 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ subprotocol
subprotocols
subtype
Svetlov
symlink
symlinks
syscall
syscalls
TCP
Expand Down
5 changes: 5 additions & 0 deletions docs/web.rst
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ instead could be enabled with ``show_index`` parameter set to ``True``::

app.router.add_static('/prefix', path_to_static_folder, show_index=True)

When a symlink from the static directory is accessed, the server responses to
client with ``HTTP/404 Not Found`` by default. To allow the server to follow
symlinks, parameter ``follow_symlinks`` should be set to ``True``::

app.router.add_static('/prefix', path_to_static_folder, follow_symlinks=True)

Template Rendering
------------------
Expand Down
7 changes: 6 additions & 1 deletion docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,8 @@ Router is any object that implements :class:`AbstractRouter` interface.
.. method:: add_static(prefix, path, *, name=None, expect_handler=None, \
chunk_size=256*1024, \
response_factory=StreamResponse, \
show_index=False)
show_index=False, \
follow_symlinks=False)

Adds a router and a handler for returning static files.

Expand Down Expand Up @@ -1520,6 +1521,10 @@ Router is any object that implements :class:`AbstractRouter` interface.
by default it's not allowed and HTTP/403 will
be returned on directory access.

:param bool follow_symlinks: flag for allowing to follow symlinks from
a directory, by default it's not allowed and
HTTP/404 will be returned on access.

:returns: new :class:`StaticRoute` instance.

.. coroutinemethod:: resolve(request)
Expand Down
30 changes: 30 additions & 0 deletions tests/test_web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,36 @@ def test_access_root_of_static_handler(tmp_dir_path, loop, test_client,
yield from r.release()


@pytest.mark.parametrize('data', ['hello world'])
@asyncio.coroutine
def test_follow_symlink(tmp_dir_path, loop, test_client, data):
"""
Tests the access to a symlink, in static folder
"""
my_dir_path = os.path.join(tmp_dir_path, 'my_dir')
os.mkdir(my_dir_path)

my_file_path = os.path.join(my_dir_path, 'my_file_in_dir')
with open(my_file_path, 'w') as fw:
fw.write(data)

my_symlink_path = os.path.join(tmp_dir_path, 'my_symlink')
os.symlink(my_dir_path, my_symlink_path)

app = web.Application(loop=loop)

# Register global static route:
app.router.add_static('/', tmp_dir_path, follow_symlinks=True)
client = yield from test_client(app)

# Request the root of the static directory.
r = yield from client.get('/my_symlink/my_file_in_dir')
assert r.status == 200
assert (yield from r.text()) == data

yield from r.release()


@pytest.mark.parametrize('dir_name,filename,data', [
('', 'test file.txt', 'test text'),
('test dir name', 'test dir file .txt', 'test text file folder')
Expand Down

0 comments on commit 1ae3ae6

Please sign in to comment.