use alloc::{
format,
string::{FromUtf8Error, String},
};
use thiserror::Error;
use url::{ParseError, Url};
use crate::{
autoconfig::types::Autoconfig,
coroutine::{DiscoveryCoroutine, DiscoveryCoroutineState, DiscoveryYield},
shared::http::{HttpGet, HttpGetError},
};
#[derive(Debug, Error)]
pub enum DiscoveryIspError {
#[error("ISP call returned invalid UTF-8 body")]
Utf8(#[source] FromUtf8Error),
#[error("ISP call returned invalid XML body")]
Xml(#[source] serde_xml_rs::Error),
#[error(transparent)]
Http(#[from] HttpGetError),
}
pub struct DiscoveryIsp {
get: HttpGet,
}
impl DiscoveryIsp {
pub fn main_url(
local_part: impl AsRef<str>,
domain: impl AsRef<str>,
secure: bool,
) -> Result<Url, ParseError> {
let domain = domain.as_ref().trim_matches('.');
let email = format!("{}@{domain}", local_part.as_ref());
let s = if secure { "s" } else { "" };
let url =
format!("http{s}://autoconfig.{domain}/mail/config-v1.1.xml?emailaddress={email}");
Url::parse(&url)
}
pub fn fallback_url(domain: impl AsRef<str>, secure: bool) -> Result<Url, ParseError> {
let domain = domain.as_ref().trim_matches('.');
let s = if secure { "s" } else { "" };
let url = format!("http{s}://{domain}/.well-known/autoconfig/mail/config-v1.1.xml");
Url::parse(&url)
}
pub fn db_url(domain: impl AsRef<str>, secure: bool) -> Result<Url, ParseError> {
let domain = domain.as_ref().trim_matches('.');
let s = if secure { "s" } else { "" };
let url = format!("http{s}://autoconfig.thunderbird.net/v1.1/{domain}");
Url::parse(&url)
}
pub fn all_urls(
local_part: impl AsRef<str>,
domain: impl AsRef<str>,
) -> Result<[Url; 5], ParseError> {
let local_part = local_part.as_ref();
let domain = domain.as_ref();
Ok([
Self::main_url(local_part, domain, true)?,
Self::main_url(local_part, domain, false)?,
Self::fallback_url(domain, true)?,
Self::fallback_url(domain, false)?,
Self::db_url(domain, true)?,
])
}
pub fn new(url: Url) -> Self {
Self {
get: HttpGet::new(url),
}
}
}
impl DiscoveryCoroutine for DiscoveryIsp {
type Yield = DiscoveryYield;
type Return = Result<Autoconfig, DiscoveryIspError>;
fn resume(&mut self, arg: Option<&[u8]>) -> DiscoveryCoroutineState<Self::Yield, Self::Return> {
match self.get.resume(arg) {
DiscoveryCoroutineState::Yielded(y) => DiscoveryCoroutineState::Yielded(y),
DiscoveryCoroutineState::Complete(Err(err)) => {
DiscoveryCoroutineState::Complete(Err(err.into()))
}
DiscoveryCoroutineState::Complete(Ok(bytes)) => {
let body = match String::from_utf8(bytes) {
Ok(body) => body,
Err(err) => {
return DiscoveryCoroutineState::Complete(Err(DiscoveryIspError::Utf8(
err,
)));
}
};
match serde_xml_rs::from_str(&body) {
Ok(autoconfig) => DiscoveryCoroutineState::Complete(Ok(autoconfig)),
Err(err) => DiscoveryCoroutineState::Complete(Err(DiscoveryIspError::Xml(err))),
}
}
}
}
}