1use ferrin_spec::error::JsonParseError;
4use serde_json::Value;
5
6use crate::error::SchemaError;
7use crate::schema::Schema;
8
9pub const DEFAULT_MAX_DEPTH: usize = 128;
11
12pub const DEFAULT_MAX_BYTES: usize = 64 * 1024 * 1024;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ParseLimits {
18 pub max_depth: usize,
20 pub max_bytes: usize,
22}
23
24impl Default for ParseLimits {
25 fn default() -> Self {
26 Self {
27 max_depth: DEFAULT_MAX_DEPTH,
28 max_bytes: DEFAULT_MAX_BYTES,
29 }
30 }
31}
32
33#[derive(Debug, thiserror::Error)]
34enum LimitError {
35 #[error("input of {len} bytes exceeds the limit of {max} bytes")]
36 TooLarge { len: usize, max: usize },
37 #[error("nesting depth exceeds the limit of {max}")]
38 TooDeep { max: usize },
39 #[error("object contains forbidden prototype property")]
40 PrototypeProperty,
41}
42
43pub fn parse(text: &str) -> Result<Value, JsonParseError> {
50 parse_with(text, ParseLimits::default())
51}
52
53pub fn parse_with(text: &str, limits: ParseLimits) -> Result<Value, JsonParseError> {
60 if text.len() > limits.max_bytes {
61 return Err(JsonParseError::new(
62 truncate(text),
63 LimitError::TooLarge {
64 len: text.len(),
65 max: limits.max_bytes,
66 },
67 ));
68 }
69 let value: Value =
70 serde_json::from_str(text).map_err(|error| JsonParseError::new(truncate(text), error))?;
71 if depth(&value) > limits.max_depth {
72 return Err(JsonParseError::new(
73 truncate(text),
74 LimitError::TooDeep {
75 max: limits.max_depth,
76 },
77 ));
78 }
79 check_object_keys(&value).map_err(|error| JsonParseError::new(truncate(text), error))?;
80 Ok(value)
81}
82
83fn check_object_keys(value: &Value) -> Result<(), LimitError> {
84 let mut pending = vec![value];
85 while let Some(value) = pending.pop() {
86 match value {
87 Value::Object(fields) => {
88 if fields.contains_key("__proto__")
89 || fields
90 .get("constructor")
91 .and_then(Value::as_object)
92 .is_some_and(|constructor| constructor.contains_key("prototype"))
93 {
94 return Err(LimitError::PrototypeProperty);
95 }
96 pending.extend(fields.values());
97 }
98 Value::Array(values) => pending.extend(values),
99 _ => {}
100 }
101 }
102 Ok(())
103}
104
105pub fn parse_with_schema<T>(text: &str, schema: &Schema<T>) -> Result<T, SchemaError> {
112 let value = parse(text)?;
113 Ok(schema.validate(value)?)
114}
115
116#[must_use]
118pub fn is_parsable(text: &str) -> bool {
119 parse(text).is_ok()
120}
121
122#[must_use]
124pub fn depth(value: &Value) -> usize {
125 let mut max = 0;
126 let mut stack: Vec<(&Value, usize)> = vec![(value, 0)];
127 while let Some((current, level)) = stack.pop() {
128 match current {
129 Value::Array(items) => {
130 max = max.max(level + 1);
131 stack.extend(items.iter().map(|item| (item, level + 1)));
132 }
133 Value::Object(map) => {
134 max = max.max(level + 1);
135 stack.extend(map.values().map(|item| (item, level + 1)));
136 }
137 _ => max = max.max(level),
138 }
139 }
140 max
141}
142
143fn truncate(text: &str) -> String {
145 const MAX: usize = 4096;
146 if text.len() <= MAX {
147 return text.to_owned();
148 }
149 let mut end = MAX;
150 while !text.is_char_boundary(end) {
151 end -= 1;
152 }
153 format!("{}...", &text[..end])
154}