Commit ce12717ad7 for freeswitch.com

commit ce12717ad7adc9872aae1c5779f5df60e43d2db3
Author: Andrey Volk <andywolk@gmail.com>
Date:   Thu May 7 20:14:34 2026 +0300

    Merge commit from fork

    Co-authored-by: Jakub Karolczyk <jakub.karolczyk@signalwire.com>

diff --git a/src/switch_xml.c b/src/switch_xml.c
index d54294df92..0194645640 100644
--- a/src/switch_xml.c
+++ b/src/switch_xml.c
@@ -838,6 +838,7 @@ static short switch_xml_internal_dtd(switch_xml_root_t root, char *s, switch_siz
 	char q, *c, *t, *n = NULL, *v, **ent, **pe;
 	int i, j;
 	char **sstmp;
+	switch_bool_t disable_dtd = switch_true(switch_core_get_variable("xml_disable_dtd"));

 	pe = (char **) memcpy(switch_must_malloc(sizeof(SWITCH_XML_NIL)), SWITCH_XML_NIL, sizeof(SWITCH_XML_NIL));

@@ -847,7 +848,7 @@ static short switch_xml_internal_dtd(switch_xml_root_t root, char *s, switch_siz

 		if (!*s)
 			break;
-		else if (!strncmp(s, "<!ENTITY", 8)) {	/* parse entity definitions */
+		else if (!strncmp(s, "<!ENTITY", 8) && !disable_dtd) {	/* parse entity definitions if dtd is not explicitly disabled */
 			int use_pe;

 			c = s += strspn(s + 8, SWITCH_XML_WS) + 8;	/* skip white space separator */
@@ -881,7 +882,7 @@ static short switch_xml_internal_dtd(switch_xml_root_t root, char *s, switch_siz
 				break;
 			} else
 				ent[i] = n;		/* set entity name */
-		} else if (!strncmp(s, "<!ATTLIST", 9)) {	/* parse default attributes */
+		} else if (!strncmp(s, "<!ATTLIST", 9) && !disable_dtd) {	/* parse default attributes if dtd is not explicitly disabled */
 			t = s + strspn(s + 9, SWITCH_XML_WS) + 9;	/* skip whitespace separator */
 			if (!*t) {
 				switch_xml_err(root, t, "unclosed <!ATTLIST");
diff --git a/tests/unit/switch_xml.c b/tests/unit/switch_xml.c
index 5bdfb6def5..a48aa4f032 100644
--- a/tests/unit/switch_xml.c
+++ b/tests/unit/switch_xml.c
@@ -117,6 +117,74 @@ FST_MINCORE_BEGIN("./conf")
 			free(xml_string);
 		}
 		FST_TEST_END()
+
+		FST_TEST_BEGIN(test_dtd)
+		{
+			const char *text = "<xml><!DOCTYPE Response [<!ENTITY lol \"haha\"><!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">]><Response><Say>&lol1;</Say></Response></xml>";
+			switch_xml_t xml = switch_xml_parse_str_dynamic((char *)text, SWITCH_TRUE);
+			char *xml_string = NULL;
+
+			fst_requires(xml);
+			xml_string = switch_xml_toxml_ex(xml, SWITCH_FALSE, SWITCH_FALSE);
+			fst_requires(xml_string);
+			fst_check_string_equals(xml_string, "<xml>\n  <Response>\n    <Say>hahahahahahahahahahahahahahahahahahahaha</Say>\n  </Response>\n</xml>\n");
+			free(xml_string);
+			switch_xml_free(xml);
+		}
+		FST_TEST_END()
+
+		FST_TEST_BEGIN(test_dtd_disable)
+		{
+			const char *text = "<xml><!DOCTYPE Response [<!ENTITY lol \"haha\"><!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">]><Response><Say>&lol1;</Say></Response></xml>";
+			switch_xml_t xml = NULL;
+			char *xml_string = NULL;
+
+			switch_core_set_variable("xml_disable_dtd", "true");
+			xml = switch_xml_parse_str_dynamic((char *)text, SWITCH_TRUE);
+			fst_requires(xml);
+			xml_string = switch_xml_toxml_ex(xml, SWITCH_FALSE, SWITCH_FALSE);
+			fst_requires(xml_string);
+			fst_check_string_equals(xml_string, "<xml>\n  <Response>\n    <Say>&amp;lol1;</Say>\n  </Response>\n</xml>\n");
+			free(xml_string);
+			switch_xml_free(xml);
+			switch_core_set_variable("xml_disable_dtd", "false");
+		}
+		FST_TEST_END()
+
+		FST_TEST_BEGIN(test_dtd_with_comments)
+		{
+			const char *text = "<xml><!DOCTYPE Response [<!--COMMENT1--><!ENTITY lol \"haha\"><!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\"><!--COMMENT2-->]><Response><Say>&lol1;</Say></Response></xml>";
+			switch_xml_t xml = NULL;
+			char *xml_string = NULL;
+
+			xml = switch_xml_parse_str_dynamic((char *)text, SWITCH_TRUE);
+			fst_requires(xml);
+			xml_string = switch_xml_toxml_ex(xml, SWITCH_FALSE, SWITCH_FALSE);
+			fst_requires(xml_string);
+			fst_check_string_equals(xml_string, "<xml>\n  <Response>\n    <Say>hahahahahahahahahahahahahahahahahahahaha</Say>\n  </Response>\n</xml>\n");
+			free(xml_string);
+			switch_xml_free(xml);
+		}
+		FST_TEST_END()
+
+		FST_TEST_BEGIN(test_dtd_disable_with_comments)
+		{
+			const char *text = "<xml><!DOCTYPE Response [<!--COMMENT1--><!ENTITY lol \"haha\"><!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\"><!--COMMENT2-->]><Response><Say>&lol1;</Say></Response></xml>";
+			switch_xml_t xml = NULL;
+			char *xml_string = NULL;
+
+			switch_core_set_variable("xml_disable_dtd", "true");
+			xml = switch_xml_parse_str_dynamic((char *)text, SWITCH_TRUE);
+			fst_requires(xml);
+			xml_string = switch_xml_toxml_ex(xml, SWITCH_FALSE, SWITCH_FALSE);
+			fst_requires(xml_string);
+			fst_check_string_equals(xml_string, "<xml>\n  <Response>\n    <Say>&amp;lol1;</Say>\n  </Response>\n</xml>\n");
+			free(xml_string);
+			switch_xml_free(xml);
+			switch_core_set_variable("xml_disable_dtd", "false");
+		}
+		FST_TEST_END()
+
 	}
 	FST_SUITE_END()
 }