///|
/// Resource limits for untrusted JSON documents. Limits are checked after
/// strict parsing, before a caller stores or hashes a potentially huge tree.
pub(all) struct DocumentLimits {
mut max_depth : Int
mut max_values : Int
mut max_array_length : Int
mut max_object_members : Int
mut max_string_length : Int
mut max_number_length : Int
}
///|
pub enum LimitViolation {
DepthExceeded(String, Int)
ValuesExceeded(String, Int)
ArrayTooLong(String, Int)
ObjectTooLarge(String, Int)
StringTooLong(String, Int)
NumberTooLong(String, Int)
}
///|
pub fn DocumentLimits::default() -> DocumentLimits {
{
max_depth: 256,
max_values: 100000,
max_array_length: 10000,
max_object_members: 10000,
max_string_length: 1000000,
max_number_length: 128,
}
}
///|
pub fn LimitViolation::message(self : LimitViolation) -> String {
match self {
DepthExceeded(path, limit) =>
"maximum depth " + limit.to_string() + " exceeded at " + path
ValuesExceeded(path, limit) =>
"maximum value count " + limit.to_string() + " exceeded at " + path
ArrayTooLong(path, limit) =>
"maximum array length " + limit.to_string() + " exceeded at " + path
ObjectTooLarge(path, limit) =>
"maximum object member count " +
limit.to_string() +
" exceeded at " +
path
StringTooLong(path, limit) =>
"maximum string length " + limit.to_string() + " exceeded at " + path
NumberTooLong(path, limit) =>
"maximum number token length " +
limit.to_string() +
" exceeded at " +
path
}
}
///|
/// Return the first limit violation in deterministic document order.
pub fn check_limits(
value : JsonValue,
limits : DocumentLimits,
) -> LimitViolation? {
let state = JsonStats::empty()
check_value_limits(value, "$", 0, limits, state)
}
///|
/// Parse a document and enforce resource limits before returning its tree.
pub fn parse_with_limits(
input : String,
limits : DocumentLimits,
) -> Result[JsonValue, String] {
let value = match parse(input) {
Ok(value) => value
Err(error) => return Err(error.message())
}
match check_limits(value, limits) {
Some(violation) => Err(violation.message())
None => Ok(value)
}
}
///|
fn check_value_limits(
value : JsonValue,
path : String,
depth : Int,
limits : DocumentLimits,
state : JsonStats,
) -> LimitViolation? {
state.value_count = state.value_count + 1
if depth > limits.max_depth {
return Some(DepthExceeded(path, limits.max_depth))
}
if state.value_count > limits.max_values {
return Some(ValuesExceeded(path, limits.max_values))
}
match value {
Null | Bool(_) => None
Number(number) =>
if number.length() > limits.max_number_length {
Some(NumberTooLong(path, limits.max_number_length))
} else {
None
}
String(string) =>
if string.length() > limits.max_string_length {
Some(StringTooLong(path, limits.max_string_length))
} else {
None
}
Array(values) => {
if values.length() > limits.max_array_length {
return Some(ArrayTooLong(path, limits.max_array_length))
}
let mut index = 0
while index < values.length() {
match
check_value_limits(
values[index],
path + "/" + index.to_string(),
depth + 1,
limits,
state,
) {
Some(violation) => return Some(violation)
None => ()
}
index = index + 1
}
None
}
Object(entries) => {
if entries.length() > limits.max_object_members {
return Some(ObjectTooLarge(path, limits.max_object_members))
}
for entry in entries {
match
check_value_limits(
entry.1,
path + "/" + entry.0,
depth + 1,
limits,
state,
) {
Some(violation) => return Some(violation)
None => ()
}
}
None
}
}
}