diff --git a/Cargo.toml b/Cargo.toml index 1eeca18..d545cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ unicode-general-category = { version = "1.0" } [features] default = ["serde"] serde = ["dep:serde"] +unlimited_depth = [] + + [dev-dependencies.serde] version = "1.0" @@ -49,4 +52,5 @@ name = "json5-trailing-comma-formatter" test = true [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true + diff --git a/src/parser.rs b/src/parser.rs index c6b78dd..5c4c249 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -251,12 +251,19 @@ struct JSON5Parser<'toks, 'input> { source: &'input str, source_tokens: Peekable>, lookahead: Option<&'toks TokenSpan>, + current_depth: usize, + max_depth: usize, } impl<'toks, 'input> JSON5Parser<'toks, 'input> { fn new(tokens: &'toks Tokens<'input>) -> Self { - JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source } + use crate::utils::MAX_DEPTH; + JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source, current_depth: 0, max_depth: MAX_DEPTH } + } + + fn with_max_depth(tokens: &'toks Tokens<'input>, max_depth: usize) -> Self { + JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source, current_depth: 0, max_depth: max_depth } } fn advance(&mut self) -> Option<&'toks TokenSpan> { @@ -487,6 +494,11 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { match self.check_and_consume(vec![TokType::Plus, TokType::Minus]) { None => self.parse_primary(), Some(span) => { + self.current_depth = self.current_depth + 1; + if self.current_depth > self.max_depth { + let idx = self.position(); + return Err(self.make_error(format!("max depth ({}) exceeded while parsing unary. To expand the depth, use the ``with_max_depth`` constructor or enable the `unlimited_depth` feature", self.max_depth), idx)) + } match span.1 { TokType::Plus => { let value = self.parse_unary()?; @@ -496,6 +508,7 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { return Err(self.make_error(format!("Unary operations not allowed for value {:?}", val), span.2)) } } + self.current_depth = self.current_depth - 1; Ok(JSONValue::Unary {operator: UnaryOperator::Plus, value: Box::new(value)}) } TokType::Minus => { @@ -506,6 +519,7 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { return Err(self.make_error(format!("Unary operations not allowed for value {:?}", val), span.2)) } } + self.current_depth = self.current_depth - 1; Ok(JSONValue::Unary {operator: UnaryOperator::Minus, value: Box::new(value)}) } _ => unreachable!("no") @@ -529,7 +543,14 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { fn parse_value(&mut self) -> Result, ParsingError> { - self.parse_obj_or_array() + self.current_depth = self.current_depth + 1; + if self.current_depth > self.max_depth { + let idx = self.position(); + return Err(self.make_error(format!("max depth ({}) exceeded in nested arrays/objects. To expand the depth, use the ``with_max_depth`` constructor or enable the `unlimited_depth` feature", self.max_depth), idx)) + } + let res = self.parse_obj_or_array(); + self.current_depth = self.current_depth - 1; + res } fn parse_text(&mut self) -> Result, ParsingError> { @@ -606,6 +627,23 @@ mod tests { assert!(res.is_err()); } + #[cfg(not(feature = "unlimited_depth"))] + #[test] + fn test_deeply_nested() { + let n = 4000; + let mut s = String::with_capacity(n * 2); + for _ in 0 .. n { + s.push('[') + } + for _ in 0 .. n { + s.push(']') + } + let res = from_str(s.as_str()); + assert!(res.is_err()); + assert!(res.unwrap_err().message.contains("max depth")) + } + + #[test] fn test_from_bytes() { let res = from_bytes(b"{}").unwrap(); diff --git a/src/rt/parser.rs b/src/rt/parser.rs index b08775f..d8259ef 100644 --- a/src/rt/parser.rs +++ b/src/rt/parser.rs @@ -334,12 +334,19 @@ struct JSON5Parser<'toks, 'input> { source: &'input str, source_tokens: Peekable>, lookahead: Option<&'toks TokenSpan>, + current_depth: usize, + max_depth: usize, } impl<'toks, 'input> JSON5Parser<'toks, 'input> { fn new(tokens: &'toks Tokens<'input>) -> Self { - JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source } + use crate::utils::MAX_DEPTH; + JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source, current_depth: 0, max_depth: MAX_DEPTH } + } + + fn with_max_depth(&mut self, tokens: &'toks Tokens<'input>, max_depth: usize) -> Self { + JSON5Parser { source_tokens: tokens.tok_spans.iter().peekable(), lookahead: None, source: tokens.source, current_depth: 0, max_depth: max_depth } } fn advance(&mut self) -> Option<&'toks TokenSpan> { @@ -602,6 +609,11 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { match self.check_and_consume(vec![TokType::Plus, TokType::Minus]) { None => self.parse_primary(), Some(span) => { + self.current_depth = self.current_depth + 1; + if self.current_depth > self.max_depth { + let idx = self.position(); + return Err(self.make_error(format!("max depth ({}) exceeded while parsing unary. To expand the depth, use the ``with_max_depth`` constructor or enable the `unlimited_depth` feature", self.max_depth), idx)) + } match span.1 { TokType::Plus => { let value = self.parse_unary()?; @@ -611,7 +623,7 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { return Err(self.make_error(format!("Unary operations not allowed for value {:?}", val), span.2)) } } - + self.current_depth = self.current_depth - 1; Ok(JSONValue::Unary {operator: UnaryOperator::Plus, value: Box::new(value)}) } TokType::Minus => { @@ -622,6 +634,7 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { return Err(self.make_error(format!("Unary operations not allowed for value {:?}", val), span.2)) } } + self.current_depth = self.current_depth - 1; Ok(JSONValue::Unary {operator: UnaryOperator::Minus, value: Box::new(value)}) } _ => unreachable!("no") @@ -645,7 +658,14 @@ impl<'toks, 'input> JSON5Parser<'toks, 'input> { fn parse_value(&mut self) -> Result { - self.parse_obj_or_array() + self.current_depth = self.current_depth + 1; + if self.current_depth > self.max_depth { + let idx = self.position(); + return Err(self.make_error(format!("max depth ({}) exceeded in nested arrays/objects. To expand the depth, use the ``with_max_depth`` constructor or enable the `unlimited_depth` feature", self.max_depth), idx)) + } + let res = self.parse_obj_or_array(); + self.current_depth = self.current_depth - 1; + res } fn parse_text(&mut self) -> Result { @@ -713,6 +733,22 @@ mod tests { assert!(res.is_err()); } + #[cfg(not(feature = "unlimited_depth"))] + #[test] + fn test_deeply_nested() { + let n = 4000; + let mut s = String::with_capacity(n * 2); + for _ in 0 .. n { + s.push('[') + } + for _ in 0 .. n { + s.push(']') + } + let res = crate::parser::from_str(s.as_str()); + assert!(res.is_err()); + assert!(res.unwrap_err().message.contains("max depth")) + } + #[test] fn test_foo() { let res = from_str("{}").unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 50b7b7b..1e3de01 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -158,4 +158,21 @@ fn char_from_u32(u: u32) -> Result { fn err>(message: S) -> String { message.into() -} \ No newline at end of file +} + + +#[cfg(all(not(feature = "unlimited_depth"), not(target_os = "windows"), debug_assertions))] +pub (crate) const MAX_DEPTH: usize = 1000; + +#[cfg(all(not(feature = "unlimited_depth"), not(target_os = "windows"), not(debug_assertions)))] +pub (crate) const MAX_DEPTH: usize = 3000; + + +#[cfg(all(not(feature = "unlimited_depth"), target_os = "windows", debug_assertions))] +pub (crate) const MAX_DEPTH: usize = 700; + +#[cfg(all(not(feature = "unlimited_depth"), target_os = "windows", not(debug_assertions)))] +pub (crate) const MAX_DEPTH: usize = 2000; + +#[cfg(feature = "unlimited_depth")] +pub (crate) const MAX_DEPTH: usize = usize::MAX;