1use salvo_core::http::Request;
13
14use crate::CaptchaFinder;
15
16#[derive(Debug)]
18pub struct CaptchaFormFinder {
19 pub token_name: String,
23
24 pub answer_name: String,
28}
29
30impl CaptchaFormFinder {
31 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn token_name(mut self, token_name: String) -> Self {
38 self.token_name = token_name;
39 self
40 }
41
42 pub fn answer_name(mut self, answer_name: String) -> Self {
44 self.answer_name = answer_name;
45 self
46 }
47}
48
49impl Default for CaptchaFormFinder {
50 fn default() -> Self {
54 Self {
55 token_name: "captcha_token".to_string(),
56 answer_name: "captcha_answer".to_string(),
57 }
58 }
59}
60
61impl CaptchaFinder for CaptchaFormFinder {
62 async fn find_token(&self, req: &mut Request) -> Option<Option<String>> {
63 req.form_data()
64 .await
65 .ok()
66 .and_then(|form| form.fields.get(&self.token_name).cloned().map(Some))
67 }
68
69 async fn find_answer(&self, req: &mut Request) -> Option<Option<String>> {
70 req.form_data()
71 .await
72 .ok()
73 .and_then(|form| form.fields.get(&self.answer_name).cloned().map(Some))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use salvo_core::http::{header, HeaderValue, ReqBody};
80
81 use super::*;
82
83 #[tokio::test]
84 #[rstest::rstest]
85 #[case::not_found(
86 None,
87 None,
88 None,
89 None,
90 "application/x-www-form-urlencoded",
91 None,
92 None
93 )]
94 #[case::not_found(None, None, None, None, "text/plain", None, None)]
95 #[case::normal(
96 None,
97 None,
98 Some(("captcha_token", "token")),
99 Some(("captcha_answer", "answer")),
100 "application/x-www-form-urlencoded",
101 Some(Some("token")),
102 Some(Some("answer"))
103 )]
104 #[case::custom_keys(
105 Some("custom_token"),
106 Some("custom_answer"),
107 Some(("custom_token", "token")),
108 Some(("custom_answer", "answer")),
109 "application/x-www-form-urlencoded",
110 Some(Some("token")),
111 Some(Some("answer"))
112 )]
113 #[case::only_token(
114 None,
115 None,
116 Some(("captcha_token", "token")),
117 None,
118 "application/x-www-form-urlencoded",
119 Some(Some("token")),
120 None
121 )]
122 #[case::only_answer(
123 None,
124 None,
125 None,
126 Some(("captcha_answer", "answer")),
127 "application/x-www-form-urlencoded",
128 None,
129 Some(Some("answer"))
130 )]
131 #[case::custom_not_found(
132 Some("custom_token"),
133 Some("custom_answer"),
134 None,
135 None,
136 "application/x-www-form-urlencoded",
137 None,
138 None
139 )]
140 #[case::custom_not_found_with_body(
141 Some("custom_token"),
142 Some("custom_answer"),
143 Some(("captcha_token", "token")),
144 Some(("captcha_answer", "answer")),
145 "application/x-www-form-urlencoded",
146 None,
147 None
148 )]
149 #[case::invalid_type(
150 None,
151 None,
152 Some(("captcha_token", "token")),
153 Some(("captcha_answer", "answer")),
154 "application/json",
155 None,
156 None
157 )]
158 async fn test_form_finder(
159 #[case] custom_token_key: Option<&'static str>,
160 #[case] custom_answer_key: Option<&'static str>,
161 #[case] token_key_val: Option<(&'static str, &'static str)>,
162 #[case] answer_key_val: Option<(&'static str, &'static str)>,
163 #[case] content_type: &'static str,
164 #[case] excepted_token: Option<Option<&'static str>>,
165 #[case] excepted_answer: Option<Option<&'static str>>,
166 ) {
167 let mut req = Request::default();
168 let mut finder = CaptchaFormFinder::new();
169 if let Some(token_key) = custom_token_key {
170 finder = finder.token_name(token_key.to_string())
171 }
172 if let Some(answer_key) = custom_answer_key {
173 finder = finder.answer_name(answer_key.to_string())
174 }
175
176 let body = token_key_val
177 .zip(answer_key_val)
178 .map(|((t_k, t_v), (a_k, a_v))| format!("{t_k}={t_v}&{a_k}={a_v}"))
179 .unwrap_or_else(|| {
180 token_key_val
181 .or(answer_key_val)
182 .map(|(k, v)| format!("{k}={v}"))
183 .unwrap_or_default()
184 });
185
186 *req.body_mut() = ReqBody::Once(body.into());
187 let headers = req.headers_mut();
188 headers.insert(
189 header::CONTENT_TYPE,
190 HeaderValue::from_str(content_type).unwrap(),
191 );
192
193 assert_eq!(
194 finder.find_token(&mut req).await,
195 excepted_token.map(|o| o.map(ToOwned::to_owned))
196 );
197 assert_eq!(
198 finder.find_answer(&mut req).await,
199 excepted_answer.map(|o| o.map(ToOwned::to_owned))
200 );
201 }
202}