taxy_api/
subject_name.rs

1use crate::error::Error;
2use rustls_pki_types::ServerName;
3use serde::{Deserialize, Serialize};
4use std::{fmt::Display, net::IpAddr, str::FromStr};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum SubjectName {
8    DnsName(String),
9    WildcardDnsName(String),
10    IPAddress(IpAddr),
11}
12
13impl SubjectName {
14    pub fn test(&self, name: &str) -> bool {
15        match self {
16            Self::DnsName(n) => n.eq_ignore_ascii_case(name),
17            Self::WildcardDnsName(n) => n.eq_ignore_ascii_case(
18                name.trim_start_matches(|c| c != '.')
19                    .trim_start_matches('.'),
20            ),
21            Self::IPAddress(addr) => match addr {
22                IpAddr::V4(addr) => name.eq_ignore_ascii_case(&addr.to_string()),
23                IpAddr::V6(addr) => name.eq_ignore_ascii_case(&addr.to_string()),
24            },
25        }
26    }
27}
28
29impl Serialize for SubjectName {
30    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
31    where
32        S: serde::Serializer,
33    {
34        serializer.serialize_str(&self.to_string())
35    }
36}
37
38impl<'de> Deserialize<'de> for SubjectName {
39    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40    where
41        D: serde::Deserializer<'de>,
42    {
43        let s = String::deserialize(deserializer)?;
44        Self::from_str(&s).map_err(serde::de::Error::custom)
45    }
46}
47
48impl Display for SubjectName {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::DnsName(name) => write!(f, "{}", name),
52            Self::WildcardDnsName(name) => write!(f, "*.{}", name),
53            Self::IPAddress(addr) => write!(f, "{}", addr),
54        }
55    }
56}
57
58impl FromStr for SubjectName {
59    type Err = Error;
60
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        if ServerName::try_from(s.replace('*', "a")).is_err() {
63            return Err(Error::InvalidSubjectName {
64                name: s.to_string(),
65            });
66        }
67        let wildcard = s.starts_with("*.");
68        let name = s.trim_start_matches("*.");
69        let ipaddr: Result<IpAddr, _> = name.parse();
70        match ipaddr {
71            Ok(addr) => Ok(Self::IPAddress(addr)),
72            _ => {
73                if wildcard {
74                    Ok(Self::WildcardDnsName(name.to_string()))
75                } else {
76                    Ok(Self::DnsName(name.to_string()))
77                }
78            }
79        }
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86
87    #[test]
88    fn test_subject_name() {
89        assert_eq!(
90            SubjectName::from_str("*.example.com").unwrap(),
91            SubjectName::WildcardDnsName("example.com".to_owned())
92        );
93        assert_eq!(
94            SubjectName::from_str("example.com").unwrap(),
95            SubjectName::DnsName("example.com".to_owned())
96        );
97        assert_eq!(
98            SubjectName::from_str("127.0.0.1").unwrap(),
99            SubjectName::IPAddress(IpAddr::V4([127, 0, 0, 1].into()))
100        )
101    }
102
103    #[test]
104    fn test_subject_name_test() {
105        assert!(SubjectName::from_str("*.example.com")
106            .unwrap()
107            .test("app.example.com"));
108        assert!(SubjectName::from_str("example.com")
109            .unwrap()
110            .test("example.com"));
111        assert!(SubjectName::from_str("127.0.0.1")
112            .unwrap()
113            .test("127.0.0.1"));
114    }
115}