1use std::io::BufRead;
6use std::str::FromStr;
7
8use quick_xml::XmlVersion;
9use quick_xml::events::{BytesStart, Event};
10
11use super::{Error, ServerParams};
12use crate::context::Context;
13use crate::log::warn;
14use crate::net::read_url_with_tls;
15use crate::provider::{Protocol, Socket};
16
17#[derive(Debug)]
18struct Server {
19 pub typ: String,
20 pub hostname: String,
21 pub port: u16,
22 pub sockettype: Socket,
23 pub username: String,
24}
25
26#[derive(Debug)]
27struct MozAutoconfigure {
28 pub incoming_servers: Vec<Server>,
29 pub outgoing_servers: Vec<Server>,
30}
31
32#[derive(Debug, Default)]
33enum MozConfigTag {
34 #[default]
35 Undefined,
36 Hostname,
37 Port,
38 Sockettype,
39 Username,
40}
41
42impl FromStr for MozConfigTag {
43 type Err = ();
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 match s.trim().to_lowercase().as_ref() {
47 "hostname" => Ok(MozConfigTag::Hostname),
48 "port" => Ok(MozConfigTag::Port),
49 "sockettype" => Ok(MozConfigTag::Sockettype),
50 "username" => Ok(MozConfigTag::Username),
51 _ => Err(()),
52 }
53 }
54}
55
56fn parse_server<B: BufRead>(
58 reader: &mut quick_xml::Reader<B>,
59 server_event: &BytesStart,
60) -> Result<Option<Server>, quick_xml::Error> {
61 let end_tag = String::from_utf8_lossy(server_event.name().as_ref())
62 .trim()
63 .to_lowercase();
64
65 let typ = server_event
66 .attributes()
67 .find_map(|attr| {
68 attr.ok().filter(|a| {
69 String::from_utf8_lossy(a.key.as_ref())
70 .trim()
71 .eq_ignore_ascii_case("type")
72 })
73 })
74 .map(|typ| {
75 typ.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
76 .unwrap_or_default()
77 .to_lowercase()
78 })
79 .unwrap_or_default();
80
81 let mut hostname = None;
82 let mut port = None;
83 let mut sockettype = Socket::Automatic;
84 let mut username = None;
85
86 let mut tag_config = MozConfigTag::Undefined;
87 let mut buf = Vec::new();
88 loop {
89 match reader.read_event_into(&mut buf)? {
90 Event::Start(ref event) => {
91 tag_config = String::from_utf8_lossy(event.name().as_ref())
92 .parse()
93 .unwrap_or_default();
94 }
95 Event::End(ref event) => {
96 let tag = String::from_utf8_lossy(event.name().as_ref())
97 .trim()
98 .to_lowercase();
99
100 if tag == end_tag {
101 break;
102 }
103 }
104 Event::Text(ref event) => {
105 let val = event
106 .xml_content(XmlVersion::Implicit1_0)
107 .unwrap_or_default()
108 .trim()
109 .to_owned();
110
111 match tag_config {
112 MozConfigTag::Hostname => hostname = Some(val),
113 MozConfigTag::Port => port = Some(val.parse().unwrap_or_default()),
114 MozConfigTag::Username => username = Some(val),
115 MozConfigTag::Sockettype => {
116 sockettype = match val.to_lowercase().as_ref() {
117 "ssl" => Socket::Ssl,
118 "starttls" => Socket::Starttls,
119 "plain" => Socket::Plain,
120 _ => Socket::Automatic,
121 }
122 }
123 _ => {}
124 }
125 }
126 Event::Eof => break,
127 _ => (),
128 }
129 }
130
131 if let (Some(hostname), Some(port), Some(username)) = (hostname, port, username) {
132 Ok(Some(Server {
133 typ,
134 hostname,
135 port,
136 sockettype,
137 username,
138 }))
139 } else {
140 Ok(None)
141 }
142}
143
144fn parse_xml_reader<B: BufRead>(
145 reader: &mut quick_xml::Reader<B>,
146) -> Result<MozAutoconfigure, quick_xml::Error> {
147 let mut incoming_servers = Vec::new();
148 let mut outgoing_servers = Vec::new();
149
150 let mut buf = Vec::new();
151 loop {
152 match reader.read_event_into(&mut buf)? {
153 Event::Start(ref event) => {
154 let tag = String::from_utf8_lossy(event.name().as_ref())
155 .trim()
156 .to_lowercase();
157
158 if tag == "incomingserver" {
159 if let Some(incoming_server) = parse_server(reader, event)? {
160 incoming_servers.push(incoming_server);
161 }
162 } else if tag == "outgoingserver"
163 && let Some(outgoing_server) = parse_server(reader, event)?
164 {
165 outgoing_servers.push(outgoing_server);
166 }
167 }
168 Event::Eof => break,
169 _ => (),
170 }
171 buf.clear();
172 }
173
174 Ok(MozAutoconfigure {
175 incoming_servers,
176 outgoing_servers,
177 })
178}
179
180fn parse_xml_with_address(in_emailaddr: &str, xml_raw: &str) -> Result<MozAutoconfigure, Error> {
182 let parts: Vec<&str> = in_emailaddr.rsplitn(2, '@').collect();
184 let (in_emaillocalpart, in_emaildomain) = match &parts[..] {
185 [domain, local] => (local, domain),
186 _ => return Err(Error::InvalidEmailAddress(in_emailaddr.to_string())),
187 };
188
189 let mut reader = quick_xml::Reader::from_str(xml_raw);
190 reader.config_mut().trim_text(true);
191
192 let moz_ac = parse_xml_reader(&mut reader).map_err(|error| Error::InvalidXml {
193 position: reader.buffer_position(),
194 error,
195 })?;
196
197 let fill_placeholders = |val: &str| -> String {
198 val.replace("%EMAILADDRESS%", in_emailaddr)
199 .replace("%EMAILLOCALPART%", in_emaillocalpart)
200 .replace("%EMAILDOMAIN%", in_emaildomain)
201 };
202
203 let fill_server_placeholders = |server: Server| -> Server {
204 Server {
205 typ: server.typ,
206 hostname: fill_placeholders(&server.hostname),
207 port: server.port,
208 sockettype: server.sockettype,
209 username: fill_placeholders(&server.username),
210 }
211 };
212
213 Ok(MozAutoconfigure {
214 incoming_servers: moz_ac
215 .incoming_servers
216 .into_iter()
217 .map(fill_server_placeholders)
218 .collect(),
219 outgoing_servers: moz_ac
220 .outgoing_servers
221 .into_iter()
222 .map(fill_server_placeholders)
223 .collect(),
224 })
225}
226
227fn parse_serverparams(in_emailaddr: &str, xml_raw: &str) -> Result<Vec<ServerParams>, Error> {
229 let moz_ac = parse_xml_with_address(in_emailaddr, xml_raw)?;
230
231 let res = moz_ac
232 .incoming_servers
233 .into_iter()
234 .chain(moz_ac.outgoing_servers)
235 .filter_map(|server| {
236 let protocol = match server.typ.as_ref() {
237 "imap" => Some(Protocol::Imap),
238 "smtp" => Some(Protocol::Smtp),
239 _ => None,
240 };
241 Some(ServerParams {
242 protocol: protocol?,
243 socket: server.sockettype,
244 hostname: server.hostname,
245 port: server.port,
246 username: server.username,
247 })
248 })
249 .collect();
250 Ok(res)
251}
252
253pub(crate) async fn moz_autoconfigure(
254 context: &Context,
255 url: &str,
256 addr: &str,
257 accept_invalid_certificates: bool,
258) -> Result<Vec<ServerParams>, Error> {
259 let xml_raw = read_url_with_tls(context, url, !accept_invalid_certificates).await?;
260
261 let res = parse_serverparams(addr, &xml_raw);
262 if let Err(err) = &res {
263 warn!(
264 context,
265 "Failed to parse Thunderbird autoconfiguration XML: {}", err
266 );
267 }
268 res
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_parse_outlook_autoconfig() {
277 let xml_raw = include_str!("../../test-data/autoconfig/outlook.com.xml");
278 let res = parse_serverparams("example@outlook.com", xml_raw).expect("XML parsing failed");
279 assert_eq!(res[0].protocol, Protocol::Imap);
280 assert_eq!(res[0].hostname, "outlook.office365.com");
281 assert_eq!(res[0].port, 993);
282 assert_eq!(res[1].protocol, Protocol::Smtp);
283 assert_eq!(res[1].hostname, "smtp.office365.com");
284 assert_eq!(res[1].port, 587);
285 }
286
287 #[test]
288 fn test_parse_lakenet_autoconfig() {
289 let xml_raw = include_str!("../../test-data/autoconfig/lakenet.ch.xml");
290 let res =
291 parse_xml_with_address("example@lakenet.ch", xml_raw).expect("XML parsing failed");
292
293 assert_eq!(res.incoming_servers.len(), 4);
294
295 assert_eq!(res.incoming_servers[0].typ, "imap");
296 assert_eq!(res.incoming_servers[0].hostname, "mail.lakenet.ch");
297 assert_eq!(res.incoming_servers[0].port, 993);
298 assert_eq!(res.incoming_servers[0].sockettype, Socket::Ssl);
299 assert_eq!(res.incoming_servers[0].username, "example@lakenet.ch");
300
301 assert_eq!(res.incoming_servers[1].typ, "imap");
302 assert_eq!(res.incoming_servers[1].hostname, "mail.lakenet.ch");
303 assert_eq!(res.incoming_servers[1].port, 143);
304 assert_eq!(res.incoming_servers[1].sockettype, Socket::Starttls);
305 assert_eq!(res.incoming_servers[1].username, "example@lakenet.ch");
306
307 assert_eq!(res.incoming_servers[2].typ, "pop3");
308 assert_eq!(res.incoming_servers[2].hostname, "mail.lakenet.ch");
309 assert_eq!(res.incoming_servers[2].port, 995);
310 assert_eq!(res.incoming_servers[2].sockettype, Socket::Ssl);
311 assert_eq!(res.incoming_servers[2].username, "example@lakenet.ch");
312
313 assert_eq!(res.incoming_servers[3].typ, "pop3");
314 assert_eq!(res.incoming_servers[3].hostname, "mail.lakenet.ch");
315 assert_eq!(res.incoming_servers[3].port, 110);
316 assert_eq!(res.incoming_servers[3].sockettype, Socket::Starttls);
317 assert_eq!(res.incoming_servers[3].username, "example@lakenet.ch");
318
319 assert_eq!(res.outgoing_servers.len(), 1);
320
321 assert_eq!(res.outgoing_servers[0].typ, "smtp");
322 assert_eq!(res.outgoing_servers[0].hostname, "mail.lakenet.ch");
323 assert_eq!(res.outgoing_servers[0].port, 587);
324 assert_eq!(res.outgoing_servers[0].sockettype, Socket::Starttls);
325 assert_eq!(res.outgoing_servers[0].username, "example@lakenet.ch");
326 }
327}