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::log::warn;
13use crate::net::read_url;
14use crate::provider::{Protocol, Socket};
15
16#[derive(Debug)]
17struct Server {
18 pub typ: String,
19 pub hostname: String,
20 pub port: u16,
21 pub sockettype: Socket,
22 pub username: String,
23}
24
25#[derive(Debug)]
26struct MozAutoconfigure {
27 pub incoming_servers: Vec<Server>,
28 pub outgoing_servers: Vec<Server>,
29}
30
31#[derive(Debug)]
32enum MozConfigTag {
33 Undefined,
34 Hostname,
35 Port,
36 Sockettype,
37 Username,
38}
39
40impl Default for MozConfigTag {
41 fn default() -> Self {
42 Self::Undefined
43 }
44}
45
46impl FromStr for MozConfigTag {
47 type Err = ();
48
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 match s.trim().to_lowercase().as_ref() {
51 "hostname" => Ok(MozConfigTag::Hostname),
52 "port" => Ok(MozConfigTag::Port),
53 "sockettype" => Ok(MozConfigTag::Sockettype),
54 "username" => Ok(MozConfigTag::Username),
55 _ => Err(()),
56 }
57 }
58}
59
60fn parse_server<B: BufRead>(
62 reader: &mut quick_xml::Reader<B>,
63 server_event: &BytesStart,
64) -> Result<Option<Server>, quick_xml::Error> {
65 let end_tag = String::from_utf8_lossy(server_event.name().as_ref())
66 .trim()
67 .to_lowercase();
68
69 let typ = server_event
70 .attributes()
71 .find_map(|attr| {
72 attr.ok().filter(|a| {
73 String::from_utf8_lossy(a.key.as_ref())
74 .trim()
75 .eq_ignore_ascii_case("type")
76 })
77 })
78 .map(|typ| {
79 typ.decode_and_unescape_value(reader.decoder())
80 .unwrap_or_default()
81 .to_lowercase()
82 })
83 .unwrap_or_default();
84
85 let mut hostname = None;
86 let mut port = None;
87 let mut sockettype = Socket::Automatic;
88 let mut username = None;
89
90 let mut tag_config = MozConfigTag::Undefined;
91 let mut buf = Vec::new();
92 loop {
93 match reader.read_event_into(&mut buf)? {
94 Event::Start(ref event) => {
95 tag_config = String::from_utf8_lossy(event.name().as_ref())
96 .parse()
97 .unwrap_or_default();
98 }
99 Event::End(ref event) => {
100 let tag = String::from_utf8_lossy(event.name().as_ref())
101 .trim()
102 .to_lowercase();
103
104 if tag == end_tag {
105 break;
106 }
107 }
108 Event::Text(ref event) => {
109 let val = event.unescape().unwrap_or_default().trim().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 if let Some(outgoing_server) = parse_server(reader, event)? {
164 outgoing_servers.push(outgoing_server);
165 }
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) -> Result<Vec<ServerParams>, Error> {
258 let xml_raw = read_url(context, url).await?;
259
260 let res = parse_serverparams(addr, &xml_raw);
261 if let Err(err) = &res {
262 warn!(
263 context,
264 "Failed to parse Thunderbird autoconfiguration XML: {}", err
265 );
266 }
267 res
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_parse_outlook_autoconfig() {
276 let xml_raw = include_str!("../../test-data/autoconfig/outlook.com.xml");
277 let res = parse_serverparams("example@outlook.com", xml_raw).expect("XML parsing failed");
278 assert_eq!(res[0].protocol, Protocol::Imap);
279 assert_eq!(res[0].hostname, "outlook.office365.com");
280 assert_eq!(res[0].port, 993);
281 assert_eq!(res[1].protocol, Protocol::Smtp);
282 assert_eq!(res[1].hostname, "smtp.office365.com");
283 assert_eq!(res[1].port, 587);
284 }
285
286 #[test]
287 fn test_parse_lakenet_autoconfig() {
288 let xml_raw = include_str!("../../test-data/autoconfig/lakenet.ch.xml");
289 let res =
290 parse_xml_with_address("example@lakenet.ch", xml_raw).expect("XML parsing failed");
291
292 assert_eq!(res.incoming_servers.len(), 4);
293
294 assert_eq!(res.incoming_servers[0].typ, "imap");
295 assert_eq!(res.incoming_servers[0].hostname, "mail.lakenet.ch");
296 assert_eq!(res.incoming_servers[0].port, 993);
297 assert_eq!(res.incoming_servers[0].sockettype, Socket::Ssl);
298 assert_eq!(res.incoming_servers[0].username, "example@lakenet.ch");
299
300 assert_eq!(res.incoming_servers[1].typ, "imap");
301 assert_eq!(res.incoming_servers[1].hostname, "mail.lakenet.ch");
302 assert_eq!(res.incoming_servers[1].port, 143);
303 assert_eq!(res.incoming_servers[1].sockettype, Socket::Starttls);
304 assert_eq!(res.incoming_servers[1].username, "example@lakenet.ch");
305
306 assert_eq!(res.incoming_servers[2].typ, "pop3");
307 assert_eq!(res.incoming_servers[2].hostname, "mail.lakenet.ch");
308 assert_eq!(res.incoming_servers[2].port, 995);
309 assert_eq!(res.incoming_servers[2].sockettype, Socket::Ssl);
310 assert_eq!(res.incoming_servers[2].username, "example@lakenet.ch");
311
312 assert_eq!(res.incoming_servers[3].typ, "pop3");
313 assert_eq!(res.incoming_servers[3].hostname, "mail.lakenet.ch");
314 assert_eq!(res.incoming_servers[3].port, 110);
315 assert_eq!(res.incoming_servers[3].sockettype, Socket::Starttls);
316 assert_eq!(res.incoming_servers[3].username, "example@lakenet.ch");
317
318 assert_eq!(res.outgoing_servers.len(), 1);
319
320 assert_eq!(res.outgoing_servers[0].typ, "smtp");
321 assert_eq!(res.outgoing_servers[0].hostname, "mail.lakenet.ch");
322 assert_eq!(res.outgoing_servers[0].port, 587);
323 assert_eq!(res.outgoing_servers[0].sockettype, Socket::Starttls);
324 assert_eq!(res.outgoing_servers[0].username, "example@lakenet.ch");
325 }
326}