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