deltachat/configure/
auto_outlook.rs1use std::io::BufRead;
7
8use quick_xml::XmlVersion;
9use quick_xml::events::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)]
21struct ProtocolTag {
22 pub typ: String,
26
27 pub server: String,
31
32 pub port: u16,
36
37 pub ssl: bool,
41}
42
43enum ParsingResult {
44 Protocols(Vec<ProtocolTag>),
45
46 RedirectUrl(String),
48}
49
50fn parse_protocol<B: BufRead>(
52 reader: &mut quick_xml::Reader<B>,
53) -> Result<Option<ProtocolTag>, quick_xml::Error> {
54 let mut protocol_type = None;
55 let mut protocol_server = None;
56 let mut protocol_port = None;
57 let mut protocol_ssl = true;
58
59 let mut buf = Vec::new();
60
61 let mut current_tag: Option<String> = None;
62 loop {
63 match reader.read_event_into(&mut buf)? {
64 Event::Start(ref event) => {
65 current_tag = Some(
66 String::from_utf8_lossy(event.name().as_ref())
67 .trim()
68 .to_lowercase(),
69 );
70 }
71 Event::End(ref event) => {
72 let tag = String::from_utf8_lossy(event.name().as_ref())
73 .trim()
74 .to_lowercase();
75 if tag == "protocol" {
76 break;
77 }
78 if Some(tag) == current_tag {
79 current_tag = None;
80 }
81 }
82 Event::Text(ref e) => {
83 let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
84
85 if let Some(ref tag) = current_tag {
86 match tag.as_str() {
87 "type" => protocol_type = Some(val.trim().to_string()),
88 "server" => protocol_server = Some(val.trim().to_string()),
89 "port" => protocol_port = Some(val.trim().parse().unwrap_or_default()),
90 "ssl" => {
91 protocol_ssl = match val.trim() {
92 "on" => true,
93 "off" => false,
94 _ => true,
95 }
96 }
97 _ => {}
98 };
99 }
100 }
101 Event::Eof => break,
102 _ => {}
103 }
104 }
105
106 if let (Some(protocol_type), Some(protocol_server), Some(protocol_port)) =
107 (protocol_type, protocol_server, protocol_port)
108 {
109 Ok(Some(ProtocolTag {
110 typ: protocol_type,
111 server: protocol_server,
112 port: protocol_port,
113 ssl: protocol_ssl,
114 }))
115 } else {
116 Ok(None)
117 }
118}
119
120fn parse_redirecturl<B: BufRead>(
122 reader: &mut quick_xml::Reader<B>,
123) -> Result<String, quick_xml::Error> {
124 let mut buf = Vec::new();
125 match reader.read_event_into(&mut buf)? {
126 Event::Text(ref e) => {
127 let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
128 Ok(val.trim().to_string())
129 }
130 _ => Ok("".to_string()),
131 }
132}
133
134fn parse_xml_reader<B: BufRead>(
135 reader: &mut quick_xml::Reader<B>,
136) -> Result<ParsingResult, quick_xml::Error> {
137 let mut protocols = Vec::new();
138
139 let mut buf = Vec::new();
140 loop {
141 match reader.read_event_into(&mut buf)? {
142 Event::Start(ref e) => {
143 let tag = String::from_utf8_lossy(e.name().as_ref())
144 .trim()
145 .to_lowercase();
146
147 if tag == "protocol" {
148 if let Some(protocol) = parse_protocol(reader)? {
149 protocols.push(protocol);
150 }
151 } else if tag == "redirecturl" {
152 let redirecturl = parse_redirecturl(reader)?;
153 return Ok(ParsingResult::RedirectUrl(redirecturl));
154 }
155 }
156 Event::Eof => break,
157 _ => (),
158 }
159 buf.clear();
160 }
161
162 Ok(ParsingResult::Protocols(protocols))
163}
164
165fn parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
166 let mut reader = quick_xml::Reader::from_str(xml_raw);
167 reader.config_mut().trim_text(true);
168
169 parse_xml_reader(&mut reader).map_err(|error| Error::InvalidXml {
170 position: reader.buffer_position(),
171 error,
172 })
173}
174
175fn protocols_to_serverparams(protocols: Vec<ProtocolTag>) -> Vec<ServerParams> {
176 protocols
177 .into_iter()
178 .filter_map(|protocol| {
179 Some(ServerParams {
180 protocol: match protocol.typ.to_lowercase().as_ref() {
181 "imap" => Some(Protocol::Imap),
182 "smtp" => Some(Protocol::Smtp),
183 _ => None,
184 }?,
185 socket: match protocol.ssl {
186 true => Socket::Automatic,
187 false => Socket::Plain,
188 },
189 hostname: protocol.server,
190 port: protocol.port,
191 username: String::new(),
192 })
193 })
194 .collect()
195}
196
197pub(crate) async fn outlk_autodiscover(
198 context: &Context,
199 mut url: String,
200 accept_invalid_certificates: bool,
201) -> Result<Vec<ServerParams>, Error> {
202 for _i in 0..10 {
204 let xml_raw = read_url_with_tls(context, &url, !accept_invalid_certificates).await?;
205 let res = parse_xml(&xml_raw);
206 if let Err(err) = &res {
207 warn!(context, "{}", err);
208 }
209 match res? {
210 ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
211 ParsingResult::Protocols(protocols) => {
212 return Ok(protocols_to_serverparams(protocols));
213 }
214 }
215 }
216 Err(Error::Redirection)
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_parse_redirect() {
225 let res = parse_xml("
226<?xml version=\"1.0\" encoding=\"utf-8\"?>
227 <Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
228 <Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
229 <Account>
230 <AccountType>email</AccountType>
231 <Action>redirectUrl</Action>
232 <RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
233 </Account>
234 </Response>
235 </Autodiscover>
236 ").expect("XML is not parsed successfully");
237 if let ParsingResult::RedirectUrl(url) = res {
238 assert_eq!(
239 url,
240 "https://mail.example.com/autodiscover/autodiscover.xml"
241 );
242 } else {
243 panic!("redirecturl is not found");
244 }
245 }
246
247 #[test]
248 fn test_parse_loginparam() {
249 let res = parse_xml(
250 "\
251<?xml version=\"1.0\" encoding=\"utf-8\"?>
252<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
253 <Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
254 <Account>
255 <AccountType>email</AccountType>
256 <Action>settings</Action>
257 <Protocol>
258 <Type>IMAP</Type>
259 <Server>example.com</Server>
260 <Port>993</Port>
261 <SSL>on</SSL>
262 <AuthRequired>on</AuthRequired>
263 </Protocol>
264 <Protocol>
265 <Type>SMTP</Type>
266 <Server>smtp.example.com</Server>
267 <Port>25</Port>
268 <SSL>off</SSL>
269 <AuthRequired>on</AuthRequired>
270 </Protocol>
271 </Account>
272 </Response>
273</Autodiscover>",
274 )
275 .expect("XML is not parsed successfully");
276
277 match res {
278 ParsingResult::Protocols(protocols) => {
279 assert_eq!(protocols[0].typ, "IMAP");
280 assert_eq!(protocols[0].server, "example.com");
281 assert_eq!(protocols[0].port, 993);
282 assert_eq!(protocols[0].ssl, true);
283
284 assert_eq!(protocols[1].typ, "SMTP");
285 assert_eq!(protocols[1].server, "smtp.example.com");
286 assert_eq!(protocols[1].port, 25);
287 assert_eq!(protocols[1].ssl, false);
288 }
289 ParsingResult::RedirectUrl(_) => {
290 panic!("RedirectUrl is not expected");
291 }
292 }
293 }
294}