use ip_extract::{ExtractorBuilder, IpKind, IpMatch};
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ops::Range;
fn check_extraction(
haystack: &[u8],
expected: &[&str],
include_private: bool,
include_loopback: bool,
) {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.private_ips(include_private)
.loopback_ips(include_loopback)
.build()
.expect("Failed to build extractor");
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(
actual,
expected.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
"\nFailed for haystack: {}\n",
String::from_utf8_lossy(haystack)
);
}
#[test]
fn test_ipv4_variations() {
check_extraction(
b"Standard: 8.8.8.8, LAN: 192.168.1.1, Loop: 127.0.0.1",
&["8.8.8.8", "192.168.1.1", "127.0.0.1"],
true, true, );
}
#[test]
fn test_ipv6_variations() {
check_extraction(
b"Full: 2001:0db8:85a3:0000:0000:8a2e:0370:7334, Compressed: 2001:db8::1, Local: ::1",
&[
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:db8::1",
"::1",
],
true,
true,
);
}
#[test]
fn test_filtering() {
let haystack = b"Public: 1.1.1.1, Private: 10.0.0.1, Loopback: 127.0.0.1";
check_extraction(haystack, &["1.1.1.1"], false, false);
check_extraction(haystack, &["1.1.1.1", "10.0.0.1"], true, false);
check_extraction(haystack, &["1.1.1.1", "10.0.0.1", "127.0.0.1"], true, true);
}
#[test]
fn test_delimiters_and_junk() {
check_extraction(
b"text 1.1.1.1 text, 2.2.2.2; [3.3.3.3] (8.8.8.8)",
&["1.1.1.1", "2.2.2.2", "3.3.3.3", "8.8.8.8"],
true,
true,
);
}
#[test]
fn test_invalid_ips() {
check_extraction(
b"Not an IP: 256.256.256.256, 1.2.3, 1.2.3.4.5, ::::, 127.0.0.01",
&[],
true,
true,
);
}
#[test]
fn test_mixed_versions_tight() {
check_extraction(
b"1.1.1.1, ::1, 8.8.8.8",
&["1.1.1.1", "::1", "8.8.8.8"],
true,
true,
);
}
#[test]
fn test_ipv4_boundary_values() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(false)
.build()
.unwrap();
let haystack = b"Min: 0.0.0.0, Max: 255.255.255.255, Normal: 192.168.1.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(actual, vec!["0.0.0.0", "255.255.255.255", "192.168.1.1"]);
}
#[test]
fn test_ipv4_single_digit_octets() {
check_extraction(
b"Single digits: 1.2.3.4, 5.6.7.8",
&["1.2.3.4", "5.6.7.8"],
true,
true,
);
}
#[test]
fn test_ipv4_all_private_ranges() {
check_extraction(
b"Class A: 10.0.0.1, Class B: 172.16.0.1, Class C: 192.168.0.1",
&["10.0.0.1", "172.16.0.1", "192.168.0.1"],
true,
false,
);
check_extraction(
b"Class A: 10.0.0.1, Class B: 172.16.0.1, Class C: 192.168.0.1, Public: 8.8.8.8",
&["8.8.8.8"],
false,
false,
);
}
#[test]
fn test_ipv4_middle_private_range() {
check_extraction(
b"Start: 172.16.0.0, Mid: 172.20.0.1, End: 172.31.255.255",
&["172.16.0.0", "172.20.0.1", "172.31.255.255"],
true,
false,
);
check_extraction(
b"Private: 172.16.0.1, Public: 172.32.0.1",
&["172.32.0.1"],
false,
false,
);
}
#[test]
fn test_ipv4_link_local() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(false)
.ignore_broadcast()
.build()
.unwrap();
let haystack = b"Link-local: 169.254.1.1, Public: 8.8.8.8";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(actual, vec!["8.8.8.8"]);
}
#[test]
fn test_ipv4_broadcast_address() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(false)
.ignore_broadcast()
.build()
.unwrap();
let haystack = b"Broadcast: 255.255.255.255, Public: 8.8.8.8";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(actual, vec!["8.8.8.8"]);
}
#[test]
fn test_ipv4_loopback_range() {
check_extraction(
b"Loop start: 127.0.0.1, Loop mid: 127.100.50.25, Loop end: 127.255.255.254",
&["127.0.0.1", "127.100.50.25", "127.255.255.254"],
true,
true,
);
check_extraction(
b"Loop: 127.0.0.1, Public: 8.8.8.8",
&["8.8.8.8"],
true,
false,
);
}
#[test]
fn test_ipv4_leading_zeros_rejected() {
check_extraction(
b"Invalid: 192.168.01.1, 192.168.001.1, 01.2.3.4",
&[],
true,
true,
);
}
#[test]
fn test_ipv4_trailing_dot() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack1 = b"192.168.1.1";
let ranges1: Vec<_> = extractor.find_iter(haystack1).collect();
assert_eq!(ranges1.len(), 1);
assert_eq!(&haystack1[ranges1[0].clone()], b"192.168.1.1");
let haystack2 = b"text 192.168.1.1 more text";
let ranges2: Vec<_> = extractor.find_iter(haystack2).collect();
assert_eq!(ranges2.len(), 1);
assert_eq!(&haystack2[ranges2[0].clone()], b"192.168.1.1");
let haystack3 = b"IPs: 1.2.3.4, 5.6.7.8; (9.10.11.12):end";
let ranges3: Vec<_> = extractor.find_iter(haystack3).collect();
assert_eq!(ranges3.len(), 3);
assert_eq!(&haystack3[ranges3[0].clone()], b"1.2.3.4");
assert_eq!(&haystack3[ranges3[1].clone()], b"5.6.7.8");
assert_eq!(&haystack3[ranges3[2].clone()], b"9.10.11.12");
let haystack4 = b"The C2 IP was 192.168.1.1.";
let ranges4: Vec<_> = extractor.find_iter(haystack4).collect();
assert_eq!(ranges4.len(), 1);
assert_eq!(&haystack4[ranges4[0].clone()], b"192.168.1.1");
let haystack5 = b"First: 1.2.3.4. Second: 5.6.7.8. End.";
let ranges5: Vec<_> = extractor.find_iter(haystack5).collect();
assert_eq!(ranges5.len(), 2);
assert_eq!(&haystack5[ranges5[0].clone()], b"1.2.3.4");
assert_eq!(&haystack5[ranges5[1].clone()], b"5.6.7.8");
}
#[test]
fn test_ipv4_edge_of_input() {
check_extraction(b"192.168.1.1", &["192.168.1.1"], true, true);
check_extraction(
b"Start: 1.1.1.1 End: 8.8.8.8",
&["1.1.1.1", "8.8.8.8"],
true,
true,
);
}
#[test]
fn test_ipv6_compressed_forms() {
check_extraction(
b"All zeros: ::, Loopback: ::1, Prefix: 2001:db8::",
&["::", "::1", "2001:db8::"],
true,
true,
);
let extractor = ExtractorBuilder::new().ipv6(true).build().unwrap();
let haystack = b"IPv4-mapped: ::ffff:192.0.2.1";
let matches: Vec<_> = extractor.find_iter(haystack).collect();
assert!(matches.len() <= 1);
}
#[test]
fn test_ipv6_ula_private_range() {
check_extraction(
b"ULA start: fc00::1, ULA end: fdff::1, Public: 2001:db8::1",
&["fc00::1", "fdff::1", "2001:db8::1"],
true,
false,
);
check_extraction(
b"ULA: fc00::1, Link-local: fe80::1, Public: 2001:db8::1",
&["2001:db8::1"],
false,
false,
);
}
#[test]
fn test_ipv6_link_local() {
check_extraction(
b"Link-local: fe80::1, Public: 2001:db8::1",
&["fe80::1", "2001:db8::1"],
true,
false,
);
check_extraction(
b"Link-local: fe80::1, Public: 2001:db8::1",
&["2001:db8::1"],
false,
false,
);
}
#[test]
fn test_ipv6_loopback() {
check_extraction(
b"Loopback: ::1, Public: 2001:db8::1",
&["::1", "2001:db8::1"],
true,
true,
);
check_extraction(
b"Loopback: ::1, Public: 2001:db8::1",
&["2001:db8::1"],
true,
false,
);
}
#[test]
fn test_ipv6_full_form() {
check_extraction(
b"Full: 2001:0db8:0000:0000:0000:0000:0000:0001",
&["2001:0db8:0000:0000:0000:0000:0000:0001"],
true,
true,
);
}
#[test]
fn test_ipv6_invalid_double_compression() {
check_extraction(b"Invalid: 2001::db8::1", &[], true, true);
}
#[test]
fn test_all_filters_combination() {
let haystack = b"Public4: 8.8.8.8, Private4: 10.0.0.1, Loop4: 127.0.0.1, \
Public6: 2001:db8::1, ULA6: fc00::1, Loop6: ::1";
check_extraction(
haystack,
&[
"8.8.8.8",
"10.0.0.1",
"127.0.0.1",
"2001:db8::1",
"fc00::1",
"::1",
],
true,
true,
);
check_extraction(haystack, &["8.8.8.8", "2001:db8::1"], false, false);
check_extraction(
haystack,
&["8.8.8.8", "10.0.0.1", "2001:db8::1", "fc00::1"],
true,
false,
);
}
#[test]
fn test_ipv4_only_extractor() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(false)
.build()
.unwrap();
let haystack = b"IPv4: 192.168.1.1, IPv6: 2001:db8::1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(actual, vec!["192.168.1.1"]);
let haystack2 = b"Servers: 10.0.0.1, 8.8.8.8, fe80::1, 2001:db8::1";
let actual2: Vec<String> = extractor
.find_iter(haystack2)
.map(|range| String::from_utf8_lossy(&haystack2[range]).to_string())
.collect();
assert_eq!(actual2, vec!["10.0.0.1", "8.8.8.8"]);
let haystack3 = b"IPv6 only: 2001:db8::1, ::1, fe80::dead:beef";
let actual3: Vec<String> = extractor
.find_iter(haystack3)
.map(|range| String::from_utf8_lossy(&haystack3[range]).to_string())
.collect();
assert_eq!(actual3.len(), 0);
}
#[test]
fn test_ipv6_only_extractor() {
let extractor = ExtractorBuilder::new()
.ipv4(false)
.ipv6(true)
.build()
.unwrap();
let haystack = b"IPv4: 192.168.1.1, IPv6: 2001:db8::1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|range| String::from_utf8_lossy(&haystack[range]).to_string())
.collect();
assert_eq!(actual, vec!["2001:db8::1"]);
let haystack2 = b"Servers: 10.0.0.1, fe80::1, 8.8.8.8, 2001:db8::1";
let actual2: Vec<String> = extractor
.find_iter(haystack2)
.map(|range| String::from_utf8_lossy(&haystack2[range]).to_string())
.collect();
assert_eq!(actual2, vec!["fe80::1", "2001:db8::1"]);
let haystack3 = b"IPv4 only: 192.168.1.1, 10.0.0.1, 127.0.0.1";
let actual3: Vec<String> = extractor
.find_iter(haystack3)
.map(|range| String::from_utf8_lossy(&haystack3[range]).to_string())
.collect();
assert_eq!(actual3.len(), 0);
let haystack4 = b"Formats: ::1, 2001:db8::, fe80::dead:beef, fc00::1";
let actual4: Vec<String> = extractor
.find_iter(haystack4)
.map(|range| String::from_utf8_lossy(&haystack4[range]).to_string())
.collect();
assert_eq!(
actual4,
vec!["::1", "2001:db8::", "fe80::dead:beef", "fc00::1"]
);
}
#[test]
fn test_neither_ipv4_nor_ipv6() {
let result = ExtractorBuilder::new().ipv4(false).ipv6(false).build();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No IP address patterns selected"));
}
#[test]
fn test_parse_ipv4_bytes_public_function() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"192.168.1.1").is_some());
assert!(parse_ipv4_bytes(b"0.0.0.0").is_some());
assert!(parse_ipv4_bytes(b"255.255.255.255").is_some());
assert!(parse_ipv4_bytes(b"8.8.8.8").is_some());
assert!(parse_ipv4_bytes(b"256.1.1.1").is_none()); assert!(parse_ipv4_bytes(b"192.168.01.1").is_none()); assert!(parse_ipv4_bytes(b"1.2.3.4.5").is_none()); assert!(parse_ipv4_bytes(b"1.2.3").is_none()); assert!(parse_ipv4_bytes(b"").is_none()); assert!(parse_ipv4_bytes(b"not.an.ip.addr").is_none()); }
#[test]
fn test_json_string_with_single_ip() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.build()
.unwrap();
let json1 = br#"{"src_ip":"192.168.1.1"}"#;
let ranges1: Vec<_> = extractor.find_iter(json1).collect();
assert_eq!(ranges1.len(), 1);
assert_eq!(&json1[ranges1[0].clone()], b"192.168.1.1");
let json2 = br#"{"dst_ip":"2001:db8::1"}"#;
let ranges2: Vec<_> = extractor.find_iter(json2).collect();
assert_eq!(ranges2.len(), 1);
assert_eq!(&json2[ranges2[0].clone()], b"2001:db8::1");
let json3 = br#"{"msg":"192.168.1.1 connected"}"#;
let ranges3: Vec<_> = extractor.find_iter(json3).collect();
assert_eq!(ranges3.len(), 1);
assert_eq!(&json3[ranges3[0].clone()], b"192.168.1.1");
let json4 = br#"{"msg":"Connection from 10.0.0.5"}"#;
let ranges4: Vec<_> = extractor.find_iter(json4).collect();
assert_eq!(ranges4.len(), 1);
assert_eq!(&json4[ranges4[0].clone()], b"10.0.0.5");
let json5 = br#"{"msg":"Host 172.16.0.1 responded"}"#;
let ranges5: Vec<_> = extractor.find_iter(json5).collect();
assert_eq!(ranges5.len(), 1);
assert_eq!(&json5[ranges5[0].clone()], b"172.16.0.1");
}
#[test]
fn test_json_string_with_multiple_ips() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.build()
.unwrap();
let json1 = br#"{"syslog":"2024-01-15 Connection from 192.168.1.100 to 10.0.0.50 port 443"}"#;
let ranges1: Vec<_> = extractor.find_iter(json1).collect();
assert_eq!(ranges1.len(), 2);
assert_eq!(&json1[ranges1[0].clone()], b"192.168.1.100");
assert_eq!(&json1[ranges1[1].clone()], b"10.0.0.50");
let json2 = br#"{"log":"src=8.8.8.8 dst=2001:4860:4860::8888 proto=tcp"}"#;
let ranges2: Vec<_> = extractor.find_iter(json2).collect();
assert_eq!(ranges2.len(), 2);
assert_eq!(&json2[ranges2[0].clone()], b"8.8.8.8");
assert_eq!(&json2[ranges2[1].clone()], b"2001:4860:4860::8888");
let json3 = br#"{"ips":"1.1.1.1, 8.8.8.8; 9.9.9.9 | 1.0.0.1"}"#;
let ranges3: Vec<_> = extractor.find_iter(json3).collect();
assert_eq!(ranges3.len(), 4);
assert_eq!(&json3[ranges3[0].clone()], b"1.1.1.1");
assert_eq!(&json3[ranges3[1].clone()], b"8.8.8.8");
assert_eq!(&json3[ranges3[2].clone()], b"9.9.9.9");
assert_eq!(&json3[ranges3[3].clone()], b"1.0.0.1");
let json4 = br#"{"event":"firewall","src":"192.168.1.5","dst":"203.0.113.10","details":"Blocked connection from 192.168.1.5 to 203.0.113.10"}"#;
let ranges4: Vec<_> = extractor.find_iter(json4).collect();
assert_eq!(ranges4.len(), 4); assert_eq!(&json4[ranges4[0].clone()], b"192.168.1.5");
assert_eq!(&json4[ranges4[1].clone()], b"203.0.113.10");
assert_eq!(&json4[ranges4[2].clone()], b"192.168.1.5");
assert_eq!(&json4[ranges4[3].clone()], b"203.0.113.10");
let json5 = br#"{"ips":"","message":"no ips here"}"#;
let ranges5: Vec<_> = extractor.find_iter(json5).collect();
assert_eq!(ranges5.len(), 0);
}
#[test]
fn test_ipv6_scope_id_boundary() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.build()
.unwrap();
let input1 = b"Link-local: fe80::1%eth0";
let ranges1: Vec<_> = extractor.find_iter(input1).collect();
assert_eq!(ranges1.len(), 1);
assert_eq!(&input1[ranges1[0].clone()], b"fe80::1");
let input2 = b"Interfaces: fe80::1%eth0 fe80::dead:beef%en0 2001:db8::1%tun0";
let ranges2: Vec<_> = extractor.find_iter(input2).collect();
assert_eq!(ranges2.len(), 3);
assert_eq!(&input2[ranges2[0].clone()], b"fe80::1");
assert_eq!(&input2[ranges2[1].clone()], b"fe80::dead:beef");
assert_eq!(&input2[ranges2[2].clone()], b"2001:db8::1");
let input3 = b"fe80::cafe%wlan0 and 10.0.0.1";
let ranges3: Vec<_> = extractor.find_iter(input3).collect();
assert_eq!(ranges3.len(), 2);
assert_eq!(&input3[ranges3[0].clone()], b"fe80::cafe");
assert_eq!(&input3[ranges3[1].clone()], b"10.0.0.1");
}
#[test]
fn test_adversarial_aggressive_backtracking() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"999.999.999.999").is_none()); assert!(parse_ipv4_bytes(b"299.299.299.299").is_none());
assert!(parse_ipv4_bytes(b"192.168.999.1").is_none()); assert!(parse_ipv4_bytes(b"192.999.1.1").is_none());
assert!(parse_ipv4_bytes(b"9.9.9.9.9").is_none()); assert!(parse_ipv4_bytes(b"9.9.9.9.9.9").is_none());
assert!(parse_ipv4_bytes(b"9.9.9.9").is_some());
}
#[test]
fn test_adversarial_extraction_repeating_pattern() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"9.9.9.9.9.9.9.9 then 1.1.1.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual, vec!["1.1.1.1"]);
}
#[test]
fn test_adversarial_max_value_boundaries() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"256.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"0.256.0.0").is_none());
assert!(parse_ipv4_bytes(b"0.0.256.0").is_none());
assert!(parse_ipv4_bytes(b"0.0.0.256").is_none());
assert!(parse_ipv4_bytes(b"254.254.254.254").is_some());
assert!(parse_ipv4_bytes(b"255.255.255.255").is_some());
assert!(parse_ipv4_bytes(b"255.254.253.256").is_none()); assert!(parse_ipv4_bytes(b"256.254.253.252").is_none()); }
#[test]
fn test_adversarial_prefix_suffix_collisions() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"256.256.256.256").is_none());
assert!(parse_ipv4_bytes(b"299.299.299.299").is_none());
assert!(parse_ipv4_bytes(b"250.250.250.250").is_some());
assert!(parse_ipv4_bytes(b"260.260.260.260").is_none());
}
#[test]
fn test_adversarial_extraction_long_sequence() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9.9";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual.len(), 0);
}
#[test]
fn test_adversarial_boundary_256_variants() {
use ip_extract::parse_ipv4_bytes;
let test_cases = vec![
(b"256.0.0.0", 0),
(b"0.256.0.0", 1),
(b"0.0.256.0", 2),
(b"0.0.0.256", 3),
];
for (ip_bytes, pos) in test_cases {
assert!(
parse_ipv4_bytes(ip_bytes).is_none(),
"Failed for position {}",
pos
);
}
assert!(parse_ipv4_bytes(b"255.255.255.255").is_some());
}
#[test]
fn test_adversarial_extraction_octet_overflow_context() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"Start: 1.1.1.1, Invalid: 256.256.256.256, End: 8.8.8.8";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual, vec!["1.1.1.1", "8.8.8.8"]);
}
#[test]
fn test_adversarial_extraction_multiple_overflow_patterns() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"999.999.999.999 and 8.8.8.8 and 300.300.300.300 and 1.1.1.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual, vec!["8.8.8.8", "1.1.1.1"]);
}
#[test]
fn test_adversarial_extraction_dot_density() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"1.2.3.4.5.6.7.8.9";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual.len(), 0);
}
#[test]
fn test_adversarial_leading_zeros_aggressive() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"0255.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"0256.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"0199.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"0100.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"099.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"010.0.0.0").is_none());
assert!(parse_ipv4_bytes(b"255.0.0.0").is_some());
assert!(parse_ipv4_bytes(b"199.0.0.0").is_some());
assert!(parse_ipv4_bytes(b"100.0.0.0").is_some());
assert!(parse_ipv4_bytes(b"99.0.0.0").is_some());
assert!(parse_ipv4_bytes(b"10.0.0.0").is_some());
}
#[test]
fn test_adversarial_extraction_leading_zeros_with_overflow() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"Valid: 192.168.1.1, LeadingZero: 192.168.01.1, Overflow: 192.168.256.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual, vec!["192.168.1.1"]);
}
#[test]
fn test_adversarial_extraction_all_positions_overflow() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let cases = vec![
(b"256.1.1.1" as &[u8], "first octet overflow"),
(b"1.256.1.1", "second octet overflow"),
(b"1.1.256.1", "third octet overflow"),
(b"1.1.1.256", "fourth octet overflow"),
];
for (haystack, _description) in cases {
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual.len(), 0);
}
}
#[test]
fn test_adversarial_digit_accumulation() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"25.1.1.1").is_some());
assert!(parse_ipv4_bytes(b"255.1.1.1").is_some());
assert!(parse_ipv4_bytes(b"256.1.1.1").is_none());
assert!(parse_ipv4_bytes(b"2.1.1.1").is_some());
assert!(parse_ipv4_bytes(b"20.1.1.1").is_some());
assert!(parse_ipv4_bytes(b"200.1.1.1").is_some());
assert!(parse_ipv4_bytes(b"2000.1.1.1").is_none()); }
#[test]
fn test_adversarial_sequential_boundary_crossing() {
use ip_extract::parse_ipv4_bytes;
assert!(parse_ipv4_bytes(b"99.99.99.99").is_some());
assert!(parse_ipv4_bytes(b"100.100.100.100").is_some());
assert!(parse_ipv4_bytes(b"199.199.199.199").is_some());
assert!(parse_ipv4_bytes(b"200.200.200.200").is_some());
assert!(parse_ipv4_bytes(b"249.249.249.249").is_some());
assert!(parse_ipv4_bytes(b"250.250.250.250").is_some());
assert!(parse_ipv4_bytes(b"255.255.255.255").is_some());
assert!(parse_ipv4_bytes(b"256.256.256.256").is_none());
}
#[test]
fn test_adversarial_lookback_boundary_40_chars() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack40 = b"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1";
let actual40: Vec<String> = extractor
.find_iter(haystack40)
.map(|r| String::from_utf8_lossy(&haystack40[r]).to_string())
.collect();
assert_eq!(
actual40.len(),
0,
"40-char all-IP-chars string should extract nothing (all have invalid boundaries)"
);
}
#[test]
fn test_adversarial_lookback_boundary_50_chars() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack50 = b"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1";
let actual50: Vec<String> = extractor
.find_iter(haystack50)
.map(|r| String::from_utf8_lossy(&haystack50[r]).to_string())
.collect();
assert_eq!(
actual50.len(),
0,
"50-char all-IP-chars string should extract nothing (all have invalid boundaries)"
);
}
#[test]
fn test_adversarial_lookback_boundary_exceeds_40() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.build()
.unwrap();
let haystack = b"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.\
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.\
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual.len(), 0);
}
#[test]
fn test_adversarial_lookback_boundary_with_valid_endpoint() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 end";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(
actual.len(),
0,
"Even with valid boundary, 50 chars of IP chars hits floor and fails validation"
);
}
#[test]
fn test_adversarial_lookback_floor_within_valid_ip() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack =
b"X1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.8.8.8.8";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(
actual.len(),
0,
"Long IP char sequence before target IP prevents extraction due to lookback floor"
);
}
#[test]
fn test_adversarial_lookback_valid_extraction_with_boundary() {
let extractor = ExtractorBuilder::new().ipv4(true).build().unwrap();
let haystack = b" 1.1.1.1";
let actual: Vec<String> = extractor
.find_iter(haystack)
.map(|r| String::from_utf8_lossy(&haystack[r]).to_string())
.collect();
assert_eq!(actual, vec!["1.1.1.1"]);
}
#[test]
fn test_extract_convenience_function() {
use ip_extract::extract;
let ips = extract(b"Server at 192.168.1.1 and 2001:db8::1").unwrap();
assert_eq!(ips, vec!["192.168.1.1", "2001:db8::1"]);
let ips = extract(b"1.1.1.1 and 1.1.1.1 again").unwrap();
assert_eq!(ips, vec!["1.1.1.1", "1.1.1.1"]);
let ips = extract(b"no ips here").unwrap();
assert!(ips.is_empty());
let ips = extract(b"Connect from 10.0.0.1 to 192.168.1.1, via 8.8.8.8").unwrap();
assert_eq!(ips, vec!["10.0.0.1", "192.168.1.1", "8.8.8.8"]);
}
#[test]
fn test_extract_unique_convenience_function() {
use ip_extract::extract_unique;
let ips = extract_unique(b"1.1.1.1 and 1.1.1.1 again").unwrap();
assert_eq!(ips, vec!["1.1.1.1"]);
let ips = extract_unique(b"8.8.8.8 then 1.1.1.1 then 1.1.1.1 then 8.8.8.8").unwrap();
assert_eq!(ips, vec!["8.8.8.8", "1.1.1.1"]);
let ips = extract_unique(b"2001:db8::1, 192.168.1.1, 2001:db8::1").unwrap();
assert_eq!(ips, vec!["2001:db8::1", "192.168.1.1"]);
let ips = extract_unique(b"no ips here").unwrap();
assert!(ips.is_empty());
let ips = extract_unique(b"1.1.1.1, 2.2.2.2, 3.3.3.3").unwrap();
assert_eq!(ips, vec!["1.1.1.1", "2.2.2.2", "3.3.3.3"]);
}
#[test]
fn test_extract_unique_order_preservation() {
use ip_extract::extract_unique;
let input = b"9.9.9.9, 1.1.1.1, 5.5.5.5, 3.3.3.3";
let ips = extract_unique(input).unwrap();
assert_eq!(ips, vec!["9.9.9.9", "1.1.1.1", "5.5.5.5", "3.3.3.3"]);
assert_ne!(ips, vec!["1.1.1.1", "3.3.3.3", "5.5.5.5", "9.9.9.9"]);
}
#[test]
fn test_extract_parsed_returns_ipaddr() {
use ip_extract::extract_parsed;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
let ips = extract_parsed(b"Server at 192.168.1.1 and 8.8.8.8").unwrap();
assert_eq!(ips.len(), 2);
assert_eq!(ips[0], IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
assert_eq!(ips[1], IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
let ips = extract_parsed(b"Connect to 2001:db8::1").unwrap();
assert_eq!(ips.len(), 1);
assert!(ips[0].is_ipv6());
assert_eq!(
ips[0],
IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
);
let ips = extract_parsed(b"from 10.0.0.1 to ::1").unwrap();
assert_eq!(ips.len(), 2);
assert!(ips[0].is_ipv4());
assert!(ips[1].is_ipv6());
let ips = extract_parsed(b"1.1.1.1 and 1.1.1.1").unwrap();
assert_eq!(ips.len(), 2);
let ips = extract_parsed(b"no ips here").unwrap();
assert!(ips.is_empty());
}
#[test]
fn test_extract_unique_parsed_returns_unique_ipaddr() {
use ip_extract::extract_unique_parsed;
use std::net::{IpAddr, Ipv4Addr};
let ips = extract_unique_parsed(b"1.1.1.1 and 1.1.1.1 again").unwrap();
assert_eq!(ips.len(), 1);
assert_eq!(ips[0], IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)));
let ips = extract_unique_parsed(b"8.8.8.8 then 1.1.1.1 then 8.8.8.8").unwrap();
assert_eq!(ips.len(), 2);
assert_eq!(ips[0], IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
assert_eq!(ips[1], IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)));
let ips = extract_unique_parsed(b"2001:db8::1, 192.168.1.1, 2001:db8::1").unwrap();
assert_eq!(ips.len(), 2);
assert!(ips[0].is_ipv6());
assert!(ips[1].is_ipv4());
let ips = extract_unique_parsed(b"1.1.1.1 2.2.2.2 3.3.3.3").unwrap();
assert_eq!(ips.len(), 3);
}
#[test]
fn test_ip_match_as_bytes() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 192.168.1.1 done";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].as_bytes(), b"192.168.1.1");
}
#[test]
fn test_ip_match_as_matched_str() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"connect to 2001:db8::1 now";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].as_matched_str(), "2001:db8::1");
}
#[test]
fn test_ip_match_kind_v4() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"server 8.8.8.8 online";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].kind(), IpKind::V4);
}
#[test]
fn test_ip_match_kind_v6() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"server 2001:db8::1 online";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].kind(), IpKind::V6);
}
#[test]
fn test_ip_match_kind_mixed() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"from 10.0.0.1 to 2001:db8::1";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].kind(), IpKind::V4);
assert_eq!(matches[1].kind(), IpKind::V6);
}
#[test]
fn test_ip_match_ip_parse_v4() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 192.168.1.1";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches[0].ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
}
#[test]
fn test_ip_match_ip_parse_v6() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 2001:db8::1";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(
matches[0].ip(),
IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
);
}
#[test]
fn test_ip_match_range() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 10.0.0.1 end";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches[0].range(), 4..12);
assert_eq!(&haystack[matches[0].range()], b"10.0.0.1");
}
#[test]
fn test_match_iter_ranges_equal_find_iter() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"from 10.0.0.1 to 2001:db8::1 via 8.8.8.8";
let find_ranges: Vec<Range<usize>> = extractor.find_iter(haystack).collect();
let match_ranges: Vec<Range<usize>> =
extractor.match_iter(haystack).map(|m| m.range()).collect();
assert_eq!(find_ranges, match_ranges);
}
#[test]
fn test_match_iter_empty_haystack() {
let extractor = ExtractorBuilder::new().build().unwrap();
let matches: Vec<IpMatch> = extractor.match_iter(b"").collect();
assert!(matches.is_empty());
}
#[test]
fn test_match_iter_no_ips() {
let extractor = ExtractorBuilder::new().build().unwrap();
let matches: Vec<IpMatch> = extractor.match_iter(b"hello world no ips here").collect();
assert!(matches.is_empty());
}
#[test]
fn test_match_iter_ipv4_only_no_v6() {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(false)
.build()
.unwrap();
let haystack = b"v4: 10.0.0.1, v6: 2001:db8::1";
let matches: Vec<IpMatch> = extractor.match_iter(haystack).collect();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].kind(), IpKind::V4);
assert_eq!(matches[0].as_matched_str(), "10.0.0.1");
}
#[test]
fn test_replace_iter_no_ips() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"no ips here at all";
let mut out = Vec::new();
let count = extractor
.replace_iter(haystack, &mut out, |_m, _w| Ok(()))
.unwrap();
assert_eq!(count, 0);
assert_eq!(out, haystack);
}
#[test]
fn test_replace_iter_identity() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"from 10.0.0.1 to 2001:db8::1 done";
let mut out = Vec::new();
let count = extractor
.replace_iter(haystack, &mut out, |m, w| w.write_all(m.as_bytes()))
.unwrap();
assert_eq!(count, 2);
assert_eq!(out, haystack);
}
#[test]
fn test_replace_iter_single_ip_middle() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"server 8.8.8.8 is up";
let mut out = Vec::new();
extractor
.replace_iter(haystack, &mut out, |_m, w| w.write_all(b"[REDACTED]"))
.unwrap();
assert_eq!(out, b"server [REDACTED] is up");
}
#[test]
fn test_replace_iter_ip_at_start() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"8.8.8.8 is a DNS server";
let mut out = Vec::new();
extractor
.replace_iter(haystack, &mut out, |_m, w| w.write_all(b"[IP]"))
.unwrap();
assert_eq!(out, b"[IP] is a DNS server");
}
#[test]
fn test_replace_iter_ip_at_end() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"DNS server is 8.8.8.8";
let mut out = Vec::new();
extractor
.replace_iter(haystack, &mut out, |_m, w| w.write_all(b"[IP]"))
.unwrap();
assert_eq!(out, b"DNS server is [IP]");
}
#[test]
fn test_replace_iter_multiple_ips() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"from 10.0.0.1 to 10.0.0.2 via 10.0.0.3";
let mut out = Vec::new();
let count = extractor
.replace_iter(haystack, &mut out, |_m, w| w.write_all(b"X"))
.unwrap();
assert_eq!(count, 3);
assert_eq!(out, b"from X to X via X");
}
#[test]
fn test_replace_iter_callback_deletes() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"remove 10.0.0.1 please";
let mut out = Vec::new();
extractor
.replace_iter(haystack, &mut out, |_m, _w| Ok(()))
.unwrap();
assert_eq!(out, b"remove please");
}
#[test]
fn test_replace_iter_callback_expands() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 1.2.3.4";
let mut out = Vec::new();
extractor
.replace_iter(haystack, &mut out, |m, w| {
write!(w, "[{}={:?}]", m.as_matched_str(), m.kind())
})
.unwrap();
assert_eq!(out, b"ip: [1.2.3.4=V4]");
}
#[test]
fn test_replace_iter_returns_count() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"a 1.1.1.1 b 2.2.2.2 c 3.3.3.3 d";
let mut out = Vec::new();
let count = extractor
.replace_iter(haystack, &mut out, |m, w| w.write_all(m.as_bytes()))
.unwrap();
assert_eq!(count, 3);
}
#[test]
fn test_replace_iter_io_error_propagates() {
let extractor = ExtractorBuilder::new().build().unwrap();
let haystack = b"ip: 1.2.3.4";
let mut out = Vec::new();
let result = extractor.replace_iter(haystack, &mut out, |_m, _w| {
Err(std::io::Error::other("test error"))
});
assert!(result.is_err());
}
fn check_defang(haystack: &[u8], expected_refanged: &[&str]) {
let extractor = ExtractorBuilder::new()
.ipv4(true)
.ipv6(true)
.build()
.expect("Failed to build extractor");
let actual: Vec<String> = extractor
.match_iter(haystack)
.map(|m| m.as_str().to_string())
.collect();
assert_eq!(
actual,
expected_refanged
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
"\nFailed for haystack: {}\n",
String::from_utf8_lossy(haystack)
);
}
#[test]
fn test_defang_ipv4_basic() {
check_defang(b"Attacker at 192[.]168[.]1[.]1", &["192.168.1.1"]);
}
#[test]
fn test_defang_ipv4_mixed_with_fanged() {
check_defang(
b"Src: 10.0.0.1 Dst: 8[.]8[.]8[.]8",
&["10.0.0.1", "8.8.8.8"],
);
}
#[test]
fn test_defang_ipv4_all_defanged() {
check_defang(
b"192[.]168[.]1[.]1 and 10[.]0[.]0[.]1",
&["192.168.1.1", "10.0.0.1"],
);
}
#[test]
fn test_defang_ipv6_single_colons() {
check_defang(
b"IPv6: 2001[:]db8[:]0[:]0[:]0[:]0[:]0[:]1",
&["2001:db8:0:0:0:0:0:1"],
);
}
#[test]
fn test_defang_normal_ipv4_unchanged() {
check_defang(b"Standard: 8.8.8.8 and 1.1.1.1", &["8.8.8.8", "1.1.1.1"]);
}
#[test]
fn test_defang_normal_ipv6_unchanged() {
check_defang(b"Full: 2001:db8::1", &["2001:db8::1"]);
}
#[test]
fn test_ip_method_works_on_defanged_match() {
use std::net::IpAddr;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"192[.]168[.]1[.]1";
let m = extractor.match_iter(data).next().unwrap();
assert_eq!(m.ip(), "192.168.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_as_str_no_alloc_for_fanged() {
use std::borrow::Cow;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"8.8.8.8";
let m = extractor.match_iter(data).next().unwrap();
assert!(matches!(m.as_str(), Cow::Borrowed(_)));
}
#[test]
fn test_as_str_allocates_for_defanged() {
use std::borrow::Cow;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"8[.]8[.]8[.]8";
let m = extractor.match_iter(data).next().unwrap();
assert!(matches!(m.as_str(), Cow::Owned(_)));
assert_eq!(m.as_str().as_ref(), "8.8.8.8");
}
#[test]
fn test_defang_ipv4_last_octet_only() {
check_defang(b"192.168.1[.]50", &["192.168.1.50"]);
}
#[test]
fn test_defang_ipv4_first_separator_only() {
check_defang(b"192[.]168.0.1", &["192.168.0.1"]);
}
#[test]
fn test_defang_ipv4_two_separators() {
check_defang(b"10[.]0[.]0.1", &["10.0.0.1"]);
}
#[test]
fn test_defang_ipv4_all_separators() {
check_defang(b"8[.]8[.]8[.]8", &["8.8.8.8"]);
}
#[test]
fn test_defang_mixed_line() {
check_defang(b"from 1.2.3.4 to 5.6.7[.]8", &["1.2.3.4", "5.6.7.8"]);
}
#[test]
fn test_defang_ipv6_single_bracket_colon() {
check_defang(b"2001[:]db8::1", &["2001:db8::1"]);
}
#[test]
fn test_defang_normal_ipv4_no_regression() {
check_defang(b"Standard: 8.8.8.8 and 1.1.1.1", &["8.8.8.8", "1.1.1.1"]);
}
#[test]
fn test_defang_normal_ipv6_no_regression() {
check_defang(b"IPv6: 2001:db8::1", &["2001:db8::1"]);
}
#[test]
fn test_ip_method_on_defanged_match() {
use std::net::IpAddr;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"192.168.1[.]50";
let m = extractor.match_iter(data).next().unwrap();
assert_eq!(m.ip(), "192.168.1.50".parse::<IpAddr>().unwrap());
}
#[test]
fn test_as_str_borrowed_for_fanged() {
use std::borrow::Cow;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"8.8.8.8";
let m = extractor.match_iter(data).next().unwrap();
assert!(matches!(m.as_str(), Cow::Borrowed(_)));
}
#[test]
fn test_as_str_owned_for_defanged() {
use std::borrow::Cow;
let extractor = ExtractorBuilder::new().build().unwrap();
let data = b"192.168.1[.]50";
let m = extractor.match_iter(data).next().unwrap();
assert!(matches!(m.as_str(), Cow::Owned(_)));
assert_eq!(m.as_str().as_ref(), "192.168.1.50");
}
#[test]
fn test_smtp_received_bracket_literal_ip() {
check_extraction(
b"Received: from e33.co.us.ibm.com ([32.97.110.151]) by COL0-MC1-F36.Col0.hotmail.com",
&["32.97.110.151"],
true,
true,
);
}
#[test]
fn test_smtp_received_bare_paren_ip() {
check_extraction(
b"Received: from d03dlp02.boulder.ibm.com (9.17.202.178)\n by e33.co.us.ibm.com (192.168.1.133)",
&["9.17.202.178", "192.168.1.133"],
true,
true,
);
}
#[test]
fn test_smtp_received_hostname_bracket_ip() {
check_extraction(
b"Received: from d03relay04.boulder.ibm.com (d03relay04.boulder.ibm.com [9.17.195.106])\n by d03dlp02.boulder.ibm.com (Postfix)",
&["9.17.195.106"],
true,
true,
);
}
#[test]
fn test_smtp_received_loopback_in_brackets() {
check_extraction(
b"Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1])\n by d03av01.boulder.ibm.com",
&["127.0.0.1"],
true,
true, );
}
#[test]
fn test_smtp_received_loopback_excluded() {
check_extraction(
b"Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1])\n by d03av01.boulder.ibm.com",
&[],
true,
false, );
}
#[test]
fn test_smtp_received_full_chain() {
let headers = b"Received: from e33.co.us.ibm.com ([32.97.110.151]) by COL0-MC1-F36.Col0.hotmail.com with Microsoft SMTPSVC(6.0.3790.4900);\r\n\
Tue, 10 Jul 2012 20:40:25 -0700\r\n\
Received: from /spool/local\r\n\
by e33.co.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted\r\n\
for <asdfqwerasdf@hotmail.com> from <asdfqwerwen@us.ibm.com>;\r\n\
Tue, 10 Jul 2012 21:40:24 -0600\r\n\
Received: from d03dlp02.boulder.ibm.com (9.17.202.178)\r\n\
by e33.co.us.ibm.com (192.168.1.133) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted;\r\n\
Tue, 10 Jul 2012 21:40:22 -0600\r\n\
Received: from d03relay04.boulder.ibm.com (d03relay04.boulder.ibm.com [9.17.195.106])\r\n\
by d03dlp02.boulder.ibm.com (Postfix) with ESMTP id A425E3E4004E\r\n\
for <asdfqwerasdf@hotmail.com>; Wed, 11 Jul 2012 03:40:21 +0000 (WET)\r\n\
Received: from d03av01.boulder.ibm.com (d03av01.boulder.ibm.com [9.17.195.167])\r\n\
by d03relay04.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id q6B3dt7X244944\r\n\
for <asdfqwerasdf@hotmail.com>; Tue, 10 Jul 2012 21:40:06 -0600\r\n\
Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1])\r\n\
by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id q6B3denJ030524\r\n\
for <asdfqwerasdf@hotmail.com>; Tue, 10 Jul 2012 21:39:40 -0600\r\n\
Received: from wtfbes02.edc.lotus.com (wtfbes02.lotus.com [9.32.140.208])\r\n\
by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id q6B3ddop030485\r\n\
for <asdfqwerasdf@hotmail.com>; Tue, 10 Jul 2012 21:39:39 -0600\r\n";
check_extraction(
headers,
&[
"32.97.110.151",
"9.17.202.178",
"192.168.1.133",
"9.17.195.106",
"9.17.195.167",
"127.0.0.1",
"9.32.140.208",
],
true, true, );
}
#[test]
fn test_smtp_received_full_chain_public_only() {
let headers =
b"Received: from e33.co.us.ibm.com ([32.97.110.151]) by COL0-MC1-F36.Col0.hotmail.com;\r\n\
Received: from d03dlp02.boulder.ibm.com (9.17.202.178)\r\n\
by e33.co.us.ibm.com (192.168.1.133);\r\n\
Received: from d03relay04.boulder.ibm.com (d03relay04.boulder.ibm.com [9.17.195.106]);\r\n\
Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1]);\r\n\
Received: from wtfbes02.edc.lotus.com (wtfbes02.lotus.com [9.32.140.208]);\r\n";
check_extraction(
headers,
&[
"32.97.110.151",
"9.17.202.178",
"9.17.195.106",
"9.32.140.208",
],
false, false, );
}
#[test]
fn test_smtp_ipv6_bare_paren() {
check_extraction(
b"Received: from mail.example.com (2001:db8::1)\n by mx.example.net (2001:db8:cafe::1) with ESMTP",
&["2001:db8::1", "2001:db8:cafe::1"],
true,
true,
);
}
#[test]
fn test_smtp_ipv6_loopback_bare_paren() {
check_extraction(
b"Received: from localhost (::1) by mail.example.com",
&["::1"],
true,
true, );
}
#[test]
fn test_smtp_ipv6_loopback_excluded() {
check_extraction(
b"Received: from localhost (::1) by mail.example.com",
&[],
true,
false, );
}
#[test]
fn test_smtp_ipv6_full_address_bare_paren() {
check_extraction(
b"Received: from client (2001:0db8:85a3:0000:0000:8a2e:0370:7334) by server",
&["2001:0db8:85a3:0000:0000:8a2e:0370:7334"],
true,
true,
);
}
#[test]
fn test_smtp_ipv6_rfc5321_bracket_literal() {
check_extraction(
b"Received: from client ([IPv6:2001:db8::1]) by server",
&["2001:db8::1"],
true,
true,
);
}
#[test]
fn test_smtp_ipv6_bracket_no_prefix() {
check_extraction(
b"Received: from mail-lj1-f178.google.com ([2607:f8b0:4864:20::435]) by mx.google.com with ESMTPSA id",
&["2607:f8b0:4864:20::435"],
true,
true,
);
}
#[test]
fn test_smtp_ipv6_rfc5321_postfix_style() {
check_extraction(
b"Received: from mail.example.com (mail.example.com [IPv6:2001:db8:cafe::1]) by mx.example.net",
&["2001:db8:cafe::1"],
true,
true,
);
}
#[test]
fn test_smtp_ipv6_mixed_with_ipv4() {
let headers = b"Received: from ipv6-client (2001:db8::42)\n by mx1.example.com (203.0.113.10) with ESMTP\r\n\
Received: from mx1.example.com ([203.0.113.10])\n by mx2.example.com ([IPv6:2001:db8:1::1]) with ESMTP\r\n";
check_extraction(
headers,
&[
"2001:db8::42", "203.0.113.10", "203.0.113.10", "2001:db8:1::1", ],
true,
true,
);
}