We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[reply refactor] Allow journalists to download replies from journalist interface
After #3673 is implemented, we should allow journalists to download replies from the journalist interface UI. Note that for long-running SecureDrop instances, there will be old replies encrypted only to the source key that should be unavailable for download.

Epic: #3097

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/journalist_app/col.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 from flask import (Blueprint, redirect, url_for, render_template, flash,
4                    request, abort, send_file, current_app)
5 from flask_babel import gettext
6 from sqlalchemy.orm.exc import NoResultFound
7 
8 from db import db
9 from models import Submission
10 from journalist_app.forms import ReplyForm
11 from journalist_app.utils import (make_star_true, make_star_false, get_source,
12                                   delete_collection, col_download_unread,
13                                   col_download_all, col_star, col_un_star,
14                                   col_delete)
15 
16 
17 def make_blueprint(config):
18     view = Blueprint('col', __name__)
19 
20     @view.route('/add_star/<filesystem_id>', methods=('POST',))
21     def add_star(filesystem_id):
22         make_star_true(filesystem_id)
23         db.session.commit()
24         return redirect(url_for('main.index'))
25 
26     @view.route("/remove_star/<filesystem_id>", methods=('POST',))
27     def remove_star(filesystem_id):
28         make_star_false(filesystem_id)
29         db.session.commit()
30         return redirect(url_for('main.index'))
31 
32     @view.route('/<filesystem_id>')
33     def col(filesystem_id):
34         form = ReplyForm()
35         source = get_source(filesystem_id)
36         source.has_key = current_app.crypto_util.getkey(filesystem_id)
37         return render_template("col.html", filesystem_id=filesystem_id,
38                                source=source, form=form)
39 
40     @view.route('/delete/<filesystem_id>', methods=('POST',))
41     def delete_single(filesystem_id):
42         """deleting a single collection from its /col page"""
43         source = get_source(filesystem_id)
44         delete_collection(filesystem_id)
45         flash(gettext("{source_name}'s collection deleted")
46               .format(source_name=source.journalist_designation),
47               "notification")
48         return redirect(url_for('main.index'))
49 
50     @view.route('/process', methods=('POST',))
51     def process():
52         actions = {'download-unread': col_download_unread,
53                    'download-all': col_download_all, 'star': col_star,
54                    'un-star': col_un_star, 'delete': col_delete}
55         if 'cols_selected' not in request.form:
56             flash(gettext('No collections selected.'), 'error')
57             return redirect(url_for('main.index'))
58 
59         # getlist is cgi.FieldStorage.getlist
60         cols_selected = request.form.getlist('cols_selected')
61         action = request.form['action']
62 
63         if action not in actions:
64             return abort(500)
65 
66         method = actions[action]
67         return method(cols_selected)
68 
69     @view.route('/<filesystem_id>/<fn>')
70     def download_single_submission(filesystem_id, fn):
71         """Sends a client the contents of a single submission."""
72         if '..' in fn or fn.startswith('/'):
73             abort(404)
74 
75         try:
76             Submission.query.filter(
77                 Submission.filename == fn).one().downloaded = True
78             db.session.commit()
79         except NoResultFound as e:
80             current_app.logger.error(
81                 "Could not mark " + fn + " as downloaded: %s" % (e,))
82 
83         return send_file(current_app.storage.path(filesystem_id, fn),
84                          mimetype="application/pgp-encrypted")
85 
86     return view
87
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/securedrop/journalist_app/col.py b/securedrop/journalist_app/col.py
--- a/securedrop/journalist_app/col.py
+++ b/securedrop/journalist_app/col.py
@@ -67,18 +67,21 @@
         return method(cols_selected)
 
     @view.route('/<filesystem_id>/<fn>')
-    def download_single_submission(filesystem_id, fn):
-        """Sends a client the contents of a single submission."""
+    def download_single_file(filesystem_id, fn):
+        """Sends a client the contents of a single file, either a submission
+        or a journalist reply"""
         if '..' in fn or fn.startswith('/'):
             abort(404)
 
