diff --git a/src/files/pathbuf.rs b/src/files/pathbuf.rs
index a336a5e2bae956de6e864a78716d01835968ef42..429834e829da701d1a816cb1bb250313a2144fc7 100644
--- a/src/files/pathbuf.rs
+++ b/src/files/pathbuf.rs
@@ -4,11 +4,13 @@ use actix_web::{
   FromRequest, HttpRequest, ResponseError,
 };
 use futures_util::future::{ready, Ready};
+use percent_encoding::percent_decode_str;
 use snafu::Snafu;
 use std::{
   convert::{TryFrom, TryInto},
   ops::Deref,
-  path::{Path, PathBuf},
+  path::{Component, Path, PathBuf},
+  str::FromStr,
 };
 
 #[derive(Snafu, Debug, PartialEq)]
@@ -22,6 +24,12 @@ pub enum Error {
   /// The segment ended with the wrapped invalid character.
   #[snafu(display("The segment ended with the wrapped invalid character."))]
   BadEnd { char: char },
+  /// The path is not a valid UTF-8 string after percent-decoding
+  #[snafu(display("The path is not a valid UTF-8 string after percent-decoding."))]
+  NotValidUtf8,
+  /// The path has invalid or unexpected components.
+  #[snafu(display("The path has invalid or unexpected components."))]
+  InvalidComponents,
 }
 
 /// Return `BadRequest` for `Error`.
@@ -37,12 +45,23 @@ type Result<T, E = Error> = std::result::Result<T, E>;
 pub struct UriPathBuf(PathBuf);
 
 impl UriPathBuf {
-  pub fn new(path: &str) -> Result<Self> {
-    let mut buf = PathBuf::new();
+  pub fn new(raw_path: &str) -> Result<Self> {
+    let mut path = PathBuf::new();
 
-    for segment in path.split('/') {
+    let mut segment_count = raw_path.matches('/').count() + 1;
+
+    let decoded_path = percent_decode_str(raw_path)
+      .decode_utf8()
+      .map_err(|_| Error::NotValidUtf8)?;
+
+    if segment_count != decoded_path.matches('/').count() + 1 {
+      return Err(Error::BadChar { char: '/' });
+    }
+
+    for segment in decoded_path.split('/') {
       if segment == ".." {
-        buf.pop();
+        segment_count -= 1;
+        path.pop();
       } else if segment.starts_with('.') {
         return Err(Error::BadStart { char: '.' });
       } else if segment.starts_with('*') {
@@ -54,15 +73,32 @@ impl UriPathBuf {
       } else if segment.ends_with('<') {
         return Err(Error::BadEnd { char: '<' });
       } else if segment.is_empty() {
+        segment_count -= 1;
         continue;
       } else if cfg!(windows) && segment.contains('\\') {
         return Err(Error::BadChar { char: '\\' });
+      } else if cfg!(windows) && segment.contains(':') {
+        return Err(Error::BadChar { char: ':' });
       } else {
-        buf.push(segment)
+        path.push(segment)
+      }
+    }
+
+    for (i, component) in path.components().enumerate() {
+      if !matches!(component, Component::Normal(_)) || i >= segment_count {
+        return Err(Error::InvalidComponents);
       }
     }
 
-    Ok(UriPathBuf(buf))
+    Ok(UriPathBuf(path))
+  }
+}
+
+impl FromStr for UriPathBuf {
+  type Err = Error;
+
+  fn from_str(raw_path: &str) -> Result<Self, Self::Err> {
+    Self::new(raw_path)
   }
 }
 
@@ -70,8 +106,8 @@ impl FromRequest for UriPathBuf {
   type Error = Error;
   type Future = Ready<Result<Self, Self::Error>>;
 
-  fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
-    ready(req.try_into())
+  fn from_request(request: &HttpRequest, _: &mut Payload) -> Self::Future {
+    ready(request.try_into())
   }
 }
 
@@ -79,7 +115,7 @@ impl TryFrom<&HttpRequest> for UriPathBuf {
   type Error = Error;
 
   fn try_from(request: &HttpRequest) -> Result<Self, Error> {
-    UriPathBuf::new(request.match_info().path())
+    request.match_info().unprocessed().parse()
   }
 }
 
@@ -87,13 +123,13 @@ impl TryFrom<&ServiceRequest> for UriPathBuf {
   type Error = Error;
 
   fn try_from(request: &ServiceRequest) -> Result<Self, Error> {
-    UriPathBuf::new(request.match_info().path())
+    request.match_info().unprocessed().parse()
   }
 }
 
 impl From<UriPathBuf> for PathBuf {
-  fn from(buf: UriPathBuf) -> PathBuf {
-    buf.0
+  fn from(path: UriPathBuf) -> PathBuf {
+    path.0
   }
 }
 
@@ -110,33 +146,31 @@ mod tests {
 
   #[actix_rt::test]
   async fn test_path_buf() {
-    assert_eq!(
-      UriPathBuf::new("/test/.tt").map(|t| t.0),
-      Err(Error::BadStart { char: '.' })
-    );
-    assert_eq!(
-      UriPathBuf::new("/test/*tt").map(|t| t.0),
-      Err(Error::BadStart { char: '*' })
-    );
-    assert_eq!(
-      UriPathBuf::new("/test/tt:").map(|t| t.0),
-      Err(Error::BadEnd { char: ':' })
-    );
-    assert_eq!(
-      UriPathBuf::new("/test/tt<").map(|t| t.0),
-      Err(Error::BadEnd { char: '<' })
-    );
-    assert_eq!(
-      UriPathBuf::new("/test/tt>").map(|t| t.0),
-      Err(Error::BadEnd { char: '>' })
-    );
-    assert_eq!(
-      UriPathBuf::new("/seg1/seg2/").unwrap().0,
-      PathBuf::from_iter(vec!["seg1", "seg2"])
-    );
-    assert_eq!(
-      UriPathBuf::new("/seg1/../seg2/").unwrap().0,
-      PathBuf::from_iter(vec!["seg2"])
-    );
+    let cases: &[(&str, Result<PathBuf, Error>)] = &[
+      ("/test/.tt", Err(Error::BadStart { char: '.' })),
+      ("/test/*tt", Err(Error::BadStart { char: '*' })),
+      ("/test/tt:", Err(Error::BadEnd { char: ':' })),
+      ("/test/tt<", Err(Error::BadEnd { char: '<' })),
+      ("/test/tt>", Err(Error::BadEnd { char: '>' })),
+      ("hello%20world", Ok(PathBuf::from_iter(vec!["hello world"]))),
+      (
+        "/testing%21/hello%20world",
+        Ok(PathBuf::from_iter(vec!["testing!", "hello world"])),
+      ),
+      ("/seg1/seg2/", Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))),
+      ("/seg1/../seg2/", Ok(PathBuf::from_iter(vec!["seg2"]))),
+      (
+        "/../../../../../dev/null",
+        Ok(PathBuf::from_iter(vec!["dev/null"])),
+      ),
+      (
+        "/../../../..%2F../dev/null",
+        Err(Error::BadChar { char: '/' }),
+      ),
+    ];
+
+    for (input, expected) in cases {
+      assert_eq!(&UriPathBuf::new(input).map(|t| t.0), expected)
+    }
   }
 }