1use std::fs::File;
15use std::io::{self, Read, Seek, SeekFrom};
16
17use crate::primitives::response::FileRange;
18
19#[derive(Debug)]
21pub enum BodySourceError {
22 InvalidRange,
24 AlreadyConsumed,
26}
27
28impl std::fmt::Display for BodySourceError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::InvalidRange => write!(f, "invalid byte range"),
32 Self::AlreadyConsumed => write!(f, "resolved file already consumed"),
33 }
34 }
35}
36
37impl std::error::Error for BodySourceError {}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum BodyKind {
42 Empty,
43 Bytes,
44 FileFull,
45 FileRange,
46}
47
48#[derive(Debug)]
54pub enum BodySource {
55 Empty,
57 Bytes(Vec<u8>),
59 FileFull {
61 file: File,
62 len: u64,
63 mime: &'static str,
64 },
65 FileRange {
67 file: File,
68 range: FileRange,
69 total_len: u64,
70 mime: &'static str,
71 },
72}
73
74impl BodySource {
75 pub fn kind(&self) -> BodyKind {
77 match self {
78 Self::Empty => BodyKind::Empty,
79 Self::Bytes(_) => BodyKind::Bytes,
80 Self::FileFull { .. } => BodyKind::FileFull,
81 Self::FileRange { .. } => BodyKind::FileRange,
82 }
83 }
84
85 pub fn len(&self) -> u64 {
87 match self {
88 Self::Empty => 0,
89 Self::Bytes(b) => b.len() as u64,
90 Self::FileFull { len, .. } => *len,
91 Self::FileRange { range, .. } => range.len(),
92 }
93 }
94
95 pub fn is_empty(&self) -> bool {
97 self.len() == 0
98 }
99
100 pub fn range(&self) -> Option<FileRange> {
102 match self {
103 Self::FileRange { range, .. } => Some(*range),
104 _ => None,
105 }
106 }
107
108 pub fn read_all(&mut self) -> io::Result<Vec<u8>> {
119 match self {
120 Self::Empty => Ok(Vec::new()),
121 Self::Bytes(b) => Ok(b.clone()),
122 Self::FileFull { file, .. } => {
123 let mut buf = Vec::new();
124 file.read_to_end(&mut buf)?;
125 Ok(buf)
126 }
127 Self::FileRange { file, range, .. } => {
128 file.seek(SeekFrom::Start(range.start))?;
129 let len = usize::try_from(range.len()).map_err(|_| {
130 io::Error::new(io::ErrorKind::InvalidInput, "body range too large")
131 })?;
132 let mut buf = vec![0u8; len];
133 file.read_exact(&mut buf)?;
134 Ok(buf)
135 }
136 }
137 }
138
139 pub fn read_all_bounded(&mut self, max_bytes: usize) -> io::Result<Vec<u8>> {
150 match self {
151 Self::Empty => Ok(Vec::new()),
152 Self::Bytes(b) => {
153 let len = b.len().min(max_bytes);
154 Ok(b[..len].to_vec())
155 }
156 Self::FileFull { file, .. } => {
157 let mut buf = vec![0u8; max_bytes];
158 let n = file.read(&mut buf)?;
159 buf.truncate(n);
160 Ok(buf)
161 }
162 Self::FileRange { file, range, .. } => {
163 file.seek(SeekFrom::Start(range.start))?;
164 let len = (range.len() as usize).min(max_bytes);
165 let mut buf = vec![0u8; len];
166 file.read_exact(&mut buf)?;
167 Ok(buf)
168 }
169 }
170 }
171
172 pub fn read_range(&mut self, start: u64, end_inclusive: u64) -> io::Result<Vec<u8>> {
181 if end_inclusive < start {
182 return Ok(Vec::new());
183 }
184 match self {
185 Self::Empty => Ok(Vec::new()),
186 Self::Bytes(b) => {
187 let s: usize = start.try_into().map_err(|_| {
188 io::Error::new(io::ErrorKind::InvalidInput, "start offset too large")
189 })?;
190 let e_plus_1: usize = end_inclusive
191 .checked_add(1)
192 .and_then(|v| v.try_into().ok())
193 .ok_or_else(|| {
194 io::Error::new(io::ErrorKind::InvalidInput, "end offset too large")
195 })?;
196 let e = e_plus_1.min(b.len());
197 if s >= b.len() {
198 return Ok(Vec::new());
199 }
200 Ok(b[s..e].to_vec())
201 }
202 Self::FileFull { file, len, .. } => {
203 if start >= *len {
204 return Err(io::Error::new(
205 io::ErrorKind::InvalidInput,
206 "read start exceeds file length",
207 ));
208 }
209 if end_inclusive >= *len {
210 return Err(io::Error::new(
211 io::ErrorKind::InvalidInput,
212 "read end exceeds file length",
213 ));
214 }
215 let request_len = end_inclusive
216 .checked_sub(start)
217 .and_then(|v| v.checked_add(1))
218 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid range"))?;
219 let effective_len = request_len.min(*len - start);
220 file.seek(SeekFrom::Start(start))?;
221 let effective_len = usize::try_from(effective_len).map_err(|_| {
222 io::Error::new(io::ErrorKind::InvalidInput, "read range too large")
223 })?;
224 let mut buf = vec![0u8; effective_len];
225 file.read_exact(&mut buf)?;
226 Ok(buf)
227 }
228 Self::FileRange { file, range, .. } => {
229 let sub_len = end_inclusive
230 .checked_sub(start)
231 .and_then(|v| v.checked_add(1))
232 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid range"))?;
233 if sub_len > range.len() {
234 return Err(io::Error::new(
235 io::ErrorKind::InvalidInput,
236 "sub-range exceeds body range",
237 ));
238 }
239 let absolute_start = range.start.checked_add(start).ok_or_else(|| {
240 io::Error::new(io::ErrorKind::InvalidInput, "absolute offset overflow")
241 })?;
242 let absolute_end = absolute_start + sub_len - 1;
243 if absolute_end > range.end_inclusive {
244 return Err(io::Error::new(
245 io::ErrorKind::InvalidInput,
246 "sub-range exceeds body range",
247 ));
248 }
249 file.seek(SeekFrom::Start(absolute_start))?;
250 let sub_len = usize::try_from(sub_len).map_err(|_| {
251 io::Error::new(io::ErrorKind::InvalidInput, "sub-range too large")
252 })?;
253 let mut buf = vec![0u8; sub_len];
254 file.read_exact(&mut buf)?;
255 Ok(buf)
256 }
257 }
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use std::fs;
265 use tempfile::TempDir;
266
267 fn make_file(content: &[u8]) -> (TempDir, File) {
268 let tmp = TempDir::new().unwrap();
269 let path = tmp.path().join("test.bin");
270 fs::write(&path, content).unwrap();
271 let file = File::open(&path).unwrap();
272 (tmp, file)
273 }
274
275 #[test]
276 fn empty_body_source() {
277 let mut bs = BodySource::Empty;
278 assert_eq!(bs.kind(), BodyKind::Empty);
279 assert_eq!(bs.len(), 0);
280 assert!(bs.is_empty());
281 assert!(bs.range().is_none());
282 assert_eq!(bs.read_all().unwrap(), Vec::<u8>::new());
283 }
284
285 #[test]
286 fn bytes_body_source() {
287 let mut bs = BodySource::Bytes(b"hello".to_vec());
288 assert_eq!(bs.kind(), BodyKind::Bytes);
289 assert_eq!(bs.len(), 5);
290 assert!(!bs.is_empty());
291 assert_eq!(bs.read_all().unwrap(), b"hello");
292 }
293
294 #[test]
295 fn file_full_body_source() {
296 let (_tmp, file) = make_file(b"hello world");
297 let mut bs = BodySource::FileFull {
298 file,
299 len: 11,
300 mime: "text/plain",
301 };
302 assert_eq!(bs.kind(), BodyKind::FileFull);
303 assert_eq!(bs.len(), 11);
304 assert!(!bs.is_empty());
305 assert!(bs.range().is_none());
306 assert_eq!(bs.read_all().unwrap(), b"hello world");
307 }
308
309 #[test]
310 fn file_range_body_source() {
311 let (_tmp, file) = make_file(b"hello world");
312 let mut bs = BodySource::FileRange {
313 file,
314 range: FileRange::new(0, 4),
315 total_len: 11,
316 mime: "text/plain",
317 };
318 assert_eq!(bs.kind(), BodyKind::FileRange);
319 assert_eq!(bs.len(), 5);
320 assert!(!bs.is_empty());
321 assert_eq!(bs.range(), Some(FileRange::new(0, 4)));
322 assert_eq!(bs.read_all().unwrap(), b"hello");
323 }
324
325 #[test]
326 fn file_range_body_source_middle() {
327 let (_tmp, file) = make_file(b"hello world");
328 let mut bs = BodySource::FileRange {
329 file,
330 range: FileRange::new(6, 10),
331 total_len: 11,
332 mime: "text/plain",
333 };
334 assert_eq!(bs.read_all().unwrap(), b"world");
335 }
336
337 #[test]
338 fn read_range_on_bytes() {
339 let mut bs = BodySource::Bytes(b"abcdef".to_vec());
340 assert_eq!(bs.read_range(1, 3).unwrap(), b"bcd");
341 }
342
343 #[test]
344 fn read_range_on_file_full() {
345 let (_tmp, file) = make_file(b"abcdef");
346 let mut bs = BodySource::FileFull {
347 file,
348 len: 6,
349 mime: "text/plain",
350 };
351 assert_eq!(bs.read_range(2, 4).unwrap(), b"cde");
352 }
353
354 #[test]
355 fn read_range_on_file_range() {
356 let (_tmp, file) = make_file(b"abcdef");
357 let mut bs = BodySource::FileRange {
358 file,
359 range: FileRange::new(1, 4),
360 total_len: 6,
361 mime: "text/plain",
362 };
363 assert_eq!(bs.read_range(1, 2).unwrap(), b"cd");
365 }
366
367 #[test]
368 fn read_range_empty_on_out_of_bounds() {
369 let mut bs = BodySource::Bytes(b"ab".to_vec());
370 assert_eq!(bs.read_range(5, 10).unwrap(), Vec::<u8>::new());
371 }
372
373 #[test]
374 fn read_range_inverted_returns_empty() {
375 let mut bs = BodySource::Bytes(b"ab".to_vec());
376 assert_eq!(bs.read_range(3, 1).unwrap(), Vec::<u8>::new());
377 }
378
379 #[test]
380 fn read_range_file_full_within_bounds() {
381 let (_tmp, file) = make_file(b"hello world");
382 let mut bs = BodySource::FileFull {
383 file,
384 len: 11,
385 mime: "text/plain",
386 };
387 assert_eq!(bs.read_range(0, 4).unwrap(), b"hello");
388 assert_eq!(bs.read_range(6, 10).unwrap(), b"world");
389 }
390
391 #[test]
392 fn read_range_file_full_beyond_eof_rejected() {
393 let (_tmp, file) = make_file(b"hello");
394 let mut bs = BodySource::FileFull {
395 file,
396 len: 5,
397 mime: "text/plain",
398 };
399 let result = bs.read_range(0, 99);
400 assert!(result.is_err());
401 }
402
403 #[test]
404 fn read_range_file_full_start_past_len_rejected() {
405 let (_tmp, file) = make_file(b"hello");
406 let mut bs = BodySource::FileFull {
407 file,
408 len: 5,
409 mime: "text/plain",
410 };
411 let result = bs.read_range(10, 20);
412 assert!(result.is_err());
413 }
414
415 #[test]
416 fn read_range_file_range_within_bounds() {
417 let (_tmp, file) = make_file(b"hello world");
418 let mut bs = BodySource::FileRange {
419 file,
420 range: FileRange::new(0, 4),
421 total_len: 11,
422 mime: "text/plain",
423 };
424 assert_eq!(bs.read_range(0, 2).unwrap(), b"hel");
425 }
426
427 #[test]
428 fn read_range_file_range_beyond_end_rejected() {
429 let (_tmp, file) = make_file(b"hello world");
430 let mut bs = BodySource::FileRange {
431 file,
432 range: FileRange::new(0, 4),
433 total_len: 11,
434 mime: "text/plain",
435 };
436 let result = bs.read_range(0, 10);
437 assert!(result.is_err());
438 }
439
440 #[test]
441 fn read_range_bytes_large_offset_rejected() {
442 let mut bs = BodySource::Bytes(b"test".to_vec());
443 let result = bs.read_range(u64::MAX, u64::MAX);
444 assert!(result.is_err());
445 }
446
447 #[test]
448 fn read_range_file_range_overflow_rejected() {
449 let (_tmp, file) = make_file(b"hello world");
450 let mut bs = BodySource::FileRange {
451 file,
452 range: FileRange::new(u64::MAX - 2, u64::MAX),
453 total_len: 11,
454 mime: "text/plain",
455 };
456 let result = bs.read_range(5, 10);
457 assert!(result.is_err());
458 }
459
460 #[test]
461 fn body_source_error_display() {
462 assert_eq!(
463 BodySourceError::InvalidRange.to_string(),
464 "invalid byte range"
465 );
466 assert_eq!(
467 BodySourceError::AlreadyConsumed.to_string(),
468 "resolved file already consumed"
469 );
470 }
471
472 #[test]
473 fn read_all_bounded_bytes_within_limit() {
474 let mut bs = BodySource::Bytes(b"hello".to_vec());
475 assert_eq!(bs.read_all_bounded(10).unwrap(), b"hello");
476 }
477
478 #[test]
479 fn read_all_bounded_bytes_truncated() {
480 let mut bs = BodySource::Bytes(b"hello".to_vec());
481 assert_eq!(bs.read_all_bounded(3).unwrap(), b"hel");
482 }
483
484 #[test]
485 fn read_all_bounded_bytes_zero_limit() {
486 let mut bs = BodySource::Bytes(b"hello".to_vec());
487 assert_eq!(bs.read_all_bounded(0).unwrap(), b"");
488 }
489
490 #[test]
491 fn read_all_bounded_empty() {
492 let mut bs = BodySource::Empty;
493 assert_eq!(bs.read_all_bounded(100).unwrap(), b"");
494 }
495
496 #[test]
497 fn read_all_bounded_file_full_within_limit() {
498 let (_tmp, file) = make_file(b"hello world");
499 let mut bs = BodySource::FileFull {
500 file,
501 len: 11,
502 mime: "text/plain",
503 };
504 assert_eq!(bs.read_all_bounded(100).unwrap(), b"hello world");
505 }
506
507 #[test]
508 fn read_all_bounded_file_full_truncated() {
509 let (_tmp, file) = make_file(b"hello world");
510 let mut bs = BodySource::FileFull {
511 file,
512 len: 11,
513 mime: "text/plain",
514 };
515 assert_eq!(bs.read_all_bounded(5).unwrap(), b"hello");
516 }
517
518 #[test]
519 fn read_all_bounded_file_range_within_limit() {
520 let (_tmp, file) = make_file(b"hello world");
521 let mut bs = BodySource::FileRange {
522 file,
523 range: FileRange::new(0, 4),
524 total_len: 11,
525 mime: "text/plain",
526 };
527 assert_eq!(bs.read_all_bounded(100).unwrap(), b"hello");
528 }
529
530 #[test]
531 fn read_all_bounded_file_range_truncated() {
532 let (_tmp, file) = make_file(b"hello world");
533 let mut bs = BodySource::FileRange {
534 file,
535 range: FileRange::new(0, 10),
536 total_len: 11,
537 mime: "text/plain",
538 };
539 assert_eq!(bs.read_all_bounded(3).unwrap(), b"hel");
540 }
541}