1use std::fmt::Display;
4use std::io::{Read, Write};
5
6use byteorder::{ReadBytesExt, WriteBytesExt};
7use url::Url;
8
9use crate::error::{EncodeError, ParseError};
10use crate::name::Name;
11
12use super::{encode_string_into, Rdata, RdataTrait};
13
14#[cfg(feature = "serde")]
15use serde::Serialize;
16
17#[cfg_attr(feature = "serde", derive(Serialize))]
20#[derive(PartialEq, Eq, Clone, Debug)]
21pub enum Property {
22 Issue,
26 IssueWild,
30 Iodef,
36 Unknown(String),
37}
38
39#[cfg_attr(feature = "serde", derive(Serialize))]
41#[derive(PartialEq, Eq, Clone, Debug)]
42pub enum Value {
43 Issuer {
45 name: Option<Name>,
47 parameters: Vec<(String, String)>,
48 },
49 IodefUrl(Url),
51 Unknown(String),
53}
54
55#[cfg_attr(feature = "serde", derive(Serialize))]
60#[derive(PartialEq, Eq, Clone, Debug)]
61pub struct CAA {
62 pub issuer_critical: bool,
68 tag: Property,
73 value: Value,
78}
79
80impl Property {
81 pub(crate) fn encode_into(&self, buf: &mut impl Write) -> Result<u16, EncodeError> {
86 encode_string_into(self.to_string(), buf)
87 }
88}
89
90impl Display for Property {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 Self::Iodef => write!(f, "iodef"),
94 Self::Issue => write!(f, "issue"),
95 Self::IssueWild => write!(f, "iodef"),
96 Self::Unknown(unknown) => write!(f, "{}", unknown),
97 }
98 }
99}
100
101impl Value {
102 pub(crate) fn encode_into(&self, buf: &mut impl Write) -> Result<u16, EncodeError> {
107 encode_string_into(self.to_string(), buf)
108 }
109}
110
111impl Display for Value {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 Self::Issuer { name, parameters } => {
115 if parameters.is_empty() {
116 if let Some(name) = name {
117 write!(f, "{}", name)
118 } else {
119 write!(f, ";")
120 }
121 } else {
122 let parameters = parameters
123 .iter()
124 .map(|(tag, value)| format!("{}={}", tag, value))
125 .collect::<Vec<_>>()
126 .join(" ");
127 let name = name.as_ref().map(|n| n.to_string()).unwrap_or_default();
128 write!(f, "{}; {}", name, parameters)
129 }
130 }
131 Self::IodefUrl(url) => write!(f, "{}", url),
132
133 Self::Unknown(unknown) => write!(f, "{}", unknown),
134 }
135 }
136}
137
138impl From<&str> for Property {
139 fn from(value: &str) -> Self {
140 match value.to_ascii_lowercase().as_str() {
141 "issue" => Self::Issue,
142 "issuewild" => Self::IssueWild,
143 "iodef" => Self::Iodef,
144 _ => Self::Unknown(value.to_string()),
145 }
146 }
147}
148
149impl CAA {
150 pub fn issue(
152 issuer_critical: bool,
153 name: Option<Name>,
154 parameters: Vec<(String, String)>,
155 ) -> Self {
156 Self {
157 issuer_critical,
158 tag: Property::Issue,
159 value: Value::Issuer { name, parameters },
160 }
161 }
162
163 pub fn issue_wild(
165 issuer_critical: bool,
166 name: Option<Name>,
167 parameters: Vec<(String, String)>,
168 ) -> Self {
169 Self {
170 issuer_critical,
171 tag: Property::IssueWild,
172 value: Value::Issuer { name, parameters },
173 }
174 }
175
176 pub fn iodef(issuer_critical: bool, url: Url) -> Self {
178 Self {
179 issuer_critical,
180 tag: Property::Iodef,
181 value: Value::IodefUrl(url),
182 }
183 }
184
185 pub fn tag(&self) -> &Property {
187 &self.tag
188 }
189
190 pub fn value(&self) -> &Value {
192 &self.value
193 }
194}
195
196impl RdataTrait for CAA {
197 fn parse_rdata(rdata: &mut std::io::Cursor<&[u8]>, rdlength: u16) -> Result<Rdata, ParseError> {
198 let flags = rdata.read_u8()?;
199 let issuer_critical = (flags & (1 << 7)) != 0;
200 let tag_length = rdata.read_u8()?;
201 let mut tag = vec![0; tag_length as usize];
202 rdata.read_exact(&mut tag)?;
203 let bytes_read = 2 + tag_length;
205 let value_length = rdlength - bytes_read as u16;
206 let mut value = vec![0; value_length as usize];
207 rdata.read_exact(&mut value)?;
208
209 let tag = String::from_utf8_lossy(&tag);
210 if !tag.is_ascii() {
211 return Err(ParseError::NonAsciiCaa(tag.into_owned()));
212 }
213 let value_cow = String::from_utf8_lossy(&value);
214 let tag = Property::from(&*tag);
215 let caa = match &tag {
216 Property::Unknown(_) => Self {
217 issuer_critical,
218 tag,
219 value: Value::Unknown(value_cow.into_owned()),
220 },
221 Property::Iodef => {
222 let url = Url::parse(&value_cow)?;
223 Self {
224 issuer_critical,
225 tag,
226 value: Value::IodefUrl(url),
227 }
228 }
229 Property::Issue | Property::IssueWild => {
230 let value = value_cow.trim();
231 let (name, parameters) = if let Some((name, parameters)) = value.split_once(';') {
233 let name = name.trim();
234 let name = if name.is_empty() {
235 None
236 } else {
237 Some(
238 Name::from_ascii(name)
239 .map_err(|_| ParseError::InvalidCaaIssueName(name.to_string()))?,
240 )
241 };
242 let parameters = parameters.trim();
243 let tag_values: Result<Vec<_>, _> = parameters
244 .split(&[' ', '\t'])
245 .filter(|s| !s.is_empty())
247 .map(|tag_value| {
248 tag_value.split_once('=').ok_or_else(|| {
249 ParseError::InvalidCaaParameter(parameters.to_string())
250 })
251 })
252 .collect();
253 let tag_values: Vec<_> = tag_values?
254 .iter()
255 .map(|(tag, value)| (tag.to_string(), value.to_string()))
256 .collect();
257 (name, tag_values)
258 } else {
259 let name = Name::from_ascii(value)
260 .map_err(|_| ParseError::InvalidCaaIssueName(value_cow.into_owned()))?;
261 (Some(name), vec![])
262 };
263 Self {
264 issuer_critical,
265 tag,
266 value: Value::Issuer { name, parameters },
267 }
268 }
269 };
270
271 Ok(Rdata::CAA(caa))
272 }
273
274 fn encode_rdata_into(&self, buf: &mut impl Write) -> Result<u16, EncodeError> {
275 let flags = if self.issuer_critical { 1 << 7 } else { 0 };
276 buf.write_u8(flags)?;
277 let tag_byte_count = self.tag.encode_into(buf)?;
278 let value_byte_count = self.value.encode_into(buf)?;
279
280 Ok(1 + tag_byte_count + value_byte_count)
281 }
282}
283
284impl Display for CAA {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 if self.issuer_critical {
287 write!(f, "1 ")?;
288 } else {
289 write!(f, "0 ")?;
290 }
291 write!(f, "{} \"{}\"", self.tag, self.value)
292 }
293}