use crate::{Action, Error, Options};
use serde_json::Value;
pub fn scan(mut value: Value, options: &Options) -> Result<Option<Value>, Error> {
if options.proto_action == Action::Ignore && options.constructor_action == Action::Ignore {
return Ok(Some(value));
}
match walk(&mut value, options) {
Walk::Clean => Ok(Some(value)),
Walk::Null => {
drain(value);
Ok(None)
}
Walk::Error => {
drain(value);
Err(Error::ForbiddenProperty)
}
}
}
fn drain(value: Value) {
let mut stack = vec![value];
while let Some(node) = stack.pop() {
match node {
Value::Array(items) => stack.extend(items),
Value::Object(map) => stack.extend(map.into_iter().map(|(_, v)| v)),
_ => {}
}
}
}
enum Walk {
Clean,
Null,
Error,
}
fn walk(root: &mut Value, options: &Options) -> Walk {
let mut worklist: Vec<&mut Value> = vec![root];
while let Some(node) = worklist.pop() {
match node {
Value::Object(map) => {
if options.proto_action != Action::Ignore && map.contains_key("__proto__") {
if options.safe {
return Walk::Null;
}
if options.proto_action == Action::Error {
return Walk::Error;
}
if let Some(removed) = map.remove("__proto__") {
drain(removed);
}
}
if options.constructor_action != Action::Ignore && is_constructor_violation(map) {
if options.safe {
return Walk::Null;
}
if options.constructor_action == Action::Error {
return Walk::Error;
}
if let Some(removed) = map.remove("constructor") {
drain(removed);
}
}
worklist.extend(map.values_mut());
}
Value::Array(items) => worklist.extend(items.iter_mut()),
_ => {}
}
}
Walk::Clean
}
fn is_constructor_violation(map: &serde_json::Map<String, Value>) -> bool {
match map.get("constructor") {
Some(Value::Object(inner)) => inner.contains_key("prototype"),
_ => false,
}
}