taler-rust

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

extract.rs (7079B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use axum::{
     18     body::{Body, Bytes, HttpBody},
     19     extract::{FromRequest, FromRequestParts, Request},
     20     http::{
     21         HeaderMap, StatusCode,
     22         header::{self, CONTENT_TYPE},
     23         request::Parts,
     24     },
     25 };
     26 use http_body_util::BodyExt as _;
     27 use serde::de::DeserializeOwned;
     28 use taler_common::error_code::ErrorCode;
     29 use tracing::trace;
     30 use url::form_urlencoded;
     31 use zlib_rs::{InflateConfig, ReturnCode};
     32 
     33 use crate::{
     34     constants::MAX_BODY_LENGTH,
     35     error::{ApiError, ApiResult, failure, failure_status},
     36 };
     37 
     38 pub async fn decompressed_strict_body(headers: &HeaderMap, body: Body) -> ApiResult<Bytes> {
     39     // Check content type
     40     match headers.get(header::CONTENT_TYPE) {
     41         Some(header) => {
     42             if header.as_bytes() != b"application/json" {
     43                 return Err(failure_status(
     44                     ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
     45                     "Bad Content-Type header",
     46                     StatusCode::UNSUPPORTED_MEDIA_TYPE,
     47                 ));
     48             }
     49         }
     50         None => {
     51             return Err(failure_status(
     52                 ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
     53                 "Missing Content-Type header",
     54                 StatusCode::UNSUPPORTED_MEDIA_TYPE,
     55             ));
     56         }
     57     }
     58 
     59     // Check content length if present and well formed
     60     if let Some(length) = headers
     61         .get(header::CONTENT_LENGTH)
     62         .and_then(|it| it.to_str().ok())
     63         .and_then(|it| it.parse::<usize>().ok())
     64         && length > MAX_BODY_LENGTH
     65     {
     66         return Err(failure(
     67             ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT,
     68             format!("Body is suspiciously big > {MAX_BODY_LENGTH}B"),
     69         ));
     70     }
     71 
     72     // Check compression
     73     let compressed = if let Some(encoding) = headers.get(header::CONTENT_ENCODING) {
     74         if encoding == "deflate" {
     75             true
     76         } else {
     77             return Err(failure_status(
     78                 ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
     79                 format!(
     80                     "Unsupported encoding '{}'",
     81                     String::from_utf8_lossy(encoding.as_bytes())
     82                 ),
     83                 StatusCode::UNSUPPORTED_MEDIA_TYPE,
     84             ));
     85         }
     86     } else {
     87         false
     88     };
     89 
     90     // Buffer body
     91     let body = http_body_util::Limited::new(body, MAX_BODY_LENGTH);
     92     let bytes = match body.collect().await {
     93         Ok(chunks) => chunks.to_bytes(),
     94         Err(it) => match it.downcast::<http_body_util::LengthLimitError>() {
     95             Ok(_) => {
     96                 return Err(failure(
     97                     ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT,
     98                     format!("Body is suspiciously big > {MAX_BODY_LENGTH}B"),
     99                 ));
    100             }
    101             Err(err) => {
    102                 return Err(failure(
    103                     ErrorCode::GENERIC_UNEXPECTED_REQUEST_ERROR,
    104                     format!("Failed to read body: {err}"),
    105                 ));
    106             }
    107         },
    108     };
    109 
    110     let bytes = if compressed {
    111         let mut buf = [0; MAX_BODY_LENGTH];
    112         let (decompressed, code) =
    113             zlib_rs::decompress_slice(&mut buf, &bytes, InflateConfig::default());
    114         match code {
    115             ReturnCode::Ok => Bytes::copy_from_slice(decompressed),
    116             ReturnCode::BufError => {
    117                 return Err(failure(
    118                     ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT,
    119                     format!("Decompressed body is suspiciously big > {MAX_BODY_LENGTH}B"),
    120                 ));
    121             }
    122             _ => {
    123                 return Err(failure(
    124                     ErrorCode::GENERIC_COMPRESSION_INVALID,
    125                     "Failed to decompress body: invalid compression",
    126                 ));
    127             }
    128         }
    129     } else {
    130         bytes
    131     };
    132     trace!(target: "api", "req {}", String::from_utf8_lossy(&bytes));
    133     Ok(bytes)
    134 }
    135 
    136 #[derive(Debug, Clone, Copy, Default)]
    137 #[must_use]
    138 pub struct Req<T>(pub T);
    139 
    140 impl<T, S> FromRequest<S> for Req<T>
    141 where
    142     T: DeserializeOwned,
    143     S: Send + Sync,
    144 {
    145     type Rejection = ApiError;
    146 
    147     async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
    148         let (parts, body) = req.into_parts();
    149         let bytes = decompressed_strict_body(&parts.headers, body).await?;
    150 
    151         Self::try_from(&bytes)
    152     }
    153 }
    154 
    155 impl<T: DeserializeOwned> TryFrom<&Bytes> for Req<T> {
    156     type Error = ApiError;
    157 
    158     fn try_from(value: &Bytes) -> Result<Self, Self::Error> {
    159         let mut de = serde_json::de::Deserializer::from_slice(value);
    160         let parsed = serde_path_to_error::deserialize(&mut de)?;
    161         de.end()
    162             .map_err(|err| failure(ErrorCode::GENERIC_JSON_INVALID, err))?;
    163         Ok(Req(parsed))
    164     }
    165 }
    166 
    167 #[derive(Debug, Clone, Copy, Default)]
    168 #[must_use]
    169 pub struct OptReq<T>(pub Option<T>);
    170 
    171 impl<T, S> FromRequest<S> for OptReq<T>
    172 where
    173     T: DeserializeOwned,
    174     S: Send + Sync,
    175 {
    176     type Rejection = ApiError;
    177 
    178     async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
    179         let (parts, body) = req.into_parts();
    180         if !parts.headers.contains_key(CONTENT_TYPE) && body.size_hint().exact() == Some(0) {
    181             Ok(Self(None))
    182         } else {
    183             let bytes = decompressed_strict_body(&parts.headers, body).await?;
    184             let req = Req::try_from(&bytes)?;
    185             Ok(Self(Some(req.0)))
    186         }
    187     }
    188 }
    189 
    190 #[derive(Debug, Clone, Copy, Default)]
    191 pub struct Path<T: DeserializeOwned + Send>(pub T);
    192 
    193 impl<T: serde::de::DeserializeOwned + Send, S: Sync + Send> FromRequestParts<S> for Path<T> {
    194     type Rejection = ApiError;
    195 
    196     async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
    197         Ok(Self(
    198             axum::extract::Path::from_request_parts(parts, &()).await?.0,
    199         ))
    200     }
    201 }
    202 
    203 #[derive(Debug, Clone, Copy, Default)]
    204 pub struct Query<T>(pub T);
    205 
    206 impl<T: serde::de::DeserializeOwned, S: Sync + Send> FromRequestParts<S> for Query<T> {
    207     type Rejection = ApiError;
    208 
    209     async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
    210         let query = parts.uri.query().unwrap_or_default();
    211         let deserializer =
    212             serde_urlencoded::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
    213         let params = serde_path_to_error::deserialize(deserializer)?;
    214         Ok(Query(params))
    215     }
    216 }