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_web::{
81 dev::ServiceFactory,
82 guard,
83 http::{
84 header::{self, ContentDisposition, DispositionParam},
85 Method, StatusCode,
86 },
87 middleware::Compress,
88 test::{self, TestRequest},
89 web::{self, Bytes},
90 App, HttpResponse, Responder,
91 };
92
93 use super::*;
94 use crate::named::File;
95
96 #[actix_web::test]
97 async fn test_file_extension_to_mime() {
98 let m = file_extension_to_mime("");
99 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
100
101 let m = file_extension_to_mime("jpg");
102 assert_eq!(m, mime::IMAGE_JPEG);
103
104 let m = file_extension_to_mime("invalid extension!!");
105 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
106
107 let m = file_extension_to_mime("");
108 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
109 }
110
111 #[actix_rt::test]
112 async fn test_if_modified_since_without_if_none_match() {
113 let file = NamedFile::open("Cargo.toml").unwrap();
114 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
115
116 let req = TestRequest::default()
117 .insert_header((header::IF_MODIFIED_SINCE, since))
118 .to_http_request();
119 let resp = file.respond_to(&req);
120 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
121 }
122
123 #[actix_rt::test]
124 async fn test_if_modified_since_without_if_none_match_same() {
125 let file = NamedFile::open("Cargo.toml").unwrap();
126 let since = file.last_modified().unwrap();
127
128 let req = TestRequest::default()
129 .insert_header((header::IF_MODIFIED_SINCE, since))
130 .to_http_request();
131 let resp = file.respond_to(&req);
132 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
133 }
134
135 #[actix_rt::test]
136 async fn test_if_modified_since_with_if_none_match() {
137 let file = NamedFile::open("Cargo.toml").unwrap();
138 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
139
140 let req = TestRequest::default()
141 .insert_header((header::IF_NONE_MATCH, "miss_etag"))
142 .insert_header((header::IF_MODIFIED_SINCE, since))
143 .to_http_request();
144 let resp = file.respond_to(&req);
145 assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
146 }
147
148 #[actix_rt::test]
149 async fn test_if_unmodified_since() {
150 let file = NamedFile::open("Cargo.toml").unwrap();
151 let since = file.last_modified().unwrap();
152
153 let req = TestRequest::default()
154 .insert_header((header::IF_UNMODIFIED_SINCE, since))
155 .to_http_request();
156 let resp = file.respond_to(&req);
157 assert_eq!(resp.status(), StatusCode::OK);
158 }
159
160 #[actix_rt::test]
161 async fn test_if_unmodified_since_failed() {
162 let file = NamedFile::open("Cargo.toml").unwrap();
163 let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
164
165 let req = TestRequest::default()
166 .insert_header((header::IF_UNMODIFIED_SINCE, since))
167 .to_http_request();
168 let resp = file.respond_to(&req);
169 assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
170 }
171
172 #[actix_rt::test]
173 async fn test_named_file_text() {
174 assert!(NamedFile::open("test--").is_err());
175 let mut file = NamedFile::open("Cargo.toml").unwrap();
176 {
177 file.file();
178 let _f: &File = &file;
179 }
180 {
181 let _f: &mut File = &mut file;
182 }
183
184 let req = TestRequest::default().to_http_request();
185 let resp = file.respond_to(&req);
186 assert_eq!(
187 resp.headers().get(header::CONTENT_TYPE).unwrap(),
188 "text/x-toml"
189 );
190 assert_eq!(
191 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
192 "inline; filename=\"Cargo.toml\""
193 );
194 }
195
196 #[actix_rt::test]
197 async fn test_named_file_content_disposition() {
198 assert!(NamedFile::open("test--").is_err());
199 let mut file = NamedFile::open("Cargo.toml").unwrap();
200 {
201 file.file();
202 let _f: &File = &file;
203 }
204 {
205 let _f: &mut File = &mut file;
206 }
207
208 let req = TestRequest::default().to_http_request();
209 let resp = file.respond_to(&req);
210 assert_eq!(
211 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
212 "inline; filename=\"Cargo.toml\""
213 );
214
215 let file = NamedFile::open("Cargo.toml")
216 .unwrap()
217 .disable_content_disposition();
218 let req = TestRequest::default().to_http_request();
219 let resp = file.respond_to(&req);
220 assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
221 }
222
223 #[actix_rt::test]
224 async fn test_named_file_non_ascii_file_name() {
225 let file = crate::named::File::open("Cargo.toml").unwrap();
226
227 let mut file = NamedFile::from_file(file, "貨物.toml").unwrap();
228 {
229 file.file();
230 let _f: &File = &file;
231 }
232 {
233 let _f: &mut File = &mut file;
234 }
235
236 let req = TestRequest::default().to_http_request();
237 let resp = file.respond_to(&req);
238 assert_eq!(
239 resp.headers().get(header::CONTENT_TYPE).unwrap(),
240 "text/x-toml"
241 );
242 assert_eq!(
243 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
244 "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
245 );
246 }
247
248 #[actix_rt::test]
249 async fn test_named_file_set_content_type() {
250 let mut file = NamedFile::open("Cargo.toml")
251 .unwrap()
252 .set_content_type(mime::TEXT_XML);
253 {
254 file.file();
255 let _f: &File = &file;
256 }
257 {
258 let _f: &mut File = &mut file;
259 }
260
261 let req = TestRequest::default().to_http_request();
262 let resp = file.respond_to(&req);
263 assert_eq!(
264 resp.headers().get(header::CONTENT_TYPE).unwrap(),
265 "text/xml"
266 );
267 assert_eq!(
268 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
269 "inline; filename=\"Cargo.toml\""
270 );
271 }
272
273 #[actix_rt::test]
274 async fn test_named_file_image() {
275 let mut file = NamedFile::open("tests/test.png").unwrap();
276 {
277 file.file();
278 let _f: &File = &file;
279 }
280 {
281 let _f: &mut File = &mut file;
282 }
283
284 let req = TestRequest::default().to_http_request();
285 let resp = file.respond_to(&req);
286 assert_eq!(
287 resp.headers().get(header::CONTENT_TYPE).unwrap(),
288 "image/png"
289 );
290 assert_eq!(
291 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
292 "inline; filename=\"test.png\""
293 );
294 }
295
296 #[actix_rt::test]
297 async fn test_named_file_javascript() {
298 let file = NamedFile::open("tests/test.js").unwrap();
299
300 let req = TestRequest::default().to_http_request();
301 let resp = file.respond_to(&req);
302 assert_eq!(
303 resp.headers().get(header::CONTENT_TYPE).unwrap(),
304 "text/javascript",
305 );
306 assert_eq!(
307 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
308 "inline; filename=\"test.js\"",
309 );
310 }
311
312 #[actix_rt::test]
313 async fn test_named_file_image_attachment() {
314 let cd = ContentDisposition {
315 disposition: DispositionType::Attachment,
316 parameters: vec![DispositionParam::Filename(String::from("test.png"))],
317 };
318 let mut file = NamedFile::open("tests/test.png")
319 .unwrap()
320 .set_content_disposition(cd);
321 {
322 file.file();
323 let _f: &File = &file;
324 }
325 {
326 let _f: &mut File = &mut file;
327 }
328
329 let req = TestRequest::default().to_http_request();
330 let resp = file.respond_to(&req);
331 assert_eq!(
332 resp.headers().get(header::CONTENT_TYPE).unwrap(),
333 "image/png"
334 );
335 assert_eq!(
336 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
337 "attachment; filename=\"test.png\""
338 );
339 }
340
341 #[actix_rt::test]
342 async fn test_named_file_binary() {
343 let mut file = NamedFile::open("tests/test.binary").unwrap();
344 {
345 file.file();
346 let _f: &File = &file;
347 }
348 {
349 let _f: &mut File = &mut file;
350 }
351
352 let req = TestRequest::default().to_http_request();
353 let resp = file.respond_to(&req);
354 assert_eq!(
355 resp.headers().get(header::CONTENT_TYPE).unwrap(),
356 "application/octet-stream"
357 );
358 assert_eq!(
359 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
360 "attachment; filename=\"test.binary\""
361 );
362 }
363
364 #[allow(deprecated)]
365 #[actix_rt::test]
366 async fn status_code_customize_same_output() {
367 let file1 = NamedFile::open("Cargo.toml")
368 .unwrap()
369 .set_status_code(StatusCode::NOT_FOUND);
370
371 let file2 = NamedFile::open("Cargo.toml")
372 .unwrap()
373 .customize()
374 .with_status(StatusCode::NOT_FOUND);
375
376 let req = TestRequest::default().to_http_request();
377 let res1 = file1.respond_to(&req);
378 let res2 = file2.respond_to(&req);
379
380 assert_eq!(res1.status(), StatusCode::NOT_FOUND);
381 assert_eq!(res2.status(), StatusCode::NOT_FOUND);
382 }
383
384 #[actix_rt::test]
385 async fn test_named_file_status_code_text() {
386 let mut file = NamedFile::open("Cargo.toml").unwrap();
387
388 {
389 file.file();
390 let _f: &File = &file;
391 }
392
393 {
394 let _f: &mut File = &mut file;
395 }
396
397 let file = file.customize().with_status(StatusCode::NOT_FOUND);
398
399 let req = TestRequest::default().to_http_request();
400 let resp = file.respond_to(&req);
401 assert_eq!(
402 resp.headers().get(header::CONTENT_TYPE).unwrap(),
403 "text/x-toml"
404 );
405 assert_eq!(
406 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
407 "inline; filename=\"Cargo.toml\""
408 );
409 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
410 }
411
412 #[actix_rt::test]
413 async fn test_mime_override() {
414 fn all_attachment(_: &mime::Name<'_>) -> DispositionType {
415 DispositionType::Attachment
416 }
417
418 let srv = test::init_service(
419 App::new().service(
420 Files::new("/", ".")
421 .mime_override(all_attachment)
422 .index_file("Cargo.toml"),
423 ),
424 )
425 .await;
426
427 let request = TestRequest::get().uri("/").to_request();
428 let response = test::call_service(&srv, request).await;
429 assert_eq!(response.status(), StatusCode::OK);
430
431 let content_disposition = response
432 .headers()
433 .get(header::CONTENT_DISPOSITION)
434 .expect("To have CONTENT_DISPOSITION");
435 let content_disposition = content_disposition
436 .to_str()
437 .expect("Convert CONTENT_DISPOSITION to str");
438 assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
439 }
440
441 #[actix_rt::test]
442 async fn test_named_file_ranges_status_code() {
443 let srv = test::init_service(
444 App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
445 )
446 .await;
447
448 let request = TestRequest::get()
450 .uri("/t%65st/Cargo.toml")
451 .insert_header((header::RANGE, "bytes=10-20"))
452 .to_request();
453 let response = test::call_service(&srv, request).await;
454 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
455
456 let request = TestRequest::get()
458 .uri("/t%65st/Cargo.toml")
459 .insert_header((header::RANGE, "bytes=1-0"))
460 .to_request();
461 let response = test::call_service(&srv, request).await;
462
463 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
464 }
465
466 #[actix_rt::test]
467 async fn test_named_file_empty_range_headers() {
468 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
469
470 for range in ["", "bytes="] {
471 let response = srv
472 .get("/tests/test.binary")
473 .insert_header((header::RANGE, range))
474 .send()
475 .await
476 .unwrap();
477
478 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
479 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
480 assert_eq!(content_range.to_str().unwrap(), "bytes */100");
481 }
482 }
483
484 #[actix_rt::test]
485 async fn test_named_file_content_range_headers() {
486 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
487
488 let response = srv
490 .get("/tests/test.binary")
491 .insert_header((header::RANGE, "bytes=10-20"))
492 .send()
493 .await
494 .unwrap();
495 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
496 assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
497
498 let response = srv
500 .get("/tests/test.binary")
501 .insert_header((header::RANGE, "bytes=10-5"))
502 .send()
503 .await
504 .unwrap();
505 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
506 assert_eq!(content_range.to_str().unwrap(), "bytes */100");
507 }
508
509 #[actix_rt::test]
510 async fn test_named_file_range_header_from_zero_to_end_returns_partial_content() {
511 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
512
513 let response = srv
514 .get("/tests/test.binary")
515 .insert_header((header::RANGE, "bytes=0-"))
516 .send()
517 .await
518 .unwrap();
519
520 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
521
522 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
523 assert_eq!(content_range.to_str().unwrap(), "bytes 0-99/100");
524
525 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
526 assert_eq!(content_length.to_str().unwrap(), "100");
527
528 let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
530 assert!(transfer_encoding.is_none());
531 }
532
533 #[actix_rt::test]
534 async fn test_named_file_content_length_headers() {
535 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
536
537 let response = srv
539 .get("/tests/test.binary")
540 .insert_header((header::RANGE, "bytes=10-20"))
541 .send()
542 .await
543 .unwrap();
544 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
545 assert_eq!(content_length.to_str().unwrap(), "11");
546
547 let response = srv
549 .get("/tests/test.binary")
550 .insert_header((header::RANGE, "bytes=0-20"))
551 .send()
552 .await
553 .unwrap();
554 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
555 assert_eq!(content_length.to_str().unwrap(), "21");
556
557 let mut response = srv.get("/tests/test.binary").send().await.unwrap();
559 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
560 assert_eq!(content_length.to_str().unwrap(), "100");
561
562 let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
564 assert!(transfer_encoding.is_none());
565
566 let bytes = response.body().await.unwrap();
568 let data = web::Bytes::from(fs::read("tests/test.binary").unwrap());
569 assert_eq!(bytes, data);
570 }
571
572 #[actix_rt::test]
573 async fn test_head_content_length_headers() {
574 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
575
576 let response = srv.head("/tests/test.binary").send().await.unwrap();
577
578 let content_length = response
579 .headers()
580 .get(header::CONTENT_LENGTH)
581 .unwrap()
582 .to_str()
583 .unwrap();
584
585 assert_eq!(content_length, "100");
586 }
587
588 #[actix_rt::test]
589 async fn test_static_files_with_spaces() {
590 let srv =
591 test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
592 .await;
593 let request = TestRequest::get()
594 .uri("/tests/test%20space.binary")
595 .to_request();
596 let response = test::call_service(&srv, request).await;
597 assert_eq!(response.status(), StatusCode::OK);
598
599 let bytes = test::read_body(response).await;
600 let data = web::Bytes::from(fs::read("tests/test space.binary").unwrap());
601 assert_eq!(bytes, data);
602 }
603
604 #[cfg(not(target_os = "windows"))]
605 #[actix_rt::test]
606 async fn test_static_files_with_special_characters() {
607 let temp_dir = tempfile::tempdir().unwrap();
610 let file_with_newlines = temp_dir.path().join("test\n\x0B\x0C\rnewline.text");
611 fs::write(&file_with_newlines, "Look at my newlines").unwrap();
612
613 let srv = test::init_service(
614 App::new().service(Files::new("/", temp_dir.path()).index_file("Cargo.toml")),
615 )
616 .await;
617 let request = TestRequest::get()
618 .uri("/test%0A%0B%0C%0Dnewline.text")
619 .to_request();
620 let response = test::call_service(&srv, request).await;
621 assert_eq!(response.status(), StatusCode::OK);
622
623 let bytes = test::read_body(response).await;
624 let data = web::Bytes::from(fs::read(file_with_newlines).unwrap());
625 assert_eq!(bytes, data);
626 }
627
628 #[actix_rt::test]
629 async fn test_files_not_allowed() {
630 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
631
632 let req = TestRequest::default()
633 .uri("/Cargo.toml")
634 .method(Method::POST)
635 .to_request();
636
637 let resp = test::call_service(&srv, req).await;
638 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
639
640 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
641 let req = TestRequest::default()
642 .method(Method::PUT)
643 .uri("/Cargo.toml")
644 .to_request();
645 let resp = test::call_service(&srv, req).await;
646 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
647 }
648
649 #[actix_rt::test]
650 async fn test_files_guards() {
651 let srv = test::init_service(
652 App::new().service(Files::new("/", ".").method_guard(guard::Post())),
653 )
654 .await;
655
656 let req = TestRequest::default()
657 .uri("/Cargo.toml")
658 .method(Method::POST)
659 .to_request();
660
661 let resp = test::call_service(&srv, req).await;
662 assert_eq!(resp.status(), StatusCode::OK);
663 }
664
665 #[actix_rt::test]
666 async fn test_named_file_content_encoding() {
667 let srv = test::init_service(App::new().wrap(Compress::default()).service(
668 web::resource("/").to(|| async {
669 NamedFile::open("Cargo.toml")
670 .unwrap()
671 .set_content_encoding(header::ContentEncoding::Identity)
672 }),
673 ))
674 .await;
675
676 let request = TestRequest::get()
677 .uri("/")
678 .insert_header((header::ACCEPT_ENCODING, "gzip"))
679 .to_request();
680 let res = test::call_service(&srv, request).await;
681 assert_eq!(res.status(), StatusCode::OK);
682 assert!(res.headers().contains_key(header::CONTENT_ENCODING));
683 assert!(!test::read_body(res).await.is_empty());
684 }
685
686 #[actix_rt::test]
687 async fn test_named_file_content_encoding_gzip() {
688 let srv = test::init_service(App::new().wrap(Compress::default()).service(
689 web::resource("/").to(|| async {
690 NamedFile::open("Cargo.toml")
691 .unwrap()
692 .set_content_encoding(header::ContentEncoding::Gzip)
693 }),
694 ))
695 .await;
696
697 let request = TestRequest::get()
698 .uri("/")
699 .insert_header((header::ACCEPT_ENCODING, "gzip"))
700 .to_request();
701 let res = test::call_service(&srv, request).await;
702 assert_eq!(res.status(), StatusCode::OK);
703 assert_eq!(
704 res.headers()
705 .get(header::CONTENT_ENCODING)
706 .unwrap()
707 .to_str()
708 .unwrap(),
709 "gzip"
710 );
711 }
712
713 #[actix_rt::test]
714 async fn test_named_file_allowed_method() {
715 let req = TestRequest::default().method(Method::GET).to_http_request();
716 let file = NamedFile::open("Cargo.toml").unwrap();
717 let resp = file.respond_to(&req);
718 assert_eq!(resp.status(), StatusCode::OK);
719 }
720
721 #[actix_rt::test]
722 async fn test_static_files() {
723 let srv =
724 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
725 let req = TestRequest::with_uri("/missing").to_request();
726
727 let resp = test::call_service(&srv, req).await;
728 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
729
730 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
731
732 let req = TestRequest::default().to_request();
733 let resp = test::call_service(&srv, req).await;
734 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
735
736 let srv =
737 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
738 let req = TestRequest::with_uri("/tests").to_request();
739 let resp = test::call_service(&srv, req).await;
740 assert_eq!(
741 resp.headers().get(header::CONTENT_TYPE).unwrap(),
742 "text/html; charset=utf-8"
743 );
744
745 let bytes = test::read_body(resp).await;
746 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
747 }
748
749 #[actix_rt::test]
750 async fn test_redirect_to_slash_directory() {
751 let srv = test::init_service(
753 App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
754 )
755 .await;
756 let req = TestRequest::with_uri("/tests").to_request();
757 let resp = test::call_service(&srv, req).await;
758 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
759
760 let srv = test::init_service(
762 App::new().service(
763 Files::new("/", ".")
764 .index_file("test.png")
765 .redirect_to_slash_directory(),
766 ),
767 )
768 .await;
769 let req = TestRequest::with_uri("/tests").to_request();
770 let resp = test::call_service(&srv, req).await;
771 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
772
773 let srv = test::init_service(
775 App::new().service(
776 Files::new("/", ".")
777 .index_file("test.png")
778 .redirect_to_slash_directory()
779 .with_permanent_redirect(),
780 ),
781 )
782 .await;
783 let req = TestRequest::with_uri("/tests").to_request();
784 let resp = test::call_service(&srv, req).await;
785 assert_eq!(resp.status(), StatusCode::PERMANENT_REDIRECT);
786
787 let srv = test::init_service(
789 App::new().service(
790 Files::new("/", ".")
791 .show_files_listing()
792 .redirect_to_slash_directory(),
793 ),
794 )
795 .await;
796 let req = TestRequest::with_uri("/tests").to_request();
797 let resp = test::call_service(&srv, req).await;
798 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
799
800 let req = TestRequest::with_uri("/not_existing").to_request();
802 let resp = test::call_service(&srv, req).await;
803 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
804 }
805
806 #[actix_rt::test]
807 async fn test_static_files_bad_directory() {
808 let service = Files::new("/", "./missing").new_service(()).await.unwrap();
809
810 let req = TestRequest::with_uri("/").to_srv_request();
811 let resp = test::call_service(&service, req).await;
812
813 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
814 }
815
816 #[actix_rt::test]
817 async fn test_static_files_bad_directory_does_not_serve_cwd_files() {
818 let service = Files::new("/", "./missing").new_service(()).await.unwrap();
819
820 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
821 let resp = test::call_service(&service, req).await;
822
823 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
824 }
825
826 #[actix_rt::test]
827 async fn test_static_files_accepts_borrowed_os_string_directory() {
828 let dir = OsString::from(".");
829 let service = Files::new("/", &dir).new_service(()).await.unwrap();
830
831 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
832 let resp = test::call_service(&service, req).await;
833
834 assert_eq!(resp.status(), StatusCode::OK);
835 }
836
837 #[actix_rt::test]
838 async fn test_static_files_empty_directories() {
839 let service = Files::new("/", Vec::<PathBuf>::new())
840 .new_service(())
841 .await
842 .unwrap();
843
844 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
845 let resp = test::call_service(&service, req).await;
846 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
847
848 let service = Files::new("/", Vec::<PathBuf>::new())
849 .default_handler(|req: ServiceRequest| async {
850 Ok(req.into_response(HttpResponse::Ok().body("default content")))
851 })
852 .new_service(())
853 .await
854 .unwrap();
855
856 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
857 let resp = test::call_service(&service, req).await;
858 assert_eq!(resp.status(), StatusCode::OK);
859 assert_eq!(
860 test::read_body(resp).await,
861 Bytes::from_static(b"default content")
862 );
863 }
864
865 #[actix_rt::test]
866 async fn test_static_files_multiple_directories() {
867 let first_dir = tempfile::tempdir().unwrap();
868 let second_dir = tempfile::tempdir().unwrap();
869
870 fs::write(first_dir.path().join("shared.txt"), "first").unwrap();
871 fs::write(second_dir.path().join("shared.txt"), "second").unwrap();
872 fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
873
874 let service = Files::new("/", [first_dir.path(), second_dir.path()])
875 .new_service(())
876 .await
877 .unwrap();
878
879 let req = TestRequest::with_uri("/shared.txt").to_srv_request();
880 let resp = test::call_service(&service, req).await;
881 assert_eq!(resp.status(), StatusCode::OK);
882 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"first"));
883
884 let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
885 let resp = test::call_service(&service, req).await;
886 assert_eq!(resp.status(), StatusCode::OK);
887 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
888 }
889
890 #[actix_rt::test]
891 async fn test_static_files_multiple_directories_file_as_parent_falls_back() {
892 let first_dir = tempfile::tempdir().unwrap();
893 let second_dir = tempfile::tempdir().unwrap();
894
895 fs::write(first_dir.path().join("assets"), "file").unwrap();
896 fs::create_dir(second_dir.path().join("assets")).unwrap();
897 fs::write(
898 second_dir.path().join("assets").join("fallback.txt"),
899 "fallback",
900 )
901 .unwrap();
902
903 let service = Files::new("/", [first_dir.path(), second_dir.path()])
904 .new_service(())
905 .await
906 .unwrap();
907
908 let req = TestRequest::with_uri("/assets/fallback.txt").to_srv_request();
909 let resp = test::call_service(&service, req).await;
910 assert_eq!(resp.status(), StatusCode::OK);
911 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
912 }
913
914 #[actix_rt::test]
915 async fn test_static_files_multiple_directories_default_handler() {
916 let first_dir = tempfile::tempdir().unwrap();
917 let second_dir = tempfile::tempdir().unwrap();
918
919 fs::write(second_dir.path().join("fallback.txt"), "fallback").unwrap();
920
921 let service = Files::new("/", vec![first_dir.path(), second_dir.path()])
922 .default_handler(|req: ServiceRequest| async {
923 Ok(req.into_response(HttpResponse::Ok().body("default content")))
924 })
925 .new_service(())
926 .await
927 .unwrap();
928
929 let req = TestRequest::with_uri("/fallback.txt").to_srv_request();
930 let resp = test::call_service(&service, req).await;
931 assert_eq!(resp.status(), StatusCode::OK);
932 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
933
934 let req = TestRequest::with_uri("/missing.txt").to_srv_request();
935 let resp = test::call_service(&service, req).await;
936 assert_eq!(resp.status(), StatusCode::OK);
937 assert_eq!(
938 test::read_body(resp).await,
939 Bytes::from_static(b"default content")
940 );
941 }
942
943 #[actix_rt::test]
944 async fn test_static_files_multiple_directories_index_file() {
945 let first_dir = tempfile::tempdir().unwrap();
946 let second_dir = tempfile::tempdir().unwrap();
947
948 fs::create_dir(first_dir.path().join("nested")).unwrap();
949 fs::create_dir(second_dir.path().join("nested")).unwrap();
950 fs::write(
951 second_dir.path().join("nested").join("index.html"),
952 "second index",
953 )
954 .unwrap();
955
956 let service = Files::new("/", [first_dir.path(), second_dir.path()])
957 .index_file("index.html")
958 .new_service(())
959 .await
960 .unwrap();
961
962 let req = TestRequest::with_uri("/nested/").to_srv_request();
963 let resp = test::call_service(&service, req).await;
964 assert_eq!(resp.status(), StatusCode::OK);
965 assert_eq!(
966 test::read_body(resp).await,
967 Bytes::from_static(b"second index")
968 );
969 }
970
971 #[actix_rt::test]
972 async fn test_static_files_multiple_directories_index_file_as_parent_falls_back() {
973 let first_dir = tempfile::tempdir().unwrap();
974 let second_dir = tempfile::tempdir().unwrap();
975
976 fs::create_dir(first_dir.path().join("nested")).unwrap();
977 fs::write(first_dir.path().join("nested").join("index.html"), "file").unwrap();
978 fs::create_dir(second_dir.path().join("nested")).unwrap();
979 fs::create_dir(second_dir.path().join("nested").join("index.html")).unwrap();
980 fs::write(
981 second_dir
982 .path()
983 .join("nested")
984 .join("index.html")
985 .join("fallback.txt"),
986 "fallback",
987 )
988 .unwrap();
989
990 let service = Files::new("/", [first_dir.path(), second_dir.path()])
991 .index_file("index.html/fallback.txt")
992 .new_service(())
993 .await
994 .unwrap();
995
996 let req = TestRequest::with_uri("/nested/").to_srv_request();
997 let resp = test::call_service(&service, req).await;
998 assert_eq!(resp.status(), StatusCode::OK);
999 assert_eq!(test::read_body(resp).await, Bytes::from_static(b"fallback"));
1000 }
1001
1002 #[actix_rt::test]
1003 async fn test_static_files_index_file_error_falls_back_to_listing() {
1004 let dir = tempfile::tempdir().unwrap();
1005
1006 fs::write(dir.path().join("listed.txt"), "listed").unwrap();
1007
1008 let service = Files::new("/", dir.path())
1009 .index_file("index.html\0")
1010 .show_files_listing()
1011 .new_service(())
1012 .await
1013 .unwrap();
1014
1015 let req = TestRequest::with_uri("/").to_srv_request();
1016 let resp = test::call_service(&service, req).await;
1017 assert_eq!(resp.status(), StatusCode::OK);
1018 let bytes = test::read_body(resp).await;
1019 assert!(format!("{bytes:?}").contains("listed.txt"));
1020 }
1021
1022 #[actix_rt::test]
1023 async fn test_static_files_multiple_directories_show_files_listing() {
1024 let first_dir = tempfile::tempdir().unwrap();
1025 let second_dir = tempfile::tempdir().unwrap();
1026
1027 fs::write(first_dir.path().join("listed.txt"), "listed").unwrap();
1028
1029 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1030 .show_files_listing()
1031 .new_service(())
1032 .await
1033 .unwrap();
1034
1035 let req = TestRequest::with_uri("/").to_srv_request();
1036 let resp = test::call_service(&service, req).await;
1037 assert_eq!(resp.status(), StatusCode::OK);
1038 let bytes = test::read_body(resp).await;
1039 assert!(format!("{bytes:?}").contains("listed.txt"));
1040 }
1041
1042 #[actix_rt::test]
1043 async fn test_static_files_multiple_directories_redirect_precedence() {
1044 let first_dir = tempfile::tempdir().unwrap();
1045 let second_dir = tempfile::tempdir().unwrap();
1046
1047 fs::create_dir(first_dir.path().join("item")).unwrap();
1048 fs::write(second_dir.path().join("item"), "file").unwrap();
1049
1050 let service = Files::new("/", [first_dir.path(), second_dir.path()])
1051 .show_files_listing()
1052 .redirect_to_slash_directory()
1053 .new_service(())
1054 .await
1055 .unwrap();
1056
1057 let req = TestRequest::with_uri("/item").to_srv_request();
1058 let resp = test::call_service(&service, req).await;
1059 assert_eq!(resp.status(), StatusCode::TEMPORARY_REDIRECT);
1060 assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/item/");
1061 }
1062
1063 #[actix_rt::test]
1064 async fn test_default_handler_file_missing() {
1065 let st = Files::new("/", ".")
1066 .default_handler(|req: ServiceRequest| async {
1067 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1068 })
1069 .new_service(())
1070 .await
1071 .unwrap();
1072 let req = TestRequest::with_uri("/missing").to_srv_request();
1073 let resp = test::call_service(&st, req).await;
1074
1075 assert_eq!(resp.status(), StatusCode::OK);
1076 let bytes = test::read_body(resp).await;
1077 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
1078 }
1079
1080 #[actix_rt::test]
1081 async fn test_hidden_files_reject_cur_dir_segment() {
1082 let service = Files::new("/", Vec::<PathBuf>::new())
1083 .use_hidden_files()
1084 .default_handler(Files::new("/", ".").use_hidden_files())
1085 .new_service(())
1086 .await
1087 .unwrap();
1088
1089 let req = TestRequest::with_uri("/./Cargo.toml").to_srv_request();
1090 let resp = test::call_service(&service, req).await;
1091
1092 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1093 }
1094
1095 #[actix_rt::test]
1096 async fn test_serve_index_nested() {
1097 let service = Files::new(".", ".")
1098 .index_file("lib.rs")
1099 .new_service(())
1100 .await
1101 .unwrap();
1102
1103 let req = TestRequest::default().uri("/src").to_srv_request();
1104 let resp = test::call_service(&service, req).await;
1105
1106 assert_eq!(resp.status(), StatusCode::OK);
1107 assert_eq!(
1108 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1109 "text/x-rust"
1110 );
1111 assert_eq!(
1112 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
1113 "inline; filename=\"lib.rs\""
1114 );
1115 }
1116
1117 #[actix_rt::test]
1118 async fn integration_serve_index() {
1119 let srv = test::init_service(
1120 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
1121 )
1122 .await;
1123
1124 let req = TestRequest::get().uri("/test").to_request();
1125 let res = test::call_service(&srv, req).await;
1126 assert_eq!(res.status(), StatusCode::OK);
1127
1128 let bytes = test::read_body(res).await;
1129
1130 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1131 assert_eq!(bytes, data);
1132
1133 let req = TestRequest::get().uri("/test/").to_request();
1134 let res = test::call_service(&srv, req).await;
1135 assert_eq!(res.status(), StatusCode::OK);
1136
1137 let bytes = test::read_body(res).await;
1138 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1139 assert_eq!(bytes, data);
1140
1141 let req = TestRequest::get().uri("/test/unknown").to_request();
1143 let res = test::call_service(&srv, req).await;
1144 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1145
1146 let req = TestRequest::get().uri("/test/unknown/").to_request();
1147 let res = test::call_service(&srv, req).await;
1148 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1149 }
1150
1151 #[actix_rt::test]
1152 async fn integration_percent_encoded() {
1153 let srv = test::init_service(
1154 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
1155 )
1156 .await;
1157
1158 let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
1159 let res = test::call_service(&srv, req).await;
1160 assert_eq!(res.status(), StatusCode::OK);
1161
1162 let req = TestRequest::get().uri("/test%2Ftest.binary").to_request();
1164 let res = test::call_service(&srv, req).await;
1165 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1166
1167 let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request();
1168 let res = test::call_service(&srv, req).await;
1169 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1170 }
1171
1172 #[actix_rt::test]
1173 async fn test_percent_encoding_2() {
1174 let temp_dir = tempfile::tempdir().unwrap();
1175 let filename = match cfg!(unix) {
1176 true => "ض:?#[]{}<>()@!$&'`|*+,;= %20\n.test",
1177 false => "ض#[]{}()@!$&'`+,;= %20.test",
1178 };
1179 let filename_encoded = filename
1180 .as_bytes()
1181 .iter()
1182 .fold(String::new(), |mut buf, c| {
1183 write!(&mut buf, "%{:02X}", c).unwrap();
1184 buf
1185 });
1186 std::fs::File::create(temp_dir.path().join(filename)).unwrap();
1187
1188 let srv = test::init_service(App::new().service(Files::new("/", temp_dir.path()))).await;
1189
1190 let req = TestRequest::get()
1191 .uri(&format!("/{}", filename_encoded))
1192 .to_request();
1193 let res = test::call_service(&srv, req).await;
1194 assert_eq!(res.status(), StatusCode::OK);
1195 }
1196
1197 #[actix_rt::test]
1198 async fn test_serve_named_file() {
1199 let factory = NamedFile::open("Cargo.toml").unwrap();
1200 let srv = test::init_service(App::new().service(factory)).await;
1201
1202 let req = TestRequest::get().uri("/Cargo.toml").to_request();
1203 let res = test::call_service(&srv, req).await;
1204 assert_eq!(res.status(), StatusCode::OK);
1205
1206 let bytes = test::read_body(res).await;
1207 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1208 assert_eq!(bytes, data);
1209
1210 let req = TestRequest::get().uri("/test/unknown").to_request();
1211 let res = test::call_service(&srv, req).await;
1212 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1213 }
1214
1215 #[actix_rt::test]
1216 async fn test_serve_named_file_prefix() {
1217 let factory = NamedFile::open("Cargo.toml").unwrap();
1218 let srv =
1219 test::init_service(App::new().service(web::scope("/test").service(factory))).await;
1220
1221 let req = TestRequest::get().uri("/test/Cargo.toml").to_request();
1222 let res = test::call_service(&srv, req).await;
1223 assert_eq!(res.status(), StatusCode::OK);
1224
1225 let bytes = test::read_body(res).await;
1226 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1227 assert_eq!(bytes, data);
1228
1229 let req = TestRequest::get().uri("/Cargo.toml").to_request();
1230 let res = test::call_service(&srv, req).await;
1231 assert_eq!(res.status(), StatusCode::NOT_FOUND);
1232 }
1233
1234 #[actix_rt::test]
1235 async fn test_named_file_default_service() {
1236 let factory = NamedFile::open("Cargo.toml").unwrap();
1237 let srv = test::init_service(App::new().default_service(factory)).await;
1238
1239 for route in ["/foobar", "/baz", "/"].iter() {
1240 let req = TestRequest::get().uri(route).to_request();
1241 let res = test::call_service(&srv, req).await;
1242 assert_eq!(res.status(), StatusCode::OK);
1243
1244 let bytes = test::read_body(res).await;
1245 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1246 assert_eq!(bytes, data);
1247 }
1248 }
1249
1250 #[actix_rt::test]
1251 async fn test_default_handler_named_file() {
1252 let factory = NamedFile::open("Cargo.toml").unwrap();
1253 let st = Files::new("/", ".")
1254 .default_handler(factory)
1255 .new_service(())
1256 .await
1257 .unwrap();
1258 let req = TestRequest::with_uri("/missing").to_srv_request();
1259 let resp = test::call_service(&st, req).await;
1260
1261 assert_eq!(resp.status(), StatusCode::OK);
1262 let bytes = test::read_body(resp).await;
1263 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
1264 assert_eq!(bytes, data);
1265 }
1266
1267 #[actix_rt::test]
1268 async fn test_symlinks() {
1269 let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
1270
1271 let req = TestRequest::get()
1272 .uri("/test/tests/symlink-test.png")
1273 .to_request();
1274 let res = test::call_service(&srv, req).await;
1275 assert_eq!(res.status(), StatusCode::OK);
1276 assert_eq!(
1277 res.headers().get(header::CONTENT_DISPOSITION).unwrap(),
1278 "inline; filename=\"symlink-test.png\""
1279 );
1280 }
1281
1282 #[actix_rt::test]
1283 async fn test_index_with_show_files_listing() {
1284 let service = Files::new(".", ".")
1285 .index_file("lib.rs")
1286 .show_files_listing()
1287 .new_service(())
1288 .await
1289 .unwrap();
1290
1291 let req = TestRequest::default().uri("/src").to_srv_request();
1293 let resp = test::call_service(&service, req).await;
1294 assert_eq!(resp.status(), StatusCode::OK);
1295 assert_eq!(
1296 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1297 "text/x-rust"
1298 );
1299
1300 let req = TestRequest::default().uri("/tests").to_srv_request();
1302 let resp = test::call_service(&service, req).await;
1303 assert_eq!(
1304 resp.headers().get(header::CONTENT_TYPE).unwrap(),
1305 "text/html; charset=utf-8"
1306 );
1307 let bytes = test::read_body(resp).await;
1308 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
1309 }
1310
1311 #[actix_rt::test]
1312 async fn test_path_filter() {
1313 let st = Files::new("/", ".")
1315 .path_filter(|path, _| path.components().count() == 1)
1316 .new_service(())
1317 .await
1318 .unwrap();
1319
1320 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1321 let resp = test::call_service(&st, req).await;
1322 assert_eq!(resp.status(), StatusCode::OK);
1323
1324 let req = TestRequest::with_uri("/src/lib.rs").to_srv_request();
1325 let resp = test::call_service(&st, req).await;
1326 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1327 }
1328
1329 #[actix_rt::test]
1330 async fn test_default_handler_filter() {
1331 let st = Files::new("/", ".")
1332 .default_handler(|req: ServiceRequest| async {
1333 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1334 })
1335 .path_filter(|path, _| path.extension() == Some("png".as_ref()))
1336 .new_service(())
1337 .await
1338 .unwrap();
1339 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1340 let resp = test::call_service(&st, req).await;
1341
1342 assert_eq!(resp.status(), StatusCode::OK);
1343 let bytes = test::read_body(resp).await;
1344 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
1345 }
1346}