io_smtp/rfc4954/capability.rs
1//! AUTH EHLO capability (RFC 4954 ยง4).
2
3use alloc::vec::Vec;
4
5use thiserror::Error;
6
7/// EHLO capability keyword for SMTP authentication.
8pub const AUTH: &str = "AUTH";
9
10/// The AUTH EHLO capability: the set of SASL mechanisms offered by the server.
11///
12/// Borrows directly from the raw capability line returned by
13/// `SmtpEhloResponse::get_capability(AUTH)`.
14///
15/// # Example
16///
17/// ```ignore
18/// use io_smtp::rfc4954::capability::{AUTH, SmtpAuthCapability};
19/// use io_smtp::rfc4616::plain::PLAIN;
20/// use io_smtp::login::LOGIN;
21///
22/// let cap = SmtpAuthCapability::parse(ehlo.get_capability(AUTH).unwrap()).unwrap();
23/// assert!(cap.has(PLAIN));
24/// assert!(cap.has(LOGIN));
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct SmtpAuthCapability<'a>(Vec<&'a str>);
28
29impl<'a> SmtpAuthCapability<'a> {
30 /// Parses the raw AUTH capability line (e.g. `"AUTH PLAIN LOGIN"`).
31 pub fn parse(s: &'a str) -> Result<Self, SmtpAuthCapabilityError> {
32 let mut parts = s.split_ascii_whitespace();
33
34 match parts.next() {
35 Some(kw) if kw.eq_ignore_ascii_case(AUTH) => {}
36 _ => return Err(SmtpAuthCapabilityError),
37 }
38
39 Ok(SmtpAuthCapability(parts.collect()))
40 }
41
42 /// Returns `true` if the given SASL mechanism is advertised.
43 ///
44 /// The comparison is case-insensitive.
45 pub fn has(&self, mechanism: &str) -> bool {
46 self.0.iter().any(|m| m.eq_ignore_ascii_case(mechanism))
47 }
48
49 /// Returns an iterator over the advertised mechanism names.
50 pub fn mechanisms(&self) -> impl Iterator<Item = &str> {
51 self.0.iter().copied()
52 }
53}
54
55/// Error returned when parsing an AUTH capability string fails.
56#[derive(Debug, Error)]
57#[error("invalid AUTH capability string")]
58pub struct SmtpAuthCapabilityError;