commit 4c21931f9204c9c634c046f074a379f8abf03896
parent a9326b1c867940da4dba77c81ff81ba52630a113
Author: Antoine A <>
Date: Tue, 29 Sep 2026 18:33:29 +0200
common: HTTP spec fixes
Diffstat:
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))
}
}