taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 4c21931f9204c9c634c046f074a379f8abf03896
parent a9326b1c867940da4dba77c81ff81ba52630a113
Author: Antoine A <>
Date:   Tue, 29 Sep 2026 18:33:29 +0200

common: HTTP spec fixes

Diffstat:
Mcommon/http-client/src/builder.rs | 8++++++--
Mcommon/taler-api/src/auth.rs | 12++++++++++--
Mcommon/taler-api/src/error.rs | 6++++++
Mcommon/taler-api/src/extract.rs | 5++++-
4 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/common/http-client/src/builder.rs b/common/http-client/src/builder.rs @@ -26,6 +26,7 @@ use http::{ use http_body_util::{BodyDataStream, BodyExt, Full, Limited}; use hyper::{Method, body::Bytes}; use serde::{Serialize, de::DeserializeOwned}; +use serde_path_to_error::Track; use taler_common::encoding::base64; use tracing::{Level, trace}; use url::Url; @@ -283,8 +284,11 @@ impl Res { let str = std::string::String::from_utf8_lossy(&body); trace!(target: "http", "JSON body: {str}"); } - let deserializer = &mut serde_json::Deserializer::from_slice(&body); - let parsed = serde_path_to_error::deserialize(deserializer).map_err(ClientErr::ResJson)?; + let mut de = serde_json::Deserializer::from_slice(&body); + let parsed = serde_path_to_error::deserialize(&mut de).map_err(ClientErr::ResJson)?; + de.end().map_err(|e| { + ClientErr::ResJson(serde_path_to_error::Error::new(Track::new().path(), e)) + })?; Ok(parsed) } diff --git a/common/taler-api/src/auth.rs b/common/taler-api/src/auth.rs @@ -43,7 +43,7 @@ pub struct AuthMiddlewareState { impl AuthMiddlewareState { pub fn new(method: AuthMethod, realm: &str) -> Self { let challenge = match method { - AuthMethod::Basic(_) => format!("Basic realm=\"{realm}\" charset=\"UTF-8\""), + AuthMethod::Basic(_) => format!("Basic realm=\"{realm}\", charset=\"UTF-8\""), AuthMethod::Bearer(_) => format!("Bearer realm=\"{realm}\""), AuthMethod::None => String::new(), }; @@ -84,7 +84,7 @@ pub async fn auth_middleware( .with_header(WWW_AUTHENTICATE, challenge.clone())); }; - if scheme != hscheme { + if !scheme.eq_ignore_ascii_case(hscheme) { return Err(failure( ErrorCode::GENERIC_UNAUTHORIZED, format!("Authorization method '{hscheme}' wrong or not supported"), @@ -92,6 +92,14 @@ pub async fn auth_middleware( .with_header(WWW_AUTHENTICATE, challenge.clone())); } + let parameter = parameter.trim_start_matches(' '); + if parameter.is_empty() { + return Err(failure( + ErrorCode::GENERIC_UNAUTHORIZED, + "Authorization credentials are missing", + ) + .with_header(WWW_AUTHENTICATE, challenge.clone())); + } Ok(parameter) } diff --git a/common/taler-api/src/error.rs b/common/taler-api/src/error.rs @@ -157,6 +157,12 @@ impl From<serde_path_to_error::Error<serde_json::Error>> for ApiError { } } +impl From<serde_json::Error> for ApiError { + fn from(value: serde_json::Error) -> Self { + failure(ErrorCode::GENERIC_JSON_INVALID, &value).with_log(value.to_string()) + } +} + impl From<PathRejection> for ApiError { fn from(value: PathRejection) -> Self { match value { diff --git a/common/taler-api/src/extract.rs b/common/taler-api/src/extract.rs @@ -39,7 +39,7 @@ pub async fn decompressed_strict_body(headers: &HeaderMap, body: Body) -> ApiRes // Check content type match headers.get(header::CONTENT_TYPE) { Some(header) => { - if !header.as_bytes().starts_with(b"application/json") { + if header.as_bytes() != b"application/json" { return Err(failure_status( ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED, "Bad Content-Type header", @@ -147,6 +147,7 @@ where async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> { let (parts, body) = req.into_parts(); let bytes = decompressed_strict_body(&parts.headers, body).await?; + Self::try_from(&bytes) } } @@ -157,6 +158,8 @@ impl<T: DeserializeOwned> TryFrom<&Bytes> for Req<T> { fn try_from(value: &Bytes) -> Result<Self, Self::Error> { let mut de = serde_json::de::Deserializer::from_slice(value); let parsed = serde_path_to_error::deserialize(&mut de)?; + de.end() + .map_err(|err| failure(ErrorCode::GENERIC_JSON_INVALID, err))?; Ok(Req(parsed)) } }