tinc/private/http/
body.rs1use 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}