Compare commits

...

6 Commits

Author SHA1 Message Date
Raphael Michel
36bbd8b5e4 Remove unused import 2024-06-10 15:46:46 +02:00
Mira Weller
2bfacd925a rename parameter 2024-06-04 14:37:44 +02:00
Mira Weller
795dd64219 simplify placeholder validation 2024-06-04 14:33:57 +02:00
Mira Weller
0882bd9db0 let SafeFormatter optionally raise on missing key 2024-06-04 14:29:27 +02:00
Mira Weller
d3f1f02beb simplify SafeFormatter (skip attribute access code path altogether instead of blocklisting characters) 2024-06-04 14:28:09 +02:00
Mira Weller
7b0f7439f0 Improve validation of email templates 2024-05-29 18:40:18 +02:00
3 changed files with 38 additions and 36 deletions

View File

@@ -32,13 +32,13 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import re
from django.core.exceptions import ValidationError
from django.core.validators import BaseValidator
from django.utils.translation import gettext_lazy as _
from i18nfield.strings import LazyI18nString
from pretix.helpers.format import format_map
class PlaceholderValidator(BaseValidator):
"""
@@ -47,6 +47,12 @@ class PlaceholderValidator(BaseValidator):
which are not presented in taken list.
"""
error_message = _(
'There is an error with your placeholder syntax. Please check that the opening "{" and closing "}" curly '
'brackets on your placeholders match up. '
'Please note: to use literal "{" or "}", you need to double them as "{{" and "}}".'
)
def __init__(self, limit_value):
super().__init__(limit_value)
self.limit_value = limit_value
@@ -57,22 +63,15 @@ class PlaceholderValidator(BaseValidator):
self.__call__(v)
return
if value.count('{') != value.count('}'):
try:
format_map(value, {key.strip('{}'): "" for key in self.limit_value}, raise_on_missing=True)
except ValueError:
raise ValidationError(self.error_message, code='invalid_placeholder_syntax')
except KeyError as e:
raise ValidationError(
_('Invalid placeholder syntax: You used a different number of "{" than of "}".'),
code='invalid_placeholder_syntax',
)
data_placeholders = list(re.findall(r'({[^}]*})', value, re.X))
invalid_placeholders = []
for placeholder in data_placeholders:
if placeholder not in self.limit_value:
invalid_placeholders.append(placeholder)
if invalid_placeholders:
raise ValidationError(
_('Invalid placeholder(s): %(value)s'),
_('Invalid placeholder: {%(value)s}'),
code='invalid_placeholders',
params={'value': ", ".join(invalid_placeholders,)})
params={'value': e.args[0]})
def clean(self, x):
return x

View File

@@ -73,6 +73,7 @@ from i18nfield.utils import I18nJSONEncoder
from pretix.base.channels import get_all_sales_channels
from pretix.base.email import get_available_placeholders
from pretix.base.forms import PlaceholderValidator
from pretix.base.models import Event, LogEntry, Order, TaxRule, Voucher
from pretix.base.models.event import EventMetaValue
from pretix.base.services import tickets
@@ -713,11 +714,6 @@ class MailSettingsSetup(EventPermissionRequiredMixin, MailSettingsSetupView):
class MailSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
# return the origin text if key is missing in dict
class SafeDict(dict):
def __missing__(self, key):
return '{' + key + '}'
# create index-language mapping
@cached_property
def supported_locale(self):
@@ -742,7 +738,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
_('This value will be replaced based on dynamic parameters.'),
s
)
return self.SafeDict(ctx)
return ctx
def post(self, request, *args, **kwargs):
preview_item = request.POST.get('item', '')
@@ -758,12 +754,21 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
idx = matched.group('idx')
if idx in self.supported_locale:
with language(self.supported_locale[idx], self.request.event.settings.region):
if k.startswith('mail_subject_'):
msgs[self.supported_locale[idx]] = format_map(bleach.clean(v), self.placeholders(preview_item))
else:
msgs[self.supported_locale[idx]] = markdown_compile_email(
format_map(v, self.placeholders(preview_item))
)
try:
if k.startswith('mail_subject_'):
msgs[self.supported_locale[idx]] = format_map(
bleach.clean(v), self.placeholders(preview_item), raise_on_missing=True
)
else:
msgs[self.supported_locale[idx]] = markdown_compile_email(
format_map(v, self.placeholders(preview_item), raise_on_missing=True)
)
except ValueError:
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
PlaceholderValidator.error_message)
except KeyError as e:
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]})
return JsonResponse({
'item': preview_item,

View File

@@ -30,17 +30,15 @@ class SafeFormatter(Formatter):
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
(b) does not allow any unwanted shenanigans like attribute access or format specifiers.
"""
def __init__(self, context):
def __init__(self, context, raise_on_missing=False):
self.context = context
self.raise_on_missing = raise_on_missing
def get_field(self, field_name, args, kwargs):
if '.' in field_name or '[' in field_name:
logger.warning(f'Ignored invalid field name "{field_name}"')
return ('{' + str(field_name) + '}', field_name)
return super().get_field(field_name, args, kwargs)
return self.get_value(field_name, args, kwargs), field_name
def get_value(self, key, args, kwargs):
if key not in self.context:
if not self.raise_on_missing and key not in self.context:
return '{' + str(key) + '}'
return self.context[key]
@@ -49,7 +47,7 @@ class SafeFormatter(Formatter):
return super().format_field(value, '')
def format_map(template, context):
def format_map(template, context, raise_on_missing=False):
if not isinstance(template, str):
template = str(template)
return SafeFormatter(context).format(template)
return SafeFormatter(context, raise_on_missing).format(template)