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:
parent
c23b0635ee
commit
1cfc021453
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
|
|
12
src/env.rs
12
src/env.rs
|
@ -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 => {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(()));
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user