From fbed7e93e859f45289a3be3e661afb418c5ad16c Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 18 Oct 2024 15:41:21 -0700 Subject: [PATCH] operation: Use safe formatter for translating messages - When an app install fails, there is a small chance that the failure message is show in the area where operation spinner is shown. If that happens, operation.translated_message is accessed from the HTML template. This throws an exception if the error message that made contains excepted formatting keys. Example: "{include_once("/var/www/html/config/config.php");print($CONFIG["dbpassword"] ?? ""); }". - Also change the formatting key {exception_message} to {exception} as this would help in translation when Notification is shown which has {exception} as data dictionary value. Tests: - In the operation update message such as 'Installing app', insert unexpected formatting strings. 'Installing app {foo}'. Notice the error without the patch and how the patch fixes it. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/operation.py | 16 +++++++++++----- plinth/tests/test_operation.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plinth/operation.py b/plinth/operation.py index 6ab6952f9..8ae517592 100644 --- a/plinth/operation.py +++ b/plinth/operation.py @@ -7,6 +7,8 @@ import threading from collections import OrderedDict from typing import Callable +from plinth.utils import SafeFormatter + from . import app as app_module logger = logging.getLogger(__name__) @@ -115,7 +117,7 @@ class Operation: return self._message if self.exception: # Operation resulted in a error. - return gettext_noop('Error: {name}: {exception_message}') + return gettext_noop('Error: {name}: {exception}') if self.state == Operation.State.WAITING: return gettext_noop('Waiting to start: {name}') @@ -137,11 +139,15 @@ class Operation: """ from django.utils.translation import gettext message = gettext(self.message) - message = message.format(name=gettext(self.name), - exception_message=str(self.exception)) + data = {'name': gettext(self.name), 'exception': str(self.exception)} if self.app_id: - message = message.format( - app_name=app_module.App.get(self.app_id).info.name) + data['app_name'] = app_module.App.get(self.app_id).info.name + + try: + message = SafeFormatter().vformat(message, [], data) + except (KeyError, AttributeError) as error: + logger.warning( + 'Operation missing required key during translation: %s', error) return message diff --git a/plinth/tests/test_operation.py b/plinth/tests/test_operation.py index 429d62170..f30a97e9b 100644 --- a/plinth/tests/test_operation.py +++ b/plinth/tests/test_operation.py @@ -186,7 +186,7 @@ def test_message(app_get): assert operation.translated_message == 'message1' operation._message = None - assert operation.message == 'Error: {name}: {exception_message}' + assert operation.message == 'Error: {name}: {exception}' assert operation.translated_message == 'Error: op1: error1' operation.exception = None