From 92027345d06f97b133987aa17563bcf8fa9ed812 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 10 May 2021 11:23:44 +1000 Subject: [PATCH 1/8] Register the 405 error handler properly. Enforce the appropriate methods for each route (GET or POST only, with OPTIONS disabled). Add tests for invalid methods. Add a friendlier 500 internal server error handler --- .../resources/templates/500.html | 21 +++++++++++++++ cli/onionshare_cli/web/chat_mode.py | 2 +- cli/onionshare_cli/web/receive_mode.py | 6 ++--- cli/onionshare_cli/web/send_base_mode.py | 4 --- cli/onionshare_cli/web/share_mode.py | 6 ++--- cli/onionshare_cli/web/web.py | 27 +++++++++++++++++++ cli/onionshare_cli/web/website_mode.py | 4 +-- desktop/tests/gui_base_test.py | 14 ++++++++++ desktop/tests/test_gui_receive.py | 16 +++++++++++ desktop/tests/test_gui_share.py | 17 ++++++++++++ desktop/tests/test_gui_website.py | 16 +++++++++++ 11 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 cli/onionshare_cli/resources/templates/500.html diff --git a/cli/onionshare_cli/resources/templates/500.html b/cli/onionshare_cli/resources/templates/500.html new file mode 100644 index 00000000..9f6727d2 --- /dev/null +++ b/cli/onionshare_cli/resources/templates/500.html @@ -0,0 +1,21 @@ + + + + + OnionShare: An error occurred + + + + + + + +
+
+

+

Sorry, an unexpected error seems to have occurred, and your request didn't succeed.

