Skip to content

Commit ac6b94e

Browse files
committed
upgrade ureq to v3.0
1 parent bac4981 commit ac6b94e

File tree

3 files changed

+36
-75
lines changed

3 files changed

+36
-75
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ default-features = false
3838
features = ["http2", "rustls-tls", "stream"]
3939

4040
[dependencies.ureq]
41-
version = "2.5.0"
41+
version = "3.0.4"
4242
optional = true
4343
default-features = false
44-
features = ["tls"]
44+
features = ["rustls"]
4545

4646
[dev-dependencies]
4747
env_logger = "0.11"

RELEASE_NOTES.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# unreleased changes
2+
* Default non-async HTTP client, ureq, updated from major version 2 to 3.
3+
14
# v0.19.0
25
2025-01-03
36
* **BIG CHANGE: async support added**

src/default_client.rs

+31-73
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use crate::default_client_common::impl_set_path_root;
1919
use crate::oauth2::{Authorization, TokenCache};
2020
use crate::Error;
2121
use futures::FutureExt;
22-
use std::borrow::Cow;
23-
use std::fmt::Write;
2422
use std::str::FromStr;
2523
use std::sync::Arc;
24+
use ureq::typestate::WithBody;
25+
use ureq::Agent;
2626

2727
macro_rules! impl_update_token {
2828
($self:ident) => {
@@ -237,13 +237,18 @@ impl crate::async_client_trait::NoauthClient for TokenUpdateClient<'_> {}
237237

238238
#[derive(Debug)]
239239
struct UreqClient {
240-
agent: ureq::Agent,
240+
agent: Agent,
241241
}
242242

243243
impl Default for UreqClient {
244244
fn default() -> Self {
245245
Self {
246-
agent: ureq::Agent::new(),
246+
agent: Agent::new_with_config(
247+
Agent::config_builder()
248+
.https_only(true)
249+
.http_status_as_error(false)
250+
.build(),
251+
),
247252
}
248253
}
249254
}
@@ -253,24 +258,33 @@ impl HttpClient for UreqClient {
253258

254259
fn execute(&self, request: Self::Request, body: &[u8]) -> Result<HttpRequestResultRaw, Error> {
255260
let resp = if body.is_empty() {
256-
request.req.call()
261+
request.req.send_empty()
257262
} else {
258-
request.req.send_bytes(body)
263+
request.req.send(body)
259264
};
260265

261266
let (status, resp) = match resp {
262-
Ok(resp) => (resp.status(), resp),
263-
Err(ureq::Error::Status(status, resp)) => (status, resp),
264-
Err(e @ ureq::Error::Transport(_)) => {
267+
Ok(resp) => (resp.status().as_u16(), resp),
268+
Err(ureq::Error::Io(e)) => {
269+
return Err(e.into());
270+
}
271+
Err(e) => {
265272
return Err(RequestError { inner: e }.into());
266273
}
267274
};
268275

269-
let result_header = resp.header("Dropbox-API-Result").map(String::from);
276+
let result_header = resp
277+
.headers()
278+
.get("Dropbox-API-Result")
279+
.map(|v| String::from_utf8(v.as_bytes().to_vec()))
280+
.transpose()
281+
.map_err(|e| e.utf8_error())?;
270282

271283
let content_length = resp
272-
.header("Content-Length")
273-
.map(|s| {
284+
.headers()
285+
.get("Content-Length")
286+
.map(|v| {
287+
let s = std::str::from_utf8(v.as_bytes())?;
274288
u64::from_str(s).map_err(|e| {
275289
Error::UnexpectedResponse(format!("invalid Content-Length {s:?}: {e}"))
276290
})
@@ -281,7 +295,7 @@ impl HttpClient for UreqClient {
281295
status,
282296
result_header,
283297
content_length,
284-
body: resp.into_reader(),
298+
body: Box::new(resp.into_body().into_reader()),
285299
})
286300
}
287301

@@ -294,18 +308,12 @@ impl HttpClient for UreqClient {
294308

295309
/// This is an implementation detail of the HTTP client.
296310
pub struct UreqRequest {
297-
req: ureq::Request,
311+
req: ureq::RequestBuilder<WithBody>,
298312
}
299313

300314
impl HttpRequest for UreqRequest {
301315
fn set_header(mut self, name: &str, value: &str) -> Self {
302-
if name.eq_ignore_ascii_case("dropbox-api-arg") {
303-
// Non-ASCII and 0x7F in a header need to be escaped per the HTTP spec, and ureq doesn't
304-
// do this for us. This is only an issue for this particular header.
305-
self.req = self.req.set(name, json_escape_header(value).as_ref());
306-
} else {
307-
self.req = self.req.set(name, value);
308-
}
316+
self.req = self.req.header(name, value);
309317
self
310318
}
311319
}
@@ -316,7 +324,7 @@ impl HttpRequest for UreqRequest {
316324
pub enum DefaultClientError {
317325
/// The HTTP client encountered invalid UTF-8 data.
318326
#[error("invalid UTF-8 string")]
319-
Utf8(#[from] std::string::FromUtf8Error),
327+
Utf8(#[from] std::str::Utf8Error),
320328

321329
/// The HTTP client encountered some I/O error.
322330
#[error("I/O error: {0}")]
@@ -339,7 +347,7 @@ macro_rules! wrap_error {
339347
}
340348

341349
wrap_error!(std::io::Error);
342-
wrap_error!(std::string::FromUtf8Error);
350+
wrap_error!(std::str::Utf8Error);
343351
wrap_error!(RequestError);
344352

345353
/// Something went wrong making the request, or the server returned a response we didn't expect.
@@ -367,53 +375,3 @@ impl std::error::Error for RequestError {
367375
Some(&self.inner)
368376
}
369377
}
370-
371-
/// Replaces any non-ASCII characters (and 0x7f) with JSON-style '\uXXXX' sequence. Otherwise,
372-
/// returns it unmodified without any additional allocation or copying.
373-
fn json_escape_header(s: &str) -> Cow<'_, str> {
374-
// Unfortunately, the HTTP spec requires escaping ASCII DEL (0x7F), so we can't use the quicker
375-
// bit pattern check done in str::is_ascii() to skip this for the common case of all ASCII. :(
376-
377-
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-
// If we're still borrowed, we must have had ascii up until this point.
383-
// Clone the string up until here, and from now on we'll be pushing chars to it.
384-
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)]
398-
mod 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-
);
408-
assert_eq!(
409-
Cow::<'_, str>::Owned(
410-
r#"{"field": "some_\u00fc\u00f1\u00eec\u00f8d\u00e9_and_\u007f"}"#.to_owned()
411-
),
412-
json_escape_header("{\"field\": \"some_üñîcødé_and_\x7f\"}")
413-
);
414-
assert_eq!(
415-
Cow::<'_, str>::Owned("almost,\\u007f but not quite".to_owned()),
416-
json_escape_header("almost,\x7f but not quite")
417-
);
418-
}
419-
}

0 commit comments

Comments
 (0)