diff --git a/DESIGN.md b/DESIGN.md index 95f380c..d1b2310 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -260,6 +260,7 @@ combined via a separate flag to avoid ambiguity. - Emit the canonical network string for each IP rather than the original token - Host bits are cleared: `10.0.0.5/24` → `10.0.0.0/24` - Bare IPs get explicit prefix lengths: `192.168.1.1` → `192.168.1.1/32` +- IPv6 hex digits are lowercased: `2001:DB8::FfAb` → `2001:db8::ffab/128` - Does not affect `NonIp` spans, which are always emitted verbatim ### Deduplication: `--unique` / `-u` diff --git a/README.md b/README.md index 2ba5c6f..95244b1 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,14 @@ EOF 192.168.1.1/32 ``` +`--normalize` also lowercases IPv6 hex digits, since it always emits the +canonical network string rather than the original token: + +```sh +$ echo "2001:DB8::FfAb" | ipsort --normalize +2001:db8::ffab/128 +``` + **Extract bare IPs, discarding all decoration and structure:** ```sh diff --git a/ipsort.1.scd b/ipsort.1.scd index 4d081be..f4776b3 100644 --- a/ipsort.1.scd +++ b/ipsort.1.scd @@ -64,7 +64,8 @@ reordered, leaving comments, keys, blank lines, and all other content untouched. *-n*, *--normalize* Emit canonical network strings instead of the original token. Host bits are cleared (_10.0.0.5/24_ becomes _10.0.0.0/24_) and bare IPs gain explicit - prefix lengths (_192.168.1.1_ becomes _192.168.1.1/32_). Surrounding + prefix lengths (_192.168.1.1_ becomes _192.168.1.1/32_). IPv6 hex digits are + also lowercased (_2001:DB8::FfAb_ becomes _2001:db8::ffab/128_). Surrounding decoration is unaffected. *--ips-only* diff --git a/src/output.rs b/src/output.rs index 14f778c..a18ec0d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -30,7 +30,8 @@ //! **`--normalize`**: same as default but `Ip` spans emit the canonical //! network string (`10.0.0.0/8`) rather than the original token //! (`10.0.0.5/8`). Bare IPs gain explicit prefix lengths (`192.168.1.1` -> -//! `192.168.1.1/32`). +//! `192.168.1.1/32`). IPv6 hex digits are also lowercased (`2001:DB8::FfAb` +//! -> `2001:db8::ffab/128`). //! //! **`--ips-only`** ([`IpsOnlyMode::Flat`]): all `NonIp` spans and all //! [`ClassifiedLine::NoIp`] lines are discarded. Each `Ip` span becomes one @@ -379,6 +380,22 @@ mod tests { assert_eq!(render_with("2001:db8::1", &opts), vec!["2001:db8::1/128"]); } + #[test] + fn test_normalize_ipv6_hex_downcased() { + let opts = OutputOptions { + normalize: true, + ..Default::default() + }; + assert_eq!( + render_with("2001:DB8::FfAb/32", &opts), + vec!["2001:db8::/32"] + ); + assert_eq!( + render_with("2001:DB8::FfAb", &opts), + vec!["2001:db8::ffab/128"] + ); + } + #[test] fn test_normalize_decoration_preserved() { let opts = OutputOptions { diff --git a/tests/integration.rs b/tests/integration.rs index 361819f..585a594 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -235,6 +235,16 @@ fn test_normalize_bare_ipv6() { .stdout("2001:db8::1/128\n"); } +#[test] +fn test_normalize_downcases_ipv6_hex() { + ipsort() + .args(["--normalize"]) + .write_stdin("2001:DB8::FfAb\n") + .assert() + .success() + .stdout("2001:db8::ffab/128\n"); +} + #[test] fn test_normalize_preserves_decoration() { ipsort()