From 72ff950d910f7983c6b309850ef6fc5d88c5b126 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Tue, 25 Mar 2025 16:34:27 +0530 Subject: [PATCH] fix(relay): avoid override with header passthrough (#4931) The current implementation causes duplicate `Content-Type` headers when users override headers in the UI or use OAuth2 authentication with the agent. Web servers receive multiple `Content-Type` headers which causes undefined behavior and 400 errors for backends that don't accept duplicate headers. This also fixes inconsistent behavior when overriding the `Content-Type` header with custom values (e.g., `application/json;v=2`). While HTTP/1.1 headers are case-insensitive per RFC 7230, inconsistent handling across server implementations can treat differently-cased variations (e.g., "Content-Type" vs "content-type") as distinct headers. HTTP/2 (RFC 7540) mandates converting all header field names to lowercase, which would prevent this issue. This patch removes the automatic content-type header insertion, allowing user-defined headers to take precedence without duplication. The is a temporary workaround until we implement a HTTP/2-compliant solution with proper normalization. This was implemented initially to support moving lower level handling towards the kernel, although since the larger refactor has been slightly deferred in favor of stability, this change is suitable for current state. This will be revisited when we implement HTTP/2 compliant header handling in the kernel layer as part of our upcoming kernel efforts. Use the following request to test this out on Desktop app and Agent and override `Content-Type` header to `application/json;=v2`: ``` curl --request POST \ --url 'https://echo.qubit.codes/?qp=1' \ --header 'Content-Type: application/json;v=2' \ --data '{ "test-key": "test-value" }' ``` --- .../hoppscotch-agent/src-tauri/Cargo.lock | 4 +- .../plugin-workspace/relay/src/content.rs | 40 ++++++++++--------- .../plugin-workspace/relay/src/header.rs | 8 ---- .../tauri-plugin-relay/Cargo.lock | 2 +- .../hoppscotch-desktop/src-tauri/Cargo.lock | 4 +- pnpm-lock.yaml | 8 ++-- 6 files changed, 30 insertions(+), 36 deletions(-) diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.lock b/packages/hoppscotch-agent/src-tauri/Cargo.lock index c92ab37d..61e81672 100644 --- a/packages/hoppscotch-agent/src-tauri/Cargo.lock +++ b/packages/hoppscotch-agent/src-tauri/Cargo.lock @@ -4098,7 +4098,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relay" version = "0.1.1" -source = "git+https://github.com/CuriousCorrelation/relay.git#78f2360c022e974c34413d68355dcbc625ba1acd" +source = "git+https://github.com/CuriousCorrelation/relay.git#893cec31865dc396a3d351781ec39b7625f59862" dependencies = [ "bytes", "curl", @@ -6209,7 +6209,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/packages/hoppscotch-desktop/plugin-workspace/relay/src/content.rs b/packages/hoppscotch-desktop/plugin-workspace/relay/src/content.rs index 5ae5ecf2..b42a7d6c 100644 --- a/packages/hoppscotch-desktop/plugin-workspace/relay/src/content.rs +++ b/packages/hoppscotch-desktop/plugin-workspace/relay/src/content.rs @@ -109,10 +109,10 @@ impl<'a> ContentHandler<'a> { } fn set_text_content(&mut self, content: &str, media_type: &MediaType) -> Result<()> { - let mut headers = HashMap::new(); - headers.insert("content-type".to_string(), media_type.to_string()); - - self.merge_headers(headers); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // let mut headers = HashMap::new(); + // headers.insert("content-type".to_string(), media_type.to_string()); + // self.merge_headers(headers); self.handle .post_fields_copy(content.as_bytes()) @@ -141,10 +141,10 @@ impl<'a> ContentHandler<'a> { } })?; - let mut headers = HashMap::new(); - headers.insert("content-type".to_string(), media_type.to_string()); - - self.merge_headers(headers); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // let mut headers = HashMap::new(); + // headers.insert("content-type".to_string(), media_type.to_string()); + // self.merge_headers(headers); self.handle .post_fields_copy(json_str.as_bytes()) @@ -174,16 +174,18 @@ impl<'a> ContentHandler<'a> { .and_then(|n| n.to_str()) .unwrap_or(name); - headers.insert("content-type".to_string(), media_type.to_string()); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // headers.insert("content-type".to_string(), media_type.to_string()); headers.insert( "Content-Disposition".to_string(), format!("attachment; filename=\"{}\"", safe_name), ); } else { - headers.insert("content-type".to_string(), media_type.to_string()); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // headers.insert("content-type".to_string(), media_type.to_string()); } - self.merge_headers(headers); + // self.merge_headers(headers); self.handle.post_fields_copy(content).map_err(|e| { tracing::error!(error = %e, "Failed to set binary content"); @@ -202,10 +204,10 @@ impl<'a> ContentHandler<'a> { content: &IndexMap>, media_type: &MediaType, ) -> Result<()> { - let mut headers = HashMap::new(); - headers.insert("content-type".to_string(), media_type.to_string()); - - self.merge_headers(headers); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // let mut headers = HashMap::new(); + // headers.insert("content-type".to_string(), media_type.to_string()); + // self.merge_headers(headers); let mut form = curl::easy::Form::new(); @@ -282,10 +284,10 @@ impl<'a> ContentHandler<'a> { } fn set_urlencoded_content(&mut self, content: &String, media_type: &MediaType) -> Result<()> { - let mut headers = HashMap::new(); - headers.insert("content-type".to_string(), media_type.to_string()); - - self.merge_headers(headers); + /* TODO: Look into reintroducing this when auth handling is done by kernel */ + // let mut headers = HashMap::new(); + // headers.insert("content-type".to_string(), media_type.to_string()); + // self.merge_headers(headers); tracing::debug!(content_length = content.len(), "URL-encoded form data"); diff --git a/packages/hoppscotch-desktop/plugin-workspace/relay/src/header.rs b/packages/hoppscotch-desktop/plugin-workspace/relay/src/header.rs index 522eda71..b9a354de 100644 --- a/packages/hoppscotch-desktop/plugin-workspace/relay/src/header.rs +++ b/packages/hoppscotch-desktop/plugin-workspace/relay/src/header.rs @@ -66,12 +66,4 @@ impl<'a> HeadersBuilder<'a> { } }) } - - #[tracing::instrument(skip(self), level = "debug")] - pub(crate) fn add_content_type(&mut self, content_type: &str) -> Result<()> { - tracing::info!(content_type = %content_type, "Adding content-type header"); - let mut headers = HashMap::new(); - headers.insert("Content-Type".to_string(), content_type.to_string()); - self.add_headers(Some(&headers)) - } } diff --git a/packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock b/packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock index f3527e42..2c032c88 100644 --- a/packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock +++ b/packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock @@ -2888,7 +2888,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relay" version = "0.1.1" -source = "git+https://github.com/CuriousCorrelation/relay.git#78f2360c022e974c34413d68355dcbc625ba1acd" +source = "git+https://github.com/CuriousCorrelation/relay.git#893cec31865dc396a3d351781ec39b7625f59862" dependencies = [ "bytes", "curl", diff --git a/packages/hoppscotch-desktop/src-tauri/Cargo.lock b/packages/hoppscotch-desktop/src-tauri/Cargo.lock index 7f217fef..d69b4098 100644 --- a/packages/hoppscotch-desktop/src-tauri/Cargo.lock +++ b/packages/hoppscotch-desktop/src-tauri/Cargo.lock @@ -3956,7 +3956,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relay" version = "0.1.1" -source = "git+https://github.com/CuriousCorrelation/relay.git#78f2360c022e974c34413d68355dcbc625ba1acd" +source = "git+https://github.com/CuriousCorrelation/relay.git#893cec31865dc396a3d351781ec39b7625f59862" dependencies = [ "bytes", "curl", @@ -5082,7 +5082,7 @@ dependencies = [ [[package]] name = "tauri-plugin-relay" version = "0.1.0" -source = "git+https://github.com/CuriousCorrelation/tauri-plugin-relay#86e1ccf327e60442c6ae9c2911d9359bf9e32c1b" +source = "git+https://github.com/CuriousCorrelation/tauri-plugin-relay#fee58601fb4b0c129b5a9d87aaa0f9f87c26cd09" dependencies = [ "relay", "serde", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fce1faca..10563f78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1180,7 +1180,7 @@ importers: dependencies: '@hoppscotch/plugin-relay': specifier: github:CuriousCorrelation/tauri-plugin-relay - version: '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/86e1ccf327e60442c6ae9c2911d9359bf9e32c1b' + version: '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/fee58601fb4b0c129b5a9d87aaa0f9f87c26cd09' '@tauri-apps/api': specifier: 2.1.1 version: 2.1.1 @@ -1809,8 +1809,8 @@ packages: resolution: {tarball: https://codeload.github.com/CuriousCorrelation/tauri-plugin-appload/tar.gz/1c2e8b19db7f1b6af6d00abb907f15cdc2017298} version: 0.1.0 - '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/86e1ccf327e60442c6ae9c2911d9359bf9e32c1b': - resolution: {tarball: https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/86e1ccf327e60442c6ae9c2911d9359bf9e32c1b} + '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/fee58601fb4b0c129b5a9d87aaa0f9f87c26cd09': + resolution: {tarball: https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/fee58601fb4b0c129b5a9d87aaa0f9f87c26cd09} version: 0.1.0 '@alloc/quick-lru@5.2.0': @@ -13213,7 +13213,7 @@ snapshots: dependencies: '@tauri-apps/api': 2.1.1 - '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/86e1ccf327e60442c6ae9c2911d9359bf9e32c1b': + '@CuriousCorrelation/plugin-relay@https://codeload.github.com/CuriousCorrelation/tauri-plugin-relay/tar.gz/fee58601fb4b0c129b5a9d87aaa0f9f87c26cd09': dependencies: '@tauri-apps/api': 2.1.1