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