twilio_agnostic/
webhook.rs

1use crypto::{
2    hmac::Hmac,
3    mac::{Mac, MacResult},
4    sha1::Sha1,
5};
6use http::{header::HOST, Method};
7
8use std::collections::BTreeMap;
9
10use crate::{Client, FromMap, TwilioError};
11
12fn get_args(path: &str) -> BTreeMap<String, String> {
13    let url_segments: Vec<&str> = path.split('?').collect();
14    if url_segments.len() != 2 {
15        return BTreeMap::new();
16    }
17    let query_string = url_segments[1];
18    args_from_urlencoded(query_string.as_bytes())
19}
20
21fn args_from_urlencoded(enc: &[u8]) -> BTreeMap<String, String> {
22    url::form_urlencoded::parse(enc).into_owned().collect()
23}
24
25impl Client {
26    pub async fn parse_request<T: FromMap>(
27        &self,
28        req: http::Request<&[u8]>,
29    ) -> Result<Box<T>, TwilioError> {
30        let sig = req
31            .headers()
32            .get("X-Twilio-Signature")
33            .ok_or_else(|| TwilioError::AuthError)
34            .and_then(|d| base64::decode(d.as_bytes()).map_err(|_| TwilioError::BadRequest))?;
35
36        let (parts, body) = req.into_parts();
37
38        let host = match parts.headers.get(HOST) {
39            None => return Err(TwilioError::BadRequest),
40            Some(h) => h.to_str().map_err(|_| TwilioError::ParsingError)?,
41        };
42        let request_path = match parts.uri.path() {
43            "*" => return Err(TwilioError::BadRequest),
44            path => path,
45        };
46        let (args, post_append) = match parts.method {
47            Method::GET => (get_args(request_path), "".to_string()),
48            Method::POST => {
49                let postargs = args_from_urlencoded(&body);
50                let append = postargs
51                    .iter()
52                    .map(|(k, v)| format!("{}{}", k, v))
53                    .collect();
54                (postargs, append)
55            }
56            _ => return Err(TwilioError::BadRequest),
57        };
58
59        let effective_uri = format!("https://{}{}{}", host, request_path, post_append);
60        let mut hmac = Hmac::new(Sha1::new(), self.auth_token.as_bytes());
61        hmac.input(effective_uri.as_bytes());
62        let result = hmac.result();
63        let expected = MacResult::new(&sig);
64        if result != expected {
65            return Err(TwilioError::AuthError);
66        }
67
68        T::from_map(args)
69    }
70}