1use anyhow::{Context, Result, anyhow};
4use hmac::{KeyInit, Mac, SimpleHmac, digest::CtOutput};
5use percent_encoding::{AsciiSet, utf8_percent_encode};
6use reqwest::{Client, RequestBuilder, Response};
7use serde::Deserialize;
8use sha1::Sha1;
9use sha2::Sha256;
10use std::time::{Duration, Instant};
11use thiserror::Error;
12
13const RETRY_TIMEOUT: Duration = Duration::from_secs(5);
14const RETRY_INTERVAL: Duration = Duration::from_millis(500);
15
16pub async fn send_with_retry(mut build_req: impl FnMut() -> RequestBuilder) -> Result<Response> {
17 let start = Instant::now();
18 loop {
19 let res = build_req().send().await.context("HTTP request failed");
20 if let Err(ref err) = res
21 && Instant::now() - start < RETRY_TIMEOUT
22 {
23 log::error!("{err:#?}");
24 log::warn!("HTTP request failed, retrying...");
25 tokio::time::sleep(RETRY_INTERVAL).await;
26 } else {
27 break res;
28 }
29 }
30}
31
32#[derive(Debug, Error)]
33#[error("Http error {status} {body}")]
34pub struct HttpStatusError {
35 pub status: u16,
36 pub body: String,
37}
38
39pub async fn check_http_resp(resp: reqwest::Response) -> Result<String> {
43 let status = resp.status();
44 let text = resp.text().await?;
45
46 if status.is_success() {
47 Ok(text)
48 } else {
49 Err(anyhow!(HttpStatusError {
50 status: status.as_u16(),
51 body: text
52 }))
53 }
54}
55
56#[allow(unused)]
60pub async fn check_http_resp_bin(resp: reqwest::Response) -> Result<Vec<u8>> {
61 let status = resp.status();
62
63 if status.is_success() {
64 let bin = resp.bytes().await?.to_vec();
65 Ok(bin)
66 } else {
67 let text = resp.text().await?;
68 Err(anyhow!(HttpStatusError {
69 status: status.as_u16(),
70 body: text
71 }))
72 }
73}
74
75pub async fn checked_get_url(client: &Client, url: &str) -> Result<String> {
77 let resp = send_with_retry(|| client.get(url)).await?;
78
79 check_http_resp(resp).await
80}
81
82pub async fn checked_get_url_bin(client: &Client, url: &str) -> Result<Vec<u8>> {
83 let resp = send_with_retry(|| client.get(url)).await?;
84
85 check_http_resp_bin(resp).await
86}
87
88pub fn convert_from_json<'a, T>(json_str: &'a str) -> Result<T>
92where
93 T: Deserialize<'a>,
94{
95 let obj = serde_json::from_str::<T>(json_str)
96 .with_context(|| format!("JSON parse failed: {json_str}"))?;
97
98 Ok(obj)
99}
100
101const FRAGMENT: &AsciiSet = &percent_encoding::NON_ALPHANUMERIC
105 .remove(b'-')
106 .remove(b'.')
107 .remove(b'_')
108 .remove(b'~');
109
110pub fn percent_encode(input: &str) -> String {
111 utf8_percent_encode(input, FRAGMENT).to_string()
112}
113
114pub fn html_escape(src: &str) -> String {
115 let mut result = String::new();
116 for c in src.chars() {
117 match c {
118 '&' => result.push_str("&"),
119 '"' => result.push_str("""),
120 '\'' => result.push_str("'"),
121 '<' => result.push_str("<"),
122 '>' => result.push_str(">"),
123 _ => result.push(c),
124 }
125 }
126
127 result
128}
129
130pub type HmacSha1 = SimpleHmac<Sha1>;
131pub type HmacSha256 = SimpleHmac<Sha256>;
132
133pub fn hmac_sha1(key: &[u8], data: &[u8]) -> CtOutput<HmacSha1> {
139 let mut mac = HmacSha1::new_from_slice(key).unwrap();
140 mac.update(data);
141
142 mac.finalize()
143}
144
145pub fn hmac_sha256_verify(key: &[u8], data: &[u8], expected: &[u8]) -> Result<()> {
147 let mut mac = HmacSha256::new_from_slice(key).unwrap();
148 mac.update(data);
149 mac.verify_slice(expected)?;
150
151 Ok(())
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use hex_literal::hex;
158
159 #[test]
161 fn percent_encode_twitter_1() {
162 let str = "Ladies + Gentlemen";
163 let result = percent_encode(str);
164 let expected = "Ladies%20%2B%20Gentlemen";
165 assert_eq!(result, expected);
166 }
167
168 #[test]
169 fn percent_encode_twitter_2() {
170 let str = "An encoded string!";
171 let result = percent_encode(str);
172 let expected = "An%20encoded%20string%21";
173 assert_eq!(result, expected);
174 }
175
176 #[test]
177 fn percent_encode_twitter_3() {
178 let str = "Dogs, Cats & Mice";
179 let result = percent_encode(str);
180 let expected = "Dogs%2C%20Cats%20%26%20Mice";
181 assert_eq!(result, expected);
182 }
183
184 #[test]
185 fn percent_encode_twitter_4() {
186 let str = "☃";
187 let result = percent_encode(str);
188 let expected = "%E2%98%83";
189 assert_eq!(result, expected);
190 }
191
192 #[test]
193 fn html_escape_1() {
194 let str = "\"<a href='test'>Test&Test</a>\"";
195 let result = html_escape(str);
196 let expected = ""<a href='test'>Test&Test</a>"";
197 assert_eq!(result, expected);
198 }
199
200 #[test]
201 fn hmac_sha1_rfc2202_1() {
202 let key = &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
203 let data = b"Hi There";
204 let result = hmac_sha1(key, data).into_bytes();
205 let expected = &hex!("b617318655057264e28bc0b6fb378c8ef146be00");
206 assert_eq!(result[..], expected[..]);
207 }
208
209 #[test]
210 fn hmac_sha1_rfc2202_2() {
211 let key = b"Jefe";
212 let data = b"what do ya want for nothing?";
213 let result = hmac_sha1(key, data).into_bytes();
214 let expected = &hex!("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
215 assert_eq!(result[..], expected[..]);
216 }
217
218 #[test]
219 fn hmac_sha1_rfc2202_3() {
220 let key = &hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
221 let data = &[0xdd_u8; 50];
223 let result = hmac_sha1(key, data).into_bytes();
224 let expected = &hex!("125d7342b9ac11cd91a39af48aa17b4f63f175d3");
225 assert_eq!(result[..], expected[..]);
226 }
227
228 #[test]
229 fn hmac_sha1_rfc2202_4() {
230 let key = &hex!("0102030405060708090a0b0c0d0e0f10111213141516171819");
231 let data = &[0xcd_u8; 50];
233 let result = hmac_sha1(key, data).into_bytes();
234 let expected = &hex!("4c9007f4026250c6bc8414f9bf50c86c2d7235da");
235 assert_eq!(result[..], expected[..]);
236 }
237
238 #[test]
239 fn hmac_sha1_rfc2202_5() {
240 let key = &hex!("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c");
241 let data = b"Test With Truncation";
242 let result = hmac_sha1(key, data).into_bytes();
243 let expected = &hex!("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
244 assert_eq!(result[..], expected[..]);
245 }
246
247 #[test]
248 fn hmac_sha1_rfc2202_6() {
249 let key = &[0xaa_u8; 80];
251 let data = b"Test Using Larger Than Block-Size Key - Hash Key First";
252 let result = hmac_sha1(key, data).into_bytes();
253 let expected = &hex!("aa4ae5e15272d00e95705637ce8a3b55ed402112");
254 assert_eq!(result[..], expected[..]);
255 }
256
257 #[test]
258 fn hmac_sha1_rfc2202_7() {
259 let key = &[0xaa_u8; 80];
261 let data = b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
262 let result = hmac_sha1(key, data).into_bytes();
263 let expected = &hex!("e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
264 assert_eq!(result[..], expected[..]);
265 }
266}