Repository: freedomofpress/securedrop

Issue:
404 errors when attempting to download missing files in JI
## Description

#5573 added a flash message to be displayed when a user attempts to download submissions that are missing from the application store. The message should be displayed on the original page - sources list or individual source page -  but is being displayed on a 404 page for `/col/None` in the former case and `/None` in the latter.

## Steps to Reproduce

- submit multiple files as a source in SI
- delete one from the file store on the app server
- attempt to download via the various singular and collective download options on the JI 

## Expected Behavior
Flash message is displayed, rest of behaviour as expected

## Actual Behavior
404s as described.

error logs as follows:
```
[Tue Jan 19 15:34:43.815637 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:52968]   File "/var/www/securedrop/journalist_app/__init__.py", line 96, in _handle_http_exception
[Tue Jan 19 15:34:43.815640 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:52968]     handler = list(app.error_handler_spec['api'][error.code].values())[0]
[Tue Jan 19 15:34:43.815662 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:52968] KeyError
[Tue Jan 19 15:46:50.450869 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:53004] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found
[Tue Jan 19 15:46:51.078081 2021] [wsgi:error] [pid 9080:tid 129094201378560] [remote 127.0.0.1:53004] ERROR:flask.app:Found none when one was expected: No row was found for one()
[Tue Jan 19 15:48:00.505418 2021] [wsgi:error] [pid 9080:tid 129094201378560] [remote 127.0.0.1:53016] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found
[Tue Jan 19 15:48:01.232360 2021] [wsgi:error] [pid 9079:tid 129094200379136] [remote 127.0.0.1:53016] ERROR:flask.app:Found none when one was expected: No row was found for one()
[Tue Jan 19 15:48:15.683907 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:53028] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found
[Tue Jan 19 15:48:56.431962 2021] [wsgi:error] [pid 9080:tid 129094201378560] [remote 127.0.0.1:53032] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found
[Tue Jan 19 15:55:15.465256 2021] [wsgi:error] [pid 9080:tid 129094009444096] [remote 127.0.0.1:53048] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found
[Tue Jan 19 15:55:37.184542 2021] [wsgi:error] [pid 9079:tid 129093955651328] [remote 127.0.0.1:53058] ERROR:flask.app:File /var/lib/securedrop/store/D7NFN2FZRXJVQIU7FBLUCLPGMGM6YUEQ7WLNY6Y2U5IVFOGTQEFO6NGONBE6YKOBE54O6O4QR52VK7BPXBG26HXZXJPT33CCVBEJDSY=/1-homemade_flanker-msg.gpg not found




Discussion:
I tested on nuc7 hardware, 1.7.0~rc2:

* When I click on the name of the file to try to download it (the `/col` path) I see the expected behavior.
* When I select the file and then click "Download", I hit the `/None` path and see the expected flash message but also the unexpected 404 Not Found message:

![Screenshot from 2021-01-19 22-36-41](https://user-images.githubusercontent.com/4522213/105103435-70775e80-5a65-11eb-915e-cef392fddf0b.png)


* I also see an Internal Server Error on the `/download_unread/None` path (by clicking the X unread button)
* I was unable to repro in the dev environment 

Resolving patch:
diff --git a/securedrop/journalist_app/main.py b/securedrop/journalist_app/main.py
--- a/securedrop/journalist_app/main.py
+++ b/securedrop/journalist_app/main.py
@@ -153,7 +153,7 @@ def flag() -> str:
     @view.route('/bulk', methods=('POST',))
     def bulk() -> Union[str, werkzeug.Response]:
         action = request.form['action']
-
+        error_redirect = url_for('col.col', filesystem_id=g.filesystem_id)
         doc_names_selected = request.form.getlist('doc_names_selected')
         selected_docs = [doc for doc in g.source.collection
                          if doc.filename in doc_names_selected]
@@ -164,11 +164,13 @@ def bulk() -> Union[str, werkzeug.Response]:
             elif action in ('delete', 'confirm_delete'):
                 flash(gettext("No collections selected for deletion."),
                       "error")
-            return redirect(url_for('col.col', filesystem_id=g.filesystem_id))
+            return redirect(error_redirect)
 
         if action == 'download':
             source = get_source(g.filesystem_id)
-            return download(source.journalist_filename, selected_docs)
+            return download(
+                source.journalist_filename, selected_docs, on_error_redirect=error_redirect
+            )
         elif action == 'delete':
             return bulk_delete(g.filesystem_id, selected_docs)
         elif action == 'confirm_delete':
diff --git a/securedrop/journalist_app/utils.py b/securedrop/journalist_app/utils.py
--- a/securedrop/journalist_app/utils.py
+++ b/securedrop/journalist_app/utils.py
@@ -3,7 +3,6 @@
 import datetime
 import os
 from typing import Optional, List, Union, Any
-from urllib.parse import urlparse
 
 import flask
 import werkzeug
@@ -187,7 +186,11 @@ def mark_seen(targets: List[Union[Submission, Reply]], user: Journalist) -> None
             raise
 
 
-def download(zip_basename: str, submissions: List[Union[Submission, Reply]]) -> werkzeug.Response:
+def download(
+    zip_basename: str,
+    submissions: List[Union[Submission, Reply]],
+    on_error_redirect: Optional[str] = None
+) -> werkzeug.Response:
     """Send client contents of ZIP-file *zip_basename*-<timestamp>.zip
     containing *submissions*. The ZIP-file, being a
     :class:`tempfile.NamedTemporaryFile`, is stored on disk only
@@ -208,8 +211,9 @@ def download(zip_basename: str, submissions: List[Union[Submission, Reply]]) ->
             ),
             "error"
         )
-        referrer = urlparse(str(request.referrer)).path
-        return redirect(referrer)
+        if on_error_redirect is None:
+            on_error_redirect = url_for('main.index')
+        return redirect(on_error_redirect)
 
     attachment_filename = "{}--{}.zip".format(
         zip_basename, datetime.datetime.utcnow().strftime("%Y-%m-%d--%H-%M-%S")


Test patch: