Forbid empty input, output, and path targets

When an empty path is passed to `Env::resolve`, the result is the
current working directory. This is bad, so forbid the user to pass in
empty paths.

type: fixed
This commit is contained in:
Casey Rodarmor 2020-04-05 18:17:38 -07:00
parent c23b0635ee
commit 1cfc021453
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
13 changed files with 198 additions and 128 deletions

View File

@ -10,7 +10,6 @@ merge_imports = true
newline_style = "Unix"
normalize_comments = true
reorder_impl_items = true
required_version = "1.4.12"
tab_spaces = 2
unstable_features = true
use_field_init_shorthand = true

View File

@ -4,7 +4,7 @@ pub(crate) use std::{
char,
cmp::{Ordering, Reverse},
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
convert::TryInto,
convert::{TryFrom, TryInto},
env,
ffi::{OsStr, OsString},
fmt::{self, Display, Formatter},

View File

@ -163,14 +163,20 @@ impl Env {
&mut self.out
}
pub(crate) fn resolve(&self, path: impl AsRef<Path>) -> PathBuf {
self.dir().join(path).clean()
pub(crate) fn resolve(&self, path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref();
if path.components().count() == 0 {
return Err(Error::internal("Empty path passed to resolve"));
}
Ok(self.dir().join(path).clean())
}
pub(crate) fn read(&mut self, source: InputTarget) -> Result<Input> {
let data = match &source {
InputTarget::Path(path) => {
let absolute = self.resolve(path);
let absolute = self.resolve(path)?;
fs::read(absolute).context(error::Filesystem { path })?
}
InputTarget::Stdin => {

View File

@ -30,6 +30,8 @@ pub(crate) enum Error {
GlobParse { source: globset::Error },
#[snafu(display("Failed to serialize torrent info dictionary: {}", source))]
InfoSerialize { source: bendy::serde::Error },
#[snafu(display("Input target empty"))]
InputTargetEmpty,
#[snafu(display(
"Interal error, this may indicate a bug in intermodal: {}\n\
Consider filing an issue: https://github.com/casey/imdl/issues/new",
@ -61,6 +63,8 @@ pub(crate) enum Error {
OpenerExitStatus { exit_status: ExitStatus },
#[snafu(display("Output path already exists: `{}`", path.display()))]
OutputExists { path: PathBuf },
#[snafu(display("Output target empty"))]
OutputTargetEmpty,
#[snafu(display(
"Path `{}` contains non-normal component: {}",
path.display(),

View File

@ -7,20 +7,32 @@ pub(crate) enum InputTarget {
}
impl InputTarget {
pub(crate) fn resolve(&self, env: &Env) -> Self {
pub(crate) fn resolve(&self, env: &Env) -> Result<Self> {
match self {
Self::Path(path) => Self::Path(env.resolve(path)),
Self::Stdin => Self::Stdin,
}
Self::Path(path) => Ok(Self::Path(env.resolve(path)?)),
Self::Stdin => Ok(Self::Stdin),
}
}
impl From<&OsStr> for InputTarget {
fn from(text: &OsStr) -> Self {
pub(crate) fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> {
text
.try_into()
.map_err(|err: Error| OsString::from(err.to_string()))
}
}
impl TryFrom<&OsStr> for InputTarget {
type Error = Error;
fn try_from(text: &OsStr) -> Result<Self, Self::Error> {
if text.is_empty() {
return Err(Error::InputTargetEmpty);
};
if text == OsStr::new("-") {
Self::Stdin
Ok(Self::Stdin)
} else {
Self::Path(text.into())
Ok(Self::Path(text.into()))
}
}
}
@ -50,14 +62,17 @@ mod tests {
#[test]
fn file() {
assert_eq!(
InputTarget::from(OsStr::new("foo")),
InputTarget::Path("foo".into())
InputTarget::try_from(OsStr::new("foo")).unwrap(),
InputTarget::Path("foo".into()),
);
}
#[test]
fn stdio() {
assert_eq!(InputTarget::from(OsStr::new("-")), InputTarget::Stdin);
assert_eq!(
InputTarget::try_from(OsStr::new("-")).unwrap(),
InputTarget::Stdin
);
}
#[test]

View File

@ -2,25 +2,37 @@ use crate::common::*;
#[derive(PartialEq, Debug)]
pub(crate) enum OutputTarget {
File(PathBuf),
Path(PathBuf),
Stdout,
}
impl OutputTarget {
pub(crate) fn resolve(&self, env: &Env) -> Self {
pub(crate) fn resolve(&self, env: &Env) -> Result<Self> {
match self {
Self::File(path) => Self::File(env.resolve(path)),
Self::Stdout => Self::Stdout,
}
Self::Path(path) => Ok(Self::Path(env.resolve(path)?)),
Self::Stdout => Ok(Self::Stdout),
}
}
impl From<&OsStr> for OutputTarget {
fn from(text: &OsStr) -> Self {
pub(crate) fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> {
text
.try_into()
.map_err(|err: Error| OsString::from(err.to_string()))
}
}
impl TryFrom<&OsStr> for OutputTarget {
type Error = Error;
fn try_from(text: &OsStr) -> Result<Self, Self::Error> {
if text.is_empty() {
return Err(Error::OutputTargetEmpty);
};
if text == OsStr::new("-") {
Self::Stdout
Ok(Self::Stdout)
} else {
Self::File(text.into())
Ok(Self::Path(text.into()))
}
}
}
@ -29,7 +41,7 @@ impl Display for OutputTarget {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Stdout => write!(f, "standard output"),
Self::File(path) => write!(f, "`{}`", path.display()),
Self::Path(path) => write!(f, "`{}`", path.display()),
}
}
}
@ -41,20 +53,23 @@ mod tests {
#[test]
fn file() {
assert_eq!(
OutputTarget::from(OsStr::new("foo")),
OutputTarget::File("foo".into())
OutputTarget::try_from(OsStr::new("foo")).unwrap(),
OutputTarget::Path("foo".into())
);
}
#[test]
fn stdio() {
assert_eq!(OutputTarget::from(OsStr::new("-")), OutputTarget::Stdout);
assert_eq!(
OutputTarget::try_from(OsStr::new("-")).unwrap(),
OutputTarget::Stdout
);
}
#[test]
fn display_file() {
let path = PathBuf::from("./path");
let have = OutputTarget::File(path).to_string();
let have = OutputTarget::Path(path).to_string();
let want = "`./path`";
assert_eq!(have, want);
}

View File

@ -116,11 +116,12 @@ Examples:
long = "input",
short = "i",
value_name = "PATH",
empty_values(false),
parse(try_from_os_str = InputTarget::try_from_os_str),
help = "Read torrent contents from `PATH`. If `PATH` is a file, torrent will be a single-file \
torrent. If `PATH` is a directory, torrent will be a multi-file torrent. If `PATH` \
is `-`, read from standard input. Piece length defaults to 256KiB when reading from \
standard input if `--piece-length` is not given.",
parse(from_os_str)
)]
input: InputTarget,
#[structopt(
@ -187,11 +188,12 @@ Sort in ascending order by size, break ties in descending path order:
long = "output",
short = "o",
value_name = "TARGET",
empty_values(false),
parse(try_from_os_str = OutputTarget::try_from_os_str),
required_if("input", "-"),
help = "Save `.torrent` file to `TARGET`, or print to standard output if `TARGET` is `-`. \
Defaults to the argument to `--input` with an `.torrent` extension appended. Required \
when `--input -`.",
required_if("input", "-"),
parse(from_os_str)
)]
output: Option<OutputTarget>,
#[structopt(
@ -240,8 +242,11 @@ Sort in ascending order by size, break ties in descending path order:
impl Create {
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
let input = self.input.resolve(env);
let output = self.output.map(|output| output.resolve(env));
let input = self.input.resolve(env)?;
let output = match self.output {
Some(output) => Some(output.resolve(env)?),
None => None,
};
let mut linter = Linter::new();
linter.allow(self.allowed_lints.iter().cloned());
@ -320,14 +325,11 @@ impl Create {
files = Some(files_inner);
output
.as_ref()
.map(|output| output.resolve(env))
.unwrap_or_else(|| {
output.unwrap_or_else(|| {
let mut torrent_name = name.to_owned();
torrent_name.push_str(".torrent");
OutputTarget::File(path.parent().unwrap().join(torrent_name))
OutputTarget::Path(path.parent().unwrap().join(torrent_name))
})
}
@ -363,7 +365,7 @@ impl Create {
return Err(Error::PieceLengthSmall);
}
if let OutputTarget::File(path) = &output {
if let OutputTarget::Path(path) = &output {
if !self.force && path.exists() {
return Err(Error::OutputExists {
path: path.to_owned(),
@ -441,7 +443,7 @@ impl Create {
if !self.dry_run {
match &output {
OutputTarget::File(path) => {
OutputTarget::Path(path) => {
let mut open_options = fs::OpenOptions::new();
if self.force {
@ -490,7 +492,7 @@ impl Create {
outln!(env, "{}", link)?;
}
if let OutputTarget::File(path) = output {
if let OutputTarget::Path(path) = output {
if self.open {
Platform::open_file(&path)?;
}
@ -546,7 +548,7 @@ mod tests {
}
#[test]
fn torrent_file_is_bencode_dict() {
fn torrent_file_is_bencode_dict() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -561,10 +563,11 @@ mod tests {
}
};
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let torrent = env.resolve("foo.torrent")?;
let bytes = fs::read(torrent).unwrap();
let value = Value::from_bencode(&bytes).unwrap();
assert!(matches!(value, Value::Dict(_)));
Ok(())
}
#[test]
@ -1531,7 +1534,7 @@ mod tests {
}
#[test]
fn output() {
fn output() -> Result<()> {
let mut env = TestEnvBuilder::new()
.arg_slice(&[
"imdl",
@ -1546,17 +1549,18 @@ mod tests {
.out_is_term()
.build();
let dir = env.resolve("foo");
let dir = env.resolve("foo")?;
fs::create_dir(&dir).unwrap();
fs::write(dir.join("a"), "abc").unwrap();
fs::write(dir.join("x"), "xyz").unwrap();
fs::write(dir.join("h"), "hij").unwrap();
env.run().unwrap();
assert_eq!(env.out(), "");
Ok(())
}
#[test]
fn show() {
fn show() -> Result<()> {
let mut env = TestEnvBuilder::new()
.arg_slice(&[
"imdl",
@ -1572,7 +1576,7 @@ mod tests {
.out_is_term()
.build();
let dir = env.resolve("foo");
let dir = env.resolve("foo")?;
fs::create_dir(&dir).unwrap();
fs::write(dir.join("a"), "abc").unwrap();
fs::write(dir.join("x"), "xyz").unwrap();
@ -1600,6 +1604,7 @@ Content Size 9 bytes
212 + consts::CREATED_BY_DEFAULT.len()
);
assert_eq!(have, want);
Ok(())
}
#[test]
@ -1625,7 +1630,7 @@ Content Size 9 bytes
}
#[test]
fn force_default() {
fn force_default() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1643,8 +1648,9 @@ Content Size 9 bytes
assert_matches!(
env.run().unwrap_err(),
Error::OutputExists {path}
if path == env.resolve("foo.torrent")
)
if path == env.resolve("foo.torrent")?
);
Ok(())
}
#[test]
@ -1724,7 +1730,7 @@ Content Size 9 bytes
}
#[test]
fn skip_hidden() {
fn skip_hidden() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1745,17 +1751,17 @@ Content Size 9 bytes
if cfg!(target_os = "windows") {
Command::new("attrib")
.arg("+h")
.arg(env.resolve("foo/hidden"))
.arg(env.resolve("foo/hidden")?)
.status()
.unwrap();
} else if cfg!(target_os = "macos") {
Command::new("chflags")
.arg("hidden")
.arg(env.resolve("foo/hidden"))
.arg(env.resolve("foo/hidden")?)
.status()
.unwrap();
} else {
fs::remove_file(env.resolve("foo/hidden")).unwrap();
fs::remove_file(env.resolve("foo/hidden")?).unwrap();
}
env.run().unwrap();
@ -1767,10 +1773,11 @@ Content Size 9 bytes
Mode::Multiple { files } if files.len() == 0
);
assert_eq!(metainfo.info.pieces, PieceList::new());
Ok(())
}
#[test]
fn include_hidden() {
fn include_hidden() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1792,13 +1799,13 @@ Content Size 9 bytes
if cfg!(target_os = "windows") {
Command::new("attrib")
.arg("+h")
.arg(env.resolve("foo/hidden"))
.arg(env.resolve("foo/hidden")?)
.status()
.unwrap();
} else if cfg!(target_os = "macos") {
Command::new("chflags")
.arg("hidden")
.arg(env.resolve("foo/hidden"))
.arg(env.resolve("foo/hidden")?)
.status()
.unwrap();
}
@ -1810,12 +1817,13 @@ Content Size 9 bytes
Mode::Multiple { files } if files.len() == 2
);
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abcabc"]));
Ok(())
}
fn populate_symlinks(env: &Env) {
let dir = env.resolve("foo");
let file_src = env.resolve("bar");
let dir_src = env.resolve("dir-src");
fn populate_symlinks(env: &Env) -> Result<()> {
let dir = env.resolve("foo")?;
let file_src = env.resolve("bar")?;
let dir_src = env.resolve("dir-src")?;
let dir_contents = dir_src.join("baz");
fs::create_dir(&dir_src).unwrap();
fs::write(dir_contents, "baz").unwrap();
@ -1824,8 +1832,8 @@ Content Size 9 bytes
fs::write(file_src, "bar").unwrap();
#[cfg(unix)]
{
let file_link = env.resolve("foo/bar");
let dir_link = env.resolve("foo/dir");
let file_link = env.resolve("foo/bar")?;
let dir_link = env.resolve("foo/dir")?;
Command::new("ln")
.arg("-s")
.arg("../bar")
@ -1840,10 +1848,12 @@ Content Size 9 bytes
.status()
.unwrap();
}
Ok(())
}
#[test]
fn skip_symlinks() {
fn skip_symlinks() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1856,7 +1866,7 @@ Content Size 9 bytes
],
tree: {},
};
populate_symlinks(&env);
populate_symlinks(&env)?;
env.run().unwrap();
let metainfo = env.load_metainfo("foo.torrent");
assert_matches!(
@ -1864,11 +1874,12 @@ Content Size 9 bytes
Mode::Multiple { files } if files.is_empty()
);
assert_eq!(metainfo.info.pieces, PieceList::new());
Ok(())
}
#[test]
#[cfg(unix)]
fn follow_symlinks() {
fn follow_symlinks() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1882,7 +1893,7 @@ Content Size 9 bytes
],
tree: {},
};
populate_symlinks(&env);
populate_symlinks(&env)?;
env.run().unwrap();
let metainfo = env.load_metainfo("foo.torrent");
let mut pieces = PieceList::new();
@ -1908,11 +1919,12 @@ Content Size 9 bytes
}
_ => panic!("Expected multi-file torrent"),
}
Ok(())
}
#[test]
#[cfg(unix)]
fn symlink_root() {
fn symlink_root() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1926,8 +1938,8 @@ Content Size 9 bytes
tree: {},
};
let file_src = env.resolve("bar");
let file_link = env.resolve("foo");
let file_src = env.resolve("bar")?;
let file_link = env.resolve("foo")?;
Command::new("ln")
.arg("-s")
@ -1937,6 +1949,7 @@ Content Size 9 bytes
.unwrap();
assert_matches!(env.run().unwrap_err(), Error::SymlinkRoot { root } if root == file_link);
Ok(())
}
#[test]
@ -1967,9 +1980,8 @@ Content Size 9 bytes
);
assert_eq!(metainfo.info.pieces, PieceList::new());
}
#[test]
fn skip_hidden_attribute_dir_contents() {
fn skip_hidden_attribute_dir_contents() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -1990,7 +2002,7 @@ Content Size 9 bytes
#[cfg(target_os = "windows")]
{
env.write("foo/bar/baz", "baz");
let path = env.resolve("foo/bar");
let path = env.resolve("foo/bar")?;
Command::new("attrib")
.arg("+h")
.arg(&path)
@ -2001,7 +2013,7 @@ Content Size 9 bytes
#[cfg(target_os = "macos")]
{
env.write("foo/bar/baz", "baz");
let path = env.resolve("foo/bar");
let path = env.resolve("foo/bar")?;
Command::new("chflags")
.arg("hidden")
.arg(&path)
@ -2016,6 +2028,7 @@ Content Size 9 bytes
Mode::Multiple { files } if files.is_empty()
);
assert_eq!(metainfo.info.pieces, PieceList::new());
Ok(())
}
#[test]
@ -2255,7 +2268,7 @@ Content Size 9 bytes
}
#[test]
fn create_progress_messages() {
fn create_progress_messages() -> Result<()> {
let mut env = TestEnvBuilder::new()
.arg_slice(&[
"imdl",
@ -2268,17 +2281,18 @@ Content Size 9 bytes
])
.build();
fs::write(env.resolve("foo"), "").unwrap();
fs::write(env.resolve("foo")?, "").unwrap();
let want = format!(
"[1/3] \u{1F9FF} Searching for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \u{1F4BE} \
Writing metainfo to `{}`\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
env.resolve("foo.torrent").display()
env.resolve("foo.torrent")?.display()
);
env.run().unwrap();
assert_eq!(env.err(), want);
Ok(())
}
#[test]
@ -2436,7 +2450,7 @@ Content Size 9 bytes
}
#[test]
fn dry_run_skips_torrent_file_creation() {
fn dry_run_skips_torrent_file_creation() -> Result<()> {
let mut env = test_env! {
args: [
"torrent",
@ -2450,9 +2464,10 @@ Content Size 9 bytes
}
};
assert_matches!(env.run(), Ok(()));
let torrent = env.resolve("foo.torrent");
let torrent = env.resolve("foo.torrent")?;
let err = fs::read(torrent).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::NotFound);
Ok(())
}
#[test]

View File

@ -11,9 +11,10 @@ pub(crate) struct Link {
long = "input",
short = "i",
value_name = "METAINFO",
empty_values(false),
parse(try_from_os_str = InputTarget::try_from_os_str),
help = "Generate magnet link from metainfo at `PATH`. If `PATH` is `-`, read metainfo from \
standard input.",
parse(from_os_str)
)]
input: InputTarget,
#[structopt(

View File

@ -11,9 +11,10 @@ pub(crate) struct Show {
long = "input",
short = "i",
value_name = "PATH",
empty_values(false),
parse(try_from_os_str = InputTarget::try_from_os_str),
help = "Show information about torrent at `PATH`. If `Path` is `-`, read torrent metainfo \
from standard input.",
parse(from_os_str)
)]
input: InputTarget,
}
@ -34,7 +35,7 @@ mod tests {
use pretty_assertions::assert_eq;
#[test]
fn output() {
fn output() -> Result<()> {
let metainfo = Metainfo {
announce: Some("announce".into()),
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
@ -66,7 +67,7 @@ mod tests {
.out_is_term()
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -103,7 +104,7 @@ Announce List Tier 1: announce
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -131,10 +132,12 @@ files\tfoo
assert_eq!(have, want);
}
Ok(())
}
#[test]
fn tier_list_with_main() {
fn tier_list_with_main() -> Result<()> {
let metainfo = Metainfo {
announce: Some("a".into()),
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
@ -166,7 +169,7 @@ files\tfoo
.out_is_term()
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -203,7 +206,7 @@ Announce List Tier 1: x
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -231,10 +234,12 @@ files\tfoo
assert_eq!(have, want);
}
Ok(())
}
#[test]
fn tier_list_without_main() {
fn tier_list_without_main() -> Result<()> {
let metainfo = Metainfo {
announce: Some("a".into()),
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".into()]]),
@ -266,7 +271,7 @@ files\tfoo
.out_is_term()
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -303,7 +308,7 @@ Announce List Tier 1: b
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -331,10 +336,12 @@ files\tfoo
assert_eq!(have, want);
}
Ok(())
}
#[test]
fn trackerless() {
fn trackerless() -> Result<()> {
let metainfo = Metainfo {
announce: None,
announce_list: None,
@ -366,7 +373,7 @@ files\tfoo
.out_is_term()
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -399,7 +406,7 @@ Creation Date 1970-01-01 00:00:01 UTC
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
.build();
let path = env.resolve("foo.torrent");
let path = env.resolve("foo.torrent")?;
metainfo.dump(path).unwrap();
@ -425,5 +432,7 @@ files\tfoo
assert_eq!(have, want);
}
Ok(())
}
}

View File

@ -19,6 +19,7 @@ pub(crate) struct Stats {
long = "extract-pattern",
short = "e",
value_name = "REGEX",
empty_values(false),
help = "Extract and display values under key paths that match `REGEX`. Subkeys of a \
bencodeded dictionary are delimited by `/`, and values of a bencoded list are \
delmited by `*`. For example, given the following bencoded dictionary `{\"foo\": \
@ -31,8 +32,9 @@ pub(crate) struct Stats {
long = "input",
short = "i",
value_name = "PATH",
help = "Search `PATH` for torrents. May be a directory or a single torrent file.",
parse(from_os_str)
empty_values(false),
parse(from_os_str),
help = "Search `PATH` for torrents. May be a directory or a single torrent file."
)]
input: PathBuf,
#[structopt(
@ -47,7 +49,7 @@ impl Stats {
pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> {
options.require_unstable("torrent stats subcommand")?;
let path = env.resolve(self.input);
let path = env.resolve(self.input)?;
let mut extractor = Extractor::new(self.print, &self.extract_patterns);

View File

@ -14,18 +14,20 @@ pub(crate) struct Verify {
long = "input",
short = "i",
value_name = "METAINFO",
empty_values(false),
parse(try_from_os_str = InputTarget::try_from_os_str),
help = "Verify torrent contents against torrent metainfo in `METAINFO`. If `METAINFO` is `-`, \
read metainfo from standard input.",
parse(from_os_str)
)]
metainfo: InputTarget,
#[structopt(
long = "content",
short = "c",
value_name = "PATH",
empty_values(false),
parse(from_os_str),
help = "Verify torrent content at `PATH` against torrent metainfo. Defaults to `name` field \
of torrent info dictionary.",
parse(from_os_str)
of torrent info dictionary."
)]
content: Option<PathBuf>,
}
@ -66,7 +68,7 @@ impl Verify {
VerifyStep::Verifying { content: &content }.print(env)?;
let status = metainfo.verify(&env.resolve(content), progress_bar)?;
let status = metainfo.verify(&env.resolve(content)?, progress_bar)?;
status.print(env)?;
@ -119,7 +121,7 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
let mut verify_env = test_env! {
args: [
@ -137,7 +139,7 @@ mod tests {
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from \
`{}`\n\u{2728}\u{2728} Verification succeeded! \u{2728}\u{2728}\n",
torrent.display(),
create_env.resolve("foo").display()
create_env.resolve("foo")?.display()
);
assert_eq!(verify_env.err(), want);
@ -170,7 +172,7 @@ mod tests {
create_env.write("foo/a", "xyz");
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
let mut verify_env = test_env! {
args: [
@ -191,7 +193,7 @@ mod tests {
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
create_env.resolve("foo")?.display()
),
"Pieces corrupted.",
"error: Torrent verification failed.",
@ -227,11 +229,11 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
let foo = create_env.resolve("foo");
let foo = create_env.resolve("foo")?;
let bar = create_env.resolve("bar");
let bar = create_env.resolve("bar")?;
fs::rename(&foo, &bar).unwrap();
@ -284,8 +286,8 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let content = create_env.resolve("foo");
let torrent = create_env.resolve("foo.torrent")?;
let content = create_env.resolve("foo")?;
let mut verify_env = test_env! {
args: [
@ -340,7 +342,7 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
create_env.write("foo/a", "xyz");
create_env.write("foo/d", "efgg");
@ -377,7 +379,7 @@ mod tests {
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
create_env.resolve("foo")?.display()
),
"a: MD5 checksum mismatch: d16fb36f0911f878998c136191af705e (expected \
900150983cd24fb0d6963f7d28e17f72)",
@ -425,7 +427,7 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
create_env.write("foo/a", "xyz");
create_env.write("foo/d", "efgg");
@ -482,7 +484,7 @@ mod tests {
style.dim().paint("[2/2]"),
style.message().paint(format!(
"Verifying pieces from `{}`…",
create_env.resolve("foo").display()
create_env.resolve("foo")?.display()
))
),
&format!(
@ -531,7 +533,7 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
create_env.write("foo", "abcxyz");
@ -554,7 +556,7 @@ mod tests {
),
&format!(
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
create_env.resolve("foo").display()
create_env.resolve("foo")?.display()
),
"3 bytes too long",
"Pieces corrupted.",
@ -591,7 +593,7 @@ mod tests {
create_env.run()?;
let torrent = create_env.resolve("foo.torrent");
let torrent = create_env.resolve("foo.torrent")?;
let metainfo = fs::read(torrent).unwrap();
@ -606,7 +608,7 @@ mod tests {
tree: {},
};
fs::rename(create_env.resolve("foo"), verify_env.resolve("foo")).unwrap();
fs::rename(create_env.resolve("foo")?, verify_env.resolve("foo")?).unwrap();
assert_matches!(verify_env.run(), Ok(()));

View File

@ -56,25 +56,25 @@ impl TestEnv {
}
pub(crate) fn write(&self, path: impl AsRef<Path>, bytes: impl AsRef<[u8]>) {
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
fs::write(self.env.resolve(path).unwrap(), bytes.as_ref()).unwrap();
}
pub(crate) fn remove_file(&self, path: impl AsRef<Path>) {
fs::remove_file(self.env.resolve(path)).unwrap();
fs::remove_file(self.env.resolve(path).unwrap()).unwrap();
}
pub(crate) fn create_dir(&self, path: impl AsRef<Path>) {
fs::create_dir(self.env.resolve(path)).unwrap();
fs::create_dir(self.env.resolve(path).unwrap()).unwrap();
}
#[cfg(unix)]
pub(crate) fn metadata(&self, path: impl AsRef<Path>) -> fs::Metadata {
fs::metadata(self.env.resolve(path)).unwrap()
fs::metadata(self.env.resolve(path).unwrap()).unwrap()
}
#[cfg(unix)]
pub(crate) fn set_permissions(&self, path: impl AsRef<Path>, permissions: fs::Permissions) {
fs::set_permissions(self.env.resolve(path), permissions).unwrap();
fs::set_permissions(self.env.resolve(path).unwrap(), permissions).unwrap();
}
pub(crate) fn assert_ok(&mut self) {
@ -90,7 +90,9 @@ impl TestEnv {
}
pub(crate) fn load_metainfo(&mut self, filename: impl AsRef<Path>) -> Metainfo {
let input = self.env.read(filename.as_ref().as_os_str().into()).unwrap();
let path = filename.as_ref();
let target = InputTarget::try_from(path.as_os_str()).unwrap();
let input = self.env.read(target).unwrap();
Metainfo::from_input(&input).unwrap()
}
}

View File

@ -146,7 +146,7 @@ mod tests {
let metainfo = env.load_metainfo("foo.torrent");
assert!(metainfo.verify(&env.resolve("foo"), None)?.good());
assert!(metainfo.verify(&env.resolve("foo")?, None)?.good());
Ok(())
}
@ -177,7 +177,7 @@ mod tests {
let metainfo = env.load_metainfo("foo.torrent");
let status = metainfo.verify(&env.resolve("foo"), None)?;
let status = metainfo.verify(&env.resolve("foo")?, None)?;
assert_eq!(status.count_bad(), 0);