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
"""
Module to provide API for showing notifications.
"""
"""Module to provide API for showing notifications."""
import copy
import logging
import string
from django.core.exceptions import ValidationError
from django.db.models import Q
@ -278,22 +277,22 @@ class Notification(models.StoredNotification):
return Notification.objects.filter(*filters)[0:10]
@staticmethod
def _translate(string, data=None):
def _translate(string_, data=None):
"""Translate a string for final display using data dict."""
if not string:
if not string_:
return None
string = gettext(string)
string_ = gettext(string_)
try:
string = str(string)
string_ = str(string_)
if data:
string = string.format(**data)
string_ = SafeFormatter().vformat(string_, [], data)
except KeyError as error:
logger.warning(
'Notification missing required key during translation: %s',
error)
return string
return string_
@staticmethod
def _translate_dict(data_dict, data=None):
@ -366,3 +365,14 @@ class Notification(models.StoredNotification):
notes.append(note_context)
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.core.exceptions import ValidationError
from plinth.notification import Notification
from plinth.notification import Notification, SafeFormatter
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]
assert context_note['body'].content == \
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_)