use ehttpd::bytes::{Data, Source};
use ehttpd::http::{Request, Response};
use ehttpd_range::{RangeRequest, RangeResponse};
use rand::Rng;
use rand::rngs::ThreadRng;
use std::fs::{self, File};
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
use std::{env, thread};
#[derive(Debug, Clone)]
struct FakeData {
data: Arc<[u8]>,
path: Arc<PathBuf>,
}
impl FakeData {
pub fn generate(max: usize, rng: &mut ThreadRng) -> Self {
let mut data = vec![0u8; max];
rng.fill(data.as_mut_slice());
let path = env::temp_dir().join("ehttpd-range.fuzz-file.tmp");
fs::write(&path, &data).expect("failed to create temp file");
eprintln!("Created temp file at {}", path.display());
eprintln!(" - you may need to delete this file manually after fuzzing");
Self { data: Arc::from(data), path: Arc::new(path) }
}
}
impl Drop for FakeData {
fn drop(&mut self) {
if Arc::strong_count(&self.path) == 1 {
match fs::remove_file(self.path.as_ref()) {
Ok(_) => eprintln!("Deleted temp file at {}", self.path.display()),
Err(e) => eprintln!("Failed to delete temp file at {}: {e}", self.path.display()),
}
}
}
}
impl Deref for FakeData {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl From<FakeData> for Data {
fn from(value: FakeData) -> Self {
Data::Heap { data: value.data.clone(), range: 0..value.data.len() }
}
}
#[derive(Debug, Clone)]
struct FakeRequest {
data: FakeData,
start: usize,
end_incl: usize,
}
impl FakeRequest {
pub fn generate(data: &FakeData, rng: &mut ThreadRng) -> Self {
let len_incl = data.len().saturating_sub(1);
let start = rng.gen_range(0..=len_incl);
let end_incl = rng.gen_range(start..=len_incl);
Self { data: data.clone(), start, end_incl }
}
pub fn roundtrip(self) {
let request_data =
format!(concat!("GET / HTTP/1.1\r\n", "Range: bytes={}-{}\r\n", "\r\n"), self.start, self.end_incl);
let mut request_stream = Source::from(request_data);
let request: Request = Request::from_stream(&mut request_stream)
.expect("failed to parse request")
.expect("unexpected empty request");
assert_eq!(request.method.as_ref(), b"GET");
assert_eq!(request.target.as_ref(), b"/");
assert_eq!(request.version.as_ref(), b"HTTP/1.1");
let range = (request.range())
.expect("invalid HTTP range request")
.expect("missing expected range header")
.to_inclusive(0, self.data.len() as u64)
.expect("invalid range in range header");
assert_eq!(range, (self.start as u64)..=(self.end_incl as u64));
let response_data = {
let mut response: Response = RangeResponse::new_206_partial_content();
response.set_accept_ranges_bytes();
let file = File::open(self.data.path.as_ref()).expect("failed to open file");
response
.set_body_file_range(file, (self.start as u64)..=(self.end_incl as u64))
.expect("failed to set range body");
let mut response_data = Vec::new();
response.to_stream(&mut response_data).expect("failed to write range body");
response_data
};
let expected = format!(
concat!(
"HTTP/1.1 206 Partial Content\r\n",
"Accept-Ranges: bytes\r\n",
"Content-Range: bytes {}-{}/{}\r\n",
"Content-Length: {}\r\n",
"\r\n"
),
self.start,
self.end_incl,
self.data.len(),
self.end_incl.saturating_sub(self.start) + 1
);
let mut expected = expected.into_bytes();
expected.extend_from_slice(&self.data[self.start..=self.end_incl]);
assert_eq!(expected, response_data);
}
}
fn thread_main(data: FakeData) {
let mut rng = rand::thread_rng();
loop {
let fake = FakeRequest::generate(&data, &mut rng);
fake.roundtrip();
}
}
fn main() {
const MAX: usize = 512 * 1024 * 1024;
let threadnum = match thread::available_parallelism() {
Ok(threadnum) => usize::from(threadnum),
Err(_) => 1,
};
let mut rng = rand::thread_rng();
let data = FakeData::generate(MAX, &mut rng);
let mut threads = Vec::with_capacity(threadnum);
for _ in 0..threadnum {
let data = data.clone();
let thread = thread::spawn(|| thread_main(data));
threads.push(thread);
}
for thread in threads {
thread.join().expect("thread has panicked");
}
}