From 8bb9a2da32b2e043e8acbe4da2a9aafb2a1ae4b0 Mon Sep 17 00:00:00 2001 From: vishal-770 Date: Sat, 22 Aug 2026 01:24:22 +0530 Subject: [PATCH 1/2] fix(client): validate response content-type This checks that the response Content-Type begins with application/grpc and returns an error immediately if it does not, avoiding confusing decoding errors. Fixes #2365 --- tests/integration_tests/tests/status.rs | 9 +++------ tonic/src/client/grpc.rs | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/integration_tests/tests/status.rs b/tests/integration_tests/tests/status.rs index 423dfeca1..c3075668c 100644 --- a/tests/integration_tests/tests/status.rs +++ b/tests/integration_tests/tests/status.rs @@ -309,18 +309,15 @@ async fn status_from_server_stream_with_inferred_status() { .await .unwrap(); - let mut stream = client + let error = client .stream_call(InputStream {}) .await - .unwrap() - .into_inner(); + .unwrap_err(); assert_eq!( - stream.message().await.unwrap_err().code(), + error.code(), Code::Unavailable ); - - assert_eq!(stream.message().await.unwrap(), None); } #[tokio::test] diff --git a/tonic/src/client/grpc.rs b/tonic/src/client/grpc.rs index ed5a696c4..b01cc2716 100644 --- a/tonic/src/client/grpc.rs +++ b/tonic/src/client/grpc.rs @@ -363,6 +363,31 @@ impl Grpc { )?; let status_code = response.status(); + + let is_valid_content_type = response + .headers() + .get(http::header::CONTENT_TYPE) + .map(|val| val.as_bytes().starts_with(b"application/grpc")) + .unwrap_or(false); + + if !is_valid_content_type { + let error_msg = format!( + "invalid content-type: {:?} (expected application/grpc)", + response + .headers() + .get(http::header::CONTENT_TYPE) + .map(|v| v.to_str().unwrap_or("")) + .unwrap_or("") + ); + + if let Err(Some(status)) = crate::status::infer_grpc_status(None, status_code) { + // Return the mapped status code but with the custom error message. + return Err(Status::new(status.code(), error_msg)); + } else { + return Err(Status::unknown(error_msg)); + } + } + let trailers_only_status = Status::from_header_map(response.headers()); // We do not need to check for trailers if the `grpc-status` header is present From 79ca27507409e4c576a070374173794344a87a1d Mon Sep 17 00:00:00 2001 From: vishal-770 Date: Sat, 22 Aug 2026 01:36:39 +0530 Subject: [PATCH 2/2] style: run cargo fmt --- tests/integration_tests/tests/status.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/integration_tests/tests/status.rs b/tests/integration_tests/tests/status.rs index c3075668c..193751338 100644 --- a/tests/integration_tests/tests/status.rs +++ b/tests/integration_tests/tests/status.rs @@ -309,15 +309,9 @@ async fn status_from_server_stream_with_inferred_status() { .await .unwrap(); - let error = client - .stream_call(InputStream {}) - .await - .unwrap_err(); + let error = client.stream_call(InputStream {}).await.unwrap_err(); - assert_eq!( - error.code(), - Code::Unavailable - ); + assert_eq!(error.code(), Code::Unavailable); } #[tokio::test]