forked from CGM_Public/pretix_original
* Fix EPC QR beneficiary escaping * Fix EPC QR script encoding Keep EPC QR helper output as a plain string and serialize payment QR payloads as JSON script data before the QR replacement JavaScript parses them. This preserves apostrophes without relying on mark_safe in the helper. Assisted-by: OpenAI GPT-5 <noreply@openai.com> * "type safety" --------- Co-authored-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
co-authored by
Puneet Dixit
Raphael Michel
parent
3b285a89dd
commit
3270c4e583
@@ -25,6 +25,19 @@ import text_unidecode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
EPC_QR_ALLOWED_CHARS = set(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789/-?:().,'+ "
|
||||
)
|
||||
|
||||
|
||||
def epc_qr_field(value):
|
||||
return ''.join(
|
||||
char for char in text_unidecode.unidecode(str(value or ''))
|
||||
if char in EPC_QR_ALLOWED_CHARS
|
||||
)
|
||||
|
||||
|
||||
def dotdecimal(value):
|
||||
return str(value).replace(",", ".")
|
||||
@@ -77,7 +90,7 @@ def euro_epc_qr(
|
||||
return {
|
||||
"id": "girocode",
|
||||
"label": "EPC-QR",
|
||||
"qr_data": "\n".join(text_unidecode.unidecode(str(d or '')) for d in [
|
||||
"qr_data": "\n".join(epc_qr_field(d) for d in [
|
||||
"BCD", # Service Tag: ‘BCD’
|
||||
"002", # Version: V2
|
||||
"2", # Character set: ISO 8859-1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load escapejson %}
|
||||
|
||||
{% if payment_qr_codes %}
|
||||
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
|
||||
@@ -14,7 +15,7 @@
|
||||
{% if code_info.link %}<a aria-label="{{ code_info.link_aria_label }}" href="{{ code_info.link }}">{% endif %}
|
||||
<div class="{{ code_info.css_class }}" role="figure" aria-labelledby="banktransfer_qrcodes_{{ code_info.id }}_tab banktransfer_qrcodes_label">
|
||||
{{ code_info.html_prefix }}
|
||||
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking app’s QR-Reader to start the payment process.' %}">{{ code_info.qr_data }}</script>
|
||||
<script type="application/json" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking app’s QR-Reader to start the payment process.' %}">{{ code_info.qr_data|escapejson_dumps }}</script>
|
||||
</div>
|
||||
{% if code_info.link %}</a>{% endif %}
|
||||
</div>
|
||||
@@ -41,4 +42,4 @@
|
||||
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -666,10 +666,12 @@ var form_handlers = function (el) {
|
||||
|
||||
el.find("script[data-replace-with-qr]").each(function () {
|
||||
var $div = $("<div>");
|
||||
var qrText = this.getAttribute("type") === "application/json" && this.textContent.startsWith('"') ?
|
||||
JSON.parse(this.textContent) : $(this).html();
|
||||
$div.insertBefore($(this));
|
||||
$div.qrcode(
|
||||
{
|
||||
text: $(this).html(),
|
||||
text: qrText,
|
||||
correctLevel: 0, // M
|
||||
width: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256,
|
||||
height: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256,
|
||||
|
||||
@@ -131,10 +131,12 @@ var form_handlers = function (el) {
|
||||
|
||||
el.find("script[data-replace-with-qr]").each(function () {
|
||||
var $div = $("<div>");
|
||||
var qrText = this.getAttribute("type") === "application/json" && this.textContent.startsWith('"') ?
|
||||
JSON.parse(this.textContent) : $(this).html();
|
||||
$div.insertBefore($(this));
|
||||
$div.qrcode(
|
||||
{
|
||||
text: $(this).html(),
|
||||
text: qrText,
|
||||
correctLevel: 0, // M
|
||||
width: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256,
|
||||
height: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256,
|
||||
@@ -345,7 +347,7 @@ function setup_basics(el) {
|
||||
}).on('click', function (event) {
|
||||
setCurrentTab(this);
|
||||
});
|
||||
|
||||
|
||||
var firstTab = tabs.first().get(0);
|
||||
var lastTab = tabs.last().get(0);
|
||||
setCurrentTab(tabs.filter('[aria-selected=true]').get(0));
|
||||
@@ -658,7 +660,7 @@ $(function () {
|
||||
var currentTimeDisplayParts = [];
|
||||
timeFormatParts.forEach(function(format) {
|
||||
currentTimeDisplayParts.push([format, $("<span></span>").appendTo(currentTimeDisplay)])
|
||||
});
|
||||
});
|
||||
var duration = this.getAttribute("data-duration").split(":").reduce(function(previousValue, currentValue, currentIndex) {
|
||||
return previousValue + (currentIndex ? parseInt(currentValue, 10) * 60 : parseInt(currentValue, 10) * 60 * 60);
|
||||
}, 0);
|
||||
@@ -671,7 +673,7 @@ $(function () {
|
||||
currentTimeBar.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var offset = thisCalendar.querySelector("h3").getBoundingClientRect().width;
|
||||
var dx = Math.round(offset + (thisCalendar.scrollWidth-offset)*(currentTimeDelta/duration));
|
||||
currentTimeDisplayParts.forEach(function(part) {
|
||||
|
||||
@@ -23,6 +23,8 @@ from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import SafeData
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
@@ -77,6 +79,31 @@ TESTVERANST-12345
|
||||
'&bic=BYLADEM1MIL&amount=123%2C00&reason=TESTVERANST-12345¤cy=EUR')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_qr_codes_euro_keeps_allowed_apostrophe_unescaped(env):
|
||||
o, event = env
|
||||
codes = generate_payment_qr_codes(
|
||||
event=event,
|
||||
code='TESTVERANST-12345',
|
||||
amount=Decimal('123.00'),
|
||||
bank_details_sepa_bic='BYLADEM1MIL',
|
||||
bank_details_sepa_iban='DE37796500000069799047',
|
||||
bank_details_sepa_name='Bits\'n"Bugs',
|
||||
)
|
||||
|
||||
qr_data = codes[0]['qr_data']
|
||||
assert '\nBits\'nBugs\n' in qr_data
|
||||
assert not isinstance(qr_data, SafeData)
|
||||
|
||||
html = render_to_string(
|
||||
'pretixpresale/event/payment_qr_codes.html',
|
||||
{'payment_qr_codes': codes},
|
||||
)
|
||||
assert 'type="application/json"' in html
|
||||
assert "\\nBits'nBugs\\n" in html
|
||||
assert ''' not in html
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_qr_codes_swiss(env):
|
||||
o, event = env
|
||||
|
||||
Reference in New Issue
Block a user