@@ -380,11 +380,11 @@ def _compute_target_type_message(self):
380380 @api .depends ("name" , "request_type_id" , "registrant_id" )
381381 def _compute_stage_banner_html (self ):
382382 for rec in self :
383- cr_ref = rec .name or ""
384- cr_type = rec .request_type_id .name if rec .request_type_id else ""
383+ cr_ref = html_escape ( rec .name or "" )
384+ cr_type = html_escape ( rec .request_type_id .name ) if rec .request_type_id else ""
385385 html = f'<span class="fw-bold">{ cr_ref } </span><span class="text-muted mx-2">|</span><span>{ cr_type } </span>'
386386 if rec .registrant_id :
387- registrant = rec .registrant_id .name or ""
387+ registrant = html_escape ( rec .registrant_id .name or "" )
388388 html += (
389389 f'<span class="text-muted mx-2">|</span>'
390390 f'<i class="fa fa-user me-1 text-muted"></i>'
@@ -422,14 +422,11 @@ def _compute_required_documents_html(self):
422422 uploaded_types = rec .document_ids .mapped ("document_type_id" ).filtered (lambda c : c )
423423 items = []
424424 for doc_type in required :
425+ escaped_name = html_escape (doc_type .display_name )
425426 if doc_type in uploaded_types :
426- items .append (
427- f'<li class="text-success"><i class="fa fa-check-circle me-1"></i>{ doc_type .display_name } </li>'
428- )
427+ items .append (f'<li class="text-success"><i class="fa fa-check-circle me-1"></i>{ escaped_name } </li>' )
429428 else :
430- items .append (
431- f'<li class="text-danger"><i class="fa fa-times-circle me-1"></i>{ doc_type .display_name } </li>'
432- )
429+ items .append (f'<li class="text-danger"><i class="fa fa-times-circle me-1"></i>{ escaped_name } </li>' )
433430
434431 rec .required_documents_html = (
435432 '<div class="mb-3">'
@@ -508,8 +505,8 @@ def _compute_review_documents_html(self):
508505 html .append ("<tbody>" )
509506
510507 for doc in rec .document_ids :
511- doc_name = doc .name or ""
512- doc_type = doc .document_type_id .display_name if doc .document_type_id else ""
508+ doc_name = html_escape ( doc .name or "" )
509+ doc_type = html_escape ( doc .document_type_id .display_name ) if doc .document_type_id else ""
513510 uploaded = doc .create_date .strftime ("%Y-%m-%d" ) if doc .create_date else ""
514511 html .append (
515512 f"<tr>"
@@ -541,19 +538,20 @@ def _compute_registrant_summary_html(self):
541538 html_parts .append ('<i class="fa fa-users fa-lg text-primary me-2"></i>' )
542539 else :
543540 html_parts .append ('<i class="fa fa-user fa-lg text-primary me-2"></i>' )
544- html_parts .append (f"<strong>{ reg .name } </strong>" )
541+ html_parts .append (f"<strong>{ html_escape ( reg .name or '' ) } </strong>" )
545542 html_parts .append ("</div>" )
546543
547544 # ID badge
548545 if hasattr (reg , "spp_id" ) and reg .spp_id :
549- html_parts .append (f'<div class="mb-2"><span class="badge bg-secondary">ID: { reg .spp_id } </span></div>' )
546+ escaped_id = html_escape (reg .spp_id )
547+ html_parts .append (f'<div class="mb-2"><span class="badge bg-secondary">ID: { escaped_id } </span></div>' )
550548
551549 # Address
552550 address_parts = []
553551 if reg .street :
554- address_parts .append (reg .street )
552+ address_parts .append (html_escape ( reg .street ) )
555553 if reg .city :
556- address_parts .append (reg .city )
554+ address_parts .append (html_escape ( reg .city ) )
557555 if address_parts :
558556 html_parts .append (
559557 f'<div class="text-muted small mb-2">'
@@ -1073,7 +1071,7 @@ def _generate_preview_html(self):
10731071 action_label = action_labels .get (action , action .replace ("_" , " " ).title ())
10741072 html_parts .append (
10751073 f'<div class="mb-3 d-flex align-items-center">'
1076- f'<span class="badge bg-primary me-2">{ action_label } </span>'
1074+ f'<span class="badge bg-primary me-2">{ html_escape ( action_label ) } </span>'
10771075 f"</div>"
10781076 )
10791077
@@ -1085,7 +1083,7 @@ def _generate_preview_html(self):
10851083 for key , value in changes .items ():
10861084 if key .startswith ("_" ):
10871085 continue
1088- display_key = key .replace ("_" , " " ).title ()
1086+ display_key = html_escape ( key .replace ("_" , " " ).title () )
10891087
10901088 # Handle dict with old/new structure
10911089 if isinstance (value , dict ) and "new" in value :
@@ -1095,16 +1093,16 @@ def _generate_preview_html(self):
10951093 if old_val is None or old_val is False or old_val == "" :
10961094 old_display = '<span class="text-muted">—</span>'
10971095 else :
1098- old_display = str (old_val )
1096+ old_display = html_escape ( str (old_val ) )
10991097 # Format new value
11001098 if new_val is None or new_val is False or new_val == "" :
11011099 new_display = '<span class="text-muted">—</span>'
11021100 else :
1103- new_display = f"<strong>{ new_val } </strong>"
1101+ new_display = f"<strong>{ html_escape ( str ( new_val )) } </strong>"
11041102 display_value = f"{ old_display } → { new_display } "
11051103 elif isinstance (value , list ):
11061104 if value :
1107- display_value = "<br/>" .join (str (v ) for v in value )
1105+ display_value = "<br/>" .join (html_escape ( str (v ) ) for v in value )
11081106 else :
11091107 display_value = '<span class="text-muted">Not set</span>'
11101108 elif value is None or value is False or value == "" :
@@ -1114,7 +1112,7 @@ def _generate_preview_html(self):
11141112 # Only True reaches here (False caught above)
11151113 display_value = '<span class="badge text-bg-success">Yes</span>'
11161114 else :
1117- display_value = str (value )
1115+ display_value = html_escape ( str (value ) )
11181116
11191117 html_parts .append (f"<tr><td><strong>{ display_key } </strong></td><td>{ display_value } </td></tr>" )
11201118
@@ -1167,7 +1165,7 @@ def _render_comparison_table(self, changes, header=None):
11671165 """Render a three-column comparison table for field-mapping CR types."""
11681166 html = []
11691167 if header :
1170- html .append (f"<h4>{ header } </h4>" )
1168+ html .append (f"<h4>{ html_escape ( header ) } </h4>" )
11711169 html .append ('<table class="table table-sm table-bordered mb-0" style="width:100%">' )
11721170 html .append (
11731171 "<thead><tr>"
@@ -1182,7 +1180,7 @@ def _render_comparison_table(self, changes, header=None):
11821180 if key .startswith ("_" ):
11831181 continue
11841182 # Use key as-is if it contains spaces (human-readable), otherwise convert
1185- display_key = key if " " in key else key .replace ("_" , " " ).title ()
1183+ display_key = html_escape ( key if " " in key else key .replace ("_" , " " ).title () )
11861184
11871185 if isinstance (value , dict ) and "old" in value :
11881186 old_val = value .get ("old" )
@@ -1220,7 +1218,7 @@ def _render_action_summary(self, action, changes, header=None):
12201218 html = []
12211219
12221220 if header :
1223- html .append (f"<h4>{ header } </h4>" )
1221+ html .append (f"<h4>{ html_escape ( header ) } </h4>" )
12241222
12251223 if not changes :
12261224 html .append ('<p class="text-muted mb-0"><i class="fa fa-info-circle me-2"></i>No details to display.</p>' )
@@ -1233,7 +1231,7 @@ def _render_action_summary(self, action, changes, header=None):
12331231 for key , value in changes .items ():
12341232 if key .startswith ("_" ):
12351233 continue
1236- display_key = key if " " in key else key .replace ("_" , " " ).title ()
1234+ display_key = html_escape ( key if " " in key else key .replace ("_" , " " ).title () )
12371235 display_value = self ._format_review_value (value )
12381236 html .append (f'<tr><td class="bg-light"><strong>{ display_key } </strong></td><td>{ display_value } </td></tr>' )
12391237
0 commit comments