/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rcgen-0.14.2/src/string.rs
Line | Count | Source |
1 | | //! ASN.1 string types |
2 | | |
3 | | use std::{fmt, str::FromStr}; |
4 | | |
5 | | use crate::{Error, InvalidAsn1String}; |
6 | | |
7 | | /// ASN.1 `PrintableString` type. |
8 | | /// |
9 | | /// Supports a subset of the ASCII printable characters (described below). |
10 | | /// |
11 | | /// For the full ASCII character set, use |
12 | | /// [`Ia5String`][`crate::Ia5String`]. |
13 | | /// |
14 | | /// # Examples |
15 | | /// |
16 | | /// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]: |
17 | | /// |
18 | | /// ``` |
19 | | /// use rcgen::string::PrintableString; |
20 | | /// let hello = PrintableString::try_from("hello").unwrap(); |
21 | | /// ``` |
22 | | /// |
23 | | /// # Supported characters |
24 | | /// |
25 | | /// PrintableString is a subset of the [ASCII printable characters]. |
26 | | /// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`]. |
27 | | /// |
28 | | /// The following ASCII characters/ranges are supported: |
29 | | /// |
30 | | /// - `A..Z` |
31 | | /// - `a..z` |
32 | | /// - `0..9` |
33 | | /// - "` `" (i.e. space) |
34 | | /// - `\` |
35 | | /// - `(` |
36 | | /// - `)` |
37 | | /// - `+` |
38 | | /// - `,` |
39 | | /// - `-` |
40 | | /// - `.` |
41 | | /// - `/` |
42 | | /// - `:` |
43 | | /// - `=` |
44 | | /// - `?` |
45 | | /// |
46 | | /// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters |
47 | | /// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString |
48 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
49 | | pub struct PrintableString(String); |
50 | | |
51 | | impl PrintableString { |
52 | | /// Extracts a string slice containing the entire `PrintableString`. |
53 | 0 | pub fn as_str(&self) -> &str { |
54 | 0 | &self.0 |
55 | 0 | } |
56 | | } |
57 | | |
58 | | impl TryFrom<&str> for PrintableString { |
59 | | type Error = Error; |
60 | | |
61 | | /// Converts a `&str` to a [`PrintableString`]. |
62 | | /// |
63 | | /// Any character not in the [`PrintableString`] charset will be rejected. |
64 | | /// See [`PrintableString`] documentation for more information. |
65 | | /// |
66 | | /// The result is allocated on the heap. |
67 | 0 | fn try_from(input: &str) -> Result<Self, Error> { |
68 | 0 | input.to_string().try_into() |
69 | 0 | } |
70 | | } |
71 | | |
72 | | impl TryFrom<String> for PrintableString { |
73 | | type Error = Error; |
74 | | |
75 | | /// Converts a [`String`][`std::string::String`] into a [`PrintableString`] |
76 | | /// |
77 | | /// Any character not in the [`PrintableString`] charset will be rejected. |
78 | | /// See [`PrintableString`] documentation for more information. |
79 | | /// |
80 | | /// This conversion does not allocate or copy memory. |
81 | 0 | fn try_from(value: String) -> Result<Self, Self::Error> { |
82 | 0 | for &c in value.as_bytes() { |
83 | 0 | match c { |
84 | 0 | b'A'..=b'Z' |
85 | 0 | | b'a'..=b'z' |
86 | 0 | | b'0'..=b'9' |
87 | | | b' ' |
88 | | | b'\'' |
89 | | | b'(' |
90 | | | b')' |
91 | | | b'+' |
92 | | | b',' |
93 | | | b'-' |
94 | | | b'.' |
95 | | | b'/' |
96 | | | b':' |
97 | | | b'=' |
98 | 0 | | b'?' => (), |
99 | | _ => { |
100 | 0 | return Err(Error::InvalidAsn1String( |
101 | 0 | InvalidAsn1String::PrintableString(value), |
102 | 0 | )) |
103 | | }, |
104 | | } |
105 | | } |
106 | 0 | Ok(Self(value)) |
107 | 0 | } |
108 | | } |
109 | | |
110 | | impl FromStr for PrintableString { |
111 | | type Err = Error; |
112 | | |
113 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
114 | 0 | s.try_into() |
115 | 0 | } |
116 | | } |
117 | | |
118 | | impl AsRef<str> for PrintableString { |
119 | 0 | fn as_ref(&self) -> &str { |
120 | 0 | &self.0 |
121 | 0 | } |
122 | | } |
123 | | |
124 | | impl fmt::Display for PrintableString { |
125 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
126 | 0 | fmt::Display::fmt(self.as_str(), f) |
127 | 0 | } |
128 | | } |
129 | | |
130 | | impl PartialEq<str> for PrintableString { |
131 | 0 | fn eq(&self, other: &str) -> bool { |
132 | 0 | self.as_str() == other |
133 | 0 | } |
134 | | } |
135 | | |
136 | | impl PartialEq<String> for PrintableString { |
137 | 0 | fn eq(&self, other: &String) -> bool { |
138 | 0 | self.as_str() == other.as_str() |
139 | 0 | } |
140 | | } |
141 | | |
142 | | impl PartialEq<&str> for PrintableString { |
143 | 0 | fn eq(&self, other: &&str) -> bool { |
144 | 0 | self.as_str() == *other |
145 | 0 | } |
146 | | } |
147 | | |
148 | | impl PartialEq<&String> for PrintableString { |
149 | 0 | fn eq(&self, other: &&String) -> bool { |
150 | 0 | self.as_str() == other.as_str() |
151 | 0 | } |
152 | | } |
153 | | |
154 | | /// ASN.1 `IA5String` type. |
155 | | /// |
156 | | /// # Examples |
157 | | /// |
158 | | /// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]: |
159 | | /// |
160 | | /// ``` |
161 | | /// use rcgen::string::Ia5String; |
162 | | /// let hello = Ia5String::try_from("hello").unwrap(); |
163 | | /// ``` |
164 | | /// |
165 | | /// # Supported characters |
166 | | /// |
167 | | /// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e. |
168 | | /// the 128 characters of the ASCII alphabet. (Note: IA5 is now |
169 | | /// technically known as the International Reference Alphabet or IRA as |
170 | | /// specified in the ITU-T's T.50 recommendation). |
171 | | /// |
172 | | /// For UTF-8, use [`String`][`std::string::String`]. |
173 | | /// |
174 | | /// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard) |
175 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
176 | | pub struct Ia5String(String); |
177 | | |
178 | | impl Ia5String { |
179 | | /// Extracts a string slice containing the entire `Ia5String`. |
180 | 0 | pub fn as_str(&self) -> &str { |
181 | 0 | &self.0 |
182 | 0 | } |
183 | | } |
184 | | |
185 | | impl TryFrom<&str> for Ia5String { |
186 | | type Error = Error; |
187 | | |
188 | | /// Converts a `&str` to a [`Ia5String`]. |
189 | | /// |
190 | | /// Any character not in the [`Ia5String`] charset will be rejected. |
191 | | /// See [`Ia5String`] documentation for more information. |
192 | | /// |
193 | | /// The result is allocated on the heap. |
194 | 0 | fn try_from(input: &str) -> Result<Self, Error> { |
195 | 0 | input.to_string().try_into() |
196 | 0 | } |
197 | | } |
198 | | |
199 | | impl TryFrom<String> for Ia5String { |
200 | | type Error = Error; |
201 | | |
202 | | /// Converts a [`String`][`std::string::String`] into a [`Ia5String`] |
203 | | /// |
204 | | /// Any character not in the [`Ia5String`] charset will be rejected. |
205 | | /// See [`Ia5String`] documentation for more information. |
206 | 0 | fn try_from(input: String) -> Result<Self, Error> { |
207 | 0 | if !input.is_ascii() { |
208 | 0 | return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String( |
209 | 0 | input, |
210 | 0 | ))); |
211 | 0 | } |
212 | 0 | Ok(Self(input)) |
213 | 0 | } |
214 | | } |
215 | | |
216 | | impl FromStr for Ia5String { |
217 | | type Err = Error; |
218 | | |
219 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
220 | 0 | s.try_into() |
221 | 0 | } |
222 | | } |
223 | | |
224 | | impl AsRef<str> for Ia5String { |
225 | 0 | fn as_ref(&self) -> &str { |
226 | 0 | &self.0 |
227 | 0 | } |
228 | | } |
229 | | |
230 | | impl fmt::Display for Ia5String { |
231 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
232 | 0 | fmt::Display::fmt(self.as_str(), f) |
233 | 0 | } |
234 | | } |
235 | | |
236 | | impl PartialEq<str> for Ia5String { |
237 | 0 | fn eq(&self, other: &str) -> bool { |
238 | 0 | self.as_str() == other |
239 | 0 | } |
240 | | } |
241 | | |
242 | | impl PartialEq<String> for Ia5String { |
243 | 0 | fn eq(&self, other: &String) -> bool { |
244 | 0 | self.as_str() == other.as_str() |
245 | 0 | } |
246 | | } |
247 | | |
248 | | impl PartialEq<&str> for Ia5String { |
249 | 0 | fn eq(&self, other: &&str) -> bool { |
250 | 0 | self.as_str() == *other |
251 | 0 | } |
252 | | } |
253 | | |
254 | | impl PartialEq<&String> for Ia5String { |
255 | 0 | fn eq(&self, other: &&String) -> bool { |
256 | 0 | self.as_str() == other.as_str() |
257 | 0 | } |
258 | | } |
259 | | |
260 | | /// ASN.1 `TeletexString` type. |
261 | | /// |
262 | | /// # Examples |
263 | | /// |
264 | | /// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]: |
265 | | /// |
266 | | /// ``` |
267 | | /// use rcgen::string::TeletexString; |
268 | | /// let hello = TeletexString::try_from("hello").unwrap(); |
269 | | /// ``` |
270 | | /// |
271 | | /// # Supported characters |
272 | | /// |
273 | | /// The standard defines a complex character set allowed in this type. However, quoting the ASN.1 |
274 | | /// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a |
275 | | /// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding". |
276 | | /// |
277 | | /// `TeletexString` is included for backward compatibility, [RFC 5280] say it |
278 | | /// SHOULD NOT be used for certificates for new subjects. |
279 | | /// |
280 | | /// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html |
281 | | /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 |
282 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
283 | | pub struct TeletexString(String); |
284 | | |
285 | | impl TeletexString { |
286 | | /// Extracts a string slice containing the entire `TeletexString`. |
287 | 0 | pub fn as_str(&self) -> &str { |
288 | 0 | &self.0 |
289 | 0 | } |
290 | | |
291 | | /// Returns a byte slice of this `TeletexString`’s contents. |
292 | 0 | pub fn as_bytes(&self) -> &[u8] { |
293 | 0 | self.0.as_bytes() |
294 | 0 | } |
295 | | } |
296 | | |
297 | | impl TryFrom<&str> for TeletexString { |
298 | | type Error = Error; |
299 | | |
300 | | /// Converts a `&str` to a [`TeletexString`]. |
301 | | /// |
302 | | /// Any character not in the [`TeletexString`] charset will be rejected. |
303 | | /// See [`TeletexString`] documentation for more information. |
304 | | /// |
305 | | /// The result is allocated on the heap. |
306 | 0 | fn try_from(input: &str) -> Result<Self, Error> { |
307 | 0 | input.to_string().try_into() |
308 | 0 | } |
309 | | } |
310 | | |
311 | | impl TryFrom<String> for TeletexString { |
312 | | type Error = Error; |
313 | | |
314 | | /// Converts a [`String`][`std::string::String`] into a [`TeletexString`] |
315 | | /// |
316 | | /// Any character not in the [`TeletexString`] charset will be rejected. |
317 | | /// See [`TeletexString`] documentation for more information. |
318 | | /// |
319 | | /// This conversion does not allocate or copy memory. |
320 | 0 | fn try_from(input: String) -> Result<Self, Error> { |
321 | | // Check all bytes are visible |
322 | 0 | if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) { |
323 | 0 | return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString( |
324 | 0 | input, |
325 | 0 | ))); |
326 | 0 | } |
327 | 0 | Ok(Self(input)) |
328 | 0 | } |
329 | | } |
330 | | |
331 | | impl FromStr for TeletexString { |
332 | | type Err = Error; |
333 | | |
334 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
335 | 0 | s.try_into() |
336 | 0 | } |
337 | | } |
338 | | |
339 | | impl AsRef<str> for TeletexString { |
340 | 0 | fn as_ref(&self) -> &str { |
341 | 0 | &self.0 |
342 | 0 | } |
343 | | } |
344 | | |
345 | | impl fmt::Display for TeletexString { |
346 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
347 | 0 | fmt::Display::fmt(self.as_str(), f) |
348 | 0 | } |
349 | | } |
350 | | |
351 | | impl PartialEq<str> for TeletexString { |
352 | 0 | fn eq(&self, other: &str) -> bool { |
353 | 0 | self.as_str() == other |
354 | 0 | } |
355 | | } |
356 | | |
357 | | impl PartialEq<String> for TeletexString { |
358 | 0 | fn eq(&self, other: &String) -> bool { |
359 | 0 | self.as_str() == other.as_str() |
360 | 0 | } |
361 | | } |
362 | | |
363 | | impl PartialEq<&str> for TeletexString { |
364 | 0 | fn eq(&self, other: &&str) -> bool { |
365 | 0 | self.as_str() == *other |
366 | 0 | } |
367 | | } |
368 | | |
369 | | impl PartialEq<&String> for TeletexString { |
370 | 0 | fn eq(&self, other: &&String) -> bool { |
371 | 0 | self.as_str() == other.as_str() |
372 | 0 | } |
373 | | } |
374 | | |
375 | | /// ASN.1 `BMPString` type. |
376 | | /// |
377 | | /// # Examples |
378 | | /// |
379 | | /// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]: |
380 | | /// |
381 | | /// ``` |
382 | | /// use rcgen::string::BmpString; |
383 | | /// let hello = BmpString::try_from("hello").unwrap(); |
384 | | /// ``` |
385 | | /// |
386 | | /// # Supported characters |
387 | | /// |
388 | | /// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646), |
389 | | /// a.k.a. UCS-2. |
390 | | /// |
391 | | /// Bytes are encoded as UTF-16 big-endian. |
392 | | /// |
393 | | /// `BMPString` is included for backward compatibility, [RFC 5280] say it |
394 | | /// SHOULD NOT be used for certificates for new subjects. |
395 | | /// |
396 | | /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 |
397 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
398 | | pub struct BmpString(Vec<u8>); |
399 | | |
400 | | impl BmpString { |
401 | | /// Returns a byte slice of this `BmpString`'s contents. |
402 | | /// |
403 | | /// The inverse of this method is [`from_utf16be`]. |
404 | | /// |
405 | | /// [`from_utf16be`]: BmpString::from_utf16be |
406 | | /// |
407 | | /// # Examples |
408 | | /// |
409 | | /// ``` |
410 | | /// use rcgen::string::BmpString; |
411 | | /// let s = BmpString::try_from("hello").unwrap(); |
412 | | /// |
413 | | /// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes()); |
414 | | /// ``` |
415 | 0 | pub fn as_bytes(&self) -> &[u8] { |
416 | 0 | &self.0 |
417 | 0 | } |
418 | | |
419 | | /// Decode a UTF-16BE–encoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. |
420 | 0 | pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> { |
421 | 0 | if vec.len() % 2 != 0 { |
422 | 0 | return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( |
423 | 0 | "Invalid UTF-16 encoding".to_string(), |
424 | 0 | ))); |
425 | 0 | } |
426 | | |
427 | | // FIXME: Update this when `array_chunks` is stabilized. |
428 | 0 | for maybe_char in char::decode_utf16( |
429 | 0 | vec.chunks_exact(2) |
430 | 0 | .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])), |
431 | | ) { |
432 | | // We check we only use the BMP subset of Unicode (the first 65 536 code points) |
433 | 0 | match maybe_char { |
434 | | // Character is in the Basic Multilingual Plane |
435 | 0 | Ok(c) if (c as u64) < u64::from(u16::MAX) => (), |
436 | | // Characters outside Basic Multilingual Plane or unpaired surrogates |
437 | | _ => { |
438 | 0 | return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( |
439 | 0 | "Invalid UTF-16 encoding".to_string(), |
440 | 0 | ))); |
441 | | }, |
442 | | } |
443 | | } |
444 | 0 | Ok(Self(vec.to_vec())) |
445 | 0 | } |
446 | | } |
447 | | |
448 | | impl TryFrom<&str> for BmpString { |
449 | | type Error = Error; |
450 | | |
451 | | /// Converts a `&str` to a [`BmpString`]. |
452 | | /// |
453 | | /// Any character not in the [`BmpString`] charset will be rejected. |
454 | | /// See [`BmpString`] documentation for more information. |
455 | | /// |
456 | | /// The result is allocated on the heap. |
457 | 0 | fn try_from(value: &str) -> Result<Self, Self::Error> { |
458 | 0 | let capacity = value.len().checked_mul(2).ok_or_else(|| { |
459 | 0 | Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string())) |
460 | 0 | })?; |
461 | | |
462 | 0 | let mut bytes = Vec::with_capacity(capacity); |
463 | | |
464 | 0 | for code_point in value.encode_utf16() { |
465 | 0 | bytes.extend(code_point.to_be_bytes()); |
466 | 0 | } |
467 | | |
468 | 0 | BmpString::from_utf16be(bytes) |
469 | 0 | } |
470 | | } |
471 | | |
472 | | impl TryFrom<String> for BmpString { |
473 | | type Error = Error; |
474 | | |
475 | | /// Converts a [`String`][`std::string::String`] into a [`BmpString`] |
476 | | /// |
477 | | /// Any character not in the [`BmpString`] charset will be rejected. |
478 | | /// See [`BmpString`] documentation for more information. |
479 | | /// |
480 | | /// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation. |
481 | 0 | fn try_from(value: String) -> Result<Self, Self::Error> { |
482 | 0 | value.as_str().try_into() |
483 | 0 | } |
484 | | } |
485 | | |
486 | | impl FromStr for BmpString { |
487 | | type Err = Error; |
488 | | |
489 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
490 | 0 | s.try_into() |
491 | 0 | } |
492 | | } |
493 | | |
494 | | /// ASN.1 `UniversalString` type. |
495 | | /// |
496 | | /// # Examples |
497 | | /// |
498 | | /// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]: |
499 | | /// |
500 | | /// ``` |
501 | | /// use rcgen::string::UniversalString; |
502 | | /// let hello = UniversalString::try_from("hello").unwrap(); |
503 | | /// ``` |
504 | | /// |
505 | | /// # Supported characters |
506 | | /// |
507 | | /// The characters which can appear in the `UniversalString` type are any of the characters allowed by |
508 | | /// ISO/IEC 10646 (Unicode). |
509 | | /// |
510 | | /// Bytes are encoded like UTF-32 big-endian. |
511 | | /// |
512 | | /// `UniversalString` is included for backward compatibility, [RFC 5280] say it |
513 | | /// SHOULD NOT be used for certificates for new subjects. |
514 | | /// |
515 | | /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 |
516 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
517 | | pub struct UniversalString(Vec<u8>); |
518 | | |
519 | | impl UniversalString { |
520 | | /// Returns a byte slice of this `UniversalString`'s contents. |
521 | | /// |
522 | | /// The inverse of this method is [`from_utf32be`]. |
523 | | /// |
524 | | /// [`from_utf32be`]: UniversalString::from_utf32be |
525 | | /// |
526 | | /// # Examples |
527 | | /// |
528 | | /// ``` |
529 | | /// use rcgen::string::UniversalString; |
530 | | /// let s = UniversalString::try_from("hello").unwrap(); |
531 | | /// |
532 | | /// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes()); |
533 | | /// ``` |
534 | 0 | pub fn as_bytes(&self) -> &[u8] { |
535 | 0 | &self.0 |
536 | 0 | } |
537 | | |
538 | | /// Decode a UTF-32BE–encoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. |
539 | 0 | pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> { |
540 | 0 | if vec.len() % 4 != 0 { |
541 | 0 | return Err(Error::InvalidAsn1String( |
542 | 0 | InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), |
543 | 0 | )); |
544 | 0 | } |
545 | | |
546 | | // FIXME: Update this when `array_chunks` is stabilized. |
547 | 0 | for maybe_char in vec |
548 | 0 | .chunks_exact(4) |
549 | 0 | .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) |
550 | | { |
551 | 0 | if core::char::from_u32(maybe_char).is_none() { |
552 | 0 | return Err(Error::InvalidAsn1String( |
553 | 0 | InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), |
554 | 0 | )); |
555 | 0 | } |
556 | | } |
557 | | |
558 | 0 | Ok(Self(vec)) |
559 | 0 | } |
560 | | } |
561 | | |
562 | | impl TryFrom<&str> for UniversalString { |
563 | | type Error = Error; |
564 | | |
565 | | /// Converts a `&str` to a [`UniversalString`]. |
566 | | /// |
567 | | /// Any character not in the [`UniversalString`] charset will be rejected. |
568 | | /// See [`UniversalString`] documentation for more information. |
569 | | /// |
570 | | /// The result is allocated on the heap. |
571 | 0 | fn try_from(value: &str) -> Result<Self, Self::Error> { |
572 | 0 | let capacity = value.len().checked_mul(4).ok_or_else(|| { |
573 | 0 | Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string())) |
574 | 0 | })?; |
575 | | |
576 | 0 | let mut bytes = Vec::with_capacity(capacity); |
577 | | |
578 | | // A `char` is any ‘Unicode code point’ other than a surrogate code point. |
579 | | // The code units for UTF-32 correspond exactly to Unicode code points. |
580 | | // (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction) |
581 | | // So any `char` is a valid UTF-32, we just cast it to perform the convertion. |
582 | 0 | for char in value.chars().map(|char| char as u32) { |
583 | 0 | bytes.extend(char.to_be_bytes()) |
584 | | } |
585 | | |
586 | 0 | UniversalString::from_utf32be(bytes) |
587 | 0 | } |
588 | | } |
589 | | |
590 | | impl TryFrom<String> for UniversalString { |
591 | | type Error = Error; |
592 | | |
593 | | /// Converts a [`String`][`std::string::String`] into a [`UniversalString`] |
594 | | /// |
595 | | /// Any character not in the [`UniversalString`] charset will be rejected. |
596 | | /// See [`UniversalString`] documentation for more information. |
597 | | /// |
598 | | /// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation. |
599 | 0 | fn try_from(value: String) -> Result<Self, Self::Error> { |
600 | 0 | value.as_str().try_into() |
601 | 0 | } |
602 | | } |
603 | | |
604 | | #[cfg(test)] |
605 | | #[allow(clippy::unwrap_used)] |
606 | | mod tests { |
607 | | |
608 | | use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; |
609 | | |
610 | | #[test] |
611 | | fn printable_string() { |
612 | | const EXAMPLE_UTF8: &str = "CertificateTemplate"; |
613 | | let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap(); |
614 | | assert_eq!(printable_string, EXAMPLE_UTF8); |
615 | | assert!(PrintableString::try_from("@").is_err()); |
616 | | assert!(PrintableString::try_from("*").is_err()); |
617 | | } |
618 | | |
619 | | #[test] |
620 | | fn ia5_string() { |
621 | | const EXAMPLE_UTF8: &str = "CertificateTemplate"; |
622 | | let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap(); |
623 | | assert_eq!(ia5_string, EXAMPLE_UTF8); |
624 | | assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); |
625 | | assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); |
626 | | } |
627 | | |
628 | | #[test] |
629 | | fn teletext_string() { |
630 | | const EXAMPLE_UTF8: &str = "CertificateTemplate"; |
631 | | let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap(); |
632 | | assert_eq!(teletext_string, EXAMPLE_UTF8); |
633 | | assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); |
634 | | assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); |
635 | | } |
636 | | |
637 | | #[test] |
638 | | fn bmp_string() { |
639 | | const EXPECTED_BYTES: &[u8] = &[ |
640 | | 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, |
641 | | 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d, |
642 | | 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, |
643 | | ]; |
644 | | const EXAMPLE_UTF8: &str = "CertificateTemplate"; |
645 | | let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap(); |
646 | | assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES); |
647 | | assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok()); |
648 | | assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err()); |
649 | | } |
650 | | |
651 | | #[test] |
652 | | fn universal_string() { |
653 | | const EXPECTED_BYTES: &[u8] = &[ |
654 | | 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, |
655 | | 0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69, |
656 | | 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, |
657 | | 0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d, |
658 | | 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, |
659 | | 0x00, 0x74, 0x00, 0x00, 0x00, 0x65, |
660 | | ]; |
661 | | const EXAMPLE_UTF8: &str = "CertificateTemplate"; |
662 | | let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap(); |
663 | | assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES); |
664 | | } |
665 | | } |