diff --git a/docxtpl/template.py b/docxtpl/template.py index cf339df..385ab05 100644 --- a/docxtpl/template.py +++ b/docxtpl/template.py @@ -69,6 +69,34 @@ def _get_cached_env(autoescape=False): from .subdoc import Subdoc +# Characters that are illegal in XML 1.0 (Char production, XML spec ยง2.2). +# These must be stripped from rendered output because a single illegal character +# injected through a context value (e.g. a C0 control like "\x01") makes the whole +# part XML fail strict parsing. The legal XML whitespace controls \t (0x09), +# \n (0x0A) and \r (0x0D) are intentionally preserved. +# This mirrors the well-known openpyxl ILLEGAL_CHARACTERS_RE approach. +_ILLEGAL_XML_CHARS_RE = re.compile( + "[" + "\x00-\x08\x0b\x0c\x0e-\x1f" # C0 controls except \t \n \r + "\x7f-\x9f" # DEL + C1 controls (illegal in XML 1.0) + "\ud800-\udfff" # UTF-16 surrogate code points + "\ufdd0-\ufdef" # noncharacters + "\ufffe\uffff" # BMP noncharacters + "]" +) + + +def _strip_illegal_xml_chars(xml): + """Remove XML-1.0-illegal characters from a rendered XML string. + + Uses a search-guarded fast path so clean input (the common case) is returned + unchanged without allocating a new string. + """ + if _ILLEGAL_XML_CHARS_RE.search(xml): + return _ILLEGAL_XML_CHARS_RE.sub("", xml) + return xml + + class DocxTemplate(object): """Class for managing docx files as they were jinja2 templates""" @@ -474,6 +502,9 @@ def render_xml_part(self, src_xml, part, context, jinja_env=None): ) raise exc + # Strip XML-1.0-illegal characters injected through context values before + # the rendered string is parsed, so a single bad char can't corrupt output. + dst_xml = _strip_illegal_xml_chars(dst_xml) dst_xml = self._RE_PARAGRAPH_REMOVE_NEWLINE.sub(r"\x01", + "hdr": "header\x01", + "ftr": "footer\x01", + "prop": "property\x01", +} + +tpl.render(context, autoescape=True) +tpl.save("output/illegal_xml_chars.docx") diff --git a/tests/templates/illegal_xml_chars.docx b/tests/templates/illegal_xml_chars.docx new file mode 100644 index 0000000..7285bca Binary files /dev/null and b/tests/templates/illegal_xml_chars.docx differ