From be92ccf0da84d3797feac00b9586ae8e21243046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 6 Mar 2025 11:24:16 +0000 Subject: [PATCH] bugfix: make sure to correctly decode response content when putting it into error message (#5571) --- Cargo.lock | 9 ++++++--- Cargo.toml | 1 + common/http-api-client/Cargo.toml | 8 +++++++- common/http-api-client/src/lib.rs | 30 ++++++++++++++++++++++++++---- nym-wallet/Cargo.lock | 11 +++++++---- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f75819cd5..277779ad57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2291,9 +2291,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -5767,7 +5767,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "subtle 2.6.1", + "subtle 2.5.0", "thiserror 2.0.11", "time", "tokio", @@ -5841,8 +5841,11 @@ name = "nym-http-api-client" version = "0.1.0" dependencies = [ "async-trait", + "bytes", + "encoding_rs", "hickory-resolver", "http 1.2.0", + "mime", "nym-bin-common", "once_cell", "reqwest 0.12.4", diff --git a/Cargo.toml b/Cargo.toml index 14e86aadfc..5452485c28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -241,6 +241,7 @@ doc-comment = "0.3" dotenvy = "0.15.6" ecdsa = "0.16" ed25519-dalek = "2.1" +encoding_rs = "0.8.35" env_logger = "0.11.6" envy = "0.4" etherparse = "0.13.0" diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index 33ff7bc6f8..79a291e827 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -21,6 +21,12 @@ serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +# used for decoding text responses (they were already implicitly included) +bytes = { workspace = true } +encoding_rs = { workspace = true } +mime = { workspace = true } + + nym-bin-common = { path = "../bin-common" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] @@ -32,4 +38,4 @@ workspace = true features = ["tokio"] [dev-dependencies] -tokio = { workspace = true, features=["rt", "macros"] } +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index a8099ce111..4b2938daa4 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -147,13 +147,13 @@ use thiserror::Error; use tracing::{instrument, warn}; use url::Url; +use http::HeaderMap; +pub use reqwest::IntoUrl; #[cfg(not(target_arch = "wasm32"))] use std::net::SocketAddr; #[cfg(not(target_arch = "wasm32"))] use std::sync::Arc; -pub use reqwest::IntoUrl; - mod user_agent; pub use user_agent::UserAgent; @@ -855,6 +855,26 @@ fn sanitize_url, V: AsRef>( url } +fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String { + use encoding_rs::{Encoding, UTF_8}; + use mime::Mime; + + let content_type = headers + .get(http::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse::().ok()); + + let encoding_name = content_type + .as_ref() + .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) + .unwrap_or("utf-8"); + + let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); + + let (text, _, _) = encoding.decode(bytes); + text.into_owned() +} + /// Attempt to parse a json object from an HTTP response #[instrument(level = "debug", skip_all)] pub async fn parse_response(res: Response, allow_empty: bool) -> Result> @@ -870,6 +890,8 @@ where return Err(HttpClientError::EmptyResponse { status }); } } + let headers = res.headers().clone(); + tracing::trace!("headers: {:?}", headers); if res.status().is_success() { // internally reqwest is first retrieving bytes and then performing parsing via serde_json @@ -878,10 +900,10 @@ where match serde_json::from_slice(&full) { Ok(data) => Ok(data), Err(err) => { - let text = String::from_utf8_lossy(&full); + let content = decode_as_text(&full, headers); Err(HttpClientError::ResponseDecodeFailure { source: err, - content: text.into_owned(), + content, }) } } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 7fb3e6a067..969932de63 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -465,9 +465,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -1487,9 +1487,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -3454,8 +3454,11 @@ name = "nym-http-api-client" version = "0.1.0" dependencies = [ "async-trait", + "bytes", + "encoding_rs", "hickory-resolver", "http 1.1.0", + "mime", "nym-bin-common", "once_cell", "reqwest 0.12.4",