From 76579f865b6a12e0c816877c8480f4a9dae698c1 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 14:59:26 +0300 Subject: [PATCH] encoding: fix xml module unhandled exception crashing the program (fixes #26164) --- vlib/encoding/xml/encoding.v | 116 +++++++++++++++--------------- vlib/encoding/xml/encoding_test.v | 26 +++++++ 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/vlib/encoding/xml/encoding.v b/vlib/encoding/xml/encoding.v index 94d378e41..e734c4b8b 100644 --- a/vlib/encoding/xml/encoding.v +++ b/vlib/encoding/xml/encoding.v @@ -2,20 +2,14 @@ module xml import strings -// pretty_str returns a pretty-printed version of the XML node. It requires the current indentation -// the node is at, the depth of the node in the tree, and a map of reverse entities to use when -// escaping text. -pub fn (node XMLNode) pretty_str(original_indent string, depth int, reverse_entities map[string]string) string { - // Create the proper indentation first - mut indent_builder := strings.new_builder(original_indent.len * depth) +fn write_indent(mut builder strings.Builder, indent string, depth int) { for _ in 0 .. depth { - indent_builder.write_string(original_indent) + builder.write_string(indent) } - indent := indent_builder.str() +} - // Now we can stringify the node - mut builder := strings.new_builder(1024) - builder.write_string(indent) +fn write_pretty_xml_node(mut builder strings.Builder, node XMLNode, original_indent string, depth int, reverse_entities map[string]string) { + write_indent(mut builder, original_indent, depth) builder.write_u8(`<`) builder.write_string(node.name) @@ -31,24 +25,21 @@ pub fn (node XMLNode) pretty_str(original_indent string, depth int, reverse_enti for child in node.children { match child { string { - builder.write_string(indent) - builder.write_string(original_indent) + write_indent(mut builder, original_indent, depth + 1) builder.write_string(escape_text(child, reverse_entities: reverse_entities)) } XMLNode { - builder.write_string(child.pretty_str(original_indent, depth + 1, - reverse_entities)) + write_pretty_xml_node(mut builder, child, original_indent, depth + 1, + reverse_entities) } XMLComment { - builder.write_string(indent) - builder.write_string(original_indent) + write_indent(mut builder, original_indent, depth + 1) builder.write_string('') } XMLCData { - builder.write_string(indent) - builder.write_string(original_indent) + write_indent(mut builder, original_indent, depth + 1) builder.write_string('') @@ -57,22 +48,20 @@ pub fn (node XMLNode) pretty_str(original_indent string, depth int, reverse_enti builder.write_u8(`\n`) } - builder.write_string(indent) + write_indent(mut builder, original_indent, depth) builder.write_string('`) } else { builder.write_string('/>') } - return builder.str() } -fn (list []DTDListItem) pretty_str(indent string) string { +fn write_pretty_dtd_list(mut builder strings.Builder, list []DTDListItem, indent string) bool { if list.len == 0 { - return '' + return false } - mut builder := strings.new_builder(1024) builder.write_u8(`[`) builder.write_u8(`\n`) @@ -99,40 +88,63 @@ fn (list []DTDListItem) pretty_str(indent string) string { builder.write_u8(`\n`) } builder.write_u8(`]`) - return builder.str() + return true } -fn (doctype DocumentType) pretty_str(indent string) string { - mut builder := strings.new_builder(1024) +fn write_pretty_doctype(mut builder strings.Builder, doctype DocumentType, indent string) bool { match doctype.dtd { string { content := doctype.dtd - return if content.len > 0 { - builder.write_string('\n') - builder.str() - } else { - '' + if content.len == 0 { + return false } + builder.write_string('\n') + return true } DocumentTypeDefinition { if doctype.dtd.list.len == 0 { - return '' + return false } builder.write_string('\n') - return builder.str() + return true } } } +// pretty_str returns a pretty-printed version of the XML node. It requires the current indentation +// the node is at, the depth of the node in the tree, and a map of reverse entities to use when +// escaping text. +pub fn (node XMLNode) pretty_str(original_indent string, depth int, reverse_entities map[string]string) string { + mut builder := strings.new_builder(1024) + write_pretty_xml_node(mut builder, node, original_indent, depth, reverse_entities) + return builder.str() +} + +fn (list []DTDListItem) pretty_str(indent string) string { + mut builder := strings.new_builder(1024) + if !write_pretty_dtd_list(mut builder, list, indent) { + return '' + } + return builder.str() +} + +fn (doctype DocumentType) pretty_str(indent string) string { + mut builder := strings.new_builder(1024) + if !write_pretty_doctype(mut builder, doctype, indent) { + return '' + } + return builder.str() +} + // pretty_str returns a pretty-printed version of the XML document. It requires the string used to // indent each level of the document. pub fn (doc XMLDocument) pretty_str(indent string) string { @@ -144,28 +156,16 @@ pub fn (doc XMLDocument) pretty_str(indent string) string { document_builder.write_string(doc.encoding) document_builder.write_string('"?>\n') - comments := if doc.comments.len > 0 { - mut comments_buffer := strings.new_builder(512) - for comment in doc.comments { - comments_buffer.write_string('') - comments_buffer.write_u8(`\n`) - } - comments_buffer.str() - } else { - '' - } - - doctype_string := doc.doctype.pretty_str(indent) - if doctype_string.len > 0 { - document_builder.write_string(doctype_string) + if write_pretty_doctype(mut document_builder, doc.doctype, indent) { document_builder.write_u8(`\n`) } - if comments.len > 0 { - document_builder.write_string(comments) + for comment in doc.comments { + document_builder.write_string('') + document_builder.write_u8(`\n`) } - document_builder.write_string(doc.root.pretty_str(indent, 0, doc.parsed_reverse_entities)) + write_pretty_xml_node(mut document_builder, doc.root, indent, 0, doc.parsed_reverse_entities) return document_builder.str() } diff --git a/vlib/encoding/xml/encoding_test.v b/vlib/encoding/xml/encoding_test.v index d8b9452cb..c1621cd58 100644 --- a/vlib/encoding/xml/encoding_test.v +++ b/vlib/encoding/xml/encoding_test.v @@ -177,3 +177,29 @@ fn test_doc() { assert doc.pretty_str('\t') == values[i] } } + +fn test_large_doc_str() { + depth := 1500 + payload := '0123456789abcdef'.repeat(16) + mut node := xml.XMLNode{ + name: 'leaf' + } + for i in 0 .. depth { + node = xml.XMLNode{ + name: 'n${i}' + children: [ + payload, + node, + ] + } + } + doc := xml.XMLDocument{ + root: node + } + + rendered := doc.str() + assert rendered.starts_with('\n') + assert rendered.contains('') + assert rendered.ends_with('') + assert rendered.len > 5_000_000 +} -- 2.39.5