Skip to main content

deltachat/configure/
auto_outlook.rs

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