-        try:
-            Submission.query.filter(
-                Submission.filename == fn).one().downloaded = True
-            db.session.commit()
-        except NoResultFound as e:
-            current_app.logger.error(
-                "Could not mark " + fn + " as downloaded: %s" % (e,))
+        # only mark as read when it's a submission (and not a journalist reply)
+        if not fn.endswith('reply.gpg'):
+            try:
+                Submission.query.filter(
+                    Submission.filename == fn).one().downloaded = True
+                db.session.commit()
+            except NoResultFound as e:
+                current_app.logger.error(
+                    "Could not mark " + fn + " as downloaded: %s" % (e,))
 
         return send_file(current_app.storage.path(filesystem_id, fn),
                          mimetype="application/pgp-encrypted")

{"golden_diff": "diff --git a/securedrop/journalist_app/col.py b/securedrop/journalist_app/col.py\n--- a/securedrop/journalist_app/col.py\n+++ b/securedrop/journalist_app/col.py\n@@ -67,18 +67,21 @@\n         return method(cols_selected)\n \n     @view.route('/<filesystem_id>/<fn>')\n-    def download_single_submission(filesystem_id, fn):\n-        \"\"\"Sends a client the contents of a single submission.\"\"\"\n+    def download_single_file(filesystem_id, fn):\n+        \"\"\"Sends a client the contents of a single file, either a submission\n+        or a journalist reply\"\"\"\n         if '..' in fn or fn.startswith('/'):\n             abort(404)\n \n-        try:\n-            Submission.query.filter(\n-                Submission.filename == fn).one().downloaded = True\n-            db.session.commit()\n-        except NoResultFound as e:\n-            current_app.logger.error(\n-                \"Could not mark \" + fn + \" as downloaded: %s\" % (e,))\n+        # only mark as read when it's a submission (and not a journalist reply)\n+        if not fn.endswith('reply.gpg'):\n+            try:\n+                Submission.query.filter(\n+                    Submission.filename == fn).one().downloaded = True\n+                db.session.commit()\n+            except NoResultFound as e:\n+                current_app.logger.error(\n+                    \"Could not mark \" + fn + \" as downloaded: %s\" % (e,))\n \n         return send_file(current_app.storage.path(filesystem_id, fn),\n                          mimetype=\"application/pgp-encrypted\")\n", "issue": "[reply refactor] Allow journalists to download replies from journalist interface\nAfter #3673 is implemented, we should allow journalists to download replies from the journalist interface UI. Note that for long-running SecureDrop instances, there will be old replies encrypted only to the source key that should be unavailable for download.\r\n\r\nEpic: #3097\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nfrom flask import (Blueprint, redirect, url_for, render_template, flash,\n                   request, abort, send_file, current_app)\nfrom flask_babel import gettext\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom db import db\nfrom models import Submission\nfrom journalist_app.forms import ReplyForm\nfrom journalist_app.utils import (make_star_true, make_star_false, get_source,\n                                  delete_collection, col_download_unread,\n                                  col_download_all, col_star, col_un_star,\n                                  col_delete)\n\n\ndef make_blueprint(config):\n    view = Blueprint('col', __name__)\n\n    @view.route('/add_star/<filesystem_id>', methods=('POST',))\n    def add_star(filesystem_id):\n        make_star_true(filesystem_id)\n        db.session.commit()\n        return redirect(url_for('main.index'))\n\n    @view.route(\"/remove_star/<filesystem_id>\", methods=('POST',))\n    def remove_star(filesystem_id):\n        make_star_false(filesystem_id)\n        db.session.commit()\n        return redirect(url_for('main.index'))\n\n    @view.route('/<filesystem_id>')\n    def col(filesystem_id):\n        form = ReplyForm()\n        source = get_source(filesystem_id)\n        source.has_key = current_app.crypto_util.getkey(filesystem_id)\n        return render_template(\"col.html\", filesystem_id=filesystem_id,\n                               source=source, form=form)\n\n    @view.route('/delete/<filesystem_id>', methods=('POST',))\n    def delete_single(filesystem_id):\n        \"\"\"deleting a single collection from its /col page\"\"\"\n        source = get_source(filesystem_id)\n        delete_collection(filesystem_id)\n        flash(gettext(\"{source_name}'s collection deleted\")\n              .format(source_name=source.journalist_designation),\n              \"notification\")\n        return redirect(url_for('main.index'))\n\n    @view.route('/process', methods=('POST',))\n    def process():\n        actions = {'download-unread': col_download_unread,\n                   'download-all': col_download_all, 'star': col_star,\n                   'un-star': col_un_star, 'delete': col_delete}\n        if 'cols_selected' not in request.form:\n            flash(gettext('No collections selected.'), 'error')\n            return redirect(url_for('main.index'))\n\n        # getlist is cgi.FieldStorage.getlist\n        cols_selected = request.form.getlist('cols_selected')\n        action = request.form['action']\n\n        if action not in actions:\n            return abort(500)\n\n        method = actions[action]\n        return method(cols_selected)\n\n    @view.route('/<filesystem_id>/<fn>')\n    def download_single_submission(filesystem_id, fn):\n        \"\"\"Sends a client the contents of a single submission.\"\"\"\n        if '..' in fn or fn.startswith('/'):\n            abort(404)\n\n        try:\n            Submission.query.filter(\n                Submission.filename == fn).one().downloaded = True\n            db.session.commit()\n        except NoResultFound as e:\n            current_app.logger.error(\n                \"Could not mark \" + fn + \" as downloaded: %s\" % (e,))\n\n        return send_file(current_app.storage.path(filesystem_id, fn),\n                         mimetype=\"application/pgp-encrypted\")\n\n    return view\n", "path": "securedrop/journalist_app/col.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nfrom flask import (Blueprint, redirect, url_for, render_template, flash,\n                   request, abort, send_file, current_app)\nfrom flask_babel import gettext\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom db import db\nfrom models import Submission\nfrom journalist_app.forms import ReplyForm\nfrom journalist_app.utils import (make_star_true, make_star_false, get_source,\n                                  delete_collection, col_download_unread,\n                                  col_download_all, col_star, col_un_star,\n                                  col_delete)\n\n\ndef make_blueprint(config):\n    view = Blueprint('col', __name__)\n\n    @view.route('/add_star/<filesystem_id>', methods=('POST',))\n    def add_star(filesystem_id):\n        make_star_true(filesystem_id)\n        db.session.commit()\n        return redirect(url_for('main.index'))\n\n    @view.route(\"/remove_star/<filesystem_id>\", methods=('POST',))\n    def remove_star(filesystem_id):\n        make_star_false(filesystem_id)\n        db.session.commit()\n        return redirect(url_for('main.index'))\n\n    @view.route('/<filesystem_id>')\n    def col(filesystem_id):\n        form = ReplyForm()\n        source = get_source(filesystem_id)\n        source.has_key = current_app.crypto_util.getkey(filesystem_id)\n        return render_template(\"col.html\", filesystem_id=filesystem_id,\n                               source=source, form=form)\n\n    @view.route('/delete/<filesystem_id>', methods=('POST',))\n    def delete_single(filesystem_id):\n        \"\"\"deleting a single collection from its /col page\"\"\"\n        source = get_source(filesystem_id)\n        delete_collection(filesystem_id)\n        flash(gettext(\"{source_name}'s collection deleted\")\n              .format(source_name=source.journalist_designation),\n              \"notification\")\n        return redirect(url_for('main.index'))\n\n    @view.route('/process', methods=('POST',))\n    def process():\n        actions = {'download-unread': col_download_unread,\n                   'download-all': col_download_all, 'star': col_star,\n                   'un-star': col_un_star, 'delete': col_delete}\n        if 'cols_selected' not in request.form:\n            flash(gettext('No collections selected.'), 'error')\n            return redirect(url_for('main.index'))\n\n        # getlist is cgi.FieldStorage.getlist\n        cols_selected = request.form.getlist('cols_selected')\n        action = request.form['action']\n\n        if action not in actions:\n            return abort(500)\n\n        method = actions[action]\n        return method(cols_selected)\n\n    @view.route('/<filesystem_id>/<fn>')\n    def download_single_file(filesystem_id, fn):\n        \"\"\"Sends a client the contents of a single file, either a submission\n        or a journalist reply\"\"\"\n        if '..' in fn or fn.startswith('/'):\n            abort(404)\n\n        # only mark as read when it's a submission (and not a journalist reply)\n        if not fn.endswith('reply.gpg'):\n            try:\n                Submission.query.filter(\n                    Submission.filename == fn).one().downloaded = True\n                db.session.commit()\n            except NoResultFound as e:\n                current_app.logger.error(\n                    \"Could not mark \" + fn + \" as downloaded: %s\" % (e,))\n\n        return send_file(current_app.storage.path(filesystem_id, fn),\n                         mimetype=\"application/pgp-encrypted\")\n\n    return view\n", "path": "securedrop/journalist_app/col.py"}]}