From aaeca366f67444d95ce44c532808fa40e668e9ea Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Fri, 10 Jul 2026 16:03:53 +0300 Subject: [PATCH] ifcparse: strip XML-illegal control characters in escape_xml (#2043, #3074) escape_xml escaped the five XML metacharacters but passed control characters (0x00 to 0x1F other than tab, newline and carriage return) through unchanged. Those bytes are illegal in XML 1.0 and cannot be represented even as numeric character references, so any IFC string containing them produced non-well-formed XML and SVG output. Strip those illegal control characters before escaping. Bytes belonging to a valid UTF-8 multibyte sequence are always >= 0x80, so filtering on the low control range leaves real text intact. This is the shared helper used by the SVG serializer text and attribute sites (audited: all route through it) and by the XML/Collada paths, so both reports are resolved at one place. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 380675e2144ac1cbc732009e4ea5ed6c35e3e379) --- src/ifcparse/utils.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ifcparse/utils.cpp b/src/ifcparse/utils.cpp index 41366817b0..4ca451c45a 100644 --- a/src/ifcparse/utils.cpp +++ b/src/ifcparse/utils.cpp @@ -107,6 +107,15 @@ void ifcopenshell::sanitate_material_name(std::string& str) { } void ifcopenshell::escape_xml(std::string& str) { + // Strip characters that are illegal in XML 1.0. Control characters other + // than tab (0x09), newline (0x0A) and carriage return (0x0D) are not valid + // XML 1.0 characters and cannot even be represented as numeric character + // references, so they would otherwise make the serialized XML/SVG output + // non-well-formed. Bytes belonging to a valid UTF-8 multibyte sequence are + // always >= 0x80, so filtering on the low control range leaves them intact. + str.erase(std::remove_if(str.begin(), str.end(), [](unsigned char c) { + return c < 0x20 && c != '\t' && c != '\n' && c != '\r'; + }), str.end()); boost::replace_all(str, "&", "&"); boost::replace_all(str, "\"", """); boost::replace_all(str, "'", "'");