diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 600b1948c99..cfd7efd765d 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -21,8 +21,8 @@ from .file_sender import FileSender from .protocol import HttpVersion11 from .web_exceptions import (HTTPMethodNotAllowed, HTTPNotFound, - HTTPExpectationFailed) -from .web_reqrep import StreamResponse + HTTPExpectationFailed, HTTPForbidden) +from .web_reqrep import Response, StreamResponse __all__ = ('UrlDispatcher', 'UrlMappingMatchInfo', @@ -440,7 +440,8 @@ class StaticRoute(Route): def __init__(self, name, prefix, directory, *, expect_handler=None, chunk_size=256*1024, - response_factory=StreamResponse): + response_factory=StreamResponse, + show_index=False): assert prefix.startswith('/'), prefix assert prefix.endswith('/'), prefix super().__init__( @@ -460,6 +461,7 @@ def __init__(self, name, prefix, directory, *, self._directory = directory self._file_sender = FileSender(resp_factory=response_factory, chunk_size=chunk_size) + self._show_index = show_index def match(self, path): if not path.startswith(self._prefix): @@ -492,13 +494,56 @@ def handle(self, request): request.app.logger.exception(error) raise HTTPNotFound() from error - # Make sure that filepath is a file - if not filepath.is_file(): - raise HTTPNotFound() + # on opening a dir, load it's contents if allowed + if filepath.is_dir(): + if self._show_index: + ret = Response(text=self._directory_as_html(filepath)) + else: + raise HTTPForbidden() + elif filepath.is_file(): + ret = yield from self._file_sender.send(request, filepath) + else: + raise HTTPNotFound - ret = yield from self._file_sender.send(request, filepath) return ret + def _directory_as_html(self, filepath): + "returns directory's index as html" + # sanity check + assert filepath.is_dir() + + posix_dir_len = len(self._directory.as_posix()) + + # remove the beginning of posix path, so it would be relative + # to our added static path + relative_path_to_dir = filepath.as_posix()[posix_dir_len:] + index_of = "Index of /{}".format(relative_path_to_dir) + head = "\n{}\n".format(index_of) + h1 = "

{}

".format(index_of) + + index_list = [] + dir_index = filepath.iterdir() + for _file in dir_index: + # show file url as relative to static path + file_url = _file.as_posix()[posix_dir_len:] + + # if file is a directory, add '/' to the end of the name + if _file.is_dir(): + file_name = "{}/".format(_file.name) + else: + file_name = _file.name + + index_list.append( + '
  • {name}
  • '.format(url=file_url, + name=file_name) + ) + ul = "".format('\n'.join(index_list)) + body = "\n{}\n{}\n".format(h1, ul) + + html = "\n{}\n{}\n".format(head, body) + + return html + def __repr__(self): name = "'" + self.name + "' " if self.name is not None else "" return " {directory!r}".format( @@ -723,7 +768,8 @@ def add_route(self, method, path, handler, expect_handler=expect_handler) def add_static(self, prefix, path, *, name=None, expect_handler=None, - chunk_size=256*1024, response_factory=StreamResponse): + chunk_size=256*1024, response_factory=StreamResponse, + show_index=False): """ Adds static files view :param prefix - url prefix @@ -735,6 +781,7 @@ def add_static(self, prefix, path, *, name=None, expect_handler=None, route = StaticRoute(name, prefix, path, expect_handler=expect_handler, chunk_size=chunk_size, - response_factory=response_factory) + response_factory=response_factory, + show_index=show_index) self.register_route(route) return route diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8cf12a7a3b6..7668a33d605 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -231,7 +231,7 @@ Any web site has static files: images, JavaScript sources, CSS files etc. The best way to handle static in production is setting up reverse proxy like NGINX or using CDN services. -But for development handling static files by aiohttp server is very convinient. +But for development handling static files by aiohttp server is very convenient. Fortunatelly it can be done easy by single call:: diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 79368a98f73..29f4fc7816b 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -1203,7 +1203,8 @@ Router is any object that implements :class:`AbstractRouter` interface. :returns: new :class:`PlainRoute` or :class:`DynamicRoute` instance. .. method:: add_static(prefix, path, *, name=None, expect_handler=None, \ - chunk_size=256*1024, response_factory=StreamResponse) + chunk_size=256*1024, response_factory=StreamResponse \ + show_index=False) Adds a router and a handler for returning static files. @@ -1255,6 +1256,10 @@ Router is any object that implements :class:`AbstractRouter` interface. .. versionadded:: 0.17 + :param bool show_index: flag for allowing to show indexes of a directory, + by default it's not allowed and HTTP/403 will + be returned on directory access. + :returns: new :class:`StaticRoute` instance. .. coroutinemethod:: resolve(requst) diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py index 907e9e972ac..8feeaa9e1b3 100644 --- a/tests/test_web_urldispatcher.py +++ b/tests/test_web_urldispatcher.py @@ -29,12 +29,15 @@ def teardown(): return tmp_dir +@pytest.mark.parametrize("show_index,status", [(False, 403), (True, 200)]) @pytest.mark.run_loop -def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client): +def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client, + show_index, status): """ Tests the operation of static file server. Try to access the root of static file server, and make - sure that a proper not found error is returned. + sure that correct HTTP statuses are returned depending if we directory + index should be shown or not. """ # Put a file inside tmp_dir_path: my_file_path = os.path.join(tmp_dir_path, 'my_file') @@ -44,12 +47,11 @@ def test_access_root_of_static_handler(tmp_dir_path, create_app_and_client): app, client = yield from create_app_and_client() # Register global static route: - app.router.add_static('/', tmp_dir_path) + app.router.add_static('/', tmp_dir_path, show_index=show_index) # Request the root of the static directory. - # Expect an 404 error page. r = yield from client.get('/') - assert r.status == 404 + assert r.status == status # data = (yield from r.read()) yield from r.release()