tinc/private/http/
body.rs

1use std::str::FromStr;
2
3use axum::response::IntoResponse;
4use bytes::Buf;
5use http_body_util::BodyExt;
6
7use crate::__private::{
8    BytesLikeTracker, HttpErrorResponse, HttpErrorResponseCode, OptionalTracker, PrimitiveTracker, Tracker,
9    TrackerDeserializer, TrackerSharedState, deserialize_tracker_target,
10};
11
12pub async fn deserialize_body_json<T, B>(
13    parts: &http::request::Parts,
14    body: B,
15    tracker: &mut T,
16    target: &mut T::Target,
17    state: &mut TrackerSharedState,
18) -> Result<(), axum::response::Response>
19where
20    T: for<'de> TrackerDeserializer<'de>,
21    B: http_body::Body,
22    B::Error: std::fmt::Display,
23{
24    let Some(content_type) = parts.headers.get(http::header::CONTENT_TYPE) else {
25        return Ok(());
26    };
27
28    let content_type = content_type.to_str().map_err(|_| {
29        HttpErrorResponse {
30            code: HttpErrorResponseCode::InvalidArgument,
31            details: Default::default(),
32            message: "content-type header is not valid utf-8",
33        }
34        .into_response()
35    })?;
36
37    let content_type = mediatype::MediaTypeBuf::from_str(content_type).map_err(|err| {
38        HttpErrorResponse {
39            code: HttpErrorResponseCode::InvalidArgument,
40            details: Default::default(),
41            message: &format!("content-type header is not valid: {err}"),
42        }
43        .into_response()
44    })?;
45
46    if content_type.essence() != mediatype::media_type!(APPLICATION / JSON) {
47        return Err(HttpErrorResponse {
48            code: HttpErrorResponseCode::InvalidArgument,
49            details: Default::default(),
50            message: "content-type header is not application/json",
51        }
52        .into_response());
53    }
54
55    let body = body
56        .collect()
57        .await
58        .map_err(|err| {
59            HttpErrorResponse {
60                code: HttpErrorResponseCode::InvalidArgument,
61                details: Default::default(),
62                message: &format!("failed to read body: {err}"),
63            }
64            .into_response()
65        })?
66        .aggregate();
67
68    let mut de = serde_json::Deserializer::from_reader(body.reader());
69
70    if let Err(err) = deserialize_tracker_target(state, &mut de, tracker, target) {
71        return Err(HttpErrorResponse {
72            code: HttpErrorResponseCode::InvalidArgument,
73            details: Default::default(),
74            message: &format!("failed to deserialize body: {err}"),
75        }
76        .into_response());
77    }
78
79    Ok(())
80}
81
82impl<T> BytesLikeTracker for OptionalTracker<T>
83where
84    T: BytesLikeTracker + Default,
85    T::Target: Default,
86{
87    fn set_target(&mut self, target: &mut Self::Target, buf: impl Buf) {
88        self.0.get_or_insert_default().set_target(target.get_or_insert_default(), buf);
89    }
90}
91
92pub async fn deserialize_body_bytes<T, B>(
93    _: &http::request::Parts,
94    body: B,
95    tracker: &mut T,
96    target: &mut T::Target,
97    _: &mut TrackerSharedState,
98) -> Result<(), axum::response::Response>
99where
100    T: BytesLikeTracker,
101    B: http_body::Body,
102    B::Error: std::fmt::Debug,
103{
104    let buf = body
105        .collect()
106        .await
107        .map_err(|err| {
108            HttpErrorResponse {
109                code: HttpErrorResponseCode::InvalidArgument,
110                details: Default::default(),
111                message: &format!("failed to read body: {err:?}"),
112            }
113            .into_response()
114        })?
115        .aggregate();
116
117    tracker.set_target(target, buf);
118
119    Ok(())
120}
121
122pub trait TextLikeTracker: Tracker {
123    fn set_target(&mut self, target: &mut Self::Target, body: String);
124}
125
126impl<T> TextLikeTracker for OptionalTracker<T>
127where
128    T: TextLikeTracker + Default,
129    T::Target: Default,
130{
131    fn set_target(&mut self, target: &mut Self::Target, body: String) {
132        self.0
133            .get_or_insert_default()
134            .set_target(target.get_or_insert_default(), body);
135    }
136}
137
138impl TextLikeTracker for PrimitiveTracker<String> {
139    fn set_target(&mut self, target: &mut Self::Target, body: String) {
140        *target = body;
141    }
142}
143
144pub async fn deserialize_body_text<T, B>(
145    _: &http::request::Parts,
146    body: B,
147    tracker: &mut T,
148    target: &mut T::Target,
149    _: &mut TrackerSharedState,
150) -> Result<(), axum::response::Response>
151where
152    T: TextLikeTracker,
153    B: http_body::Body,
154    B::Error: std::fmt::Debug,
155{
156    let mut buf = body
157        .collect()
158        .await
159        .map_err(|err| {
160            HttpErrorResponse {
161                code: HttpErrorResponseCode::InvalidArgument,
162                details: Default::default(),
163                message: &format!("failed to read body: {err:?}"),
164            }
165            .into_response()
166        })?
167        .aggregate();
168
169    let mut vec = vec![0; buf.remaining()];
170    buf.copy_to_slice(&mut vec);
171
172    let string = String::from_utf8(vec).map_err(|err| {
173        HttpErrorResponse {
174            code: HttpErrorResponseCode::InvalidArgument,
175            details: Default::default(),
176            message: &format!("failed to read body: {err:?}"),
177        }
178        .into_response()
179    })?;
180
181    tracker.set_target(target, string);
182
183    Ok(())
184}