diff --git a/client/app.py b/client/app.py index 48ceb01..3b79869 100644 --- a/client/app.py +++ b/client/app.py @@ -1,9 +1,11 @@ """TieMeasureFlow Client - Flask Entry Point.""" +import json import os from flask import Flask, redirect, url_for, session, request from flask_babel import Babel from flask_wtf.csrf import CSRFProtect +from markupsafe import Markup from config import Config @@ -55,6 +57,24 @@ def create_app() -> Flask: session["language"] = lang return redirect(request.referrer or url_for("auth.login")) + @app.template_filter("tojson_attr") + def tojson_attr_filter(value): + """JSON encode safe for HTML attributes (x-data, etc.). + + Unlike |tojson, this escapes double quotes to " so the output + can be safely embedded inside double-quoted HTML attributes. + The browser decodes the entities before Alpine.js evaluates them. + """ + rv = json.dumps(value, ensure_ascii=False) + rv = ( + rv.replace("&", "\\u0026") + .replace("<", "\\u003c") + .replace(">", "\\u003e") + .replace("'", "\\u0027") + .replace('"', """) + ) + return Markup(rv) + @app.context_processor def inject_globals(): """Inject global variables into all templates.""" diff --git a/client/templates/base.html b/client/templates/base.html index e6f21a6..fcf45fd 100644 --- a/client/templates/base.html +++ b/client/templates/base.html @@ -90,7 +90,7 @@ {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %}
{% for category, message in messages %}