diff --git a/datasette/views/database.py b/datasette/views/database.py index 0cbb432b..9f43980b 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -43,7 +43,14 @@ class DatabaseDownload(BaseView): async def view_get(self, request, database, hash, correct_hash_present, **kwargs): if not self.ds.config("allow_download"): raise DatasetteError("Database download is forbidden", status=403) - filepath = self.ds.inspect()[database]["file"] + if database not in self.ds.databases: + raise DatasetteError("Invalid database", status=404) + db = self.ds.databases[database] + if db.is_memory: + raise DatasetteError("Cannot download :memory: database", status=404) + if not db.path: + raise DatasetteError("Cannot download database", status=404) + filepath = db.path return await response.file_stream( filepath, filename=os.path.basename(filepath), diff --git a/tests/fixtures.py b/tests/fixtures.py index c3883fe7..8f5f0b68 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -27,6 +27,7 @@ def make_app_client( sql_time_limit_ms=None, max_returned_rows=None, cors=False, + memory=False, config=None, filename="fixtures.db", is_immutable=False, @@ -51,6 +52,7 @@ def make_app_client( ds = Datasette( [] if is_immutable else [filepath], immutables=[filepath] if is_immutable else [], + memory=memory, cors=cors, metadata=METADATA, plugins_dir=plugins_dir, @@ -74,6 +76,9 @@ def app_client_no_files(): client.ds = ds yield client +@pytest.fixture(scope="session") +def app_client_with_memory(): + yield from make_app_client(memory=True) @pytest.fixture(scope="session") def app_client_with_hash(): diff --git a/tests/test_html.py b/tests/test_html.py index 18e6bb04..1babaa60 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -3,6 +3,7 @@ from .fixtures import ( # noqa app_client, app_client_shorter_time_limit, app_client_with_hash, + app_client_with_memory, make_app_client, ) import pytest @@ -676,12 +677,25 @@ def test_table_metadata(app_client): assert_footer_links(soup) -def test_allow_download_on(app_client): - response = app_client.get( +def test_database_download(app_client_with_memory): + # Regular page should have a download link + response = app_client_with_memory.get( "/fixtures" ) soup = Soup(response.body, 'html.parser') assert len(soup.findAll('a', {'href': re.compile(r'\.db$')})) + # Check we can actually download it + assert 200 == app_client_with_memory.get( + "/fixtures.db", + ).status + # Memory page should NOT have a download link + response2 = app_client_with_memory.get("/:memory:") + soup2 = Soup(response2.body, 'html.parser') + assert 0 == len(soup2.findAll('a', {'href': re.compile(r'\.db$')})) + # The URL itself should 404 + assert 404 == app_client_with_memory.get( + "/:memory:.db", + ).status def test_allow_download_off(): @@ -690,14 +704,12 @@ def test_allow_download_off(): }): response = client.get( "/fixtures", - ) soup = Soup(response.body, 'html.parser') assert not len(soup.findAll('a', {'href': re.compile(r'\.db$')})) # Accessing URL directly should 403 response = client.get( "/fixtures.db", - ) assert 403 == response.status