1use crate::storage::{FileStorage, StorageError, StorageResult};
57use axum::{
58 body::Body,
59 extract::{Path, State},
60 http::{
61 header::{
62 ACCEPT_RANGES, CACHE_CONTROL, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, ETAG,
63 IF_NONE_MATCH, IF_RANGE, LAST_MODIFIED, RANGE,
64 },
65 HeaderMap, HeaderValue, StatusCode,
66 },
67 response::{IntoResponse, Response},
68};
69use std::{
70 future::Future,
71 pin::Pin,
72 sync::Arc,
73 time::SystemTime,
74};
75
76pub type FileAccessControl = Arc<
80 dyn Fn(Option<String>, String) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send>>
81 + Send
82 + Sync,
83>;
84
85#[derive(Clone)]
87pub struct FileServingMiddleware<S: FileStorage> {
88 #[allow(dead_code)] storage: Arc<S>,
90 #[allow(dead_code)] access_control: Option<FileAccessControl>,
92 #[allow(dead_code)] cache_max_age: u32,
94 #[allow(dead_code)] enable_cdn_headers: bool,
96}
97
98impl<S: FileStorage> FileServingMiddleware<S> {
99 #[must_use]
101 pub fn new(storage: Arc<S>) -> Self {
102 Self {
103 storage,
104 access_control: None,
105 cache_max_age: 86400, enable_cdn_headers: false,
107 }
108 }
109
110 #[must_use]
112 pub fn with_access_control(mut self, access_control: FileAccessControl) -> Self {
113 self.access_control = Some(access_control);
114 self
115 }
116
117 #[must_use]
119 pub const fn with_cache_max_age(mut self, seconds: u32) -> Self {
120 self.cache_max_age = seconds;
121 self
122 }
123
124 #[must_use]
126 pub const fn with_cdn_headers(mut self) -> Self {
127 self.enable_cdn_headers = true;
128 self
129 }
130}
131
132pub async fn serve_file<S: FileStorage>(
163 State(storage): State<Arc<S>>,
164 Path(file_id): Path<String>,
165 headers: HeaderMap,
166) -> Result<Response, FileServingError> {
167 let metadata = storage
169 .get_metadata(&file_id)
170 .await
171 .map_err(FileServingError::Storage)?;
172
173 let data = storage
175 .retrieve(&file_id)
176 .await
177 .map_err(FileServingError::Storage)?;
178
179 let etag = format!(r#""{}-{}""#, file_id, data.len());
181
182 let content_type = if !metadata.content_type.is_empty()
184 && metadata.content_type != "application/octet-stream"
185 {
186 metadata.content_type
187 } else {
188 mime_guess::from_path(&metadata.filename)
190 .first_or_octet_stream()
191 .to_string()
192 };
193
194 if let Some(if_none_match) = headers.get(IF_NONE_MATCH) {
196 if if_none_match.to_str().is_ok_and(|v| v == etag) {
197 return Ok((StatusCode::NOT_MODIFIED, ()).into_response());
198 }
199 }
200
201 if let Some(range_header) = headers.get(RANGE) {
203 return serve_range_request(&data, range_header, &etag, &content_type, &headers);
204 }
205
206 Ok(build_file_response(data, &etag, &content_type, None))
208}
209
210fn serve_range_request(
212 data: &[u8],
213 range_header: &HeaderValue,
214 etag: &str,
215 content_type: &str,
216 headers: &HeaderMap,
217) -> Result<Response, FileServingError> {
218 let file_size = data.len();
219
220 if let Some(if_range) = headers.get(IF_RANGE) {
222 if if_range.to_str().map_or(true, |v| v != etag) {
223 return Ok(build_file_response(data.to_vec(), etag, content_type, None));
225 }
226 }
227
228 let range_str = range_header
230 .to_str()
231 .map_err(|_| FileServingError::InvalidRange)?;
232
233 if !range_str.starts_with("bytes=") {
234 return Err(FileServingError::InvalidRange);
235 }
236
237 let range_spec = &range_str[6..]; let (start_str, end_str) = range_spec
239 .split_once('-')
240 .ok_or(FileServingError::InvalidRange)?;
241
242 let is_suffix_range = start_str.is_empty();
244
245 let start: usize = if is_suffix_range {
246 let suffix_len: usize = end_str
248 .parse()
249 .map_err(|_| FileServingError::InvalidRange)?;
250 file_size.saturating_sub(suffix_len)
251 } else {
252 start_str
253 .parse()
254 .map_err(|_| FileServingError::InvalidRange)?
255 };
256
257 let end: usize = if is_suffix_range {
258 file_size - 1
260 } else if end_str.is_empty() {
261 file_size - 1
263 } else {
264 end_str
266 .parse::<usize>()
267 .map_err(|_| FileServingError::InvalidRange)?
268 .min(file_size - 1)
269 };
270
271 if start > end || start >= file_size {
273 return Err(FileServingError::RangeNotSatisfiable(file_size));
274 }
275
276 let range_data = data[start..=end].to_vec();
277
278 let content_range = format!("bytes {start}-{end}/{file_size}");
279
280 Ok(build_file_response(
281 range_data,
282 etag,
283 content_type,
284 Some((&content_range, StatusCode::PARTIAL_CONTENT)),
285 ))
286}
287
288fn build_file_response(
290 data: Vec<u8>,
291 etag: &str,
292 content_type: &str,
293 range_info: Option<(&str, StatusCode)>,
294) -> Response {
295 let mut response = Response::builder();
296
297 let status = range_info.map_or(StatusCode::OK, |(_, code)| code);
299 response = response.status(status);
300
301 response = response
303 .header(CONTENT_TYPE, content_type)
304 .header(CONTENT_LENGTH, data.len())
305 .header(ETAG, etag)
306 .header(ACCEPT_RANGES, "bytes");
307
308 if let Some((content_range, _)) = range_info {
310 response = response.header(CONTENT_RANGE, content_range);
311 }
312
313 response = response
315 .header(CACHE_CONTROL, "public, max-age=86400")
316 .header(
317 LAST_MODIFIED,
318 httpdate::fmt_http_date(SystemTime::now()),
319 );
320
321 response
322 .body(Body::from(data))
323 .unwrap_or_else(|_| Response::new(Body::empty()))
324}
325
326#[derive(Debug)]
328pub enum FileServingError {
329 Storage(StorageError),
331 InvalidRange,
333 RangeNotSatisfiable(usize),
335 AccessDenied,
337}
338
339impl std::fmt::Display for FileServingError {
340 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341 match self {
342 Self::Storage(e) => write!(f, "Storage error: {e}"),
343 Self::InvalidRange => write!(f, "Invalid range request"),
344 Self::RangeNotSatisfiable(size) => {
345 write!(f, "Range not satisfiable (file size: {size})")
346 }
347 Self::AccessDenied => write!(f, "Access denied"),
348 }
349 }
350}
351
352impl std::error::Error for FileServingError {}
353
354impl IntoResponse for FileServingError {
355 fn into_response(self) -> Response {
356 let (status, message) = match self {
357 Self::Storage(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
358 Self::InvalidRange => (StatusCode::BAD_REQUEST, self.to_string()),
359 Self::RangeNotSatisfiable(size) => {
360 let response = Response::builder()
361 .status(StatusCode::RANGE_NOT_SATISFIABLE)
362 .header(CONTENT_RANGE, format!("bytes */{size}"))
363 .body(Body::from(self.to_string()))
364 .unwrap_or_else(|_| Response::new(Body::empty()));
365 return response;
366 }
367 Self::AccessDenied => (StatusCode::FORBIDDEN, self.to_string()),
368 };
369
370 (status, message).into_response()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::storage::{LocalFileStorage, UploadedFile};
378 use tempfile::TempDir;
379
380 #[test]
381 fn test_etag_generation() {
382 let file_id = "test-file-123";
383 let data = b"Hello, World!";
384 let etag = format!(r#""{}-{}""#, file_id, data.len());
385 assert_eq!(etag, r#""test-file-123-13""#);
386 }
387
388 #[tokio::test]
389 async fn test_serve_file_uses_stored_content_type() {
390 let temp = TempDir::new().unwrap();
391 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
392
393 let file = UploadedFile::new("document.pdf", "application/pdf", b"fake pdf".to_vec());
395 let stored = storage.store(file).await.unwrap();
396
397 let headers = HeaderMap::new();
399 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
400 .await
401 .unwrap();
402
403 let content_type = response.headers().get(CONTENT_TYPE).unwrap();
405 assert_eq!(content_type, "application/pdf");
406 }
407
408 #[tokio::test]
409 async fn test_serve_file_uses_mime_guess_fallback() {
410 let temp = TempDir::new().unwrap();
411 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
412
413 let file = UploadedFile::new(
415 "image.png",
416 "application/octet-stream",
417 b"fake png".to_vec(),
418 );
419 let stored = storage.store(file).await.unwrap();
420
421 let headers = HeaderMap::new();
423 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
424 .await
425 .unwrap();
426
427 let content_type = response.headers().get(CONTENT_TYPE).unwrap();
429 assert_eq!(content_type, "image/png");
430 }
431
432 #[tokio::test]
433 async fn test_serve_file_preserves_various_content_types() {
434 let temp = TempDir::new().unwrap();
435 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
436
437 let test_cases = vec![
438 ("photo.jpg", "image/jpeg", "image/jpeg"),
439 ("video.mp4", "video/mp4", "video/mp4"),
440 ("data.json", "application/json", "application/json"),
441 ("style.css", "text/css", "text/css"),
442 ("script.js", "application/javascript", "application/javascript"),
443 ];
444
445 for (filename, stored_type, expected_type) in test_cases {
446 let file = UploadedFile::new(filename, stored_type, b"test data".to_vec());
447 let stored = storage.store(file).await.unwrap();
448
449 let headers = HeaderMap::new();
450 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
451 .await
452 .unwrap();
453
454 let content_type = response.headers().get(CONTENT_TYPE).unwrap();
455 assert_eq!(
456 content_type,
457 expected_type,
458 "Content-Type mismatch for {filename}"
459 );
460 }
461 }
462
463 #[tokio::test]
464 async fn test_serve_file_fallback_for_unknown_extension() {
465 let temp = TempDir::new().unwrap();
466 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
467
468 let file = UploadedFile::new(
470 "file.unknownext",
471 "application/octet-stream",
472 b"data".to_vec(),
473 );
474 let stored = storage.store(file).await.unwrap();
475
476 let headers = HeaderMap::new();
477 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
478 .await
479 .unwrap();
480
481 let content_type = response.headers().get(CONTENT_TYPE).unwrap();
483 assert_eq!(content_type, "application/octet-stream");
484 }
485
486 #[tokio::test]
487 async fn test_range_request_full_range() {
488 let temp = TempDir::new().unwrap();
489 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
490
491 let data = (0_u8..=255).cycle().take(1000).collect::<Vec<u8>>();
493 let file = UploadedFile::new("test.bin", "application/octet-stream", data.clone());
494 let stored = storage.store(file).await.unwrap();
495
496 let mut headers = HeaderMap::new();
498 headers.insert(RANGE, HeaderValue::from_static("bytes=100-199"));
499
500 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
501 .await
502 .unwrap();
503
504 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
506
507 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
509 assert_eq!(content_range, "bytes 100-199/1000");
510
511 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
513 assert_eq!(content_length, "100");
514
515 assert!(response.headers().contains_key(ETAG));
517
518 assert_eq!(
520 response.headers().get(ACCEPT_RANGES).unwrap(),
521 "bytes"
522 );
523 }
524
525 #[tokio::test]
526 async fn test_range_request_suffix_range() {
527 let temp = TempDir::new().unwrap();
528 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
529
530 let data = (0_u8..=255).cycle().take(1000).collect::<Vec<u8>>();
532 let file = UploadedFile::new("test.bin", "application/octet-stream", data.clone());
533 let stored = storage.store(file).await.unwrap();
534
535 let mut headers = HeaderMap::new();
537 headers.insert(RANGE, HeaderValue::from_static("bytes=-100"));
538
539 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
540 .await
541 .unwrap();
542
543 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
545
546 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
548 assert_eq!(content_range, "bytes 900-999/1000");
549
550 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
552 assert_eq!(content_length, "100");
553 }
554
555 #[tokio::test]
556 async fn test_range_request_suffix_range_exceeds_file_size() {
557 let temp = TempDir::new().unwrap();
558 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
559
560 let data = vec![42u8; 100];
562 let file = UploadedFile::new("test.bin", "application/octet-stream", data.clone());
563 let stored = storage.store(file).await.unwrap();
564
565 let mut headers = HeaderMap::new();
567 headers.insert(RANGE, HeaderValue::from_static("bytes=-500"));
568
569 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
570 .await
571 .unwrap();
572
573 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
575
576 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
577 assert_eq!(content_range, "bytes 0-99/100");
578 }
579
580 #[tokio::test]
581 async fn test_range_request_open_ended() {
582 let temp = TempDir::new().unwrap();
583 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
584
585 let data = (0_u8..=255).cycle().take(1000).collect::<Vec<u8>>();
587 let file = UploadedFile::new("test.bin", "application/octet-stream", data.clone());
588 let stored = storage.store(file).await.unwrap();
589
590 let mut headers = HeaderMap::new();
592 headers.insert(RANGE, HeaderValue::from_static("bytes=800-"));
593
594 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
595 .await
596 .unwrap();
597
598 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
600
601 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
603 assert_eq!(content_range, "bytes 800-999/1000");
604
605 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
607 assert_eq!(content_length, "200");
608 }
609
610 #[tokio::test]
611 async fn test_range_request_single_byte() {
612 let temp = TempDir::new().unwrap();
613 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
614
615 let data = vec![42u8; 100];
616 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
617 let stored = storage.store(file).await.unwrap();
618
619 let mut headers = HeaderMap::new();
621 headers.insert(RANGE, HeaderValue::from_static("bytes=50-50"));
622
623 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
624 .await
625 .unwrap();
626
627 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
628
629 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
630 assert_eq!(content_range, "bytes 50-50/100");
631
632 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
633 assert_eq!(content_length, "1");
634 }
635
636 #[tokio::test]
637 async fn test_range_request_invalid_format_no_bytes_prefix() {
638 let temp = TempDir::new().unwrap();
639 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
640
641 let data = vec![42u8; 100];
642 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
643 let stored = storage.store(file).await.unwrap();
644
645 let mut headers = HeaderMap::new();
647 headers.insert(RANGE, HeaderValue::from_static("0-99"));
648
649 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
650
651 assert!(response.is_err());
653 let err = response.unwrap_err();
654 assert!(matches!(err, FileServingError::InvalidRange));
655 }
656
657 #[tokio::test]
658 async fn test_range_request_invalid_format_no_dash() {
659 let temp = TempDir::new().unwrap();
660 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
661
662 let data = vec![42u8; 100];
663 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
664 let stored = storage.store(file).await.unwrap();
665
666 let mut headers = HeaderMap::new();
668 headers.insert(RANGE, HeaderValue::from_static("bytes=50"));
669
670 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
671
672 assert!(response.is_err());
673 let err = response.unwrap_err();
674 assert!(matches!(err, FileServingError::InvalidRange));
675 }
676
677 #[tokio::test]
678 async fn test_range_request_invalid_non_numeric() {
679 let temp = TempDir::new().unwrap();
680 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
681
682 let data = vec![42u8; 100];
683 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
684 let stored = storage.store(file).await.unwrap();
685
686 let mut headers = HeaderMap::new();
688 headers.insert(RANGE, HeaderValue::from_static("bytes=abc-def"));
689
690 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
691
692 assert!(response.is_err());
693 let err = response.unwrap_err();
694 assert!(matches!(err, FileServingError::InvalidRange));
695 }
696
697 #[tokio::test]
698 async fn test_range_request_start_greater_than_end() {
699 let temp = TempDir::new().unwrap();
700 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
701
702 let data = vec![42u8; 100];
703 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
704 let stored = storage.store(file).await.unwrap();
705
706 let mut headers = HeaderMap::new();
708 headers.insert(RANGE, HeaderValue::from_static("bytes=50-20"));
709
710 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
711
712 assert!(response.is_err());
714 let err = response.unwrap_err();
715 assert!(matches!(err, FileServingError::RangeNotSatisfiable(100)));
716 }
717
718 #[tokio::test]
719 async fn test_range_request_start_beyond_file_size() {
720 let temp = TempDir::new().unwrap();
721 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
722
723 let data = vec![42u8; 100];
724 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
725 let stored = storage.store(file).await.unwrap();
726
727 let mut headers = HeaderMap::new();
729 headers.insert(RANGE, HeaderValue::from_static("bytes=100-199"));
730
731 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
732
733 assert!(response.is_err());
735 let err = response.unwrap_err();
736 assert!(matches!(err, FileServingError::RangeNotSatisfiable(100)));
737 }
738
739 #[tokio::test]
740 async fn test_range_request_end_exceeds_file_size() {
741 let temp = TempDir::new().unwrap();
742 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
743
744 let data = vec![42u8; 100];
745 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
746 let stored = storage.store(file).await.unwrap();
747
748 let mut headers = HeaderMap::new();
750 headers.insert(RANGE, HeaderValue::from_static("bytes=50-200"));
751
752 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
753 .await
754 .unwrap();
755
756 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
757
758 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
760 assert_eq!(content_range, "bytes 50-99/100");
761
762 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
763 assert_eq!(content_length, "50");
764 }
765
766 #[tokio::test]
767 async fn test_range_request_with_if_range_matching_etag() {
768 let temp = TempDir::new().unwrap();
769 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
770
771 let data = vec![42u8; 100];
772 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
773 let stored = storage.store(file).await.unwrap();
774
775 let headers = HeaderMap::new();
777 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
778 .await
779 .unwrap();
780 let etag = response.headers().get(ETAG).unwrap().clone();
781
782 let mut headers = HeaderMap::new();
784 headers.insert(RANGE, HeaderValue::from_static("bytes=0-49"));
785 headers.insert(IF_RANGE, etag);
786
787 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
788 .await
789 .unwrap();
790
791 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
793 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
794 assert_eq!(content_range, "bytes 0-49/100");
795 }
796
797 #[tokio::test]
798 async fn test_range_request_with_if_range_non_matching_etag() {
799 let temp = TempDir::new().unwrap();
800 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
801
802 let data = vec![42u8; 100];
803 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
804 let stored = storage.store(file).await.unwrap();
805
806 let mut headers = HeaderMap::new();
808 headers.insert(RANGE, HeaderValue::from_static("bytes=0-49"));
809 headers.insert(IF_RANGE, HeaderValue::from_static("\"wrong-etag\""));
810
811 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
812 .await
813 .unwrap();
814
815 assert_eq!(response.status(), StatusCode::OK);
817 assert!(!response.headers().contains_key(CONTENT_RANGE));
818
819 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
820 assert_eq!(content_length, "100");
821 }
822
823 #[tokio::test]
824 async fn test_range_not_satisfiable_error_includes_content_range_header() {
825 let temp = TempDir::new().unwrap();
826 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
827
828 let data = vec![42u8; 100];
829 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
830 let stored = storage.store(file).await.unwrap();
831
832 let mut headers = HeaderMap::new();
834 headers.insert(RANGE, HeaderValue::from_static("bytes=200-299"));
835
836 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers).await;
837
838 assert!(response.is_err());
839 let err = response.unwrap_err();
840
841 let response = err.into_response();
843 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
844
845 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
847 assert_eq!(content_range, "bytes */100");
848 }
849
850 #[tokio::test]
851 async fn test_range_request_preserves_cache_headers() {
852 let temp = TempDir::new().unwrap();
853 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
854
855 let data = vec![42u8; 100];
856 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
857 let stored = storage.store(file).await.unwrap();
858
859 let mut headers = HeaderMap::new();
860 headers.insert(RANGE, HeaderValue::from_static("bytes=0-49"));
861
862 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
863 .await
864 .unwrap();
865
866 assert!(response.headers().contains_key(ETAG));
868 assert!(response.headers().contains_key(CACHE_CONTROL));
869 assert!(response.headers().contains_key(LAST_MODIFIED));
870 }
871
872 #[tokio::test]
873 async fn test_no_range_header_serves_full_file() {
874 let temp = TempDir::new().unwrap();
875 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
876
877 let data = vec![42u8; 100];
878 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
879 let stored = storage.store(file).await.unwrap();
880
881 let headers = HeaderMap::new();
883
884 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
885 .await
886 .unwrap();
887
888 assert_eq!(response.status(), StatusCode::OK);
889 assert!(!response.headers().contains_key(CONTENT_RANGE));
890
891 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
892 assert_eq!(content_length, "100");
893
894 assert_eq!(
896 response.headers().get(ACCEPT_RANGES).unwrap(),
897 "bytes"
898 );
899 }
900
901 #[tokio::test]
902 async fn test_range_request_first_byte() {
903 let temp = TempDir::new().unwrap();
904 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
905
906 let data = vec![42u8; 100];
907 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
908 let stored = storage.store(file).await.unwrap();
909
910 let mut headers = HeaderMap::new();
912 headers.insert(RANGE, HeaderValue::from_static("bytes=0-0"));
913
914 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
915 .await
916 .unwrap();
917
918 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
919
920 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
921 assert_eq!(content_range, "bytes 0-0/100");
922
923 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
924 assert_eq!(content_length, "1");
925 }
926
927 #[tokio::test]
928 async fn test_range_request_last_byte() {
929 let temp = TempDir::new().unwrap();
930 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
931
932 let data = vec![42u8; 100];
933 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
934 let stored = storage.store(file).await.unwrap();
935
936 let mut headers = HeaderMap::new();
938 headers.insert(RANGE, HeaderValue::from_static("bytes=99-99"));
939
940 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
941 .await
942 .unwrap();
943
944 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
945
946 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
947 assert_eq!(content_range, "bytes 99-99/100");
948
949 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
950 assert_eq!(content_length, "1");
951 }
952
953 #[tokio::test]
954 async fn test_range_request_entire_file_as_range() {
955 let temp = TempDir::new().unwrap();
956 let storage = Arc::new(LocalFileStorage::new(temp.path().to_path_buf()).unwrap());
957
958 let data = vec![42u8; 100];
959 let file = UploadedFile::new("test.bin", "application/octet-stream", data);
960 let stored = storage.store(file).await.unwrap();
961
962 let mut headers = HeaderMap::new();
964 headers.insert(RANGE, HeaderValue::from_static("bytes=0-99"));
965
966 let response = serve_file(State(storage.clone()), Path(stored.id.clone()), headers)
967 .await
968 .unwrap();
969
970 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
972
973 let content_range = response.headers().get(CONTENT_RANGE).unwrap();
974 assert_eq!(content_range, "bytes 0-99/100");
975
976 let content_length = response.headers().get(CONTENT_LENGTH).unwrap();
977 assert_eq!(content_length, "100");
978 }
979}