1use std::str::from_utf8;
4use std::sync::Arc;
5
6use crate::error::{Error, ErrorKind, Result};
7use crate::macros::all_events;
8use crate::{Event, EventKind, RequestParser};
9
10#[cfg(feature = "http")]
11mod http;
12
13#[cfg(feature = "http")]
14pub use self::http::ParseRequest;
15
16pub(crate) fn parse_body(kind: EventKind, body: &str) -> Result<Event> {
18 macro_rules! match_kind_parse_body {
19 ($( $k:ident ),*) => {
20 match kind {
21 $(
22 EventKind::$k => {
23 ::serde_json::from_str(body).map(Event::$k)
24 },
25 )*
26 }
27 };
28 }
29
30 all_events!(match_kind_parse_body).map_err(Error::parse_body_failed)
31}
32
33fn valid_header_value(value: &str) -> bool {
35 value
36 .as_bytes()
37 .iter()
38 .all(|c| (0x20..=0x7E).contains(c) || *c == 0x09)
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub(crate) struct Inner {
43 verification_token: String,
44}
45
46impl Inner {
47 pub(crate) fn new(verification_token: &str) -> Self {
48 Self {
49 verification_token: verification_token.to_string(),
50 }
51 }
52}
53
54impl RequestParser {
55 pub fn new(verification_token: &str) -> Self {
66 Self {
67 inner: Arc::new(Inner::new(verification_token)),
68 }
69 }
70
71 pub fn parse_headers<'a, H, K, V>(&self, headers: H) -> Result<EventKind>
115 where
116 H: IntoIterator<Item = (&'a K, &'a V)>,
117 K: AsRef<[u8]> + ?Sized + 'static,
118 V: AsRef<[u8]> + ?Sized + 'static,
119 {
120 let mut content_type = None;
122 let mut token = None;
124 let mut kind = None;
126 for (k, v) in headers {
127 let Ok(k) = from_utf8(k.as_ref()) else {
128 continue;
129 };
130 let v = from_utf8(v.as_ref());
131 match k.to_lowercase().as_str() {
132 "content-type" => {
133 let v = v.map_err(Error::read_content_type_failed)?;
134 content_type = Some(v);
135 }
136 "x-traq-bot-token" => {
137 let v = v.map_err(Error::read_bot_token_failed)?;
138 token = Some(v);
139 }
140 "x-traq-bot-event" => {
141 let v = v.map_err(Error::read_bot_event_failed)?;
142 kind = Some(v);
143 }
144 _ => continue,
145 }
146 }
147 content_type
148 .ok_or(ErrorKind::ContentTypeNotFound)
149 .map(|ct| ct.starts_with("application/json"))?
150 .then_some(())
151 .ok_or(ErrorKind::ContentTypeMismatch)?;
152 token
153 .ok_or(ErrorKind::BotTokenNotFound)
154 .and_then(|t| {
155 valid_header_value(t)
156 .then_some(t)
157 .ok_or(ErrorKind::ReadBotTokenFailed)
158 })
159 .map(|t| t == self.inner.verification_token)?
160 .then_some(())
161 .ok_or(ErrorKind::BotTokenMismatch)?;
162 kind.ok_or(ErrorKind::BotEventNotFound)
163 .and_then(|k| {
164 valid_header_value(k)
165 .then_some(k)
166 .ok_or(ErrorKind::ReadBotEventFailed)
167 })?
168 .parse()
169 .map_err(Error::bot_event_mismatch)
170 }
171
172 pub fn parse<'a, H, K, V>(&self, headers: H, body: &[u8]) -> Result<Event>
206 where
207 H: IntoIterator<Item = (&'a K, &'a V)>,
208 K: AsRef<[u8]> + ?Sized + 'static,
209 V: AsRef<[u8]> + ?Sized + 'static,
210 {
211 let kind = self.parse_headers(headers)?;
212 let body = from_utf8(body).map_err(Error::read_body_failed)?;
213 parse_body(kind, body)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::macros::test_parse_payload;
221
222 use ::http::header::HeaderMap;
223 use ::http::header::CONTENT_TYPE;
224
225 #[test]
226 fn request_parser_new() {
227 let verification_token = "verification_token";
228 let parser = RequestParser::new(verification_token);
229 println!("{parser:?}");
230 }
231
232 #[test]
233 fn parse_failure() {
234 use crate::test_utils::make_parser;
235 let parser = make_parser();
236 let mut headers = HeaderMap::new();
237 assert_eq!(
238 parser.parse(&headers, b"").map_err(|e| e.kind()),
239 Err(ErrorKind::ContentTypeNotFound)
240 );
241 headers.insert(CONTENT_TYPE, "text/plain".parse().unwrap());
242 assert_eq!(
243 parser.parse(&headers, b"").map_err(|e| e.kind()),
244 Err(ErrorKind::ContentTypeMismatch)
245 );
246 headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
247 assert_eq!(
248 parser.parse(&headers, b"").map_err(|e| e.kind()),
249 Err(ErrorKind::BotTokenNotFound)
250 );
251 headers.insert("X-TRAQ-BOT-TOKEN", "invalid token".parse().unwrap());
252 assert_eq!(
253 parser.parse(&headers, b"").map_err(|e| e.kind()),
254 Err(ErrorKind::ReadBotTokenFailed)
255 );
256 headers.insert("X-TRAQ-BOT-TOKEN", "invalid_token".parse().unwrap());
257 assert_eq!(
258 parser.parse(&headers, b"").map_err(|e| e.kind()),
259 Err(ErrorKind::BotTokenMismatch)
260 );
261 headers.insert(
262 "X-TRAQ-BOT-TOKEN",
263 "traqbotverificationtoken".parse().unwrap(),
264 );
265 assert_eq!(
266 parser.parse(&headers, b"").map_err(|e| e.kind()),
267 Err(ErrorKind::BotEventNotFound)
268 );
269 headers.insert("X-TRAQ-BOT-EVENT", "invalid event".parse().unwrap());
270 assert_eq!(
271 parser.parse(&headers, b"").map_err(|e| e.kind()),
272 Err(ErrorKind::ReadBotEventFailed)
273 );
274 headers.insert("X-TRAQ-BOT-EVENT", "invalid_event".parse().unwrap());
275 assert_eq!(
276 parser.parse(&headers, b"").map_err(|e| e.kind()),
277 Err(ErrorKind::BotEventMismatch)
278 );
279 headers.insert("X-TRAQ-BOT-EVENT", "PING".parse().unwrap());
280 assert_eq!(
281 parser
282 .parse(&headers, &[0, 159, 146, 150])
283 .map_err(|e| e.kind()),
284 Err(ErrorKind::ReadBodyFailed)
285 );
286 assert_eq!(
287 parser.parse(&headers, b"").map_err(|e| e.kind()),
288 Err(ErrorKind::ParseBodyFailed)
289 );
290 }
291
292 test_parse_payload! {"system", Ping}
293
294 test_parse_payload! {"system", Joined}
295
296 test_parse_payload! {"system", Left}
297
298 test_parse_payload! {"message", MessageCreated}
299
300 test_parse_payload! {"message", MessageDeleted}
301
302 test_parse_payload! {"message", MessageUpdated}
303
304 test_parse_payload! {"message", DirectMessageCreated}
305
306 test_parse_payload! {"message", DirectMessageDeleted}
307
308 test_parse_payload! {"message", DirectMessageUpdated}
309
310 test_parse_payload! {"message", BotMessageStampsUpdated}
311
312 test_parse_payload! {"channel", ChannelCreated}
313
314 test_parse_payload! {"channel", ChannelTopicChanged}
315
316 test_parse_payload! {"user", UserCreated}
317
318 test_parse_payload! {"stamp", StampCreated}
319
320 test_parse_payload! {"tag", TagAdded}
321
322 test_parse_payload! {"tag", TagRemoved}
323
324 test_parse_payload! {"user-group", UserGroupCreated}
325
326 test_parse_payload! {"user-group", UserGroupUpdated}
327
328 test_parse_payload! {"user-group", UserGroupDeleted}
329
330 test_parse_payload! {"user-group", UserGroupMemberAdded}
331
332 test_parse_payload! {"user-group", UserGroupMemberUpdated}
333
334 test_parse_payload! {"user-group", UserGroupMemberRemoved}
335
336 test_parse_payload! {"user-group", UserGroupAdminAdded}
337
338 test_parse_payload! {"user-group", UserGroupAdminRemoved}
339}