diff --git a/benches/reader_benchmark.rs b/benches/reader_benchmark.rs index 7d89517..4e0d973 100644 --- a/benches/reader_benchmark.rs +++ b/benches/reader_benchmark.rs @@ -14,7 +14,13 @@ fn bench_compare(c: &mut Criterion, name: &str, json: &str) { group.bench_with_input("struson-skip", json, |b, json| { b.iter(|| { call_unwrap(|| { - let mut json_reader = JsonStreamReader::new(json.as_bytes()); + let mut json_reader = JsonStreamReader::new_custom( + json.as_bytes(), + ReaderSettings { + max_nesting_depth: None, + ..Default::default() + }, + ); json_reader.skip_value()?; json_reader.consume_trailing_whitespace()?; Ok(()) @@ -28,6 +34,7 @@ fn bench_compare(c: &mut Criterion, name: &str, json: &str) { json.as_bytes(), ReaderSettings { track_path: false, + max_nesting_depth: None, ..Default::default() }, ); @@ -112,7 +119,13 @@ fn bench_compare(c: &mut Criterion, name: &str, json: &str) { group.bench_with_input("struson-read", json, |b, json| { b.iter(|| { call_unwrap(|| { - let json_reader = JsonStreamReader::new(json.as_bytes()); + let json_reader = JsonStreamReader::new_custom( + json.as_bytes(), + ReaderSettings { + max_nesting_depth: None, + ..Default::default() + }, + ); struson_read(json_reader) }); }) @@ -125,6 +138,7 @@ fn bench_compare(c: &mut Criterion, name: &str, json: &str) { json.as_bytes(), ReaderSettings { track_path: false, + max_nesting_depth: None, ..Default::default() }, ); diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 16cfbf5..fbc5b88 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -692,17 +692,20 @@ pub enum UnexpectedStructureKind { /// Index (starting at 0) of the expected item expected_index: u32, }, + /// A JSON object does not have a member with a certain name MissingObjectMember { /// Name of the expected member member_name: String, }, + /// A JSON array or object has fewer elements than expected /// /// This is a more general version than [`TooShortArray`](UnexpectedStructureKind::TooShortArray) /// and [`MissingObjectMember`](UnexpectedStructureKind::MissingObjectMember) where it is only known /// that more elements are expected without knowing the expected index or member name. FewerElementsThanExpected, + /// A JSON array or object has more elements than expected MoreElementsThanExpected, } @@ -715,6 +718,7 @@ pub enum ReaderError { /// A syntax error was encountered #[error("syntax error: {0}")] SyntaxError(#[from] JsonSyntaxError), + /// The next JSON value had an unexpected type /// /// This error can occur for example when trying to read a JSON number when the next value is actually @@ -728,6 +732,7 @@ pub enum ReaderError { /// Location where the error occurred in the JSON document location: JsonReaderPosition, }, + /// The JSON document had an unexpected structure /// /// This error occurs when trying to consume more elements than a JSON array or object has, or @@ -751,6 +756,18 @@ pub enum ReaderError { /// Location where the error occurred in the JSON document location: JsonReaderPosition, }, + + /// The maximum nesting depth was exceeded while reading + /// + /// See [`ReaderSettings::max_nesting_depth`] for more information. + #[error("maximum nesting depth {max_nesting_depth} exceeded at {location}")] + MaxNestingDepthExceeded { + /// The maximum nesting depth + max_nesting_depth: u32, + /// Location within the JSON document + location: JsonReaderPosition, + }, + /// An unsupported JSON number value was encountered /// /// See [`ReaderSettings::restrict_number_values`] for more information. @@ -761,6 +778,7 @@ pub enum ReaderError { /// Location of the number value within the JSON document location: JsonReaderPosition, }, + /// An IO error occurred while trying to read from the underlying reader, or /// malformed UTF-8 data was encountered #[error("IO error '{error}' at (roughly) {location}")] diff --git a/src/reader/stream_reader.rs b/src/reader/stream_reader.rs index beaa22a..b683e1a 100644 --- a/src/reader/stream_reader.rs +++ b/src/reader/stream_reader.rs @@ -90,22 +90,17 @@ const INITIAL_VALUE_BYTES_BUF_CAPACITY: usize = 128; /// JSON reader will keep retrying to read data. /// /// # Security -/// Besides UTF-8 validation this JSON reader only implements a basic restriction on JSON numbers, -/// see [`ReaderSettings::restrict_number_values`], but does not implement any other security -/// related measures. In particular it does **not**: +/// Besides UTF-8 validation this JSON reader only implements the following basic security features: +/// - restriction on JSON numbers, see [`ReaderSettings::restrict_number_values`] +/// - nesting depth limit, see [`ReaderSettings::max_nesting_depth`] +/// +/// But it does not implement any other security related measures. In particular it does **not**: /// /// - Impose a limit on the length of the document /// /// Especially when the JSON data comes from a compressed data stream (such as gzip) large JSON documents /// could be used for denial of service attacks. /// -/// - Impose a limit on the nesting depth -/// -/// JSON arrays and objects might be arbitrary deeply nested. Trying to process such JSON documents -/// in a recursive way could therefore lead to a stack overflow. While this JSON reader implementation -/// does not use recursive calls, users of this reader must make sure to not use recursive calls -/// either or track and limit the nesting depth. -/// /// - Detect duplicate member names /// /// The JSON specification allows duplicate member names, but does not dictate how to handle @@ -312,6 +307,22 @@ pub struct ReaderSettings { /// for errors. pub track_path: bool, + /// Maximum nesting depth + /// + /// The maximum nesting depth specifies how many nested JSON arrays or objects may + /// be started before returning [`ReaderError::MaxNestingDepthExceeded`]. + /// For example a maximum nesting depth of 2 allows to start one JSON array or object + /// and within that another nested array or object, such as `{"outer": {"inner": 1}}`. + /// Trying to read any further nested JSON array or object inside that will return an error.\ + /// The value `None` means there is no limit. + /// + /// The maximum nesting depth tries to protect against deeply nested JSON data which + /// could lead to a stack overflow during reading, so setting this to `None` or high + /// values should be done with care. While the implementation of [`JsonStreamReader`] + /// does not use recursion and will therefore likely not encounter a stack overflow, + /// users of it are probably going to use recursion in some form. + pub max_nesting_depth: Option, + /// Whether to restrict which JSON number values are supported /// /// The JSON specification does not impose any restrictions on the size or precision of JSON numbers. @@ -320,8 +331,8 @@ pub struct ReaderSettings { /// be exploited for denial of service attacks, especially when they are parsed as arbitrary-precision /// "big integer" / "big decimal". /// - /// When enabled exponent values smaller than -99, larger than 99 (e.g. `5e100`) and numbers whose - /// string representation has more than 100 characters will be rejected and a + /// When this setting is enabled, exponent values smaller than -99, larger than 99 (e.g. `5e100`) + /// and numbers whose string representation has more than 100 characters will be rejected and a /// [`ReaderError::UnsupportedNumberValue`] is returned. Otherwise, when disabled, all JSON /// number values are allowed. /// @@ -331,14 +342,17 @@ pub struct ReaderSettings { pub restrict_number_values: bool, } +const DEFAULT_MAX_NESTING_DEPTH: u32 = 128; // update documentation when changing this value + impl Default for ReaderSettings { /// Creates the default JSON reader settings /// - /// - comments: disallowed - /// - trailing comma: disallowed - /// - multiple top-level values: disallowed - /// - track JSON path: enabled - /// - restrict number values: enabled + /// - [comments](Self::allow_comments): disallowed + /// - [trailing comma](Self::allow_trailing_comma): disallowed + /// - [multiple top-level values](Self::allow_multiple_top_level): disallowed + /// - [track JSON path](Self::track_path): enabled + /// - [max nesting depth](Self::max_nesting_depth): 128 + /// - [restrict number values](Self::restrict_number_values): enabled /// /// These defaults are compliant with the JSON specification. fn default() -> Self { @@ -347,6 +361,7 @@ impl Default for ReaderSettings { allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, + max_nesting_depth: Some(DEFAULT_MAX_NESTING_DEPTH), restrict_number_values: true, } } @@ -900,6 +915,7 @@ impl JsonStreamReader { fn start_expected_value_type( &mut self, expected: ValueType, + check_depth: bool, ) -> Result { if self.expects_member_name { panic!("Incorrect reader usage: Cannot read value when expecting member name"); @@ -909,6 +925,19 @@ impl JsonStreamReader { let peeked = self.map_peeked(peeked_internal)?; return if peeked == expected { + if check_depth { + // Check nesting depth before consuming token, so that error location points + // at token instead of behind it + if let Some(max_nesting_depth) = self.reader_settings.max_nesting_depth { + if self.stack.len() as u32 >= max_nesting_depth { + return Err(ReaderError::MaxNestingDepthExceeded { + max_nesting_depth, + location: self.create_error_location(), + }); + } + } + } + self.consume_peeked(); Ok(peeked_internal) } else { @@ -920,6 +949,19 @@ impl JsonStreamReader { }; } + fn on_container_start( + &mut self, + expected_value_type: ValueType, + stack_value: StackValue, + ) -> Result<(), ReaderError> { + self.start_expected_value_type(expected_value_type, true)?; + + self.stack.push(stack_value); + // The new container is initially empty + self.is_empty = true; + Ok(()) + } + fn on_container_end(&mut self) { self.stack.pop(); if let Some(ref mut json_path) = self.json_path { @@ -1763,7 +1805,7 @@ trait NumberBytesReader: NumberBytesProvider { // TODO: Try to find a cleaner solution without using macro? macro_rules! collect_next_number_bytes { ( |$self:ident| $reader_creator:expr ) => {{ - $self.start_expected_value_type(ValueType::Number)?; + $self.start_expected_value_type(ValueType::Number, false)?; // unwrap() is safe because start_expected_value_type already peeked at first number byte let first_byte = $self.peek_byte()?.unwrap(); @@ -1892,8 +1934,7 @@ impl JsonReader for JsonStreamReader { } fn begin_array(&mut self) -> Result<(), ReaderError> { - self.start_expected_value_type(ValueType::Array)?; - self.stack.push(StackValue::Array); + self.on_container_start(ValueType::Array, StackValue::Array)?; if let Some(ref mut json_path) = self.json_path { json_path.push(JsonPathPiece::ArrayItem(0)); @@ -1901,7 +1942,6 @@ impl JsonReader for JsonStreamReader { // Clear this because it is only relevant for objects; will be restored when entering parent object (if any) again self.expects_member_name = false; - self.is_empty = true; Ok(()) } @@ -1922,8 +1962,7 @@ impl JsonReader for JsonStreamReader { } fn begin_object(&mut self) -> Result<(), ReaderError> { - self.start_expected_value_type(ValueType::Object)?; - self.stack.push(StackValue::Object); + self.on_container_start(ValueType::Object, StackValue::Object)?; if let Some(ref mut json_path) = self.json_path { // Push a placeholder which is replaced once the name of the first member is read @@ -1931,7 +1970,6 @@ impl JsonReader for JsonStreamReader { json_path.push(JsonPathPiece::ObjectMember("".to_owned())); } - self.is_empty = true; self.expects_member_name = true; Ok(()) } @@ -1996,18 +2034,18 @@ impl JsonReader for JsonStreamReader { } fn next_bool(&mut self) -> Result { - let result = Ok(match self.start_expected_value_type(ValueType::Boolean)? { + let value = match self.start_expected_value_type(ValueType::Boolean, false)? { PeekedValue::BooleanTrue => true, PeekedValue::BooleanFalse => false, // Call to start_expected_value_type should have verified type _ => unreachable!("Peeked value is not a boolean"), - }); + }; self.on_value_end(); - result + Ok(value) } fn next_null(&mut self) -> Result<(), ReaderError> { - self.start_expected_value_type(ValueType::Null)?; + self.start_expected_value_type(ValueType::Null, false)?; self.on_value_end(); Ok(()) } @@ -2091,7 +2129,7 @@ impl JsonReader for JsonStreamReader { depth += 1; } ValueType::String => { - self.start_expected_value_type(ValueType::String)?; + self.start_expected_value_type(ValueType::String, false)?; self.skip_all_string_bytes()?; self.on_value_end(); } @@ -2119,21 +2157,21 @@ impl JsonReader for JsonStreamReader { } fn next_string(&mut self) -> Result { - self.start_expected_value_type(ValueType::String)?; + self.start_expected_value_type(ValueType::String, false)?; let result = self.read_string(false)?.get_string(self); self.on_value_end(); Ok(result) } fn next_str(&mut self) -> Result<&str, ReaderError> { - self.start_expected_value_type(ValueType::String)?; + self.start_expected_value_type(ValueType::String, false)?; let str_bytes = self.read_string(true)?; self.on_value_end(); Ok(str_bytes.get_str(self)) } fn next_string_reader(&mut self) -> Result, ReaderError> { - self.start_expected_value_type(ValueType::String)?; + self.start_expected_value_type(ValueType::String, false)?; self.is_string_value_reader_active = true; Ok(Box::new(StringValueReader { json_reader: self, @@ -2235,7 +2273,7 @@ impl JsonReader for JsonStreamReader { depth += 1; } ValueType::String => { - self.start_expected_value_type(ValueType::String)?; + self.start_expected_value_type(ValueType::String, false)?; // Write value in a streaming way using value writer let mut string_writer = json_writer.string_value_writer()?; @@ -3828,6 +3866,130 @@ mod tests { json_reader.end_object().unwrap(); } + fn new_reader_with_limit(json: &str, limit: Option) -> JsonStreamReader<&[u8]> { + JsonStreamReader::new_custom( + json.as_bytes(), + ReaderSettings { + max_nesting_depth: limit, + ..Default::default() + }, + ) + } + + #[test] + fn nesting_limit() -> TestResult { + fn assert_limit_reached( + result: Result, + expected_limit: u32, + expected_column: u64, + expected_path: &JsonPath, + ) { + match result { + Err(ReaderError::MaxNestingDepthExceeded { + max_nesting_depth, + location, + }) => { + assert_eq!(expected_limit, max_nesting_depth); + assert_eq!( + JsonReaderPosition { + path: Some(expected_path.to_vec()), + line_pos: Some(LinePosition { + line: 0, + column: expected_column + }), + // Assume input is ASCII only on single line; treat column as byte pos + data_pos: Some(expected_column), + }, + location + ) + } + r => panic!("unexpected result: {r:?}"), + } + } + + // Test default limit + let depth = DEFAULT_MAX_NESTING_DEPTH; + let json = "[".repeat(depth as usize) + "true]"; + let mut json_reader = new_reader(&json); + for _ in 0..depth { + json_reader.begin_array()?; + } + assert_eq!(true, json_reader.next_bool()?); + + // Test default limit reached + let depth = DEFAULT_MAX_NESTING_DEPTH + 1; + let json = "[".repeat(depth as usize) + "true]"; + let mut json_reader = new_reader(&json); + for _ in 0..DEFAULT_MAX_NESTING_DEPTH { + json_reader.begin_array()?; + } + assert_limit_reached( + json_reader.begin_array(), + DEFAULT_MAX_NESTING_DEPTH, + DEFAULT_MAX_NESTING_DEPTH as u64, + &vec![JsonPathPiece::ArrayItem(0); DEFAULT_MAX_NESTING_DEPTH as usize], + ); + + // Test no limit + let depth = DEFAULT_MAX_NESTING_DEPTH + 10; + let json = "[".repeat(depth as usize) + "true]"; + let mut json_reader = new_reader_with_limit(&json, None); + for _ in 0..depth { + json_reader.begin_array()?; + } + assert_eq!(true, json_reader.next_bool()?); + + let mut json_reader = new_reader_with_limit("[", Some(0)); + assert_limit_reached(json_reader.begin_array(), 0, 0, &json_path![]); + + let mut json_reader = new_reader_with_limit("{", Some(0)); + assert_limit_reached(json_reader.begin_object(), 0, 0, &json_path![]); + + // No limit error should returned on value type mismatch + let mut json_reader = new_reader_with_limit("true", Some(0)); + match json_reader.begin_array() { + Err(ReaderError::UnexpectedValueType { + expected: ValueType::Array, + actual: ValueType::Boolean, + .. + }) => {} + r => panic!("unexpected result: {r:?}"), + } + assert_eq!(true, json_reader.next_bool()?); + + // Mixed array and object + let mut json_reader = new_reader_with_limit("[{", Some(1)); + json_reader.begin_array()?; + assert_limit_reached(json_reader.begin_object(), 1, 1, &json_path![0]); + + let mut json_reader = new_reader_with_limit("{\"a\": [", Some(1)); + json_reader.begin_object()?; + assert_eq!("a", json_reader.next_name()?); + assert_limit_reached(json_reader.begin_array(), 1, 6, &json_path!["a"]); + + // Verify that closing arrays and objects properly decreases the depth again + let mut json_reader = new_reader_with_limit("[[{}], {\"a\": [{}]}", Some(3)); + json_reader.begin_array()?; + json_reader.begin_array()?; + json_reader.begin_object()?; + json_reader.end_object()?; + json_reader.end_array()?; + json_reader.begin_object()?; + assert_eq!("a", json_reader.next_name()?); + json_reader.begin_array()?; + assert_limit_reached(json_reader.begin_object(), 3, 14, &json_path![1, "a", 0]); + + // Currently also affects skipping values + let mut json_reader = new_reader_with_limit("[[", Some(1)); + assert_limit_reached(json_reader.skip_value(), 1, 1, &json_path![0]); + + // Currently also affects `seek_to` + let mut json_reader = new_reader_with_limit("[[", Some(1)); + assert_limit_reached(json_reader.seek_to(&json_path![0, 0]), 1, 1, &json_path![0]); + + Ok(()) + } + #[test] fn skip_array() -> TestResult { let mut json_reader = new_reader( @@ -3858,14 +4020,14 @@ mod tests { fn skip_array_deeply_nested() -> TestResult { let nesting_depth = 20_000; let json = "[".repeat(nesting_depth) + "true" + "]".repeat(nesting_depth).as_str(); - let mut json_reader = new_reader(&json); + let mut json_reader = new_reader_with_limit(&json, None); json_reader.skip_value()?; json_reader.consume_trailing_whitespace()?; // Also test with malformed JSON to verify that deeply nested value is actually reached let json = "[".repeat(nesting_depth) + "@" + "]".repeat(nesting_depth).as_str(); - let mut json_reader = new_reader(&json); + let mut json_reader = new_reader_with_limit(&json, None); assert_parse_error_with_path( None, json_reader.skip_value(), @@ -3903,14 +4065,14 @@ mod tests { let nesting_depth = 20_000; let json_start = r#"{"a":"#; let json = json_start.repeat(nesting_depth) + "true" + "}".repeat(nesting_depth).as_str(); - let mut json_reader = new_reader(&json); + let mut json_reader = new_reader_with_limit(&json, None); json_reader.skip_value()?; json_reader.consume_trailing_whitespace()?; // Also test with malformed JSON to verify that deeply nested value is actually reached let json = json_start.repeat(nesting_depth) + "@" + "}".repeat(nesting_depth).as_str(); - let mut json_reader = new_reader(&json); + let mut json_reader = new_reader_with_limit(&json, None); assert_parse_error_with_path( None, json_reader.skip_value(), @@ -5457,13 +5619,13 @@ mod tests { let json_number = "123"; let mut json_reader = new_with_debuggable_reader(json_number.as_bytes()); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 0, buf_str: \"\", peeked: None, is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: true } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 0, buf_str: \"\", peeked: None, is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: true } }", format!("{json_reader:?}") ); assert_eq!(ValueType::Number, json_reader.peek()?); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 3, buf_str: \"123\", peeked: Some(NumberStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: true } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 3, buf_str: \"123\", peeked: Some(NumberStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: true } }", format!("{json_reader:?}") ); @@ -5485,7 +5647,7 @@ mod tests { assert_eq!(ValueType::Number, json_reader.peek()?); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 600, buf_str: \"123456123456123456123456123456123456123456123...\", peeked: Some(NumberStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: false } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 600, buf_str: \"123456123456123456123456123456123456123456123...\", peeked: Some(NumberStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: false } }", format!("{json_reader:?}") ); @@ -5501,7 +5663,7 @@ mod tests { let mut json_reader = new_with_debuggable_reader(json); assert_eq!(ValueType::String, json_reader.peek()?); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 15, buf_str: \"this is a test...\", ...buf...: [195], peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: true } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 15, buf_str: \"this is a test...\", ...buf...: [195], peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: true } }", format!("{json_reader:?}") ); Ok(()) @@ -5514,7 +5676,7 @@ mod tests { let mut json_reader = new_with_debuggable_reader(json); assert_eq!(ValueType::String, json_reader.peek()?); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 2, buf_str: \"a...\", ...buf...: [255], peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: true } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 2, buf_str: \"a...\", ...buf...: [255], peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: true } }", format!("{json_reader:?}") ); Ok(()) @@ -5530,7 +5692,7 @@ mod tests { let mut json_reader = new_with_debuggable_reader(json.as_slice()); assert_eq!(ValueType::String, json_reader.peek()?); assert_eq!( - "JsonStreamReader { reader: debuggable-reader, buf_count: 121, buf_str: \"abcdefabcdefabcdefabcdefabcdefabcdefabcdefabc...\", peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, restrict_number_values: true } }", + "JsonStreamReader { reader: debuggable-reader, buf_count: 121, buf_str: \"abcdefabcdefabcdefabcdefabcdefabcdefabcdefabc...\", peeked: Some(StringStart), is_empty: true, expects_member_name: false, stack: [], is_string_value_reader_active: false, line: 0, column: 0, byte_pos: 0, json_path: Some([]), reader_settings: ReaderSettings { allow_comments: false, allow_trailing_comma: false, allow_multiple_top_level: false, track_path: true, max_nesting_depth: Some(128), restrict_number_values: true } }", format!("{json_reader:?}") ); Ok(()) diff --git a/src/serde/de.rs b/src/serde/de.rs index 6461e4b..a821ffc 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -84,7 +84,7 @@ impl From for DeserializerError { /// information on its security. /// /// This deserializer performs UTF-8 validation (implicitly through its usage of `JsonReader`) -/// and implements a [maximum nesting depth](JsonReaderDeserializer::new_with_custom_nesting_limit), +/// and implements a [nesting depth limit](JsonReaderDeserializer::new_with_custom_nesting_limit), /// but besides that no additional security measures are implemented. /// In particular it does **not**: /// @@ -111,6 +111,9 @@ impl From for DeserializerError { /// using the parsed numbers, especially `i128`, `u128`, `f32` and `f64` values, since they might be /// extremely large. /// +/// When using [`JsonStreamReader`](crate::reader::JsonStreamReader) as underlying reader, JSON numbers +/// can be restricted using [`ReaderSettings::restrict_number_values`](crate::reader::ReaderSettings::restrict_number_values). +/// /// - Impose restrictions on content of member names and string values /// /// The only restriction is that member names and string values are valid UTF-8 strings, besides @@ -177,7 +180,7 @@ pub struct JsonReaderDeserializer<'a, R: JsonReader> { depth: u32, } -const DEFAULT_MAX_NESTING_DEPTH: u32 = 128; +const DEFAULT_MAX_NESTING_DEPTH: u32 = 128; // update documentation when changing this value impl<'a, R: JsonReader> JsonReaderDeserializer<'a, R> { /// Creates a deserializer wrapping a [`JsonReader`] @@ -206,12 +209,19 @@ impl<'a, R: JsonReader> JsonReaderDeserializer<'a, R> { /// deserializer is allowed to start before returning [`DeserializerError::MaxNestingDepthExceeded`]. /// For example a maximum nesting depth of 2 allows the deserializer to start one JSON /// array or object and within that another nested array or object, such as `{"outer": {"inner": 1}}`. - /// Trying to deserialize any further nested JSON array or object will return an error. + /// Trying to deserialize any further nested JSON array or object inside that will return + /// an error. + /// /// The maximum depth does not consider how many JSON arrays or objects the provided - /// [`JsonReader`] has already started before being using by this deserializer. + /// [`JsonReader`] has already started before being using by this deserializer. It is + /// also independent from [`ReaderSettings::max_nesting_depth`](crate::reader::ReaderSettings::max_nesting_depth) + /// of the `JsonReader`. This allows for example to disable the limit on the `JsonReader` + /// to seek to a deeply nested value, and then use this deserializer (with a nesting limit) + /// for deserializing the value. /// /// The maximum nesting depth tries to protect against deeply nested JSON data which - /// could lead to a stack overflow, so high maximum depth values should be used with care. + /// could lead to a stack overflow during deserialization, so high maximum depth values + /// should be used with care.\ /// However, the maximum depth cannot protect against a stack overflow caused by an /// error-prone [`Deserialize`] or [`Visitor`] implementation which recursively calls /// deserializer methods without actually consuming any value, for example continuously