use rhai::{Engine, EvalAltResult};
use crate::error::TraverserError;
pub const LIMIT_MAX_OPERATIONS: &str = "script.max_operations";
pub const LIMIT_MAX_STRING_SIZE: &str = "script.max_string_size";
pub const LIMIT_MAX_ARRAY_SIZE: &str = "script.max_array_size";
pub const LIMIT_MAX_MAP_SIZE: &str = "script.max_map_size";
pub const LIMIT_MAX_CALL_LEVELS: &str = "script.max_call_levels";
pub const LIMIT_MAX_VALUE_DEPTH: &str = "script.max_value_depth";
pub const DEFAULT_MAX_OPERATIONS: u64 = 10_000_000;
pub const DEFAULT_MAX_STRING_SIZE: usize = 16 * 1024 * 1024;
pub const DEFAULT_MAX_ARRAY_SIZE: usize = 100_000;
pub const DEFAULT_MAX_MAP_SIZE: usize = 100_000;
pub const DEFAULT_MAX_CALL_LEVELS: usize = 32;
pub const DEFAULT_MAX_VALUE_DEPTH: usize = crate::value::depth::DEFAULT_MAX_VALUE_DEPTH;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScriptLimits {
pub max_operations: u64,
pub max_string_size: usize,
pub max_array_size: usize,
pub max_map_size: usize,
pub max_call_levels: usize,
pub max_value_depth: usize,
}
impl Default for ScriptLimits {
fn default() -> Self {
Self {
max_operations: DEFAULT_MAX_OPERATIONS,
max_string_size: DEFAULT_MAX_STRING_SIZE,
max_array_size: DEFAULT_MAX_ARRAY_SIZE,
max_map_size: DEFAULT_MAX_MAP_SIZE,
max_call_levels: DEFAULT_MAX_CALL_LEVELS,
max_value_depth: DEFAULT_MAX_VALUE_DEPTH,
}
}
}
impl ScriptLimits {
pub fn unlimited() -> Self {
Self {
max_operations: 0,
max_string_size: 0,
max_array_size: 0,
max_map_size: 0,
max_call_levels: DEFAULT_MAX_CALL_LEVELS,
max_value_depth: DEFAULT_MAX_VALUE_DEPTH,
}
}
pub fn with_max_operations(mut self, max: u64) -> Self {
self.max_operations = max;
self
}
pub fn with_max_string_size(mut self, max: usize) -> Self {
self.max_string_size = max;
self
}
pub fn with_max_array_size(mut self, max: usize) -> Self {
self.max_array_size = max;
self
}
pub fn with_max_map_size(mut self, max: usize) -> Self {
self.max_map_size = max;
self
}
pub fn with_max_call_levels(mut self, max: usize) -> Self {
self.max_call_levels = max;
self
}
pub fn with_max_value_depth(mut self, max: usize) -> Self {
self.max_value_depth = max;
self
}
pub(crate) fn effective_max_value_depth(&self) -> usize {
if self.max_value_depth == 0 {
DEFAULT_MAX_VALUE_DEPTH
} else {
self.max_value_depth
}
}
pub fn apply(&self, engine: &mut Engine) {
engine.set_max_operations(self.max_operations);
engine.set_max_string_size(self.max_string_size);
engine.set_max_array_size(self.max_array_size);
engine.set_max_map_size(self.max_map_size);
let levels = if self.max_call_levels == 0 {
DEFAULT_MAX_CALL_LEVELS
} else {
self.max_call_levels
};
engine.set_max_call_levels(levels);
}
}
pub fn resource_limit_from_rhai(
err: &EvalAltResult,
limits: &ScriptLimits,
) -> Option<TraverserError> {
let (limit, max) = match err {
EvalAltResult::ErrorInFunctionCall(_, _, inner, _)
| EvalAltResult::ErrorInModule(_, inner, _) => {
return resource_limit_from_rhai(inner, limits);
}
EvalAltResult::ErrorTooManyOperations(_) => (LIMIT_MAX_OPERATIONS, limits.max_operations),
EvalAltResult::ErrorStackOverflow(_) => {
(LIMIT_MAX_CALL_LEVELS, limits.max_call_levels as u64)
}
EvalAltResult::ErrorDataTooLarge(what, _) => {
let what = what.to_ascii_lowercase();
if what.contains("string") {
(LIMIT_MAX_STRING_SIZE, limits.max_string_size as u64)
} else if what.contains("array") || what.contains("blob") {
(LIMIT_MAX_ARRAY_SIZE, limits.max_array_size as u64)
} else if what.contains("map") {
(LIMIT_MAX_MAP_SIZE, limits.max_map_size as u64)
} else {
return None;
}
}
_ => return None,
};
Some(TraverserError::ResourceLimitExceeded {
limit: limit.to_string(),
max: (max > 0).then_some(max),
observed: None,
})
}