1use core::future::Future;
4
5use super::{HeaderName, MediaType, ResponseWriter, StatusCode, TransportRequest};
6
7pub const MAX_INFORMATIONAL_RESPONSES: u8 = 8;
9pub const MAX_RESPONSE_CHUNKS: usize = 4_096;
11pub const MAX_RAW_RESPONSE_BODY_BYTES: usize = 64 * 1024 * 1024;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum ResponseMediaPolicy<'a> {
17 Required(&'a [MediaType<'a>]),
19 Optional(&'a [MediaType<'a>]),
21 Forbidden,
23}
24
25#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub enum TrailerPolicy {
28 Reject,
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum RawResponsePolicyError {
35 InformationalLimitTooLarge,
37 BodyLimitTooLarge,
39 MissingMediaType,
41 DuplicateMediaType,
43 TooManyAdmittedHeaders,
45 DuplicateAdmittedHeader,
47 UnsafeAdmittedHeader,
49 ForbiddenMediaHasBodyLimit,
51}
52
53impl_static_error!(RawResponsePolicyError,
54 Self::InformationalLimitTooLarge => "informational response limit is too large",
55 Self::BodyLimitTooLarge => "raw response body limit is too large",
56 Self::MissingMediaType => "response media policy has no accepted media type",
57 Self::DuplicateMediaType => "response media policy contains duplicate media types",
58 Self::TooManyAdmittedHeaders => "too many response headers are admitted",
59 Self::DuplicateAdmittedHeader => "an admitted response header is duplicated",
60 Self::UnsafeAdmittedHeader => "an unsafe response header was admitted",
61 Self::ForbiddenMediaHasBodyLimit => "forbidden response media has a nonzero body limit",
62);
63
64#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct RawResponsePolicy<'a> {
67 success_body_bytes: usize,
68 error_body_bytes: usize,
69 success_media: ResponseMediaPolicy<'a>,
70 error_media: ResponseMediaPolicy<'a>,
71 admitted_headers: [Option<HeaderName<'a>>; super::MAX_RESPONSE_HEADERS],
72 admitted_header_count: usize,
73 informational_limit: u8,
74 trailer_policy: TrailerPolicy,
75}
76
77impl<'a> RawResponsePolicy<'a> {
78 pub fn new(
80 success_body_bytes: usize,
81 error_body_bytes: usize,
82 success_media: ResponseMediaPolicy<'a>,
83 error_media: ResponseMediaPolicy<'a>,
84 admitted_headers: &[HeaderName<'a>],
85 informational_limit: u8,
86 ) -> Result<Self, RawResponsePolicyError> {
87 if success_body_bytes > MAX_RAW_RESPONSE_BODY_BYTES
88 || error_body_bytes > MAX_RAW_RESPONSE_BODY_BYTES
89 {
90 return Err(RawResponsePolicyError::BodyLimitTooLarge);
91 }
92 if informational_limit > MAX_INFORMATIONAL_RESPONSES {
93 return Err(RawResponsePolicyError::InformationalLimitTooLarge);
94 }
95 validate_media(success_media)?;
96 validate_media(error_media)?;
97 if (matches!(success_media, ResponseMediaPolicy::Forbidden) && success_body_bytes != 0)
98 || (matches!(error_media, ResponseMediaPolicy::Forbidden) && error_body_bytes != 0)
99 {
100 return Err(RawResponsePolicyError::ForbiddenMediaHasBodyLimit);
101 }
102 validate_headers(admitted_headers)?;
103 let mut owned_headers = [None; super::MAX_RESPONSE_HEADERS];
104 for (index, header) in admitted_headers.iter().copied().enumerate() {
105 if let Some(slot) = owned_headers.get_mut(index) {
106 *slot = Some(header);
107 }
108 }
109 Ok(Self {
110 success_body_bytes,
111 error_body_bytes,
112 success_media,
113 error_media,
114 admitted_headers: owned_headers,
115 admitted_header_count: admitted_headers.len(),
116 informational_limit,
117 trailer_policy: TrailerPolicy::Reject,
118 })
119 }
120
121 #[must_use]
123 pub const fn body_limit(self, status: StatusCode) -> usize {
124 if status.is_success() {
125 self.success_body_bytes
126 } else {
127 self.error_body_bytes
128 }
129 }
130
131 #[must_use]
133 pub const fn max_body_bytes(self) -> usize {
134 if self.success_body_bytes > self.error_body_bytes {
135 self.success_body_bytes
136 } else {
137 self.error_body_bytes
138 }
139 }
140
141 #[must_use]
143 pub const fn media_policy(self, status: StatusCode) -> ResponseMediaPolicy<'a> {
144 if status.is_success() {
145 self.success_media
146 } else {
147 self.error_media
148 }
149 }
150
151 #[must_use]
153 pub fn admits_header(self, name: &str) -> bool {
154 self.admitted_headers
155 .iter()
156 .take(self.admitted_header_count)
157 .flatten()
158 .any(|candidate| candidate.eq_ignore_ascii_case(name))
159 }
160
161 #[must_use]
163 pub const fn informational_limit(self) -> u8 {
164 self.informational_limit
165 }
166
167 #[must_use]
169 pub const fn trailer_policy(self) -> TrailerPolicy {
170 self.trailer_policy
171 }
172}
173
174#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum InformationalResponseError {
177 SwitchingProtocols,
179 TooManyInformationalResponses,
181 MissingFinalResponse,
183}
184
185impl_static_error!(InformationalResponseError,
186 Self::SwitchingProtocols => "switching protocols is forbidden",
187 Self::TooManyInformationalResponses => "too many informational responses",
188 Self::MissingFinalResponse => "final HTTP response is missing",
189);
190
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193pub struct InformationalResponseTracker {
194 limit: u8,
195 observed: u8,
196}
197
198impl InformationalResponseTracker {
199 #[must_use]
201 pub const fn new(policy: RawResponsePolicy<'_>) -> Self {
202 Self {
203 limit: policy.informational_limit,
204 observed: 0,
205 }
206 }
207
208 pub fn observe(&mut self, status: StatusCode) -> Result<bool, InformationalResponseError> {
210 if status.get() == 101 {
211 return Err(InformationalResponseError::SwitchingProtocols);
212 }
213 if status.get() < 200 {
214 self.observed = self
215 .observed
216 .checked_add(1)
217 .ok_or(InformationalResponseError::TooManyInformationalResponses)?;
218 if self.observed > self.limit {
219 return Err(InformationalResponseError::TooManyInformationalResponses);
220 }
221 return Ok(false);
222 }
223 Ok(true)
224 }
225
226 #[must_use]
228 pub const fn observed(self) -> u8 {
229 self.observed
230 }
231}
232
233pub trait BlockingRawHttpExecutor {
235 type Error;
237
238 fn execute(
243 &self,
244 request: TransportRequest<'_>,
245 policy: RawResponsePolicy<'_>,
246 response: &mut ResponseWriter<'_>,
247 ) -> Result<(), Self::Error>;
248}
249
250pub trait AsyncRawHttpExecutor {
252 type Error;
254
255 fn execute<'executor, 'request, 'policy, 'writer>(
261 &'executor self,
262 request: TransportRequest<'request>,
263 policy: RawResponsePolicy<'policy>,
264 response: &'writer mut ResponseWriter<'_>,
265 ) -> impl Future<Output = Result<(), Self::Error>> + Send + 'writer
266 where
267 'executor: 'writer,
268 'request: 'writer,
269 'policy: 'writer;
270}
271
272fn validate_media(policy: ResponseMediaPolicy<'_>) -> Result<(), RawResponsePolicyError> {
273 let media = match policy {
274 ResponseMediaPolicy::Required(media) | ResponseMediaPolicy::Optional(media) => media,
275 ResponseMediaPolicy::Forbidden => return Ok(()),
276 };
277 if media.is_empty() {
278 return Err(RawResponsePolicyError::MissingMediaType);
279 }
280 for (index, value) in media.iter().enumerate() {
281 if media.get(..index).is_some_and(|seen| {
282 seen.iter()
283 .any(|candidate| candidate.as_str().eq_ignore_ascii_case(value.as_str()))
284 }) {
285 return Err(RawResponsePolicyError::DuplicateMediaType);
286 }
287 }
288 Ok(())
289}
290
291fn validate_headers(headers: &[HeaderName<'_>]) -> Result<(), RawResponsePolicyError> {
292 if headers.len() > super::MAX_RESPONSE_HEADERS {
293 return Err(RawResponsePolicyError::TooManyAdmittedHeaders);
294 }
295 for (index, header) in headers.iter().enumerate() {
296 if is_unsafe_response_header(header.as_str()) {
297 return Err(RawResponsePolicyError::UnsafeAdmittedHeader);
298 }
299 if headers.get(..index).is_some_and(|seen| {
300 seen.iter()
301 .any(|candidate| candidate.eq_ignore_ascii_case(header.as_str()))
302 }) {
303 return Err(RawResponsePolicyError::DuplicateAdmittedHeader);
304 }
305 }
306 Ok(())
307}
308
309fn is_unsafe_response_header(name: &str) -> bool {
310 [
311 "authorization",
312 "connection",
313 "cookie",
314 "proxy-authenticate",
315 "proxy-authorization",
316 "set-cookie",
317 "te",
318 "trailer",
319 "transfer-encoding",
320 "upgrade",
321 ]
322 .iter()
323 .any(|candidate| name.eq_ignore_ascii_case(candidate))
324}
325
326#[cfg(test)]
327mod tests {
328 use super::{
329 InformationalResponseError, InformationalResponseTracker, RawResponsePolicy,
330 RawResponsePolicyError, ResponseMediaPolicy,
331 };
332 use crate::transport::{HeaderName, MediaType, StatusCode};
333
334 fn policy(limit: u8) -> Result<RawResponsePolicy<'static>, RawResponsePolicyError> {
335 RawResponsePolicy::new(
336 1024,
337 256,
338 ResponseMediaPolicy::Required(&[MediaType::JSON]),
339 ResponseMediaPolicy::Optional(&[MediaType::JSON]),
340 &[],
341 limit,
342 )
343 }
344
345 #[test]
346 fn selects_independent_success_and_error_limits() {
347 let Ok(policy) = policy(2) else {
348 return;
349 };
350 assert_eq!(policy.body_limit(StatusCode::OK), 1024);
351 assert_eq!(
352 policy.body_limit(StatusCode::new(400).unwrap_or(StatusCode::TOO_MANY_REQUESTS)),
353 256
354 );
355 }
356
357 #[test]
358 fn bounds_informationals_and_rejects_switching_protocols() {
359 let Ok(policy) = policy(2) else {
360 return;
361 };
362 let mut tracker = InformationalResponseTracker::new(policy);
363 let early = StatusCode::new(103).unwrap_or(StatusCode::OK);
364 assert_eq!(tracker.observe(early), Ok(false));
365 assert_eq!(tracker.observe(early), Ok(false));
366 assert_eq!(
367 tracker.observe(early),
368 Err(InformationalResponseError::TooManyInformationalResponses)
369 );
370 let switching = StatusCode::new(101).unwrap_or(StatusCode::OK);
371 assert_eq!(
372 InformationalResponseTracker::new(policy).observe(switching),
373 Err(InformationalResponseError::SwitchingProtocols)
374 );
375 }
376
377 #[test]
378 fn rejects_unsafe_and_duplicate_admitted_headers() {
379 let unsafe_header = HeaderName::new("set-cookie");
380 assert!(unsafe_header.is_ok());
381 if let Ok(unsafe_header) = unsafe_header {
382 assert!(matches!(
383 RawResponsePolicy::new(
384 1,
385 1,
386 ResponseMediaPolicy::Optional(&[MediaType::JSON]),
387 ResponseMediaPolicy::Optional(&[MediaType::JSON]),
388 &[unsafe_header],
389 0,
390 ),
391 Err(RawResponsePolicyError::UnsafeAdmittedHeader)
392 ));
393 }
394 let first = HeaderName::new("x-request-id");
395 let second = HeaderName::new("X-Request-ID");
396 assert!(first.is_ok() && second.is_ok());
397 if let (Ok(first), Ok(second)) = (first, second) {
398 assert!(matches!(
399 RawResponsePolicy::new(
400 1,
401 1,
402 ResponseMediaPolicy::Optional(&[MediaType::JSON]),
403 ResponseMediaPolicy::Optional(&[MediaType::JSON]),
404 &[first, second],
405 0,
406 ),
407 Err(RawResponsePolicyError::DuplicateAdmittedHeader)
408 ));
409 }
410 }
411}