1use std::{pin::pin, task::Poll};
2
3use actix_utils::future::poll_fn;
4use bytes::{Bytes, BytesMut};
5use derive_more::{Display, Error};
6use futures_core::ready;
7
8use super::{BodySize, MessageBody};
9
10pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
33 to_bytes_limited(body, usize::MAX)
34 .await
35 .expect("body should never yield more than usize::MAX bytes")
36}
37
38#[derive(Debug, Display, Error)]
40#[display("limit exceeded while collecting body bytes")]
41#[non_exhaustive]
42pub struct BodyLimitExceeded;
43
44pub async fn to_bytes_limited<B: MessageBody>(
71 body: B,
72 limit: usize,
73) -> Result<Result<Bytes, B::Error>, BodyLimitExceeded> {
74 const INITIAL_ALLOC_BYTES: usize = 32 * 1024;
76
77 let cap = match body.size() {
78 BodySize::None | BodySize::Sized(0) => return Ok(Ok(Bytes::new())),
79 BodySize::Sized(size) if size as usize > limit => return Err(BodyLimitExceeded),
80 BodySize::Sized(size) => (size as usize).min(INITIAL_ALLOC_BYTES),
81 BodySize::Stream => INITIAL_ALLOC_BYTES,
82 };
83
84 let mut exceeded_limit = false;
85 let mut buf = BytesMut::with_capacity(cap);
86
87 let mut body = pin!(body);
88
89 match poll_fn(|cx| loop {
90 let body = body.as_mut();
91
92 match ready!(body.poll_next(cx)) {
93 Some(Ok(bytes)) => {
94 if buf.len() + bytes.len() > limit {
96 exceeded_limit = true;
98 return Poll::Ready(Ok(()));
99 }
100
101 buf.extend_from_slice(&bytes)
102 }
103 None => return Poll::Ready(Ok(())),
104 Some(Err(err)) => return Poll::Ready(Err(err)),
105 }
106 })
107 .await
108 {
109 Err(err) => Ok(Err(err)),
111
112 Ok(()) if exceeded_limit => Err(BodyLimitExceeded),
114
115 Ok(()) => Ok(Ok(buf.freeze())),
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use std::io;
123
124 use futures_util::{stream, StreamExt as _};
125
126 use super::*;
127 use crate::{
128 body::{BodyStream, SizedStream},
129 Error,
130 };
131
132 #[actix_rt::test]
133 async fn to_bytes_complete() {
134 let bytes = to_bytes(()).await.unwrap();
135 assert!(bytes.is_empty());
136
137 let body = Bytes::from_static(b"123");
138 let bytes = to_bytes(body).await.unwrap();
139 assert_eq!(bytes, b"123"[..]);
140 }
141
142 #[actix_rt::test]
143 async fn to_bytes_streams() {
144 let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
145 .map(Ok::<_, Error>);
146 let body = BodyStream::new(stream);
147 let bytes = to_bytes(body).await.unwrap();
148 assert_eq!(bytes, b"123abc"[..]);
149 }
150
151 #[actix_rt::test]
152 async fn to_bytes_limited_complete() {
153 let bytes = to_bytes_limited((), 0).await.unwrap().unwrap();
154 assert!(bytes.is_empty());
155
156 let bytes = to_bytes_limited((), 1).await.unwrap().unwrap();
157 assert!(bytes.is_empty());
158
159 assert!(to_bytes_limited(Bytes::from_static(b"12"), 0)
160 .await
161 .is_err());
162 assert!(to_bytes_limited(Bytes::from_static(b"12"), 1)
163 .await
164 .is_err());
165 assert!(to_bytes_limited(Bytes::from_static(b"12"), 2).await.is_ok());
166 assert!(to_bytes_limited(Bytes::from_static(b"12"), 3).await.is_ok());
167 }
168
169 #[actix_rt::test]
170 async fn to_bytes_limited_streams() {
171 let body = SizedStream::new(8, stream::empty().map(Ok::<_, Error>));
173 assert!(to_bytes_limited(body, 3).await.is_err());
174
175 let body = SizedStream::new(3, stream::empty().map(Ok::<_, Error>));
177 assert!(to_bytes_limited(body, 3).await.unwrap().unwrap().is_empty());
178
179 let stream = stream::iter(vec![Bytes::from_static(b"1234")]).map(Ok::<_, Error>);
181 let body = SizedStream::new(3, stream);
182 assert!(to_bytes_limited(body, 3).await.is_err());
183
184 let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
185 .map(Ok::<_, Error>);
186 let body = BodyStream::new(stream);
187 assert!(to_bytes_limited(body, 3).await.is_err());
188 }
189
190 #[actix_rt::test]
191 async fn to_body_limit_error() {
192 let err_stream = stream::once(async { Err(io::Error::other("")) });
193 let body = SizedStream::new(8, err_stream);
194 assert!(to_bytes_limited(body, 10).await.unwrap().is_err());
196 }
197}