sequoia_cert_store/store.rs
1use std::borrow::Cow;
2use std::str;
3use std::sync::Arc;
4use std::sync::atomic::AtomicUsize;
5use std::sync::atomic::Ordering;
6
7use anyhow::Context;
8
9use sequoia_openpgp as openpgp;
10use openpgp::Fingerprint;
11use openpgp::KeyHandle;
12use openpgp::Result;
13use openpgp::cert::Cert;
14use openpgp::cert::ValidCert;
15use openpgp::packet::UserID;
16
17#[cfg(certd)]
18pub use openpgp_cert_d;
19
20mod userid_index;
21pub use userid_index::UserIDIndex;
22
23#[cfg(certd)]
24pub mod certd;
25#[cfg(certd)]
26pub use certd::CertD;
27
28pub mod certs;
29pub use certs::Certs;
30
31// The keyserver backend is optional.
32#[cfg(feature = "keyserver")]
33pub mod keyserver;
34#[cfg(feature = "keyserver")]
35pub use keyserver::KeyServer;
36
37// If it is disabled, we swap in a dummy to reduce changes to the rest
38// of the code.
39pub mod no_keyserver;
40#[cfg(not(feature = "keyserver"))]
41use no_keyserver as keyserver;
42#[cfg(not(feature = "keyserver"))]
43pub(crate) use keyserver::KeyServer;
44
45#[cfg(pep)]
46pub mod pep;
47#[cfg(pep)]
48pub use pep::Pep;
49
50use super::TRACE;
51
52use crate::LazyCert;
53
54#[derive(Debug, Clone)]
55pub struct UserIDQueryParams {
56 anchor_start: bool,
57 anchor_end: bool,
58 email: bool,
59 ignore_case: bool,
60}
61assert_send_and_sync!(UserIDQueryParams);
62
63impl UserIDQueryParams {
64 /// Returns a new `UserIDQueryParams`.
65 ///
66 /// By default, the query is configured to perform an exact match
67 /// on the User ID. That is, the pattern must match the start and
68 /// end of the User ID, and case is considered significant.
69 pub fn new() -> Self {
70 Self {
71 anchor_start: true,
72 anchor_end: true,
73 email: false,
74 ignore_case: false,
75 }
76 }
77
78 /// Sets whether the pattern must match the start of the User ID
79 /// or email address.
80 pub fn set_anchor_start(&mut self, anchor_start: bool) -> &mut Self {
81 self.anchor_start = anchor_start;
82 self
83 }
84
85 /// Returns whether the pattern must match the start of the User
86 /// ID or email address.
87 pub fn anchor_start(&self) -> bool {
88 self.anchor_start
89 }
90
91 /// Sets whether the pattern must match the end of the User
92 /// ID or email address.
93 pub fn set_anchor_end(&mut self, anchor_end: bool) -> &mut Self {
94 self.anchor_end = anchor_end;
95 self
96 }
97
98 /// Returns whether the pattern must match the end of the User
99 /// ID or email address.
100 pub fn anchor_end(&self) -> bool {
101 self.anchor_end
102 }
103
104 /// Sets whether the pattern must match the User ID or the
105 /// normalized email address.
106 ///
107 /// The email address to check the pattern against is extracted
108 /// from the User ID using [`UserID::email_normalized`].
109 ///
110 /// Note: the pattern is *not* normalized, even if the anchors are
111 /// set. If you want to search by email address you need to
112 /// normalize it your yourself.
113 ///
114 /// [`UserID::email_normalized`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/packet/prelude/struct.UserID.html#method.email_normalized
115 pub fn set_email(&mut self, email: bool) -> &mut Self {
116 self.email = email;
117 self
118 }
119
120 /// Returns whether the pattern must match the User ID or the
121 /// normalized email address.
122 ///
123 /// See [`UserIDQueryParams::set_email`] for more details.
124 pub fn email(&self) -> bool {
125 self.email
126 }
127
128 /// Sets whether to ignore the case when matching the User ID or
129 /// email address.
130 ///
131 /// Uses the empty local.
132 ///
133 /// When matching an email address, the domain is always matched
134 /// in a case insensitive manner. The localpart, however, is
135 /// matched in a case sensitive manner by default.
136 pub fn set_ignore_case(&mut self, ignore_case: bool) -> &mut Self {
137 self.ignore_case = ignore_case;
138 self
139 }
140
141 /// Returns whether to ignore the case when matching the User ID
142 /// or email address.
143 ///
144 /// See [`UserIDQueryParams::set_ignore_case`] for more details.
145 pub fn ignore_case(&self) -> bool {
146 self.ignore_case
147 }
148
149 /// Checks that the User ID satisfies the constraints.
150 pub fn check(&self, userid: &UserID, pattern: &str) -> bool {
151 tracer!(TRACE, "UserIDQueryParams::check");
152
153 // XXX: If you change this function,
154 // UserIDIndex::select_userid contains similar code. Update
155 // that too.
156 match self {
157 UserIDQueryParams {
158 anchor_start: true,
159 anchor_end: true,
160 email: false,
161 ignore_case: false,
162 } => {
163 // Exact User ID match.
164 userid.value() == pattern.as_bytes()
165 }
166
167 UserIDQueryParams {
168 anchor_start: true,
169 anchor_end: true,
170 email: true,
171 ignore_case: false,
172 } => {
173 // Exact email match.
174 if let Ok(Some(email)) = userid.email_normalized() {
175 email == pattern
176 } else {
177 false
178 }
179 }
180
181 UserIDQueryParams {
182 anchor_start,
183 anchor_end,
184 email,
185 ignore_case,
186 } => {
187 t!("Considering if {:?} matches {:?} \
188 (anchors: {}, {}, ignore case: {})",
189 pattern, userid, anchor_start, anchor_end, ignore_case);
190
191 // Substring search.
192 let mut userid = if *email {
193 if let Ok(Some(email)) = userid.email_normalized() {
194 Cow::Owned(email)
195 } else {
196 t!("User ID does not contain a valid email address");
197 return false;
198 }
199 } else {
200 String::from_utf8_lossy(userid.value())
201 };
202
203 if *ignore_case {
204 userid = Cow::Owned(userid.to_lowercase());
205 }
206
207 let mut pattern = pattern;
208 let _pattern;
209 if *ignore_case {
210 // Convert to lowercase without tailoring,
211 // i.e. without taking any locale into account.
212 // See:
213 //
214 // - https://www.w3.org/International/wiki/Case_folding
215 // - https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
216 // - http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992
217 _pattern = pattern.to_lowercase();
218 pattern = &_pattern[..];
219 }
220
221 if match (*anchor_start, *anchor_end) {
222 (true, true) => userid == pattern,
223 (true, false) => userid.starts_with(pattern),
224 (false, true) => userid.ends_with(pattern),
225 (false, false) => userid.contains(pattern),
226 }
227 {
228 t!("*** {:?} matches {:?} (anchors: {}, {})",
229 pattern, userid, *anchor_start, anchor_end);
230 true
231 } else {
232 false
233 }
234 }
235 }
236 }
237
238 /// Checks that at least one User ID satisfies the constraints.
239 pub fn check_lazy_cert(&self, cert: &LazyCert, pattern: &str) -> bool {
240 cert.userids().any(|userid| self.check(&userid, pattern))
241 }
242
243 /// Checks that at least one User ID satisfies the constraints.
244 pub fn check_cert(&self, cert: &Cert, pattern: &str) -> bool {
245 cert.userids().any(|ua| self.check(ua.userid(), pattern))
246 }
247
248 /// Checks that at least one User ID satisfies the constraints.
249 pub fn check_valid_cert(&self, vc: &ValidCert, pattern: &str) -> bool {
250 vc.userids().any(|ua| self.check(ua.userid(), pattern))
251 }
252
253 /// Returns whether the supplied email address is actually a valid
254 /// email address.
255 ///
256 /// If it is valid, returns the normalized email address.
257 pub fn is_email(email: &str) -> Result<String> {
258 let email_check = UserID::from(format!("<{}>", email));
259 match email_check.email() {
260 Ok(Some(email_check)) => {
261 if email != email_check {
262 return Err(StoreError::InvalidEmail(
263 email.to_string(), None).into());
264 }
265 }
266 Ok(None) => {
267 return Err(StoreError::InvalidEmail(
268 email.to_string(), None).into());
269 }
270 Err(err) => {
271 return Err(StoreError::InvalidEmail(
272 email.to_string(), Some(err)).into());
273 }
274 }
275
276 match UserID::from(&email[..]).email_normalized() {
277 Err(err) => {
278 Err(StoreError::InvalidEmail(
279 email.to_string(), Some(err)).into())
280 }
281 Ok(None) => {
282 Err(StoreError::InvalidEmail(
283 email.to_string(), None).into())
284 }
285 Ok(Some(email)) => {
286 Ok(email)
287 }
288 }
289 }
290
291 /// Returns whether the supplied domain address is actually a
292 /// valid domain for an email address.
293 ///
294 /// Returns the normalized domain.
295 pub fn is_domain(domain: &str) -> Result<String> {
296 let localpart = "user@";
297 let email = format!("{}{}", localpart, domain);
298 let email = Self::is_email(&email)?;
299
300 // We get the normalized email address back. Chop off the
301 // username and the @.
302 assert!(email.starts_with(localpart));
303 Ok(email[localpart.len()..].to_string())
304 }
305}
306
307/// [`Store`] specific error codes.
308#[non_exhaustive]
309#[derive(thiserror::Error, Debug)]
310pub enum StoreError {
311 /// No certificate was found.
312 #[error("{0} was not found")]
313 NotFound(KeyHandle),
314
315 /// No certificate matches the search criteria.
316 #[error("No certificates matched {0}")]
317 NoMatches(String),
318
319 /// The email address does not appear to be a valid email address.
320 #[error("{0:?} does not appear to be a valid email address")]
321 InvalidEmail(String, #[source] Option<anyhow::Error>),
322}
323
324/// Status messages.
325///
326/// Status messages sent by [`StatusListener::update`].
327///
328/// The transaction id allows messages to be grouped together. For
329/// instance, `LookupStarted` will return a new transaction id and
330/// further messages related to that lookup including `LookupFinished`
331/// and `LookupFailed` will use the same transaction id.
332#[non_exhaustive]
333#[derive(Debug)]
334pub enum StatusUpdate<'a, 'c> {
335 /// Sent when a lookup is starting.
336 ///
337 /// usize is the transaction id.
338 ///
339 /// The first `&str` is a short human-readable description of the
340 /// backend.
341 ///
342 /// `KeyHandle` is what is being looked up.
343 ///
344 /// The last `&str` is a short human-readable message describing
345 /// the look up.
346 LookupStarted(usize, &'a str, &'a KeyHandle, Option<&'a str>),
347
348 /// Sent while a lookup is on-going.
349 ///
350 /// usize is the transaction id. It will match the transaction id
351 /// sent in the `LookupStart` message.
352 ///
353 /// The first `&str` is a short human-readable description of the
354 /// backend.
355 ///
356 /// `KeyHandle` is what was being looked up.
357 ///
358 /// The last `&str` is a short human-readable message describing
359 /// something that happened, e.g., "WKD returned FPR", etc.
360 LookupStatus(usize, &'a str, &'a KeyHandle, &'a str),
361
362 /// Sent when a lookup has been successful.
363 ///
364 /// usize is the transaction id. It will match the transaction id
365 /// sent in the `LookupStart` message.
366 ///
367 /// The first `&str` is a short human-readable description of the
368 /// backend.
369 ///
370 /// `KeyHandle` is what was being looked up.
371 ///
372 /// The certificates are the returned results.
373 ///
374 /// The last `&str` is a short human-readable message describing
375 /// what happened, e.g., "found in cache", etc.
376 LookupFinished(usize, &'a str, &'a KeyHandle,
377 &'a [Arc<LazyCert<'c>>], Option<&'a str>),
378
379 /// Sent when a lookup has failed.
380 ///
381 /// usize is the transaction id. It will match the transaction id
382 /// sent in the `LookupStart` message.
383 ///
384 /// The first `&str` is a short human-readable description of the
385 /// backend.
386 ///
387 /// `KeyHandle` is what was being looked up.
388 ///
389 /// The error is the reason that the lookup failed. A backend
390 /// should set this to `None` if no certificate was present.
391 LookupFailed(usize, &'a str, &'a KeyHandle, Option<&'a anyhow::Error>),
392
393 /// Sent when a search is started.
394 ///
395 /// usize is the transaction id.
396 ///
397 /// The first `&str` is a short human-readable description of the
398 /// backend.
399 ///
400 /// The second `&str` is the pattern being searched for.
401 ///
402 /// The last `&str` is a short human-readable message describing
403 /// what is being looked up.
404 SearchStarted(usize, &'a str, &'a str, Option<&'a str>),
405
406 /// Sent while a search is on-going.
407 ///
408 /// usize is the transaction id. It will match the transaction id
409 /// sent in the `LookupStart` message.
410 ///
411 /// The first `&str` is a short human-readable description of the
412 /// backend.
413 ///
414 /// The second `&str` is the pattern being searched for.
415 ///
416 /// The last `&str` is a short human-readable message describing
417 /// something that happened, e.g., "WKD returned FPR", etc.
418 SearchStatus(usize, &'a str, &'a str, &'a str),
419
420 /// Sent when a search is successful.
421 ///
422 /// usize is the transaction id. It will match the transaction id
423 /// sent in the `LookupStart` message.
424 ///
425 /// The first `&str` is a short human-readable description of the
426 /// backend.
427 ///
428 /// The second `&str` is the pattern being searched for.
429 ///
430 /// The certificates are the returned results.
431 ///
432 /// The last `&str` is a short human-readable message describing
433 /// what happened, e.g., "found in cache", "found 5 matching
434 /// certificates", etc.
435 SearchFinished(usize, &'a str, &'a str, &'a [Arc<LazyCert<'c>>],
436 Option<&'a str>),
437
438 /// Sent whenever something has been lookup successfully.
439 ///
440 /// usize is the transaction id. It will match the transaction id
441 /// sent in the `LookupStart` message.
442 ///
443 /// The first `&str` is a short human-readable description of the
444 /// backend.
445 ///
446 /// The second `&str` is the pattern being searched for.
447 ///
448 /// The error is the reason that the search failed. A backend
449 /// should set this to `None` if no matching certificate was
450 /// found.
451 SearchFailed(usize, &'a str, &'a str, Option<&'a anyhow::Error>),
452}
453
454impl<'a, 'c> std::fmt::Display for StatusUpdate<'a, 'c> {
455 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>)
456 -> std::result::Result<(), std::fmt::Error>
457 {
458 use StatusUpdate::*;
459
460 match self {
461 LookupStarted(_tx, backend, kh, msg) => {
462 if let Some(msg) = msg {
463 write!(fmt, "{}: Looking up {}: {}...",
464 backend, kh, msg)
465 } else {
466 write!(fmt, "{}: Looking up {}...",
467 backend, kh)
468 }
469 }
470 LookupStatus(_tx, backend, kh, msg) => {
471 write!(fmt, "{}: Looking up {}: {}",
472 backend, kh, msg)
473 }
474 LookupFinished(_tx, backend, kh, results, msg) => {
475 if let Some(msg) = msg {
476 write!(fmt, "{}: Looking up {}, returned {} results: {}",
477 backend, kh, results.len(), msg)
478 } else {
479 write!(fmt, "{}: Looking up {}, returned {} results",
480 backend, kh, results.len())
481 }
482 }
483 LookupFailed(_tx, backend, kh, err) => {
484 if let Some(err) = err {
485 write!(fmt, "{}: Looking up {}, failed: {}",
486 backend, kh, err)
487 } else {
488 write!(fmt, "{}: Looking up {}, returned no results",
489 backend, kh)
490 }
491 }
492
493 SearchStarted(_tx, backend, pattern, msg) => {
494 if let Some(msg) = msg {
495 write!(fmt, "{}: Searching for {:?}: {}...",
496 backend, pattern, msg)
497 } else {
498 write!(fmt, "{}: Searching for {:?}...",
499 backend, pattern)
500 }
501 }
502 SearchStatus(_tx, backend, pattern, msg) => {
503 write!(fmt, "{}: Searching for {:?}: {}",
504 backend, pattern, msg)
505 }
506 SearchFinished(_tx, backend, pattern, results, msg) => {
507 if let Some(msg) = msg {
508 write!(fmt, "{}: Searching for {:?}, returned {} results: {}",
509 backend, pattern, results.len(), msg)
510 } else {
511 write!(fmt, "{}: Searching for {:?}, returned {} results",
512 backend, pattern, results.len())
513 }
514 }
515 SearchFailed(_tx, backend, pattern, err) => {
516 if let Some(err) = err {
517 write!(fmt, "{}: Searching for {:?} failed: {}",
518 backend, pattern, err)
519 } else {
520 write!(fmt, "{}: Searching for {:?}, returned no results",
521 backend, pattern)
522 }
523 }
524 }
525 }
526}
527
528
529/// A callback mechanism to indicate what a backend is doing.
530///
531/// We use an enum instead of a separate function for each message so
532/// that a naive listener can just print each message to stdout.
533///
534/// This is primarily interesting for backends like a keyserver.
535///
536/// Currently, backends are not required to implement this trait. In
537/// fact [`KeyServer`] is the only backend that implements it. A
538/// caller can add a listener to a `KeyServer` using
539/// [`KeyServer::add_listener`].
540pub trait StatusListener {
541 /// A status update.
542 fn update(&self, status: &StatusUpdate);
543}
544
545/// Returns certificates from a backing store.
546pub trait Store<'a> {
547 /// Returns the certificates whose fingerprint matches the handle.
548 ///
549 /// Returns [`StoreError::NotFound`] if no certificate is found.
550 ///
551 /// The caller may assume that looking up a fingerprint returns at
552 /// most one certificate.
553 fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>>;
554
555 /// Returns the certificate with the specified fingerprint, if any.
556 ///
557 /// Returns [`StoreError::NotFound`] if the certificate is not found.
558 ///
559 /// The default implementation is implemented in terms of
560 /// [`Store::lookup_by_cert`].
561 fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
562 -> Result<Arc<LazyCert<'a>>>
563 {
564 let kh = KeyHandle::from(fingerprint.clone());
565
566 self.lookup_by_cert(&kh)
567 .and_then(|v| {
568 assert!(v.len() <= 1,
569 "Looking up {} returned multiple certificates: {}",
570 fingerprint,
571 v.iter()
572 .map(|c| {
573 c.fingerprint().to_string()
574 })
575 .collect::<Vec<String>>()
576 .join(", "));
577 v.into_iter().next()
578 .ok_or(StoreError::NotFound(kh).into())
579 })
580 }
581
582 /// Returns certificates that have a key with the specified
583 /// handle, if any.
584 ///
585 /// Returns [`StoreError::NotFound`] if no certificate is not found.
586 ///
587 /// Note: even if you pass a fingerprint, this may return multiple
588 /// certificates as the same subkey may be attached to multiple
589 /// certificates.
590 fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>>;
591
592 /// Returns certificates that have a User ID matching the
593 /// specified pattern according to the query parameters.
594 ///
595 /// User IDs are interpreted as UTF-8 strings. If a user ID is
596 /// not valid UTF-8, then it will be converted to UTF-8 in a lossy
597 /// manner using the same semantics as
598 /// [`String::from_utf8_lossy`].
599 ///
600 /// This function returns all matching user IDs, even those that
601 /// are not self signed. It's up to the caller to authenticate
602 /// the returned user IDs.
603 fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
604 -> Result<Vec<Arc<LazyCert<'a>>>>;
605
606 /// Performs an exact match on the User ID.
607 ///
608 /// The pattern is anchored, and the match is case sensitive.
609 ///
610 /// The user ID is interpreted as a UTF-8 string. If the user ID
611 /// is not valid UTF-8, then it will be converted to UTF-8 in a
612 /// lossy manner using the same semantics as
613 /// [`String::from_utf8_lossy`].
614 ///
615 /// This function returns all matching user IDs, even those that
616 /// are not self signed. It's up to the caller to authenticate
617 /// the returned user IDs.
618 fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
619 self.select_userid(
620 &UserIDQueryParams::new()
621 .set_email(false)
622 .set_anchor_start(true)
623 .set_anchor_end(true)
624 .set_ignore_case(false),
625 &String::from_utf8_lossy(userid.value()))
626 }
627
628 /// Performs a case insenitive, substring match on the User ID.
629 ///
630 /// The pattern is not anchored, and it is matched case
631 /// insensitively.
632 ///
633 /// User IDs are interpreted as UTF-8 strings. If a user ID is
634 /// not valid UTF-8, then it will be converted to UTF-8 in a lossy
635 /// manner using the same semantics as
636 /// [`String::from_utf8_lossy`].
637 ///
638 /// This function returns all matching user IDs, even those that
639 /// are not self signed. It's up to the caller to authenticate
640 /// the returned user IDs.
641 fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
642 self.select_userid(
643 &UserIDQueryParams::new()
644 .set_email(false)
645 .set_anchor_start(false)
646 .set_anchor_end(false)
647 .set_ignore_case(true),
648 pattern)
649 }
650
651 /// Returns certificates that have a User ID with the specified
652 /// email address.
653 ///
654 /// The pattern is interpreted as an email address. It is first
655 /// normalized, and then matched against the normalized email
656 /// address, it is anchored, and the match is case sensitive.
657 ///
658 /// User IDs are interpreted as UTF-8 strings. If a user ID is
659 /// not valid UTF-8, then it will be converted to UTF-8 in a lossy
660 /// manner using the same semantics as
661 /// [`String::from_utf8_lossy`].
662 ///
663 /// This function returns all matching user IDs, even those that
664 /// are not self signed. It's up to the caller to authenticate
665 /// the returned user IDs.
666 fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
667 let userid = crate::email_to_userid(&email)?;
668 let email = userid.email_normalized()?.expect("have one");
669
670 self.select_userid(
671 &UserIDQueryParams::new()
672 .set_email(true)
673 .set_anchor_start(true)
674 .set_anchor_end(true)
675 .set_ignore_case(false),
676 &email)
677 }
678
679 /// Performs a case insenitive, substring match on the normalized
680 /// email address.
681 ///
682 /// The pattern is matched against the normalized email address,
683 /// it is not anchored, and it is matched case insensitively. The
684 /// pattern itself is *not* normalized.
685 ///
686 /// User IDs are interpreted as UTF-8 strings. If a user ID is
687 /// not valid UTF-8, then it will be converted to UTF-8 in a lossy
688 /// manner using the same semantics as
689 /// [`String::from_utf8_lossy`].
690 ///
691 /// This function returns all matching user IDs, even those that
692 /// are not self signed. It's up to the caller to authenticate
693 /// the returned user IDs.
694 fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
695 self.select_userid(
696 &UserIDQueryParams::new()
697 .set_email(true)
698 .set_anchor_start(false)
699 .set_anchor_end(false)
700 .set_ignore_case(true),
701 pattern)
702 }
703
704 /// Returns certificates that have User ID with an email address
705 /// from the specified domain.
706 ///
707 /// The pattern is interpreted as a domain address. It is first
708 /// normalized, and then matched against the normalized email
709 /// address, it is anchored, and the match is case sensitive.
710 ///
711 /// `domain` must be a bare domain, like `example.org`; it should
712 /// not start with an `@`. This does not match subdomains. That
713 /// is, it will match `alice@foo.bar.com` when searching for
714 /// `bar.com`.
715 ///
716 /// User IDs are interpreted as UTF-8 strings. If a user ID is
717 /// not valid UTF-8, then it will be converted to UTF-8 in a lossy
718 /// manner using the same semantics as
719 /// [`String::from_utf8_lossy`].
720 ///
721 /// This function returns all matching user IDs, even those that
722 /// are not self signed. It's up to the caller to authenticate
723 /// the returned user IDs.
724 fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
725 let localpart = "localpart";
726 let email = format!("{}@{}", localpart, domain);
727 let userid = crate::email_to_userid(&email)?;
728 let email = userid.email_normalized()?.expect("have one");
729 let domain = &email[email.rfind('@').expect("have an @")..];
730
731 self.select_userid(
732 &UserIDQueryParams::new()
733 .set_email(true)
734 .set_anchor_start(false)
735 .set_anchor_end(true)
736 .set_ignore_case(false),
737 domain)
738 }
739
740 /// Lists all of the certificates.
741 ///
742 /// If a backend is not able to enumerate all the certificates,
743 /// then it should return those that it knows about. For
744 /// instance, some keyservers allow certificates to be looked up
745 /// by fingerprint, but not to enumerate all of the certificates.
746 /// Thus, a user must not assume that if a certificate is not
747 /// returned by this function, it cannot be found by name.
748 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b>;
749
750 /// Returns all of the certificates.
751 ///
752 /// The default implementation is implemented in terms of
753 /// [`Store::fingerprints`] and [`Store::lookup_by_cert_fpr`]. Many backends
754 /// will be able to do this more efficiently.
755 fn certs<'b>(&'b self)
756 -> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
757 where 'a: 'b
758 {
759 Box::new(self.fingerprints()
760 .filter_map(|fpr| {
761 self.lookup_by_cert_fpr(&fpr).ok()
762 }))
763 }
764
765 /// Prefills the cache.
766 ///
767 /// Prefilling the cache makes sense when you plan to examine most
768 /// certificates. It doesn't make sense if you are just
769 /// authenticating a single or a few bindings.
770 ///
771 /// This function may be multi-threaded.
772 ///
773 /// Errors should be silently ignored and propagated when the
774 /// operation in question is executed directly.
775 fn prefetch_all(&self) {
776 }
777
778 /// Prefetches some certificates.
779 ///
780 /// Prefilling the cache makes sense when you plan to examine some
781 /// certificates in the near future.
782 ///
783 /// This interface is useful as it allows batching, which may be
784 /// more efficient, especially when the certificates are accessed
785 /// over the network. And, the function may be multi-threaded.
786 ///
787 /// Errors should be silently ignored and propagated when the
788 /// operation in question is executed directly.
789 fn prefetch_some(&self, certs: &[KeyHandle]) {
790 let _ = certs;
791 }
792}
793
794// The references in Store need a different lifetime from the contents
795// of the Box. Otherwise, a `Backend` that is a `&Box<Store>` would
796// create a self referential data structure.
797impl<'a: 't, 't, T> Store<'a> for Box<T>
798where T: Store<'a> + ?Sized + 't
799{
800 fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
801 self.as_ref().lookup_by_cert(kh)
802 }
803
804 fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
805 -> Result<Arc<LazyCert<'a>>>
806 {
807 self.as_ref().lookup_by_cert_fpr(fingerprint)
808 }
809
810 fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
811 self.as_ref().lookup_by_cert_or_subkey(kh)
812 }
813
814 fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
815 -> Result<Vec<Arc<LazyCert<'a>>>>
816 {
817 self.as_ref().select_userid(query, pattern)
818 }
819
820 fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
821 self.as_ref().lookup_by_userid(userid)
822 }
823
824 fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
825 self.as_ref().grep_userid(pattern)
826 }
827
828 fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
829 self.as_ref().lookup_by_email(email)
830 }
831
832 fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
833 self.as_ref().grep_email(pattern)
834 }
835
836 fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
837 self.as_ref().lookup_by_email_domain(domain)
838 }
839
840 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
841 self.as_ref().fingerprints()
842 }
843
844 fn certs<'b>(&'b self)
845 -> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
846 where 'a: 'b
847 {
848 self.as_ref().certs()
849 }
850
851 fn prefetch_all(&self) {
852 self.as_ref().prefetch_all()
853 }
854
855 fn prefetch_some(&self, certs: &[KeyHandle]) {
856 self.as_ref().prefetch_some(certs)
857 }
858}
859
860impl<'a: 't, 't, T> Store<'a> for &'t T
861where T: Store<'a> + ?Sized
862{
863 fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
864 (*self).lookup_by_cert(kh)
865 }
866
867 fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
868 -> Result<Arc<LazyCert<'a>>>
869 {
870 (*self).lookup_by_cert_fpr(fingerprint)
871 }
872
873 fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
874 (*self).lookup_by_cert_or_subkey(kh)
875 }
876
877 fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
878 -> Result<Vec<Arc<LazyCert<'a>>>>
879 {
880 (*self).select_userid(query, pattern)
881 }
882
883 fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
884 (*self).lookup_by_userid(userid)
885 }
886
887 fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
888 (*self).grep_userid(pattern)
889 }
890
891 fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
892 (*self).lookup_by_email(email)
893 }
894
895 fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
896 (*self).grep_email(pattern)
897 }
898
899 fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
900 (*self).lookup_by_email_domain(domain)
901 }
902
903 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
904 (*self).fingerprints()
905 }
906
907 fn certs<'b>(&'b self)
908 -> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
909 where 'a: 'b
910 {
911 (*self).certs()
912 }
913}
914
915impl<'a: 't, 't, T> Store<'a> for &'t mut T
916where T: Store<'a> + ?Sized
917{
918 fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
919 (**self).lookup_by_cert(kh)
920 }
921
922 fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
923 -> Result<Arc<LazyCert<'a>>>
924 {
925 (**self).lookup_by_cert_fpr(fingerprint)
926 }
927
928 fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
929 (**self).lookup_by_cert_or_subkey(kh)
930 }
931
932 fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
933 -> Result<Vec<Arc<LazyCert<'a>>>>
934 {
935 (**self).select_userid(query, pattern)
936 }
937
938 fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
939 (**self).lookup_by_userid(userid)
940 }
941
942 fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
943 (**self).grep_userid(pattern)
944 }
945
946 fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
947 (**self).lookup_by_email(email)
948 }
949
950 fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
951 (**self).grep_email(pattern)
952 }
953
954 fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
955 (**self).lookup_by_email_domain(domain)
956 }
957
958 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
959 (**self).fingerprints()
960 }
961
962 fn certs<'b>(&'b self)
963 -> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
964 where 'a: 'b
965 {
966 (**self).certs()
967 }
968
969 fn prefetch_all(&self) {
970 (**self).prefetch_all()
971 }
972
973 fn prefetch_some(&self, certs: &[KeyHandle]) {
974 (**self).prefetch_some(certs)
975 }
976}
977
978impl<'a, T> Store<'a> for Arc<T>
979where T: Store<'a> + ?Sized
980{
981 fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
982 self.as_ref().lookup_by_cert(kh)
983 }
984
985 fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
986 -> Result<Arc<LazyCert<'a>>>
987 {
988 self.as_ref().lookup_by_cert_fpr(fingerprint)
989 }
990
991 fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
992 self.as_ref().lookup_by_cert_or_subkey(kh)
993 }
994
995 fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
996 -> Result<Vec<Arc<LazyCert<'a>>>>
997 {
998 self.as_ref().select_userid(query, pattern)
999 }
1000
1001 fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
1002 self.as_ref().lookup_by_userid(userid)
1003 }
1004
1005 fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
1006 self.as_ref().grep_userid(pattern)
1007 }
1008
1009 fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
1010 self.as_ref().lookup_by_email(email)
1011 }
1012
1013 fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
1014 self.as_ref().grep_email(pattern)
1015 }
1016
1017 fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
1018 self.as_ref().lookup_by_email_domain(domain)
1019 }
1020
1021 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
1022 self.as_ref().fingerprints()
1023 }
1024
1025 fn certs<'b>(&'b self)
1026 -> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
1027 where 'a: 'b
1028 {
1029 self.as_ref().certs()
1030 }
1031}
1032
1033/// Merges two certificates.
1034///
1035/// This is primarily useful as the `merge_strategy` callback to
1036/// [`StoreUpdate::update_by`].
1037pub trait MergeCerts<'a> {
1038 /// Merges two certificates.
1039 ///
1040 /// This is primarily useful as the `merge_strategy` callback to
1041 /// [`StoreUpdate::update_by`].
1042 ///
1043 /// The default implementation merges the two certificates using
1044 /// [`Cert::merge_public`]. This means that any secret key
1045 /// material in `disk` is preserved, any secret key material in
1046 /// `new` is ignored, and unhashed subpacket areas are merged.
1047 ///
1048 /// Note: `self` is a normal reference, not a mutable reference.
1049 /// This means that the underlying implementation has to use
1050 /// interior mutability. The advantage is that multiple threads
1051 /// can have a reference to the store, and update it in parallel.
1052 fn merge_public<'b>(&self,
1053 new: Arc<LazyCert<'a>>,
1054 disk: Option<Arc<LazyCert<'b>>>)
1055 -> Result<Arc<LazyCert<'a>>>
1056 {
1057 if let Some(disk) = disk {
1058 let merged = disk.to_cert()?.clone()
1059 .merge_public(new.to_cert()?.clone())?;
1060 Ok(Arc::new(LazyCert::from(merged)))
1061 } else {
1062 if new.is_tsk() {
1063 Ok(Arc::new(LazyCert::from(new.to_cert()?.clone()
1064 .strip_secret_key_material())))
1065 } else {
1066 Ok(new)
1067 }
1068 }
1069 }
1070}
1071
1072impl<'a> MergeCerts<'a> for () {
1073}
1074
1075/// Provides an interface to update a backing store.
1076pub trait StoreUpdate<'a>: Store<'a> {
1077 /// Insert a certificate.
1078 ///
1079 /// This uses the default implementation of [`MergeCerts`] to
1080 /// merge the certificate with any existing certificate.
1081 ///
1082 /// Note: `self` is a normal reference, not a mutable reference.
1083 /// This means that the underlying implementation has to use
1084 /// interior mutability. The advantage is that multiple threads
1085 /// can have a reference to the store, and update it in parallel.
1086 fn update(&self, cert: Arc<LazyCert<'a>>) -> Result<()> {
1087 self.update_by(cert, &mut ())?;
1088
1089 Ok(())
1090 }
1091
1092 /// Inserts a certificate into the store.
1093 ///
1094 /// Inserts a certificate into the store and uses `merge_strategy`
1095 /// to merge it with the existing certificate, if any.
1096 ///
1097 /// Unless there is an error, you must call `merge_strategy`.
1098 /// This is the case even if the certificate is not on the
1099 /// backend. In that case, you must pass `None` for the on-disk
1100 /// version. This allows `merge_strategy` to generate statistics,
1101 /// and to modify the certificate before it is saved, e.g., by
1102 /// stripping third-party certifications.
1103 ///
1104 /// To use the default merge strategy, either call
1105 /// [`StoreUpdate::update`] directly, or pass `&mut ()`.
1106 ///
1107 /// Note: `self` and `merge_strategy` are normal references, not a
1108 /// mutable reference. This means that the underlying
1109 /// implementation has to use interior mutability. The advantage
1110 /// is that multiple threads can have a reference to the store,
1111 /// and update it in parallel.
1112 fn update_by(&self, cert: Arc<LazyCert<'a>>,
1113 merge_strategy: &dyn MergeCerts<'a>)
1114 -> Result<Arc<LazyCert<'a>>>;
1115}
1116
1117impl<'a: 't, 't, T> StoreUpdate<'a> for Box<T>
1118where T: StoreUpdate<'a> + ?Sized + 't
1119{
1120 fn update(&self, cert: Arc<LazyCert<'a>>) -> Result<()> {
1121 self.as_ref().update(cert)
1122 }
1123
1124 fn update_by(&self, cert: Arc<LazyCert<'a>>,
1125 merge_strategy: &dyn MergeCerts<'a>)
1126 -> Result<Arc<LazyCert<'a>>>
1127 {
1128 self.as_ref().update_by(cert, merge_strategy)
1129 }
1130}
1131
1132impl<'a: 't, 't, T> StoreUpdate<'a> for &'t T
1133where T: StoreUpdate<'a> + ?Sized
1134{
1135 fn update(&self, cert: Arc<LazyCert<'a>>) -> Result<()> {
1136 (*self).update(cert)
1137 }
1138
1139 fn update_by(&self, cert: Arc<LazyCert<'a>>,
1140 merge_strategy: &dyn MergeCerts<'a>)
1141 -> Result<Arc<LazyCert<'a>>>
1142 {
1143 (*self).update_by(cert, merge_strategy)
1144 }
1145}
1146
1147impl<'a, T> StoreUpdate<'a> for Arc<T>
1148where T: StoreUpdate<'a> + ?Sized
1149{
1150 fn update(&self, cert: Arc<LazyCert<'a>>) -> Result<()> {
1151 self.as_ref().update(cert)
1152 }
1153
1154 fn update_by(&self, cert: Arc<LazyCert<'a>>,
1155 merge_strategy: &dyn MergeCerts<'a>)
1156 -> Result<Arc<LazyCert<'a>>>
1157 {
1158 self.as_ref().update_by(cert, merge_strategy)
1159 }
1160}
1161
1162/// Merges two certificates and collects statistics.
1163///
1164/// This is primarily useful as the `merge_strategy` callback to
1165/// [`StoreUpdate::update_by`].
1166#[derive(Debug)]
1167#[non_exhaustive]
1168pub struct MergePublicCollectStats {
1169 /// Number of new certificates.
1170 new_certs: AtomicUsize,
1171
1172 /// Number of unchanged certificates.
1173 ///
1174 /// Note: there may be false negative. That is some certificates
1175 /// may be unchanged, but the heuristic thinks that they have been
1176 /// updated.
1177 unchanged_certs: AtomicUsize,
1178
1179 /// Number of update certificates.
1180 updated_certs: AtomicUsize,
1181
1182 /// Number of errors.
1183 errors: AtomicUsize,
1184}
1185assert_send_and_sync!(MergePublicCollectStats);
1186
1187impl MergePublicCollectStats {
1188 /// Returns a new `MergePublicCollectStats` with all stats set to 0.
1189 pub fn new() -> Self {
1190 Self {
1191 new_certs: AtomicUsize::new(0),
1192 unchanged_certs: AtomicUsize::new(0),
1193 updated_certs: AtomicUsize::new(0),
1194 errors: AtomicUsize::new(0),
1195 }
1196 }
1197
1198 /// Returns the number of new certificates.
1199 pub fn new_certs(&self) -> usize {
1200 self.new_certs.load(Ordering::Relaxed)
1201 }
1202
1203 /// Returns the number of unchanged certificates.
1204 pub fn unchanged_certs(&self) -> usize {
1205 self.unchanged_certs.load(Ordering::Relaxed)
1206 }
1207
1208 /// Returns the number of updated certificates.
1209 pub fn updated_certs(&self) -> usize {
1210 self.updated_certs.load(Ordering::Relaxed)
1211 }
1212
1213 /// Returns the number of errors.
1214 pub fn errors(&self) -> usize {
1215 self.errors.load(Ordering::Relaxed)
1216 }
1217
1218 /// Increments the number of new certificates by 1.
1219 pub fn inc_new_certs(&self) {
1220 self.new_certs.fetch_add(1, Ordering::Relaxed);
1221 }
1222
1223 /// Increments the number of unchanged certificates by 1.
1224 pub fn inc_unchanged_certs(&self) {
1225 self.unchanged_certs.fetch_add(1, Ordering::Relaxed);
1226 }
1227
1228 /// Increments the number of updated certificates by 1.
1229 pub fn inc_updated_certs(&self) {
1230 self.updated_certs.fetch_add(1, Ordering::Relaxed);
1231 }
1232
1233 /// Increments the number of errors by 1.
1234 pub fn inc_errors(&self) {
1235 self.errors.fetch_add(1, Ordering::Relaxed);
1236 }
1237}
1238
1239impl<'a> MergeCerts<'a> for MergePublicCollectStats {
1240 /// Merges two certificates.
1241 ///
1242 /// This is primarily useful as the `merge_strategy` callback to
1243 /// [`StoreUpdate::update_by`].
1244 ///
1245 /// This implementation has the same merge semantics as the
1246 /// default implementation, but it also updates the statistics in
1247 /// `self`.
1248 ///
1249 /// # Examples
1250 ///
1251 /// ```rust
1252 /// use std::any::Any;
1253 /// use std::sync::Arc;
1254 ///
1255 /// use sequoia_openpgp as openpgp;
1256 /// # use openpgp::Result;
1257 /// use openpgp::cert::prelude::*;
1258 /// use openpgp::parse::Parse;
1259 ///
1260 /// use sequoia_cert_store as cert_store;
1261 /// use cert_store::CertStore;
1262 /// use cert_store::LazyCert;
1263 /// use cert_store::store::MergePublicCollectStats;
1264 /// use cert_store::store::StoreUpdate;
1265 ///
1266 /// # fn main() -> Result<()> {
1267 /// let (cert, _rev) = CertBuilder::new().generate()?;
1268 ///
1269 /// let mut certs = CertStore::empty();
1270 ///
1271 /// let mut stats = MergePublicCollectStats::new();
1272 ///
1273 /// certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats)
1274 /// .expect("valid");
1275 ///
1276 /// assert_eq!(stats.new_certs(), 1);
1277 /// # Ok(()) }
1278 /// ```
1279 fn merge_public<'b, 'rb>(&self,
1280 new: Arc<LazyCert<'a>>,
1281 disk: Option<Arc<LazyCert<'b>>>)
1282 -> Result<Arc<LazyCert<'a>>>
1283 {
1284 let disk = if let Some(disk) = disk {
1285 disk
1286 } else {
1287 self.inc_new_certs();
1288
1289 if new.is_tsk() {
1290 return Ok(Arc::new(LazyCert::from(
1291 new.to_cert()?.clone().strip_secret_key_material())))
1292 } else {
1293 return Ok(new);
1294 }
1295 };
1296
1297 let fpr = new.fingerprint();
1298
1299 let disk = disk.to_cert()
1300 .with_context(|| {
1301 format!("Parsing {} as returned from the cert directory", fpr)
1302 })?
1303 .clone();
1304
1305 let new = new.to_cert()
1306 .with_context(|| {
1307 format!("Parsing {} as being inserted into \
1308 the cert directory",
1309 fpr)
1310 })?
1311 .clone();
1312
1313 if disk == new {
1314 self.inc_unchanged_certs();
1315 Ok(Arc::new(LazyCert::from(new)))
1316 } else {
1317 // If the on-disk version has secrets, we preserve them.
1318 // If new has secrets, we ignore them.
1319 match disk.clone().merge_public(new) {
1320 Ok(merged) => {
1321 if merged == disk {
1322 self.inc_unchanged_certs();
1323 } else {
1324 self.inc_updated_certs();
1325 }
1326 Ok(Arc::new(LazyCert::from(merged)))
1327 }
1328 Err(err) => {
1329 self.inc_errors();
1330 Err(err.into())
1331 }
1332 }
1333 }
1334 }
1335}
1336
1337#[cfg(test)]
1338mod tests {
1339 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
1340 use wasm_bindgen_test::wasm_bindgen_test as test;
1341
1342 use super::*;
1343
1344 use crate::store;
1345
1346 // Make sure we can pass a &Box<Store> where a generic type
1347 // needs to implement Store.
1348 #[test]
1349 fn store_boxed() -> Result<()> {
1350 struct Foo<'a, B>
1351 where B: Store<'a>
1352 {
1353 backend: B,
1354 _a: std::marker::PhantomData<&'a ()>,
1355 }
1356
1357 impl<'a, B> Foo<'a, B>
1358 where B: Store<'a>
1359 {
1360 fn new(backend: B) -> Self
1361 {
1362 Foo {
1363 backend,
1364 _a: std::marker::PhantomData,
1365 }
1366 }
1367
1368 fn count(&self) -> usize {
1369 self.backend.certs().count()
1370 }
1371 }
1372
1373 let backend = store::Certs::empty();
1374 let backend: Box<dyn Store> = Box::new(backend);
1375 let foo = Foo::new(&backend);
1376
1377 // Do something (anything) with the backend.
1378 assert_eq!(foo.count(), 0);
1379
1380 Ok(())
1381 }
1382
1383 // Make sure we can pass a &Box<StoreUpdate> where a generic type
1384 // needs to implement Store.
1385 #[test]
1386 fn store_update_boxed() -> Result<()> {
1387 struct Foo<'a, B>
1388 where B: StoreUpdate<'a>
1389 {
1390 backend: B,
1391 _a: std::marker::PhantomData<&'a ()>,
1392 }
1393
1394 impl<'a, B> Foo<'a, B>
1395 where B: StoreUpdate<'a>
1396 {
1397 fn new(backend: B) -> Self
1398 {
1399 Foo {
1400 backend,
1401 _a: std::marker::PhantomData,
1402 }
1403 }
1404
1405 fn count(&self) -> usize {
1406 self.backend.certs().count()
1407 }
1408 }
1409
1410 let backend = store::Certs::empty();
1411 let backend: Box<dyn StoreUpdate> = Box::new(backend);
1412 let foo = Foo::new(&backend);
1413
1414 // Do something (anything) with the backend.
1415 assert_eq!(foo.count(), 0);
1416
1417 Ok(())
1418 }
1419
1420 #[test]
1421 fn is_email() {
1422 assert!(UserIDQueryParams::is_email("foo@domain.com").is_ok());
1423
1424 // Need a local part.
1425 assert!(UserIDQueryParams::is_email("@domain.com").is_err());
1426 // Need a domain.
1427 assert!(UserIDQueryParams::is_email("foo@").is_err());
1428
1429 // One @
1430 assert!(UserIDQueryParams::is_email("foo").is_err());
1431 assert!(UserIDQueryParams::is_email("foo@@domain.com").is_err());
1432 assert!(UserIDQueryParams::is_email("foo@a@domain.com").is_err());
1433
1434 // Bare email address, not wrapped in angle brackets.
1435 assert!(UserIDQueryParams::is_email("<foo@domain.com>").is_err());
1436
1437 // Whitespace is not allowed.
1438 assert!(UserIDQueryParams::is_email(" foo@domain.com").is_err());
1439 assert!(UserIDQueryParams::is_email("foo o@domain.com").is_err());
1440 assert!(UserIDQueryParams::is_email("foo@do main.com").is_err());
1441 assert!(UserIDQueryParams::is_email("foo@domain.com ").is_err());
1442 }
1443
1444 #[test]
1445 fn is_domain() {
1446 assert!(UserIDQueryParams::is_domain("domain.com").is_ok());
1447
1448 // No at.
1449 assert!(UserIDQueryParams::is_domain("foo").is_ok());
1450 assert!(UserIDQueryParams::is_domain("@domain.com").is_err());
1451 assert!(UserIDQueryParams::is_domain("foo@").is_err());
1452 assert!(UserIDQueryParams::is_domain("foo@@domain.com").is_err());
1453 assert!(UserIDQueryParams::is_domain("foo@a@domain.com").is_err());
1454 assert!(UserIDQueryParams::is_domain("<foo@domain.com>").is_err());
1455 }
1456
1457 include!("../tests/keyring.rs");
1458
1459 // Check that MergePublicCollectStats works as advertised.
1460 #[test]
1461 fn store_update_merge_public_collect_stats() {
1462 use std::collections::HashSet;
1463
1464 use openpgp::Cert;
1465 use openpgp::parse::Parse;
1466
1467 use crate::CertStore;
1468 use crate::store::MergePublicCollectStats;
1469
1470 let certs = CertStore::empty();
1471
1472 let mut stats = MergePublicCollectStats::new();
1473
1474 let mut seen = HashSet::new();
1475
1476 for (i, cert) in keyring::certs.iter().enumerate() {
1477 let cert = Cert::from_bytes(&cert.bytes()).expect("valid");
1478 let fpr = cert.fingerprint();
1479 seen.insert(fpr.clone());
1480
1481 certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats)
1482 .expect("valid");
1483
1484 eprintln!("After inserting {} ({}), stats: {:?}",
1485 i, fpr, stats);
1486
1487 assert_eq!(stats.new_certs(), seen.len());
1488 assert_eq!(stats.new_certs()
1489 + stats.updated_certs()
1490 + stats.unchanged_certs(),
1491 i + 1);
1492 }
1493
1494 let new = stats.new_certs();
1495 let updated = stats.updated_certs();
1496 let unchanged = stats.unchanged_certs();
1497
1498 // Insert again. This time nothing should change.
1499 for (i, cert) in keyring::certs.iter().enumerate() {
1500 let cert = Cert::from_bytes(&cert.bytes()).expect("valid");
1501 let fpr = cert.fingerprint();
1502
1503 certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats)
1504 .expect("valid");
1505
1506 eprintln!("After reinserting {} ({}), stats: {:?}",
1507 i, fpr, stats);
1508
1509 // These should not change:
1510 assert_eq!(stats.new_certs(), new);
1511 // Update should also not change, but there may be false
1512 // positives.
1513 assert_eq!(stats.unchanged_certs() + stats.updated_certs(),
1514 updated + unchanged + i + 1);
1515 }
1516 }
1517}