git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

fix guard against deep recursion in toml

sonxoxzuYaroslav Bolyukin2026-04-25parent: #d5225b8.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
before · crates/jrsonnet-stdlib/src/manifest/toml.rs
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	IStr, ObjValue, Result, ResultExt, Val, bail, in_description_frame,5	manifest::{ManifestFormat, escape_string_json_buf},6	val::ArrValue,7};89pub struct TomlFormat<'s> {10	/// Padding before fields, i.e11	/// ```toml12	/// [a]13	///   b = 114	/// ## <- this15	/// ```16	padding: Cow<'s, str>,17	/// Do not emit sections for objects, consisting only from sections:18	/// ```toml19	/// # false20	/// [a]21	/// [a.b]22	///23	/// # true24	/// [a.b]25	/// ```26	skip_empty_sections: bool,27	/// If true - then order of fields is preserved as written,28	/// instead of sorting alphabetically29	#[cfg(feature = "exp-preserve-order")]30	preserve_order: bool,31}32impl TomlFormat<'_> {33	pub fn cli(34		padding: usize,35		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,36	) -> Self {37		let padding = " ".repeat(padding);38		Self {39			padding: Cow::Owned(padding),40			skip_empty_sections: true,41			#[cfg(feature = "exp-preserve-order")]42			preserve_order,43		}44	}45	pub fn std_to_toml(46		padding: String,47		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,48	) -> Self {49		Self {50			padding: Cow::Owned(padding),51			skip_empty_sections: false,52			#[cfg(feature = "exp-preserve-order")]53			preserve_order,54		}55	}56}5758fn bare_allowed(s: &str) -> bool {59	s.bytes()60		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))61}6263fn escape_key_toml_buf(key: &str, buf: &mut String) {64	if bare_allowed(key) {65		buf.push_str(key);66	} else {67		escape_string_json_buf(key, buf);68	}69}7071fn is_section(val: &Val) -> Result<bool> {72	Ok(match val {73		Val::Arr(a) => {74			if a.is_empty() {75				return Ok(false);76			}77			for e in a.iter() {78				let e = e?;79				if !matches!(e, Val::Obj(_)) {80					return Ok(false);81				}82			}83			true84		}85		Val::Obj(_) => true,86		_ => false,87	})88}8990fn manifest_value(91	val: &Val,92	inline: bool,93	buf: &mut String,94	cur_padding: &str,95	options: &TomlFormat<'_>,96) -> Result<()> {97	use std::fmt::Write;98	match val {99		Val::Bool(true) => buf.push_str("true"),100		Val::Bool(false) => buf.push_str("false"),101		Val::Str(s) => {102			escape_string_json_buf(&s.clone().into_flat(), buf);103		}104		Val::Num(n) => write!(buf, "{n}").unwrap(),105		#[cfg(feature = "exp-bigint")]106		Val::BigInt(n) => write!(buf, "{n}").unwrap(),107		Val::Arr(a) => {108			buf.push('[');109110			let mut had_items = false;111			for (i, e) in a.iter().enumerate() {112				had_items = true;113				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;114115				if i != 0 {116					buf.push(',');117				}118				if inline {119					buf.push(' ');120				} else {121					buf.push('\n');122					buf.push_str(cur_padding);123					buf.push_str(&options.padding);124				}125126				in_description_frame(127					|| format!("elem <{i}> manifestification"),128					|| manifest_value(&e, true, buf, "", options),129				)?;130			}131132			if !had_items {133			} else if inline {134				buf.push(' ');135			} else {136				buf.push('\n');137				buf.push_str(cur_padding);138			}139			buf.push(']');140		}141		Val::Obj(o) => {142			o.run_assertions()?;143			buf.push('{');144145			let mut had_fields = false;146			for (i, (k, v)) in o147				.iter(148					#[cfg(feature = "exp-preserve-order")]149					options.preserve_order,150				)151				.enumerate()152			{153				had_fields = true;154				let v = v.with_description(|| format!("field <{k}> evaluation"))?;155156				if i != 0 {157					buf.push(',');158				}159				buf.push(' ');160161				escape_key_toml_buf(&k, buf);162				buf.push_str(" = ");163				in_description_frame(164					|| format!("field <{k}> manifestification"),165					|| manifest_value(&v, true, buf, "", options),166				)?;167			}168169			if had_fields {170				buf.push(' ');171			}172173			buf.push('}');174		}175		Val::Null => {176			bail!("tried to manifest null")177		}178		Val::Func(_) => {179			bail!("tried to manifest function")180		}181	}182	Ok(())183}184185fn manifest_table_internal(186	obj: &ObjValue,187	path: &mut Vec<IStr>,188	buf: &mut String,189	cur_padding: &mut String,190	options: &TomlFormat<'_>,191) -> Result<()> {192	let mut sections = Vec::new();193	let mut first = true;194	for (key, value) in obj.iter(195		#[cfg(feature = "exp-preserve-order")]196		options.preserve_order,197	) {198		let value = value.with_description(|| format!("field <{key}> evaluation"))?;199		if is_section(&value)? {200			sections.push((key, value));201		} else {202			if !first {203				buf.push('\n');204			}205			first = false;206			buf.push_str(cur_padding);207			escape_key_toml_buf(&key, buf);208			buf.push_str(" = ");209			in_description_frame(210				|| format!("table <{key}> manifestification"),211				|| manifest_value(&value, false, buf, cur_padding, options),212			)?;213		}214	}215	for (k, v) in sections {216		if !first {217			buf.push_str("\n\n");218		}219		first = false;220		path.push(k.clone());221		in_description_frame(222			|| format!("section <{k}> manifestification"),223			|| match v {224				Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),225				Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),226				_ => unreachable!("iterating over sections"),227			},228		)?;229		path.pop();230	}231	Ok(())232}233234fn manifest_table(235	obj: &ObjValue,236	path: &mut Vec<IStr>,237	buf: &mut String,238	cur_padding: &mut String,239	options: &TomlFormat<'_>,240) -> Result<()> {241	if options.skip_empty_sections242		&& !obj.is_empty()243		&& obj244			.iter(245				#[cfg(feature = "exp-preserve-order")]246				false,247			)248			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?249	{250		manifest_table_internal(obj, path, buf, cur_padding, options)?;251		return Ok(());252	}253	buf.push_str(cur_padding);254	buf.push('[');255	for (i, k) in path.iter().enumerate() {256		if i != 0 {257			buf.push('.');258		}259		escape_key_toml_buf(k, buf);260	}261	buf.push(']');262	if obj.is_empty() {263		return Ok(());264	}265	buf.push('\n');266	let prev_len = cur_padding.len();267	cur_padding.push_str(&options.padding);268	manifest_table_internal(obj, path, buf, cur_padding, options)?;269	cur_padding.truncate(prev_len);270	Ok(())271}272fn manifest_table_array(273	arr: &ArrValue,274	path: &mut Vec<IStr>,275	buf: &mut String,276	cur_padding: &mut String,277	options: &TomlFormat<'_>,278) -> Result<()> {279	let mut formatted_path = String::new();280	{281		formatted_path.push_str(cur_padding);282		formatted_path.push_str("[[");283		for (i, k) in path.iter().enumerate() {284			if i != 0 {285				formatted_path.push('.');286			}287			escape_key_toml_buf(k, &mut formatted_path);288		}289		formatted_path.push_str("]]");290	}291	let prev_len = cur_padding.len();292	cur_padding.push_str(&options.padding);293	for (i, e) in arr.iter().enumerate() {294		let obj = e.expect("already tested").as_obj().expect("already tested");295		if i != 0 {296			buf.push_str("\n\n");297		}298		buf.push_str(&formatted_path);299		if obj.is_empty() {300			continue;301		}302		buf.push('\n');303		manifest_table_internal(&obj, path, buf, cur_padding, options)?;304	}305	cur_padding.truncate(prev_len);306	Ok(())307}308309impl ManifestFormat for TomlFormat<'_> {310	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {311		match val {312			Val::Obj(obj) => {313				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)314			}315			_ => bail!("toml body should be object"),316		}317	}318}