A now defunct web server engine that is notable for its overuse of unsafe code, the memory safety bugs caused by that, and its deletion by its creator.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1430 lines
47 KiB

  1. #![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)]
  2. //! Static files support
  3. use std::cell::RefCell;
  4. use std::fmt::Write;
  5. use std::fs::{DirEntry, File};
  6. use std::future::Future;
  7. use std::io::{Read, Seek};
  8. use std::path::{Path, PathBuf};
  9. use std::pin::Pin;
  10. use std::rc::Rc;
  11. use std::task::{Context, Poll};
  12. use std::{cmp, io};
  13. use actix_service::boxed::{self, BoxService, BoxServiceFactory};
  14. use actix_service::{IntoServiceFactory, Service, ServiceFactory};
  15. use actix_web::dev::{
  16. AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
  17. ServiceResponse,
  18. };
  19. use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
  20. use actix_web::guard::Guard;
  21. use actix_web::http::header::{self, DispositionType};
  22. use actix_web::http::Method;
  23. use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
  24. use bytes::Bytes;
  25. use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
  26. use futures::Stream;
  27. use mime;
  28. use mime_guess::from_ext;
  29. use percent_encoding::{utf8_percent_encode, CONTROLS};
  30. use v_htmlescape::escape as escape_html_entity;
  31. mod error;
  32. mod named;
  33. mod range;
  34. use self::error::{FilesError, UriSegmentError};
  35. pub use crate::named::NamedFile;
  36. pub use crate::range::HttpRange;
  37. type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
  38. type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
  39. /// Return the MIME type associated with a filename extension (case-insensitive).
  40. /// If `ext` is empty or no associated type for the extension was found, returns
  41. /// the type `application/octet-stream`.
  42. #[inline]
  43. pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
  44. from_ext(ext).first_or_octet_stream()
  45. }
  46. fn handle_error(err: BlockingError<io::Error>) -> Error {
  47. match err {
  48. BlockingError::Error(err) => err.into(),
  49. BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
  50. }
  51. }
  52. #[doc(hidden)]
  53. /// A helper created from a `std::fs::File` which reads the file
  54. /// chunk-by-chunk on a `ThreadPool`.
  55. pub struct ChunkedReadFile {
  56. size: u64,
  57. offset: u64,
  58. file: Option<File>,
  59. fut:
  60. Option<LocalBoxFuture<'static, Result<(File, Bytes), BlockingError<io::Error>>>>,
  61. counter: u64,
  62. }
  63. impl Stream for ChunkedReadFile {
  64. type Item = Result<Bytes, Error>;
  65. fn poll_next(
  66. mut self: Pin<&mut Self>,
  67. cx: &mut Context,
  68. ) -> Poll<Option<Self::Item>> {
  69. if let Some(ref mut fut) = self.fut {
  70. return match Pin::new(fut).poll(cx) {
  71. Poll::Ready(Ok((file, bytes))) => {
  72. self.fut.take();
  73. self.file = Some(file);
  74. self.offset += bytes.len() as u64;
  75. self.counter += bytes.len() as u64;
  76. Poll::Ready(Some(Ok(bytes)))
  77. }
  78. Poll::Ready(Err(e)) => Poll::Ready(Some(Err(handle_error(e)))),
  79. Poll::Pending => Poll::Pending,
  80. };
  81. }
  82. let size = self.size;
  83. let offset = self.offset;
  84. let counter = self.counter;
  85. if size == counter {
  86. Poll::Ready(None)
  87. } else {
  88. let mut file = self.file.take().expect("Use after completion");
  89. self.fut = Some(
  90. web::block(move || {
  91. let max_bytes: usize;
  92. max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
  93. let mut buf = Vec::with_capacity(max_bytes);
  94. file.seek(io::SeekFrom::Start(offset))?;
  95. let nbytes =
  96. file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
  97. if nbytes == 0 {
  98. return Err(io::ErrorKind::UnexpectedEof.into());
  99. }
  100. Ok((file, Bytes::from(buf)))
  101. })
  102. .boxed_local(),
  103. );
  104. self.poll_next(cx)
  105. }
  106. }
  107. }
  108. type DirectoryRenderer =
  109. dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>;
  110. /// A directory; responds with the generated directory listing.
  111. #[derive(Debug)]
  112. pub struct Directory {
  113. /// Base directory
  114. pub base: PathBuf,
  115. /// Path of subdirectory to generate listing for
  116. pub path: PathBuf,
  117. }
  118. impl Directory {
  119. /// Create a new directory
  120. pub fn new(base: PathBuf, path: PathBuf) -> Directory {
  121. Directory { base, path }
  122. }
  123. /// Is this entry visible from this directory?
  124. pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
  125. if let Ok(ref entry) = *entry {
  126. if let Some(name) = entry.file_name().to_str() {
  127. if name.starts_with('.') {
  128. return false;
  129. }
  130. }
  131. if let Ok(ref md) = entry.metadata() {
  132. let ft = md.file_type();
  133. return ft.is_dir() || ft.is_file() || ft.is_symlink();
  134. }
  135. }
  136. false
  137. }
  138. }
  139. // show file url as relative to static path
  140. macro_rules! encode_file_url {
  141. ($path:ident) => {
  142. utf8_percent_encode(&$path, CONTROLS)
  143. };
  144. }
  145. // " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt; / -- &#x2f;
  146. macro_rules! encode_file_name {
  147. ($entry:ident) => {
  148. escape_html_entity(&$entry.file_name().to_string_lossy())
  149. };
  150. }
  151. fn directory_listing(
  152. dir: &Directory,
  153. req: &HttpRequest,
  154. ) -> Result<ServiceResponse, io::Error> {
  155. let index_of = format!("Index of {}", req.path());
  156. let mut body = String::new();
  157. let base = Path::new(req.path());
  158. for entry in dir.path.read_dir()? {
  159. if dir.is_visible(&entry) {
  160. let entry = entry.unwrap();
  161. let p = match entry.path().strip_prefix(&dir.path) {
  162. Ok(p) if cfg!(windows) => {
  163. base.join(p).to_string_lossy().replace("\\", "/")
  164. }
  165. Ok(p) => base.join(p).to_string_lossy().into_owned(),
  166. Err(_) => continue,
  167. };
  168. // if file is a directory, add '/' to the end of the name
  169. if let Ok(metadata) = entry.metadata() {
  170. if metadata.is_dir() {
  171. let _ = write!(
  172. body,
  173. "<li><a href=\"{}\">{}/</a></li>",
  174. encode_file_url!(p),
  175. encode_file_name!(entry),
  176. );
  177. } else {
  178. let _ = write!(
  179. body,
  180. "<li><a href=\"{}\">{}</a></li>",
  181. encode_file_url!(p),
  182. encode_file_name!(entry),
  183. );
  184. }
  185. } else {
  186. continue;
  187. }
  188. }
  189. }
  190. let html = format!(
  191. "<html>\
  192. <head><title>{}</title></head>\
  193. <body><h1>{}</h1>\
  194. <ul>\
  195. {}\
  196. </ul></body>\n</html>",
  197. index_of, index_of, body
  198. );
  199. Ok(ServiceResponse::new(
  200. req.clone(),
  201. HttpResponse::Ok()
  202. .content_type("text/html; charset=utf-8")
  203. .body(html),
  204. ))
  205. }
  206. type MimeOverride = dyn Fn(&mime::Name) -> DispositionType;
  207. /// Static files handling
  208. ///
  209. /// `Files` service must be registered with `App::service()` method.
  210. ///
  211. /// ```rust
  212. /// use actix_web::App;
  213. /// use actix_files as fs;
  214. ///
  215. /// fn main() {
  216. /// let app = App::new()
  217. /// .service(fs::Files::new("/static", "."));
  218. /// }
  219. /// ```
  220. pub struct Files {
  221. path: String,
  222. directory: PathBuf,
  223. index: Option<String>,
  224. show_index: bool,
  225. redirect_to_slash: bool,
  226. default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
  227. renderer: Rc<DirectoryRenderer>,
  228. mime_override: Option<Rc<MimeOverride>>,
  229. file_flags: named::Flags,
  230. guards: Option<Rc<Box<dyn Guard>>>,
  231. }
  232. impl Clone for Files {
  233. fn clone(&self) -> Self {
  234. Self {
  235. directory: self.directory.clone(),
  236. index: self.index.clone(),
  237. show_index: self.show_index,
  238. redirect_to_slash: self.redirect_to_slash,
  239. default: self.default.clone(),
  240. renderer: self.renderer.clone(),
  241. file_flags: self.file_flags,
  242. path: self.path.clone(),
  243. mime_override: self.mime_override.clone(),
  244. guards: self.guards.clone(),
  245. }
  246. }
  247. }
  248. impl Files {
  249. /// Create new `Files` instance for specified base directory.
  250. ///
  251. /// `File` uses `ThreadPool` for blocking filesystem operations.
  252. /// By default pool with 5x threads of available cpus is used.
  253. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable.
  254. pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
  255. let orig_dir = dir.into();
  256. let dir = match orig_dir.canonicalize() {
  257. Ok(canon_dir) => canon_dir,
  258. Err(_) => {
  259. log::error!("Specified path is not a directory: {:?}", orig_dir);
  260. PathBuf::new()
  261. }
  262. };
  263. Files {
  264. path: path.to_string(),
  265. directory: dir,
  266. index: None,
  267. show_index: false,
  268. redirect_to_slash: false,
  269. default: Rc::new(RefCell::new(None)),
  270. renderer: Rc::new(directory_listing),
  271. mime_override: None,
  272. file_flags: named::Flags::default(),
  273. guards: None,
  274. }
  275. }
  276. /// Show files listing for directories.
  277. ///
  278. /// By default show files listing is disabled.
  279. pub fn show_files_listing(mut self) -> Self {
  280. self.show_index = true;
  281. self
  282. }
  283. /// Redirects to a slash-ended path when browsing a directory.
  284. ///
  285. /// By default never redirect.
  286. pub fn redirect_to_slash_directory(mut self) -> Self {
  287. self.redirect_to_slash = true;
  288. self
  289. }
  290. /// Set custom directory renderer
  291. pub fn files_listing_renderer<F>(mut self, f: F) -> Self
  292. where
  293. for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
  294. + 'static,
  295. {
  296. self.renderer = Rc::new(f);
  297. self
  298. }
  299. /// Specifies mime override callback
  300. pub fn mime_override<F>(mut self, f: F) -> Self
  301. where
  302. F: Fn(&mime::Name) -> DispositionType + 'static,
  303. {
  304. self.mime_override = Some(Rc::new(f));
  305. self
  306. }
  307. /// Set index file
  308. ///
  309. /// Shows specific index file for directory "/" instead of
  310. /// showing files listing.
  311. pub fn index_file<T: Into<String>>(mut self, index: T) -> Self {
  312. self.index = Some(index.into());
  313. self
  314. }
  315. #[inline]
  316. /// Specifies whether to use ETag or not.
  317. ///
  318. /// Default is true.
  319. pub fn use_etag(mut self, value: bool) -> Self {
  320. self.file_flags.set(named::Flags::ETAG, value);
  321. self
  322. }
  323. #[inline]
  324. /// Specifies whether to use Last-Modified or not.
  325. ///
  326. /// Default is true.
  327. pub fn use_last_modified(mut self, value: bool) -> Self {
  328. self.file_flags.set(named::Flags::LAST_MD, value);
  329. self
  330. }
  331. /// Specifies custom guards to use for directory listings and files.
  332. ///
  333. /// Default behaviour allows GET and HEAD.
  334. #[inline]
  335. pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self {
  336. self.guards = Some(Rc::new(Box::new(guards)));
  337. self
  338. }
  339. /// Disable `Content-Disposition` header.
  340. ///
  341. /// By default Content-Disposition` header is enabled.
  342. #[inline]
  343. pub fn disable_content_disposition(mut self) -> Self {
  344. self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
  345. self
  346. }
  347. /// Sets default handler which is used when no matched file could be found.
  348. pub fn default_handler<F, U>(mut self, f: F) -> Self
  349. where
  350. F: IntoServiceFactory<U>,
  351. U: ServiceFactory<
  352. Config = (),
  353. Request = ServiceRequest,
  354. Response = ServiceResponse,
  355. Error = Error,
  356. > + 'static,
  357. {
  358. // create and configure default resource
  359. self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
  360. f.into_factory().map_init_err(|_| ()),
  361. )))));
  362. self
  363. }
  364. }
  365. impl HttpServiceFactory for Files {
  366. fn register(self, config: &mut AppService) {
  367. if self.default.borrow().is_none() {
  368. *self.default.borrow_mut() = Some(config.default_service());
  369. }
  370. let rdef = if config.is_root() {
  371. ResourceDef::root_prefix(&self.path)
  372. } else {
  373. ResourceDef::prefix(&self.path)
  374. };
  375. config.register_service(rdef, None, self, None)
  376. }
  377. }
  378. impl ServiceFactory for Files {
  379. type Request = ServiceRequest;
  380. type Response = ServiceResponse;
  381. type Error = Error;
  382. type Config = ();
  383. type Service = FilesService;
  384. type InitError = ();
  385. type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
  386. fn new_service(&self, _: ()) -> Self::Future {
  387. let mut srv = FilesService {
  388. directory: self.directory.clone(),
  389. index: self.index.clone(),
  390. show_index: self.show_index,
  391. redirect_to_slash: self.redirect_to_slash,
  392. default: None,
  393. renderer: self.renderer.clone(),
  394. mime_override: self.mime_override.clone(),
  395. file_flags: self.file_flags,
  396. guards: self.guards.clone(),
  397. };
  398. if let Some(ref default) = *self.default.borrow() {
  399. default
  400. .new_service(())
  401. .map(move |result| match result {
  402. Ok(default) => {
  403. srv.default = Some(default);
  404. Ok(srv)
  405. }
  406. Err(_) => Err(()),
  407. })
  408. .boxed_local()
  409. } else {
  410. ok(srv).boxed_local()
  411. }
  412. }
  413. }
  414. pub struct FilesService {
  415. directory: PathBuf,
  416. index: Option<String>,
  417. show_index: bool,
  418. redirect_to_slash: bool,
  419. default: Option<HttpService>,
  420. renderer: Rc<DirectoryRenderer>,
  421. mime_override: Option<Rc<MimeOverride>>,
  422. file_flags: named::Flags,
  423. guards: Option<Rc<Box<dyn Guard>>>,
  424. }
  425. impl FilesService {
  426. fn handle_err(
  427. &mut self,
  428. e: io::Error,
  429. req: ServiceRequest,
  430. ) -> Either<
  431. Ready<Result<ServiceResponse, Error>>,
  432. LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
  433. > {
  434. log::debug!("Files: Failed to handle {}: {}", req.path(), e);
  435. if let Some(ref mut default) = self.default {
  436. Either::Right(default.call(req))
  437. } else {
  438. Either::Left(ok(req.error_response(e)))
  439. }
  440. }
  441. }
  442. impl Service for FilesService {
  443. type Request = ServiceRequest;
  444. type Response = ServiceResponse;
  445. type Error = Error;
  446. type Future = Either<
  447. Ready<Result<Self::Response, Self::Error>>,
  448. LocalBoxFuture<'static, Result<Self::Response, Self::Error>>,
  449. >;
  450. fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
  451. Poll::Ready(Ok(()))
  452. }
  453. fn call(&mut self, req: ServiceRequest) -> Self::Future {
  454. let is_method_valid = if let Some(guard) = &self.guards {
  455. // execute user defined guards
  456. (**guard).check(req.head())
  457. } else {
  458. // default behaviour
  459. match *req.method() {
  460. Method::HEAD | Method::GET => true,
  461. _ => false,
  462. }
  463. };
  464. if !is_method_valid {
  465. return Either::Left(ok(req.into_response(
  466. actix_web::HttpResponse::MethodNotAllowed()
  467. .header(header::CONTENT_TYPE, "text/plain")
  468. .body("Request did not meet this resource's requirements."),
  469. )));
  470. }
  471. let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
  472. Ok(item) => item,
  473. Err(e) => return Either::Left(ok(req.error_response(e))),
  474. };
  475. // full filepath
  476. let path = match self.directory.join(&real_path.0).canonicalize() {
  477. Ok(path) => path,
  478. Err(e) => return self.handle_err(e, req),
  479. };
  480. if path.is_dir() {
  481. if let Some(ref redir_index) = self.index {
  482. if self.redirect_to_slash && !req.path().ends_with('/') {
  483. let redirect_to = format!("{}/", req.path());
  484. return Either::Left(ok(req.into_response(
  485. HttpResponse::Found()
  486. .header(header::LOCATION, redirect_to)
  487. .body("")
  488. .into_body(),
  489. )));
  490. }
  491. let path = path.join(redir_index);
  492. match NamedFile::open(path) {
  493. Ok(mut named_file) => {
  494. if let Some(ref mime_override) = self.mime_override {
  495. let new_disposition =
  496. mime_override(&named_file.content_type.type_());
  497. named_file.content_disposition.disposition = new_disposition;
  498. }
  499. named_file.flags = self.file_flags;
  500. let (req, _) = req.into_parts();
  501. Either::Left(ok(match named_file.into_response(&req) {
  502. Ok(item) => ServiceResponse::new(req, item),
  503. Err(e) => ServiceResponse::from_err(e, req),
  504. }))
  505. }
  506. Err(e) => self.handle_err(e, req),
  507. }
  508. } else if self.show_index {
  509. let dir = Directory::new(self.directory.clone(), path);
  510. let (req, _) = req.into_parts();
  511. let x = (self.renderer)(&dir, &req);
  512. match x {
  513. Ok(resp) => Either::Left(ok(resp)),
  514. Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
  515. }
  516. } else {
  517. Either::Left(ok(ServiceResponse::from_err(
  518. FilesError::IsDirectory,
  519. req.into_parts().0,
  520. )))
  521. }
  522. } else {
  523. match NamedFile::open(path) {
  524. Ok(mut named_file) => {
  525. if let Some(ref mime_override) = self.mime_override {
  526. let new_disposition =
  527. mime_override(&named_file.content_type.type_());
  528. named_file.content_disposition.disposition = new_disposition;
  529. }
  530. named_file.flags = self.file_flags;
  531. let (req, _) = req.into_parts();
  532. match named_file.into_response(&req) {
  533. Ok(item) => {
  534. Either::Left(ok(ServiceResponse::new(req.clone(), item)))
  535. }
  536. Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
  537. }
  538. }
  539. Err(e) => self.handle_err(e, req),
  540. }
  541. }
  542. }
  543. }
  544. #[derive(Debug)]
  545. struct PathBufWrp(PathBuf);
  546. impl PathBufWrp {
  547. fn get_pathbuf(path: &str) -> Result<Self, UriSegmentError> {
  548. let mut buf = PathBuf::new();
  549. for segment in path.split('/') {
  550. if segment == ".." {
  551. buf.pop();
  552. } else if segment.starts_with('.') {
  553. return Err(UriSegmentError::BadStart('.'));
  554. } else if segment.starts_with('*') {
  555. return Err(UriSegmentError::BadStart('*'));
  556. } else if segment.ends_with(':') {
  557. return Err(UriSegmentError::BadEnd(':'));
  558. } else if segment.ends_with('>') {
  559. return Err(UriSegmentError::BadEnd('>'));
  560. } else if segment.ends_with('<') {
  561. return Err(UriSegmentError::BadEnd('<'));
  562. } else if segment.is_empty() {
  563. continue;
  564. } else if cfg!(windows) && segment.contains('\\') {
  565. return Err(UriSegmentError::BadChar('\\'));
  566. } else {
  567. buf.push(segment)
  568. }
  569. }
  570. Ok(PathBufWrp(buf))
  571. }
  572. }
  573. impl FromRequest for PathBufWrp {
  574. type Error = UriSegmentError;
  575. type Future = Ready<Result<Self, Self::Error>>;
  576. type Config = ();
  577. fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
  578. ready(PathBufWrp::get_pathbuf(req.match_info().path()))
  579. }
  580. }
  581. #[cfg(test)]
  582. mod tests {
  583. use std::fs;
  584. use std::iter::FromIterator;
  585. use std::ops::Add;
  586. use std::time::{Duration, SystemTime};
  587. use super::*;
  588. use actix_web::guard;
  589. use actix_web::http::header::{
  590. self, ContentDisposition, DispositionParam, DispositionType,
  591. };
  592. use actix_web::http::{Method, StatusCode};
  593. use actix_web::middleware::Compress;
  594. use actix_web::test::{self, TestRequest};
  595. use actix_web::{App, Responder};
  596. #[actix_rt::test]
  597. async fn test_file_extension_to_mime() {
  598. let m = file_extension_to_mime("jpg");
  599. assert_eq!(m, mime::IMAGE_JPEG);
  600. let m = file_extension_to_mime("invalid extension!!");
  601. assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
  602. let m = file_extension_to_mime("");
  603. assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
  604. }
  605. #[actix_rt::test]
  606. async fn test_if_modified_since_without_if_none_match() {
  607. let file = NamedFile::open("Cargo.toml").unwrap();
  608. let since =
  609. header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
  610. let req = TestRequest::default()
  611. .header(header::IF_MODIFIED_SINCE, since)
  612. .to_http_request();
  613. let resp = file.respond_to(&req).await.unwrap();
  614. assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
  615. }
  616. #[actix_rt::test]
  617. async fn test_if_modified_since_with_if_none_match() {
  618. let file = NamedFile::open("Cargo.toml").unwrap();
  619. let since =
  620. header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
  621. let req = TestRequest::default()
  622. .header(header::IF_NONE_MATCH, "miss_etag")
  623. .header(header::IF_MODIFIED_SINCE, since)
  624. .to_http_request();
  625. let resp = file.respond_to(&req).await.unwrap();
  626. assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
  627. }
  628. #[actix_rt::test]
  629. async fn test_named_file_text() {
  630. assert!(NamedFile::open("test--").is_err());
  631. let mut file = NamedFile::open("Cargo.toml").unwrap();
  632. {
  633. file.file();
  634. let _f: &File = &file;
  635. }
  636. {
  637. let _f: &mut File = &mut file;
  638. }
  639. let req = TestRequest::default().to_http_request();
  640. let resp = file.respond_to(&req).await.unwrap();
  641. assert_eq!(
  642. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  643. "text/x-toml"
  644. );
  645. assert_eq!(
  646. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  647. "inline; filename=\"Cargo.toml\""
  648. );
  649. }
  650. #[actix_rt::test]
  651. async fn test_named_file_content_disposition() {
  652. assert!(NamedFile::open("test--").is_err());
  653. let mut file = NamedFile::open("Cargo.toml").unwrap();
  654. {
  655. file.file();
  656. let _f: &File = &file;
  657. }
  658. {
  659. let _f: &mut File = &mut file;
  660. }
  661. let req = TestRequest::default().to_http_request();
  662. let resp = file.respond_to(&req).await.unwrap();
  663. assert_eq!(
  664. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  665. "inline; filename=\"Cargo.toml\""
  666. );
  667. let file = NamedFile::open("Cargo.toml")
  668. .unwrap()
  669. .disable_content_disposition();
  670. let req = TestRequest::default().to_http_request();
  671. let resp = file.respond_to(&req).await.unwrap();
  672. assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
  673. }
  674. #[actix_rt::test]
  675. async fn test_named_file_non_ascii_file_name() {
  676. let mut file =
  677. NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
  678. .unwrap();
  679. {
  680. file.file();
  681. let _f: &File = &file;
  682. }
  683. {
  684. let _f: &mut File = &mut file;
  685. }
  686. let req = TestRequest::default().to_http_request();
  687. let resp = file.respond_to(&req).await.unwrap();
  688. assert_eq!(
  689. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  690. "text/x-toml"
  691. );
  692. assert_eq!(
  693. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  694. "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
  695. );
  696. }
  697. #[actix_rt::test]
  698. async fn test_named_file_set_content_type() {
  699. let mut file = NamedFile::open("Cargo.toml")
  700. .unwrap()
  701. .set_content_type(mime::TEXT_XML);
  702. {
  703. file.file();
  704. let _f: &File = &file;
  705. }
  706. {
  707. let _f: &mut File = &mut file;
  708. }
  709. let req = TestRequest::default().to_http_request();
  710. let resp = file.respond_to(&req).await.unwrap();
  711. assert_eq!(
  712. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  713. "text/xml"
  714. );
  715. assert_eq!(
  716. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  717. "inline; filename=\"Cargo.toml\""
  718. );
  719. }
  720. #[actix_rt::test]
  721. async fn test_named_file_image() {
  722. let mut file = NamedFile::open("tests/test.png").unwrap();
  723. {
  724. file.file();
  725. let _f: &File = &file;
  726. }
  727. {
  728. let _f: &mut File = &mut file;
  729. }
  730. let req = TestRequest::default().to_http_request();
  731. let resp = file.respond_to(&req).await.unwrap();
  732. assert_eq!(
  733. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  734. "image/png"
  735. );
  736. assert_eq!(
  737. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  738. "inline; filename=\"test.png\""
  739. );
  740. }
  741. #[actix_rt::test]
  742. async fn test_named_file_image_attachment() {
  743. let cd = ContentDisposition {
  744. disposition: DispositionType::Attachment,
  745. parameters: vec![DispositionParam::Filename(String::from("test.png"))],
  746. };
  747. let mut file = NamedFile::open("tests/test.png")
  748. .unwrap()
  749. .set_content_disposition(cd);
  750. {
  751. file.file();
  752. let _f: &File = &file;
  753. }
  754. {
  755. let _f: &mut File = &mut file;
  756. }
  757. let req = TestRequest::default().to_http_request();
  758. let resp = file.respond_to(&req).await.unwrap();
  759. assert_eq!(
  760. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  761. "image/png"
  762. );
  763. assert_eq!(
  764. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  765. "attachment; filename=\"test.png\""
  766. );
  767. }
  768. #[actix_rt::test]
  769. async fn test_named_file_binary() {
  770. let mut file = NamedFile::open("tests/test.binary").unwrap();
  771. {
  772. file.file();
  773. let _f: &File = &file;
  774. }
  775. {
  776. let _f: &mut File = &mut file;
  777. }
  778. let req = TestRequest::default().to_http_request();
  779. let resp = file.respond_to(&req).await.unwrap();
  780. assert_eq!(
  781. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  782. "application/octet-stream"
  783. );
  784. assert_eq!(
  785. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  786. "attachment; filename=\"test.binary\""
  787. );
  788. }
  789. #[actix_rt::test]
  790. async fn test_named_file_status_code_text() {
  791. let mut file = NamedFile::open("Cargo.toml")
  792. .unwrap()
  793. .set_status_code(StatusCode::NOT_FOUND);
  794. {
  795. file.file();
  796. let _f: &File = &file;
  797. }
  798. {
  799. let _f: &mut File = &mut file;
  800. }
  801. let req = TestRequest::default().to_http_request();
  802. let resp = file.respond_to(&req).await.unwrap();
  803. assert_eq!(
  804. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  805. "text/x-toml"
  806. );
  807. assert_eq!(
  808. resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  809. "inline; filename=\"Cargo.toml\""
  810. );
  811. assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  812. }
  813. #[actix_rt::test]
  814. async fn test_mime_override() {
  815. fn all_attachment(_: &mime::Name) -> DispositionType {
  816. DispositionType::Attachment
  817. }
  818. let mut srv = test::init_service(
  819. App::new().service(
  820. Files::new("/", ".")
  821. .mime_override(all_attachment)
  822. .index_file("Cargo.toml"),
  823. ),
  824. )
  825. .await;
  826. let request = TestRequest::get().uri("/").to_request();
  827. let response = test::call_service(&mut srv, request).await;
  828. assert_eq!(response.status(), StatusCode::OK);
  829. let content_disposition = response
  830. .headers()
  831. .get(header::CONTENT_DISPOSITION)
  832. .expect("To have CONTENT_DISPOSITION");
  833. let content_disposition = content_disposition
  834. .to_str()
  835. .expect("Convert CONTENT_DISPOSITION to str");
  836. assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
  837. }
  838. #[actix_rt::test]
  839. async fn test_named_file_ranges_status_code() {
  840. let mut srv = test::init_service(
  841. App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
  842. )
  843. .await;
  844. // Valid range header
  845. let request = TestRequest::get()
  846. .uri("/t%65st/Cargo.toml")
  847. .header(header::RANGE, "bytes=10-20")
  848. .to_request();
  849. let response = test::call_service(&mut srv, request).await;
  850. assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
  851. // Invalid range header
  852. let request = TestRequest::get()
  853. .uri("/t%65st/Cargo.toml")
  854. .header(header::RANGE, "bytes=1-0")
  855. .to_request();
  856. let response = test::call_service(&mut srv, request).await;
  857. assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
  858. }
  859. #[actix_rt::test]
  860. async fn test_named_file_content_range_headers() {
  861. let mut srv = test::init_service(
  862. App::new().service(Files::new("/test", ".").index_file("tests/test.binary")),
  863. )
  864. .await;
  865. // Valid range header
  866. let request = TestRequest::get()
  867. .uri("/t%65st/tests/test.binary")
  868. .header(header::RANGE, "bytes=10-20")
  869. .to_request();
  870. let response = test::call_service(&mut srv, request).await;
  871. let contentrange = response
  872. .headers()
  873. .get(header::CONTENT_RANGE)
  874. .unwrap()
  875. .to_str()
  876. .unwrap();
  877. assert_eq!(contentrange, "bytes 10-20/100");
  878. // Invalid range header
  879. let request = TestRequest::get()
  880. .uri("/t%65st/tests/test.binary")
  881. .header(header::RANGE, "bytes=10-5")
  882. .to_request();
  883. let response = test::call_service(&mut srv, request).await;
  884. let contentrange = response
  885. .headers()
  886. .get(header::CONTENT_RANGE)
  887. .unwrap()
  888. .to_str()
  889. .unwrap();
  890. assert_eq!(contentrange, "bytes */100");
  891. }
  892. #[actix_rt::test]
  893. async fn test_named_file_content_length_headers() {
  894. // use actix_web::body::{MessageBody, ResponseBody};
  895. let mut srv = test::init_service(
  896. App::new().service(Files::new("test", ".").index_file("tests/test.binary")),
  897. )
  898. .await;
  899. // Valid range header
  900. let request = TestRequest::get()
  901. .uri("/t%65st/tests/test.binary")
  902. .header(header::RANGE, "bytes=10-20")
  903. .to_request();
  904. let _response = test::call_service(&mut srv, request).await;
  905. // let contentlength = response
  906. // .headers()
  907. // .get(header::CONTENT_LENGTH)
  908. // .unwrap()
  909. // .to_str()
  910. // .unwrap();
  911. // assert_eq!(contentlength, "11");
  912. // Invalid range header
  913. let request = TestRequest::get()
  914. .uri("/t%65st/tests/test.binary")
  915. .header(header::RANGE, "bytes=10-8")
  916. .to_request();
  917. let response = test::call_service(&mut srv, request).await;
  918. assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
  919. // Without range header
  920. let request = TestRequest::get()
  921. .uri("/t%65st/tests/test.binary")
  922. // .no_default_headers()
  923. .to_request();
  924. let _response = test::call_service(&mut srv, request).await;
  925. // let contentlength = response
  926. // .headers()
  927. // .get(header::CONTENT_LENGTH)
  928. // .unwrap()
  929. // .to_str()
  930. // .unwrap();
  931. // assert_eq!(contentlength, "100");
  932. // chunked
  933. let request = TestRequest::get()
  934. .uri("/t%65st/tests/test.binary")
  935. .to_request();
  936. let response = test::call_service(&mut srv, request).await;
  937. // with enabled compression
  938. // {
  939. // let te = response
  940. // .headers()
  941. // .get(header::TRANSFER_ENCODING)
  942. // .unwrap()
  943. // .to_str()
  944. // .unwrap();
  945. // assert_eq!(te, "chunked");
  946. // }
  947. let bytes = test::read_body(response).await;
  948. let data = Bytes::from(fs::read("tests/test.binary").unwrap());
  949. assert_eq!(bytes, data);
  950. }
  951. #[actix_rt::test]
  952. async fn test_head_content_length_headers() {
  953. let mut srv = test::init_service(
  954. App::new().service(Files::new("test", ".").index_file("tests/test.binary")),
  955. )
  956. .await;
  957. // Valid range header
  958. let request = TestRequest::default()
  959. .method(Method::HEAD)
  960. .uri("/t%65st/tests/test.binary")
  961. .to_request();
  962. let _response = test::call_service(&mut srv, request).await;
  963. // TODO: fix check
  964. // let contentlength = response
  965. // .headers()
  966. // .get(header::CONTENT_LENGTH)
  967. // .unwrap()
  968. // .to_str()
  969. // .unwrap();
  970. // assert_eq!(contentlength, "100");
  971. }
  972. #[actix_rt::test]
  973. async fn test_static_files_with_spaces() {
  974. let mut srv = test::init_service(
  975. App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
  976. )
  977. .await;
  978. let request = TestRequest::get()
  979. .uri("/tests/test%20space.binary")
  980. .to_request();
  981. let response = test::call_service(&mut srv, request).await;
  982. assert_eq!(response.status(), StatusCode::OK);
  983. let bytes = test::read_body(response).await;
  984. let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
  985. assert_eq!(bytes, data);
  986. }
  987. #[actix_rt::test]
  988. async fn test_files_not_allowed() {
  989. let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
  990. let req = TestRequest::default()
  991. .uri("/Cargo.toml")
  992. .method(Method::POST)
  993. .to_request();
  994. let resp = test::call_service(&mut srv, req).await;
  995. assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
  996. let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
  997. let req = TestRequest::default()
  998. .method(Method::PUT)
  999. .uri("/Cargo.toml")
  1000. .to_request();
  1001. let resp = test::call_service(&mut srv, req).await;
  1002. assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
  1003. }
  1004. #[actix_rt::test]
  1005. async fn test_files_guards() {
  1006. let mut srv = test::init_service(
  1007. App::new().service(Files::new("/", ".").use_guards(guard::Post())),
  1008. )
  1009. .await;
  1010. let req = TestRequest::default()
  1011. .uri("/Cargo.toml")
  1012. .method(Method::POST)
  1013. .to_request();
  1014. let resp = test::call_service(&mut srv, req).await;
  1015. assert_eq!(resp.status(), StatusCode::OK);
  1016. }
  1017. #[actix_rt::test]
  1018. async fn test_named_file_content_encoding() {
  1019. let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
  1020. web::resource("/").to(|| {
  1021. async {
  1022. NamedFile::open("Cargo.toml")
  1023. .unwrap()
  1024. .set_content_encoding(header::ContentEncoding::Identity)
  1025. }
  1026. }),
  1027. ))
  1028. .await;
  1029. let request = TestRequest::get()
  1030. .uri("/")
  1031. .header(header::ACCEPT_ENCODING, "gzip")
  1032. .to_request();
  1033. let res = test::call_service(&mut srv, request).await;
  1034. assert_eq!(res.status(), StatusCode::OK);
  1035. assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
  1036. }
  1037. #[actix_rt::test]
  1038. async fn test_named_file_content_encoding_gzip() {
  1039. let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
  1040. web::resource("/").to(|| {
  1041. async {
  1042. NamedFile::open("Cargo.toml")
  1043. .unwrap()
  1044. .set_content_encoding(header::ContentEncoding::Gzip)
  1045. }
  1046. }),
  1047. ))
  1048. .await;
  1049. let request = TestRequest::get()
  1050. .uri("/")
  1051. .header(header::ACCEPT_ENCODING, "gzip")
  1052. .to_request();
  1053. let res = test::call_service(&mut srv, request).await;
  1054. assert_eq!(res.status(), StatusCode::OK);
  1055. assert_eq!(
  1056. res.headers()
  1057. .get(header::CONTENT_ENCODING)
  1058. .unwrap()
  1059. .to_str()
  1060. .unwrap(),
  1061. "gzip"
  1062. );
  1063. }
  1064. #[actix_rt::test]
  1065. async fn test_named_file_allowed_method() {
  1066. let req = TestRequest::default().method(Method::GET).to_http_request();
  1067. let file = NamedFile::open("Cargo.toml").unwrap();
  1068. let resp = file.respond_to(&req).await.unwrap();
  1069. assert_eq!(resp.status(), StatusCode::OK);
  1070. }
  1071. #[actix_rt::test]
  1072. async fn test_static_files() {
  1073. let mut srv = test::init_service(
  1074. App::new().service(Files::new("/", ".").show_files_listing()),
  1075. )
  1076. .await;
  1077. let req = TestRequest::with_uri("/missing").to_request();
  1078. let resp = test::call_service(&mut srv, req).await;
  1079. assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1080. let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
  1081. let req = TestRequest::default().to_request();
  1082. let resp = test::call_service(&mut srv, req).await;
  1083. assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1084. let mut srv = test::init_service(
  1085. App::new().service(Files::new("/", ".").show_files_listing()),
  1086. )
  1087. .await;
  1088. let req = TestRequest::with_uri("/tests").to_request();
  1089. let resp = test::call_service(&mut srv, req).await;
  1090. assert_eq!(
  1091. resp.headers().get(header::CONTENT_TYPE).unwrap(),
  1092. "text/html; charset=utf-8"
  1093. );
  1094. let bytes = test::read_body(resp).await;
  1095. assert!(format!("{:?}", bytes).contains("/tests/test.png"));
  1096. }
  1097. #[actix_rt::test]
  1098. async fn test_redirect_to_slash_directory() {
  1099. // should not redirect if no index
  1100. let mut srv = test::init_service(
  1101. App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
  1102. )
  1103. .await;
  1104. let req = TestRequest::with_uri("/tests").to_request();
  1105. let resp = test::call_service(&mut srv, req).await;
  1106. assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1107. // should redirect if index present
  1108. let mut srv = test::init_service(
  1109. App::new().service(
  1110. Files::new("/", ".")
  1111. .index_file("test.png")
  1112. .redirect_to_slash_directory(),
  1113. ),
  1114. )
  1115. .await;
  1116. let req = TestRequest::with_uri("/tests").to_request();
  1117. let resp = test::call_service(&mut srv, req).await;
  1118. assert_eq!(resp.status(), StatusCode::FOUND);
  1119. // should not redirect if the path is wrong
  1120. let req = TestRequest::with_uri("/not_existing").to_request();
  1121. let resp = test::call_service(&mut srv, req).await;
  1122. assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1123. }
  1124. #[actix_rt::test]
  1125. async fn test_static_files_bad_directory() {
  1126. let _st: Files = Files::new("/", "missing");
  1127. let _st: Files = Files::new("/", "Cargo.toml");
  1128. }
  1129. #[actix_rt::test]
  1130. async fn test_default_handler_file_missing() {
  1131. let mut st = Files::new("/", ".")
  1132. .default_handler(|req: ServiceRequest| {
  1133. ok(req.into_response(HttpResponse::Ok().body("default content")))
  1134. })
  1135. .new_service(())
  1136. .await
  1137. .unwrap();
  1138. let req = TestRequest::with_uri("/missing").to_srv_request();
  1139. let resp = test::call_service(&mut st, req).await;
  1140. assert_eq!(resp.status(), StatusCode::OK);
  1141. let bytes = test::read_body(resp).await;
  1142. assert_eq!(bytes, Bytes::from_static(b"default content"));
  1143. }
  1144. // #[actix_rt::test]
  1145. // async fn test_serve_index() {
  1146. // let st = Files::new(".").index_file("test.binary");
  1147. // let req = TestRequest::default().uri("/tests").finish();
  1148. // let resp = st.handle(&req).respond_to(&req).unwrap();
  1149. // let resp = resp.as_msg();
  1150. // assert_eq!(resp.status(), StatusCode::OK);
  1151. // assert_eq!(
  1152. // resp.headers()
  1153. // .get(header::CONTENT_TYPE)
  1154. // .expect("content type"),
  1155. // "application/octet-stream"
  1156. // );
  1157. // assert_eq!(
  1158. // resp.headers()
  1159. // .get(header::CONTENT_DISPOSITION)
  1160. // .expect("content disposition"),
  1161. // "attachment; filename=\"test.binary\""
  1162. // );
  1163. // let req = TestRequest::default().uri("/tests/").finish();
  1164. // let resp = st.handle(&req).respond_to(&req).unwrap();
  1165. // let resp = resp.as_msg();
  1166. // assert_eq!(resp.status(), StatusCode::OK);
  1167. // assert_eq!(
  1168. // resp.headers().get(header::CONTENT_TYPE).unwrap(),
  1169. // "application/octet-stream"
  1170. // );
  1171. // assert_eq!(
  1172. // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  1173. // "attachment; filename=\"test.binary\""
  1174. // );
  1175. // // nonexistent index file
  1176. // let req = TestRequest::default().uri("/tests/unknown").finish();
  1177. // let resp = st.handle(&req).respond_to(&req).unwrap();
  1178. // let resp = resp.as_msg();
  1179. // assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1180. // let req = TestRequest::default().uri("/tests/unknown/").finish();
  1181. // let resp = st.handle(&req).respond_to(&req).unwrap();
  1182. // let resp = resp.as_msg();
  1183. // assert_eq!(resp.status(), StatusCode::NOT_FOUND);
  1184. // }
  1185. // #[actix_rt::test]
  1186. // async fn test_serve_index_nested() {
  1187. // let st = Files::new(".").index_file("mod.rs");
  1188. // let req = TestRequest::default().uri("/src/client").finish();
  1189. // let resp = st.handle(&req).respond_to(&req).unwrap();
  1190. // let resp = resp.as_msg();
  1191. // assert_eq!(resp.status(), StatusCode::OK);
  1192. // assert_eq!(
  1193. // resp.headers().get(header::CONTENT_TYPE).unwrap(),
  1194. // "text/x-rust"
  1195. // );
  1196. // assert_eq!(
  1197. // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
  1198. // "inline; filename=\"mod.rs\""
  1199. // );
  1200. // }
  1201. // #[actix_rt::test]
  1202. // fn integration_serve_index() {
  1203. // let mut srv = test::TestServer::with_factory(|| {
  1204. // App::new().handler(
  1205. // "test",
  1206. // Files::new(".").index_file("Cargo.toml"),
  1207. // )
  1208. // });
  1209. // let request = srv.get().uri(srv.url("/test")).finish().unwrap();
  1210. // let response = srv.execute(request.send()).unwrap();
  1211. // assert_eq!(response.status(), StatusCode::OK);
  1212. // let bytes = srv.execute(response.body()).unwrap();
  1213. // let data = Bytes::from(fs::read("Cargo.toml").unwrap());
  1214. // assert_eq!(bytes, data);
  1215. // let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
  1216. // let response = srv.execute(request.send()).unwrap();
  1217. // assert_eq!(response.status(), StatusCode::OK);
  1218. // let bytes = srv.execute(response.body()).unwrap();
  1219. // let data = Bytes::from(fs::read("Cargo.toml").unwrap());
  1220. // assert_eq!(bytes, data);
  1221. // // nonexistent index file
  1222. // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
  1223. // let response = srv.execute(request.send()).unwrap();
  1224. // assert_eq!(response.status(), StatusCode::NOT_FOUND);
  1225. // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
  1226. // let response = srv.execute(request.send()).unwrap();
  1227. // assert_eq!(response.status(), StatusCode::NOT_FOUND);
  1228. // }
  1229. // #[actix_rt::test]
  1230. // fn integration_percent_encoded() {
  1231. // let mut srv = test::TestServer::with_factory(|| {
  1232. // App::new().handler(
  1233. // "test",
  1234. // Files::new(".").index_file("Cargo.toml"),
  1235. // )
  1236. // });
  1237. // let request = srv
  1238. // .get()
  1239. // .uri(srv.url("/test/%43argo.toml"))
  1240. // .finish()
  1241. // .unwrap();
  1242. // let response = srv.execute(request.send()).unwrap();
  1243. // assert_eq!(response.status(), StatusCode::OK);
  1244. // }
  1245. #[actix_rt::test]
  1246. async fn test_path_buf() {
  1247. assert_eq!(
  1248. PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0),
  1249. Err(UriSegmentError::BadStart('.'))
  1250. );
  1251. assert_eq!(
  1252. PathBufWrp::get_pathbuf("/test/*tt").map(|t| t.0),
  1253. Err(UriSegmentError::BadStart('*'))
  1254. );
  1255. assert_eq!(
  1256. PathBufWrp::get_pathbuf("/test/tt:").map(|t| t.0),
  1257. Err(UriSegmentError::BadEnd(':'))
  1258. );
  1259. assert_eq!(
  1260. PathBufWrp::get_pathbuf("/test/tt<").map(|t| t.0),
  1261. Err(UriSegmentError::BadEnd('<'))
  1262. );
  1263. assert_eq!(
  1264. PathBufWrp::get_pathbuf("/test/tt>").map(|t| t.0),
  1265. Err(UriSegmentError::BadEnd('>'))
  1266. );
  1267. assert_eq!(
  1268. PathBufWrp::get_pathbuf("/seg1/seg2/").unwrap().0,
  1269. PathBuf::from_iter(vec!["seg1", "seg2"])
  1270. );
  1271. assert_eq!(
  1272. PathBufWrp::get_pathbuf("/seg1/../seg2/").unwrap().0,
  1273. PathBuf::from_iter(vec!["seg2"])
  1274. );
  1275. }
  1276. }