Skip to main content

actix_files/
lib.rs

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