1use alloc::vec::Vec;
4use const_oid::db::{
5 DB, Database,
6 rfc3280::EMAIL_ADDRESS,
7 rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER},
8};
9use core::{
10 fmt::{self, Write},
11 str::FromStr,
12};
13use der::{
14 Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd,
15 asn1::{
16 Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef,
17 Utf8StringRef,
18 },
19};
20
21pub type AttributeType = ObjectIdentifier;
29
30pub type AttributeValue = Any;
38
39#[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)]
62#[allow(missing_docs)]
63pub struct Attribute {
64 pub oid: AttributeType,
65 pub values: SetOfVec<AttributeValue>,
66}
67
68pub type Attributes = SetOfVec<Attribute>;
76
77#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
88#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd, Hash)]
89#[allow(missing_docs)]
90pub struct AttributeTypeAndValue {
91 pub oid: AttributeType,
92 pub value: AttributeValue,
93}
94
95#[derive(Copy, Clone)]
96enum Escape {
97 None,
98 Some,
99 Hex(u8),
100}
101
102struct Parser {
103 state: Escape,
104 bytes: Vec<u8>,
105}
106
107impl Parser {
108 pub fn new() -> Self {
109 Self {
110 state: Escape::None,
111 bytes: Vec::new(),
112 }
113 }
114
115 fn push(&mut self, c: u8) {
116 self.state = Escape::None;
117 self.bytes.push(c);
118 }
119
120 pub fn add(&mut self, c: u8) -> Result<(), Error> {
121 match (self.state, c) {
122 (Escape::Hex(p), b'0'..=b'9') => self.push(p | (c - b'0')),
123 (Escape::Hex(p), b'a'..=b'f') => self.push(p | (c - b'a' + 10)),
124 (Escape::Hex(p), b'A'..=b'F') => self.push(p | (c - b'A' + 10)),
125
126 (Escape::Some, b'0'..=b'9') => self.state = Escape::Hex((c - b'0') << 4),
127 (Escape::Some, b'a'..=b'f') => self.state = Escape::Hex((c - b'a' + 10) << 4),
128 (Escape::Some, b'A'..=b'F') => self.state = Escape::Hex((c - b'A' + 10) << 4),
129
130 (Escape::Some, b' ' | b'"' | b'#' | b'=' | b'\\') => self.push(c),
131 (Escape::Some, b'+' | b',' | b';' | b'<' | b'>') => self.push(c),
132
133 (Escape::None, b'\\') => self.state = Escape::Some,
134 (Escape::None, ..) => self.push(c),
135
136 _ => return Err(ErrorKind::Failed.into()),
137 }
138
139 Ok(())
140 }
141
142 pub fn as_bytes(&self) -> &[u8] {
143 &self.bytes
144 }
145}
146
147impl AttributeTypeAndValue {
148 fn from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
150 let mut iter = match val.len() % 2 {
152 0 => [].iter().cloned().chain(val.bytes()),
153 1 => [0u8].iter().cloned().chain(val.bytes()),
154 _ => unreachable!(),
155 };
156
157 let mut bytes = Vec::with_capacity(val.len().div_ceil(2));
159
160 while let (Some(h), Some(l)) = (iter.next(), iter.next()) {
161 let mut byte = 0u8;
162
163 for (half, shift) in [(h, 4), (l, 0)] {
164 match half {
165 b'0'..=b'9' => byte |= (half - b'0') << shift,
166 b'a'..=b'f' => byte |= (half - b'a' + 10) << shift,
167 b'A'..=b'F' => byte |= (half - b'A' + 10) << shift,
168 _ => return Err(ErrorKind::Failed.into()),
169 }
170 }
171
172 bytes.push(byte);
173 }
174
175 Ok(Self {
176 oid,
177 value: Any::from_der(&bytes)?,
178 })
179 }
180
181 fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
183 let mut parser = Parser::new();
185 for c in val.bytes() {
186 parser.add(c)?;
187 }
188
189 let tag = match oid {
190 COUNTRY_NAME => Tag::PrintableString,
191 DOMAIN_COMPONENT => Tag::Ia5String,
192 SERIAL_NUMBER => Tag::PrintableString,
195 EMAIL_ADDRESS => Tag::Ia5String,
198 _ => Tag::Utf8String,
199 };
200
201 Ok(Self {
202 oid,
203 value: Any::new(tag, parser.as_bytes())?,
204 })
205 }
206
207 #[deprecated(
213 since = "0.2.1",
214 note = "use AttributeTypeAndValue::from_str(...)?.to_der()"
215 )]
216 pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> {
217 Self::from_str(s)?.to_der()
218 }
219}
220
221impl FromStr for AttributeTypeAndValue {
227 type Err = Error;
228
229 fn from_str(s: &str) -> der::Result<Self> {
230 let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?;
231 let (key, val) = s.split_at(idx);
232 let val = &val[1..];
233
234 let oid = match DB.by_name(key) {
236 Some(oid) => *oid,
237 None => ObjectIdentifier::new(key)?,
238 };
239
240 match val.strip_prefix('#') {
242 Some(val) => Self::from_hex(oid, val),
243 None => Self::from_delimited_str(oid, val),
244 }
245 }
246}
247
248impl fmt::Display for AttributeTypeAndValue {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 let val = match self.value.tag() {
254 Tag::PrintableString => PrintableStringRef::try_from(&self.value)
255 .ok()
256 .map(|s| s.as_str()),
257 Tag::Utf8String => Utf8StringRef::try_from(&self.value)
258 .ok()
259 .map(|s| s.as_str()),
260 Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()),
261 Tag::TeletexString => TeletexStringRef::try_from(&self.value)
262 .ok()
263 .map(|s| s.as_str()),
264 _ => None,
265 };
266
267 if let (Some(key), Some(val)) = (DB.shortest_name_by_oid(&self.oid), val) {
268 write!(f, "{}=", key.to_ascii_uppercase())?;
269
270 let mut iter = val.char_indices().peekable();
271 while let Some((i, c)) = iter.next() {
272 match c {
273 '#' if i == 0 => write!(f, "\\#")?,
274 ' ' if i == 0 || iter.peek().is_none() => write!(f, "\\ ")?,
275 '"' | '+' | ',' | ';' | '<' | '>' | '\\' => write!(f, "\\{c}")?,
276 '\x00'..='\x1f' | '\x7f' => write!(f, "\\{:02x}", c as u8)?,
277 _ => f.write_char(c)?,
278 }
279 }
280 } else {
281 let value = self.value.to_der().or(Err(fmt::Error))?;
282
283 write!(f, "{}=#", self.oid)?;
284 for c in value {
285 write!(f, "{c:02x}")?;
286 }
287 }
288
289 Ok(())
290 }
291}
292
293trait ShortestName {
295 fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>;
296}
297
298impl<'a> ShortestName for Database<'a> {
299 fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> {
300 let mut best_match: Option<&'a str> = None;
301
302 for m in self.find_names_for_oid(*oid) {
303 if let Some(previous) = best_match {
304 if m.len() < previous.len() {
305 best_match = Some(m);
306 }
307 } else {
308 best_match = Some(m);
309 }
310 }
311
312 best_match
313 }
314}