+
+
+ + + diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index 8b2a5673..c772818d 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -46,7 +46,7 @@ class ChatModeWeb: The web app routes for chatting """ - @self.web.app.route("/") + @self.web.app.route("/", methods=["GET"], provide_automatic_options=False) def index(): history_id = self.cur_history_id self.cur_history_id += 1 diff --git a/cli/onionshare_cli/web/receive_mode.py b/cli/onionshare_cli/web/receive_mode.py index f5aae296..b3a146e3 100644 --- a/cli/onionshare_cli/web/receive_mode.py +++ b/cli/onionshare_cli/web/receive_mode.py @@ -71,7 +71,7 @@ class ReceiveModeWeb: The web app routes for receiving files """ - @self.web.app.route("/") + @self.web.app.route("/", methods=["GET"], provide_automatic_options=False) def index(): history_id = self.cur_history_id self.cur_history_id += 1 @@ -93,7 +93,7 @@ class ReceiveModeWeb: ) return self.web.add_security_headers(r) - @self.web.app.route("/upload", methods=["POST"]) + @self.web.app.route("/upload", methods=["POST"], provide_automatic_options=False) def upload(ajax=False): """ Handle the upload files POST request, though at this point, the files have @@ -225,7 +225,7 @@ class ReceiveModeWeb: ) return self.web.add_security_headers(r) - @self.web.app.route("/upload-ajax", methods=["POST"]) + @self.web.app.route("/upload-ajax", methods=["POST"], provide_automatic_options=False) def upload_ajax_public(): if not self.can_upload: return self.web.error403() diff --git a/cli/onionshare_cli/web/send_base_mode.py b/cli/onionshare_cli/web/send_base_mode.py index 742f6f75..2f3e0bbd 100644 --- a/cli/onionshare_cli/web/send_base_mode.py +++ b/cli/onionshare_cli/web/send_base_mode.py @@ -208,10 +208,6 @@ class SendBaseModeWeb: history_id = self.cur_history_id self.cur_history_id += 1 - # Only GET requests are allowed, any other method should fail - if request.method != "GET": - return self.web.error405(history_id) - self.web.add_request( self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, diff --git a/cli/onionshare_cli/web/share_mode.py b/cli/onionshare_cli/web/share_mode.py index 95aec1ba..51ddd674 100644 --- a/cli/onionshare_cli/web/share_mode.py +++ b/cli/onionshare_cli/web/share_mode.py @@ -134,8 +134,8 @@ class ShareModeWeb(SendBaseModeWeb): The web app routes for sharing files """ - @self.web.app.route("/", defaults={"path": ""}) - @self.web.app.route("/") + @self.web.app.route("/", defaults={"path": ""}, methods=["GET"], provide_automatic_options=False) + @self.web.app.route("/", methods=["GET"], provide_automatic_options=False) def index(path): """ Render the template for the onionshare landing page. @@ -160,7 +160,7 @@ class ShareModeWeb(SendBaseModeWeb): return self.render_logic(path) - @self.web.app.route("/download") + @self.web.app.route("/download", methods=["GET"], provide_automatic_options=False) def download(): """ Download the zip file. diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index d88a7e4e..f190d94d 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -229,6 +229,20 @@ class Web: mode.cur_history_id += 1 return self.error404(history_id) + @self.app.errorhandler(405) + def method_not_allowed(e): + mode = self.get_mode() + history_id = mode.cur_history_id + mode.cur_history_id += 1 + return self.error405(history_id) + + @self.app.errorhandler(500) + def method_not_allowed(e): + mode = self.get_mode() + history_id = mode.cur_history_id + mode.cur_history_id += 1 + return self.error500(history_id) + @self.app.route("//shutdown") def shutdown(password_candidate): """ @@ -305,6 +319,19 @@ class Web: ) return self.add_security_headers(r) + def error500(self, history_id): + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 500}, + ) + + self.add_request(Web.REQUEST_OTHER, request.path) + r = make_response( + render_template("500.html", static_url_path=self.static_url_path), 405 + ) + return self.add_security_headers(r) + def add_security_headers(self, r): """ Add security headers to a request diff --git a/cli/onionshare_cli/web/website_mode.py b/cli/onionshare_cli/web/website_mode.py index 6badd399..29b2cc9b 100644 --- a/cli/onionshare_cli/web/website_mode.py +++ b/cli/onionshare_cli/web/website_mode.py @@ -37,8 +37,8 @@ class WebsiteModeWeb(SendBaseModeWeb): The web app routes for sharing a website """ - @self.web.app.route("/", defaults={"path": ""}) - @self.web.app.route("/") + @self.web.app.route("/", defaults={"path": ""}, methods=["GET", "POST"], provide_automatic_options=False) + @self.web.app.route("/", methods=["GET", "POST"], provide_automatic_options=False) def path_public(path): return path_logic(path) diff --git a/desktop/tests/gui_base_test.py b/desktop/tests/gui_base_test.py index c6a5da2f..d630cdf0 100644 --- a/desktop/tests/gui_base_test.py +++ b/desktop/tests/gui_base_test.py @@ -452,6 +452,20 @@ class GuiBaseTest(unittest.TestCase): # We should have timed out now self.assertEqual(tab.get_mode().server_status.status, 0) + def hit_405(self, url, expected_resp, data = {}, methods = [] ): + """Test various HTTP methods and the response""" + for method in methods: + if method == "put": + r = requests.put(url, data = data) + if method == "post": + r = requests.post(url, data = data) + if method == "delete": + r = requests.delete(url) + if method == "options": + r = requests.options(url) + self.assertTrue(expected_resp in r.text) + self.assertFalse('Werkzeug' in r.headers) + # Grouped tests follow from here def run_all_common_setup_tests(self): diff --git a/desktop/tests/test_gui_receive.py b/desktop/tests/test_gui_receive.py index 6e14ae67..40bebc12 100644 --- a/desktop/tests/test_gui_receive.py +++ b/desktop/tests/test_gui_receive.py @@ -286,3 +286,19 @@ class TestReceive(GuiBaseTest): self.run_all_upload_non_writable_dir_tests(tab) self.close_all_tabs() + + def test_405_page_returned_for_invalid_methods(self): + """ + Our custom 405 page should return for invalid methods + """ + tab = self.new_receive_tab() + + tab.get_mode().mode_settings_widget.public_checkbox.click() + + self.run_all_common_setup_tests() + self.run_all_receive_mode_setup_tests(tab) + self.run_all_receive_mode_tests(tab) + url = f"http://127.0.0.1:{tab.app.port}/" + self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "post", "delete", "options"]) + + self.close_all_tabs() diff --git a/desktop/tests/test_gui_share.py b/desktop/tests/test_gui_share.py index 380d63f6..531e456f 100644 --- a/desktop/tests/test_gui_share.py +++ b/desktop/tests/test_gui_share.py @@ -608,3 +608,20 @@ class TestShare(GuiBaseTest): self.hit_401(tab) self.close_all_tabs() + + def test_405_page_returned_for_invalid_methods(self): + """ + Our custom 405 page should return for invalid methods + """ + tab = self.new_share_tab() + + tab.get_mode().autostop_sharing_checkbox.click() + tab.get_mode().mode_settings_widget.public_checkbox.click() + + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests(tab) + self.run_all_share_mode_started_tests(tab) + url = f"http://127.0.0.1:{tab.app.port}/" + self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "post", "delete", "options"]) + self.history_widgets_present(tab) + self.close_all_tabs() diff --git a/desktop/tests/test_gui_website.py b/desktop/tests/test_gui_website.py index a838cb96..6bb6bb7a 100644 --- a/desktop/tests/test_gui_website.py +++ b/desktop/tests/test_gui_website.py @@ -99,3 +99,19 @@ class TestWebsite(GuiBaseTest): tab.get_mode().disable_csp_checkbox.click() self.run_all_website_mode_download_tests(tab) self.close_all_tabs() + + def test_405_page_returned_for_invalid_methods(self): + """ + Our custom 405 page should return for invalid methods + """ + tab = self.new_website_tab() + + tab.get_mode().mode_settings_widget.public_checkbox.click() + + self.run_all_common_setup_tests() + self.run_all_website_mode_setup_tests(tab) + self.run_all_website_mode_started_tests(tab) + url = f"http://127.0.0.1:{tab.app.port}/" + self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "delete", "options"]) + + self.close_all_tabs() From dbdb04a3bf9f384f28a8abee76086920ec7a44a6 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 10 May 2021 11:42:13 +1000 Subject: [PATCH 2/8] Fix receive mode test --- desktop/tests/test_gui_receive.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/desktop/tests/test_gui_receive.py b/desktop/tests/test_gui_receive.py index 40bebc12..b523b0fa 100644 --- a/desktop/tests/test_gui_receive.py +++ b/desktop/tests/test_gui_receive.py @@ -297,8 +297,11 @@ class TestReceive(GuiBaseTest): self.run_all_common_setup_tests() self.run_all_receive_mode_setup_tests(tab) - self.run_all_receive_mode_tests(tab) + self.upload_file(tab, self.tmpfile_test, "test.txt") url = f"http://127.0.0.1:{tab.app.port}/" self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "post", "delete", "options"]) + self.server_is_stopped(tab) + self.web_server_is_stopped(tab) + self.server_status_indicator_says_closed(tab) self.close_all_tabs() From d1cbe2faf6cbb5632aa5420504ac5bc4fccf5ccf Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 10 May 2021 15:57:23 +1000 Subject: [PATCH 3/8] Fix HTTP return code for custom 500 internal server error handler --- cli/onionshare_cli/web/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index f190d94d..96c6295c 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -328,7 +328,7 @@ class Web: self.add_request(Web.REQUEST_OTHER, request.path) r = make_response( - render_template("500.html", static_url_path=self.static_url_path), 405 + render_template("500.html", static_url_path=self.static_url_path), 500 ) return self.add_security_headers(r) From e409141362c157870d46ad8a0b0452d401b74acc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 11 May 2021 08:14:49 +1000 Subject: [PATCH 4/8] Website mode doesn't need to support POST as a method --- cli/onionshare_cli/web/website_mode.py | 4 ++-- desktop/tests/test_gui_website.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/onionshare_cli/web/website_mode.py b/cli/onionshare_cli/web/website_mode.py index 29b2cc9b..5ab1b184 100644 --- a/cli/onionshare_cli/web/website_mode.py +++ b/cli/onionshare_cli/web/website_mode.py @@ -37,8 +37,8 @@ class WebsiteModeWeb(SendBaseModeWeb): The web app routes for sharing a website """ - @self.web.app.route("/", defaults={"path": ""}, methods=["GET", "POST"], provide_automatic_options=False) - @self.web.app.route("/", methods=["GET", "POST"], provide_automatic_options=False) + @self.web.app.route("/", defaults={"path": ""}, methods=["GET"], provide_automatic_options=False) + @self.web.app.route("/", methods=["GET"], provide_automatic_options=False) def path_public(path): return path_logic(path) diff --git a/desktop/tests/test_gui_website.py b/desktop/tests/test_gui_website.py index 6bb6bb7a..f526756a 100644 --- a/desktop/tests/test_gui_website.py +++ b/desktop/tests/test_gui_website.py @@ -112,6 +112,6 @@ class TestWebsite(GuiBaseTest): self.run_all_website_mode_setup_tests(tab) self.run_all_website_mode_started_tests(tab) url = f"http://127.0.0.1:{tab.app.port}/" - self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "delete", "options"]) + self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "post", "delete", "options"]) self.close_all_tabs() From c2bd0a6a22c7dae63f2614b5e3aae7aeb4daf279 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 11 May 2021 08:39:44 +1000 Subject: [PATCH 5/8] Disable OPTIONS on the update-session-username route on Chat mode --- cli/onionshare_cli/web/chat_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index c772818d..385972fe 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -72,7 +72,7 @@ class ChatModeWeb: ) return self.web.add_security_headers(r) - @self.web.app.route("/update-session-username", methods=["POST"]) + @self.web.app.route("/update-session-username", methods=["POST"], provide_automatic_options=False) def update_session_username(): history_id = self.cur_history_id data = request.get_json() From 52d625203cb19650ba2c96afd348cc35d7a4b230 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 11 May 2021 08:41:17 +1000 Subject: [PATCH 6/8] Adds attribute self.mode_supports_file_requests in Web class. Don't send REQUEST_INDIVIDUAL_FILE_STARTED to the frontend if the mode doesn't support this, so that we don't trigger a chain reaction of toggling history widgets and the like. Set this attribute to True by default since most modes use it, but turn it off for Chat mode. Prevents an exception when sending a bad HTTP method or a 404 to a chat room --- cli/onionshare_cli/web/web.py | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index 96c6295c..b947b491 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -152,6 +152,7 @@ class Web: self.receive_mode = None self.website_mode = None self.chat_mode = None + self.mode_supports_file_requests = True if self.mode == "share": self.share_mode = ShareModeWeb(self.common, self) elif self.mode == "receive": @@ -162,6 +163,9 @@ class Web: self.socketio = SocketIO() self.socketio.init_app(self.app) self.chat_mode = ChatModeWeb(self.common, self) + # Chat mode has no concept of individual file requests that + # turn into history widgets in the GUI + self.mode_supports_file_requests = False self.cleanup_filenames = [] @@ -294,11 +298,12 @@ class Web: return self.add_security_headers(r) def error404(self, history_id): - self.add_request( - self.REQUEST_INDIVIDUAL_FILE_STARTED, - request.path, - {"id": history_id, "status_code": 404}, - ) + if self.mode_supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 404}, + ) self.add_request(Web.REQUEST_OTHER, request.path) r = make_response( @@ -307,11 +312,12 @@ class Web: return self.add_security_headers(r) def error405(self, history_id): - self.add_request( - self.REQUEST_INDIVIDUAL_FILE_STARTED, - request.path, - {"id": history_id, "status_code": 405}, - ) + if self.mode_supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 405}, + ) self.add_request(Web.REQUEST_OTHER, request.path) r = make_response( @@ -320,11 +326,12 @@ class Web: return self.add_security_headers(r) def error500(self, history_id): - self.add_request( - self.REQUEST_INDIVIDUAL_FILE_STARTED, - request.path, - {"id": history_id, "status_code": 500}, - ) + if self.mode_supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 500}, + ) self.add_request(Web.REQUEST_OTHER, request.path) r = make_response( From 9aedb0cc003b1d469ac9ca556c89e1e242b73b21 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 11 May 2021 08:41:56 +1000 Subject: [PATCH 7/8] Add the 'Test 405 HTTP response for bad methods' test to the Chat mode GUI tests, which uncovered the exception that the previous commit fixes --- desktop/tests/test_gui_chat.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/desktop/tests/test_gui_chat.py b/desktop/tests/test_gui_chat.py index 7a19168b..50110100 100644 --- a/desktop/tests/test_gui_chat.py +++ b/desktop/tests/test_gui_chat.py @@ -47,7 +47,7 @@ class TestChat(GuiBaseTest): self.assertTrue(jsonResponse["success"]) self.assertEqual(jsonResponse["username"], "oniontest") - def run_all_chat_mode_tests(self, tab): + def run_all_chat_mode_started_tests(self, tab): """Tests in chat mode after starting a chat""" self.server_working_on_start_button_pressed(tab) self.server_status_indicator_says_starting(tab) @@ -58,8 +58,9 @@ class TestChat(GuiBaseTest): self.have_copy_url_button(tab) self.have_show_qr_code_button(tab) self.server_status_indicator_says_started(tab) - self.view_chat(tab) - self.change_username(tab) + + def run_all_chat_mode_stopping_tests(self, tab): + """Tests stopping a chat""" self.server_is_stopped(tab) self.web_server_is_stopped(tab) self.server_status_indicator_says_closed(tab) @@ -71,5 +72,23 @@ class TestChat(GuiBaseTest): Test chat mode """ tab = self.new_chat_tab() - self.run_all_chat_mode_tests(tab) + self.run_all_chat_mode_started_tests(tab) + self.view_chat(tab) + self.change_username(tab) + self.run_all_chat_mode_stopping_tests(tab) + self.close_all_tabs() + + + def test_405_page_returned_for_invalid_methods(self): + """ + Our custom 405 page should return for invalid methods + """ + tab = self.new_chat_tab() + + tab.get_mode().mode_settings_widget.public_checkbox.click() + + self.run_all_chat_mode_started_tests(tab) + url = f"http://127.0.0.1:{tab.app.port}/" + self.hit_405(url, expected_resp="OnionShare: 405 Method Not Allowed", data = {'foo':'bar'}, methods = ["put", "post", "delete", "options"]) + self.run_all_chat_mode_stopping_tests(tab) self.close_all_tabs() From aa7a6e321bc20d1c143e61a0cd6bce2ec1d81334 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 11 May 2021 09:25:22 +1000 Subject: [PATCH 8/8] Move the 'supports_file_requests' attribute into the actual modes rather than the Web class --- cli/onionshare_cli/web/chat_mode.py | 6 ++++++ cli/onionshare_cli/web/receive_mode.py | 4 ++++ cli/onionshare_cli/web/send_base_mode.py | 4 ++++ cli/onionshare_cli/web/web.py | 13 ++++++------- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index 385972fe..91c41d2f 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -39,6 +39,12 @@ class ChatModeWeb: # This tracks the history id self.cur_history_id = 0 + # Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED + # and maybe other events when requests come in to this mode + # Chat mode has no concept of individual file requests that + # turn into history widgets in the GUI, so set it to False + self.supports_file_requests = False + self.define_routes() def define_routes(self): diff --git a/cli/onionshare_cli/web/receive_mode.py b/cli/onionshare_cli/web/receive_mode.py index b3a146e3..76abb0a8 100644 --- a/cli/onionshare_cli/web/receive_mode.py +++ b/cli/onionshare_cli/web/receive_mode.py @@ -64,6 +64,10 @@ class ReceiveModeWeb: # This tracks the history id self.cur_history_id = 0 + # Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED + # and maybe other events when requests come in to this mode + self.supports_file_requests = True + self.define_routes() def define_routes(self): diff --git a/cli/onionshare_cli/web/send_base_mode.py b/cli/onionshare_cli/web/send_base_mode.py index 2f3e0bbd..e448d2dd 100644 --- a/cli/onionshare_cli/web/send_base_mode.py +++ b/cli/onionshare_cli/web/send_base_mode.py @@ -52,6 +52,10 @@ class SendBaseModeWeb: # This tracks the history id self.cur_history_id = 0 + # Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED + # and maybe other events when requests come in to this mode + self.supports_file_requests = True + self.define_routes() self.init() diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index b947b491..56e307b4 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -152,7 +152,6 @@ class Web: self.receive_mode = None self.website_mode = None self.chat_mode = None - self.mode_supports_file_requests = True if self.mode == "share": self.share_mode = ShareModeWeb(self.common, self) elif self.mode == "receive": @@ -163,9 +162,6 @@ class Web: self.socketio = SocketIO() self.socketio.init_app(self.app) self.chat_mode = ChatModeWeb(self.common, self) - # Chat mode has no concept of individual file requests that - # turn into history widgets in the GUI - self.mode_supports_file_requests = False self.cleanup_filenames = [] @@ -298,7 +294,8 @@ class Web: return self.add_security_headers(r) def error404(self, history_id): - if self.mode_supports_file_requests: + mode = self.get_mode() + if mode.supports_file_requests: self.add_request( self.REQUEST_INDIVIDUAL_FILE_STARTED, request.path, @@ -312,7 +309,8 @@ class Web: return self.add_security_headers(r) def error405(self, history_id): - if self.mode_supports_file_requests: + mode = self.get_mode() + if mode.supports_file_requests: self.add_request( self.REQUEST_INDIVIDUAL_FILE_STARTED, request.path, @@ -326,7 +324,8 @@ class Web: return self.add_security_headers(r) def error500(self, history_id): - if self.mode_supports_file_requests: + mode = self.get_mode() + if mode.supports_file_requests: self.add_request( self.REQUEST_INDIVIDUAL_FILE_STARTED, request.path,