redstr/transformations/
cloudflare.rs1use crate::rng::SimpleRng;
2
3pub fn cloudflare_turnstile_variation(input: &str) -> String {
20 let mut rng = SimpleRng::new();
21
22 let patterns = [
24 format!("{}-{}", input, generate_random_suffix(&mut rng)),
25 format!("{}_{}", input, generate_random_suffix(&mut rng)),
26 format!("cf-turnstile-{}", input),
27 format!("turnstile-{}-{}", input, generate_random_suffix(&mut rng)),
28 ];
29
30 patterns[rng.next() as usize % patterns.len()].clone()
31}
32
33pub fn cloudflare_challenge_response(input: &str) -> String {
49 let mut rng = SimpleRng::new();
50
51 if input.contains("cf_clearance") || input.contains("__cf_bm") {
53 input
55 .chars()
56 .map(|c| {
57 match rng.next() % 10 {
58 0..=6 => c.to_string(),
59 7 => {
60 if c == '=' {
62 if rng.next() % 2 == 0 {
63 " = ".to_string()
64 } else {
65 "=".to_string()
66 }
67 } else {
68 c.to_string()
69 }
70 }
71 8 => {
72 if c == '_' && rng.next() % 3 == 0 {
74 "-".to_string()
75 } else if c == '-' && rng.next() % 3 == 0 {
76 "_".to_string()
77 } else {
78 c.to_string()
79 }
80 }
81 _ => c.to_string(),
82 }
83 })
84 .collect()
85 } else if input.contains("turnstile") || input.contains("challenge") {
86 let mut result = input.to_string();
88 if rng.next() % 2 == 0 {
89 result.push_str(&format!("-{}", generate_random_suffix(&mut rng)));
90 }
91 result
92 } else {
93 format!("{}-{}", input, generate_random_suffix(&mut rng))
95 }
96}
97
98pub fn tls_handshake_pattern(input: &str) -> String {
114 let mut rng = SimpleRng::new();
115
116 let separators = [":", ",", " ", "-"];
118 let separator = separators[rng.next() as usize % separators.len()];
119
120 input
121 .split(|c: char| [':', ',', ' ', '-'].contains(&c))
122 .enumerate()
123 .map(|(i, part)| {
124 if i > 0 {
125 format!("{}{}", separator, part)
126 } else {
127 part.to_string()
128 }
129 })
130 .collect::<Vec<_>>()
131 .join("")
132}
133
134pub fn canvas_fingerprint_variation(input: &str) -> String {
151 let mut rng = SimpleRng::new();
152
153 input
156 .chars()
157 .map(|c| {
158 match rng.next() % 15 {
159 0..=11 => c.to_string(),
160 12 => {
161 match c {
163 '0' => {
164 if rng.next() % 2 == 0 {
165 "O".to_string()
166 } else {
167 "0".to_string()
168 }
169 }
170 '1' => {
171 if rng.next() % 2 == 0 {
172 "l".to_string()
173 } else {
174 "1".to_string()
175 }
176 }
177 'O' => {
178 if rng.next() % 2 == 0 {
179 "0".to_string()
180 } else {
181 "O".to_string()
182 }
183 }
184 'l' => {
185 if rng.next() % 2 == 0 {
186 "1".to_string()
187 } else {
188 "l".to_string()
189 }
190 }
191 _ => c.to_string(),
192 }
193 }
194 13 => {
195 if c.is_whitespace() && rng.next() % 2 == 0 {
197 " ".to_string() } else {
199 c.to_string()
200 }
201 }
202 _ => c.to_string(),
203 }
204 })
205 .collect()
206}
207
208pub fn webgl_fingerprint_obfuscate(input: &str) -> String {
224 let mut rng = SimpleRng::new();
225
226 input
228 .chars()
229 .map(|c| {
230 match rng.next() % 12 {
231 0..=9 => c.to_string(),
232 10 => {
233 if c.is_ascii_digit() && rng.next() % 4 == 0 {
235 let digit = c.to_digit(10).unwrap();
237 let new_digit = (digit + 1) % 10;
238 char::from_digit(new_digit, 10).unwrap_or(c).to_string()
239 } else {
240 c.to_string()
241 }
242 }
243 _ => {
244 if c == '.' && rng.next() % 3 == 0 {
246 ".".to_string()
247 } else if c.is_alphabetic() && rng.next() % 5 == 0 {
248 if c.is_uppercase() {
249 c.to_lowercase().to_string()
250 } else {
251 c.to_uppercase().to_string()
252 }
253 } else {
254 c.to_string()
255 }
256 }
257 }
258 })
259 .collect()
260}
261
262pub fn font_fingerprint_consistency(input: &str) -> String {
278 let mut rng = SimpleRng::new();
279
280 if input.is_empty() {
282 return input.to_string();
283 }
284
285 let fonts: Vec<&str> = input
287 .split(|c: char| [',', ';'].contains(&c))
288 .map(|s| s.trim())
289 .filter(|s| !s.is_empty())
290 .collect();
291
292 if fonts.is_empty() {
293 return input.to_string();
294 }
295
296 let separators = [", ", ",", "; ", ";"];
297 let separator = separators[rng.next() as usize % separators.len()];
298
299 fonts
300 .iter()
301 .enumerate()
302 .map(|(i, font)| {
303 let mut font_str = font.to_string();
304 if rng.next() % 4 == 0 && !font_str.starts_with('"') {
306 font_str = format!("\"{}\"", font_str);
307 }
308 if i > 0 {
309 format!("{}{}", separator, font_str)
310 } else {
311 font_str
312 }
313 })
314 .collect::<Vec<_>>()
315 .join("")
316}
317
318fn generate_random_suffix(rng: &mut SimpleRng) -> String {
320 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
321 .chars()
322 .collect();
323 let length = 8 + (rng.next() % 8) as usize; (0..length)
325 .map(|_| chars[rng.next() as usize % chars.len()])
326 .collect()
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_cloudflare_turnstile_variation() {
335 let challenge = "challenge-token";
336 let result = cloudflare_turnstile_variation(challenge);
337 assert!(result.len() > challenge.len());
338 assert!(result.contains(challenge));
339 }
340
341 #[test]
342 fn test_cloudflare_challenge_response() {
343 let challenge = "cf_clearance=abc123";
344 let result = cloudflare_challenge_response(challenge);
345 assert!(result.len() > 0);
346 assert!(
348 result.to_lowercase().contains("cf_clearance")
349 || result.to_lowercase().contains("cf-clearance")
350 );
351 }
352
353 #[test]
354 fn test_tls_handshake_pattern() {
355 let pattern = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256";
356 let result = tls_handshake_pattern(pattern);
357 assert!(result.len() > 0);
358 assert!(result.contains("TLS"));
359 }
360
361 #[test]
362 fn test_canvas_fingerprint_variation() {
363 let canvas_data = "canvas-fingerprint-data";
364 let result = canvas_fingerprint_variation(canvas_data);
365 assert!(result.len() > 0);
366 assert!(result.contains("canvas"));
367 }
368
369 #[test]
370 fn test_webgl_fingerprint_obfuscate() {
371 let webgl_data = "WebGL 2.0 Renderer: ANGLE";
372 let result = webgl_fingerprint_obfuscate(webgl_data);
373 assert!(result.len() > 0);
374 assert!(result.to_lowercase().contains("webgl"));
375 }
376
377 #[test]
378 fn test_font_fingerprint_consistency() {
379 let font_list = "Arial, Helvetica, Times New Roman";
380 let result = font_fingerprint_consistency(font_list);
381 assert!(result.len() > 0);
382 assert!(result.to_lowercase().contains("arial"));
383 }
384
385 #[test]
386 fn test_cloudflare_challenge_response_turnstile() {
387 let challenge = "turnstile-challenge-123";
388 let result = cloudflare_challenge_response(challenge);
389 assert!(result.len() > 0);
390 assert!(result.contains("turnstile") || result.contains("challenge"));
391 }
392
393 #[test]
394 fn test_cloudflare_challenge_response_generic() {
395 let challenge = "generic-challenge";
396 let result = cloudflare_challenge_response(challenge);
397 assert!(result.len() > 0);
398 assert!(result.contains(challenge));
400 }
401
402 #[test]
403 fn test_cloudflare_turnstile_variation_empty_string() {
404 let result = cloudflare_turnstile_variation("");
405 assert!(result.len() > 0);
406 }
407
408 #[test]
409 fn test_cloudflare_turnstile_variation_unicode() {
410 let challenge = "challenge-测试-токен";
411 let result = cloudflare_turnstile_variation(challenge);
412 assert!(result.contains(challenge));
413 }
414
415 #[test]
416 fn test_cloudflare_challenge_response_cf_bm() {
417 let challenge = "__cf_bm=cookie123";
418 let result = cloudflare_challenge_response(challenge);
419 assert!(result.len() > 0);
420 assert!(result.to_lowercase().contains("cf_bm") || result.to_lowercase().contains("cf-bm"));
421 }
422
423 #[test]
424 fn test_cloudflare_challenge_response_empty_string() {
425 let result = cloudflare_challenge_response("");
426 assert!(result.len() > 0);
428 }
429
430 #[test]
431 fn test_tls_handshake_pattern_empty_string() {
432 let result = tls_handshake_pattern("");
433 assert_eq!(result, "");
434 }
435
436 #[test]
437 fn test_tls_handshake_pattern_single_cipher() {
438 let pattern = "TLS_AES_256_GCM_SHA384";
439 let result = tls_handshake_pattern(pattern);
440 assert!(result.contains("TLS"));
441 }
442
443 #[test]
444 fn test_tls_handshake_pattern_comma_separated() {
445 let pattern = "TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256";
446 let result = tls_handshake_pattern(pattern);
447 assert!(result.contains("TLS"));
448 }
449
450 #[test]
451 fn test_tls_handshake_pattern_space_separated() {
452 let pattern = "TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256";
453 let result = tls_handshake_pattern(pattern);
454 assert!(result.contains("TLS"));
455 }
456
457 #[test]
458 fn test_canvas_fingerprint_variation_empty_string() {
459 let result = canvas_fingerprint_variation("");
460 assert_eq!(result, "");
461 }
462
463 #[test]
464 fn test_canvas_fingerprint_variation_unicode() {
465 let canvas_data = "canvas-测试-data";
466 let result = canvas_fingerprint_variation(canvas_data);
467 assert!(result.len() > 0);
468 }
469
470 #[test]
471 fn test_canvas_fingerprint_variation_preserves_length_approximately() {
472 let canvas_data = "test123";
473 let result = canvas_fingerprint_variation(canvas_data);
474 assert!(result.len() >= canvas_data.len() - 2);
476 assert!(result.len() <= canvas_data.len() + 2);
477 }
478
479 #[test]
480 fn test_webgl_fingerprint_obfuscate_empty_string() {
481 let result = webgl_fingerprint_obfuscate("");
482 assert_eq!(result, "");
483 }
484
485 #[test]
486 fn test_webgl_fingerprint_obfuscate_with_version() {
487 let webgl_data = "WebGL 2.0";
488 let result = webgl_fingerprint_obfuscate(webgl_data);
489 assert!(result.len() > 0);
490 assert!(result.to_lowercase().contains("webgl"));
491 }
492
493 #[test]
494 fn test_webgl_fingerprint_obfuscate_preserves_structure() {
495 let webgl_data = "WebGL 2.0 Renderer: ANGLE";
496 let result = webgl_fingerprint_obfuscate(webgl_data);
497 assert!(result.to_lowercase().contains("webgl"));
499 assert!(result.to_lowercase().contains("renderer"));
500 }
501
502 #[test]
503 fn test_font_fingerprint_consistency_empty_string() {
504 let result = font_fingerprint_consistency("");
505 assert_eq!(result, "");
506 }
507
508 #[test]
509 fn test_font_fingerprint_consistency_single_font() {
510 let font_list = "Arial";
511 let result = font_fingerprint_consistency(font_list);
512 assert!(result.len() > 0);
513 assert!(result.to_lowercase().contains("arial"));
514 }
515
516 #[test]
517 fn test_font_fingerprint_consistency_semicolon_separated() {
518 let font_list = "Arial; Helvetica; Times New Roman";
519 let result = font_fingerprint_consistency(font_list);
520 assert!(result.len() > 0);
521 assert!(result.to_lowercase().contains("arial"));
522 }
523
524 #[test]
525 fn test_font_fingerprint_consistency_preserves_fonts() {
526 let font_list = "Arial, Helvetica, Times New Roman";
527 let result = font_fingerprint_consistency(font_list);
528 assert!(result.to_lowercase().contains("arial"));
530 assert!(result.to_lowercase().contains("helvetica"));
531 assert!(result.to_lowercase().contains("times"));
532 }
533
534 #[test]
535 fn test_cloudflare_turnstile_variation_multiple_calls() {
536 let challenge = "test-challenge";
537 let results: Vec<String> = (0..10)
538 .map(|_| cloudflare_turnstile_variation(challenge))
539 .collect();
540 for result in &results {
542 assert!(result.contains(challenge));
543 }
544 let unique_results: std::collections::HashSet<&String> = results.iter().collect();
546 assert!(unique_results.len() >= 1);
548 }
549
550 #[test]
551 fn test_cloudflare_challenge_response_special_characters() {
552 let challenge = "cf_clearance=abc!@#$%^&*()123";
553 let result = cloudflare_challenge_response(challenge);
554 assert!(result.len() > 0);
555 assert!(
556 result.to_lowercase().contains("cf_clearance")
557 || result.to_lowercase().contains("cf-clearance")
558 );
559 }
560
561 #[test]
562 fn test_tls_handshake_pattern_preserves_ciphers() {
563 let pattern = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256";
564 let result = tls_handshake_pattern(pattern);
565 assert!(result.contains("TLS_AES_256_GCM_SHA384"));
567 assert!(result.contains("TLS_CHACHA20_POLY1305_SHA256"));
568 }
569
570 #[test]
571 fn test_cloudflare_turnstile_variation_long_string() {
572 let challenge = "a".repeat(1000);
573 let result = cloudflare_turnstile_variation(&challenge);
574 assert!(result.len() > challenge.len());
575 assert!(result.contains(&challenge));
576 }
577
578 #[test]
579 fn test_cloudflare_challenge_response_long_string() {
580 let challenge = "cf_clearance=".to_string() + &"a".repeat(500);
581 let result = cloudflare_challenge_response(&challenge);
582 assert!(result.len() > 0);
583 assert!(
584 result.to_lowercase().contains("cf_clearance")
585 || result.to_lowercase().contains("cf-clearance")
586 );
587 }
588
589 #[test]
590 fn test_tls_handshake_pattern_long_list() {
591 let pattern = (0..20)
592 .map(|i| format!("TLS_CIPHER_{}", i))
593 .collect::<Vec<_>>()
594 .join(":");
595 let result = tls_handshake_pattern(&pattern);
596 assert!(result.len() > 0);
597 assert!(result.contains("TLS_CIPHER_0"));
598 assert!(result.contains("TLS_CIPHER_19"));
599 }
600
601 #[test]
602 fn test_canvas_fingerprint_variation_special_chars_only() {
603 let canvas_data = "!@#$%^&*()";
604 let result = canvas_fingerprint_variation(canvas_data);
605 assert!(result.len() > 0);
606 }
607
608 #[test]
609 fn test_webgl_fingerprint_obfuscate_long_string() {
610 let webgl_data = "WebGL ".to_string() + &"2.0 ".repeat(100);
611 let result = webgl_fingerprint_obfuscate(&webgl_data);
612 assert!(result.len() > 0);
613 assert!(result.to_lowercase().contains("webgl"));
614 }
615
616 #[test]
617 fn test_font_fingerprint_consistency_long_list() {
618 let fonts: Vec<String> = (0..50).map(|i| format!("Font{}", i)).collect();
619 let font_list = fonts.join(", ");
620 let result = font_fingerprint_consistency(&font_list);
621 assert!(result.len() > 0);
622 assert!(result.to_lowercase().contains("font0"));
623 assert!(result.to_lowercase().contains("font49"));
624 }
625
626 #[test]
627 fn test_cloudflare_turnstile_variation_randomness() {
628 let challenge = "test-challenge";
629 let mut results = std::collections::HashSet::new();
630 for _ in 0..50 {
632 results.insert(cloudflare_turnstile_variation(challenge));
633 }
634 assert!(results.len() >= 1);
637 }
638
639 #[test]
640 fn test_cloudflare_challenge_response_randomness() {
641 let challenge = "cf_clearance=test123";
642 let mut results = std::collections::HashSet::new();
643 for _ in 0..50 {
644 results.insert(cloudflare_challenge_response(challenge));
645 }
646 assert!(results.len() >= 1);
648 }
649
650 #[test]
651 fn test_tls_handshake_pattern_variation() {
652 let pattern = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256";
653 let mut results = std::collections::HashSet::new();
654 for _ in 0..20 {
655 results.insert(tls_handshake_pattern(pattern));
656 }
657 assert!(results.len() >= 1);
659 }
660
661 #[test]
662 fn test_font_fingerprint_consistency_variation() {
663 let font_list = "Arial, Helvetica";
664 let mut results = std::collections::HashSet::new();
665 for _ in 0..20 {
666 results.insert(font_fingerprint_consistency(font_list));
667 }
668 assert!(results.len() >= 1);
670 }
671
672 #[test]
673 fn test_cloudflare_functions_preserve_non_empty_output() {
674 let test_cases = vec![
675 "test",
676 "challenge-token-123",
677 "cf_clearance=abc",
678 "TLS_AES_256_GCM_SHA384",
679 "canvas-data",
680 "WebGL 2.0",
681 "Arial, Helvetica",
682 ];
683
684 for input in test_cases {
685 assert!(
686 cloudflare_turnstile_variation(input).len() > 0,
687 "turnstile failed for: {}",
688 input
689 );
690 assert!(
691 cloudflare_challenge_response(input).len() > 0,
692 "challenge_response failed for: {}",
693 input
694 );
695 let _ = tls_handshake_pattern(input);
697 let _ = canvas_fingerprint_variation(input);
698 let _ = webgl_fingerprint_obfuscate(input);
699 let _ = font_fingerprint_consistency(input);
700 }
701 }
702
703 #[test]
704 fn test_cloudflare_challenge_response_all_cookie_formats() {
705 let formats = vec![
706 "cf_clearance=token123",
707 "__cf_bm=cookie456",
708 "cf-clearance=token789",
709 "__cf-bm=cookie012",
710 ];
711
712 for format in formats {
713 let result = cloudflare_challenge_response(format);
714 assert!(result.len() > 0, "Failed for format: {}", format);
715 let lower = result.to_lowercase();
717 assert!(
718 lower.contains("cf") || lower.contains("clearance") || lower.contains("bm"),
719 "Result doesn't contain cookie identifier: {}",
720 result
721 );
722 }
723 }
724}