include!(concat!(env!("OUT_DIR"), "/differential_data.rs"));
#[derive(Debug, Clone, PartialEq)]
pub struct Probe {
pub payload: String,
pub tests: ProbeTarget,
pub description: String,
pub expected_blocked: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProbeTarget {
SqlKeyword(String),
SqlOperator(String),
SqlComment(String),
SqlQuote,
SqlTautology(String),
XssTag(String),
XssEvent(String),
XssExecFunction(String),
CmdSeparator(String),
CmdCommand(String),
CmdPath(String),
Baseline,
}
#[must_use]
pub fn generate_probes() -> Vec<Probe> {
let mut probes = Vec::new();
probes.push(baseline_probe(BASELINE_PAYLOAD, BASELINE_DESCRIPTION));
probes.extend(sql_keyword_probes());
probes.extend(sql_operator_probes());
probes.extend(sql_comment_probes());
probes.push(Probe {
payload: "'".into(),
tests: ProbeTarget::SqlQuote,
description: "SQL single quote".into(),
expected_blocked: true,
});
probes.extend(sql_tautology_probes());
probes.extend(xss_tag_probes());
probes.extend(xss_event_probes());
probes.extend(xss_function_probes());
probes.extend(command_separator_probes());
probes.extend(command_name_probes());
probes.extend(command_path_probes());
probes
}
pub(crate) fn baseline_probe(payload: &str, description: &str) -> Probe {
Probe {
payload: payload.into(),
tests: ProbeTarget::Baseline,
description: description.into(),
expected_blocked: false,
}
}
pub(crate) fn sql_keyword_probes() -> Vec<Probe> {
SQL_KEYWORDS
.iter()
.map(|&keyword| Probe {
payload: format!("test {keyword} value"),
tests: ProbeTarget::SqlKeyword(keyword.to_string()),
description: format!("SQL keyword: {keyword}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn sql_operator_probes() -> Vec<Probe> {
SQL_OPERATORS
.iter()
.map(|&operator| Probe {
payload: format!("test{operator}test"),
tests: ProbeTarget::SqlOperator(operator.to_string()),
description: format!("SQL operator: {operator}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn sql_comment_probes() -> Vec<Probe> {
SQL_COMMENTS
.iter()
.map(|&comment| Probe {
payload: format!("test{comment}test"),
tests: ProbeTarget::SqlComment(comment.to_string()),
description: format!("SQL comment: {comment}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn sql_tautology_probes() -> Vec<Probe> {
SQL_TAUTOLOGIES
.iter()
.map(|&tautology| Probe {
payload: tautology.to_string(),
tests: ProbeTarget::SqlTautology(tautology.to_string()),
description: format!("SQL tautology: {tautology}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn xss_tag_probes() -> Vec<Probe> {
XSS_TAGS
.iter()
.map(|&(name, payload, expected_blocked)| Probe {
payload: payload.into(),
tests: ProbeTarget::XssTag(name.into()),
description: format!("XSS tag: {name}"),
expected_blocked,
})
.collect()
}
pub(crate) fn xss_event_probes() -> Vec<Probe> {
XSS_EVENTS
.iter()
.map(|&event| Probe {
payload: format!("<x {event}=1>"),
tests: ProbeTarget::XssEvent(event.to_string()),
description: format!("XSS event: {event}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn xss_function_probes() -> Vec<Probe> {
XSS_FUNCTIONS
.iter()
.map(|&(name, payload, expected_blocked)| Probe {
payload: payload.into(),
tests: ProbeTarget::XssExecFunction(name.into()),
description: format!("XSS function: {name}"),
expected_blocked,
})
.collect()
}
pub(crate) fn command_separator_probes() -> Vec<Probe> {
COMMAND_SEPARATORS
.iter()
.map(|&separator| Probe {
payload: format!("test{separator}test"),
tests: ProbeTarget::CmdSeparator(separator.to_string()),
description: format!("CMD separator: {separator}"),
expected_blocked: true,
})
.collect()
}
pub(crate) fn command_name_probes() -> Vec<Probe> {
COMMAND_NAMES
.iter()
.map(|&command| Probe {
payload: command.to_string(),
tests: ProbeTarget::CmdCommand(command.to_string()),
description: format!("CMD command: {command}"),
expected_blocked: false,
})
.collect()
}
pub(crate) fn command_path_probes() -> Vec<Probe> {
COMMAND_PATHS
.iter()
.map(|&path| Probe {
payload: path.to_string(),
tests: ProbeTarget::CmdPath(path.to_string()),
description: format!("CMD path: {path}"),
expected_blocked: true,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{ProbeTarget, generate_probes};
#[test]
fn generate_probes_has_baseline() {
let probes = generate_probes();
assert!(
probes
.iter()
.any(|probe| probe.tests == ProbeTarget::Baseline)
);
}
#[test]
fn generate_probes_covers_all_categories() {
let probes = generate_probes();
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::SqlKeyword(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::SqlOperator(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::SqlComment(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::SqlQuote))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::SqlTautology(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::XssTag(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::XssEvent(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::XssExecFunction(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::CmdSeparator(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::CmdCommand(_)))
);
assert!(
probes
.iter()
.any(|probe| matches!(probe.tests, ProbeTarget::CmdPath(_)))
);
}
#[test]
fn generate_probes_has_many() {
let probes = generate_probes();
assert!(
probes.len() >= 60,
"expected 60+ probes, got {}",
probes.len()
);
}
#[test]
fn probes_have_descriptions() {
let probes = generate_probes();
for probe in &probes {
assert!(
!probe.description.is_empty(),
"probe should have description"
);
assert!(
!probe.payload.is_empty() || probe.tests == ProbeTarget::Baseline,
"probe should have payload"
);
}
}
#[test]
fn sql_quote_expected_blocked() {
let probes = generate_probes();
let quote = probes
.iter()
.find(|p| matches!(p.tests, ProbeTarget::SqlQuote));
assert!(quote.is_some());
assert!(
quote.unwrap().expected_blocked,
"SQL quote should be expected blocked"
);
}
}