Add validation for paths

Summary:
When introducing `Component` we said that it will not have any slashes (`/`). This diff adds enforcement for that along with enforcing that `Component` is not empty.
`Path` and by extension `Component` also have a set of invalid bytes: `\0`, `\1` and `\n`. I got these checks from the mononoke codebase (mononoke-types/path.rs)

Reviewed By: DurhamG

Differential Revision: D14001106

fbshipit-source-id: 5acbdf66214e54f1034fd89d90e88c3e904d7f9b
This commit is contained in:
Stefan Filip 2019-02-19 13:20:39 -08:00 committed by Facebook Github Bot
parent 65604c2a4e
commit fc1901e4a4

View File

@ -3,7 +3,7 @@
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
use failure::Fallible;
use failure::{bail, Fallible};
use std::borrow::{Borrow, ToOwned};
use std::convert::AsRef;
use std::fmt;
@ -29,8 +29,9 @@ impl RepoPathBuf {
Default::default()
}
pub fn from_string(s: String) -> Self {
RepoPathBuf(s)
pub fn from_string(s: String) -> Fallible<Self> {
validate_path(&s)?;
Ok(RepoPathBuf(s))
}
pub fn push<P: AsRef<RepoPath>>(&mut self, path: P) {
@ -73,15 +74,18 @@ impl fmt::Display for RepoPathBuf {
impl RepoPath {
pub fn from_utf8(s: &[u8]) -> Fallible<&RepoPath> {
let utf8_str = std::str::from_utf8(s)?;
Ok(RepoPath::from_str(utf8_str))
RepoPath::from_str(utf8_str)
}
pub fn from_str(s: &str) -> &RepoPath {
unsafe { mem::transmute(s) }
pub fn from_str(s: &str) -> Fallible<&RepoPath> {
validate_path(s)?;
Ok(unsafe { mem::transmute(s) })
}
pub fn components(&self) -> impl Iterator<Item = &PathComponent> {
self.0.split(SEPARATOR).map(|s| PathComponent::from_str(s))
self.0
.split(SEPARATOR)
.map(|s| PathComponent::from_str_unchecked(s))
}
}
@ -105,8 +109,9 @@ impl fmt::Display for RepoPath {
}
impl PathComponentBuf {
pub fn from_string(s: String) -> Self {
PathComponentBuf(s)
pub fn from_string(s: String) -> Fallible<Self> {
validate_component(&s)?;
Ok(PathComponentBuf(s))
}
}
@ -138,10 +143,15 @@ impl fmt::Display for PathComponentBuf {
impl PathComponent {
pub fn from_utf8(s: &[u8]) -> Fallible<&PathComponent> {
let utf8_str = std::str::from_utf8(s)?;
Ok(PathComponent::from_str(utf8_str))
PathComponent::from_str(utf8_str)
}
pub fn from_str(s: &str) -> &PathComponent {
pub fn from_str(s: &str) -> Fallible<&PathComponent> {
validate_component(s)?;
Ok(PathComponent::from_str_unchecked(s))
}
fn from_str_unchecked(s: &str) -> &PathComponent {
unsafe { mem::transmute(s) }
}
}
@ -154,7 +164,7 @@ impl AsRef<PathComponent> for PathComponent {
impl AsRef<RepoPath> for PathComponent {
fn as_ref(&self) -> &RepoPath {
RepoPath::from_str(&self.0)
unsafe { mem::transmute(&self.0) }
}
}
@ -171,6 +181,31 @@ impl fmt::Display for PathComponent {
}
}
fn validate_path(s: &str) -> Fallible<()> {
if s.bytes().next_back() == Some(b'/') {
bail!("Invalid path: ends with `/`.");
}
for component in s.split(SEPARATOR) {
validate_component(component)?;
}
Ok(())
}
fn validate_component(s: &str) -> Fallible<()> {
if s.is_empty() {
bail!("Invalid component: empty.");
}
if s == "." || s == ".." {
bail!("Invalid component: {}", s);
}
for b in s.bytes() {
if b == 0u8 || b == 1u8 || b == b'\n' || b == b'/' {
bail!("Invalid component: contains byte {}.", b);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@ -186,7 +221,7 @@ mod tests {
format!("{}", RepoPath::from_utf8(b"slice").unwrap()),
"slice"
);
assert_eq!(format!("{}", RepoPath::from_str("slice")), "slice");
assert_eq!(format!("{}", RepoPath::from_str("slice").unwrap()), "slice");
}
#[test]
@ -196,7 +231,7 @@ mod tests {
"RepoPath(\"slice\")"
);
assert_eq!(
format!("{:?}", RepoPath::from_str("slice")),
format!("{:?}", RepoPath::from_str("slice").unwrap()),
"RepoPath(\"slice\")"
);
}
@ -205,7 +240,10 @@ mod tests {
fn test_pathbuf_display() {
assert_eq!(format!("{}", RepoPathBuf::new()), "");
assert_eq!(
format!("{}", RepoPathBuf::from_string(String::from("slice"))),
format!(
"{}",
RepoPathBuf::from_string(String::from("slice")).unwrap()
),
"slice"
);
}
@ -214,27 +252,33 @@ mod tests {
fn test_pathbuf_debug() {
assert_eq!(format!("{:?}", RepoPathBuf::new()), "RepoPathBuf(\"\")");
assert_eq!(
format!("{:?}", RepoPathBuf::from_string(String::from("slice"))),
format!(
"{:?}",
RepoPathBuf::from_string(String::from("slice")).unwrap()
),
"RepoPathBuf(\"slice\")"
);
}
#[test]
fn test_repo_path_conversions() {
let repo_path_buf = RepoPathBuf::from_string(String::from("path_buf"));
let repo_path_buf = RepoPathBuf::from_string(String::from("path_buf")).unwrap();
assert_eq!(repo_path_buf.as_ref().to_owned(), repo_path_buf);
let repo_path = RepoPath::from_str("path");
let repo_path = RepoPath::from_str("path").unwrap();
assert_eq!(repo_path.to_owned().as_ref(), repo_path);
}
#[test]
fn test_repo_path_push() {
let mut repo_path_buf = RepoPathBuf::new();
repo_path_buf.push(RepoPath::from_str("one"));
assert_eq!(repo_path_buf.as_ref(), RepoPath::from_str("one"));
repo_path_buf.push(RepoPath::from_str("two"));
assert_eq!(repo_path_buf.as_ref(), RepoPath::from_str("one/two"));
repo_path_buf.push(RepoPath::from_str("one").unwrap());
assert_eq!(repo_path_buf.as_ref(), RepoPath::from_str("one").unwrap());
repo_path_buf.push(RepoPath::from_str("two").unwrap());
assert_eq!(
repo_path_buf.as_ref(),
RepoPath::from_str("one/two").unwrap()
);
}
#[test]
@ -248,7 +292,10 @@ mod tests {
format!("{}", PathComponent::from_utf8(b"slice").unwrap()),
"slice"
);
assert_eq!(format!("{}", PathComponent::from_str("slice")), "slice");
assert_eq!(
format!("{}", PathComponent::from_str("slice").unwrap()),
"slice"
);
}
#[test]
@ -258,52 +305,91 @@ mod tests {
"PathComponent(\"slice\")"
);
assert_eq!(
format!("{:?}", PathComponent::from_str("slice")),
format!("{:?}", PathComponent::from_str("slice").unwrap()),
"PathComponent(\"slice\")"
);
)
}
#[test]
fn test_componentbuf_display() {
assert_eq!(
format!("{}", PathComponentBuf::from_string(String::from("slice"))),
"slice"
format!(
"{}",
PathComponentBuf::from_string(String::from("slice")).unwrap()
),
"slice",
);
}
#[test]
fn test_componentbuf_debug() {
assert_eq!(
format!("{:?}", PathComponentBuf::from_string(String::from("slice"))),
format!(
"{:?}",
PathComponentBuf::from_string(String::from("slice")).unwrap()
),
"PathComponentBuf(\"slice\")"
);
}
#[test]
fn test_component_conversions() {
let componentbuf = PathComponentBuf::from_string(String::from("componentbuf"));
let componentbuf = PathComponentBuf::from_string(String::from("componentbuf")).unwrap();
assert_eq!(componentbuf.as_ref().to_owned(), componentbuf);
let component = PathComponent::from_str("component");
let component = PathComponent::from_str("component").unwrap();
assert_eq!(component.to_owned().as_ref(), component);
}
#[test]
fn test_path_components() {
let mut iter = RepoPath::from_str("foo/bar/baz.txt").components();
assert_eq!(iter.next().unwrap(), PathComponent::from_str("foo"));
assert_eq!(iter.next().unwrap(), PathComponent::from_str("bar"));
assert_eq!(iter.next().unwrap(), PathComponent::from_str("baz.txt"));
let mut iter = RepoPath::from_str("foo/bar/baz.txt").unwrap().components();
assert_eq!(
iter.next().unwrap(),
PathComponent::from_str("foo").unwrap()
);
assert_eq!(
iter.next().unwrap(),
PathComponent::from_str("bar").unwrap()
);
assert_eq!(
iter.next().unwrap(),
PathComponent::from_str("baz.txt").unwrap()
);
assert!(iter.next().is_none());
}
#[test]
fn test_append_component_to_path() {
let expected = RepoPathBuf::from_string(String::from("foo/bar/baz.txt"));
let expected = RepoPath::from_str("foo/bar/baz.txt").unwrap();
let mut pathbuf = RepoPathBuf::new();
for component in expected.components() {
pathbuf.push(component);
}
assert_eq!(pathbuf, expected);
assert_eq!(pathbuf.deref(), expected);
}
#[test]
fn test_validate_path() {
assert_eq!(
format!("{}", validate_path("\n").unwrap_err()),
"Invalid component: contains byte 10."
);
assert_eq!(
format!("{}", validate_path("boo/").unwrap_err()),
"Invalid path: ends with `/`."
);
}
#[test]
fn test_validate_component() {
assert_eq!(
format!("{}", validate_component("foo/bar").unwrap_err()),
"Invalid component: contains byte 47."
);
assert_eq!(
format!("{}", validate_component("").unwrap_err()),
"Invalid component: empty."
);
}
}