1use super::keyword::{
4 between_obfuscate, case_alternate, mysql_versioned_comment, percentage_prefix,
5 random_case_alternate, space_to_comment, space_to_dash, space_to_hash, space_to_plus,
6 space_to_random_blank, sql_comment_insert, unmagic_quotes, whitespace_insert,
7};
8use super::structural::{
9 base64_encode, base64_url_encode, chunked_split, deflate_encode, gzip_encode, hex_encode,
10 null_byte_inject, overlong_utf8, overlong_utf8_more, parameter_pollute, utf7_encode,
11};
12use super::unicode::{
13 fullwidth_encode, homoglyph_encode, html_entity_decimal_encode, html_entity_encode,
14 iis_unicode_encode, json_string_encode, unicode_encode,
15};
16use super::url::{double_url_encode, triple_url_encode, url_encode, url_encode_lower};
17use crate::error::EncodeError;
18
19pub const MAX_PAYLOAD_SIZE: usize = 8 * 1024 * 1024;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
28#[non_exhaustive]
29pub enum Strategy {
30 UrlEncode,
33 UrlEncodeLower,
36 DoubleUrlEncode,
39 TripleUrlEncode,
42 UnicodeEncode,
45 IisUnicodeEncode,
48 JsonEncode,
51 HtmlEntityEncode,
54 HtmlEntityDecimalEncode,
57 CaseAlternation,
60 RandomCase,
63 WhitespaceInsertion,
66 SqlCommentInsertion,
69 MysqlVersionedComment,
72 NullByte,
75 OverlongUtf8,
78 OverlongUtf8More,
81 ChunkedSplit,
84 ParameterPollution,
87 Base64Encode,
90 Base64UrlEncode,
93 HexEncode,
96 Utf7Encode,
99 GzipEncode,
102 DeflateEncode,
105 SpaceToComment,
108 SpaceToDash,
111 SpaceToHash,
114 SpaceToPlus,
117 SpaceToRandomBlank,
120 PercentagePrefix,
123 BetweenObfuscation,
126 UnmagicQuotes,
129 FullwidthEncode,
132 HomoglyphEncode,
135}
136
137impl Strategy {
138 #[must_use]
140 pub const fn as_str(&self) -> &'static str {
141 match self {
142 Self::UrlEncode => "UrlEncode",
143 Self::UrlEncodeLower => "UrlEncodeLower",
144 Self::DoubleUrlEncode => "DoubleUrlEncode",
145 Self::TripleUrlEncode => "TripleUrlEncode",
146 Self::UnicodeEncode => "UnicodeEncode",
147 Self::IisUnicodeEncode => "IisUnicodeEncode",
148 Self::JsonEncode => "JsonEncode",
149 Self::HtmlEntityEncode => "HtmlEntityEncode",
150 Self::HtmlEntityDecimalEncode => "HtmlEntityDecimalEncode",
151 Self::CaseAlternation => "CaseAlternation",
152 Self::RandomCase => "RandomCase",
153 Self::WhitespaceInsertion => "WhitespaceInsertion",
154 Self::SqlCommentInsertion => "SqlCommentInsertion",
155 Self::MysqlVersionedComment => "MysqlVersionedComment",
156 Self::NullByte => "NullByte",
157 Self::OverlongUtf8 => "OverlongUtf8",
158 Self::OverlongUtf8More => "OverlongUtf8More",
159 Self::ChunkedSplit => "ChunkedSplit",
160 Self::ParameterPollution => "ParameterPollution",
161 Self::Base64Encode => "Base64Encode",
162 Self::Base64UrlEncode => "Base64UrlEncode",
163 Self::HexEncode => "HexEncode",
164 Self::Utf7Encode => "Utf7Encode",
165 Self::GzipEncode => "GzipEncode",
166 Self::DeflateEncode => "DeflateEncode",
167 Self::SpaceToComment => "SpaceToComment",
168 Self::SpaceToDash => "SpaceToDash",
169 Self::SpaceToHash => "SpaceToHash",
170 Self::SpaceToPlus => "SpaceToPlus",
171 Self::SpaceToRandomBlank => "SpaceToRandomBlank",
172 Self::PercentagePrefix => "PercentagePrefix",
173 Self::BetweenObfuscation => "BetweenObfuscation",
174 Self::UnmagicQuotes => "UnmagicQuotes",
175 Self::FullwidthEncode => "FullwidthEncode",
176 Self::HomoglyphEncode => "HomoglyphEncode",
177 }
178 }
179
180 #[must_use]
186 pub const fn contexts(&self) -> &'static [&'static str] {
187 match self {
188 Self::UrlEncode
189 | Self::UrlEncodeLower
190 | Self::DoubleUrlEncode
191 | Self::TripleUrlEncode
192 | Self::ParameterPollution => &[],
193 Self::UnicodeEncode => &["json", "javascript"],
194 Self::IisUnicodeEncode => &["iis", "asp"],
195 Self::JsonEncode => &["json"],
196 Self::HtmlEntityEncode | Self::HtmlEntityDecimalEncode => &["html"],
197 Self::CaseAlternation | Self::RandomCase | Self::WhitespaceInsertion => &[],
198 Self::SqlCommentInsertion
199 | Self::MysqlVersionedComment
200 | Self::SpaceToComment
201 | Self::SpaceToDash
202 | Self::SpaceToRandomBlank
203 | Self::BetweenObfuscation => &["sql"],
204 Self::SpaceToHash => &["sql", "mysql"],
205 Self::SpaceToPlus => &["url-encoded"],
206 Self::NullByte => &["php", "cgi"],
207 Self::OverlongUtf8 | Self::OverlongUtf8More => &["iis-6"],
208 Self::ChunkedSplit => &["http-request-body"],
209 Self::Base64Encode | Self::Base64UrlEncode | Self::HexEncode => &[],
210 Self::Utf7Encode => &["iis", "legacy-dotnet"],
211 Self::GzipEncode | Self::DeflateEncode => &["http-request-body"],
212 Self::PercentagePrefix => &[],
213 Self::UnmagicQuotes => &["php", "gbk", "big5", "shift-jis"],
214 Self::FullwidthEncode => &["nfkc", "java", "dotnet", "python3", "postgresql"],
215 Self::HomoglyphEncode => &[],
216 }
217 }
218}
219
220fn check_size(payload: &[u8]) -> Result<(), EncodeError> {
221 if payload.len() > MAX_PAYLOAD_SIZE {
222 Err(EncodeError::PayloadTooLarge {
223 max: MAX_PAYLOAD_SIZE,
224 actual: payload.len(),
225 })
226 } else {
227 Ok(())
228 }
229}
230
231pub fn encode(payload: impl AsRef<[u8]>, strategy: Strategy) -> Result<String, EncodeError> {
243 let payload = payload.as_ref();
244 check_size(payload)?;
245
246 match strategy {
247 Strategy::UrlEncode => Ok(url_encode(payload)),
248 Strategy::UrlEncodeLower => Ok(url_encode_lower(payload)),
249 Strategy::DoubleUrlEncode => Ok(double_url_encode(payload)),
250 Strategy::TripleUrlEncode => Ok(triple_url_encode(payload)),
251 Strategy::UnicodeEncode => {
252 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
253 Ok(unicode_encode(text))
254 }
255 Strategy::IisUnicodeEncode => {
256 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
257 Ok(iis_unicode_encode(text))
258 }
259 Strategy::JsonEncode => {
260 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
261 Ok(json_string_encode(text))
262 }
263 Strategy::HtmlEntityEncode => {
264 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
265 Ok(html_entity_encode(text))
266 }
267 Strategy::HtmlEntityDecimalEncode => {
268 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
269 Ok(html_entity_decimal_encode(text))
270 }
271 Strategy::CaseAlternation => {
272 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
273 Ok(case_alternate(text))
274 }
275 Strategy::RandomCase => {
276 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
277 Ok(random_case_alternate(text))
278 }
279 Strategy::WhitespaceInsertion => {
280 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
281 Ok(whitespace_insert(text))
282 }
283 Strategy::SqlCommentInsertion => {
284 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
285 Ok(sql_comment_insert(text))
286 }
287 Strategy::MysqlVersionedComment => {
288 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
289 Ok(mysql_versioned_comment(text, 50_000))
290 }
291 Strategy::NullByte => Ok(null_byte_inject(payload)),
292 Strategy::OverlongUtf8 => Ok(overlong_utf8(payload)),
293 Strategy::OverlongUtf8More => Ok(overlong_utf8_more(payload)),
294 Strategy::ChunkedSplit => {
295 let body = chunked_split(payload, 1024)?.body;
296 String::from_utf8(body).map_err(|_| EncodeError::InvalidUtf8)
297 }
298 Strategy::ParameterPollution => Ok(parameter_pollute(payload)),
299 Strategy::Base64Encode => Ok(base64_encode(payload)),
300 Strategy::Base64UrlEncode => Ok(base64_url_encode(payload)),
301 Strategy::HexEncode => Ok(hex_encode(payload)),
302 Strategy::Utf7Encode => {
303 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
304 Ok(utf7_encode(text))
305 }
306 Strategy::GzipEncode => Ok(gzip_encode(payload)?),
307 Strategy::DeflateEncode => Ok(deflate_encode(payload)?),
308 Strategy::SpaceToComment => {
309 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
310 Ok(space_to_comment(text))
311 }
312 Strategy::SpaceToDash => {
313 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
314 Ok(space_to_dash(text))
315 }
316 Strategy::SpaceToHash => {
317 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
318 Ok(space_to_hash(text))
319 }
320 Strategy::SpaceToPlus => {
321 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
322 Ok(space_to_plus(text))
323 }
324 Strategy::SpaceToRandomBlank => {
325 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
326 Ok(space_to_random_blank(text))
327 }
328 Strategy::PercentagePrefix => {
329 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
330 Ok(percentage_prefix(text))
331 }
332 Strategy::BetweenObfuscation => {
333 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
334 Ok(between_obfuscate(text))
335 }
336 Strategy::UnmagicQuotes => Ok(unmagic_quotes(payload)),
337 Strategy::FullwidthEncode => {
338 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
339 Ok(fullwidth_encode(text))
340 }
341 Strategy::HomoglyphEncode => {
342 let text = std::str::from_utf8(payload).map_err(|_| EncodeError::InvalidUtf8)?;
343 Ok(homoglyph_encode(text))
344 }
345 }
346}
347
348#[must_use]
350pub fn all_strategies() -> Vec<Strategy> {
351 let mut strategies = vec![
352 Strategy::CaseAlternation,
353 Strategy::RandomCase,
354 Strategy::WhitespaceInsertion,
355 Strategy::SqlCommentInsertion,
356 Strategy::SpaceToPlus,
357 Strategy::SpaceToRandomBlank,
358 Strategy::SpaceToComment,
359 Strategy::SpaceToDash,
360 Strategy::SpaceToHash,
361 Strategy::UrlEncode,
362 Strategy::UrlEncodeLower,
363 Strategy::DoubleUrlEncode,
364 Strategy::UnicodeEncode,
365 Strategy::IisUnicodeEncode,
366 Strategy::JsonEncode,
367 Strategy::HtmlEntityEncode,
368 Strategy::HtmlEntityDecimalEncode,
369 Strategy::NullByte,
370 Strategy::PercentagePrefix,
371 Strategy::TripleUrlEncode,
372 Strategy::ChunkedSplit,
373 Strategy::ParameterPollution,
374 Strategy::MysqlVersionedComment,
375 Strategy::Base64Encode,
376 Strategy::Base64UrlEncode,
377 Strategy::OverlongUtf8,
378 Strategy::OverlongUtf8More,
379 Strategy::HexEncode,
380 Strategy::Utf7Encode,
381 Strategy::BetweenObfuscation,
382 Strategy::UnmagicQuotes,
383 Strategy::FullwidthEncode,
384 Strategy::HomoglyphEncode,
385 Strategy::GzipEncode,
386 Strategy::DeflateEncode,
387 ];
388 strategies.sort_by(|a, b| {
389 super::layered::aggressiveness(*a)
390 .partial_cmp(&super::layered::aggressiveness(*b))
391 .unwrap_or(std::cmp::Ordering::Equal)
392 });
393 strategies
394}