1#![warn(missing_docs, missing_debug_implementations)]
15#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
16#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
17#![cfg_attr(docsrs, feature(doc_cfg))]
18
19use std::path::Path;
20
21use actix_service::boxed::{BoxService, BoxServiceFactory};
22use actix_web::{
23 dev::{RequestHead, ServiceRequest, ServiceResponse},
24 error::Error,
25 http::header::DispositionType,
26};
27use mime_guess::from_ext;
28
29mod chunked;
30mod directory;
31mod encoding;
32mod error;
33mod files;
34mod named;
35mod path_buf;
36mod range;
37mod service;
38
39pub use self::{
40 chunked::ChunkedReadFile,
41 directory::Directory,
42 error::UriSegmentError,
43 files::{Files, FilesDirs},
44 named::NamedFile,
45 path_buf::PathBufWrap,
46 range::HttpRange,
47 service::FilesService,
48};
49use self::{
50 directory::{directory_listing, DirectoryRenderer},
51 error::FilesError,
52};
53
54type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
55type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
56
57#[inline]
61pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
62 from_ext(ext).first_or_octet_stream()
63}
64
65type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
66
67type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
68
69#[cfg(test)]
70mod tests {
71 use std::{
72 ffi::OsString,
73 fmt::Write as _,
74 fs::{self},
75 ops::Add,
76 path::PathBuf,
77 time::{Duration, SystemTime},
78 };
79
80 use actix_http::header::HttpDate;
81 use actix_web::{
82 body,
83 dev::ServiceFactory,
84 guard,
85 http::{
86 header::{self, ContentDisposition, DispositionParam},
87 Method, StatusCode,
88 },
89 middleware::Compress,
90 test::{self, TestRequest},
91 web::{self, Bytes},
92 App, HttpResponse, Responder,
93 };
94
95 use super::*;
96 use crate::named::File;
97
98 #[actix_web::test]
99 async fn test_file_extension_to_mime() {
100 let m = file_extension_to_mime("");
101 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
102
103 let m = file_extension_to_mime("jpg");
104 assert_eq!(m, mime::IMAGE_JPEG);
105
106 let m = file_extension_to_mime("invalid extension!!");
107 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
108
109 let m = file_extension_to_mime("");
110 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
111 }
112
113 #[actix_rt::test]
114 async fn test_if_none_match_get_and_head() {
115 let file = NamedFile::open("Cargo.toml").unwrap();
116 let etag = file.etag().unwrap().to_string();
117
118 for method in [Method::GET, Method::HEAD] {
119 for value in [etag.as_str(), "*"] {
120 let req = TestRequest::default()
121 .method(method.clone())
122 .insert_header((header::IF_NONE_MATCH, value))
123 .to_http_request();
124 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
125
126 assert_eq!(
127 resp.status(),
128 StatusCode::NOT_MODIFIED,
129 "{method} with matching If-None-Match {value} should return 304"
130 );
131 assert!(
132 body::to_bytes(resp.into_body()).await.unwrap().is_empty(),
133 "{method} with matching If-None-Match {value} should have an empty body"
134 );
135 }
136 }
137 }
138
139 #[actix_rt::test]
140 async fn test_if_none_match_post() {
141 let file = NamedFile::open("Cargo.toml").unwrap();
142 let etag = file.etag().unwrap().to_string();
143
144 for value in [etag.as_str(), "*"] {
145 let req = TestRequest::post()
146 .insert_header((header::IF_NONE_MATCH, value))
147 .to_http_request();
148 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
149
150 assert_eq!(
151 resp.status(),
152 StatusCode::PRECONDITION_FAILED,
153 "POST with matching If-None-Match {value} should return 412"
154 );
155 assert!(
156 body::to_bytes(resp.into_body()).await.unwrap().is_empty(),
157 "POST with matching If-None-Match {value} should have an empty body"
158 );
159 }
160 }
161
162 #[actix_rt::test]
163 async fn test_if_modified_since_get_and_head() {
164 let file = NamedFile::open("Cargo.toml").unwrap();
165 let since = file.last_modified().unwrap();
166
167 for method in [Method::GET, Method::HEAD] {
168 let req = TestRequest::default()
169 .method(method.clone())
170 .insert_header((header::IF_MODIFIED_SINCE, since))
171 .to_http_request();
172 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
173
174 assert_eq!(
175 resp.status(),
176 StatusCode::NOT_MODIFIED,
177 "{method} with matching If-Modified-Since should return 304"
178 );
179 assert!(
180 body::to_bytes(resp.into_body()).await.unwrap().is_empty(),
181 "{method} with matching If-Modified-Since should have an empty body"
182 );
183 }
184 }
185
186 #[actix_rt::test]
187 async fn test_if_modified_since_post() {
188 let file = NamedFile::open("Cargo.toml").unwrap();
189 let since = file.last_modified().unwrap();
190
191 let req = TestRequest::post()
192 .insert_header((header::IF_MODIFIED_SINCE, since))
193 .to_http_request();
194 let resp = file.into_response(&req);
195
196 assert_eq!(
197 resp.status(),
198 StatusCode::OK,
199 "POST should ignore If-Modified-Since and return 200"
200 );
201
202 let bytes = body::to_bytes(resp.into_body()).await.unwrap();
203
204 assert_eq!(
205 bytes.as_ref(),
206 fs::read("Cargo.toml").unwrap(),
207 "POST should return the full file despite If-Modified-Since"
208 );
209 }
210
211 #[actix_rt::test]
212 async fn test_if_modified_since_without_if_none_match() {
213 let file = NamedFile::open("Cargo.toml").unwrap();
214 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
215
216 let req = TestRequest::default()
217 .insert_header((header::IF_MODIFIED_SINCE, since))
218 .to_http_request();
219 let resp = file.respond_to(&req);
220 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
221 }
222
223 #[actix_rt::test]
224 async fn test_if_modified_since_without_if_none_match_same() {
225 let file = NamedFile::open("Cargo.toml").unwrap();
226 let since = file.last_modified().unwrap();
227
228 let req = TestRequest::default()
229 .insert_header((header::IF_MODIFIED_SINCE, since))
230 .to_http_request();
231 let resp = file.respond_to(&req);
232 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
233 }
234
235 #[actix_rt::test]
236 async fn test_if_modified_since_with_if_none_match() {
237 let file = NamedFile::open("Cargo.toml").unwrap();
238 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
239
240 let req = TestRequest::default()
241 .insert_header((header::IF_NONE_MATCH, "miss_etag"))
242 .insert_header((header::IF_MODIFIED_SINCE, since))
243 .to_http_request();
244 let resp = file.respond_to(&req);
245 assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
246 }
247
248 #[actix_rt::test]
249 async fn test_if_match_overrides_if_unmodified_since() {
250 let file = NamedFile::open("Cargo.toml").unwrap();
251 let etag = file.etag().unwrap();
252 let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
253
254 let req = TestRequest::default()
255 .insert_header((header::IF_MATCH, etag))
256 .insert_header((header::IF_UNMODIFIED_SINCE, since))
257 .to_http_request();
258 let resp = file.respond_to(&req);
259 assert_eq!(resp.status(), StatusCode::OK);
260 }
261
262 #[actix_rt::test]
263 async fn test_if_match_failure_with_if_unmodified_since() {
264 let file = NamedFile::open("Cargo.toml").unwrap();
265 let since = file.last_modified().unwrap();
266
267 let req = TestRequest::default()
268 .insert_header((header::IF_MATCH, "\"nonmatching-etag\""))
269 .insert_header((header::IF_UNMODIFIED_SINCE, since))
270 .to_http_request();
271 let resp = file.respond_to(&req);
272 assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
273 }
274
275 #[actix_rt::test]
276 async fn test_if_unmodified_since() {
277 let file = NamedFile::open("Cargo.toml").unwrap();
278 let since = file.last_modified().unwrap();
279
280 let req = TestRequest::default()
281 .insert_header((header::IF_UNMODIFIED_SINCE, since))
282 .to_http_request();
283 let resp = file.respond_to(&req);
284 assert_eq!(resp.status(), StatusCode::OK);
285 }
286
287 #[actix_rt::test]
288 async fn test_if_unmodified_since_failed() {
289 let file = NamedFile::open("Cargo.toml").unwrap();
290 let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
291
292 let req = TestRequest::default()
293 .insert_header((header::IF_UNMODIFIED_SINCE, since))
294 .to_http_request();
295 let resp = file.respond_to(&req);
296 assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
297 }
298
299 #[actix_rt::test]
300 async fn test_named_file_text() {
301 assert!(NamedFile::open("test--").is_err());
302 let mut file = NamedFile::open("Cargo.toml").unwrap();
303 {
304 file.file();
305 let _f: &File = &file;
306 }
307 {
308 let _f: &mut File = &mut file;
309 }
310
311 let req = TestRequest::default().to_http_request();
312 let resp = file.respond_to(&req);
313 assert_eq!(
314 resp.headers().get(header::CONTENT_TYPE).unwrap(),
315 "text/x-toml"
316 );
317 assert_eq!(
318 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
319 "inline; filename=\"Cargo.toml\""
320 );
321 }
322
323 #[actix_rt::test]
324 async fn test_named_file_content_disposition() {
325 assert!(NamedFile::open("test--").is_err());
326 let mut file = NamedFile::open("Cargo.toml").unwrap();
327 {
328 file.file();
329 let _f: &File = &file;
330 }
331 {
332 let _f: &mut File = &mut file;
333 }
334
335 let req = TestRequest::default().to_http_request();
336 let resp = file.respond_to(&req);
337 assert_eq!(
338 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
339 "inline; filename=\"Cargo.toml\""
340 );
341
342 let file = NamedFile::open("Cargo.toml")
343 .unwrap()
344 .disable_content_disposition();
345 let req = TestRequest::default().to_http_request();
346 let resp = file.respond_to(&req);
347 assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
348 }
349
350 #[actix_rt::test]
351 async fn test_named_file_non_ascii_file_name() {
352 let file = crate::named::File::open("Cargo.toml").unwrap();
353
354 let mut file = NamedFile::from_file(file, "貨物.toml").unwrap();
355 {
356 file.file();
357 let _f: &File = &file;
358 }
359 {
360 let _f: &mut File = &mut file;
361 }
362
363 let req = TestRequest::default().to_http_request();
364 let resp = file.respond_to(&req);
365 assert_eq!(
366 resp.headers().get(header::CONTENT_TYPE).unwrap(),
367 "text/x-toml"
368 );
369 assert_eq!(
370 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
371 "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
372 );
373 }
374
375 #[actix_rt::test]
376 async fn test_named_file_set_content_type() {
377 let mut file = NamedFile::open("Cargo.toml")
378 .unwrap()
379 .set_content_type(mime::TEXT_XML);
380 {
381 file.file();
382 let _f: &File = &file;
383 }
384 {
385 let _f: &mut File = &mut file;
386 }
387
388 let req = TestRequest::default().to_http_request();
389 let resp = file.respond_to(&req);
390 assert_eq!(
391 resp.headers().get(header::CONTENT_TYPE).unwrap(),
392 "text/xml"
393 );
394 assert_eq!(
395 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
396 "inline; filename=\"Cargo.toml\""
397 );
398 }
399
400 #[actix_rt::test]
401 async fn test_named_file_image() {
402 let mut file = NamedFile::open("tests/test.png").unwrap();
403 {
404 file.file();
405 let _f: &File = &file;
406 }
407 {
408 let _f: &mut File = &mut file;
409 }
410
411 let req = TestRequest::default().to_http_request();
412 let resp = file.respond_to(&req);
413 assert_eq!(
414 resp.headers().get(header::CONTENT_TYPE).unwrap(),
415 "image/png"
416 );
417 assert_eq!(
418 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
419 "inline; filename=\"test.png\""
420 );
421 }
422
423 #[actix_rt::test]
424 async fn test_named_file_javascript() {
425 let file = NamedFile::open("tests/test.js").unwrap();
426
427 let req = TestRequest::default().to_http_request();
428 let resp = file.respond_to(&req);
429 assert_eq!(
430 resp.headers().get(header::CONTENT_TYPE).unwrap(),
431 "text/javascript",
432 );
433 assert_eq!(
434 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
435 "inline; filename=\"test.js\"",
436 );
437 }
438
439 #[actix_rt::test]
440 async fn test_named_file_image_attachment() {
441 let cd = ContentDisposition {
442 disposition: DispositionType::Attachment,
443 parameters: vec![DispositionParam::Filename(String::from("test.png"))],
444 };
445 let mut file = NamedFile::open("tests/test.png")
446 .unwrap()
447 .set_content_disposition(cd);
448 {
449 file.file();
450 let _f: &File = &file;
451 }
452 {
453 let _f: &mut File = &mut file;
454 }
455
456 let req = TestRequest::default().to_http_request();
457 let resp = file.respond_to(&req);
458 assert_eq!(
459 resp.headers().get(header::CONTENT_TYPE).unwrap(),
460 "image/png"
461 );
462 assert_eq!(
463 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
464 "attachment; filename=\"test.png\""
465 );
466 }
467
468 #[actix_rt::test]
469 async fn test_named_file_binary() {
470 let mut file = NamedFile::open("tests/test.binary").unwrap();
471 {
472 file.file();
473 let _f: &File = &file;
474 }
475 {
476 let _f: &mut File = &mut file;
477 }
478
479 let req = TestRequest::default().to_http_request();
480 let resp = file.respond_to(&req);
481 assert_eq!(
482 resp.headers().get(header::CONTENT_TYPE).unwrap(),
483 "application/octet-stream"
484 );
485 assert_eq!(
486 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
487 "attachment; filename=\"test.binary\""
488 );
489 }
490
491 #[allow(deprecated)]
492 #[actix_rt::test]
493 async fn status_code_customize_same_output() {
494 let file1 = NamedFile::open("Cargo.toml")
495 .unwrap()
496 .set_status_code(StatusCode::NOT_FOUND);
497
498 let file2 = NamedFile::open("Cargo.toml")
499 .unwrap()
500 .customize()
501 .with_status(StatusCode::NOT_FOUND);
502
503 let req = TestRequest::default().to_http_request();
504 let res1 = file1.respond_to(&req);
505 let res2 = file2.respond_to(&req);
506
507 assert_eq!(res1.status(), StatusCode::NOT_FOUND);
508 assert_eq!(res2.status(), StatusCode::NOT_FOUND);
509 }
510
511 #[actix_rt::test]
512 async fn test_named_file_status_code_text() {
513 let mut file = NamedFile::open("Cargo.toml").unwrap();
514
515 {
516 file.file();
517 let _f: &File = &file;
518 }
519
520 {
521 let _f: &mut File = &mut file;
522 }
523
524 let file = file.customize().with_status(StatusCode::NOT_FOUND);
525
526 let req = TestRequest::default().to_http_request();
527 let resp = file.respond_to(&req);
528 assert_eq!(
529 resp.headers().get(header::CONTENT_TYPE).unwrap(),
530 "text/x-toml"
531 );
532 assert_eq!(
533 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
534 "inline; filename=\"Cargo.toml\""
535 );
536 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
537 }
538
539 #[actix_rt::test]
540 async fn test_mime_override() {
541 fn all_attachment(_: &mime::Name<'_>) -> DispositionType {
542 DispositionType::Attachment
543 }
544
545 let srv = test::init_service(
546 App::new().service(
547 Files::new("/", ".")
548 .mime_override(all_attachment)
549 .index_file("Cargo.toml"),
550 ),
551 )
552 .await;
553
554 let request = TestRequest::get().uri("/").to_request();
555 let response = test::call_service(&srv, request).await;
556 assert_eq!(response.status(), StatusCode::OK);
557
558 let content_disposition = response
559 .headers()
560 .get(header::CONTENT_DISPOSITION)
561 .expect("To have CONTENT_DISPOSITION");
562 let content_disposition = content_disposition
563 .to_str()
564 .expect("Convert CONTENT_DISPOSITION to str");
565 assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
566 }
567
568 #[actix_rt::test]
569 async fn test_named_file_ranges_status_code() {
570 let srv = test::init_service(
571 App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
572 )
573 .await;
574
575 let request = TestRequest::get()
577 .uri("/t%65st/Cargo.toml")
578 .insert_header((header::RANGE, "bytes=10-20"))
579 .to_request();
580 let response = test::call_service(&srv, request).await;
581 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
582
583 let request = TestRequest::get()
585 .uri("/t%65st/Cargo.toml")
586 .insert_header((header::RANGE, "bytes=1-0"))
587 .to_request();
588 let response = test::call_service(&srv, request).await;
589
590 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
591 }
592
593 #[actix_rt::test]
594 async fn test_named_file_if_none_match_before_range() {
595 let req = TestRequest::get().to_http_request();
596 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
597 let etag = resp.headers().get(header::ETAG).unwrap().clone();
598
599 for range in ["bytes=999999999-", "bytes=0-9"] {
600 let req = TestRequest::get()
601 .insert_header((header::IF_NONE_MATCH, etag.clone()))
602 .insert_header((header::RANGE, range))
603 .to_http_request();
604 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
605
606 assert_eq!(
607 resp.status(),
608 StatusCode::NOT_MODIFIED,
609 "matching If-None-Match with {range} should return 304"
610 );
611 assert!(
612 !resp.headers().contains_key(header::CONTENT_RANGE),
613 "conditional response with {range} should omit Content-Range"
614 );
615 assert!(
616 body::to_bytes(resp.into_body()).await.unwrap().is_empty(),
617 "conditional response with {range} should have an empty body"
618 );
619 }
620 }
621
622 #[actix_rt::test]
623 async fn test_named_file_if_match_before_range() {
624 for range in ["bytes=999999999-", "bytes=0-9"] {
625 let req = TestRequest::get()
626 .insert_header((header::IF_MATCH, "\"stale\""))
627 .insert_header((header::RANGE, range))
628 .to_http_request();
629 let resp = NamedFile::open("Cargo.toml").unwrap().into_response(&req);
630
631 assert_eq!(
632 resp.status(),
633 StatusCode::PRECONDITION_FAILED,
634 "nonmatching If-Match with {range} should return 412"
635 );
636 assert!(
637 !resp.headers().contains_key(header::CONTENT_RANGE),
638 "conditional response with {range} should omit Content-Range"
639 );
640 assert!(
641 body::to_bytes(resp.into_body()).await.unwrap().is_empty(),
642 "conditional response with {range} should have an empty body"
643 );
644 }
645 }
646
647 #[actix_web::test]
648 async fn byte_range_units_are_case_insensitive() {
649 let file = tempfile::NamedTempFile::new().unwrap();
650 fs::write(file.path(), b"0123456789").unwrap();
651
652 for range in ["Bytes=2-4", "BYTES=2-4", "bYtEs=2-4", "bytes=2-4"] {
653 let req = TestRequest::get()
654 .insert_header((header::RANGE, range))
655 .to_http_request();
656 let response = NamedFile::open(file.path()).unwrap().into_response(&req);
657
658 assert_eq!(
659 response.status(),
660 StatusCode::PARTIAL_CONTENT,
661 "range {range} should produce the expected status"
662 );
663 assert_eq!(
664 response.headers().get(header::CONTENT_RANGE).unwrap(),
665 "bytes 2-4/10"
666 );
667 assert_eq!(
668 body::to_bytes(response.into_body()).await.unwrap(),
669 b"234"[..]
670 );
671 }
672 }
673
674 #[actix_web::test]
675 async fn unknown_range_units_are_ignored() {
676 let file = tempfile::NamedTempFile::new().unwrap();
677 fs::write(file.path(), b"0123456789").unwrap();
678
679 for range in ["items=2-4", "BYTESX=2-4", "items=first", "items=100-200"] {
680 let req = TestRequest::get()
681 .insert_header((header::RANGE, range))
682 .to_http_request();
683 let response = NamedFile::open(file.path()).unwrap().into_response(&req);
684
685 assert_eq!(
686 response.status(),
687 StatusCode::OK,
688 "range {range} should produce the expected status"
689 );
690 assert!(!response.headers().contains_key(header::CONTENT_RANGE));
691 assert_eq!(
692 body::to_bytes(response.into_body()).await.unwrap(),
693 b"0123456789"[..]
694 );
695 }
696 }
697
698 #[actix_rt::test]
699 async fn test_named_file_if_range_etag() {
700 let file = NamedFile::open("Cargo.toml").unwrap();
701 let etag = file.etag().unwrap();
702
703 let req = TestRequest::default()
704 .insert_header((header::RANGE, "bytes=0-9"))
705 .insert_header((header::IF_RANGE, etag.to_string()))
706 .to_http_request();
707
708 let resp = file.respond_to(&req);
709 assert_eq!(resp.status(), StatusCode::PARTIAL_CONTENT);
710
711 for stale in ["\"stale\"", "W/\"stale\"", "not a validator"] {
713 let file = NamedFile::open("Cargo.toml").unwrap();
714 let content = fs::read("Cargo.toml").unwrap();
715
716 let req = TestRequest::default()
717 .insert_header((header::RANGE, "bytes=0-9"))
718 .insert_header((header::IF_RANGE, stale))
719 .to_http_request();
720
721 let resp = file.respond_to(&req);
722 assert_eq!(resp.status(), StatusCode::OK, "If-Range: {stale}");
723 assert!(!resp.headers().contains_key(header::CONTENT_RANGE));
724
725 let body = body::to_bytes(resp.into_body()).await.unwrap();
726
727 assert_eq!(
728 body, content,
729 "Response body and full file content should match",
730 );
731 }
732 }
733
734 #[actix_rt::test]
735 async fn test_named_file_if_range_date() {
736 let file = NamedFile::open("Cargo.toml").unwrap();
737 let content = fs::read("Cargo.toml").unwrap();
738 let modified = file.last_modified().unwrap();
739
740 let req = TestRequest::default()
741 .insert_header((header::RANGE, "bytes=0-9"))
742 .insert_header((header::IF_RANGE, modified))
743 .to_http_request();
744
745 let resp = file.respond_to(&req);
746 assert_eq!(resp.status(), StatusCode::PARTIAL_CONTENT);
747
748 let file = NamedFile::open("Cargo.toml").unwrap();
749 let later = HttpDate::from(SystemTime::from(modified).add(Duration::from_secs(60)));
750 let req = TestRequest::default()
751 .insert_header((header::RANGE, "bytes=0-9"))
752 .insert_header((header::IF_RANGE, later))
753 .to_http_request();
754
755 let resp = file.respond_to(&req);
756 assert_eq!(resp.status(), StatusCode::OK);
757 assert!(!resp.headers().contains_key(header::CONTENT_RANGE));
758
759 let body = body::to_bytes(resp.into_body()).await.unwrap();
760
761 assert_eq!(
762 body, content,
763 "Response body and full file content should match",
764 );
765 }
766
767 #[actix_rt::test]
768 async fn test_named_file_empty_range_headers() {
769 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
770
771 for range in ["", "bytes="] {
772 let response = srv
773 .get("/tests/test.binary")
774 .insert_header((header::RANGE, range))
775 .send()
776 .await
777 .unwrap();
778
779 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
780 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
781 assert_eq!(content_range.to_str().unwrap(), "bytes */100");
782 }
783 }
784
785 #[actix_rt::test]
786 async fn test_named_file_content_range_headers() {
787 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
788
789 let response = srv
791 .get("/tests/test.binary")
792 .insert_header((header::RANGE, "bytes=10-20"))
793 .send()
794 .await
795 .unwrap();
796 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
797 assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
798
799 let response = srv
801 .get("/tests/test.binary")
802 .insert_header((header::RANGE, "bytes=10-5"))
803 .send()
804 .await
805 .unwrap();
806 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
807 assert_eq!(content_range.to_str().unwrap(), "bytes */100");
808 }
809
810 #[actix_rt::test]
811 async fn test_named_file_range_header_from_zero_to_end_returns_partial_content() {
812 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
813
814 let response = srv
815 .get("/tests/test.binary")
816 .insert_header((header::RANGE, "bytes=0-"))
817 .send()
818 .await
819 .unwrap();
820
821 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
822
823 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
824 assert_eq!(content_range.to_str().unwrap(), "bytes 0-99/100");
825
826 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
827 assert_eq!(content_length.to_str().unwrap(), "100");
828
829 let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
831 assert!(transfer_encoding.is_none());
832 }
833
834 #[actix_rt::test]
835 async fn test_named_file_content_length_headers() {
836 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
837
838 let response = srv
840 .get("/tests/test.binary")
841 .insert_header((header::RANGE, "bytes=10-20"))
842 .send()
843 .await
844 .unwrap();
845 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
846 assert_eq!(content_length.to_str().unwrap(), "11");
847
848 let response = srv
850 .get("/tests/test.binary")
851 .insert_header((header::RANGE, "bytes=0-20"))
852 .send()
853 .await
854 .unwrap();
855 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
856 assert_eq!(content_length.to_str().unwrap(), "21");
857
858 let mut response = srv.get("/tests/test.binary").send().await.unwrap();
860 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
861 assert_eq!(content_length.to_str().unwrap(), "100");
862
863 let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
865 assert!(transfer_encoding.is_none());
866
867 let bytes = response.body().await.unwrap();
869 let data = web::Bytes::from(fs::read("tests/test.binary").unwrap());
870 assert_eq!(bytes, data);
871 }
872
873 #[actix_rt::test]
874 async fn test_head_content_length_headers() {
875 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
876
877 let response = srv.head("/tests/test.binary").send().await.unwrap();
878
879 let content_length = response
880 .headers()
881 .get(header::CONTENT_LENGTH)
882 .unwrap()
883 .to_str()
884 .unwrap();
885
886 assert_eq!(content_length, "100");
887 }
888
889 #[actix_rt::test]
890 async fn test_static_files_with_spaces() {
891 let srv =
892 test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
893 .await;
894 let request = TestRequest::get()
895 .uri("/tests/test%20space.binary")
896 .to_request();
897 let response = test::call_service(&srv, request).await;
898 assert_eq!(response.status(), StatusCode::OK);
899
900 let bytes = test::read_body(response).await;
901 let data = web::Bytes::from(fs::read("tests/test space.binary").unwrap());
902 assert_eq!(bytes, data);
903 }
904
905 #[cfg(not(target_os = "windows"))]
906 #[actix_rt::test]
907 async fn test_static_files_with_special_characters() {
908 let temp_dir = tempfile::tempdir().unwrap();
911 let file_with_newlines = temp_dir.path().join("test\n\x0B\x0C\rnewline.text");
912 fs::write(&file_with_newlines, "Look at my newlines").unwrap();
913
914 let srv = test::init_service(
915 App::new().service(Files::new("/", temp_dir.path()).index_file("Cargo.toml")),
916 )
917 .await;
918 let request = TestRequest::get()
919 .uri("/test%0A%0B%0C%0Dnewline.text")
920 .to_request();
921 let response = test::call_service(&srv, request).await;
922 assert_eq!(response.status(), StatusCode::OK);
923
924 let bytes = test::read_body(response).await;
925 let data = web::Bytes::from(fs::read(file_with_newlines).unwrap());
926 assert_eq!(bytes, data);
927 }
928
929 #[actix_rt::test]
930 async fn test_files_not_allowed() {
931 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
932
933 let req = TestRequest::default()
934 .uri("/Cargo.toml")
935 .method(Method::POST)
936 .to_request();
937
938 let resp = test::call_service(&srv, req).await;
939 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
940
941 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
942 let req = TestRequest::default()
943 .method(Method::PUT)
944 .uri("/Cargo.toml")
945 .to_request();
946 let resp = test::call_service(&srv, req).await;
947 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
948 }
949
950 #[actix_rt::test]
951 async fn test_files_guards() {
952 let srv = test::init_service(
953 App::new().service(Files::new("/", ".").method_guard(guard::Post())),
954 )
955 .await;
956
957 let req = TestRequest::default()
958 .uri("/Cargo.toml")
959 .method(Method::POST)
960 .to_request();
961
962 let resp = test::call_service(&srv, req).await;
963 assert_eq!(resp.status(), StatusCode::OK);
964 }
965
966 #[actix_rt::test]
967 async fn test_named_file_content_encoding() {
968 let srv = test::init_service(App::new().wrap(Compress::default()).service(
969 web::resource("/").to(|| async {
970 NamedFile::open("Cargo.toml")
971 .unwrap()
972 .set_content_encoding(header::ContentEncoding::Identity)
973 }),
974 ))
975 .await;
976
977 let request = TestRequest::get()
978 .uri("/")
979 .insert_header((header::ACCEPT_ENCODING, "gzip"))
980 .to_request();
981 let res = test::call_service(&srv, request).await;
982 assert_eq!(res.status(), StatusCode::OK);
983 assert!(res.headers().contains_key(header::CONTENT_ENCODING));
984 assert!(!test::read_body(res).await.is_empty());
985 }
986
987 #[actix_rt::test]
988 async fn test_named_file_content_encoding_gzip() {
989 let srv = test::init_service(App::new().wrap(Compress::default()).service(
990 web::resource("/").to(|| async {
991 NamedFile::open("Cargo.toml")
992 .unwrap()
993 .set_content_encoding(header::ContentEncoding::Gzip)
994 }),
995 ))
996 .await;
997
998 let request = TestRequest::get()
999 .uri("/")
1000 .insert_header((header::ACCEPT_ENCODING, "gzip"))
1001 .to_request();
1002 let res = test::call_service(&srv, request).await;
1003 assert_eq!(res.status(), StatusCode::OK);
1004 assert_eq!(
1005 res.headers()
1006 .get(header::CONTENT_ENCODING)
1007 .unwrap()
1008 .to_str()
1009 .unwrap(),
1010 "gzip"
1011 );
1012 }
1013
1014 #[actix_rt::test]
1015 async fn test_named_file_allowed_method() {
1016 let req = TestRequest::default().method(Method::GET).to_http_request();
1017 let file = NamedFile::open("Cargo.toml").unwrap();
1018 let resp = file.respond_to(&req);
1019 assert_eq!(resp.status(), StatusCode::OK);
1020 }
1021
1022 #[actix_rt::test]
1023 async fn test_static_files() {
1024 let srv =
1025 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
1026 let req = TestRequest::with_uri("/missing").to_request();
1027
1028 let resp = test::call_service(&srv, req).await;
1029 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1030
1031 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
1032
1033 let req = TestRequest::default().to_request();
1034 let resp = test::call_service(&srv, req).await;
1035 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1036
1037 let srv =
1038 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
1039 let req = TestRequest::with_uri("/tests").to_request();
1040 let resp = test::call_service(&srv, req).await;
1041 assert_eq!(
1042 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1043 "text/html; charset=utf-8"
1044 );
1045
1046 let bytes = test::read_body(resp).await;
1047 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
1048 }
1049
1050 #[actix_rt::test]
1051 async fn test_redirect_to_slash_directory() {
1052 let srv = test::init_service(
1054 App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
1055 )
1056 .await;
1057 let req = TestRequest::with_uri("/tests").to_request();
1058 let resp = test::call_service(&srv, req).await;
1059 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1060
1061 let srv = test::init_service(
1063 App::new().service(
1064 Files::new("/", ".")
1065 .index_file("test.png")
1066 .redirect_to_slash_directory(),
1067 ),
1068 )
1069 .await;
1070 let req = TestRequest::with_uri("/tests").to_request();
1071 let resp = test::call_service(&srv, req).await;
1072 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
1073
1074 let srv = test::init_service(
1076 App::new().service(
1077 Files::new("/", ".")
1078 .index_file("test.png")
1079 .redirect_to_slash_directory()
1080 .with_permanent_redirect(),
1081 ),
1082 )
1083 .await;
1084 let req = TestRequest::with_uri("/tests").to_request();
1085 let resp = test::call_service(&srv, req).await;
1086 assert_eq!(resp.status(), StatusCode::PERMANENT_REDIRECT);
1087
1088 let srv = test::init_service(
1090 App::new().service(
1091 Files::new("/", ".")
1092 .show_files_listing()
1093 .redirect_to_slash_directory(),
1094 ),
1095 )
1096 .await;
1097 let req = TestRequest::with_uri("/tests").to_request();
1098 let resp = test::call_service(&srv, req).await;
1099 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
1100
1101 let req = TestRequest::with_uri("/not_existing").to_request();
1103 let resp = test::call_service(&srv, req).await;
1104 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1105 }
1106
1107 #[actix_rt::test]
1108 async fn test_static_files_bad_directory() {
1109 let service = Files::new("/", "./missing").new_service(()).await.unwrap();
1110
1111 let req = TestRequest::with_uri("/").to_srv_request();
1112 let resp = test::call_service(&service, req).await;
1113
1114 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1115 }
1116
1117 #[actix_rt::test]
1118 async fn test_static_files_bad_directory_does_not_serve_cwd_files() {
1119 let service = Files::new("/", "./missing").new_service(()).await.unwrap();
1120
1121 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1122 let resp = test::call_service(&service, req).await;
1123
1124 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1125 }
1126
1127 #[actix_rt::test]
1128 async fn test_static_files_accepts_borrowed_os_string_directory() {
1129 let dir = OsString::from(".");
1130 let service = Files::new("/", &dir).new_service(()).await.unwrap();
1131
1132 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1133 let resp = test::call_service(&service, req).await;
1134
1135 assert_eq!(resp.status(), StatusCode::OK);
1136 }
1137
1138 #[actix_rt::test]
1139 async fn test_static_files_empty_directories() {
1140 let service = Files::new("/", Vec::<PathBuf>::new())
1141 .new_service(())
1142 .await
1143 .unwrap();
1144
1145 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1146 let resp = test::call_service(&service, req).await;
1147 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1148
1149 let service = Files::new("/", Vec::<PathBuf>::new())
1150 .default_handler(|req: ServiceRequest| async {
1151 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1152 })
1153 .new_service(())
1154 .await
1155 .unwrap();
1156
1157 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1158 let resp = test::call_service(&service, req).await;
1159 assert_eq!(resp.status(), StatusCode::OK);
1160 assert_eq!(
1161 test::read_body(resp).await,
1162 Bytes::from_static(b"default content")
1163 );
1164 }
1165
1166 #[actix_rt::test]
1167 async fn test_static_files_multiple_directories() {
1168 let first_dir = tempfile::tempdir().unwrap();
1169 let second_dir = tempfile::tempdir().unwrap();
1170
1171 fs::write(first_dir.path().join("shared.txt"), "first").unwrap();
1172 fs::write(second_dir.path().join("shared.txt"), "second").unwrap();
1173 fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
1174
1175 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1176 .new_service(())
1177 .await
1178 .unwrap();
1179
1180 let req = TestRequest::with_uri("/shared.txt").to_srv_request();
1181 let resp = test::call_service(&service, req).await;
1182 assert_eq!(resp.status(), StatusCode::OK);
1183 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"first"));
1184
1185 let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
1186 let resp = test::call_service(&service, req).await;
1187 assert_eq!(resp.status(), StatusCode::OK);
1188 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
1189 }
1190
1191 #[actix_rt::test]
1192 async fn test_static_files_multiple_directories_file_as_parent_falls_back() {
1193 let first_dir = tempfile::tempdir().unwrap();
1194 let second_dir = tempfile::tempdir().unwrap();
1195
1196 fs::write(first_dir.path().join("assets"), "file").unwrap();
1197 fs::create_dir(second_dir.path().join("assets")).unwrap();
1198 fs::write(
1199 second_dir.path().join("assets").join("fallback.txt"),
1200 "fallback",
1201 )
1202 .unwrap();
1203
1204 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1205 .new_service(())
1206 .await
1207 .unwrap();
1208
1209 let req = TestRequest::with_uri("/assets/fallback.txt").to_srv_request();
1210 let resp = test::call_service(&service, req).await;
1211 assert_eq!(resp.status(), StatusCode::OK);
1212 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
1213 }
1214
1215 #[actix_rt::test]
1216 async fn test_static_files_multiple_directories_default_handler() {
1217 let first_dir = tempfile::tempdir().unwrap();
1218 let second_dir = tempfile::tempdir().unwrap();
1219
1220 fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
1221
1222 let service = Files::new("/", vec![first_dir.path(), second_dir.path()])
1223 .default_handler(|req: ServiceRequest| async {
1224 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1225 })
1226 .new_service(())
1227 .await
1228 .unwrap();
1229
1230 let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
1231 let resp = test::call_service(&service, req).await;
1232 assert_eq!(resp.status(), StatusCode::OK);
1233 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
1234
1235 let req = TestRequest::with_uri("/missing.txt").to_srv_request();
1236 let resp = test::call_service(&service, req).await;
1237 assert_eq!(resp.status(), StatusCode::OK);
1238 assert_eq!(
1239 test::read_body(resp).await,
1240 Bytes::from_static(b"default content")
1241 );
1242 }
1243
1244 #[actix_rt::test]
1245 async fn test_static_files_multiple_directories_index_file() {
1246 let first_dir = tempfile::tempdir().unwrap();
1247 let second_dir = tempfile::tempdir().unwrap();
1248
1249 fs::create_dir(first_dir.path().join("nested")).unwrap();
1250 fs::create_dir(second_dir.path().join("nested")).unwrap();
1251 fs::write(
1252 second_dir.path().join("nested").join("index.html"),
1253 "second index",
1254 )
1255 .unwrap();
1256
1257 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1258 .index_file("index.html")
1259 .new_service(())
1260 .await
1261 .unwrap();
1262
1263 let req = TestRequest::with_uri("/nested/").to_srv_request();
1264 let resp = test::call_service(&service, req).await;
1265 assert_eq!(resp.status(), StatusCode::OK);
1266 assert_eq!(
1267 test::read_body(resp).await,
1268 Bytes::from_static(b"second index")
1269 );
1270 }
1271
1272 #[actix_rt::test]
1273 async fn test_static_files_multiple_directories_index_file_as_parent_falls_back() {
1274 let first_dir = tempfile::tempdir().unwrap();
1275 let second_dir = tempfile::tempdir().unwrap();
1276
1277 fs::create_dir(first_dir.path().join("nested")).unwrap();
1278 fs::write(first_dir.path().join("nested").join("index.html"), "file").unwrap();
1279 fs::create_dir(second_dir.path().join("nested")).unwrap();
1280 fs::create_dir(second_dir.path().join("nested").join("index.html")).unwrap();
1281 fs::write(
1282 second_dir
1283 .path()
1284 .join("nested")
1285 .join("index.html")
1286 .join("fallback.txt"),
1287 "fallback",
1288 )
1289 .unwrap();
1290
1291 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1292 .index_file("index.html/fallback.txt")
1293 .new_service(())
1294 .await
1295 .unwrap();
1296
1297 let req = TestRequest::with_uri("/nested/").to_srv_request();
1298 let resp = test::call_service(&service, req).await;
1299 assert_eq!(resp.status(), StatusCode::OK);
1300 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
1301 }
1302
1303 #[actix_rt::test]
1304 async fn test_static_files_index_file_error_falls_back_to_listing() {
1305 let dir = tempfile::tempdir().unwrap();
1306
1307 fs::write(dir.path().join("listed.txt"), "listed").unwrap();
1308
1309 let service = Files::new("/", dir.path())
1310 .index_file("index.html\0")
1311 .show_files_listing()
1312 .new_service(())
1313 .await
1314 .unwrap();
1315
1316 let req = TestRequest::with_uri("/").to_srv_request();
1317 let resp = test::call_service(&service, req).await;
1318 assert_eq!(resp.status(), StatusCode::OK);
1319 let bytes = test::read_body(resp).await;
1320 assert!(format!("{bytes:?}").contains("listed.txt"));
1321 }
1322
1323 #[actix_rt::test]
1324 async fn test_static_files_multiple_directories_show_files_listing() {
1325 let first_dir = tempfile::tempdir().unwrap();
1326 let second_dir = tempfile::tempdir().unwrap();
1327
1328 fs::write(first_dir.path().join("listed.txt"), "listed").unwrap();
1329
1330 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1331 .show_files_listing()
1332 .new_service(())
1333 .await
1334 .unwrap();
1335
1336 let req = TestRequest::with_uri("/").to_srv_request();
1337 let resp = test::call_service(&service, req).await;
1338 assert_eq!(resp.status(), StatusCode::OK);
1339 let bytes = test::read_body(resp).await;
1340 assert!(format!("{bytes:?}").contains("listed.txt"));
1341 }
1342
1343 #[actix_rt::test]
1344 async fn test_static_files_multiple_directories_redirect_precedence() {
1345 let first_dir = tempfile::tempdir().unwrap();
1346 let second_dir = tempfile::tempdir().unwrap();
1347
1348 fs::create_dir(first_dir.path().join("item")).unwrap();
1349 fs::write(second_dir.path().join("item"), "file").unwrap();
1350
1351 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1352 .show_files_listing()
1353 .redirect_to_slash_directory()
1354 .new_service(())
1355 .await
1356 .unwrap();
1357
1358 let req = TestRequest::with_uri("/item").to_srv_request();
1359 let resp = test::call_service(&service, req).await;
1360 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
1361 assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/item/");
1362 }
1363
1364 #[actix_rt::test]
1365 async fn test_default_handler_file_missing() {
1366 let st = Files::new("/", ".")
1367 .default_handler(|req: ServiceRequest| async {
1368 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1369 })
1370 .new_service(())
1371 .await
1372 .unwrap();
1373 let req = TestRequest::with_uri("/missing").to_srv_request();
1374 let resp = test::call_service(&st, req).await;
1375
1376 assert_eq!(resp.status(), StatusCode::OK);
1377 let bytes = test::read_body(resp).await;
1378 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
1379 }
1380
1381 #[actix_rt::test]
1382 async fn test_hidden_files_reject_cur_dir_segment() {
1383 let service = Files::new("/", Vec::<PathBuf>::new())
1384 .use_hidden_files()
1385 .default_handler(Files::new("/", ".").use_hidden_files())
1386 .new_service(())
1387 .await
1388 .unwrap();
1389
1390 let req = TestRequest::with_uri("/./Cargo.toml").to_srv_request();
1391 let resp = test::call_service(&service, req).await;
1392
1393 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1394 }
1395
1396 #[actix_rt::test]
1397 async fn test_serve_index_nested() {
1398 let service = Files::new(".", ".")
1399 .index_file("lib.rs")
1400 .new_service(())
1401 .await
1402 .unwrap();
1403
1404 let req = TestRequest::default().uri("/src").to_srv_request();
1405 let resp = test::call_service(&service, req).await;
1406
1407 assert_eq!(resp.status(), StatusCode::OK);
1408 assert_eq!(
1409 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1410 "text/x-rust"
1411 );
1412 assert_eq!(
1413 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
1414 "inline; filename=\"lib.rs\""
1415 );
1416 }
1417
1418 #[actix_rt::test]
1419 async fn integration_serve_index() {
1420 let srv = test::init_service(
1421 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
1422 )
1423 .await;
1424
1425 let req = TestRequest::get().uri("/test").to_request();
1426 let res = test::call_service(&srv, req).await;
1427 assert_eq!(res.status(), StatusCode::OK);
1428
1429 let bytes = test::read_body(res).await;
1430
1431 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1432 assert_eq!(bytes, data);
1433
1434 let req = TestRequest::get().uri("/test/").to_request();
1435 let res = test::call_service(&srv, req).await;
1436 assert_eq!(res.status(), StatusCode::OK);
1437
1438 let bytes = test::read_body(res).await;
1439 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1440 assert_eq!(bytes, data);
1441
1442 let req = TestRequest::get().uri("/test/unknown").to_request();
1444 let res = test::call_service(&srv, req).await;
1445 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1446
1447 let req = TestRequest::get().uri("/test/unknown/").to_request();
1448 let res = test::call_service(&srv, req).await;
1449 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1450 }
1451
1452 #[actix_rt::test]
1453 async fn integration_percent_encoded() {
1454 let srv = test::init_service(
1455 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
1456 )
1457 .await;
1458
1459 let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
1460 let res = test::call_service(&srv, req).await;
1461 assert_eq!(res.status(), StatusCode::OK);
1462
1463 let req = TestRequest::get().uri("/test%2Ftest.binary").to_request();
1465 let res = test::call_service(&srv, req).await;
1466 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1467
1468 let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request();
1469 let res = test::call_service(&srv, req).await;
1470 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1471 }
1472
1473 #[actix_rt::test]
1474 async fn test_percent_encoding_2() {
1475 let temp_dir = tempfile::tempdir().unwrap();
1476 let filename = match cfg!(unix) {
1477 true => "ض:?#[]{}<>()@!$&'`|*+,;= %20\n.test",
1478 false => "ض#[]{}()@!$&'`+,;= %20.test",
1479 };
1480 let filename_encoded = filename
1481 .as_bytes()
1482 .iter()
1483 .fold(String::new(), |mut buf, c| {
1484 write!(&mut buf, "%{:02X}", c).unwrap();
1485 buf
1486 });
1487 std::fs::File::create(temp_dir.path().join(filename)).unwrap();
1488
1489 let srv = test::init_service(App::new().service(Files::new("/", temp_dir.path()))).await;
1490
1491 let req = TestRequest::get()
1492 .uri(&format!("/{}", filename_encoded))
1493 .to_request();
1494 let res = test::call_service(&srv, req).await;
1495 assert_eq!(res.status(), StatusCode::OK);
1496 }
1497
1498 #[actix_rt::test]
1499 async fn test_serve_named_file() {
1500 let factory = NamedFile::open("Cargo.toml").unwrap();
1501 let srv = test::init_service(App::new().service(factory)).await;
1502
1503 let req = TestRequest::get().uri("/Cargo.toml").to_request();
1504 let res = test::call_service(&srv, req).await;
1505 assert_eq!(res.status(), StatusCode::OK);
1506
1507 let bytes = test::read_body(res).await;
1508 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1509 assert_eq!(bytes, data);
1510
1511 let req = TestRequest::get().uri("/test/unknown").to_request();
1512 let res = test::call_service(&srv, req).await;
1513 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1514 }
1515
1516 #[actix_rt::test]
1517 async fn test_serve_named_file_prefix() {
1518 let factory = NamedFile::open("Cargo.toml").unwrap();
1519 let srv =
1520 test::init_service(App::new().service(web::scope("/test").service(factory))).await;
1521
1522 let req = TestRequest::get().uri("/test/Cargo.toml").to_request();
1523 let res = test::call_service(&srv, req).await;
1524 assert_eq!(res.status(), StatusCode::OK);
1525
1526 let bytes = test::read_body(res).await;
1527 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1528 assert_eq!(bytes, data);
1529
1530 let req = TestRequest::get().uri("/Cargo.toml").to_request();
1531 let res = test::call_service(&srv, req).await;
1532 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1533 }
1534
1535 #[actix_rt::test]
1536 async fn test_named_file_default_service() {
1537 let factory = NamedFile::open("Cargo.toml").unwrap();
1538 let srv = test::init_service(App::new().default_service(factory)).await;
1539
1540 for route in ["/foobar", "/baz", "/"].iter() {
1541 let req = TestRequest::get().uri(route).to_request();
1542 let res = test::call_service(&srv, req).await;
1543 assert_eq!(res.status(), StatusCode::OK);
1544
1545 let bytes = test::read_body(res).await;
1546 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1547 assert_eq!(bytes, data);
1548 }
1549 }
1550
1551 #[actix_rt::test]
1552 async fn test_default_handler_named_file() {
1553 let factory = NamedFile::open("Cargo.toml").unwrap();
1554 let st = Files::new("/", ".")
1555 .default_handler(factory)
1556 .new_service(())
1557 .await
1558 .unwrap();
1559 let req = TestRequest::with_uri("/missing").to_srv_request();
1560 let resp = test::call_service(&st, req).await;
1561
1562 assert_eq!(resp.status(), StatusCode::OK);
1563 let bytes = test::read_body(resp).await;
1564 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1565 assert_eq!(bytes, data);
1566 }
1567
1568 #[actix_rt::test]
1569 async fn test_symlinks() {
1570 let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
1571
1572 let req = TestRequest::get()
1573 .uri("/test/tests/symlink-test.png")
1574 .to_request();
1575 let res = test::call_service(&srv, req).await;
1576 assert_eq!(res.status(), StatusCode::OK);
1577 assert_eq!(
1578 res.headers().get(header::CONTENT_DISPOSITION).unwrap(),
1579 "inline; filename=\"symlink-test.png\""
1580 );
1581 }
1582
1583 #[actix_rt::test]
1584 async fn test_index_with_show_files_listing() {
1585 let service = Files::new(".", ".")
1586 .index_file("lib.rs")
1587 .show_files_listing()
1588 .new_service(())
1589 .await
1590 .unwrap();
1591
1592 let req = TestRequest::default().uri("/src").to_srv_request();
1594 let resp = test::call_service(&service, req).await;
1595 assert_eq!(resp.status(), StatusCode::OK);
1596 assert_eq!(
1597 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1598 "text/x-rust"
1599 );
1600
1601 let req = TestRequest::default().uri("/tests").to_srv_request();
1603 let resp = test::call_service(&service, req).await;
1604 assert_eq!(
1605 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1606 "text/html; charset=utf-8"
1607 );
1608 let bytes = test::read_body(resp).await;
1609 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
1610 }
1611
1612 #[actix_rt::test]
1613 async fn test_path_filter() {
1614 let st = Files::new("/", ".")
1616 .path_filter(|path, _| path.components().count() == 1)
1617 .new_service(())
1618 .await
1619 .unwrap();
1620
1621 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1622 let resp = test::call_service(&st, req).await;
1623 assert_eq!(resp.status(), StatusCode::OK);
1624
1625 let req = TestRequest::with_uri("/src/lib.rs").to_srv_request();
1626 let resp = test::call_service(&st, req).await;
1627 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1628 }
1629
1630 #[actix_rt::test]
1631 async fn test_default_handler_filter() {
1632 let st = Files::new("/", ".")
1633 .default_handler(|req: ServiceRequest| async {
1634 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1635 })
1636 .path_filter(|path, _| path.extension() == Some("png".as_ref()))
1637 .new_service(())
1638 .await
1639 .unwrap();
1640 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1641 let resp = test::call_service(&st, req).await;
1642
1643 assert_eq!(resp.status(), StatusCode::OK);
1644 let bytes = test::read_body(resp).await;
1645 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
1646 }
1647}