use rskit_errors::{AppError, AppResult, ErrorCode};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidationLimits {
pub max_depth: usize,
pub max_nodes: usize,
pub max_string_bytes: usize,
pub max_key_bytes: usize,
pub max_total_string_bytes: usize,
}
impl Default for ValidationLimits {
fn default() -> Self {
Self {
max_depth: 128,
max_nodes: 100_000,
max_string_bytes: 1_048_576,
max_key_bytes: 16_384,
max_total_string_bytes: 16_777_216,
}
}
}
impl ValidationLimits {
#[must_use]
pub const fn new(max_depth: usize, max_nodes: usize) -> Self {
Self {
max_depth,
max_nodes,
max_string_bytes: 1_048_576,
max_key_bytes: 16_384,
max_total_string_bytes: 16_777_216,
}
}
#[must_use]
pub const fn with_max_string_bytes(mut self, max_string_bytes: usize) -> Self {
self.max_string_bytes = max_string_bytes;
self
}
#[must_use]
pub const fn with_max_key_bytes(mut self, max_key_bytes: usize) -> Self {
self.max_key_bytes = max_key_bytes;
self
}
#[must_use]
pub const fn with_max_total_string_bytes(mut self, max_total_string_bytes: usize) -> Self {
self.max_total_string_bytes = max_total_string_bytes;
self
}
}
pub(crate) fn check_json_limits(
name: &str,
value: &Value,
limits: ValidationLimits,
) -> AppResult<()> {
let mut node_count = 0usize;
let mut total_string_bytes = 0usize;
let mut stack = vec![(value, 1usize)];
while let Some((node, depth)) = stack.pop() {
node_count = node_count.saturating_add(1);
if node_count > limits.max_nodes {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!(
"{name} exceeds maximum JSON node count {}",
limits.max_nodes
),
));
}
if depth > limits.max_depth {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!("{name} exceeds maximum JSON depth {}", limits.max_depth),
));
}
match node {
Value::Array(values) => {
stack.extend(values.iter().map(|value| (value, depth + 1)));
}
Value::Object(values) => {
for key in values.keys() {
check_string_bytes(name, "object key", key.len(), limits)?;
total_string_bytes = add_string_bytes(
name,
total_string_bytes,
key.len(),
limits.max_total_string_bytes,
)?;
}
stack.extend(values.values().map(|value| (value, depth + 1)));
}
Value::String(value) => {
check_string_bytes(name, "string", value.len(), limits)?;
total_string_bytes = add_string_bytes(
name,
total_string_bytes,
value.len(),
limits.max_total_string_bytes,
)?;
}
Value::Null | Value::Bool(_) | Value::Number(_) => {}
}
}
Ok(())
}
fn check_string_bytes(
name: &str,
kind: &str,
len: usize,
limits: ValidationLimits,
) -> AppResult<()> {
let max = if kind == "object key" {
limits.max_key_bytes
} else {
limits.max_string_bytes
};
if len > max {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!("{name} {kind} exceeds maximum UTF-8 byte length {max}"),
));
}
Ok(())
}
fn add_string_bytes(
name: &str,
current: usize,
additional: usize,
max_total: usize,
) -> AppResult<usize> {
let total = current.saturating_add(additional);
if total > max_total {
return Err(AppError::new(
ErrorCode::InvalidInput,
format!("{name} exceeds maximum cumulative string byte length {max_total}"),
));
}
Ok(total)
}