twilio_agnostic/
webhook.rs1use 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}