krabka_authz/
host_format.rs1use std::{
21 fmt::{self, Write},
22 net::IpAddr,
23};
24
25#[must_use]
28pub fn jdk_host_address(ip: IpAddr) -> String {
29 let mut out = String::with_capacity(39);
30 let _ = write_jdk_host_address(ip, &mut out);
31 out
32}
33
34pub fn jdk_host_address_is(ip: IpAddr, text: &str) -> bool {
38 let mut rest = PrefixMatcher { rest: text };
39 write_jdk_host_address(ip, &mut rest).is_ok() && rest.rest.is_empty()
40}
41
42fn write_jdk_host_address(ip: IpAddr, out: &mut impl Write) -> fmt::Result {
46 match ip.to_canonical() {
47 IpAddr::V4(v4) => write!(out, "{v4}"),
48 IpAddr::V6(v6) => {
49 for (i, segment) in v6.segments().iter().enumerate() {
50 if i > 0 {
51 out.write_char(':')?;
52 }
53 write!(out, "{segment:x}")?;
54 }
55 Ok(())
56 }
57 }
58}
59
60struct PrefixMatcher<'a> {
63 rest: &'a str,
64}
65
66impl Write for PrefixMatcher<'_> {
67 fn write_str(&mut self, s: &str) -> fmt::Result {
68 self.rest = self.rest.strip_prefix(s).ok_or(fmt::Error)?;
69 Ok(())
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use {
76 super::{jdk_host_address, jdk_host_address_is},
77 proptest::prelude::*,
78 std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
79 };
80
81 #[yare::parameterized(
82 ipv4_localhost = { IpAddr::V4(Ipv4Addr::LOCALHOST), "127.0.0.1" },
83 ipv4_other = { IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), "10.0.0.5" },
84 ipv6_localhost_expanded = { IpAddr::V6(Ipv6Addr::LOCALHOST), "0:0:0:0:0:0:0:1" },
85 ipv6_unspecified_expanded = { IpAddr::V6(Ipv6Addr::UNSPECIFIED), "0:0:0:0:0:0:0:0" },
86 ipv6_groups_expanded = {
87 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 5)),
88 "2001:db8:0:0:0:0:0:5",
89 },
90 ipv4_mapped_ipv6 = {
91 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x0a00, 0x0005)),
92 "10.0.0.5",
93 },
94 )]
95 fn matches_jdk_text_form(ip: IpAddr, expected: &str) {
96 assert2::assert!(jdk_host_address(ip) == expected);
97 assert2::assert!(jdk_host_address_is(ip, expected));
98 }
99
100 #[yare::parameterized(
103 truncated = { IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), "10.0.0." },
104 extended = { IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), "10.0.0.50" },
105 compressed_ipv6 = { IpAddr::V6(Ipv6Addr::LOCALHOST), "::1" },
106 mapped_ipv6_text = {
107 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x0a00, 0x0005)),
108 "::ffff:10.0.0.5",
109 },
110 empty = { IpAddr::V4(Ipv4Addr::LOCALHOST), "" },
111 wildcard = { IpAddr::V4(Ipv4Addr::LOCALHOST), "*" },
112 )]
113 fn rejects_other_text(ip: IpAddr, text: &str) {
114 assert2::assert!(!jdk_host_address_is(ip, text));
115 }
116
117 proptest! {
118 #[test]
121 fn streaming_comparison_agrees_with_rendering(
122 ip in any::<IpAddr>(),
123 other in "[0-9a-f:.]{0,40}",
124 ) {
125 let rendered = jdk_host_address(ip);
126 assert2::assert!(jdk_host_address_is(ip, &rendered));
127 assert2::assert!(jdk_host_address_is(ip, &other) == (other == rendered));
128 }
129 }
130}