Repository: algesten/ureq
Pull request #472 (2022-01-29 15:20:27)

Fix rustls version spec
`>=0.20.1` doesn't have an upper bound and will accept 0.21 (and higher) once they're available, but they'd presumably have breaking changes.
`0.20.1` means `^0.20.1`, which means `>=0.20.1, <0.21.0`.

https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 040f179..c6767aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,11 +35,12 @@ socks = { version = "0.3", optional = true }
 serde = { version = "1", optional = true }
 serde_json = { version = "1", optional = true }
 encoding_rs = { version = "0.8", optional = true }
+sync_wrapper = { version = "0.1" } 
 cookie_store = { version = "0.15", optional = true, default-features = false, features = ["preserve_order"] }
 log = "0.4"
 webpki = { version = "0.22", optional = true }
 webpki-roots = { version = "0.22", optional = true }
-rustls = { version = ">=0.20.1", optional = true }
+rustls = { version = "0.20.1", optional = true }
 rustls-native-certs = { version = "0.6", optional = true }
 native-tls = { version = "0.2", optional = true }
 flate2 = { version = "1.0.22", optional = true }
diff --git a/src/response.rs b/src/response.rs
index de209d0..23508b8 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -3,6 +3,7 @@ use std::str::FromStr;
 use std::{fmt, io::BufRead};
 
 use chunked_transfer::Decoder as ChunkDecoder;
+use sync_wrapper::SyncWrapper;
 use url::Url;
 
 use crate::error::{Error, ErrorKind::BadStatus};
