use super::*;
fn take_recycled_buffer() -> Option<Vec<u8>> {
BODY_BUFFER_POOL.with(|pool: &RefCell<Vec<Vec<u8>>>| pool.borrow_mut().pop())
}
fn return_recycled_buffer(buffer: Vec<u8>) {
if buffer.capacity() > MAX_POOLED_BUFFER_CAPACITY {
return;
}
BODY_BUFFER_POOL.with(|pool: &RefCell<Vec<Vec<u8>>>| {
let mut pool: RefMut<'_, Vec<Vec<u8>>> = pool.borrow_mut();
if pool.len() < MAX_POOLED_BUFFERS {
pool.push(buffer);
}
});
}
pub(crate) async fn pump_response<F>(
framer: &mut F,
reader: DuplexStream,
max_head_size: usize,
is_head: bool,
) -> Result<(), ResponseError>
where
F: ResponseFramer,
{
let mut chunk: Vec<u8> = match take_recycled_buffer() {
Some(mut buffer) => {
buffer.clear();
buffer.resize(PROTOCOL_BODY_CHUNK, 0);
buffer
}
None => vec![0; PROTOCOL_BODY_CHUNK],
};
let outcome: Result<(), ResponseError> =
pump_response_inner(framer, reader, max_head_size, is_head, &mut chunk).await;
return_recycled_buffer(chunk);
outcome
}
async fn pump_response_inner<F>(
framer: &mut F,
mut reader: DuplexStream,
max_head_size: usize,
is_head: bool,
read_chunk: &mut [u8],
) -> Result<(), ResponseError>
where
F: ResponseFramer,
{
let mut pending: Vec<u8> = Vec::new();
let mut head: Option<FramedHead> = None;
let mut body_len: usize = 0;
let mut suppress_body: bool = is_head;
let mut announced_len: Option<usize> = None;
loop {
let read: usize = tokio::io::AsyncReadExt::read(&mut reader, read_chunk).await?;
if read == 0 {
break;
}
if head.is_none() {
pending.extend_from_slice(&read_chunk[..read]);
let Some(body_start) = find_head_end(&pending) else {
if pending.len() > max_head_size {
return Err(ResponseError::Send(RESPONSE_HEAD_TOO_LONG.to_owned()));
}
continue;
};
if body_start > max_head_size {
return Err(ResponseError::Send(RESPONSE_HEAD_TOO_LONG.to_owned()));
}
let (status_code, mut headers): (usize, ProtocolHeaders) =
parse_response_head(&pending[..body_start])?;
validate_status_code(status_code)?;
if has_transfer_encoding(&headers) {
return Err(ResponseError::Send(
UNSUPPORTED_TRANSFER_ENCODING.to_owned(),
));
}
announced_len = parse_content_length(&headers)?;
suppress_body = is_bodyless_status(status_code, is_head);
if hides_entity_length(status_code, is_head) {
headers.remove(CONTENT_LENGTH);
}
let head_body: &[u8] = if suppress_body {
&[]
} else {
&pending[body_start..]
};
let parsed: FramedHead = FramedHead {
status_code,
headers,
};
framer.send_head(&parsed, suppress_body).await?;
if !head_body.is_empty() {
let overflowed: bool = check_body_budget(announced_len, body_len, head_body.len());
body_len += head_body.len();
if overflowed {
return Err(ResponseError::Send(OVERFLOWING_RESPONSE_BODY.to_owned()));
}
framer.send_data(head_body).await?;
}
head = Some(parsed);
pending.clear();
continue;
}
if suppress_body {
continue;
}
let overflowed: bool = check_body_budget(announced_len, body_len, read);
body_len += read;
if overflowed {
return Err(ResponseError::Send(OVERFLOWING_RESPONSE_BODY.to_owned()));
}
framer.send_data(&read_chunk[..read]).await?;
}
let Some(head) = head else {
return Err(ResponseError::Send(MISSING_RESPONSE_HEAD.to_owned()));
};
if !suppress_body
&& let Some(announced) = announced_len
&& body_len != announced
{
return Err(ResponseError::Send(TRUNCATED_RESPONSE_BODY.to_owned()));
}
framer.finish().await?;
let _framed: FramedHead = head;
let _forwarded: usize = body_len;
Ok(())
}
pub(crate) fn hides_entity_length(status_code: usize, _is_head: bool) -> bool {
status_code < HttpStatus::Ok.code() || status_code == HttpStatus::NoContent.code()
}
pub(crate) fn check_body_budget(
announced_len: Option<usize>,
forwarded: usize,
incoming: usize,
) -> bool {
match announced_len {
Some(announced) => forwarded.saturating_add(incoming) > announced,
None => false,
}
}
pub(crate) fn validate_status_code(status_code: usize) -> Result<(), ResponseError> {
let is_code: bool = (MIN_STATUS_CODE..=MAX_STATUS_CODE).contains(&status_code);
if !is_code {
return Err(ResponseError::Send(INVALID_RESPONSE_STATUS.to_owned()));
}
Ok(())
}