1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4 string::ToString,
5};
6
7use alpm_parsers::traits::ParserUntil;
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 ModalResult,
17 Parser,
18 combinator::{alt, not, peek, repeat_till},
19 error::{ContextError, ErrMode, StrContext, StrContextValue},
20 token::any,
21};
22
23use crate::Error;
24
25#[derive(Clone, Debug, Eq, PartialEq)]
66#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
67pub enum OpenPGPIdentifier {
68 #[cfg_attr(feature = "serde", serde(rename = "openpgp_key_id"))]
70 OpenPGPKeyId(OpenPGPKeyId),
71 #[cfg_attr(feature = "serde", serde(rename = "openpgp_v4_fingerprint"))]
73 OpenPGPv4Fingerprint(OpenPGPv4Fingerprint),
74}
75
76impl FromStr for OpenPGPIdentifier {
77 type Err = Error;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 match s.parse::<OpenPGPv4Fingerprint>() {
81 Ok(fingerprint) => Ok(OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)),
82 Err(_) => match s.parse::<OpenPGPKeyId>() {
83 Ok(key_id) => Ok(OpenPGPIdentifier::OpenPGPKeyId(key_id)),
84 Err(e) => Err(e),
85 },
86 }
87 }
88}
89
90impl From<OpenPGPKeyId> for OpenPGPIdentifier {
91 fn from(key_id: OpenPGPKeyId) -> Self {
92 OpenPGPIdentifier::OpenPGPKeyId(key_id)
93 }
94}
95
96impl From<OpenPGPv4Fingerprint> for OpenPGPIdentifier {
97 fn from(fingerprint: OpenPGPv4Fingerprint) -> Self {
98 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)
99 }
100}
101
102impl Display for OpenPGPIdentifier {
103 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
104 match self {
105 OpenPGPIdentifier::OpenPGPKeyId(key_id) => write!(f, "{key_id}"),
106 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint) => write!(f, "{fingerprint}"),
107 }
108 }
109}
110
111#[derive(Clone, Debug, Eq, PartialEq)]
147#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
148pub struct OpenPGPKeyId(String);
149
150impl OpenPGPKeyId {
151 pub fn new(key_id: String) -> Result<Self, Error> {
155 if key_id.len() == 16 && key_id.chars().all(|c| c.is_ascii_hexdigit()) {
156 Ok(Self(key_id.to_ascii_uppercase()))
157 } else {
158 Err(Error::InvalidOpenPGPKeyId(key_id))
159 }
160 }
161
162 pub fn as_str(&self) -> &str {
164 &self.0
165 }
166
167 pub fn into_inner(self) -> String {
169 self.0
170 }
171}
172
173impl FromStr for OpenPGPKeyId {
174 type Err = Error;
175
176 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 Self::new(s.to_string())
186 }
187}
188
189impl Display for OpenPGPKeyId {
190 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
192 write!(f, "{}", self.0)
193 }
194}
195
196#[derive(Clone, Debug, Eq, PartialEq)]
241#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
242pub struct OpenPGPv4Fingerprint(String);
243
244impl OpenPGPv4Fingerprint {
245 pub fn new(fingerprint: String) -> Result<Self, Error> {
250 Self::from_str(&fingerprint)
251 }
252
253 pub fn as_str(&self) -> &str {
255 &self.0
256 }
257
258 pub fn into_inner(self) -> String {
260 self.0
261 }
262}
263
264impl FromStr for OpenPGPv4Fingerprint {
265 type Err = Error;
266
267 fn from_str(s: &str) -> Result<Self, Self::Err> {
277 let normalized = s.to_ascii_uppercase().replace(" ", "");
278
279 if !s.starts_with(' ')
280 && !s.ends_with(' ')
281 && normalized.len() == 40
282 && normalized.chars().all(|c| c.is_ascii_hexdigit())
283 {
284 Ok(Self(normalized))
285 } else {
286 Err(Error::InvalidOpenPGPv4Fingerprint)
287 }
288 }
289}
290
291impl Display for OpenPGPv4Fingerprint {
292 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
294 write!(f, "{}", self.as_str().to_ascii_uppercase())
295 }
296}
297
298#[derive(Clone, Debug, Eq, PartialEq)]
329#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
330pub struct Base64OpenPGPSignature(String);
331
332impl Base64OpenPGPSignature {
333 pub fn new(signature: String) -> Result<Self, Error> {
338 Self::from_str(&signature)
339 }
340
341 pub fn as_str(&self) -> &str {
343 &self.0
344 }
345
346 pub fn into_inner(self) -> String {
348 self.0
349 }
350}
351
352impl AsRef<str> for Base64OpenPGPSignature {
353 fn as_ref(&self) -> &str {
354 self.as_str()
355 }
356}
357
358impl FromStr for Base64OpenPGPSignature {
359 type Err = Error;
360
361 fn from_str(s: &str) -> Result<Self, Self::Err> {
374 BASE64_STANDARD
375 .decode(s)
376 .map_err(|_| Error::InvalidBase64Encoding {
377 expected_item: t!("error-invalid-base64-encoding-pgp-signature"),
378 })?
379 .to_vec();
380 Ok(Self(s.to_string()))
381 }
382}
383
384impl Display for Base64OpenPGPSignature {
385 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
387 write!(f, "{}", self.0)
388 }
389}
390
391#[derive(Clone, Debug, Eq, PartialEq)]
425#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
426pub struct Packager {
427 name: String,
428 email: EmailAddress,
429}
430
431impl Packager {
432 pub fn new(name: String, email: EmailAddress) -> Packager {
434 Packager { name, email }
435 }
436
437 pub fn name(&self) -> &str {
439 &self.name
440 }
441
442 pub fn email(&self) -> &EmailAddress {
444 &self.email
445 }
446}
447
448impl ParserUntil for Packager {
449 fn parser_until<'a, P>(delimiter: P) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
455 where
456 P: Parser<&'a str, &'a str, ErrMode<ContextError>>,
457 {
458 let mut delimiter_parser = delimiter;
461 move |input: &mut &'a str| -> ModalResult<Self> {
462 not("<")
465 .context(StrContext::Label("packager name"))
466 .context(StrContext::Expected(StrContextValue::Description(
467 "a packager name",
468 )))
469 .parse_next(input)?;
470
471 let name = repeat_till::<_, _, (), _, _, _, _>(
473 1..,
474 any,
475 peek(alt(("<", delimiter_parser.by_ref()))),
476 )
477 .take()
478 .map(|name: &str| name.trim())
479 .verify(|name: &str| !name.is_empty())
480 .map(|name: &str| name.to_string())
481 .context(StrContext::Label("packager name"))
482 .parse_next(input)?;
483
484 '<'.context(StrContext::Label("packager"))
486 .context(StrContext::Expected(StrContextValue::Description(
487 "opening delimiter '<' for email address",
488 )))
489 .parse_next(input)?;
490
491 let email = repeat_till::<_, _, (), _, _, _, _>(
493 1..,
494 any,
495 peek(alt((">", delimiter_parser.by_ref()))),
496 )
497 .take()
498 .try_map(EmailAddress::from_str)
499 .context(StrContext::Label("Email address"))
500 .parse_next(input)?;
501
502 '>'.context(StrContext::Label("packager"))
504 .context(StrContext::Expected(StrContextValue::Description(
505 "closing delimiter '>' of packager email address",
506 )))
507 .parse_next(input)?;
508
509 peek(delimiter_parser.by_ref())
510 .context(StrContext::Label("packager: unexpected trailing content"))
511 .context(StrContext::Expected(StrContextValue::Description(
512 "end of input.",
513 )))
514 .parse_next(input)?;
515
516 Ok(Self { name, email })
517 }
518 }
519}
520
521impl FromStr for Packager {
522 type Err = Error;
523 fn from_str(s: &str) -> Result<Packager, Self::Err> {
531 Ok(Self::parser_until_eof.parse(s)?)
532 }
533}
534
535impl Display for Packager {
536 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
537 write!(fmt, "{} <{}>", self.name, self.email)
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use insta::assert_snapshot;
544 use rstest::rstest;
545 #[cfg(feature = "serde")]
546 use testresult::TestResult;
547
548 use super::*;
549 use crate::configure_insta;
550
551 #[rstest]
552 #[case("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")]
553 #[case("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")]
554 #[case("1234567890abcdef1234567890abcdef12345678")]
555 #[case("1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678")]
556 fn test_parse_openpgp_fingerprint(#[case] input: &str) -> Result<(), Error> {
557 input.parse::<OpenPGPv4Fingerprint>()?;
558 Ok(())
559 }
560
561 #[rstest]
562 #[case(
564 "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0",
565 Err(Error::InvalidOpenPGPv4Fingerprint)
566 )]
567 #[case(
569 "1234567890ABCDEF1234567890ABCDEF1234567",
570 Err(Error::InvalidOpenPGPv4Fingerprint)
571 )]
572 #[case(
574 "1234567890ABCDEF1234567890ABCDEF1234567890",
575 Err(Error::InvalidOpenPGPv4Fingerprint)
576 )]
577 #[case(
579 " 4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E",
580 Err(Error::InvalidOpenPGPv4Fingerprint)
581 )]
582 #[case(
584 "4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E ",
585 Err(Error::InvalidOpenPGPv4Fingerprint)
586 )]
587 #[case("invalid", Err(Error::InvalidOpenPGPv4Fingerprint))]
589 fn test_parse_invalid_openpgp_fingerprint(
590 #[case] input: &str,
591 #[case] expected: Result<OpenPGPv4Fingerprint, Error>,
592 ) {
593 let result = input.parse::<OpenPGPv4Fingerprint>();
594 assert_eq!(result, expected);
595 }
596
597 #[cfg(feature = "serde")]
599 #[rstest]
600 #[case("A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0")]
601 #[case("invalid")]
602 fn openpgp_fingerprint_deserialize_error(#[case] input: &str) {
603 let Err(serde_json::Error { .. }) =
604 serde_json::from_str::<OpenPGPv4Fingerprint>(&format!("\"{input}\""))
605 else {
606 panic!("'{input}' erroneously deserialized as an OpenPGPv4Fingerprint")
607 };
608 }
609
610 #[rstest]
611 #[case("2F2670AC164DB36F")]
612 #[case("584A3EBFE705CDCD")]
613 fn test_parse_openpgp_key_id(#[case] input: &str) -> Result<(), Error> {
614 input.parse::<OpenPGPKeyId>()?;
615 Ok(())
616 }
617
618 #[cfg(feature = "serde")]
619 #[test]
620 fn test_serialize_openpgp_key_id() -> TestResult {
621 let id = "584A3EBFE705CDCD".parse::<OpenPGPKeyId>()?;
622 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPKeyId(id))?;
623 assert_eq!(r#"{"openpgp_key_id":"584A3EBFE705CDCD"}"#, json);
624
625 Ok(())
626 }
627
628 #[cfg(feature = "serde")]
629 #[rstest]
630 #[case(
631 "1234567890abcdef1234567890abcdef12345678",
632 "1234567890ABCDEF1234567890ABCDEF12345678"
633 )]
634 #[case(
635 "1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678",
636 "1234567890ABCDEF1234567890ABCDEF12345678"
637 )]
638 fn test_serialize_openpgp_v4_fingerprint(
639 #[case] input: &str,
640 #[case] output: &str,
641 ) -> TestResult {
642 let print = input.parse::<OpenPGPv4Fingerprint>()?;
643 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPv4Fingerprint(print))?;
644 assert_eq!(format!("{{\"openpgp_v4_fingerprint\":\"{output}\"}}"), json);
645
646 Ok(())
647 }
648
649 #[rstest]
650 #[case("1234567890ABCGH", Err(Error::InvalidOpenPGPKeyId("1234567890ABCGH".to_string())))]
652 #[case("1234567890ABCDE", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDE".to_string())))]
654 #[case("1234567890ABCDEF0", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDEF0".to_string())))]
656 #[case("invalid", Err(Error::InvalidOpenPGPKeyId("invalid".to_string())))]
658 fn test_parse_invalid_openpgp_key_id(
659 #[case] input: &str,
660 #[case] expected: Result<OpenPGPKeyId, Error>,
661 ) {
662 let result = input.parse::<OpenPGPKeyId>();
663 assert_eq!(result, expected);
664 }
665
666 #[cfg(feature = "serde")]
668 #[rstest]
669 #[case("1234567890ABCGH")]
670 #[case("invalid")]
671 fn openpgp_key_id_deserialize_error(#[case] input: &str) {
672 let Err(serde_json::Error { .. }) =
673 serde_json::from_str::<OpenPGPKeyId>(&format!("\"{input}\""))
674 else {
675 panic!("'{input}' erroneously deserialized as an OpenPGPKeyId")
676 };
677 }
678
679 #[rstest]
680 #[case("d2hhdCBhcmUgeW91IGxvb2tpbmcgZm9yPyA7LTsK")]
681 fn test_parse_openpgp_signature(#[case] input: &str) -> Result<(), Error> {
682 input.parse::<Base64OpenPGPSignature>()?;
683 Ok(())
684 }
685
686 #[rstest]
687 #[case(
689 "d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK",
690 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
691 )]
692 #[case("!@#$%^&*", Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") }))]
694 #[case(
696 "iHUEABYKh9mi7GCIlMAP9ws/jU4WEbgE=",
697 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
698 )]
699 fn test_parse_invalid_openpgp_signature(
700 #[case] input: &str,
701 #[case] expected: Result<Base64OpenPGPSignature, Error>,
702 ) {
703 let result = input.parse::<Base64OpenPGPSignature>();
704 assert_eq!(result, expected);
705 }
706
707 #[cfg(feature = "serde")]
709 #[rstest]
710 #[case("d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK")]
711 #[case("!@#$%^&*")]
712 fn openpgp_signature_deserialize_error(#[case] input: &str) {
713 let Err(serde_json::Error { .. }) =
714 serde_json::from_str::<Base64OpenPGPSignature>(&format!("\"{input}\""))
715 else {
716 panic!("'{input}' erroneously deserialized as a Base64OpenPGPSignature")
717 };
718 }
719
720 #[rstest]
721 #[case(
722 "Foobar McFooface (The Third) <foobar@mcfooface.org>",
723 Packager{
724 name: "Foobar McFooface (The Third)".to_string(),
725 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
726 }
727 )]
728 #[case(
729 "Foobar McFooface <foobar@mcfooface.org>",
730 Packager{
731 name: "Foobar McFooface".to_string(),
732 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
733 }
734 )]
735 fn valid_packager(#[case] from_str: &str, #[case] packager: Packager) {
736 assert_eq!(Packager::from_str(from_str), Ok(packager));
737 }
738
739 #[rstest]
741 #[case::no_name("<foobar@mcfooface.org>")]
742 #[case::no_name_and_address_not_wrapped("foobar@mcfooface.org")]
743 #[case::no_wrapped_address("Foobar McFooface")]
744 #[case::two_wrapped_addresses(
745 "Foobar McFooface <foobar@mcfooface.org> <foobar@mcfoofacemcfooface.org>"
746 )]
747 #[case::address_without_local_part("Foobar McFooface <@mcfooface.org>")]
748 fn invalid_packager(#[case] input: &str) {
749 let Err(err_msg) = Packager::from_str(input) else {
750 panic!("'{input}' erroneously parsed as a Package")
751 };
752
753 let (test_name, _guard) = configure_insta();
754 assert_snapshot!(test_name, err_msg.to_string());
755 }
756
757 #[rstest]
758 #[case(
759 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
760 "Foobar McFooface <foobar@mcfooface.org>"
761 )]
762 fn packager_format_string(#[case] packager: Packager, #[case] packager_str: &str) {
763 assert_eq!(packager_str, format!("{packager}"));
764 }
765
766 #[rstest]
767 #[case(Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(), "Foobar McFooface")]
768 fn packager_name(#[case] packager: Packager, #[case] name: &str) {
769 assert_eq!(name, packager.name());
770 }
771
772 #[rstest]
773 #[case(
774 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
775 &EmailAddress::from_str("foobar@mcfooface.org").unwrap(),
776 )]
777 fn packager_email(#[case] packager: Packager, #[case] email: &EmailAddress) {
778 assert_eq!(email, packager.email());
779 }
780}