amont_runtime/
json_read.rs1#[derive(Debug, Clone, PartialEq)]
12pub enum Value {
13 Null,
14 Bool(bool),
15 Num(String),
16 Str(String),
17 Arr(Vec<Value>),
18 Obj(Vec<(String, Value)>),
19}
20
21const MAX_DEPTH: usize = 256;
22
23impl Value {
24 pub fn get(&self, key: &str) -> Option<&Value> {
26 match self {
27 Value::Obj(members) => members.iter().find(|(k, _)| k == key).map(|(_, v)| v),
28 _ => None,
29 }
30 }
31
32 pub fn as_str(&self) -> Option<&str> {
33 match self {
34 Value::Str(s) => Some(s),
35 _ => None,
36 }
37 }
38
39 pub fn is_true(&self) -> bool {
40 matches!(self, Value::Bool(true))
41 }
42
43 pub fn members(&self) -> &[(String, Value)] {
44 match self {
45 Value::Obj(m) => m,
46 _ => &[],
47 }
48 }
49
50 pub fn items(&self) -> &[Value] {
51 match self {
52 Value::Arr(a) => a,
53 _ => &[],
54 }
55 }
56}
57
58pub fn parse(text: &str) -> Option<Value> {
59 let mut p = Parser {
60 s: text.as_bytes(),
61 i: 0,
62 };
63 let v = p.value(0)?;
64 p.ws();
65 (p.i == p.s.len()).then_some(v)
66}
67
68struct Parser<'a> {
69 s: &'a [u8],
70 i: usize,
71}
72
73impl Parser<'_> {
74 fn ws(&mut self) {
75 while self.i < self.s.len() && matches!(self.s[self.i], b' ' | b'\t' | b'\n' | b'\r') {
76 self.i += 1;
77 }
78 }
79
80 fn eat(&mut self, lit: &[u8]) -> Option<()> {
81 if self.s.get(self.i..self.i + lit.len())? == lit {
82 self.i += lit.len();
83 Some(())
84 } else {
85 None
86 }
87 }
88
89 fn value(&mut self, depth: usize) -> Option<Value> {
90 if depth > MAX_DEPTH {
91 return None;
92 }
93 self.ws();
94 match *self.s.get(self.i)? {
95 b'{' => self.object(depth),
96 b'[' => self.array(depth),
97 b'"' => self.string().map(Value::Str),
98 b't' => self.eat(b"true").map(|_| Value::Bool(true)),
99 b'f' => self.eat(b"false").map(|_| Value::Bool(false)),
100 b'n' => self.eat(b"null").map(|_| Value::Null),
101 b'-' | b'0'..=b'9' => self.number(),
102 _ => None,
103 }
104 }
105
106 fn object(&mut self, depth: usize) -> Option<Value> {
107 self.i += 1;
108 let mut members = Vec::new();
109 self.ws();
110 if self.s.get(self.i) == Some(&b'}') {
111 self.i += 1;
112 return Some(Value::Obj(members));
113 }
114 loop {
115 self.ws();
116 let key = self.string()?;
117 self.ws();
118 self.eat(b":")?;
119 let v = self.value(depth + 1)?;
120 members.push((key, v));
121 self.ws();
122 match *self.s.get(self.i)? {
123 b',' => self.i += 1,
124 b'}' => {
125 self.i += 1;
126 return Some(Value::Obj(members));
127 }
128 _ => return None,
129 }
130 }
131 }
132
133 fn array(&mut self, depth: usize) -> Option<Value> {
134 self.i += 1;
135 let mut items = Vec::new();
136 self.ws();
137 if self.s.get(self.i) == Some(&b']') {
138 self.i += 1;
139 return Some(Value::Arr(items));
140 }
141 loop {
142 items.push(self.value(depth + 1)?);
143 self.ws();
144 match *self.s.get(self.i)? {
145 b',' => self.i += 1,
146 b']' => {
147 self.i += 1;
148 return Some(Value::Arr(items));
149 }
150 _ => return None,
151 }
152 }
153 }
154
155 fn number(&mut self) -> Option<Value> {
156 let start = self.i;
157 while self.i < self.s.len()
158 && matches!(
159 self.s[self.i],
160 b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9'
161 )
162 {
163 self.i += 1;
164 }
165 let text = std::str::from_utf8(&self.s[start..self.i]).ok()?;
166 text.parse::<f64>().ok()?;
167 Some(Value::Num(text.to_string()))
168 }
169
170 fn string(&mut self) -> Option<String> {
171 if self.s.get(self.i) != Some(&b'"') {
172 return None;
173 }
174 self.i += 1;
175 let mut out: Vec<u8> = Vec::new();
176 loop {
177 let c = *self.s.get(self.i)?;
178 self.i += 1;
179 match c {
180 b'"' => return String::from_utf8(out).ok(),
181 b'\\' => {
182 let e = *self.s.get(self.i)?;
183 self.i += 1;
184 match e {
185 b'"' => out.push(b'"'),
186 b'\\' => out.push(b'\\'),
187 b'/' => out.push(b'/'),
188 b'b' => out.push(8),
189 b'f' => out.push(12),
190 b'n' => out.push(b'\n'),
191 b'r' => out.push(b'\r'),
192 b't' => out.push(b'\t'),
193 b'u' => {
194 let hex = std::str::from_utf8(self.s.get(self.i..self.i + 4)?).ok()?;
195 self.i += 4;
196 let mut code = u32::from_str_radix(hex, 16).ok()?;
197 if (0xD800..0xDC00).contains(&code) {
199 self.eat(b"\\u")?;
200 let lo =
201 std::str::from_utf8(self.s.get(self.i..self.i + 4)?).ok()?;
202 self.i += 4;
203 let lo = u32::from_str_radix(lo, 16).ok()?;
204 code =
205 0x10000 + ((code - 0xD800) << 10) + (lo.checked_sub(0xDC00)?);
206 }
207 let ch = char::from_u32(code)?;
208 let mut buf = [0u8; 4];
209 out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
210 }
211 _ => return None,
212 }
213 }
214 c if c < 0x20 => return None,
215 c => out.push(c),
216 }
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn json_read_parses_a_lockfile_shape() {
227 let v = parse(
228 r#"{"name":"x","lockfileVersion":3,"packages":{"":{"name":"x"},
229 "node_modules/a":{"version":"1.0.0","integrity":"sha512-AA==","dev":true},
230 "node_modules/@esbuild/darwin-arm64":{"version":"0.2.0","optional":true,"os":["darwin"],"cpu":["arm64"]}}}"#,
231 )
232 .expect("parses");
233 let pk = v.get("packages").unwrap();
234 assert_eq!(pk.members().len(), 3);
235 let a = pk.get("node_modules/a").unwrap();
236 assert_eq!(a.get("version").and_then(Value::as_str), Some("1.0.0"));
237 assert!(a.get("dev").unwrap().is_true());
238 let e = pk.get("node_modules/@esbuild/darwin-arm64").unwrap();
239 assert_eq!(e.get("os").unwrap().items()[0].as_str(), Some("darwin"));
240 }
241
242 #[test]
243 fn json_read_escapes_and_unicode() {
244 let v = parse(r#"["a\"b\\c\n", "é😀", -1.5e3, null, false]"#).unwrap();
245 let items = v.items();
246 assert_eq!(items[0].as_str(), Some("a\"b\\c\n"));
247 assert_eq!(items[1].as_str(), Some("é😀"));
248 assert_eq!(items[2], Value::Num("-1.5e3".into()));
249 assert_eq!(items[3], Value::Null);
250 }
251
252 #[test]
253 fn json_read_refuses_garbage_and_depth_bombs() {
254 assert!(parse("{\"a\":}").is_none());
255 assert!(parse("[1,2] trailing").is_none());
256 assert!(parse(&"[".repeat(10_000)).is_none());
257 }
258}