Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scanpipe/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
# Syntax in .env: SCANCODEIO_GLOBAL_WEBHOOK=target_url=https://webhook.url,
# trigger_on_each_run=False,include_summary=True,include_results=False
"GLOBAL_WEBHOOK": {},
# Maximum file size, in bytes, served inline in the browser rather than
# forced as an attachment download. Above this size, the browser tab
# rendering the file (e.g. a large JSON) risks hanging.
"INLINE_DOWNLOAD_MAX_SIZE": 10_000_000,
# Default limit for "most common" entries in QuerySets.
"MOST_COMMON_LIMIT": 7,
# Syntax in .env: SCANCODEIO_NETRC_LOCATION="~/.netrc"
Expand Down
26 changes: 22 additions & 4 deletions scanpipe/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ def test_scanpipe_views_project_details_download_input_view(self):
response = self.client.get(url)
self.assertTrue(response.getvalue().startswith(b"# SPDX-License-Identifier"))
self.assertEqual("application/octet-stream", response.headers["Content-Type"])
self.assertEqual(
'inline; filename="notice.NOTICE"',
response.headers["Content-Disposition"],
)

with override_settings(SCANPIPE={"INLINE_DOWNLOAD_MAX_SIZE": 0}):
response = self.client.get(url)
self.assertEqual(
'attachment; filename="notice.NOTICE"',
response.headers["Content-Disposition"],
Expand All @@ -381,6 +388,13 @@ def test_scanpipe_views_project_details_download_output_view(self):
response = self.client.get(url)
self.assertTrue(response.getvalue().startswith(b"# SPDX-License-Identifier"))
self.assertEqual("application/octet-stream", response.headers["Content-Type"])
self.assertEqual(
'inline; filename="notice.NOTICE"',
response.headers["Content-Disposition"],
)

with override_settings(SCANPIPE={"INLINE_DOWNLOAD_MAX_SIZE": 0}):
response = self.client.get(url)
self.assertEqual(
'attachment; filename="notice.NOTICE"',
response.headers["Content-Disposition"],
Expand Down Expand Up @@ -1491,6 +1505,10 @@ def test_project_packages_export_json(self):

self.assertIsInstance(response, FileResponse)
self.assertEqual(response.get("Content-Type"), "application/json")
self.assertTrue(response.get("Content-Disposition").startswith("inline"))

with override_settings(SCANPIPE={"INLINE_DOWNLOAD_MAX_SIZE": 0}):
response = self.client.get(url + "?export_json=True")
self.assertTrue(response.get("Content-Disposition").startswith("attachment"))

file_content = b"".join(response.streaming_content).decode("utf-8")
Expand Down Expand Up @@ -1556,7 +1574,7 @@ def test_project_dependencies_export_json(self):

self.assertIsInstance(response, FileResponse)
self.assertEqual(response.get("Content-Type"), "application/json")
self.assertTrue(response.get("Content-Disposition").startswith("attachment"))
self.assertTrue(response.get("Content-Disposition").startswith("inline"))

file_content = b"".join(response.streaming_content).decode("utf-8")
json_data = json.loads(file_content)
Expand Down Expand Up @@ -1592,7 +1610,7 @@ def test_project_relations_export_json(self):

self.assertIsInstance(response, FileResponse)
self.assertEqual(response.get("Content-Type"), "application/json")
self.assertTrue(response.get("Content-Disposition").startswith("attachment"))
self.assertTrue(response.get("Content-Disposition").startswith("inline"))

file_content = b"".join(response.streaming_content).decode("utf-8")
json_data = json.loads(file_content)
Expand All @@ -1616,7 +1634,7 @@ def test_project_messages_export_json(self):

self.assertIsInstance(response, FileResponse)
self.assertEqual(response.get("Content-Type"), "application/json")
self.assertTrue(response.get("Content-Disposition").startswith("attachment"))
self.assertTrue(response.get("Content-Disposition").startswith("inline"))

file_content = b"".join(response.streaming_content).decode("utf-8")
json_data = json.loads(file_content)
Expand All @@ -1642,7 +1660,7 @@ def test_project_codebase_resources_export_json(self):

self.assertIsInstance(response, FileResponse)
self.assertEqual(response.get("Content-Type"), "application/json")
self.assertTrue(response.get("Content-Disposition").startswith("attachment"))
self.assertTrue(response.get("Content-Disposition").startswith("inline"))

file_content = b"".join(response.streaming_content).decode("utf-8")
json_data = json.loads(file_content)
Expand Down
9 changes: 6 additions & 3 deletions scanpipe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,13 @@ def export_json_file_response(self):
serializer = serializer_class(queryset, many=True)
serialized_data = json.dumps(serializer.data, indent=2, cls=DjangoJSONEncoder)

output_file = io.BytesIO(serialized_data.encode("utf-8"))
encoded_data = serialized_data.encode("utf-8")
output_file = io.BytesIO(encoded_data)
is_too_large = len(encoded_data) > scanpipe_settings.INLINE_DOWNLOAD_MAX_SIZE

return FileResponse(
output_file,
as_attachment=True,
as_attachment=is_too_large,
filename=self.get_export_json_filename(),
content_type="application/json",
)
Expand Down Expand Up @@ -1532,7 +1534,8 @@ def download_project_file(request, slug, filename, path_type):
if not file_path.exists():
raise Http404(f"{file_path} not found")

return FileResponse(file_path.open("rb"), as_attachment=True)
is_too_large = file_path.stat().st_size > scanpipe_settings.INLINE_DOWNLOAD_MAX_SIZE
return FileResponse(file_path.open("rb"), as_attachment=is_too_large)


@conditional_login_required
Expand Down
Loading