We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Special Link: about:accounts
AS Router Action:
 - `SHOW_FIREFOX_ACCOUNTS`: opens the firefox accounts signup page (about:accounts?action=signup&entrypoint=snippets)

Currently blocked waiting
 - documentation on AS Router. See also #521 
 - code changes in  (bug [1478569](https://bugzilla.mozilla.org/show_bug.cgi?id=1478569))

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `snippets/base/util.py`
Content:
```
1 import copy
2 import datetime
3 import re
4 
5 from product_details import product_details
6 from product_details.version_compare import version_list
7 
8 EPOCH = datetime.datetime.utcfromtimestamp(0)
9 
10 
11 def get_object_or_none(model_class, **filters):
12     """
13     Identical to Model.get, except instead of throwing exceptions, this returns
14     None.
15     """
16     try:
17         return model_class.objects.get(**filters)
18     except (model_class.DoesNotExist, model_class.MultipleObjectsReturned):
19         return None
20 
21 
22 def first(collection, callback):
23     """
24     Find the first item in collection that, when passed to callback, returns
25     True. Returns None if no such item is found.
26     """
27     return next((item for item in collection if callback(item)), None)
28 
29 
30 def create_locales():
31     from snippets.base.models import TargetedLocale
32 
33     for code, name in product_details.languages.items():
34         locale = TargetedLocale.objects.get_or_create(code=code.lower())[0]
35         name = name['English']
36         if locale.name != name:
37             locale.name = name
38             locale.save()
39 
40 
41 def create_countries():
42     from snippets.base.models import TargetedCountry
43 
44     for code, name in product_details.get_regions('en-US').items():
45         country = TargetedCountry.objects.get_or_create(code=code)[0]
46         if country.name != name:
47             country.name = name
48             country.save()
49 
50 
51 def current_firefox_major_version():
52     full_version = version_list(
53         product_details.firefox_history_major_releases)[0]
54 
55     return full_version.split('.', 1)[0]
56 
57 
58 def fluent_link_extractor(data, variables):
59     """Replaces all <a> elements with fluent.js link elements sequentially
60     numbered.
61 
62     Returns a tuple with the new text and a dict of all the links with url and
63     custom metric where available.
64 
65     """
66     class Replacer:
67         link_counter = 0
68         links = {}
69 
70         def __call__(self, matchobj):
71             keyname = 'link{0}'.format(self.link_counter)
72             replacement = '<{keyname}>{text}</{keyname}>'.format(
73                 keyname=keyname,
74                 text=matchobj.group('innerText'))
75             # Find the URL
76             url_match = re.search('href="(?P<url>.+?)"', matchobj.group('attrs'))
77             url = ''
78 
79             if url_match:
80                 url = url_match.group('url')
81             self.links[keyname] = {
82                 'url': url,
83             }
84 
85             # Find the optional data-metric attrib
86             metric_match = re.search('data-metric="(?P<metric>.+?)"', matchobj.group('attrs'))
87             if metric_match:
88                 self.links[keyname]['metric'] = metric_match.group('metric')
89 
90             self.link_counter += 1
91             return replacement
92 
93     local_data = copy.deepcopy(data)
94     replacer = Replacer()
95     for variable in variables:
96         local_data[variable] = re.sub('(<a(?P<attrs> .*?)>)(?P<innerText>.+?)(</a>)',
97                                       replacer, local_data[variable])
98 
99     local_data['links'] = replacer.links
100     return local_data
101 
102 
103 def to_unix_time_seconds(dt):
104     return int((dt - EPOCH).total_seconds())
105
```

Path: `snippets/base/validators.py`
Content:
```
1 import re
2 import json
3 from io import StringIO
4 
5 import xml.sax
6 from xml.sax import ContentHandler
7 
8 from django.core.exceptions import ValidationError
9 from django.core.validators import BaseValidator
10 from django.utils.deconstruct import deconstructible
11 
12 import bleach
13 
14 ALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']
15 ALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}
16 
17 
18 @deconstructible
19 class MinValueValidator(BaseValidator):
20     message = 'Ensure this value is greater than or equal to %(limit_value)s.'
21     code = 'min_value'
22 
23     def compare(self, a, b):
24         return int(a) < int(b)
25 
26 
27 def validate_xml_template(data):
28     parser = xml.sax.make_parser()
29     parser.setContentHandler(ContentHandler())
30     parser.setFeature(xml.sax.handler.feature_external_ges, 0)
31 
32     xml_str = '<div>\n{0}</div>'.format(data)
33     try:
34         parser.parse(StringIO(xml_str))
35     except xml.sax.SAXParseException as e:
36         # getLineNumber() - 1 to get the correct line number because
37         # we're wrapping contents into a div.
38         error_msg = (
39             'XML Error: {message} in line {line} column {column}').format(
40                 message=e.getMessage(), line=e.getLineNumber() - 1, column=e.getColumnNumber())
41         raise ValidationError(error_msg)
42     return data
43 
44 
45 def validate_xml_variables(data):
46     data_dict = json.loads(data)
47 
48     # set up a safer XML parser that does not resolve external
49     # entities
50     parser = xml.sax.make_parser()
51     parser.setContentHandler(ContentHandler())
52     parser.setFeature(xml.sax.handler.feature_external_ges, 0)
53 
54     for name, value in data_dict.items():
55         # Skip over values that aren't strings.
56         if not isinstance(value, str):
57             continue
58 
59         xml_str = '<div>{0}</div>'.format(value)
60         try:
61             parser.parse(StringIO(xml_str))
62         except xml.sax.SAXParseException as e:
63             error_msg = (
64                 'Data is not XML valid.\n'
65                 'XML Error in value "{name}": {message} in column {column}'
66                 .format(name=name, message=e.getMessage(),
67                         column=e.getColumnNumber()))
68             raise ValidationError(error_msg)
69     return data
70 
71 
72 def validate_as_router_fluent_variables(data, variables):
73     data_dict = json.loads(data)
74 
75     for variable in variables:
76         text = data_dict[variable]
77         bleached_text = bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
78         # Bleach escapes '&' to '&amp;'. We need to revert back to compare with
79         # text
80         bleached_text = bleached_text.replace('&amp;', '&')
81         if text != bleached_text:
82             error_msg = ('Variable `{}` contains unsupported tags.'
83                          'Only {} are supported'.format(variable, ', '.join(ALLOWED_TAGS)))
84             raise ValidationError(error_msg)
85     return data
86 
87 
88 def validate_regex(regex_str):
89     if regex_str.startswith('/'):
90         try:
91             re.compile(regex_str[1:-1])
92         except re.error as exp:
93             raise ValidationError(str(exp))
94     return regex_str
95
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/snippets/base/util.py b/snippets/base/util.py
--- a/snippets/base/util.py
+++ b/snippets/base/util.py
@@ -78,9 +78,20 @@
 
             if url_match:
                 url = url_match.group('url')
-            self.links[keyname] = {
-                'url': url,
-            }
+
+            if url == 'special:appMenu':
+                self.links[keyname] = {
+                    'action': 'OPEN_APPLICATIONS_MENU',
+                    'args': 'appMenu',
+                }
+            elif url == 'special:accounts':
+                self.links[keyname] = {
+                    'action': 'SHOW_FIREFOX_ACCOUNTS',
+                }
+            else:
+                self.links[keyname] = {
+                    'url': url,
+                }
 
             # Find the optional data-metric attrib
             metric_match = re.search('data-metric="(?P<metric>.+?)"', matchobj.group('attrs'))
diff --git a/snippets/base/validators.py b/snippets/base/validators.py
--- a/snippets/base/validators.py
+++ b/snippets/base/validators.py
@@ -13,6 +13,7 @@
 
 ALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']
 ALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}
+ALLOWED_PROTOCOLS = ['https', 'special']
 
 
 @deconstructible
@@ -74,13 +75,22 @@
 
     for variable in variables:
         text = data_dict[variable]
-        bleached_text = bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
+        bleached_text = bleach.clean(
+            text,
+            tags=ALLOWED_TAGS,
+            attributes=ALLOWED_ATTRIBUTES,
+            # Allow only secure protocols and custom special links.
+            protocols=ALLOWED_PROTOCOLS,
+        )
         # Bleach escapes '&' to '&amp;'. We need to revert back to compare with
         # text
         bleached_text = bleached_text.replace('&amp;', '&')
+
         if text != bleached_text:
-            error_msg = ('Variable `{}` contains unsupported tags.'
-                         'Only {} are supported'.format(variable, ', '.join(ALLOWED_TAGS)))
+            error_msg = (
+                'Variable `{}` contains unsupported tags or insecure links.'
+                'Only {} tags and https links are supported'
+            ).format(variable, ', '.join(ALLOWED_TAGS))
             raise ValidationError(error_msg)
     return data

{"golden_diff": "diff --git a/snippets/base/util.py b/snippets/base/util.py\n--- a/snippets/base/util.py\n+++ b/snippets/base/util.py\n@@ -78,9 +78,20 @@\n \n             if url_match:\n                 url = url_match.group('url')\n-            self.links[keyname] = {\n-                'url': url,\n-            }\n+\n+            if url == 'special:appMenu':\n+                self.links[keyname] = {\n+                    'action': 'OPEN_APPLICATIONS_MENU',\n+                    'args': 'appMenu',\n+                }\n+            elif url == 'special:accounts':\n+                self.links[keyname] = {\n+                    'action': 'SHOW_FIREFOX_ACCOUNTS',\n+                }\n+            else:\n+                self.links[keyname] = {\n+                    'url': url,\n+                }\n \n             # Find the optional data-metric attrib\n             metric_match = re.search('data-metric=\"(?P<metric>.+?)\"', matchobj.group('attrs'))\ndiff --git a/snippets/base/validators.py b/snippets/base/validators.py\n--- a/snippets/base/validators.py\n+++ b/snippets/base/validators.py\n@@ -13,6 +13,7 @@\n \n ALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']\n ALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}\n+ALLOWED_PROTOCOLS = ['https', 'special']\n \n \n @deconstructible\n@@ -74,13 +75,22 @@\n \n     for variable in variables:\n         text = data_dict[variable]\n-        bleached_text = bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)\n+        bleached_text = bleach.clean(\n+            text,\n+            tags=ALLOWED_TAGS,\n+            attributes=ALLOWED_ATTRIBUTES,\n+            # Allow only secure protocols and custom special links.\n+            protocols=ALLOWED_PROTOCOLS,\n+        )\n         # Bleach escapes '&' to '&amp;'. We need to revert back to compare with\n         # text\n         bleached_text = bleached_text.replace('&amp;', '&')\n+\n         if text != bleached_text:\n-            error_msg = ('Variable `{}` contains unsupported tags.'\n-                         'Only {} are supported'.format(variable, ', '.join(ALLOWED_TAGS)))\n+            error_msg = (\n+                'Variable `{}` contains unsupported tags or insecure links.'\n+                'Only {} tags and https links are supported'\n+            ).format(variable, ', '.join(ALLOWED_TAGS))\n             raise ValidationError(error_msg)\n     return data\n", "issue": "Special Link: about:accounts\nAS Router Action:\r\n - `SHOW_FIREFOX_ACCOUNTS`: opens the firefox accounts signup page (about:accounts?action=signup&entrypoint=snippets)\r\n\r\nCurrently blocked waiting\r\n - documentation on AS Router. See also #521 \r\n - code changes in  (bug [1478569](https://bugzilla.mozilla.org/show_bug.cgi?id=1478569))\n", "before_files": [{"content": "import copy\nimport datetime\nimport re\n\nfrom product_details import product_details\nfrom product_details.version_compare import version_list\n\nEPOCH = datetime.datetime.utcfromtimestamp(0)\n\n\ndef get_object_or_none(model_class, **filters):\n    \"\"\"\n    Identical to Model.get, except instead of throwing exceptions, this returns\n    None.\n    \"\"\"\n    try:\n        return model_class.objects.get(**filters)\n    except (model_class.DoesNotExist, model_class.MultipleObjectsReturned):\n        return None\n\n\ndef first(collection, callback):\n    \"\"\"\n    Find the first item in collection that, when passed to callback, returns\n    True. Returns None if no such item is found.\n    \"\"\"\n    return next((item for item in collection if callback(item)), None)\n\n\ndef create_locales():\n    from snippets.base.models import TargetedLocale\n\n    for code, name in product_details.languages.items():\n        locale = TargetedLocale.objects.get_or_create(code=code.lower())[0]\n        name = name['English']\n        if locale.name != name:\n            locale.name = name\n            locale.save()\n\n\ndef create_countries():\n    from snippets.base.models import TargetedCountry\n\n    for code, name in product_details.get_regions('en-US').items():\n        country = TargetedCountry.objects.get_or_create(code=code)[0]\n        if country.name != name:\n            country.name = name\n            country.save()\n\n\ndef current_firefox_major_version():\n    full_version = version_list(\n        product_details.firefox_history_major_releases)[0]\n\n    return full_version.split('.', 1)[0]\n\n\ndef fluent_link_extractor(data, variables):\n    \"\"\"Replaces all <a> elements with fluent.js link elements sequentially\n    numbered.\n\n    Returns a tuple with the new text and a dict of all the links with url and\n    custom metric where available.\n\n    \"\"\"\n    class Replacer:\n        link_counter = 0\n        links = {}\n\n        def __call__(self, matchobj):\n            keyname = 'link{0}'.format(self.link_counter)\n            replacement = '<{keyname}>{text}</{keyname}>'.format(\n                keyname=keyname,\n                text=matchobj.group('innerText'))\n            # Find the URL\n            url_match = re.search('href=\"(?P<url>.+?)\"', matchobj.group('attrs'))\n            url = ''\n\n            if url_match:\n                url = url_match.group('url')\n            self.links[keyname] = {\n                'url': url,\n            }\n\n            # Find the optional data-metric attrib\n            metric_match = re.search('data-metric=\"(?P<metric>.+?)\"', matchobj.group('attrs'))\n            if metric_match:\n                self.links[keyname]['metric'] = metric_match.group('metric')\n\n            self.link_counter += 1\n            return replacement\n\n    local_data = copy.deepcopy(data)\n    replacer = Replacer()\n    for variable in variables:\n        local_data[variable] = re.sub('(<a(?P<attrs> .*?)>)(?P<innerText>.+?)(</a>)',\n                                      replacer, local_data[variable])\n\n    local_data['links'] = replacer.links\n    return local_data\n\n\ndef to_unix_time_seconds(dt):\n    return int((dt - EPOCH).total_seconds())\n", "path": "snippets/base/util.py"}, {"content": "import re\nimport json\nfrom io import StringIO\n\nimport xml.sax\nfrom xml.sax import ContentHandler\n\nfrom django.core.exceptions import ValidationError\nfrom django.core.validators import BaseValidator\nfrom django.utils.deconstruct import deconstructible\n\nimport bleach\n\nALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']\nALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}\n\n\n@deconstructible\nclass MinValueValidator(BaseValidator):\n    message = 'Ensure this value is greater than or equal to %(limit_value)s.'\n    code = 'min_value'\n\n    def compare(self, a, b):\n        return int(a) < int(b)\n\n\ndef validate_xml_template(data):\n    parser = xml.sax.make_parser()\n    parser.setContentHandler(ContentHandler())\n    parser.setFeature(xml.sax.handler.feature_external_ges, 0)\n\n    xml_str = '<div>\\n{0}</div>'.format(data)\n    try:\n        parser.parse(StringIO(xml_str))\n    except xml.sax.SAXParseException as e:\n        # getLineNumber() - 1 to get the correct line number because\n        # we're wrapping contents into a div.\n        error_msg = (\n            'XML Error: {message} in line {line} column {column}').format(\n                message=e.getMessage(), line=e.getLineNumber() - 1, column=e.getColumnNumber())\n        raise ValidationError(error_msg)\n    return data\n\n\ndef validate_xml_variables(data):\n    data_dict = json.loads(data)\n\n    # set up a safer XML parser that does not resolve external\n    # entities\n    parser = xml.sax.make_parser()\n    parser.setContentHandler(ContentHandler())\n    parser.setFeature(xml.sax.handler.feature_external_ges, 0)\n\n    for name, value in data_dict.items():\n        # Skip over values that aren't strings.\n        if not isinstance(value, str):\n            continue\n\n        xml_str = '<div>{0}</div>'.format(value)\n        try:\n            parser.parse(StringIO(xml_str))\n        except xml.sax.SAXParseException as e:\n            error_msg = (\n                'Data is not XML valid.\\n'\n                'XML Error in value \"{name}\": {message} in column {column}'\n                .format(name=name, message=e.getMessage(),\n                        column=e.getColumnNumber()))\n            raise ValidationError(error_msg)\n    return data\n\n\ndef validate_as_router_fluent_variables(data, variables):\n    data_dict = json.loads(data)\n\n    for variable in variables:\n        text = data_dict[variable]\n        bleached_text = bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)\n        # Bleach escapes '&' to '&amp;'. We need to revert back to compare with\n        # text\n        bleached_text = bleached_text.replace('&amp;', '&')\n        if text != bleached_text:\n            error_msg = ('Variable `{}` contains unsupported tags.'\n                         'Only {} are supported'.format(variable, ', '.join(ALLOWED_TAGS)))\n            raise ValidationError(error_msg)\n    return data\n\n\ndef validate_regex(regex_str):\n    if regex_str.startswith('/'):\n        try:\n            re.compile(regex_str[1:-1])\n        except re.error as exp:\n            raise ValidationError(str(exp))\n    return regex_str\n", "path": "snippets/base/validators.py"}], "after_files": [{"content": "import copy\nimport datetime\nimport re\n\nfrom product_details import product_details\nfrom product_details.version_compare import version_list\n\nEPOCH = datetime.datetime.utcfromtimestamp(0)\n\n\ndef get_object_or_none(model_class, **filters):\n    \"\"\"\n    Identical to Model.get, except instead of throwing exceptions, this returns\n    None.\n    \"\"\"\n    try:\n        return model_class.objects.get(**filters)\n    except (model_class.DoesNotExist, model_class.MultipleObjectsReturned):\n        return None\n\n\ndef first(collection, callback):\n    \"\"\"\n    Find the first item in collection that, when passed to callback, returns\n    True. Returns None if no such item is found.\n    \"\"\"\n    return next((item for item in collection if callback(item)), None)\n\n\ndef create_locales():\n    from snippets.base.models import TargetedLocale\n\n    for code, name in product_details.languages.items():\n        locale = TargetedLocale.objects.get_or_create(code=code.lower())[0]\n        name = name['English']\n        if locale.name != name:\n            locale.name = name\n            locale.save()\n\n\ndef create_countries():\n    from snippets.base.models import TargetedCountry\n\n    for code, name in product_details.get_regions('en-US').items():\n        country = TargetedCountry.objects.get_or_create(code=code)[0]\n        if country.name != name:\n            country.name = name\n            country.save()\n\n\ndef current_firefox_major_version():\n    full_version = version_list(\n        product_details.firefox_history_major_releases)[0]\n\n    return full_version.split('.', 1)[0]\n\n\ndef fluent_link_extractor(data, variables):\n    \"\"\"Replaces all <a> elements with fluent.js link elements sequentially\n    numbered.\n\n    Returns a tuple with the new text and a dict of all the links with url and\n    custom metric where available.\n\n    \"\"\"\n    class Replacer:\n        link_counter = 0\n        links = {}\n\n        def __call__(self, matchobj):\n            keyname = 'link{0}'.format(self.link_counter)\n            replacement = '<{keyname}>{text}</{keyname}>'.format(\n                keyname=keyname,\n                text=matchobj.group('innerText'))\n            # Find the URL\n            url_match = re.search('href=\"(?P<url>.+?)\"', matchobj.group('attrs'))\n            url = ''\n\n            if url_match:\n                url = url_match.group('url')\n\n            if url == 'special:appMenu':\n                self.links[keyname] = {\n                    'action': 'OPEN_APPLICATIONS_MENU',\n                    'args': 'appMenu',\n                }\n            elif url == 'special:accounts':\n                self.links[keyname] = {\n                    'action': 'SHOW_FIREFOX_ACCOUNTS',\n                }\n            else:\n                self.links[keyname] = {\n                    'url': url,\n                }\n\n            # Find the optional data-metric attrib\n            metric_match = re.search('data-metric=\"(?P<metric>.+?)\"', matchobj.group('attrs'))\n            if metric_match:\n                self.links[keyname]['metric'] = metric_match.group('metric')\n\n            self.link_counter += 1\n            return replacement\n\n    local_data = copy.deepcopy(data)\n    replacer = Replacer()\n    for variable in variables:\n        local_data[variable] = re.sub('(<a(?P<attrs> .*?)>)(?P<innerText>.+?)(</a>)',\n                                      replacer, local_data[variable])\n\n    local_data['links'] = replacer.links\n    return local_data\n\n\ndef to_unix_time_seconds(dt):\n    return int((dt - EPOCH).total_seconds())\n", "path": "snippets/base/util.py"}, {"content": "import re\nimport json\nfrom io import StringIO\n\nimport xml.sax\nfrom xml.sax import ContentHandler\n\nfrom django.core.exceptions import ValidationError\nfrom django.core.validators import BaseValidator\nfrom django.utils.deconstruct import deconstructible\n\nimport bleach\n\nALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']\nALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}\nALLOWED_PROTOCOLS = ['https', 'special']\n\n\n@deconstructible\nclass MinValueValidator(BaseValidator):\n    message = 'Ensure this value is greater than or equal to %(limit_value)s.'\n    code = 'min_value'\n\n    def compare(self, a, b):\n        return int(a) < int(b)\n\n\ndef validate_xml_template(data):\n    parser = xml.sax.make_parser()\n    parser.setContentHandler(ContentHandler())\n    parser.setFeature(xml.sax.handler.feature_external_ges, 0)\n\n    xml_str = '<div>\\n{0}</div>'.format(data)\n    try:\n        parser.parse(StringIO(xml_str))\n    except xml.sax.SAXParseException as e:\n        # getLineNumber() - 1 to get the correct line number because\n        # we're wrapping contents into a div.\n        error_msg = (\n            'XML Error: {message} in line {line} column {column}').format(\n                message=e.getMessage(), line=e.getLineNumber() - 1, column=e.getColumnNumber())\n        raise ValidationError(error_msg)\n    return data\n\n\ndef validate_xml_variables(data):\n    data_dict = json.loads(data)\n\n    # set up a safer XML parser that does not resolve external\n    # entities\n    parser = xml.sax.make_parser()\n    parser.setContentHandler(ContentHandler())\n    parser.setFeature(xml.sax.handler.feature_external_ges, 0)\n\n    for name, value in data_dict.items():\n        # Skip over values that aren't strings.\n        if not isinstance(value, str):\n            continue\n\n        xml_str = '<div>{0}</div>'.format(value)\n        try:\n            parser.parse(StringIO(xml_str))\n        except xml.sax.SAXParseException as e:\n            error_msg = (\n                'Data is not XML valid.\\n'\n                'XML Error in value \"{name}\": {message} in column {column}'\n                .format(name=name, message=e.getMessage(),\n                        column=e.getColumnNumber()))\n            raise ValidationError(error_msg)\n    return data\n\n\ndef validate_as_router_fluent_variables(data, variables):\n    data_dict = json.loads(data)\n\n    for variable in variables:\n        text = data_dict[variable]\n        bleached_text = bleach.clean(\n            text,\n            tags=ALLOWED_TAGS,\n            attributes=ALLOWED_ATTRIBUTES,\n            # Allow only secure protocols and custom special links.\n            protocols=ALLOWED_PROTOCOLS,\n        )\n        # Bleach escapes '&' to '&amp;'. We need to revert back to compare with\n        # text\n        bleached_text = bleached_text.replace('&amp;', '&')\n\n        if text != bleached_text:\n            error_msg = (\n                'Variable `{}` contains unsupported tags or insecure links.'\n                'Only {} tags and https links are supported'\n            ).format(variable, ', '.join(ALLOWED_TAGS))\n            raise ValidationError(error_msg)\n    return data\n\n\ndef validate_regex(regex_str):\n    if regex_str.startswith('/'):\n        try:\n            re.compile(regex_str[1:-1])\n        except re.error as exp:\n            raise ValidationError(str(exp))\n    return regex_str\n", "path": "snippets/base/validators.py"}]}