1use crate::Error;
15use crate::oauth2::{Authorization, TokenCache};
16use std::borrow::Cow;
17use std::fmt::Write;
18use std::str::FromStr;
19use std::sync::Arc;
20use futures::FutureExt;
21use crate::client_trait::{AppAuthClient, HttpClient, HttpRequest, HttpRequestResultRaw,
22 NoauthClient, TeamAuthClient, TeamSelect, UserAuthClient};
23use crate::default_client_common::impl_set_path_root;
24
25macro_rules! impl_update_token {
26 ($self:ident) => {
27 fn update_token(&$self, old_token: Arc<String>) -> Result<bool, Error> {
28 info!("refreshing auth token");
29 match $self.tokens.update_token(
30 TokenUpdateClient { inner: &$self.inner },
31 old_token,
32 ).now_or_never().unwrap() {
33 Ok(_) => Ok(true),
34 Err(e) => {
35 error!("failed to update auth token: {e}");
36 Err(e.into())
37 }
38 }
39 }
40 };
41}
42
43pub struct UserAuthDefaultClient {
45 inner: UreqClient,
46 tokens: Arc<TokenCache>,
47 path_root: Option<String>, }
49
50impl UserAuthDefaultClient {
51 pub fn new(auth: Authorization) -> Self {
53 Self::from_token_cache(Arc::new(TokenCache::new(auth)))
54 }
55
56 pub fn from_token_cache(tokens: Arc<TokenCache>) -> Self {
59 Self {
60 inner: UreqClient::default(),
61 tokens,
62 path_root: None,
63 }
64 }
65
66 impl_set_path_root!(self);
67}
68
69impl HttpClient for UserAuthDefaultClient {
70 type Request = UreqRequest;
71
72 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
73 self.inner.execute(request, body)
74 }
75
76 fn new_request(&self, url: &str) -> Self::Request {
77 self.inner.new_request(url)
78 }
79
80 impl_update_token!(self);
81
82 fn token(&self) -> Option<Arc<String>> {
83 self.tokens.get_token()
84 }
85
86 fn path_root(&self) -> Option<&str> {
87 self.path_root.as_deref()
88 }
89}
90
91impl UserAuthClient for UserAuthDefaultClient {}
92
93pub struct TeamAuthDefaultClient {
95 inner: UreqClient,
96 tokens: Arc<TokenCache>,
97 path_root: Option<String>, team_select: Option<TeamSelect>,
99}
100
101impl TeamAuthDefaultClient {
102 pub fn new(tokens: impl Into<Arc<TokenCache>>) -> Self {
104 Self {
105 inner: UreqClient::default(),
106 tokens: tokens.into(),
107 path_root: None,
108 team_select: None,
109 }
110 }
111
112 pub fn select(&mut self, team_select: Option<TeamSelect>) {
114 self.team_select = team_select;
115 }
116
117 impl_set_path_root!(self);
118}
119
120impl HttpClient for TeamAuthDefaultClient {
121 type Request = UreqRequest;
122
123 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
124 self.inner.execute(request, body)
125 }
126
127 fn new_request(&self, url: &str) -> Self::Request {
128 self.inner.new_request(url)
129 }
130
131 fn token(&self) -> Option<Arc<String>> {
132 self.tokens.get_token()
133 }
134
135 impl_update_token!(self);
136
137 fn path_root(&self) -> Option<&str> {
138 self.path_root.as_deref()
139 }
140
141 fn team_select(&self) -> Option<&TeamSelect> {
142 self.team_select.as_ref()
143 }
144}
145
146impl TeamAuthClient for TeamAuthDefaultClient {}
147
148#[derive(Debug)]
150pub struct AppAuthDefaultClient {
151 inner: UreqClient,
152 path_root: Option<String>,
153 auth: String,
154}
155
156impl AppAuthDefaultClient {
157 pub fn new(app_key: &str, app_secret: &str) -> Self {
159 use base64::prelude::*;
160 let encoded = BASE64_STANDARD.encode(format!("{app_key}:{app_secret}"));
161 Self {
162 inner: UreqClient::default(),
163 path_root: None,
164 auth: format!("Basic {encoded}"),
165 }
166 }
167
168 impl_set_path_root!(self);
169}
170
171impl HttpClient for AppAuthDefaultClient {
172 type Request = UreqRequest;
173
174 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
175 self.inner.execute(request, body)
176 }
177
178 fn new_request(&self, url: &str) -> Self::Request {
179 self.inner.new_request(url)
180 .set_header("Authorization", &self.auth)
181 }
182}
183
184impl AppAuthClient for AppAuthDefaultClient {}
185
186#[derive(Debug, Default)]
188pub struct NoauthDefaultClient {
189 inner: UreqClient,
190 path_root: Option<String>,
191}
192
193impl NoauthDefaultClient {
194 impl_set_path_root!(self);
195}
196
197impl HttpClient for NoauthDefaultClient {
198 type Request = UreqRequest;
199
200 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
201 self.inner.execute(request, body)
202 }
203
204 fn new_request(&self, url: &str) -> Self::Request {
205 self.inner.new_request(url)
206 }
207
208 fn path_root(&self) -> Option<&str> {
209 self.path_root.as_deref()
210 }
211}
212
213impl NoauthClient for NoauthDefaultClient {}
214
215struct TokenUpdateClient<'a> {
218 inner: &'a UreqClient,
219}
220
221impl HttpClient for TokenUpdateClient<'_> {
222 type Request = UreqRequest;
223
224 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
225 self.inner.execute(request, body)
226 }
227
228 fn new_request(&self, url: &str) -> Self::Request {
229 self.inner.new_request(url)
230 }
231}
232
233impl crate::async_client_trait::NoauthClient for TokenUpdateClient<'_> {}
234
235#[derive(Debug)]
236struct UreqClient {
237 agent: ureq::Agent,
238}
239
240impl Default for UreqClient {
241 fn default() -> Self {
242 Self {
243 agent: ureq::Agent::new(),
244 }
245 }
246}
247
248impl HttpClient for UreqClient {
249 type Request = UreqRequest;
250
251 fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
252 let resp = if body.is_empty() {
253 request.req.call()
254 } else {
255 request.req.send_bytes(body)
256 };
257
258 let (status, resp) = match resp {
259 Ok(resp) => {
260 (resp.status(), resp)
261 }
262 Err(ureq::Error::Status(status, resp)) => {
263 (status, resp)
264 }
265 Err(e @ ureq::Error::Transport(_)) => {
266 return Err(RequestError { inner: e }.into());
267 }
268 };
269
270 let result_header = resp.header("Dropbox-API-Result").map(String::from);
271
272 let content_length = resp.header("Content-Length")
273 .map(|s| {
274 u64::from_str(s)
275 .map_err(|e| Error::UnexpectedResponse(
276 format!("invalid Content-Length {s:?}: {e}")))
277 })
278 .transpose()?;
279
280 Ok(HttpRequestResultRaw {
281 status,
282 result_header,
283 content_length,
284 body: resp.into_reader(),
285 })
286 }
287
288 fn new_request(&self, url: &str) -> Self::Request {
289 UreqRequest {
290 req: self.agent.post(url),
291 }
292 }
293}
294
295pub struct UreqRequest {
297 req: ureq::Request,
298}
299
300impl HttpRequest for UreqRequest {
301 fn set_header(mut self, name: &str, value: &str) -> Self {
302 if name.eq_ignore_ascii_case("dropbox-api-arg") {
303 self.req = self.req.set(name, json_escape_header(value).as_ref());
306 } else {
307 self.req = self.req.set(name, value);
308 }
309 self
310 }
311}
312
313#[derive(thiserror::Error, Debug)]
315#[allow(clippy::large_enum_variant)] pub enum DefaultClientError {
317 #[error("invalid UTF-8 string")]
319 Utf8(#[from] std::string::FromUtf8Error),
320
321 #[error("I/O error: {0}")]
323 #[allow(clippy::upper_case_acronyms)]
324 IO(#[from] std::io::Error),
325
326 #[error(transparent)]
328 Request(#[from] RequestError),
329}
330
331macro_rules! wrap_error {
332 ($e:ty) => {
333 impl From<$e> for crate::Error {
334 fn from(e: $e) -> Self {
335 Self::HttpClient(Box::new(DefaultClientError::from(e)))
336 }
337 }
338 }
339}
340
341wrap_error!(std::io::Error);
342wrap_error!(std::string::FromUtf8Error);
343wrap_error!(RequestError);
344
345pub struct RequestError {
350 inner: ureq::Error,
351}
352
353impl std::fmt::Display for RequestError {
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 <ureq::Error as std::fmt::Display>::fmt(&self.inner, f)
356 }
357}
358
359impl std::fmt::Debug for RequestError {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 <ureq::Error as std::fmt::Debug>::fmt(&self.inner, f)
362 }
363}
364
365impl std::error::Error for RequestError {
366 fn cause(&self) -> Option<&dyn std::error::Error> {
367 Some(&self.inner)
368 }
369}
370
371fn json_escape_header(s: &str) -> Cow<'_, str> {
374 let mut out = Cow::Borrowed(s);
378 for (i, c) in s.char_indices() {
379 if !c.is_ascii() || c == '\x7f' {
380 let mstr = match out {
381 Cow::Borrowed(_) => {
382 out = Cow::Owned(s[0..i].to_owned());
385 out.to_mut()
386 }
387 Cow::Owned(ref mut m) => m,
388 };
389 write!(mstr, "\\u{:04x}", c as u32).unwrap();
390 } else if let Cow::Owned(ref mut o) = out {
391 o.push(c);
392 }
393 }
394 out
395}
396
397#[cfg(test)]
398mod test {
399 use super::*;
400
401 #[test]
402 fn test_json_escape() {
403 assert_eq!(Cow::Borrowed("foobar"), json_escape_header("foobar"));
404 assert_eq!(
405 Cow::<'_, str>::Owned("tro\\u0161kovi".to_owned()),
406 json_escape_header("troškovi"));
407 assert_eq!(
408 Cow::<'_, str>::Owned(
409 r#"{"field": "some_\u00fc\u00f1\u00eec\u00f8d\u00e9_and_\u007f"}"#.to_owned()),
410 json_escape_header("{\"field\": \"some_üñîcødé_and_\x7f\"}"));
411 assert_eq!(
412 Cow::<'_, str>::Owned("almost,\\u007f but not quite".to_owned()),
413 json_escape_header("almost,\x7f but not quite"));
414 }
415}