#![cfg(feature = "axum")]
use axum::{
body::Body,
http::{Request, StatusCode},
routing::post,
Router,
};
use secure_boundary::{
validate::{SecureValidate, ValidationContext},
xml::SecureXml,
};
use serde::Deserialize;
use tower::ServiceExt;
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct XmlPayload {
name: String,
}
impl SecureValidate for XmlPayload {
fn validate_syntax(&self, _ctx: &ValidationContext) -> Result<(), &'static str> {
Ok(())
}
fn validate_semantics(&self, _ctx: &ValidationContext) -> Result<(), &'static str> {
Ok(())
}
}
fn xml_router() -> Router {
Router::new().route(
"/xml",
post(|p: SecureXml<XmlPayload>| async move { p.into_inner().name }),
)
}
#[tokio::test]
async fn secure_xml_valid_accepted() {
let app = xml_router();
let xml = "<XmlPayload><name>test</name></XmlPayload>";
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/xml")
.header("content-type", "application/xml")
.body(Body::from(xml))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn secure_xml_xxe_payload_rejected() {
let app = xml_router();
let xml = concat!(
r#"<?xml version="1.0"?>"#,
r#"<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>"#,
r#"<XmlPayload><name>&xxe;</name></XmlPayload>"#
);
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/xml")
.header("content-type", "application/xml")
.body(Body::from(xml))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
}
#[tokio::test]
async fn secure_xml_external_dtd_rejected() {
let app = xml_router();
let xml = concat!(
r#"<?xml version="1.0"?>"#,
r#"<!DOCTYPE foo SYSTEM "http://evil.com/evil.dtd">"#,
r#"<XmlPayload><name>test</name></XmlPayload>"#
);
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/xml")
.header("content-type", "application/xml")
.body(Body::from(xml))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
}
#[tokio::test]
async fn secure_xml_billion_laughs_rejected() {
let app = xml_router();
let xml = concat!(
r#"<?xml version="1.0"?>"#,
r#"<!DOCTYPE lolz [<!ENTITY lol "lol"><!ENTITY lol2 "&lol;&lol;">]>"#,
r#"<XmlPayload><name>&lol2;</name></XmlPayload>"#
);
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/xml")
.header("content-type", "application/xml")
.body(Body::from(xml))
.unwrap(),
)
.await
.unwrap();
assert!(
response.status() == StatusCode::UNPROCESSABLE_ENTITY
|| response.status() == StatusCode::PAYLOAD_TOO_LARGE
);
}