1use base64::{engine::general_purpose::URL_SAFE, Engine};
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4use url::Url;
5
6use crate::{
7 transformation_params::{Height, TransformationParams, Width},
8 Key,
9};
10
11#[derive(Debug, Clone)]
13pub struct Verifier {
14 key: Key,
15}
16
17impl Verifier {
18 pub const fn new(key: Key) -> Self {
20 Self { key }
21 }
22
23 pub fn verify(&self, signature: &str, value: &str) -> bool {
48 let Ok(digest) = URL_SAFE.decode(signature) else {
49 tracing::warn!("could not Base64 decode signature");
50 return false;
51 };
52
53 let mut mac = Hmac::<Sha256>::new_from_slice(self.key.as_slice())
54 .expect("HMAC can take key of any size");
55 mac.update(value.as_bytes());
56 mac.verify_slice(&digest).is_ok()
57 }
58}
59
60#[derive(Debug)]
62pub struct SignedUrl {
63 base: Url,
64 key: Key,
65 params: TransformationParams,
66 target: Url,
67}
68
69impl SignedUrl {
70 const fn new(key: Key, base: Url, target: Url, params: TransformationParams) -> Self {
71 Self {
72 base,
73 key,
74 params,
75 target,
76 }
77 }
78
79 fn sign(&self, data: &[u8]) -> String {
80 let mut mac = Hmac::<Sha256>::new_from_slice(self.key.as_slice())
81 .expect("HMAC can take key of any size");
82 mac.update(data);
83 URL_SAFE.encode(mac.finalize().into_bytes())
84 }
85
86 pub fn generate_signed_url(&self) -> Result<Url, url::ParseError> {
90 let params_encoded = self.params.to_string();
91 let url_encoded = urlencoding::encode(self.target.as_ref());
92 let combined_encoded = format!("{params_encoded}{url_encoded}");
93 let signature = self.sign(combined_encoded.as_bytes());
94
95 self.base
96 .join(&format!("{signature}/{params_encoded}/{url_encoded}"))
97 }
98}
99
100#[derive(Debug)]
102pub struct SignedUrlBuilder<K, B, P, T> {
103 key: K,
104 base: B,
105 params: P,
106 target: T,
107}
108
109impl SignedUrlBuilder<(), (), (), ()> {
110 pub const fn new() -> Self {
112 Self {
113 key: (),
114 base: (),
115 params: (),
116 target: (),
117 }
118 }
119
120 pub const fn key(self, key: Key) -> SignedUrlBuilder<Key, (), (), ()> {
122 let Self {
123 base,
124 params,
125 target,
126 ..
127 } = self;
128 SignedUrlBuilder {
129 key,
130 base,
131 params,
132 target,
133 }
134 }
135}
136
137impl SignedUrlBuilder<Key, (), (), ()> {
138 pub const fn base(self, base: Url) -> SignedUrlBuilder<Key, Url, (), ()> {
140 let Self {
141 key,
142 params,
143 target,
144 ..
145 } = self;
146 SignedUrlBuilder {
147 key,
148 base,
149 params,
150 target,
151 }
152 }
153}
154
155impl SignedUrlBuilder<Key, Url, (), ()> {
156 pub fn params(self) -> SignedUrlBuilder<Key, Url, TransformationParams, ()> {
158 let Self {
159 key, base, target, ..
160 } = self;
161 let params = TransformationParams::default();
162 SignedUrlBuilder {
163 key,
164 base,
165 target,
166 params,
167 }
168 }
169}
170
171impl SignedUrlBuilder<Key, Url, TransformationParams, ()> {
172 pub fn height(self, height: Height) -> Self {
174 let Self {
175 key,
176 base,
177 target,
178 mut params,
179 ..
180 } = self;
181 params.height = Some(height);
182 SignedUrlBuilder {
183 key,
184 base,
185 target,
186 params,
187 }
188 }
189
190 pub fn width(self, width: Width) -> Self {
192 let Self {
193 key,
194 base,
195 target,
196 mut params,
197 ..
198 } = self;
199 params.width = Some(width);
200 SignedUrlBuilder {
201 key,
202 base,
203 target,
204 params,
205 }
206 }
207
208 pub fn target(self, target: Url) -> SignedUrlBuilder<Key, Url, TransformationParams, Url> {
210 let Self {
211 key, base, params, ..
212 } = self;
213 SignedUrlBuilder {
214 key,
215 base,
216 target,
217 params,
218 }
219 }
220}
221
222impl SignedUrlBuilder<Key, Url, TransformationParams, Url> {
223 pub fn build(self) -> SignedUrl {
225 SignedUrl::new(self.key, self.base, self.target, self.params)
226 }
227}
228
229impl Default for SignedUrlBuilder<(), (), (), ()> {
230 fn default() -> Self {
231 Self::new()
232 }
233}