diff --git a/src/ansys/dynamicreporting/core/common_utils.py b/src/ansys/dynamicreporting/core/common_utils.py index f588b5b11..3dc8f1197 100644 --- a/src/ansys/dynamicreporting/core/common_utils.py +++ b/src/ansys/dynamicreporting/core/common_utils.py @@ -3,6 +3,8 @@ import platform import re +import bleach + from . import DEFAULT_ANSYS_VERSION as CURRENT_VERSION from .constants import JSON_NECESSARY_KEYS, JSON_TEMPLATE_KEYS, REPORT_TYPES from .exceptions import InvalidAnsysPath @@ -189,3 +191,68 @@ def populate_template(id_str, attr, parent_template, create_template_func, logge template.set_filter(filter_str=attr["item_filter"] if "item_filter" in attr else "") return template + + +PROPERTIES_EXEMPT = { + "link_text", + "userdef_name", + "filter_x_title", + "filter_y_title", + "labels_column", + "labels_row", + "title", + "line_marker_text", + "plot_title", + "xtitle", + "ytitle", + "ztitle", + "nan_display", + "table_title", + "image_title", + "slider_title", + "TOCName", +} + + +def validate_html_dictionary(data): + for key, value in data.items(): + # Do not validate HTML key + if key == "HTML": + continue + + # Recursive case for nested dictionaries + if isinstance(value, dict): + # Specific checks for properties key + if key == "properties": + subdict = {k: v for k, v in value.items() if k not in PROPERTIES_EXEMPT} + validate_html_dictionary(subdict) + else: + validate_html_dictionary(value) + + # Check for lists + elif isinstance(value, list): + validate_html_list(value, key) + + # Main check for strings + elif isinstance(value, str): + cleaned_string = bleach.clean(value, strip=True) + if cleaned_string != value: + raise ValueError(f"{key} contains HTML content.") + + # Ignore other types + else: + continue + + +def validate_html_list(value_list, key): + for item in value_list: + if isinstance(item, str): + cleaned_string = bleach.clean(item, strip=True) + if cleaned_string != item: + raise ValueError(f"{key} contains HTML content.") + elif isinstance(item, dict): + validate_html_dictionary(item) + elif isinstance(item, list): + validate_html_list(item, key) + else: + continue diff --git a/src/ansys/dynamicreporting/core/serverless/template.py b/src/ansys/dynamicreporting/core/serverless/template.py index c8cb1bb98..53fffb2fe 100644 --- a/src/ansys/dynamicreporting/core/serverless/template.py +++ b/src/ansys/dynamicreporting/core/serverless/template.py @@ -8,6 +8,7 @@ from django.template.loader import render_to_string from django.utils import timezone +from ..common_utils import validate_html_dictionary from ..constants import JSON_ATTR_KEYS from ..exceptions import ADRException, TemplateDoesNotExist, TemplateReorderOutOfBounds from .base import BaseModel, StrEnum @@ -251,6 +252,8 @@ def set_params(self, new_params: dict) -> None: new_params = {} if not isinstance(new_params, dict): raise TypeError("input must be a dictionary") + if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1": + validate_html_dictionary(new_params) self.params = json.dumps(new_params) def add_params(self, new_params: dict): diff --git a/src/ansys/dynamicreporting/core/utils/report_objects.py b/src/ansys/dynamicreporting/core/utils/report_objects.py index 67ad6543b..892287bea 100755 --- a/src/ansys/dynamicreporting/core/utils/report_objects.py +++ b/src/ansys/dynamicreporting/core/utils/report_objects.py @@ -22,6 +22,7 @@ import pytz from . import extremely_ugly_hacks, report_utils +from ..common_utils import validate_html_dictionary from ..exceptions import TemplateDoesNotExist, TemplateReorderOutOfBounds from .encoders import PayloaddataEncoder @@ -413,6 +414,8 @@ def set_params(self, d: dict = None): d = {} if type(d) is not dict: raise ValueError("Error: input must be a dictionary") + if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1": + validate_html_dictionary(d) self.params = json.dumps(d) return @@ -1541,6 +1544,8 @@ def set_params(self, d: dict = None): d = {} if type(d) is not dict: raise ValueError("Error: input must be a dictionary") + if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1": + validate_html_dictionary(d) self.params = json.dumps(d) return diff --git a/tests/test_report_objects.py b/tests/test_report_objects.py index 78564ad9c..be7e1f4aa 100755 --- a/tests/test_report_objects.py +++ b/tests/test_report_objects.py @@ -1,5 +1,6 @@ import datetime import json +import os import uuid import pytest @@ -1964,6 +1965,62 @@ def test_unit_template() -> None: assert succ and succ_two and succ_three and succ_four +@pytest.mark.ado_test +def test_template_validation() -> None: + os.environ["ADR_VALIDATION_BETAFLAG_ANSYS"] = "1" + a = ro.Template() + try: + a.set_params( + { + "reduce_params": { + "reduce_type": "row", + "operations": [ + "test 1", + ["test 2", 1], + {"source_rows": "'Phase*'", "output_rows": "Maximum"}, + ], + }, + "properties": {"plot": "line", "plot_title": "Reduced Table"}, + "HTML": "