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