@@ -67,7 +68,7 @@ pub struct Response {
     // Boxed to avoid taking up too much size.
     unit: Option<Box<Unit>>,
     // Boxed to avoid taking up too much size.
-    stream: Box<Stream>,
+    stream: SyncWrapper<Box<Stream>>,
     /// The redirect history of this response, if any. The history starts with
     /// the first response received and ends with the response immediately
     /// previous to this one.
@@ -292,7 +293,7 @@ impl Response {
             self.length
         };
 
-        let stream = self.stream;
+        let stream = self.stream.into_inner();
         let unit = self.unit;
         if let Some(unit) = &unit {
             let result = stream.set_read_timeout(unit.agent.config.timeout_read);
@@ -512,7 +513,7 @@ impl Response {
             status,
             headers,
             unit: unit.map(Box::new),
-            stream: Box::new(stream.into()),
+            stream: SyncWrapper::new(Box::new(stream.into())),
             history: vec![],
             length,
             compression,
@@ -520,8 +521,9 @@ impl Response {
     }
 
     #[cfg(test)]
-    pub fn as_write_vec(&self) -> &[u8] {
-        self.stream.as_write_vec()
+    pub fn into_written_bytes(self) -> Vec<u8> {
+        // Deliberately consume `self` so that any access to `self.stream` must be non-shared.
+        self.stream.into_inner().written_bytes()
     }
 
     #[cfg(test)]
diff --git a/src/stream.rs b/src/stream.rs
index 1b64f0f..c8cee56 100644
--- a/src/stream.rs
+++ b/src/stream.rs
@@ -18,7 +18,7 @@ use crate::error::ErrorKind;
 use crate::unit::Unit;
 
 /// Trait for things implementing [std::io::Read] + [std::io::Write]. Used in [TlsConnector].
-pub trait ReadWrite: Read + Write + Send + Sync + 'static {
+pub trait ReadWrite: Read + Write + Send + 'static {
     fn socket(&self) -> Option<&TcpStream>;
 }
 
@@ -31,14 +31,16 @@ pub trait TlsConnector: Send + Sync {
 }
 
 pub(crate) struct Stream {
-    inner: BufReader<Box<dyn Inner + Send + Sync + 'static>>,
+    inner: BufReader<Box<dyn Inner + Send + 'static>>,
 }
 
 trait Inner: Read + Write {
     fn is_poolable(&self) -> bool;
     fn socket(&self) -> Option<&TcpStream>;
-    fn as_write_vec(&self) -> &[u8] {
-        panic!("as_write_vec on non Test stream");
+
+    /// The bytes written to the stream as a Vec<u8>. This is used for tests only.
+    fn written_bytes(&self) -> Vec<u8> {
+        panic!("written_bytes on non Test stream");
     }
 }
 
@@ -76,8 +78,10 @@ impl Inner for TestStream {
     fn socket(&self) -> Option<&TcpStream> {
         None
     }
-    fn as_write_vec(&self) -> &[u8] {
-        &self.1
+
+    /// For tests only
+    fn written_bytes(&self) -> Vec<u8> {
+        self.1.clone()
     }
 }
 
@@ -184,7 +188,7 @@ impl fmt::Debug for Stream {
 }
 
 impl Stream {
-    fn new(t: impl Inner + Send + Sync + 'static) -> Stream {
+    fn new(t: impl Inner + Send + 'static) -> Stream {
         Stream::logged_create(Stream {
             inner: BufReader::new(Box::new(t)),
         })
@@ -274,8 +278,8 @@ impl Stream {
     }
 
     #[cfg(test)]
-    pub fn as_write_vec(&self) -> &[u8] {
-        self.inner.get_ref().as_write_vec()
+    pub fn written_bytes(&self) -> Vec<u8> {
+        self.inner.get_ref().written_bytes()
     }
 }
 


Test patch:
diff --git a/src/test/body_send.rs b/src/test/body_send.rs
index 12f23e4..76ae004 100644
--- a/src/test/body_send.rs
+++ b/src/test/body_send.rs
@@ -10,7 +10,7 @@ fn content_length_on_str() {
     let resp = post("test://host/content_length_on_str")
         .send_string("Hello World!!!")
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nContent-Length: 14\r\n"));
 }
@@ -24,7 +24,7 @@ fn user_set_content_length_on_str() {
         .set("Content-Length", "12345")
         .send_string("Hello World!!!")
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nContent-Length: 12345\r\n"));
 }
@@ -43,7 +43,7 @@ fn content_length_on_json() {
     let resp = post("test://host/content_length_on_json")
         .send_json(serde_json::Value::Object(json))
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nContent-Length: 20\r\n"));
 }
@@ -57,7 +57,7 @@ fn content_length_and_chunked() {
         .set("Transfer-Encoding", "chunked")
         .send_string("Hello World!!!")
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("Transfer-Encoding: chunked\r\n"));
     assert!(!s.contains("\r\nContent-Length:\r\n"));
@@ -73,7 +73,7 @@ fn str_with_encoding() {
         .set("Content-Type", "text/plain; charset=iso-8859-1")
         .send_string("Hällo Wörld!!!")
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     assert_eq!(
         &vec[vec.len() - 14..],
         //H  ä    l    l    o    _   W   ö    r    l    d    !   !   !
@@ -95,7 +95,7 @@ fn content_type_on_json() {
     let resp = post("test://host/content_type_on_json")
         .send_json(serde_json::Value::Object(json))
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nContent-Type: application/json\r\n"));
 }
@@ -115,7 +115,7 @@ fn content_type_not_overriden_on_json() {
         .set("content-type", "text/plain")
         .send_json(serde_json::Value::Object(json))
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\ncontent-type: text/plain\r\n"));
 }
diff --git a/src/test/query_string.rs b/src/test/query_string.rs
index f1fba8c..4ab8f21 100644
--- a/src/test/query_string.rs
+++ b/src/test/query_string.rs
@@ -8,7 +8,7 @@ fn no_query_string() {
         test::make_response(200, "OK", vec![], vec![])
     });
     let resp = get("test://host/no_query_string").call().unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("GET /no_query_string HTTP/1.1"))
 }
@@ -23,7 +23,7 @@ fn escaped_query_string() {
         .query("baz", "yo lo")
         .call()
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(
         s.contains("GET /escaped_query_string?foo=bar&baz=yo+lo HTTP/1.1"),
@@ -38,7 +38,7 @@ fn query_in_path() {
         test::make_response(200, "OK", vec![], vec![])
     });
     let resp = get("test://host/query_in_path?foo=bar").call().unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("GET /query_in_path?foo=bar HTTP/1.1"))
 }
@@ -52,7 +52,7 @@ fn query_in_path_and_req() {
         .query("baz", "1 2 3")
         .call()
         .unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("GET /query_in_path_and_req?foo=bar&baz=1+2+3 HTTP/1.1"))
 }
diff --git a/src/test/simple.rs b/src/test/simple.rs
index 29829cf..3391c20 100644
--- a/src/test/simple.rs
+++ b/src/test/simple.rs
@@ -120,7 +120,7 @@ fn escape_path() {
         test::make_response(200, "OK", vec![], vec![])
     });
     let resp = get("test://host/escape_path here").call().unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("GET /escape_path%20here HTTP/1.1"))
 }
@@ -198,7 +198,7 @@ pub fn host_no_port() {
         test::make_response(200, "OK", vec![], vec![])
     });
     let resp = get("test://myhost/host_no_port").call().unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nHost: myhost\r\n"));
 }
@@ -209,7 +209,7 @@ pub fn host_with_port() {
         test::make_response(200, "OK", vec![], vec![])
     });
     let resp = get("test://myhost:234/host_with_port").call().unwrap();
-    let vec = resp.as_write_vec();
+    let vec = resp.into_written_bytes();
     let s = String::from_utf8_lossy(&vec);
     assert!(s.contains("\r\nHost: myhost:234\r\n"));
 }