use crate::rangeext::RangeExt;
use ehttpd::{
bytes::{Data, DataSliceExt, Source},
error,
error::Error,
http::{Response, ResponseExt},
};
use std::{
fs::File,
io::{Read, Seek, SeekFrom},
ops::{Range, RangeBounds, RangeInclusive},
};
pub trait ResponseRangeExt
where
Self: Sized,
{
fn new_206_partial_content() -> Self;
fn set_accept_ranges_bytes(&mut self);
fn set_accept_ranges_none(&mut self);
fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
where
T: RangeBounds<u64>;
fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
where
T: Into<Data>,
R: RangeBounds<usize>;
fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
where
T: Into<File>,
R: RangeBounds<u64>;
}
impl<const HEADER_SIZE_MAX: usize> ResponseRangeExt for Response<HEADER_SIZE_MAX> {
fn new_206_partial_content() -> Self {
Self::new_status_reason(206, "Partial Content")
}
fn set_accept_ranges_bytes(&mut self) {
self.set_field("Accept-Ranges", "bytes")
}
fn set_accept_ranges_none(&mut self) {
self.set_field("Accept-Ranges", "none")
}
fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
where
T: RangeBounds<u64>,
{
let range = RangeInclusive::from_range_bounds(range, 0, total)
.ok_or_else(|| error!("Range would exceed total limit"))?;
let range_string = format!("{}-{}/{total}", range.start(), range.end());
self.set_field("Content-Range", range_string);
Ok(())
}
fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
where
T: Into<Data>,
R: RangeBounds<usize>,
{
if !self.status.eq(b"206") {
return Err(error!("Response is not a 206 response"));
}
let data: Data = data.into();
let Range { start, end } =
Range::from_range_bounds(range, 0, data.len()).ok_or_else(|| error!("Range would exceed data size"))?;
let subdata = data.subcopy(start..end).expect("range would exceed data size");
self.set_content_range(start as u64..end as u64, data.len() as u64)?;
self.set_body_data(subdata);
Ok(())
}
fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
where
T: Into<File>,
R: RangeBounds<u64>,
{
if !self.status.eq(b"206") {
return Err(error!("Response is not a 206 response"));
}
let mut file = file.into();
let file_size = file.metadata()?.len();
let Range { start, end } =
Range::from_range_bounds(range, 0, file_size).ok_or_else(|| error!("Range would exceed file size"))?;
let len = end.saturating_sub(start);
file.seek(SeekFrom::Start(start))?;
let file = file.take(end.saturating_sub(start));
self.set_content_range(start..end, file_size)?;
self.set_content_length(len);
let source = Source::from_other(file);
self.set_body(source);
Ok(())
}
}