Repository: 0x676e67/wreq (Rust)

Issue:
Is this expected [duplicate headers] ?
**Describe the bug**
Request specific headers doesn't replace client default_headers, but it inserts a duplicate entry with same name.

**To Reproduce**
```rust
use wreq::Client;
use wreq::header;

#[tokio::main]
async fn main() -> wreq::Result<()> {
	let mut client_headers = header::HeaderMap::new();
    client_headers.insert("MY-H", header::HeaderValue::from_static("Value00"));

    let client = Client::builder()
        .default_headers(client_headers)
        .build()?;

	let mut req_headers = header::HeaderMap::new();
    req_headers.insert("MY-H", header::HeaderValue::from_static("Value11"));

    let resp = client
        .get("https://tls.peet.ws/api/all")
        .headers(req_headers)
        .send().await?;
    println!("{}", resp.text().await?);

    Ok(())
}
```

**Expected behavior**
default header should get replaced by new one. (Simply put, the value should get changed).



Discussion:
I think this should be overwritten. Are you interested in fixing it?



Patch:
diff --git a/src/client/http/service.rs b/src/client/http/service.rs
index 30d2c7d4c..69253904a 100644
--- a/src/client/http/service.rs
+++ b/src/client/http/service.rs
@@ -4,10 +4,7 @@ use std::{
 };
 
 use futures_util::future::{self, Either, Ready};
