Repository: getsentry/sentry

Issue:
BadHeaderError: Header values can't contain newlines (got 'attachment; filename="vendor.js.map\r\n\n             ...
https://sentry.io/sentry/sentry/issues/202980928/

```
BadHeaderError: Header values can't contain newlines (got 'attachment; filename="vendor.js.map\r\n\n                "')
(1 additional frame(s) were not displayed)
...
  File "sentry/api/base.py", line 86, in handle_exception
    return super(Endpoint, self).handle_exception(exc)
  File "sentry/api/base.py", line 180, in dispatch
    response = handler(request, *args, **kwargs)
  File "sentry/api/endpoints/release_file_details.py", line 123, in get
    return self.download(releasefile)
  File "sentry/api/endpoints/release_file_details.py", line 83, in download
    response['Content-Disposition'] = 'attachment; filename="%s"' % posixpath.basename(releasefile.name)

BadHeaderError: Header values can't contain newlines (got 'attachment; filename="vendor.js.map\r\n\n                "')
```


Discussion:

Resolving patch:
diff --git a/src/sentry/api/endpoints/release_file_details.py b/src/sentry/api/endpoints/release_file_details.py
--- a/src/sentry/api/endpoints/release_file_details.py
+++ b/src/sentry/api/endpoints/release_file_details.py
@@ -80,7 +80,7 @@ def download(self, releasefile):
             content_type=file.headers.get('content-type', 'application/octet-stream'),
         )
         response['Content-Length'] = file.size
-        response['Content-Disposition'] = 'attachment; filename="%s"' % posixpath.basename(releasefile.name)
+        response['Content-Disposition'] = 'attachment; filename="%s"' % posixpath.basename(" ".join(releasefile.name.split()))
         return response
 
     @attach_scenarios([retrieve_file_scenario])


Test patch:
diff --git a/tests/sentry/api/endpoints/test_release_file_details.py b/tests/sentry/api/endpoints/test_release_file_details.py
--- a/tests/sentry/api/endpoints/test_release_file_details.py
+++ b/tests/sentry/api/endpoints/test_release_file_details.py
@@ -58,7 +58,7 @@ def test_file_download(self):
 
         from six import BytesIO
         f = File.objects.create(
-            name='application.js',
+            name='applicatiosn.js',
             type='release.file',
         )
         f.putfile(BytesIO('File contents here'))
@@ -68,7 +68,7 @@ def test_file_download(self):
             project=project,
             release=release,
             file=f,
-            name='http://example.com/application.js'
+            name='  http://example.com/appli\n\rcatios n.js\n\n\r  '
         )
 
         url = reverse('sentry-api-0-release-file-details', kwargs={
@@ -80,7 +80,7 @@ def test_file_download(self):
 
         response = self.client.get(url + '?download=1')
         assert response.status_code == 200, response.content
-        assert response.get('Content-Disposition') == 'attachment; filename="application.js"'
+        assert response.get('Content-Disposition') == 'attachment; filename="appli catios n.js"'
         assert response.get('Content-Length') == six.text_type(f.size)
         assert response.get('Content-Type') == 'application/octet-stream'
         assert 'File contents here' == BytesIO(b"".join(response.streaming_content)).getvalue()