pub(super) const MAX_JSON5_NESTING_DEPTH: u32 = 64;
pub(super) fn json5_nesting_depth(source: &str) -> u32 {
let mut depth: u32 = 0;
let mut max_depth: u32 = 0;
let mut in_string: Option<char> = None;
let mut chars = source.chars().peekable();
while let Some(c) = chars.next() {
if let Some(quote) = in_string {
match c {
'\\' => {
chars.next(); }
q if q == quote => in_string = None,
_ => {}
}
continue;
}
match c {
'/' if chars.peek() == Some(&'/') => {
chars.next(); for c2 in chars.by_ref() {
if c2 == '\n' {
break;
}
}
}
'/' if chars.peek() == Some(&'*') => {
chars.next(); let mut prev = '\0';
let mut closed = false;
for c2 in chars.by_ref() {
if prev == '*' && c2 == '/' {
closed = true;
break;
}
prev = c2;
}
if !closed {
return u32::MAX; }
}
'"' | '\'' => in_string = Some(c),
'[' | '{' => {
depth = depth.saturating_add(1);
max_depth = max_depth.max(depth);
}
']' | '}' => depth = depth.saturating_sub(1),
_ => {}
}
}
if in_string.is_some() {
return u32::MAX; }
max_depth
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::flockfile::error::FlockfileError;
use crate::config::flockfile::file::Flockfile;
use crate::config::flockfile::format::FlockFormat;
#[test]
fn json5_beyond_max_nesting_depth_is_rejected_without_crashing() {
let src = "[".repeat(5000);
assert_eq!(
Flockfile::parse(&src, FlockFormat::Json5).unwrap_err(),
FlockfileError::Json5("nesting depth exceeds 64".to_string())
);
}
#[test]
fn json5_nesting_depth_counts_concurrently_open_brackets() {
let nested = format!("{}{}", "[".repeat(10), "]".repeat(10));
assert_eq!(json5_nesting_depth(&nested), 10);
}
#[test]
fn json5_nesting_depth_ignores_brackets_inside_strings() {
let src = r#"{ "a": "[[[[[[[[[[", "b": "esc\"aped [ too" }"#;
assert_eq!(json5_nesting_depth(src), 1); }
#[test]
fn json5_legitimately_nested_doc_still_parses() {
let src = r#"{
app: [{
name: "web",
script: "./srv",
readiness_probe: { kind: "http", target: "http://localhost/x" },
}],
}"#;
let flock = Flockfile::parse(src, FlockFormat::Json5).unwrap();
assert_eq!(flock.apps.len(), 1);
}
#[test]
fn json5_line_comment_apostrophe_does_not_hide_deep_nesting() {
let src = format!("// don't nest\n{}", "[".repeat(5000));
assert_eq!(
Flockfile::parse(&src, FlockFormat::Json5).unwrap_err(),
FlockfileError::Json5("nesting depth exceeds 64".to_string())
);
}
#[test]
fn json5_block_comment_apostrophe_does_not_hide_deep_nesting() {
let src = format!("/* it's fine */\n{}", "[".repeat(5000));
assert_eq!(
Flockfile::parse(&src, FlockFormat::Json5).unwrap_err(),
FlockfileError::Json5("nesting depth exceeds 64".to_string())
);
}
#[test]
fn json5_benign_comment_does_not_undercount_a_real_document() {
let src = r#"{
/* it's the app list */
app: [{
name: "web",
script: "./srv",
readiness_probe: { kind: "http", target: "http://localhost/x" },
}],
}"#;
let flock = Flockfile::parse(src, FlockFormat::Json5).unwrap();
assert_eq!(flock.apps.len(), 1);
}
}