-use http::{
-    HeaderMap, HeaderValue, Request, Response,
-    header::{Entry, PROXY_AUTHORIZATION},
-};
+use http::{HeaderMap, HeaderValue, Request, Response, header::PROXY_AUTHORIZATION};
 use tower::{Layer, Service};
 
 use crate::{
@@ -121,19 +118,9 @@ where
         {
             // insert default headers in the request headers
             // without overwriting already appended headers.
-            let headers = req.headers_mut();
-            for (name, value) in &self.config.headers {
-                match headers.entry(name) {
-                    // If the header already exists, append the new value to it.
-                    Entry::Occupied(mut entry) => {
-                        entry.append(value.clone());
-                    }
-                    // If the header does not exist, insert it.
-                    Entry::Vacant(entry) => {
-                        entry.insert(value.clone());
-                    }
-                }
-            }
+            let mut src = self.config.headers.clone();
+            crate::util::replace_headers(&mut src, req.headers().clone());
+            *req.headers_mut() = src;
         }
 
         // store the original headers in request extensions


Test patch:
diff --git a/tests/client.rs b/tests/client.rs
index 898a33f1b..d1dcc9a02 100644
--- a/tests/client.rs
+++ b/tests/client.rs
@@ -6,8 +6,8 @@ use std::collections::HashMap;
 use http::{
     HeaderMap, HeaderValue, Version,
     header::{
-        self, AUTHORIZATION, CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE, REFERER,
-        TRANSFER_ENCODING,
+        self, ACCEPT, AUTHORIZATION, CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE, COOKIE, REFERER,
+        TRANSFER_ENCODING, USER_AGENT,
     },
 };
 use http_body_util::BodyExt;
@@ -234,6 +234,104 @@ async fn test_headers_order_with_request() {
     assert_eq!(res.status(), wreq::StatusCode::OK);
 }
 
+#[tokio::test]
+async fn test_overwrite_headers() {
+    let server = server::http(move |req| async move {
+        let path = req.uri().path();
+        match path {
+            "/1" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers()[USER_AGENT], "my-custom-agent");
+                let mut cookies = req.headers().get_all(COOKIE).iter();
+                assert_eq!(cookies.next().unwrap(), "a=b");
+                assert_eq!(cookies.next().unwrap(), "c=d");
+                assert_eq!(cookies.next(), None);
+            }
+            "/2" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers()[USER_AGENT], "my-custom-agent");
+                let mut cookies = req.headers().get_all(COOKIE).iter();
+                assert_eq!(cookies.next().unwrap(), "e=f");
+                assert_eq!(cookies.next().unwrap(), "g=h");
+                assert_eq!(cookies.next(), None);
+            }
+            "/3" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers()[USER_AGENT], "default-agent");
+                let mut cookies = req.headers().get_all(COOKIE).iter();
+                assert_eq!(cookies.next().unwrap(), "a=b");
+                assert_eq!(cookies.next().unwrap(), "c=d");
+                assert_eq!(cookies.next(), None);
+            }
+            "/4" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers()[USER_AGENT], "default-agent");
+                let mut cookies = req.headers().get_all(COOKIE).iter();
+                assert_eq!(cookies.next().unwrap(), "e=f");
+                assert_eq!(cookies.next().unwrap(), "g=h");
+                assert_eq!(cookies.next(), None);
+            }
+            _ => {
+                unreachable!("Unexpected request path: {}", path);
+            }
+        }
+
+        http::Response::default()
+    });
+
+    let mut default_headers = header::HeaderMap::new();
+    default_headers.insert(
+        USER_AGENT,
+        header::HeaderValue::from_static("default-agent"),
+    );
+    default_headers.insert(COOKIE, header::HeaderValue::from_static("a=b"));
+    default_headers.append(COOKIE, header::HeaderValue::from_static("c=d"));
+
+    let client = Client::builder()
+        .no_proxy()
+        .default_headers(default_headers)
+        .build()
+        .unwrap();
+
+    let url = format!("http://{}/1", server.addr());
+    let res = client
+        .get(&url)
+        .header(USER_AGENT, "my-custom-agent")
+        .send()
+        .await
+        .unwrap();
+    assert_eq!(res.uri(), url.as_str());
+    assert_eq!(res.status(), wreq::StatusCode::OK);
+
+    let url = format!("http://{}/2", server.addr());
+    let res = client
+        .get(&url)
+        .header(USER_AGENT, "my-custom-agent")
+        .header(COOKIE, "e=f")
+        .header_append(COOKIE, "g=h")
+        .send()
+        .await
+        .unwrap();
+    assert_eq!(res.uri(), url.as_str());
+    assert_eq!(res.status(), wreq::StatusCode::OK);
+
+    let url = format!("http://{}/3", server.addr());
+    let res = client.get(&url).send().await.unwrap();
+    assert_eq!(res.uri(), url.as_str());
+    assert_eq!(res.status(), wreq::StatusCode::OK);
+
+    let url = format!("http://{}/4", server.addr());
+    let res = client
+        .get(&url)
+        .header(COOKIE, "e=f")
+        .header_append(COOKIE, "g=h")
+        .send()
+        .await
+        .unwrap();
+    assert_eq!(res.uri(), url.as_str());
+    assert_eq!(res.status(), wreq::StatusCode::OK);
+}
+
 #[tokio::test]
 async fn donot_set_content_length_0_if_have_no_body() {
     let server = server::http(move |req| async move {
@@ -800,40 +898,36 @@ async fn tunnel_includes_proxy_auth_with_multiple_proxies() {
 #[tokio::test]
 async fn skip_default_headers() {
     let server = server::http(move |req| async move {
-        if req.uri() != "/skip" && req.uri() != "/no_skip" {
-            panic!("Unexpected request URI: {}", req.uri());
-        }
-
-        if req.uri() == "/skip" {
-            assert_eq!(req.method(), "GET");
-            assert_eq!(req.headers().get("user-agent"), None);
-            assert_eq!(req.headers().get("accept"), None);
-        }
-
-        if req.uri() == "/no_skip" {
-            assert_eq!(req.method(), "GET");
-            assert_eq!(
-                req.headers().get("user-agent"),
-                Some(&"test-agent".parse().unwrap())
-            );
-            assert_eq!(req.headers().get("accept"), Some(&"*/*".parse().unwrap()));
+        let path = req.uri().path();
+        match path {
+            "/skip" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers().get(USER_AGENT), None);
+                assert_eq!(req.headers().get(ACCEPT), None);
+            }
+            "/no_skip" => {
+                assert_eq!(req.method(), "GET");
+                assert_eq!(req.headers()[USER_AGENT], "test-agent");
+                assert_eq!(req.headers()[ACCEPT], "*/*");
+            }
+            _ => unreachable!("Unexpected request path: {path}"),
         }
 
         http::Response::default()
     });
 
-    let url = format!("http://{}/skip", server.addr());
     let client = Client::builder()
         .default_headers({
             let mut headers = wreq::header::HeaderMap::new();
-            headers.insert("user-agent", "test-agent".parse().unwrap());
-            headers.insert("accept", "*/*".parse().unwrap());
+            headers.insert(USER_AGENT, "test-agent".parse().unwrap());
+            headers.insert(ACCEPT, "*/*".parse().unwrap());
             headers
         })
         .no_proxy()
         .build()
         .unwrap();
 
+    let url = format!("http://{}/skip", server.addr());
     let res = client
         .get(&url)
         .default_headers(false)
@@ -844,17 +938,6 @@ async fn skip_default_headers() {
     assert_eq!(res.status(), wreq::StatusCode::OK);
 
     let url = format!("http://{}/no_skip", server.addr());
-    let client = Client::builder()
-        .default_headers({
-            let mut headers = wreq::header::HeaderMap::new();
-            headers.insert("user-agent", "test-agent".parse().unwrap());
-            headers.insert("accept", "*/*".parse().unwrap());
-            headers
-        })
-        .no_proxy()
-        .build()
-        .unwrap();
-
     let res = client.get(&url).send().await.unwrap();
     assert_eq!(res.uri(), url.as_str());
     assert_eq!(res.status(), wreq::StatusCode::OK);
@@ -863,19 +946,26 @@ async fn skip_default_headers() {
 #[tokio::test]
 async fn test_client_same_header_values_append() {
     let server = server::http(move |req| async move {
-        let cookie_values: Vec<_> = req.headers().get_all(header::COOKIE).iter().collect();
-        assert_eq!(cookie_values.len(), 4);
-
-        assert_eq!(cookie_values[0], "duplicate=same_value");
-        assert_eq!(cookie_values[1], "duplicate=same_value");
-        assert_eq!(cookie_values[2], "unique1=value1");
-        assert_eq!(cookie_values[3], "unique2=value2");
+        let path = req.uri().path();
+        match path {
+            "/duplicate-cookies" => {
+                let cookie_values: Vec<_> = req.headers().get_all(header::COOKIE).iter().collect();
+                assert_eq!(cookie_values.len(), 1);
+                assert_eq!(cookie_values[0], "duplicate=same_value");
+            }
+            "/no-duplicate-cookies" => {
+                let cookie_values: Vec<_> = req.headers().get_all(header::COOKIE).iter().collect();
+                assert_eq!(cookie_values.len(), 3);
+                assert_eq!(cookie_values[0], "duplicate=same_value");
+                assert_eq!(cookie_values[1], "unique1=value1");
+                assert_eq!(cookie_values[2], "unique2=value2");
+            }
+            _ => unreachable!("Unexpected request path: {}", path),
+        }
 
         http::Response::default()
     });
 
-    let url = format!("http://{}/duplicate-cookies", server.addr());
-
     let client = Client::builder()
         .no_proxy()
         .default_headers({
@@ -892,12 +982,18 @@ async fn test_client_same_header_values_append() {
         .unwrap();
 
     let res = client
-        .get(&url)
+        .get(format!("http://{}/duplicate-cookies", server.addr()))
         .header(header::COOKIE, "duplicate=same_value")
         .send()
         .await
         .unwrap();
+    assert_eq!(res.status(), wreq::StatusCode::OK);
 
+    let res = client
+        .get(format!("http://{}/no-duplicate-cookies", server.addr()))
+        .send()
+        .await
+        .unwrap();
     assert_eq!(res.status(), wreq::StatusCode::OK);
 }