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