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