mod common;
use secure_json_parse::{parse, Action, Error, Options};
use serde_json::json;
#[test]
fn top_level_array_with_proto_is_walked() {
let text = r#"[{"__proto__": {"x": 1}}, {"a": 2}]"#;
let opts = Options::default().proto_action(Action::Remove);
let v = parse(text, &opts).unwrap().unwrap();
assert_eq!(v, json!([{}, {"a": 2}]));
assert!(matches!(
parse(text, &Options::default()),
Err(Error::ForbiddenProperty)
));
}
#[test]
fn array_nested_in_object_constructor_removed() {
let text = r#"{"list": [{"constructor": {"prototype": {}}}]}"#;
let opts = Options::default().constructor_action(Action::Remove);
let v = parse(text, &opts).unwrap().unwrap();
assert_eq!(v, json!({"list": [{}]}));
}
#[test]
fn duplicate_proto_keys_still_detected() {
let text = r#"{"__proto__": {"x": 1}, "__proto__": {"y": 2}}"#;
assert!(matches!(
parse(text, &Options::default()),
Err(Error::ForbiddenProperty)
));
let opts = Options::default().proto_action(Action::Remove);
let v = parse(text, &opts).unwrap().unwrap();
assert_eq!(v, json!({}));
}
#[test]
fn lookalike_keys_do_not_trip() {
let inputs = [
r#"{"proto": {"x": 7}}"#,
r#"{"__proto": {"x": 7}}"#,
r#"{"constructorr": {"prototype": {}}}"#,
r#"{"constructo": {"prototype": {}}}"#,
r#"{"_proto_": {"x": 7}}"#,
];
for text in inputs {
let v = parse(text, &Options::default());
assert!(v.is_ok(), "expected ok for {text:?}");
}
}
#[test]
fn benchmark_lookalike_input_is_clean() {
let text = r#"{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto": { "y": 8 }, "f": { "g": 2 } } }"#;
assert!(parse(text, &Options::default()).is_ok());
}
fn nested(depth: usize, leaf: &str) -> String {
let mut text = String::new();
for _ in 0..depth {
text.push_str(r#"{"a":"#);
}
text.push_str(leaf);
for _ in 0..depth {
text.push('}');
}
text
}
#[test]
fn walk_reaches_the_deepest_accepted_input() {
let deepest = nested(125, r#"{"__proto__": {"x": 1}}"#);
assert!(matches!(
parse(&deepest, &Options::default()),
Err(Error::ForbiddenProperty)
));
}
#[test]
fn parser_rejects_one_past_the_ceiling() {
let past = nested(128, "1");
assert!(matches!(
parse(&past, &Options::default()),
Err(Error::Syntax(_))
));
}
#[test]
fn safe_parse_on_primitive_is_value() {
use secure_json_parse::{safe_parse, SafeOutcome};
assert!(matches!(safe_parse("0"), SafeOutcome::Value(_)));
assert!(matches!(safe_parse("null"), SafeOutcome::Value(_)));
assert!(matches!(safe_parse(r#""X""#), SafeOutcome::Value(_)));
}