Repository: algesten/ureq
Pull request #106 (2020-07-01 04:53:29)

Error when LimitedRead gets too few bytes.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 5e082fe..abcb7b5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,7 @@ socks-proxy = ["socks"]
 [dependencies]
 base64 = "0.12"
 chunked_transfer = "1.2.0"
-cookie = { version = "0.13", features = ["percent-encode"], optional = true}
+cookie = { version = "0.14", features = ["percent-encode"], optional = true}
 lazy_static = "1"
 qstring = "0.7"
 url = "2"
diff --git a/src/request.rs b/src/request.rs
index 59e60b3..715fb36 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -39,7 +39,7 @@ pub struct Request {
 
     // via agent
     pub(crate) method: String,
-    path: String,
+    url: String,
 
     // from request itself
     pub(crate) headers: Vec<Header>,
@@ -72,11 +72,11 @@ impl ::std::fmt::Debug for Request {
 }
 
 impl Request {
-    pub(crate) fn new(agent: &Agent, method: String, path: String) -> Request {
+    pub(crate) fn new(agent: &Agent, method: String, url: String) -> Request {
         Request {
             agent: Arc::clone(&agent.state),
             method,
-            path,
+            url,
             headers: agent.headers.clone(),
             redirects: 5,
             ..Default::default()
@@ -260,7 +260,7 @@ impl Request {
     ///     .build();
     /// assert_eq!("foobar", req.header("x-api-Key").unwrap());
     /// ```
-    pub fn header<'a>(&self, name: &'a str) -> Option<&str> {
+    pub fn header(&self, name: &str) -> Option<&str> {
         header::get_header(&self.headers, name)
     }
 
@@ -288,7 +288,7 @@ impl Request {
     ///     .build();
     /// assert_eq!(true, req.has("x-api-Key"));
     /// ```
-    pub fn has<'a>(&self, name: &'a str) -> bool {
+    pub fn has(&self, name: &str) -> bool {
         header::has_header(&self.headers, name)
     }
 
@@ -304,7 +304,7 @@ impl Request {
     ///     "2.3.4.5",
     /// ]);
     /// ```
-    pub fn all<'a>(&self, name: &'a str) -> Vec<&str> {
+    pub fn all(&self, name: &str) -> Vec<&str> {
         header::get_all_headers(&self.headers, name)
     }
 
@@ -512,7 +512,7 @@ impl Request {
     /// assert_eq!(req.get_url(), "https://cool.server/innit");
     /// ```
     pub fn get_url(&self) -> &str {
-        &self.path
+        &self.url
     }
 
     /// Normalizes and returns the host that will be used for this request.
@@ -558,7 +558,7 @@ impl Request {
             .map(|u| unit::combine_query(&u, &self.query, true))
     }
 
-    /// The normalized path of this request.
+    /// The normalized url of this request.
     ///
     /// Example:
     /// ```
@@ -572,7 +572,7 @@ impl Request {
 
     fn to_url(&self) -> Result<Url, Error> {
         URL_BASE
-            .join(&self.path)
+            .join(&self.url)
             .map_err(|e| Error::BadUrl(format!("{}", e)))
     }
 
diff --git a/src/response.rs b/src/response.rs
index 3e52ead..3611567 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -122,7 +122,7 @@ impl Response {
     }
 
     /// The header corresponding header value for the give name, if any.
-    pub fn header<'a>(&self, name: &'a str) -> Option<&str> {
+    pub fn header(&self, name: &str) -> Option<&str> {
         self.headers
             .iter()
             .find(|h| h.is_name(name))
@@ -139,12 +139,12 @@ impl Response {
     }
 
     /// Tells if the response has the named header.
-    pub fn has<'a>(&self, name: &'a str) -> bool {
+    pub fn has(&self, name: &str) -> bool {
         self.header(name).is_some()
     }
 
     /// All headers corresponding values for the give name, or empty vector.
-    pub fn all<'a>(&self, name: &'a str) -> Vec<&str> {
+    pub fn all(&self, name: &str) -> Vec<&str> {
         self.headers
             .iter()
             .filter(|h| h.is_name(name))
@@ -631,6 +631,15 @@ impl<R: Read> Read for LimitedRead<R> {
             buf
         };
         match self.reader.read(from) {
+            // https://tools.ietf.org/html/rfc7230#page-33
+            // If the sender closes the connection or
+            // the recipient times out before the indicated number of octets are
+            // received, the recipient MUST consider the message to be
+            // incomplete and close the connection.
+            Ok(0) => Err(IoError::new(
+                ErrorKind::InvalidData,
+                "response body closed before all bytes were read",
+            )),
             Ok(amount) => {
                 self.position += amount;
                 Ok(amount)
@@ -640,6 +649,15 @@ impl<R: Read> Read for LimitedRead<R> {
     }
 }
 
+#[test]
+fn short_read() {
+    use std::io::Cursor;
+    let mut lr = LimitedRead::new(Cursor::new(vec![b'a'; 3]), 10);
+    let mut buf = vec![0; 1000];
+    let result = lr.read_to_end(&mut buf);
+    assert!(result.is_err());
+}
+
 impl<R: Read> From<LimitedRead<R>> for Stream
 where
     Stream: From<R>,
diff --git a/src/unit.rs b/src/unit.rs
index b6fd18b..e28c404 100644
--- a/src/unit.rs
+++ b/src/unit.rs
@@ -116,15 +116,15 @@ impl Unit {
     }
 
     #[cfg(test)]
-    pub fn header<'a>(&self, name: &'a str) -> Option<&str> {
+    pub fn header(&self, name: &str) -> Option<&str> {
         header::get_header(&self.headers, name)
     }
     #[cfg(test)]
-    pub fn has<'a>(&self, name: &'a str) -> bool {
+    pub fn has(&self, name: &str) -> bool {
         header::has_header(&self.headers, name)
     }
     #[cfg(test)]
-    pub fn all<'a>(&self, name: &'a str) -> Vec<&str> {
+    pub fn all(&self, name: &str) -> Vec<&str> {
         header::get_all_headers(&self.headers, name)
     }
 }
@@ -250,7 +250,7 @@ fn extract_cookies(_state: &std::sync::Mutex<Option<AgentState>>, _url: &Url) ->
 
 // TODO check so cookies can't be set for tld:s
 #[cfg(feature = "cookie")]
-fn match_cookies<'a>(jar: &'a CookieJar, domain: &str, path: &str, is_secure: bool) -> Vec<Header> {
+fn match_cookies(jar: &CookieJar, domain: &str, path: &str, is_secure: bool) -> Vec<Header> {
     jar.iter()
         .filter(|c| {
             // if there is a domain, it must be matched.


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fce37c2..94379ff 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,4 +31,4 @@ jobs:
         with:
           command: doc
           # Keep in sync with Cargo.toml's [package.metadata.docs.rs]
-          args: --no-default-features --features "tls json charset cookies socks-proxy"
+          args: --no-default-features --no-deps --features "tls json charset cookies socks-proxy"