///|
/// The capabilities advertised by the server in the EHLO reply.
pub struct SmtpCaps {
auth : Array[String]
mut size : Int?
mut has_8bitmime : Bool
mut has_smtputf8 : Bool
mut has_pipelining : Bool
mut has_starttls : Bool
raw : Array[String]
} derive(Eq, Debug)
///|
pub fn SmtpCaps::empty() -> SmtpCaps {
{
auth: [],
size: None,
has_8bitmime: false,
has_smtputf8: false,
has_pipelining: false,
has_starttls: false,
raw: [],
}
}
///|
pub fn SmtpCaps::auth(self : SmtpCaps) -> Array[String] {
self.auth
}
///|
pub fn SmtpCaps::size(self : SmtpCaps) -> Int? {
self.size
}
///|
pub fn SmtpCaps::has_8bitmime(self : SmtpCaps) -> Bool {
self.has_8bitmime
}
///|
pub fn SmtpCaps::has_smtputf8(self : SmtpCaps) -> Bool {
self.has_smtputf8
}
///|
pub fn SmtpCaps::has_pipelining(self : SmtpCaps) -> Bool {
self.has_pipelining
}
///|
pub fn SmtpCaps::has_starttls(self : SmtpCaps) -> Bool {
self.has_starttls
}
///|
/// True when the server advertises `mech` in its AUTH capability
/// (case-insensitive).
pub fn SmtpCaps::supports_auth(self : SmtpCaps, mech : String) -> Bool {
for m in self.auth {
if m.to_upper() == mech.to_upper() {
return true
}
}
false
}
///|
/// Parse the EHLO capability lines (the reply lines after the greeting line,
/// without their numeric prefixes). `MM_SMTP_004`.
pub fn parse_caps(lines : Array[String]) -> SmtpCaps {
let caps = SmtpCaps::empty()
for raw in lines {
let line = raw.trim().to_owned()
let upper = line.to_upper()
caps.raw.push(line)
if upper == "8BITMIME" {
caps.has_8bitmime = true
} else if upper == "SMTPUTF8" {
caps.has_smtputf8 = true
} else if upper == "PIPELINING" {
caps.has_pipelining = true
} else if upper == "STARTTLS" {
caps.has_starttls = true
} else if upper == "SIZE" {
caps.size = None
} else if upper.has_prefix("SIZE ") {
let rest = line[5:line.length()].to_owned()
match parse_int(rest) {
Some(n) => caps.size = Some(n)
None => caps.size = None
}
} else if upper.has_prefix("AUTH") {
// AUTH LOGIN PLAIN CRAM-MD5 or AUTH=LOGIN PLAIN
let rest = if upper.has_prefix("AUTH=") {
line[5:line.length()].to_owned()
} else {
line[4:line.length()].trim().to_owned()
}
for mech in rest.split(" ") {
let m = mech.trim().to_owned()
if !m.is_empty() {
caps.auth.push(m.to_upper())
}
}
}
}
caps
}
///|
fn parse_int(s : String) -> Int? {
if s.is_empty() {
return None
}
let mut n = 0
for b in string_to_bytes(s) {
let c = b.to_int()
if c < 0x30 || c > 0x39 {
return None
}
n = n * 10 + (c - 0x30)
}
Some(n)
}