alpm_types/openpgp.rs
1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4 string::ToString,
5};
6
7use alpm_parsers::prelude::*;
8use base64::{Engine, prelude::BASE64_STANDARD};
9use email_address::EmailAddress;
10use fluent_i18n::t;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13#[cfg(feature = "serde")]
14use serde_with::DeserializeFromStr;
15use winnow::{
16 ascii::space0,
17 combinator::{alt, not, peek, repeat_till},
18 error::ErrMode,
19 token::any,
20};
21
22use crate::Error;
23
24/// An OpenPGP key identifier.
25///
26/// The `OpenPGPIdentifier` enum represents a valid OpenPGP identifier, which can be either an
27/// OpenPGP Key ID or an OpenPGP v4 fingerprint.
28///
29/// This type wraps an [`OpenPGPKeyId`] and an [`OpenPGPv4Fingerprint`] and provides a unified
30/// interface for both.
31///
32/// ## Examples
33///
34/// ```
35/// use std::str::FromStr;
36///
37/// use alpm_types::{Error, OpenPGPIdentifier, OpenPGPKeyId, OpenPGPv4Fingerprint};
38/// # fn main() -> Result<(), alpm_types::Error> {
39/// // Create a OpenPGPIdentifier from a valid OpenPGP v4 fingerprint
40/// let key = OpenPGPIdentifier::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?;
41/// assert_eq!(
42/// key,
43/// OpenPGPIdentifier::OpenPGPv4Fingerprint(OpenPGPv4Fingerprint::from_str(
44/// "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E"
45/// )?)
46/// );
47/// assert_eq!(key.to_string(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
48/// assert_eq!(
49/// key,
50/// OpenPGPv4Fingerprint::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?.into()
51/// );
52///
53/// // Create a OpenPGPIdentifier from a valid OpenPGP Key ID
54/// let key = OpenPGPIdentifier::from_str("2F2670AC164DB36F")?;
55/// assert_eq!(
56/// key,
57/// OpenPGPIdentifier::OpenPGPKeyId(OpenPGPKeyId::from_str("2F2670AC164DB36F")?)
58/// );
59/// assert_eq!(key.to_string(), "2F2670AC164DB36F");
60/// assert_eq!(key, OpenPGPKeyId::from_str("2F2670AC164DB36F")?.into());
61/// # Ok(())
62/// # }
63/// ```
64#[derive(Clone, Debug, Eq, PartialEq)]
65#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
66pub enum OpenPGPIdentifier {
67 /// An OpenPGP Key ID.
68 #[cfg_attr(feature = "serde", serde(rename = "openpgp_key_id"))]
69 OpenPGPKeyId(OpenPGPKeyId),
70 /// An OpenPGP v4 fingerprint.
71 #[cfg_attr(feature = "serde", serde(rename = "openpgp_v4_fingerprint"))]
72 OpenPGPv4Fingerprint(OpenPGPv4Fingerprint),
73}
74
75impl FromStr for OpenPGPIdentifier {
76 type Err = Error;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 match s.parse::<OpenPGPv4Fingerprint>() {
80 Ok(fingerprint) => Ok(OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)),
81 Err(_) => match s.parse::<OpenPGPKeyId>() {
82 Ok(key_id) => Ok(OpenPGPIdentifier::OpenPGPKeyId(key_id)),
83 Err(e) => Err(e),
84 },
85 }
86 }
87}
88
89impl From<OpenPGPKeyId> for OpenPGPIdentifier {
90 fn from(key_id: OpenPGPKeyId) -> Self {
91 OpenPGPIdentifier::OpenPGPKeyId(key_id)
92 }
93}
94
95impl From<OpenPGPv4Fingerprint> for OpenPGPIdentifier {
96 fn from(fingerprint: OpenPGPv4Fingerprint) -> Self {
97 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)
98 }
99}
100
101impl Display for OpenPGPIdentifier {
102 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
103 match self {
104 OpenPGPIdentifier::OpenPGPKeyId(key_id) => write!(f, "{key_id}"),
105 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint) => write!(f, "{fingerprint}"),
106 }
107 }
108}
109
110/// An OpenPGP Key ID.
111///
112/// The `OpenPGPKeyId` type wraps a `String` representing an [OpenPGP Key ID],
113/// ensuring that it consists of exactly 16 uppercase hexadecimal characters.
114///
115/// [OpenPGP Key ID]: https://openpgp.dev/book/glossary.html#term-Key-ID
116///
117/// ## Note
118///
119/// - This type supports constructing from both uppercase and lowercase hexadecimal characters but
120/// guarantees to return the key ID in uppercase.
121///
122/// - The usage of this type is highly discouraged as the keys may not be unique. This will lead to
123/// a linting error in the future.
124///
125/// ## Examples
126///
127/// ```
128/// use std::str::FromStr;
129///
130/// use alpm_types::{Error, OpenPGPKeyId};
131///
132/// # fn main() -> Result<(), alpm_types::Error> {
133/// // Create OpenPGPKeyId from a valid key ID
134/// let key = OpenPGPKeyId::from_str("2F2670AC164DB36F")?;
135/// assert_eq!(key.as_str(), "2F2670AC164DB36F");
136///
137/// // Attempting to create an OpenPGPKeyId from an invalid key ID will fail
138/// assert!(OpenPGPKeyId::from_str("INVALIDKEYID").is_err());
139///
140/// // Format as String
141/// assert_eq!(format!("{key}"), "2F2670AC164DB36F");
142/// # Ok(())
143/// # }
144/// ```
145#[derive(Clone, Debug, Eq, PartialEq)]
146#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
147pub struct OpenPGPKeyId(String);
148
149impl OpenPGPKeyId {
150 /// Creates a new `OpenPGPKeyId` instance.
151 ///
152 /// See [`OpenPGPKeyId::from_str`] for more information on how the OpenPGP Key ID is validated.
153 pub fn new(key_id: String) -> Result<Self, Error> {
154 if key_id.len() == 16 && key_id.chars().all(|c| c.is_ascii_hexdigit()) {
155 Ok(Self(key_id.to_ascii_uppercase()))
156 } else {
157 Err(Error::InvalidOpenPGPKeyId(key_id))
158 }
159 }
160
161 /// Returns a reference to the inner OpenPGP Key ID as a `&str`.
162 pub fn as_str(&self) -> &str {
163 &self.0
164 }
165
166 /// Consumes the `OpenPGPKeyId` and returns the inner `String`.
167 pub fn into_inner(self) -> String {
168 self.0
169 }
170}
171
172impl FromStr for OpenPGPKeyId {
173 type Err = Error;
174
175 /// Creates a new `OpenPGPKeyId` instance after validating that it follows the correct format.
176 ///
177 /// A valid OpenPGP Key ID should be exactly 16 characters long and consist only
178 /// of digits (`0-9`) and hexadecimal letters (`A-F`).
179 ///
180 /// # Errors
181 ///
182 /// Returns an error if the OpenPGP Key ID is not valid.
183 fn from_str(s: &str) -> Result<Self, Self::Err> {
184 Self::new(s.to_string())
185 }
186}
187
188impl Display for OpenPGPKeyId {
189 /// Converts the `OpenPGPKeyId` to an uppercase `String`.
190 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
191 write!(f, "{}", self.0)
192 }
193}
194
195/// An OpenPGP v4 fingerprint.
196///
197/// The `OpenPGPv4Fingerprint` type wraps a `String` representing an [OpenPGP v4 fingerprint],
198/// ensuring that it consists of 40 uppercase hexadecimal characters with optional whitespace
199/// separators.
200///
201/// [OpenPGP v4 fingerprint]: https://openpgp.dev/book/certificates.html#fingerprint
202///
203/// ## Note
204///
205/// - This type supports constructing from both uppercase and lowercase hexadecimal characters, with
206/// and without whitespace separators, but guarantees to return the fingerprint in uppercase and
207/// with no whitespaces.
208///
209/// - Whitespaces are only allowed between hexadecimal characters, not at the start or end of the
210/// fingerprint.
211///
212/// ## Examples
213///
214/// ```
215/// use std::str::FromStr;
216///
217/// use alpm_types::{Error, OpenPGPv4Fingerprint};
218///
219/// # fn main() -> Result<(), alpm_types::Error> {
220/// // Create OpenPGPv4Fingerprint from a valid OpenPGP v4 fingerprint
221/// let key = OpenPGPv4Fingerprint::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?;
222/// assert_eq!(key.as_str(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
223///
224/// // Space separated fingerprint is also valid
225/// let key = OpenPGPv4Fingerprint::from_str("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")?;
226/// assert_eq!(key.as_str(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
227///
228/// // Attempting to create a OpenPGPv4Fingerprint from an invalid fingerprint will fail
229/// assert!(OpenPGPv4Fingerprint::from_str("INVALIDKEY").is_err());
230///
231/// // Format as String
232/// assert_eq!(
233/// format!("{}", key),
234/// "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E"
235/// );
236/// # Ok(())
237/// # }
238/// ```
239#[derive(Clone, Debug, Eq, PartialEq)]
240#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
241pub struct OpenPGPv4Fingerprint(String);
242
243impl OpenPGPv4Fingerprint {
244 /// Creates a new `OpenPGPv4Fingerprint` instance
245 ///
246 /// See [`OpenPGPv4Fingerprint::from_str`] for more information on how the OpenPGP v4
247 /// fingerprint is validated.
248 pub fn new(fingerprint: String) -> Result<Self, Error> {
249 Self::from_str(&fingerprint)
250 }
251
252 /// Returns a reference to the inner OpenPGP v4 fingerprint as a `&str`.
253 pub fn as_str(&self) -> &str {
254 &self.0
255 }
256
257 /// Consumes the `OpenPGPv4Fingerprint` and returns the inner `String`.
258 pub fn into_inner(self) -> String {
259 self.0
260 }
261}
262
263impl FromStr for OpenPGPv4Fingerprint {
264 type Err = Error;
265
266 /// Creates a new `OpenPGPv4Fingerprint` instance after validating that it follows the correct
267 /// format.
268 ///
269 /// A valid OpenPGP v4 fingerprint should be a 40 characters long string of digits (`0-9`)
270 /// and hexadecimal letters (`A-F`) optionally separated by whitespaces.
271 ///
272 /// # Errors
273 ///
274 /// Returns an error if the OpenPGP v4 fingerprint is not valid.
275 fn from_str(s: &str) -> Result<Self, Self::Err> {
276 let normalized = s.to_ascii_uppercase().replace(" ", "");
277
278 if !s.starts_with(' ')
279 && !s.ends_with(' ')
280 && normalized.len() == 40
281 && normalized.chars().all(|c| c.is_ascii_hexdigit())
282 {
283 Ok(Self(normalized))
284 } else {
285 Err(Error::InvalidOpenPGPv4Fingerprint)
286 }
287 }
288}
289
290impl Display for OpenPGPv4Fingerprint {
291 /// Converts the `OpenPGPv4Fingerprint` to a uppercase `String`.
292 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
293 write!(f, "{}", self.as_str().to_ascii_uppercase())
294 }
295}
296
297/// A base64 encoded OpenPGP detached signature.
298///
299/// Wraps a [`String`] representing a [base64] encoded [OpenPGP detached signature]
300/// ensuring it consists of valid [base64] characters.
301///
302/// ## Examples
303///
304/// ```
305/// use std::str::FromStr;
306///
307/// use alpm_types::{Error, Base64OpenPGPSignature};
308///
309/// # fn main() -> Result<(), alpm_types::Error> {
310/// // Create Base64OpenPGPSignature from a valid base64 String
311/// let sig = Base64OpenPGPSignature::from_str("iHUEABYKAB0WIQRizHP4hOUpV7L92IObeih9mi7GCAUCaBZuVAAKCRCbeih9mi7GCIlMAP9ws/jU4f580ZRQlTQKvUiLbAZOdcB7mQQj83hD1Nc/GwD/WIHhO1/OQkpMERejUrLo3AgVmY3b4/uGhx9XufWEbgE=")?;
312///
313/// // Attempting to create a Base64OpenPGPSignature from an invalid base64 String will fail
314/// assert!(Base64OpenPGPSignature::from_str("!@#$^&*").is_err());
315///
316/// // Format as String
317/// assert_eq!(
318/// format!("{}", sig),
319/// "iHUEABYKAB0WIQRizHP4hOUpV7L92IObeih9mi7GCAUCaBZuVAAKCRCbeih9mi7GCIlMAP9ws/jU4f580ZRQlTQKvUiLbAZOdcB7mQQj83hD1Nc/GwD/WIHhO1/OQkpMERejUrLo3AgVmY3b4/uGhx9XufWEbgE="
320/// );
321/// # Ok(())
322/// # }
323/// ```
324///
325/// [base64]: https://en.wikipedia.org/wiki/Base64
326/// [OpenPGP detached signature]: https://openpgp.dev/book/signing_data.html#detached-signatures
327#[derive(Clone, Debug, Eq, PartialEq)]
328#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
329pub struct Base64OpenPGPSignature(String);
330
331impl Base64OpenPGPSignature {
332 /// Creates a new [`Base64OpenPGPSignature`] instance.
333 ///
334 /// See [`Base64OpenPGPSignature::from_str`] for more information on how the OpenPGP signature
335 /// is validated.
336 pub fn new(signature: String) -> Result<Self, Error> {
337 Self::from_str(&signature)
338 }
339
340 /// Returns a reference to the inner OpenPGP signature as a `&str`.
341 pub fn as_str(&self) -> &str {
342 &self.0
343 }
344
345 /// Consumes the [`Base64OpenPGPSignature`] and returns the inner [`String`].
346 pub fn into_inner(self) -> String {
347 self.0
348 }
349}
350
351impl AsRef<str> for Base64OpenPGPSignature {
352 fn as_ref(&self) -> &str {
353 self.as_str()
354 }
355}
356
357impl FromStr for Base64OpenPGPSignature {
358 type Err = Error;
359
360 /// Creates a new [`Base64OpenPGPSignature`] instance after validating that it follows the
361 /// correct format.
362 ///
363 /// A valid [OpenPGP signature] should consist only of [base64] characters (A-Z, a-z, 0-9, +, /)
364 /// and may include padding characters (=) at the end.
365 ///
366 /// # Errors
367 ///
368 /// Returns an error if the OpenPGP signature is not valid.
369 ///
370 /// [base64]: https://en.wikipedia.org/wiki/Base64
371 /// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html#detached-signatures
372 fn from_str(s: &str) -> Result<Self, Self::Err> {
373 BASE64_STANDARD
374 .decode(s)
375 .map_err(|_| Error::InvalidBase64Encoding {
376 expected_item: t!("error-invalid-base64-encoding-pgp-signature"),
377 })?
378 .to_vec();
379 Ok(Self(s.to_string()))
380 }
381}
382
383impl Display for Base64OpenPGPSignature {
384 /// Converts the [`Base64OpenPGPSignature`] to a [`String`].
385 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
386 write!(f, "{}", self.0)
387 }
388}
389
390/// A packager of a package
391///
392/// A `Packager` is represented by a User ID (e.g. `"Foobar McFooFace <foobar@mcfooface.org>"`).
393/// Internally this struct wraps a `String` for the name and an `EmailAddress` for a valid email
394/// address.
395///
396/// ## Examples
397/// ```
398/// use std::str::FromStr;
399///
400/// use alpm_types::{Error, Packager};
401///
402/// # fn main() -> Result<(), alpm_types::Error> {
403/// // create Packager from &str
404/// let packager = Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?;
405///
406/// // get name
407/// assert_eq!("Foobar McFooface", packager.name());
408///
409/// // get email
410/// assert_eq!("foobar@mcfooface.org", packager.email().to_string());
411///
412/// // get email domain
413/// assert_eq!("mcfooface.org", packager.email().domain());
414///
415/// // format as String
416/// assert_eq!(
417/// "Foobar McFooface <foobar@mcfooface.org>",
418/// format!("{}", packager)
419/// );
420/// # Ok(())
421/// # }
422/// ```
423#[derive(Clone, Debug, Eq, PartialEq)]
424#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
425pub struct Packager {
426 name: String,
427 email: EmailAddress,
428}
429
430impl Packager {
431 /// Create a new Packager
432 pub fn new(name: String, email: EmailAddress) -> Packager {
433 Packager { name, email }
434 }
435
436 /// Return the name of the Packager
437 pub fn name(&self) -> &str {
438 &self.name
439 }
440
441 /// Return the email of the Packager
442 pub fn email(&self) -> &EmailAddress {
443 &self.email
444 }
445}
446
447impl ParserUntil for Packager {
448 /// Parses a [`Packager`] from a string slice.
449 ///
450 /// # Errors
451 ///
452 /// Returns an error if `input` does not represent a valid [`Packager`].
453 fn parser_until<'a, P>(delimiter: P) -> impl Parser<Input<'a>, Self, ErrMode<ParseStack<'a>>>
454 where
455 P: Parser<Input<'a>, &'a str, ErrMode<ParseStack<'a>>>,
456 {
457 // Define the actual parser closure.
458 // The delimiter is moved into the closure and borrowed via `by_ref()` on each call.
459 let mut delimiter_parser = delimiter;
460 let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
461 // This format is a bit opaque and may include leading whitespaces.
462 // Consume any of those so we don't have to guess about them later on.
463 space0.parse_next(input)?;
464
465 // Make sure the first character isn't a `<`, which may happen if the packager name is
466 // missing.
467 not("<")
468 .expected_text("a packager name")
469 .parse_next(input)?;
470
471 // The name that precedes the email address
472 let name = repeat_till::<_, _, (), _, _, _, _>(
473 1..,
474 any,
475 peek(alt(("<", delimiter_parser.by_ref()))),
476 )
477 .take()
478 .map(|name: &str| name.trim().to_string())
479 .layer("packager name")
480 .parse_next(input)?;
481
482 // The '<' delimiter that marks the start of the email string
483 '<'.expected_text("opening delimiter '<'")
484 .layer("Email address")
485 .parse_next(input)?;
486
487 // The email address, which is validated by the EmailAddress struct.
488 let email = repeat_till::<_, _, (), _, _, _, _>(
489 1..,
490 any,
491 peek(alt((">", delimiter_parser.by_ref()))),
492 )
493 .take()
494 .try_map(EmailAddress::from_str)
495 .layer("email address")
496 .parse_next(input)?;
497
498 // The '>' delimiter that marks the end of the email string
499 '>'.expected_text("closing delimiter '>' of packager email address")
500 .layer("Email address")
501 .parse_next(input)?;
502
503 peek(delimiter_parser.by_ref())
504 .label("packager: unexpected trailing content")
505 .expected_text("end of input.")
506 .parse_next(input)?;
507
508 Ok(Self { name, email })
509 };
510
511 parser.layer("alpm packager")
512 }
513}
514
515impl FromStr for Packager {
516 type Err = Error;
517 /// Creates a [`Packager`] from a string slice.
518 ///
519 /// Delegates to [`Packager::parser_until`].
520 ///
521 /// # Errors
522 ///
523 /// Returns an error if [`Packager::parser_until`] fails.
524 fn from_str(s: &str) -> Result<Packager, Self::Err> {
525 Ok(Self::parser_until_eof.parse(Input::new(s))?)
526 }
527}
528
529impl Display for Packager {
530 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
531 write!(fmt, "{} <{}>", self.name, self.email)
532 }
533}
534
535#[cfg(test)]
536mod tests {
537 use insta::assert_snapshot;
538 use rstest::rstest;
539 use testresult::TestResult;
540
541 use super::*;
542 use crate::configure_insta;
543
544 #[rstest]
545 #[case("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")]
546 #[case("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")]
547 #[case("1234567890abcdef1234567890abcdef12345678")]
548 #[case("1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678")]
549 fn test_parse_openpgp_fingerprint(#[case] input: &str) -> Result<(), Error> {
550 input.parse::<OpenPGPv4Fingerprint>()?;
551 Ok(())
552 }
553
554 #[rstest]
555 // Contains non-hex characters 'G' and 'H'
556 #[case("A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0")]
557 // Less than 40 characters
558 #[case("1234567890ABCDEF1234567890ABCDEF1234567")]
559 // More than 40 characters
560 #[case("1234567890ABCDEF1234567890ABCDEF1234567890")]
561 // Starts with whitespace
562 #[case(" 4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")]
563 // Ends with whitespace
564 #[case("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E ")]
565 // Just invalid
566 #[case("invalid")]
567 fn openpgpv4_fingerprint_from_str_fails_on_invalid_input(#[case] input: &str) -> TestResult {
568 let Err(Error::InvalidOpenPGPv4Fingerprint) = OpenPGPv4Fingerprint::from_str(input) else {
569 panic!("Expected to fail with Error::InvalidOpenPGPv4Fingerprint");
570 };
571
572 Ok(())
573 }
574
575 /// Make sure that invalid OpenPGP v4 fingerprints don't deserialize.
576 #[cfg(feature = "serde")]
577 #[rstest]
578 #[case("A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0")]
579 #[case("invalid")]
580 fn openpgp_fingerprint_deserialize_error(#[case] input: &str) {
581 let Err(serde_json::Error { .. }) =
582 serde_json::from_str::<OpenPGPv4Fingerprint>(&format!("\"{input}\""))
583 else {
584 panic!("'{input}' erroneously deserialized as an OpenPGPv4Fingerprint")
585 };
586 }
587
588 #[rstest]
589 #[case("2F2670AC164DB36F")]
590 #[case("584A3EBFE705CDCD")]
591 fn test_parse_openpgp_key_id(#[case] input: &str) -> Result<(), Error> {
592 input.parse::<OpenPGPKeyId>()?;
593 Ok(())
594 }
595
596 #[cfg(feature = "serde")]
597 #[test]
598 fn test_serialize_openpgp_key_id() -> TestResult {
599 let id = "584A3EBFE705CDCD".parse::<OpenPGPKeyId>()?;
600 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPKeyId(id))?;
601 assert_eq!(r#"{"openpgp_key_id":"584A3EBFE705CDCD"}"#, json);
602
603 Ok(())
604 }
605
606 #[cfg(feature = "serde")]
607 #[rstest]
608 #[case(
609 "1234567890abcdef1234567890abcdef12345678",
610 "1234567890ABCDEF1234567890ABCDEF12345678"
611 )]
612 #[case(
613 "1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678",
614 "1234567890ABCDEF1234567890ABCDEF12345678"
615 )]
616 fn test_serialize_openpgp_v4_fingerprint(
617 #[case] input: &str,
618 #[case] output: &str,
619 ) -> TestResult {
620 let print = input.parse::<OpenPGPv4Fingerprint>()?;
621 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPv4Fingerprint(print))?;
622 assert_eq!(format!("{{\"openpgp_v4_fingerprint\":\"{output}\"}}"), json);
623
624 Ok(())
625 }
626
627 /// Ensures, that [`OpenPGPKeyId::from_str`] fails on invalid input.
628 #[rstest]
629 // Contains non-hex characters 'G' and 'H'
630 #[case("1234567890ABCGH")]
631 // Less than 16 characters
632 #[case("1234567890ABCDE")]
633 // More than 16 characters
634 #[case("1234567890ABCDEF0")]
635 // Just invalid
636 #[case("invalid")]
637 fn test_parse_invalid_openpgp_key_id(#[case] input: &str) {
638 let Err(Error::InvalidOpenPGPKeyId(invalid_input)) = OpenPGPKeyId::from_str(input) else {
639 panic!("Expected to fail with Error::InvalidOpenPGPKeyId");
640 };
641
642 assert_eq!(invalid_input, input);
643 }
644
645 /// Make sure that invalid OpenPGP key IDs don't deserialize.
646 #[cfg(feature = "serde")]
647 #[rstest]
648 #[case("1234567890ABCGH")]
649 #[case("invalid")]
650 fn openpgp_key_id_deserialize_error(#[case] input: &str) {
651 let Err(serde_json::Error { .. }) =
652 serde_json::from_str::<OpenPGPKeyId>(&format!("\"{input}\""))
653 else {
654 panic!("'{input}' erroneously deserialized as an OpenPGPKeyId")
655 };
656 }
657
658 #[rstest]
659 #[case("d2hhdCBhcmUgeW91IGxvb2tpbmcgZm9yPyA7LTsK")]
660 fn test_parse_openpgp_signature(#[case] input: &str) -> Result<(), Error> {
661 input.parse::<Base64OpenPGPSignature>()?;
662 Ok(())
663 }
664
665 /// Ensures, that [`Base64OpenPGPSignature::from_str`] fails on invalid input.
666 #[rstest]
667 // "=" in the middle
668 #[case("d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK")]
669 // invalid characters
670 #[case("!@#$%^&*")]
671 // just invalid
672 #[case("iHUEABYKh9mi7GCIlMAP9ws/jU4WEbgE=")]
673 fn base64_openpgp_signature_from_str_fails_on_invalid_input(#[case] input: &str) {
674 let Err(Error::InvalidBase64Encoding { .. }) = Base64OpenPGPSignature::from_str(input)
675 else {
676 panic!("Expected to fail with Error::InvalidBase64Encoding");
677 };
678 }
679
680 /// Make sure that invalid base64 encoded OpenPGP signatures don't deserialize.
681 #[cfg(feature = "serde")]
682 #[rstest]
683 #[case("d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK")]
684 #[case("!@#$%^&*")]
685 fn openpgp_signature_deserialize_error(#[case] input: &str) {
686 let Err(serde_json::Error { .. }) =
687 serde_json::from_str::<Base64OpenPGPSignature>(&format!("\"{input}\""))
688 else {
689 panic!("'{input}' erroneously deserialized as a Base64OpenPGPSignature")
690 };
691 }
692
693 #[rstest]
694 #[case(
695 "Foobar McFooface (The Third) <foobar@mcfooface.org>",
696 Packager{
697 name: "Foobar McFooface (The Third)".to_string(),
698 email: EmailAddress::from_str("foobar@mcfooface.org")?
699 }
700 )]
701 #[case(
702 "Foobar McFooface <foobar@mcfooface.org>",
703 Packager{
704 name: "Foobar McFooface".to_string(),
705 email: EmailAddress::from_str("foobar@mcfooface.org")?
706 }
707 )]
708 fn valid_packager(#[case] from_str: &str, #[case] packager: Packager) -> TestResult {
709 assert_eq!(Packager::from_str(from_str)?, packager);
710
711 Ok(())
712 }
713
714 /// Test that invalid packager expressions are detected as such and throw the expected error.
715 #[rstest]
716 #[case::no_name("<foobar@mcfooface.org>")]
717 #[case::no_name_and_address_not_wrapped("foobar@mcfooface.org")]
718 #[case::no_wrapped_address("Foobar McFooface")]
719 #[case::two_wrapped_addresses(
720 "Foobar McFooface <foobar@mcfooface.org> <foobar@mcfoofacemcfooface.org>"
721 )]
722 #[case::address_without_local_part("Foobar McFooface <@mcfooface.org>")]
723 fn invalid_packager(#[case] input: &str) -> TestResult {
724 let (test_name, _guard) = configure_insta();
725
726 let Err(err_msg) = Packager::from_str(input) else {
727 panic!("'{input}' erroneously parsed as a Package")
728 };
729
730 assert_snapshot!(test_name, err_msg.to_string());
731
732 Ok(())
733 }
734
735 #[rstest]
736 #[case(
737 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
738 "Foobar McFooface <foobar@mcfooface.org>"
739 )]
740 fn packager_format_string(
741 #[case] packager: Packager,
742 #[case] packager_str: &str,
743 ) -> TestResult {
744 assert_eq!(packager_str, format!("{packager}"));
745
746 Ok(())
747 }
748
749 #[rstest]
750 #[case(Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?, "Foobar McFooface")]
751 fn packager_name(#[case] packager: Packager, #[case] name: &str) -> TestResult {
752 assert_eq!(name, packager.name());
753
754 Ok(())
755 }
756
757 #[rstest]
758 #[case(
759 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
760 &EmailAddress::from_str("foobar@mcfooface.org")?,
761 )]
762 fn packager_email(#[case] packager: Packager, #[case] email: &EmailAddress) -> TestResult {
763 assert_eq!(email, packager.email());
764
765 Ok(())
766 }
767}