use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Technique {
PayloadEncoding(String),
ContentTypeSwitch(String),
BoundaryManipulation,
JsonUnicodeEscape,
TlsFingerprint(String),
UserAgentRotation,
Http2Settings,
GrammarMutation(String),
HeaderObfuscation(String),
RequestSmuggling(String),
H2Evasion(String),
DifferentialProbe,
BodyPadding(usize),
}
impl Technique {
#[must_use]
pub fn from_pool_key(s: &str) -> Option<Self> {
if let Some(rest) = s.strip_prefix("encoding:") {
return Some(Self::PayloadEncoding(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("content-type:") {
return Some(Self::ContentTypeSwitch(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("tls:") {
return Some(Self::TlsFingerprint(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("grammar:") {
return Some(Self::GrammarMutation(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("header:") {
return Some(Self::HeaderObfuscation(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("smuggling:") {
return Some(Self::RequestSmuggling(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("h2:") {
return Some(Self::H2Evasion(rest.to_string()));
}
if let Some(rest) = s.strip_prefix("body-padding:") {
return rest.parse::<usize>().ok().map(Self::BodyPadding);
}
match s {
"boundary-manipulation" => Some(Self::BoundaryManipulation),
"json-unicode-escape" => Some(Self::JsonUnicodeEscape),
"user-agent-rotation" => Some(Self::UserAgentRotation),
"http2-settings" => Some(Self::Http2Settings),
"differential-probe" => Some(Self::DifferentialProbe),
_ => None,
}
}
}
impl fmt::Display for Technique {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PayloadEncoding(s) => write!(f, "encoding:{s}"),
Self::ContentTypeSwitch(s) => write!(f, "content-type:{s}"),
Self::BoundaryManipulation => f.write_str("boundary-manipulation"),
Self::JsonUnicodeEscape => f.write_str("json-unicode-escape"),
Self::TlsFingerprint(s) => write!(f, "tls:{s}"),
Self::UserAgentRotation => f.write_str("user-agent-rotation"),
Self::Http2Settings => f.write_str("http2-settings"),
Self::GrammarMutation(s) => write!(f, "grammar:{s}"),
Self::HeaderObfuscation(s) => write!(f, "header:{s}"),
Self::RequestSmuggling(s) => write!(f, "smuggling:{s}"),
Self::H2Evasion(s) => write!(f, "h2:{s}"),
Self::DifferentialProbe => f.write_str("differential-probe"),
Self::BodyPadding(n) => write!(f, "body-padding:{n}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn technique_display() {
let t = Technique::PayloadEncoding("UrlEncode".into());
assert_eq!(t.to_string(), "encoding:UrlEncode");
assert_eq!(
Technique::from_pool_key("encoding:UrlEncode"),
Some(t.clone())
);
assert_eq!(
Technique::GrammarMutation("sql_tautology".into()).to_string(),
"grammar:sql_tautology"
);
assert_eq!(
Technique::RequestSmuggling("CL.TE".into()).to_string(),
"smuggling:CL.TE"
);
}
#[test]
fn body_padding_roundtrip() {
let t = Technique::BodyPadding(16384);
assert_eq!(t.to_string(), "body-padding:16384");
assert_eq!(Technique::from_pool_key("body-padding:16384"), Some(t));
assert_eq!(Technique::from_pool_key("body-padding:not-a-number"), None);
}
#[test]
fn technique_serde_roundtrip() {
let tech = Technique::ContentTypeSwitch("multipart".into());
let json = serde_json::to_string(&tech).expect("serialize");
let deserialized: Technique = serde_json::from_str(&json).expect("deserialize");
assert_eq!(tech, deserialized);
}
}