1#![deny(rust_2018_idioms, nonstandard_style)]
15#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
16#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
17#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
18#![cfg_attr(docsrs, feature(doc_auto_cfg))]
19
20use std::path::Path;
21
22use actix_service::boxed::{BoxService, BoxServiceFactory};
23use actix_web::{
24 dev::{RequestHead, ServiceRequest, ServiceResponse},
25 error::Error,
26 http::header::DispositionType,
27};
28use mime_guess::from_ext;
29
30mod chunked;
31mod directory;
32mod encoding;
33mod error;
34mod files;
35mod named;
36mod path_buf;
37mod range;
38mod service;
39
40pub use self::{
41 chunked::ChunkedReadFile, directory::Directory, files::Files, named::NamedFile,
42 range::HttpRange, service::FilesService,
43};
44use self::{
45 directory::{directory_listing, DirectoryRenderer},
46 error::FilesError,
47 path_buf::PathBufWrap,
48};
49
50type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
51type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
52
53#[inline]
57pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
58 from_ext(ext).first_or_octet_stream()
59}
60
61type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
62
63type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
64
65#[cfg(test)]
66mod tests {
67 use std::{
68 fmt::Write as _,
69 fs::{self},
70 ops::Add,
71 time::{Duration, SystemTime},
72 };
73
74 use actix_web::{
75 dev::ServiceFactory,
76 guard,
77 http::{
78 header::{self, ContentDisposition, DispositionParam},
79 Method, StatusCode,
80 },
81 middleware::Compress,
82 test::{self, TestRequest},
83 web::{self, Bytes},
84 App, HttpResponse, Responder,
85 };
86
87 use super::*;
88 use crate::named::File;
89
90 #[actix_web::test]
91 async fn test_file_extension_to_mime() {
92 let m = file_extension_to_mime("");
93 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
94
95 let m = file_extension_to_mime("jpg");
96 assert_eq!(m, mime::IMAGE_JPEG);
97
98 let m = file_extension_to_mime("invalid extension!!");
99 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
100
101 let m = file_extension_to_mime("");
102 assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
103 }
104
105 #[actix_rt::test]
106 async fn test_if_modified_since_without_if_none_match() {
107 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
108 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
109
110 let req = TestRequest::default()
111 .insert_header((header::IF_MODIFIED_SINCE, since))
112 .to_http_request();
113 let resp = file.respond_to(&req);
114 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
115 }
116
117 #[actix_rt::test]
118 async fn test_if_modified_since_without_if_none_match_same() {
119 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
120 let since = file.last_modified().unwrap();
121
122 let req = TestRequest::default()
123 .insert_header((header::IF_MODIFIED_SINCE, since))
124 .to_http_request();
125 let resp = file.respond_to(&req);
126 assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
127 }
128
129 #[actix_rt::test]
130 async fn test_if_modified_since_with_if_none_match() {
131 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
132 let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
133
134 let req = TestRequest::default()
135 .insert_header((header::IF_NONE_MATCH, "miss_etag"))
136 .insert_header((header::IF_MODIFIED_SINCE, since))
137 .to_http_request();
138 let resp = file.respond_to(&req);
139 assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
140 }
141
142 #[actix_rt::test]
143 async fn test_if_unmodified_since() {
144 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
145 let since = file.last_modified().unwrap();
146
147 let req = TestRequest::default()
148 .insert_header((header::IF_UNMODIFIED_SINCE, since))
149 .to_http_request();
150 let resp = file.respond_to(&req);
151 assert_eq!(resp.status(), StatusCode::OK);
152 }
153
154 #[actix_rt::test]
155 async fn test_if_unmodified_since_failed() {
156 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
157 let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
158
159 let req = TestRequest::default()
160 .insert_header((header::IF_UNMODIFIED_SINCE, since))
161 .to_http_request();
162 let resp = file.respond_to(&req);
163 assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
164 }
165
166 #[actix_rt::test]
167 async fn test_named_file_text() {
168 assert!(NamedFile::open_async("test--").await.is_err());
169 let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
170 {
171 file.file();
172 let _f: &File = &file;
173 }
174 {
175 let _f: &mut File = &mut file;
176 }
177
178 let req = TestRequest::default().to_http_request();
179 let resp = file.respond_to(&req);
180 assert_eq!(
181 resp.headers().get(header::CONTENT_TYPE).unwrap(),
182 "text/x-toml"
183 );
184 assert_eq!(
185 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
186 "inline; filename=\"Cargo.toml\""
187 );
188 }
189
190 #[actix_rt::test]
191 async fn test_named_file_content_disposition() {
192 assert!(NamedFile::open_async("test--").await.is_err());
193 let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
194 {
195 file.file();
196 let _f: &File = &file;
197 }
198 {
199 let _f: &mut File = &mut file;
200 }
201
202 let req = TestRequest::default().to_http_request();
203 let resp = file.respond_to(&req);
204 assert_eq!(
205 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
206 "inline; filename=\"Cargo.toml\""
207 );
208
209 let file = NamedFile::open_async("Cargo.toml")
210 .await
211 .unwrap()
212 .disable_content_disposition();
213 let req = TestRequest::default().to_http_request();
214 let resp = file.respond_to(&req);
215 assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
216 }
217
218 #[actix_rt::test]
219 async fn test_named_file_non_ascii_file_name() {
220 let file = {
221 #[cfg(feature = "experimental-io-uring")]
222 {
223 crate::named::File::open("Cargo.toml").await.unwrap()
224 }
225
226 #[cfg(not(feature = "experimental-io-uring"))]
227 {
228 crate::named::File::open("Cargo.toml").unwrap()
229 }
230 };
231
232 let mut file = NamedFile::from_file(file, "貨物.toml").unwrap();
233 {
234 file.file();
235 let _f: &File = &file;
236 }
237 {
238 let _f: &mut File = &mut file;
239 }
240
241 let req = TestRequest::default().to_http_request();
242 let resp = file.respond_to(&req);
243 assert_eq!(
244 resp.headers().get(header::CONTENT_TYPE).unwrap(),
245 "text/x-toml"
246 );
247 assert_eq!(
248 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
249 "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
250 );
251 }
252
253 #[actix_rt::test]
254 async fn test_named_file_set_content_type() {
255 let mut file = NamedFile::open_async("Cargo.toml")
256 .await
257 .unwrap()
258 .set_content_type(mime::TEXT_XML);
259 {
260 file.file();
261 let _f: &File = &file;
262 }
263 {
264 let _f: &mut File = &mut file;
265 }
266
267 let req = TestRequest::default().to_http_request();
268 let resp = file.respond_to(&req);
269 assert_eq!(
270 resp.headers().get(header::CONTENT_TYPE).unwrap(),
271 "text/xml"
272 );
273 assert_eq!(
274 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
275 "inline; filename=\"Cargo.toml\""
276 );
277 }
278
279 #[actix_rt::test]
280 async fn test_named_file_image() {
281 let mut file = NamedFile::open_async("tests/test.png").await.unwrap();
282 {
283 file.file();
284 let _f: &File = &file;
285 }
286 {
287 let _f: &mut File = &mut file;
288 }
289
290 let req = TestRequest::default().to_http_request();
291 let resp = file.respond_to(&req);
292 assert_eq!(
293 resp.headers().get(header::CONTENT_TYPE).unwrap(),
294 "image/png"
295 );
296 assert_eq!(
297 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
298 "inline; filename=\"test.png\""
299 );
300 }
301
302 #[actix_rt::test]
303 async fn test_named_file_javascript() {
304 let file = NamedFile::open_async("tests/test.js").await.unwrap();
305
306 let req = TestRequest::default().to_http_request();
307 let resp = file.respond_to(&req);
308 assert_eq!(
309 resp.headers().get(header::CONTENT_TYPE).unwrap(),
310 "application/javascript; charset=utf-8"
311 );
312 assert_eq!(
313 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
314 "inline; filename=\"test.js\""
315 );
316 }
317
318 #[actix_rt::test]
319 async fn test_named_file_image_attachment() {
320 let cd = ContentDisposition {
321 disposition: DispositionType::Attachment,
322 parameters: vec![DispositionParam::Filename(String::from("test.png"))],
323 };
324 let mut file = NamedFile::open_async("tests/test.png")
325 .await
326 .unwrap()
327 .set_content_disposition(cd);
328 {
329 file.file();
330 let _f: &File = &file;
331 }
332 {
333 let _f: &mut File = &mut file;
334 }
335
336 let req = TestRequest::default().to_http_request();
337 let resp = file.respond_to(&req);
338 assert_eq!(
339 resp.headers().get(header::CONTENT_TYPE).unwrap(),
340 "image/png"
341 );
342 assert_eq!(
343 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
344 "attachment; filename=\"test.png\""
345 );
346 }
347
348 #[actix_rt::test]
349 async fn test_named_file_binary() {
350 let mut file = NamedFile::open_async("tests/test.binary").await.unwrap();
351 {
352 file.file();
353 let _f: &File = &file;
354 }
355 {
356 let _f: &mut File = &mut file;
357 }
358
359 let req = TestRequest::default().to_http_request();
360 let resp = file.respond_to(&req);
361 assert_eq!(
362 resp.headers().get(header::CONTENT_TYPE).unwrap(),
363 "application/octet-stream"
364 );
365 assert_eq!(
366 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
367 "attachment; filename=\"test.binary\""
368 );
369 }
370
371 #[allow(deprecated)]
372 #[actix_rt::test]
373 async fn status_code_customize_same_output() {
374 let file1 = NamedFile::open_async("Cargo.toml")
375 .await
376 .unwrap()
377 .set_status_code(StatusCode::NOT_FOUND);
378
379 let file2 = NamedFile::open_async("Cargo.toml")
380 .await
381 .unwrap()
382 .customize()
383 .with_status(StatusCode::NOT_FOUND);
384
385 let req = TestRequest::default().to_http_request();
386 let res1 = file1.respond_to(&req);
387 let res2 = file2.respond_to(&req);
388
389 assert_eq!(res1.status(), StatusCode::NOT_FOUND);
390 assert_eq!(res2.status(), StatusCode::NOT_FOUND);
391 }
392
393 #[actix_rt::test]
394 async fn test_named_file_status_code_text() {
395 let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
396
397 {
398 file.file();
399 let _f: &File = &file;
400 }
401
402 {
403 let _f: &mut File = &mut file;
404 }
405
406 let file = file.customize().with_status(StatusCode::NOT_FOUND);
407
408 let req = TestRequest::default().to_http_request();
409 let resp = file.respond_to(&req);
410 assert_eq!(
411 resp.headers().get(header::CONTENT_TYPE).unwrap(),
412 "text/x-toml"
413 );
414 assert_eq!(
415 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
416 "inline; filename=\"Cargo.toml\""
417 );
418 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
419 }
420
421 #[actix_rt::test]
422 async fn test_mime_override() {
423 fn all_attachment(_: &mime::Name<'_>) -> DispositionType {
424 DispositionType::Attachment
425 }
426
427 let srv = test::init_service(
428 App::new().service(
429 Files::new("/", ".")
430 .mime_override(all_attachment)
431 .index_file("Cargo.toml"),
432 ),
433 )
434 .await;
435
436 let request = TestRequest::get().uri("/").to_request();
437 let response = test::call_service(&srv, request).await;
438 assert_eq!(response.status(), StatusCode::OK);
439
440 let content_disposition = response
441 .headers()
442 .get(header::CONTENT_DISPOSITION)
443 .expect("To have CONTENT_DISPOSITION");
444 let content_disposition = content_disposition
445 .to_str()
446 .expect("Convert CONTENT_DISPOSITION to str");
447 assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
448 }
449
450 #[actix_rt::test]
451 async fn test_named_file_ranges_status_code() {
452 let srv = test::init_service(
453 App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
454 )
455 .await;
456
457 let request = TestRequest::get()
459 .uri("/t%65st/Cargo.toml")
460 .insert_header((header::RANGE, "bytes=10-20"))
461 .to_request();
462 let response = test::call_service(&srv, request).await;
463 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
464
465 let request = TestRequest::get()
467 .uri("/t%65st/Cargo.toml")
468 .insert_header((header::RANGE, "bytes=1-0"))
469 .to_request();
470 let response = test::call_service(&srv, request).await;
471
472 assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
473 }
474
475 #[actix_rt::test]
476 async fn test_named_file_content_range_headers() {
477 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
478
479 let response = srv
481 .get("/tests/test.binary")
482 .insert_header((header::RANGE, "bytes=10-20"))
483 .send()
484 .await
485 .unwrap();
486 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
487 assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
488
489 let response = srv
491 .get("/tests/test.binary")
492 .insert_header((header::RANGE, "bytes=10-5"))
493 .send()
494 .await
495 .unwrap();
496 let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
497 assert_eq!(content_range.to_str().unwrap(), "bytes */100");
498 }
499
500 #[actix_rt::test]
501 async fn test_named_file_content_length_headers() {
502 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
503
504 let response = srv
506 .get("/tests/test.binary")
507 .insert_header((header::RANGE, "bytes=10-20"))
508 .send()
509 .await
510 .unwrap();
511 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
512 assert_eq!(content_length.to_str().unwrap(), "11");
513
514 let response = srv
516 .get("/tests/test.binary")
517 .insert_header((header::RANGE, "bytes=0-20"))
518 .send()
519 .await
520 .unwrap();
521 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
522 assert_eq!(content_length.to_str().unwrap(), "21");
523
524 let mut response = srv.get("/tests/test.binary").send().await.unwrap();
526 let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
527 assert_eq!(content_length.to_str().unwrap(), "100");
528
529 let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
531 assert!(transfer_encoding.is_none());
532
533 let bytes = response.body().await.unwrap();
535 let data = web::Bytes::from(fs::read("tests/test.binary").unwrap());
536 assert_eq!(bytes, data);
537 }
538
539 #[actix_rt::test]
540 async fn test_head_content_length_headers() {
541 let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
542
543 let response = srv.head("/tests/test.binary").send().await.unwrap();
544
545 let content_length = response
546 .headers()
547 .get(header::CONTENT_LENGTH)
548 .unwrap()
549 .to_str()
550 .unwrap();
551
552 assert_eq!(content_length, "100");
553 }
554
555 #[actix_rt::test]
556 async fn test_static_files_with_spaces() {
557 let srv =
558 test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
559 .await;
560 let request = TestRequest::get()
561 .uri("/tests/test%20space.binary")
562 .to_request();
563 let response = test::call_service(&srv, request).await;
564 assert_eq!(response.status(), StatusCode::OK);
565
566 let bytes = test::read_body(response).await;
567 let data = web::Bytes::from(fs::read("tests/test space.binary").unwrap());
568 assert_eq!(bytes, data);
569 }
570
571 #[cfg(not(target_os = "windows"))]
572 #[actix_rt::test]
573 async fn test_static_files_with_special_characters() {
574 let temp_dir = tempfile::tempdir().unwrap();
577 let file_with_newlines = temp_dir.path().join("test\n\x0B\x0C\rnewline.text");
578 fs::write(&file_with_newlines, "Look at my newlines").unwrap();
579
580 let srv = test::init_service(
581 App::new().service(Files::new("/", temp_dir.path()).index_file("Cargo.toml")),
582 )
583 .await;
584 let request = TestRequest::get()
585 .uri("/test%0A%0B%0C%0Dnewline.text")
586 .to_request();
587 let response = test::call_service(&srv, request).await;
588 assert_eq!(response.status(), StatusCode::OK);
589
590 let bytes = test::read_body(response).await;
591 let data = web::Bytes::from(fs::read(file_with_newlines).unwrap());
592 assert_eq!(bytes, data);
593 }
594
595 #[actix_rt::test]
596 async fn test_files_not_allowed() {
597 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
598
599 let req = TestRequest::default()
600 .uri("/Cargo.toml")
601 .method(Method::POST)
602 .to_request();
603
604 let resp = test::call_service(&srv, req).await;
605 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
606
607 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
608 let req = TestRequest::default()
609 .method(Method::PUT)
610 .uri("/Cargo.toml")
611 .to_request();
612 let resp = test::call_service(&srv, req).await;
613 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
614 }
615
616 #[actix_rt::test]
617 async fn test_files_guards() {
618 let srv = test::init_service(
619 App::new().service(Files::new("/", ".").method_guard(guard::Post())),
620 )
621 .await;
622
623 let req = TestRequest::default()
624 .uri("/Cargo.toml")
625 .method(Method::POST)
626 .to_request();
627
628 let resp = test::call_service(&srv, req).await;
629 assert_eq!(resp.status(), StatusCode::OK);
630 }
631
632 #[actix_rt::test]
633 async fn test_named_file_content_encoding() {
634 let srv = test::init_service(App::new().wrap(Compress::default()).service(
635 web::resource("/").to(|| async {
636 NamedFile::open_async("Cargo.toml")
637 .await
638 .unwrap()
639 .set_content_encoding(header::ContentEncoding::Identity)
640 }),
641 ))
642 .await;
643
644 let request = TestRequest::get()
645 .uri("/")
646 .insert_header((header::ACCEPT_ENCODING, "gzip"))
647 .to_request();
648 let res = test::call_service(&srv, request).await;
649 assert_eq!(res.status(), StatusCode::OK);
650 assert!(res.headers().contains_key(header::CONTENT_ENCODING));
651 assert!(!test::read_body(res).await.is_empty());
652 }
653
654 #[actix_rt::test]
655 async fn test_named_file_content_encoding_gzip() {
656 let srv = test::init_service(App::new().wrap(Compress::default()).service(
657 web::resource("/").to(|| async {
658 NamedFile::open_async("Cargo.toml")
659 .await
660 .unwrap()
661 .set_content_encoding(header::ContentEncoding::Gzip)
662 }),
663 ))
664 .await;
665
666 let request = TestRequest::get()
667 .uri("/")
668 .insert_header((header::ACCEPT_ENCODING, "gzip"))
669 .to_request();
670 let res = test::call_service(&srv, request).await;
671 assert_eq!(res.status(), StatusCode::OK);
672 assert_eq!(
673 res.headers()
674 .get(header::CONTENT_ENCODING)
675 .unwrap()
676 .to_str()
677 .unwrap(),
678 "gzip"
679 );
680 }
681
682 #[actix_rt::test]
683 async fn test_named_file_allowed_method() {
684 let req = TestRequest::default().method(Method::GET).to_http_request();
685 let file = NamedFile::open_async("Cargo.toml").await.unwrap();
686 let resp = file.respond_to(&req);
687 assert_eq!(resp.status(), StatusCode::OK);
688 }
689
690 #[actix_rt::test]
691 async fn test_static_files() {
692 let srv =
693 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
694 let req = TestRequest::with_uri("/missing").to_request();
695
696 let resp = test::call_service(&srv, req).await;
697 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
698
699 let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
700
701 let req = TestRequest::default().to_request();
702 let resp = test::call_service(&srv, req).await;
703 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
704
705 let srv =
706 test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
707 let req = TestRequest::with_uri("/tests").to_request();
708 let resp = test::call_service(&srv, req).await;
709 assert_eq!(
710 resp.headers().get(header::CONTENT_TYPE).unwrap(),
711 "text/html; charset=utf-8"
712 );
713
714 let bytes = test::read_body(resp).await;
715 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
716 }
717
718 #[actix_rt::test]
719 async fn test_redirect_to_slash_directory() {
720 let srv = test::init_service(
722 App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
723 )
724 .await;
725 let req = TestRequest::with_uri("/tests").to_request();
726 let resp = test::call_service(&srv, req).await;
727 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
728
729 let srv = test::init_service(
731 App::new().service(
732 Files::new("/", ".")
733 .index_file("test.png")
734 .redirect_to_slash_directory(),
735 ),
736 )
737 .await;
738 let req = TestRequest::with_uri("/tests").to_request();
739 let resp = test::call_service(&srv, req).await;
740 assert_eq!(resp.status(), StatusCode::FOUND);
741
742 let srv = test::init_service(
744 App::new().service(
745 Files::new("/", ".")
746 .show_files_listing()
747 .redirect_to_slash_directory(),
748 ),
749 )
750 .await;
751 let req = TestRequest::with_uri("/tests").to_request();
752 let resp = test::call_service(&srv, req).await;
753 assert_eq!(resp.status(), StatusCode::FOUND);
754
755 let req = TestRequest::with_uri("/not_existing").to_request();
757 let resp = test::call_service(&srv, req).await;
758 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
759 }
760
761 #[actix_rt::test]
762 async fn test_static_files_bad_directory() {
763 let service = Files::new("/", "./missing").new_service(()).await.unwrap();
764
765 let req = TestRequest::with_uri("/").to_srv_request();
766 let resp = test::call_service(&service, req).await;
767
768 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
769 }
770
771 #[actix_rt::test]
772 async fn test_default_handler_file_missing() {
773 let st = Files::new("/", ".")
774 .default_handler(|req: ServiceRequest| async {
775 Ok(req.into_response(HttpResponse::Ok().body("default content")))
776 })
777 .new_service(())
778 .await
779 .unwrap();
780 let req = TestRequest::with_uri("/missing").to_srv_request();
781 let resp = test::call_service(&st, req).await;
782
783 assert_eq!(resp.status(), StatusCode::OK);
784 let bytes = test::read_body(resp).await;
785 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
786 }
787
788 #[actix_rt::test]
789 async fn test_serve_index_nested() {
790 let service = Files::new(".", ".")
791 .index_file("lib.rs")
792 .new_service(())
793 .await
794 .unwrap();
795
796 let req = TestRequest::default().uri("/src").to_srv_request();
797 let resp = test::call_service(&service, req).await;
798
799 assert_eq!(resp.status(), StatusCode::OK);
800 assert_eq!(
801 resp.headers().get(header::CONTENT_TYPE).unwrap(),
802 "text/x-rust"
803 );
804 assert_eq!(
805 resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
806 "inline; filename=\"lib.rs\""
807 );
808 }
809
810 #[actix_rt::test]
811 async fn integration_serve_index() {
812 let srv = test::init_service(
813 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
814 )
815 .await;
816
817 let req = TestRequest::get().uri("/test").to_request();
818 let res = test::call_service(&srv, req).await;
819 assert_eq!(res.status(), StatusCode::OK);
820
821 let bytes = test::read_body(res).await;
822
823 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
824 assert_eq!(bytes, data);
825
826 let req = TestRequest::get().uri("/test/").to_request();
827 let res = test::call_service(&srv, req).await;
828 assert_eq!(res.status(), StatusCode::OK);
829
830 let bytes = test::read_body(res).await;
831 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
832 assert_eq!(bytes, data);
833
834 let req = TestRequest::get().uri("/test/unknown").to_request();
836 let res = test::call_service(&srv, req).await;
837 assert_eq!(res.status(), StatusCode::NOT_FOUND);
838
839 let req = TestRequest::get().uri("/test/unknown/").to_request();
840 let res = test::call_service(&srv, req).await;
841 assert_eq!(res.status(), StatusCode::NOT_FOUND);
842 }
843
844 #[actix_rt::test]
845 async fn integration_percent_encoded() {
846 let srv = test::init_service(
847 App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
848 )
849 .await;
850
851 let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
852 let res = test::call_service(&srv, req).await;
853 assert_eq!(res.status(), StatusCode::OK);
854
855 let req = TestRequest::get().uri("/test%2Ftest.binary").to_request();
857 let res = test::call_service(&srv, req).await;
858 assert_eq!(res.status(), StatusCode::NOT_FOUND);
859
860 let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request();
861 let res = test::call_service(&srv, req).await;
862 assert_eq!(res.status(), StatusCode::NOT_FOUND);
863 }
864
865 #[actix_rt::test]
866 async fn test_percent_encoding_2() {
867 let temp_dir = tempfile::tempdir().unwrap();
868 let filename = match cfg!(unix) {
869 true => "ض:?#[]{}<>()@!$&'`|*+,;= %20\n.test",
870 false => "ض#[]{}()@!$&'`+,;= %20.test",
871 };
872 let filename_encoded = filename
873 .as_bytes()
874 .iter()
875 .fold(String::new(), |mut buf, c| {
876 write!(&mut buf, "%{:02X}", c).unwrap();
877 buf
878 });
879 std::fs::File::create(temp_dir.path().join(filename)).unwrap();
880
881 let srv = test::init_service(App::new().service(Files::new("/", temp_dir.path()))).await;
882
883 let req = TestRequest::get()
884 .uri(&format!("/{}", filename_encoded))
885 .to_request();
886 let res = test::call_service(&srv, req).await;
887 assert_eq!(res.status(), StatusCode::OK);
888 }
889
890 #[actix_rt::test]
891 async fn test_serve_named_file() {
892 let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
893 let srv = test::init_service(App::new().service(factory)).await;
894
895 let req = TestRequest::get().uri("/Cargo.toml").to_request();
896 let res = test::call_service(&srv, req).await;
897 assert_eq!(res.status(), StatusCode::OK);
898
899 let bytes = test::read_body(res).await;
900 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
901 assert_eq!(bytes, data);
902
903 let req = TestRequest::get().uri("/test/unknown").to_request();
904 let res = test::call_service(&srv, req).await;
905 assert_eq!(res.status(), StatusCode::NOT_FOUND);
906 }
907
908 #[actix_rt::test]
909 async fn test_serve_named_file_prefix() {
910 let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
911 let srv =
912 test::init_service(App::new().service(web::scope("/test").service(factory))).await;
913
914 let req = TestRequest::get().uri("/test/Cargo.toml").to_request();
915 let res = test::call_service(&srv, req).await;
916 assert_eq!(res.status(), StatusCode::OK);
917
918 let bytes = test::read_body(res).await;
919 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
920 assert_eq!(bytes, data);
921
922 let req = TestRequest::get().uri("/Cargo.toml").to_request();
923 let res = test::call_service(&srv, req).await;
924 assert_eq!(res.status(), StatusCode::NOT_FOUND);
925 }
926
927 #[actix_rt::test]
928 async fn test_named_file_default_service() {
929 let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
930 let srv = test::init_service(App::new().default_service(factory)).await;
931
932 for route in ["/foobar", "/baz", "/"].iter() {
933 let req = TestRequest::get().uri(route).to_request();
934 let res = test::call_service(&srv, req).await;
935 assert_eq!(res.status(), StatusCode::OK);
936
937 let bytes = test::read_body(res).await;
938 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
939 assert_eq!(bytes, data);
940 }
941 }
942
943 #[actix_rt::test]
944 async fn test_default_handler_named_file() {
945 let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
946 let st = Files::new("/", ".")
947 .default_handler(factory)
948 .new_service(())
949 .await
950 .unwrap();
951 let req = TestRequest::with_uri("/missing").to_srv_request();
952 let resp = test::call_service(&st, req).await;
953
954 assert_eq!(resp.status(), StatusCode::OK);
955 let bytes = test::read_body(resp).await;
956 let data = Bytes::from(fs::read("Cargo.toml").unwrap());
957 assert_eq!(bytes, data);
958 }
959
960 #[actix_rt::test]
961 async fn test_symlinks() {
962 let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
963
964 let req = TestRequest::get()
965 .uri("/test/tests/symlink-test.png")
966 .to_request();
967 let res = test::call_service(&srv, req).await;
968 assert_eq!(res.status(), StatusCode::OK);
969 assert_eq!(
970 res.headers().get(header::CONTENT_DISPOSITION).unwrap(),
971 "inline; filename=\"symlink-test.png\""
972 );
973 }
974
975 #[actix_rt::test]
976 async fn test_index_with_show_files_listing() {
977 let service = Files::new(".", ".")
978 .index_file("lib.rs")
979 .show_files_listing()
980 .new_service(())
981 .await
982 .unwrap();
983
984 let req = TestRequest::default().uri("/src").to_srv_request();
986 let resp = test::call_service(&service, req).await;
987 assert_eq!(resp.status(), StatusCode::OK);
988 assert_eq!(
989 resp.headers().get(header::CONTENT_TYPE).unwrap(),
990 "text/x-rust"
991 );
992
993 let req = TestRequest::default().uri("/tests").to_srv_request();
995 let resp = test::call_service(&service, req).await;
996 assert_eq!(
997 resp.headers().get(header::CONTENT_TYPE).unwrap(),
998 "text/html; charset=utf-8"
999 );
1000 let bytes = test::read_body(resp).await;
1001 assert!(format!("{:?}", bytes).contains("/tests/test.png"));
1002 }
1003
1004 #[actix_rt::test]
1005 async fn test_path_filter() {
1006 let st = Files::new("/", ".")
1008 .path_filter(|path, _| path.components().count() == 1)
1009 .new_service(())
1010 .await
1011 .unwrap();
1012
1013 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1014 let resp = test::call_service(&st, req).await;
1015 assert_eq!(resp.status(), StatusCode::OK);
1016
1017 let req = TestRequest::with_uri("/src/lib.rs").to_srv_request();
1018 let resp = test::call_service(&st, req).await;
1019 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1020 }
1021
1022 #[actix_rt::test]
1023 async fn test_default_handler_filter() {
1024 let st = Files::new("/", ".")
1025 .default_handler(|req: ServiceRequest| async {
1026 Ok(req.into_response(HttpResponse::Ok().body("default content")))
1027 })
1028 .path_filter(|path, _| path.extension() == Some("png".as_ref()))
1029 .new_service(())
1030 .await
1031 .unwrap();
1032 let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
1033 let resp = test::call_service(&st, req).await;
1034
1035 assert_eq!(resp.status(), StatusCode::OK);
1036 let bytes = test::read_body(resp).await;
1037 assert_eq!(bytes, web::Bytes::from_static(b"default content"));
1038 }
1039}