use std::collections::BTreeMap;
use yaml_rust2::Event;
use yaml_rust2::parser::{MarkedEventReceiver, Parser};
use yaml_rust2::scanner::Marker;
pub const MAX_TOML_NESTING_DEPTH: usize = 64;
pub fn check_toml_nesting_depth(content: &str, max_depth: usize) -> std::result::Result<(), usize> {
let bytes = content.as_bytes();
let len = bytes.len();
let mut depth: usize = 0;
let mut i = 0;
let mut bracket_stack: Vec<u8> = Vec::new();
let mut dot_frames: Vec<usize> = vec![0];
let mut in_key = true;
while i < len {
match bytes[i] {
b'#' => {
while i < len && bytes[i] != b'\n' {
i += 1;
}
}
quote @ (b'"' | b'\'') => {
let is_multiline =
bytes.get(i + 1) == Some("e) && bytes.get(i + 2) == Some("e);
i = if is_multiline {
skip_multiline_string(bytes, i + 3, quote)
} else {
skip_single_line_string(bytes, i + 1, quote)
};
}
b'{' => {
depth += 1;
if depth > max_depth {
return Err(depth);
}
bracket_stack.push(b'{');
dot_frames.push(0);
in_key = true;
i += 1;
}
b'[' => {
depth += 1;
if depth > max_depth {
return Err(depth);
}
bracket_stack.push(b'[');
i += 1;
}
b'}' => {
if dot_frames.len() > 1 {
depth = depth.saturating_sub(dot_frames.pop().unwrap_or(0));
}
depth = depth.saturating_sub(1);
bracket_stack.pop();
in_key = false;
i += 1;
}
b']' => {
depth = depth.saturating_sub(1);
bracket_stack.pop();
i += 1;
}
b'.' if in_key => {
depth += 1;
if let Some(top) = dot_frames.last_mut() {
*top += 1;
}
if depth > max_depth {
return Err(depth);
}
i += 1;
}
b'=' if in_key => {
in_key = false;
i += 1;
}
b',' => {
match bracket_stack.last() {
Some(b'{') => {
if dot_frames.len() > 1 {
depth = depth.saturating_sub(dot_frames.pop().unwrap_or(0));
}
dot_frames.push(0);
in_key = true;
}
Some(b'[') => in_key = false,
_ => {}
}
i += 1;
}
b'\n' => {
if bracket_stack.is_empty() {
depth = depth.saturating_sub(dot_frames[0]);
dot_frames[0] = 0;
in_key = true;
}
i += 1;
}
_ => i += 1,
}
}
Ok(())
}
fn skip_single_line_string(bytes: &[u8], mut i: usize, quote: u8) -> usize {
let len = bytes.len();
while i < len {
if bytes[i] == b'\\' && quote == b'"' {
i += 2;
continue;
}
if bytes[i] == quote {
return i + 1;
}
i += 1;
}
i
}
fn skip_multiline_string(bytes: &[u8], mut i: usize, quote: u8) -> usize {
let len = bytes.len();
while i < len {
if bytes[i] == b'\\' && quote == b'"' {
i += 2;
continue;
}
if bytes[i] == quote {
let run_start = i;
while i < len && bytes[i] == quote {
i += 1;
}
if i - run_start >= 3 {
return i;
}
continue;
}
i += 1;
}
i
}
pub const MAX_YAML_NESTING_DEPTH: usize = 64;
pub fn check_yaml_nesting_depth(content: &str, max_depth: usize) -> std::result::Result<(), usize> {
let bytes = content.as_bytes();
let len = bytes.len();
let mut depth: usize = 0;
let mut indent_stack: Vec<usize> = Vec::new();
let mut bracket_stack: Vec<u8> = Vec::new();
let mut i = 0;
while i < len {
let mut indent = 0;
while i < len && bytes[i] == b' ' {
indent += 1;
i += 1;
}
if i >= len || bytes[i] == b'\n' || bytes[i] == b'#' {
i = skip_to_eol(bytes, i);
if i < len && bytes[i] == b'\n' {
i += 1;
}
continue;
}
while indent_stack.last().is_some_and(|&top| top > indent) {
indent_stack.pop();
depth = depth.saturating_sub(1);
}
let mut col = indent;
while i < len && bytes[i] == b'-' && (i + 1 == len || matches!(bytes[i + 1], b' ' | b'\n'))
{
if indent_stack.last() != Some(&col) {
depth += 1;
if depth > max_depth {
return Err(depth);
}
indent_stack.push(col);
}
i += 1;
col += 1;
while i < len && bytes[i] == b' ' {
i += 1;
col += 1;
}
}
if i < len && !matches!(bytes[i], b'\n' | b'#') && indent_stack.last() != Some(&col) {
depth += 1;
if depth > max_depth {
return Err(depth);
}
indent_stack.push(col);
}
let mut prev: u8 = b' ';
while i < len && bytes[i] != b'\n' {
match bytes[i] {
b'#' => {
i = skip_to_eol(bytes, i);
break;
}
quote @ (b'"' | b'\'')
if matches!(prev, b' ' | b'\t' | b':' | b',' | b'[' | b'{' | b'-') =>
{
i = skip_yaml_string(bytes, i + 1, quote);
prev = quote;
}
b'[' | b'{' => {
depth += 1;
if depth > max_depth {
return Err(depth);
}
bracket_stack.push(bytes[i]);
prev = bytes[i];
i += 1;
}
b']' | b'}' => {
if bracket_stack.pop().is_some() {
depth = depth.saturating_sub(1);
}
prev = bytes[i];
i += 1;
}
b => {
prev = b;
i += 1;
}
}
}
if i < len && bytes[i] == b'\n' {
i += 1;
}
}
Ok(())
}
fn skip_to_eol(bytes: &[u8], mut i: usize) -> usize {
let len = bytes.len();
while i < len && bytes[i] != b'\n' {
i += 1;
}
i
}
fn skip_yaml_string(bytes: &[u8], mut i: usize, quote: u8) -> usize {
let len = bytes.len();
while i < len && bytes[i] != b'\n' {
if bytes[i] == b'\\' && quote == b'"' {
if bytes.get(i + 1) == Some(&b'\n') {
break;
}
i += 2;
continue;
}
if bytes[i] == quote {
if quote == b'\'' && bytes.get(i + 1) == Some(&b'\'') {
i += 2;
continue;
}
return i + 1;
}
i += 1;
}
i
}
const YAML_NODE_OVERHEAD_BYTES: u64 = size_of::<yaml_rust2::Yaml>() as u64;
pub const MAX_YAML_EXPANDED_BYTES: usize = 32 * 1024 * 1024;
pub fn check_yaml_expansion(content: &str, max_bytes: usize) -> std::result::Result<(), usize> {
struct Receiver {
max: u64,
consumed: u64,
exceeded: bool,
stack: Vec<(usize, u64)>,
anchors: BTreeMap<usize, u64>,
}
impl Receiver {
fn charge(&mut self, n: u64) {
if self.exceeded {
return;
}
self.consumed = self.consumed.saturating_add(n);
if self.consumed > self.max {
self.exceeded = true;
}
}
fn finish(&mut self, size: u64, aid: usize) {
if self.exceeded {
return;
}
if aid > 0 {
self.charge(size);
self.anchors.insert(aid, size);
}
if let Some((_, parent_size)) = self.stack.last_mut() {
*parent_size = parent_size.saturating_add(size);
}
}
}
impl MarkedEventReceiver for Receiver {
fn on_event(&mut self, ev: Event, _mark: Marker) {
if self.exceeded {
return;
}
match ev {
Event::SequenceStart(aid, _) | Event::MappingStart(aid, _) => {
self.stack.push((aid, 0));
}
Event::SequenceEnd | Event::MappingEnd => {
if let Some((aid, children_size)) = self.stack.pop() {
self.charge(YAML_NODE_OVERHEAD_BYTES);
self.finish(children_size.saturating_add(YAML_NODE_OVERHEAD_BYTES), aid);
}
}
Event::Scalar(ref v, _, aid, _) => {
let size = YAML_NODE_OVERHEAD_BYTES.saturating_add(v.len() as u64);
self.charge(size);
self.finish(size, aid);
}
Event::Alias(id) => {
let size = self
.anchors
.get(&id)
.copied()
.unwrap_or(YAML_NODE_OVERHEAD_BYTES);
self.charge(size);
self.finish(size, 0);
}
_ => {}
}
}
}
let mut recv = Receiver {
max: max_bytes as u64,
consumed: 0,
exceeded: false,
stack: Vec::new(),
anchors: BTreeMap::new(),
};
let _ = Parser::new(content.chars()).load(&mut recv, true);
if recv.exceeded {
Err(usize::try_from(recv.consumed).unwrap_or(usize::MAX))
} else {
Ok(())
}
}
pub const MAX_JSON_NESTING_DEPTH: usize = 64;
pub fn check_json_nesting_depth(
content: &[u8],
max_depth: usize,
) -> std::result::Result<(), usize> {
let mut depth: usize = 0;
let mut in_string = false;
let mut escaped = false;
for &b in content {
if in_string {
if escaped {
escaped = false;
} else if b == b'\\' {
escaped = true;
} else if b == b'"' {
in_string = false;
}
continue;
}
match b {
b'"' => in_string = true,
b'[' | b'{' => {
depth += 1;
if depth > max_depth {
return Err(depth);
}
}
b']' | b'}' => depth = depth.saturating_sub(1),
_ => {}
}
}
Ok(())
}
#[must_use]
fn json_depth_error(depth: usize) -> serde_json::Error {
serde::de::Error::custom(format!(
"JSON nesting depth {depth} exceeds maximum of {MAX_JSON_NESTING_DEPTH}"
))
}
pub fn parse_json_checked<T: serde::de::DeserializeOwned>(
bytes: &[u8],
) -> std::result::Result<T, serde_json::Error> {
if let Err(depth) = check_json_nesting_depth(bytes, MAX_JSON_NESTING_DEPTH) {
return Err(json_depth_error(depth));
}
serde_json::from_slice(bytes)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DependencySource {
Registry,
Git {
url: String,
rev: Option<String>,
},
Path { path: String },
Url { url: String },
Sdk { sdk: String },
Workspace,
CustomRegistry { url: String },
}
impl DependencySource {
pub fn is_registry(&self) -> bool {
matches!(self, Self::Registry | Self::CustomRegistry { .. })
}
pub fn is_version_resolvable(&self) -> bool {
matches!(self, Self::Registry)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LoadingState {
#[default]
Idle,
Loading,
Loaded,
Failed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_toml_nesting_depth_empty_content() {
assert_eq!(check_toml_nesting_depth("", 4), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_no_brackets() {
assert_eq!(check_toml_nesting_depth("a = 1\nb = \"text\"\n", 4), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_exactly_at_max() {
let content = format!("a = {}1{}", "[".repeat(4), "]".repeat(4));
assert_eq!(check_toml_nesting_depth(&content, 4), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_one_over_max() {
let content = format!("a = {}1{}", "[".repeat(5), "]".repeat(5));
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_toml_nesting_depth_double_quoted_string_ignored() {
let content = r#"a = "[[[[[unbalanced brackets]]]]]""#;
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_single_quoted_string_ignored() {
let content = "a = '[[[[[unbalanced brackets]]]]]'";
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_escaped_quote_in_string() {
let content = r#"a = "embedded \" quote [[[[[""#;
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_comment_ignored() {
let content = "# [[[[[unbalanced comment brackets]]]]]\na = 1\n";
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_mixed_array_and_table_nesting() {
let content = "a = [{ b = [{ c = 1 }] }]";
assert_eq!(check_toml_nesting_depth(content, 4), Ok(()));
assert_eq!(check_toml_nesting_depth(content, 3), Err(4));
}
#[test]
fn test_check_toml_nesting_depth_inline_table_at_production_boundary() {
let depth = MAX_TOML_NESTING_DEPTH;
let at_max = format!("a = {}1{}", "{a=".repeat(depth), "}".repeat(depth));
assert_eq!(check_toml_nesting_depth(&at_max, depth), Ok(()));
let over_max = format!("a = {}1{}", "{a=".repeat(depth + 1), "}".repeat(depth + 1));
assert_eq!(check_toml_nesting_depth(&over_max, depth), Err(depth + 1));
}
#[test]
fn test_check_toml_nesting_depth_dotted_header_at_production_boundary() {
let depth = MAX_TOML_NESTING_DEPTH;
let at_max = format!("[{}a]\ny = 1\n", "a.".repeat(depth - 1));
assert_eq!(check_toml_nesting_depth(&at_max, depth), Ok(()));
let over_max = format!("[{}a]\ny = 1\n", "a.".repeat(depth));
assert_eq!(check_toml_nesting_depth(&over_max, depth), Err(depth + 1));
}
#[test]
fn test_check_toml_nesting_depth_dotted_key_at_production_boundary() {
let depth = MAX_TOML_NESTING_DEPTH;
let at_max = format!("a{} = 1\n", ".a".repeat(depth));
assert_eq!(check_toml_nesting_depth(&at_max, depth), Ok(()));
let over_max = format!("a{} = 1\n", ".a".repeat(depth + 1));
assert_eq!(check_toml_nesting_depth(&over_max, depth), Err(depth + 1));
}
#[test]
fn test_check_toml_nesting_depth_legitimate_dotted_keys_accepted() {
let content = r#"
[tool.poetry.dependencies]
requests = { version = "^2.28", extras = ["socks"] }
[libraries]
spring-boot = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring" }
[metrics]
cpu_load = 3.14
"#;
assert_eq!(
check_toml_nesting_depth(content, MAX_TOML_NESTING_DEPTH),
Ok(())
);
}
#[test]
fn test_check_toml_nesting_depth_dotted_array_of_tables_header_at_boundary() {
let depth = MAX_TOML_NESTING_DEPTH;
let at_max = format!("[[{}a]]\ny = 1\n", "a.".repeat(depth - 2));
assert_eq!(check_toml_nesting_depth(&at_max, depth), Ok(()));
let over_max = format!("[[{}a]]\ny = 1\n", "a.".repeat(depth - 1));
assert_eq!(check_toml_nesting_depth(&over_max, depth), Err(depth + 1));
}
#[test]
fn test_check_toml_nesting_depth_dotted_key_inside_bracket_header_composes() {
let content = "[a.b]\nc.d.e = {f.g = 1}\n";
assert_eq!(check_toml_nesting_depth(content, 4), Ok(()));
assert_eq!(check_toml_nesting_depth(content, 3), Err(4));
}
#[test]
fn test_check_toml_nesting_depth_many_dotted_key_statements_do_not_accumulate() {
let mut content = String::new();
for i in 0..500 {
content.push_str(&format!("k{i}.a.b.c = {i}\n"));
}
assert_eq!(check_toml_nesting_depth(&content, 4), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_sibling_inline_tables_in_array_do_not_accumulate() {
let mut content = String::from("deps = [\n");
for i in 0..200 {
content.push_str(&format!(
" {{ name = \"pkg{i}\", version.ref = \"v\" }},\n"
));
}
content.push_str("]\n");
assert_eq!(
check_toml_nesting_depth(&content, MAX_TOML_NESTING_DEPTH),
Ok(())
);
}
#[test]
fn test_check_toml_nesting_depth_multiline_literal_odd_quote_count() {
let content = format!("a = '''don't'''\nb = {}1{}", "[".repeat(5), "]".repeat(5));
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_toml_nesting_depth_rejects_original_sigabrt_payloads() {
let n = 20_000;
let payload1 = format!("a = '''don't'''\nb = {}1{}", "[".repeat(n), "]".repeat(n));
let payload2 = format!(
"a = \"\"\"x\"y\"\"\"\nb = {}1{}",
"[".repeat(n),
"]".repeat(n)
);
assert!(check_toml_nesting_depth(&payload1, MAX_TOML_NESTING_DEPTH).is_err());
assert!(check_toml_nesting_depth(&payload2, MAX_TOML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_toml_nesting_depth_multiline_basic_odd_quote_count() {
let content = format!(
"a = \"\"\"x\"y\"\"\"\nb = {}1{}",
"[".repeat(5),
"]".repeat(5)
);
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_toml_nesting_depth_multiline_basic_trailing_extra_quotes() {
let content = format!(
"a = \"\"\"ends with two quotes: \"\"\"\"\"\nb = {}1{}",
"[".repeat(5),
"]".repeat(5)
);
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_toml_nesting_depth_brackets_inside_multiline_string_ignored() {
let content = "d = \"\"\"\nsize is 5\" wide [[[unbalanced]]]\n\"\"\"\ne = 1\n";
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_multiline_literal_brackets_ignored() {
let content = "d = '''\n[[[unbalanced brackets]]]\n'''\ne = 1\n";
assert_eq!(check_toml_nesting_depth(content, 0), Ok(()));
}
#[test]
fn test_check_toml_nesting_depth_multiline_basic_one_extra_trailing_quote() {
let content = format!(
"a = \"\"\"ends with one quote: \"\"\"\"\nb = {}1{}",
"[".repeat(5),
"]".repeat(5)
);
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_toml_nesting_depth_adjacent_multiline_strings_then_nesting() {
let content = format!(
"a = \"\"\"first\"\"\"\nb = '''second'''\nc = {}1{}",
"[".repeat(5),
"]".repeat(5)
);
assert_eq!(check_toml_nesting_depth(&content, 4), Err(5));
}
fn nested_mapping(n: usize) -> String {
let mut s = String::new();
for i in 0..n {
s.push_str(&" ".repeat(i));
s.push_str("k:\n");
}
s
}
#[test]
fn test_check_yaml_nesting_depth_empty_content() {
assert_eq!(check_yaml_nesting_depth("", 4), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_no_nesting() {
assert_eq!(
check_yaml_nesting_depth("name: foo\nversion: 1.0.0\n", 1),
Ok(())
);
}
#[test]
fn test_check_yaml_nesting_depth_dash_chain_exactly_at_max() {
let content = format!("{}1", "- ".repeat(3));
assert_eq!(check_yaml_nesting_depth(&content, 4), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_dash_chain_one_over_max() {
let content = format!("{}1", "- ".repeat(4));
assert_eq!(check_yaml_nesting_depth(&content, 4), Err(5));
}
#[test]
fn test_check_yaml_nesting_depth_block_mapping_exactly_at_max() {
assert_eq!(check_yaml_nesting_depth(&nested_mapping(4), 4), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_block_mapping_one_over_max() {
assert_eq!(check_yaml_nesting_depth(&nested_mapping(5), 4), Err(5));
}
#[test]
fn test_check_yaml_nesting_depth_double_quoted_string_ignored() {
let content = r#"a: "[[[[[unbalanced brackets]]]]]""#;
assert_eq!(check_yaml_nesting_depth(content, 1), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_single_quoted_string_ignored() {
let content = "a: '[[[[[unbalanced brackets]]]]]'";
assert_eq!(check_yaml_nesting_depth(content, 1), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_escaped_quote_in_string() {
let content = r#"a: "embedded \" quote [[[[[""#;
assert_eq!(check_yaml_nesting_depth(content, 1), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_comment_ignored() {
let content = "# [[[[[unbalanced comment brackets]]]]]\na: 1\n";
assert_eq!(check_yaml_nesting_depth(content, 1), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_mixed_flow_and_block_nesting() {
let content = "a:\n b: [c, [d, [e]]]\n";
assert_eq!(check_yaml_nesting_depth(content, 5), Ok(()));
assert_eq!(check_yaml_nesting_depth(content, 4), Err(5));
}
#[test]
fn test_check_yaml_nesting_depth_dash_chain_at_production_boundary() {
let depth = MAX_YAML_NESTING_DEPTH;
let at_max = format!("{}1", "- ".repeat(depth - 1));
assert_eq!(check_yaml_nesting_depth(&at_max, depth), Ok(()));
let over_max = format!("{}1", "- ".repeat(depth));
assert_eq!(check_yaml_nesting_depth(&over_max, depth), Err(depth + 1));
}
#[test]
fn test_check_yaml_nesting_depth_block_mapping_at_production_boundary() {
let depth = MAX_YAML_NESTING_DEPTH;
assert_eq!(
check_yaml_nesting_depth(&nested_mapping(depth), depth),
Ok(())
);
assert_eq!(
check_yaml_nesting_depth(&nested_mapping(depth + 1), depth),
Err(depth + 1)
);
}
#[test]
fn test_check_yaml_nesting_depth_rejects_original_sigabrt_payloads() {
let dash_chain = format!("{}1", "- ".repeat(6000));
assert!(check_yaml_nesting_depth(&dash_chain, MAX_YAML_NESTING_DEPTH).is_err());
let block_mapping = nested_mapping(2500);
assert!(check_yaml_nesting_depth(&block_mapping, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_apostrophe_does_not_blind_scanner() {
let payload = format!(
"name: my_app\ndescription: A package that doesn't panic\n{}1",
"- ".repeat(MAX_YAML_NESTING_DEPTH + 1)
);
assert!(check_yaml_nesting_depth(&payload, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_stray_double_quote_does_not_blind_scanner() {
let payload = format!(
"size: 6\" long\n{}1",
"- ".repeat(MAX_YAML_NESTING_DEPTH + 1)
);
assert!(check_yaml_nesting_depth(&payload, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_unterminated_quote_only_blinds_one_line() {
let payload = format!(
"a: \"unterminated\n{}1",
"- ".repeat(MAX_YAML_NESTING_DEPTH + 1)
);
assert!(check_yaml_nesting_depth(&payload, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_backslash_before_newline_does_not_extend_string() {
let payload = format!(
"a: \"unterminated\\\n{}1",
"- ".repeat(MAX_YAML_NESTING_DEPTH + 1)
);
assert!(check_yaml_nesting_depth(&payload, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_unclosed_bracket_does_not_blind_scanner() {
let payload = format!("a: [\n{}1", "- ".repeat(MAX_YAML_NESTING_DEPTH + 1));
assert!(check_yaml_nesting_depth(&payload, MAX_YAML_NESTING_DEPTH).is_err());
}
#[test]
fn test_check_yaml_nesting_depth_many_sibling_keys_do_not_accumulate() {
let mut content = String::from("dependencies:\n");
for i in 0..2000 {
content.push_str(&format!(" pkg{i}: ^1.0.0\n"));
}
assert_eq!(check_yaml_nesting_depth(&content, 2), Ok(()));
}
#[test]
fn test_check_yaml_nesting_depth_multiline_flow_list_siblings_do_not_accumulate() {
let mut content = String::from("dependencies: [\n");
for _ in 0..2000 {
content.push_str(" a,\n");
}
content.push_str("]\n");
assert_eq!(check_yaml_nesting_depth(&content, 3), Ok(()));
}
fn doubling_chain(n: usize) -> String {
let mut s = String::from("name: app\na0: &a0 [x, x]\n");
for i in 1..=n {
s.push_str(&format!("a{i}: &a{i} [*a{prev}, *a{prev}]\n", prev = i - 1));
}
s
}
#[test]
fn test_check_yaml_expansion_empty_content() {
assert_eq!(check_yaml_expansion("", MAX_YAML_EXPANDED_BYTES), Ok(()));
}
#[test]
fn test_check_yaml_expansion_rejects_n30_doubling_chain_attack() {
assert!(check_yaml_expansion(&doubling_chain(30), MAX_YAML_EXPANDED_BYTES).is_err());
}
#[test]
fn test_check_yaml_expansion_realistic_pubspec_yaml_accepted() {
let yaml = r"
name: my_app
description: A sample app
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
http: ^1.0.0
provider: ^6.0.0
my_pkg:
git:
url: https://github.com/user/repo.git
ref: main
path: packages/my_pkg
dev_dependencies:
build_runner: ^2.4.0
";
assert_eq!(check_yaml_expansion(yaml, MAX_YAML_EXPANDED_BYTES), Ok(()));
}
#[test]
fn test_check_yaml_expansion_few_hundred_package_lockfile_accepted() {
let mut lock = String::from("packages:\n");
for i in 0..300 {
lock.push_str(&format!(
" pkg_{i}:\n dependency: \"direct main\"\n description:\n name: pkg_{i}\n url: \"https://pub.dev\"\n source: hosted\n version: \"1.{i}.0\"\n"
));
}
assert_eq!(check_yaml_expansion(&lock, MAX_YAML_EXPANDED_BYTES), Ok(()));
}
#[test]
fn test_check_yaml_expansion_asterisk_in_plain_scalar_not_misread_as_alias() {
let cases = [
"description: A widget *multiplier* helper\n",
"description: see *.dart files\n",
"e: text &y more\n",
];
for content in cases {
assert_eq!(
check_yaml_expansion(content, MAX_YAML_EXPANDED_BYTES),
Ok(()),
"false positive on: {content:?}"
);
}
}
#[test]
fn test_check_yaml_expansion_alias_to_undefined_anchor_accepted() {
assert_eq!(
check_yaml_expansion("a: *undefined\n", MAX_YAML_EXPANDED_BYTES),
Ok(())
);
}
#[test]
fn test_check_yaml_expansion_self_referential_alias_accepted() {
assert_eq!(
check_yaml_expansion("a: &x [1, *x]\n", MAX_YAML_EXPANDED_BYTES),
Ok(())
);
}
#[test]
fn test_check_yaml_expansion_at_production_boundary() {
assert_eq!(
check_yaml_expansion(&doubling_chain(14), MAX_YAML_EXPANDED_BYTES),
Ok(())
);
assert!(check_yaml_expansion(&doubling_chain(15), MAX_YAML_EXPANDED_BYTES).is_err());
}
#[test]
fn test_check_yaml_expansion_large_scalar_anchor_aliased_many_times_rejected() {
let anchor_value = "A".repeat(1_000_000);
let mut content = format!("s: &s \"{anchor_value}\"\nl:\n");
for _ in 0..32 {
content.push_str(" - *s\n");
}
assert!(check_yaml_expansion(&content, MAX_YAML_EXPANDED_BYTES).is_err());
}
#[test]
fn test_check_yaml_expansion_exact_max_boundary() {
let scalar_len = 1000usize;
let max_bytes = YAML_NODE_OVERHEAD_BYTES as usize + scalar_len;
let content = "a".repeat(scalar_len);
assert_eq!(check_yaml_expansion(&content, max_bytes), Ok(()));
assert!(check_yaml_expansion(&content, max_bytes - 1).is_err());
}
#[test]
fn test_check_yaml_expansion_matches_recursive_byte_weight_oracle() {
fn recursive_weight(y: &yaml_rust2::Yaml) -> u64 {
match y {
yaml_rust2::Yaml::String(s) => YAML_NODE_OVERHEAD_BYTES + s.len() as u64,
yaml_rust2::Yaml::Array(arr) => {
YAML_NODE_OVERHEAD_BYTES + arr.iter().map(recursive_weight).sum::<u64>()
}
yaml_rust2::Yaml::Hash(h) => {
YAML_NODE_OVERHEAD_BYTES
+ h.iter()
.map(|(k, v)| recursive_weight(k) + recursive_weight(v))
.sum::<u64>()
}
_ => YAML_NODE_OVERHEAD_BYTES,
}
}
fn smallest_accepted(content: &str) -> u64 {
let (mut lo, mut hi) = (0u64, 1_000_000u64);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if check_yaml_expansion(content, usize::try_from(mid).unwrap_or(usize::MAX)).is_ok()
{
hi = mid;
} else {
lo = mid + 1;
}
}
lo
}
let docs = [
r#"a: "hello""#,
r#"a: ["x", "yy", "zzz"]"#,
"a:\n b: \"value\"\n c:\n - \"one\"\n - \"two\"\n",
];
for content in docs {
let parsed = yaml_rust2::YamlLoader::load_from_str(content).unwrap();
let expected = recursive_weight(&parsed[0]);
assert_eq!(
smallest_accepted(content),
expected,
"oracle mismatch for {content:?}"
);
}
}
#[test]
fn test_dependency_source_registry() {
let source = DependencySource::Registry;
assert_eq!(source, DependencySource::Registry);
assert!(source.is_registry());
assert!(source.is_version_resolvable());
}
#[test]
fn test_dependency_source_git() {
let source = DependencySource::Git {
url: "https://github.com/user/repo".into(),
rev: Some("main".into()),
};
assert!(!source.is_registry());
assert!(!source.is_version_resolvable());
match source {
DependencySource::Git { url, rev } => {
assert_eq!(url, "https://github.com/user/repo");
assert_eq!(rev, Some("main".into()));
}
_ => panic!("Expected Git source"),
}
}
#[test]
fn test_dependency_source_git_no_rev() {
let source = DependencySource::Git {
url: "https://github.com/user/repo".into(),
rev: None,
};
match source {
DependencySource::Git { url, rev } => {
assert_eq!(url, "https://github.com/user/repo");
assert!(rev.is_none());
}
_ => panic!("Expected Git source"),
}
}
#[test]
fn test_dependency_source_path() {
let source = DependencySource::Path {
path: "../local-crate".into(),
};
assert!(!source.is_registry());
match source {
DependencySource::Path { path } => {
assert_eq!(path, "../local-crate");
}
_ => panic!("Expected Path source"),
}
}
#[test]
fn test_dependency_source_url() {
let source = DependencySource::Url {
url: "https://example.com/package.whl".into(),
};
assert!(!source.is_registry());
assert!(!source.is_version_resolvable());
}
#[test]
fn test_dependency_source_sdk() {
let source = DependencySource::Sdk {
sdk: "flutter".into(),
};
assert!(!source.is_registry());
}
#[test]
fn test_dependency_source_workspace() {
let source = DependencySource::Workspace;
assert!(!source.is_registry());
assert!(!source.is_version_resolvable());
}
#[test]
fn test_dependency_source_custom_registry() {
let source = DependencySource::CustomRegistry {
url: "https://gems.example.com".into(),
};
assert!(source.is_registry());
assert!(!source.is_version_resolvable());
}
#[test]
fn test_dependency_source_clone() {
let source1 = DependencySource::Git {
url: "https://example.com/repo".into(),
rev: Some("v1.0".into()),
};
let source2 = source1.clone();
assert_eq!(source1, source2);
}
#[test]
fn test_dependency_source_equality() {
let reg1 = DependencySource::Registry;
let reg2 = DependencySource::Registry;
assert_eq!(reg1, reg2);
let git1 = DependencySource::Git {
url: "https://example.com".into(),
rev: None,
};
let git2 = DependencySource::Git {
url: "https://example.com".into(),
rev: None,
};
assert_eq!(git1, git2);
let git3 = DependencySource::Git {
url: "https://different.com".into(),
rev: None,
};
assert_ne!(git1, git3);
}
#[test]
fn test_check_json_nesting_depth_shallow_accepted() {
let content = br#"{"a":[1,2,{"b":3}],"c":"[not{real}nesting]"}"#;
assert_eq!(check_json_nesting_depth(content, 4), Ok(()));
}
#[test]
fn test_check_json_nesting_depth_string_brackets_ignored() {
let content = br#"{"a":"[[[[[\"]]]]]"}"#;
assert_eq!(check_json_nesting_depth(content, 1), Ok(()));
}
#[test]
fn test_check_json_nesting_depth_mixed_array_and_object_nesting() {
let content = br#"[{"a":[{"b":1}]}]"#;
assert_eq!(check_json_nesting_depth(content, 4), Ok(()));
assert_eq!(check_json_nesting_depth(content, 3), Err(4));
}
#[test]
fn test_check_json_nesting_depth_deeply_nested_array_rejected() {
let deeply_nested = format!("{}1{}", "[".repeat(10), "]".repeat(10));
assert_eq!(
check_json_nesting_depth(deeply_nested.as_bytes(), 4),
Err(5)
);
}
#[test]
fn test_check_json_nesting_depth_unterminated_string_blinds_scanner_but_serde_json_still_rejects()
{
let payload = format!("\"unterminated{}1", "[".repeat(MAX_JSON_NESTING_DEPTH + 1));
assert_eq!(
check_json_nesting_depth(payload.as_bytes(), MAX_JSON_NESTING_DEPTH),
Ok(())
);
assert!(serde_json::from_str::<serde_json::Value>(&payload).is_err());
}
#[test]
fn test_check_json_nesting_depth_at_production_boundary() {
let depth = MAX_JSON_NESTING_DEPTH;
let at_max = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
assert_eq!(check_json_nesting_depth(at_max.as_bytes(), depth), Ok(()));
let over_max = format!("{}1{}", "[".repeat(depth + 1), "]".repeat(depth + 1));
assert_eq!(
check_json_nesting_depth(over_max.as_bytes(), depth),
Err(depth + 1)
);
}
#[test]
fn test_dependency_source_debug() {
let source = DependencySource::Registry;
let debug = format!("{:?}", source);
assert_eq!(debug, "Registry");
let git = DependencySource::Git {
url: "https://example.com".into(),
rev: Some("main".into()),
};
let git_debug = format!("{:?}", git);
assert!(git_debug.contains("https://example.com"));
assert!(git_debug.contains("main"));
}
#[test]
fn test_loading_state_default() {
assert_eq!(LoadingState::default(), LoadingState::Idle);
}
#[test]
fn test_loading_state_copy() {
let state = LoadingState::Loading;
let copied = state;
assert_eq!(state, copied);
}
#[test]
fn test_loading_state_debug() {
let debug_str = format!("{:?}", LoadingState::Loading);
assert_eq!(debug_str, "Loading");
}
#[test]
fn test_loading_state_all_variants() {
let variants = [
LoadingState::Idle,
LoadingState::Loading,
LoadingState::Loaded,
LoadingState::Failed,
];
for (i, v1) in variants.iter().enumerate() {
for (j, v2) in variants.iter().enumerate() {
if i == j {
assert_eq!(v1, v2);
} else {
assert_ne!(v1, v2);
}
}
}
}
}