notification: Don't fail when formatting message strings

- When a notification's message contains unexpected formatting characters such
as '{}', showing the notification and consequently the entire FreedomBox web
interface fails. Prevent that by make sure that that message formatting never
fails.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-09-09 13:13:46 -07:00 committed by James Valleroy
parent d8da0a41e5
commit d69167bcfa
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 32 additions and 10 deletions

View File

@ -1,10 +1,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """Module to provide API for showing notifications."""
Module to provide API for showing notifications.
"""
import copy import copy
import logging import logging
import string
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
@ -278,22 +277,22 @@ class Notification(models.StoredNotification):
return Notification.objects.filter(*filters)[0:10] return Notification.objects.filter(*filters)[0:10]
@staticmethod @staticmethod
def _translate(string, data=None): def _translate(string_, data=None):
"""Translate a string for final display using data dict.""" """Translate a string for final display using data dict."""
if not string: if not string_:
return None return None
string = gettext(string) string_ = gettext(string_)
try: try:
string = str(string) string_ = str(string_)
if data: if data:
string = string.format(**data) string_ = SafeFormatter().vformat(string_, [], data)
except KeyError as error: except KeyError as error:
logger.warning( logger.warning(
'Notification missing required key during translation: %s', 'Notification missing required key during translation: %s',
error) error)
return string return string_
@staticmethod @staticmethod
def _translate_dict(data_dict, data=None): def _translate_dict(data_dict, data=None):
@ -366,3 +365,14 @@ class Notification(models.StoredNotification):
notes.append(note_context) notes.append(note_context)
return {'notifications': notes, 'max_severity': max_severity} return {'notifications': notes, 'max_severity': max_severity}
class SafeFormatter(string.Formatter):
"""A string.format() handler to deal with missing arguments."""
def get_value(self, key, args, kwargs):
"""Retrieve a given field value."""
try:
return super().get_value(key, args, kwargs)
except (IndexError, KeyError):
return f'?{key}?'

View File

@ -10,7 +10,7 @@ import pytest
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from plinth.notification import Notification from plinth.notification import Notification, SafeFormatter
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -413,3 +413,15 @@ def test_display_context_body_template(note, user, load_cfg, rf):
context_note = context['notifications'][0] context_note = context['notifications'][0]
assert context_note['body'].content == \ assert context_note['body'].content == \
b'Test notification body /plinth/help/about/\n' b'Test notification body /plinth/help/about/\n'
@pytest.mark.parametrize('input_, output', (
(('', [], {}), ''),
(('{} {}', [10, 20], {}), '10 20'),
(('{1} {0} {key1}', [10, 20], dict(key1='value1')), '20 10 value1'),
(('{2} {1} {key1}', [10, 20], {}), '?2? 20 ?key1?'),
))
def test_safe_string_formatter(input_, output):
"""Test the safe string formatter."""
formatter = SafeFormatter()
assert output == formatter.vformat(*input_)