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"
|
newline_style = "Unix"
|
||||||
normalize_comments = true
|
normalize_comments = true
|
||||||
reorder_impl_items = true
|
reorder_impl_items = true
|
||||||
required_version = "1.4.12"
|
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
unstable_features = true
|
unstable_features = true
|
||||||
use_field_init_shorthand = true
|
use_field_init_shorthand = true
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub(crate) use std::{
|
||||||
char,
|
char,
|
||||||
cmp::{Ordering, Reverse},
|
cmp::{Ordering, Reverse},
|
||||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||||
convert::TryInto,
|
convert::{TryFrom, TryInto},
|
||||||
env,
|
env,
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
|
|
12
src/env.rs
12
src/env.rs
|
@ -163,14 +163,20 @@ impl Env {
|
||||||
&mut self.out
|
&mut self.out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve(&self, path: impl AsRef<Path>) -> PathBuf {
|
pub(crate) fn resolve(&self, path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||||
self.dir().join(path).clean()
|
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> {
|
pub(crate) fn read(&mut self, source: InputTarget) -> Result<Input> {
|
||||||
let data = match &source {
|
let data = match &source {
|
||||||
InputTarget::Path(path) => {
|
InputTarget::Path(path) => {
|
||||||
let absolute = self.resolve(path);
|
let absolute = self.resolve(path)?;
|
||||||
fs::read(absolute).context(error::Filesystem { path })?
|
fs::read(absolute).context(error::Filesystem { path })?
|
||||||
}
|
}
|
||||||
InputTarget::Stdin => {
|
InputTarget::Stdin => {
|
||||||
|
|
|
@ -30,6 +30,8 @@ pub(crate) enum Error {
|
||||||
GlobParse { source: globset::Error },
|
GlobParse { source: globset::Error },
|
||||||
#[snafu(display("Failed to serialize torrent info dictionary: {}", source))]
|
#[snafu(display("Failed to serialize torrent info dictionary: {}", source))]
|
||||||
InfoSerialize { source: bendy::serde::Error },
|
InfoSerialize { source: bendy::serde::Error },
|
||||||
|
#[snafu(display("Input target empty"))]
|
||||||
|
InputTargetEmpty,
|
||||||
#[snafu(display(
|
#[snafu(display(
|
||||||
"Interal error, this may indicate a bug in intermodal: {}\n\
|
"Interal error, this may indicate a bug in intermodal: {}\n\
|
||||||
Consider filing an issue: https://github.com/casey/imdl/issues/new",
|
Consider filing an issue: https://github.com/casey/imdl/issues/new",
|
||||||
|
@ -61,6 +63,8 @@ pub(crate) enum Error {
|
||||||
OpenerExitStatus { exit_status: ExitStatus },
|
OpenerExitStatus { exit_status: ExitStatus },
|
||||||
#[snafu(display("Output path already exists: `{}`", path.display()))]
|
#[snafu(display("Output path already exists: `{}`", path.display()))]
|
||||||
OutputExists { path: PathBuf },
|
OutputExists { path: PathBuf },
|
||||||
|
#[snafu(display("Output target empty"))]
|
||||||
|
OutputTargetEmpty,
|
||||||
#[snafu(display(
|
#[snafu(display(
|
||||||
"Path `{}` contains non-normal component: {}",
|
"Path `{}` contains non-normal component: {}",
|
||||||
path.display(),
|
path.display(),
|
||||||
|
|
|
@ -7,20 +7,32 @@ pub(crate) enum InputTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputTarget {
|
impl InputTarget {
|
||||||
pub(crate) fn resolve(&self, env: &Env) -> Self {
|
pub(crate) fn resolve(&self, env: &Env) -> Result<Self> {
|
||||||
match self {
|
match self {
|
||||||
Self::Path(path) => Self::Path(env.resolve(path)),
|
Self::Path(path) => Ok(Self::Path(env.resolve(path)?)),
|
||||||
Self::Stdin => Self::Stdin,
|
Self::Stdin => Ok(Self::Stdin),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 From<&OsStr> for InputTarget {
|
impl TryFrom<&OsStr> for InputTarget {
|
||||||
fn from(text: &OsStr) -> Self {
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(text: &OsStr) -> Result<Self, Self::Error> {
|
||||||
|
if text.is_empty() {
|
||||||
|
return Err(Error::InputTargetEmpty);
|
||||||
|
};
|
||||||
|
|
||||||
if text == OsStr::new("-") {
|
if text == OsStr::new("-") {
|
||||||
Self::Stdin
|
Ok(Self::Stdin)
|
||||||
} else {
|
} else {
|
||||||
Self::Path(text.into())
|
Ok(Self::Path(text.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,14 +62,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn file() {
|
fn file() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
InputTarget::from(OsStr::new("foo")),
|
InputTarget::try_from(OsStr::new("foo")).unwrap(),
|
||||||
InputTarget::Path("foo".into())
|
InputTarget::Path("foo".into()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdio() {
|
fn stdio() {
|
||||||
assert_eq!(InputTarget::from(OsStr::new("-")), InputTarget::Stdin);
|
assert_eq!(
|
||||||
|
InputTarget::try_from(OsStr::new("-")).unwrap(),
|
||||||
|
InputTarget::Stdin
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,25 +2,37 @@ use crate::common::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub(crate) enum OutputTarget {
|
pub(crate) enum OutputTarget {
|
||||||
File(PathBuf),
|
Path(PathBuf),
|
||||||
Stdout,
|
Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputTarget {
|
impl OutputTarget {
|
||||||
pub(crate) fn resolve(&self, env: &Env) -> Self {
|
pub(crate) fn resolve(&self, env: &Env) -> Result<Self> {
|
||||||
match self {
|
match self {
|
||||||
Self::File(path) => Self::File(env.resolve(path)),
|
Self::Path(path) => Ok(Self::Path(env.resolve(path)?)),
|
||||||
Self::Stdout => Self::Stdout,
|
Self::Stdout => Ok(Self::Stdout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 From<&OsStr> for OutputTarget {
|
impl TryFrom<&OsStr> for OutputTarget {
|
||||||
fn from(text: &OsStr) -> Self {
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(text: &OsStr) -> Result<Self, Self::Error> {
|
||||||
|
if text.is_empty() {
|
||||||
|
return Err(Error::OutputTargetEmpty);
|
||||||
|
};
|
||||||
|
|
||||||
if text == OsStr::new("-") {
|
if text == OsStr::new("-") {
|
||||||
Self::Stdout
|
Ok(Self::Stdout)
|
||||||
} else {
|
} 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 {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Stdout => write!(f, "standard output"),
|
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]
|
#[test]
|
||||||
fn file() {
|
fn file() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OutputTarget::from(OsStr::new("foo")),
|
OutputTarget::try_from(OsStr::new("foo")).unwrap(),
|
||||||
OutputTarget::File("foo".into())
|
OutputTarget::Path("foo".into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdio() {
|
fn stdio() {
|
||||||
assert_eq!(OutputTarget::from(OsStr::new("-")), OutputTarget::Stdout);
|
assert_eq!(
|
||||||
|
OutputTarget::try_from(OsStr::new("-")).unwrap(),
|
||||||
|
OutputTarget::Stdout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_file() {
|
fn display_file() {
|
||||||
let path = PathBuf::from("./path");
|
let path = PathBuf::from("./path");
|
||||||
let have = OutputTarget::File(path).to_string();
|
let have = OutputTarget::Path(path).to_string();
|
||||||
let want = "`./path`";
|
let want = "`./path`";
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,11 +116,12 @@ Examples:
|
||||||
long = "input",
|
long = "input",
|
||||||
short = "i",
|
short = "i",
|
||||||
value_name = "PATH",
|
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 \
|
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` \
|
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 \
|
is `-`, read from standard input. Piece length defaults to 256KiB when reading from \
|
||||||
standard input if `--piece-length` is not given.",
|
standard input if `--piece-length` is not given.",
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
input: InputTarget,
|
input: InputTarget,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -187,11 +188,12 @@ Sort in ascending order by size, break ties in descending path order:
|
||||||
long = "output",
|
long = "output",
|
||||||
short = "o",
|
short = "o",
|
||||||
value_name = "TARGET",
|
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 `-`. \
|
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 \
|
Defaults to the argument to `--input` with an `.torrent` extension appended. Required \
|
||||||
when `--input -`.",
|
when `--input -`.",
|
||||||
required_if("input", "-"),
|
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
output: Option<OutputTarget>,
|
output: Option<OutputTarget>,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -240,8 +242,11 @@ Sort in ascending order by size, break ties in descending path order:
|
||||||
|
|
||||||
impl Create {
|
impl Create {
|
||||||
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
||||||
let input = self.input.resolve(env);
|
let input = self.input.resolve(env)?;
|
||||||
let output = self.output.map(|output| output.resolve(env));
|
let output = match self.output {
|
||||||
|
Some(output) => Some(output.resolve(env)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let mut linter = Linter::new();
|
let mut linter = Linter::new();
|
||||||
linter.allow(self.allowed_lints.iter().cloned());
|
linter.allow(self.allowed_lints.iter().cloned());
|
||||||
|
@ -320,15 +325,12 @@ impl Create {
|
||||||
|
|
||||||
files = Some(files_inner);
|
files = Some(files_inner);
|
||||||
|
|
||||||
output
|
output.unwrap_or_else(|| {
|
||||||
.as_ref()
|
let mut torrent_name = name.to_owned();
|
||||||
.map(|output| output.resolve(env))
|
torrent_name.push_str(".torrent");
|
||||||
.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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
InputTarget::Stdin => {
|
InputTarget::Stdin => {
|
||||||
|
@ -363,7 +365,7 @@ impl Create {
|
||||||
return Err(Error::PieceLengthSmall);
|
return Err(Error::PieceLengthSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let OutputTarget::File(path) = &output {
|
if let OutputTarget::Path(path) = &output {
|
||||||
if !self.force && path.exists() {
|
if !self.force && path.exists() {
|
||||||
return Err(Error::OutputExists {
|
return Err(Error::OutputExists {
|
||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
|
@ -441,7 +443,7 @@ impl Create {
|
||||||
|
|
||||||
if !self.dry_run {
|
if !self.dry_run {
|
||||||
match &output {
|
match &output {
|
||||||
OutputTarget::File(path) => {
|
OutputTarget::Path(path) => {
|
||||||
let mut open_options = fs::OpenOptions::new();
|
let mut open_options = fs::OpenOptions::new();
|
||||||
|
|
||||||
if self.force {
|
if self.force {
|
||||||
|
@ -490,7 +492,7 @@ impl Create {
|
||||||
outln!(env, "{}", link)?;
|
outln!(env, "{}", link)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let OutputTarget::File(path) = output {
|
if let OutputTarget::Path(path) = output {
|
||||||
if self.open {
|
if self.open {
|
||||||
Platform::open_file(&path)?;
|
Platform::open_file(&path)?;
|
||||||
}
|
}
|
||||||
|
@ -546,7 +548,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn torrent_file_is_bencode_dict() {
|
fn torrent_file_is_bencode_dict() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -561,10 +563,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let torrent = env.resolve("foo.torrent");
|
let torrent = env.resolve("foo.torrent")?;
|
||||||
let bytes = fs::read(torrent).unwrap();
|
let bytes = fs::read(torrent).unwrap();
|
||||||
let value = Value::from_bencode(&bytes).unwrap();
|
let value = Value::from_bencode(&bytes).unwrap();
|
||||||
assert!(matches!(value, Value::Dict(_)));
|
assert!(matches!(value, Value::Dict(_)));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1531,7 +1534,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn output() {
|
fn output() -> Result<()> {
|
||||||
let mut env = TestEnvBuilder::new()
|
let mut env = TestEnvBuilder::new()
|
||||||
.arg_slice(&[
|
.arg_slice(&[
|
||||||
"imdl",
|
"imdl",
|
||||||
|
@ -1546,17 +1549,18 @@ mod tests {
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo")?;
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
fs::write(dir.join("a"), "abc").unwrap();
|
fs::write(dir.join("a"), "abc").unwrap();
|
||||||
fs::write(dir.join("x"), "xyz").unwrap();
|
fs::write(dir.join("x"), "xyz").unwrap();
|
||||||
fs::write(dir.join("h"), "hij").unwrap();
|
fs::write(dir.join("h"), "hij").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
assert_eq!(env.out(), "");
|
assert_eq!(env.out(), "");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn show() {
|
fn show() -> Result<()> {
|
||||||
let mut env = TestEnvBuilder::new()
|
let mut env = TestEnvBuilder::new()
|
||||||
.arg_slice(&[
|
.arg_slice(&[
|
||||||
"imdl",
|
"imdl",
|
||||||
|
@ -1572,7 +1576,7 @@ mod tests {
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo")?;
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
fs::write(dir.join("a"), "abc").unwrap();
|
fs::write(dir.join("a"), "abc").unwrap();
|
||||||
fs::write(dir.join("x"), "xyz").unwrap();
|
fs::write(dir.join("x"), "xyz").unwrap();
|
||||||
|
@ -1600,6 +1604,7 @@ Content Size 9 bytes
|
||||||
212 + consts::CREATED_BY_DEFAULT.len()
|
212 + consts::CREATED_BY_DEFAULT.len()
|
||||||
);
|
);
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1625,7 +1630,7 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn force_default() {
|
fn force_default() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1643,8 +1648,9 @@ Content Size 9 bytes
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
env.run().unwrap_err(),
|
env.run().unwrap_err(),
|
||||||
Error::OutputExists {path}
|
Error::OutputExists {path}
|
||||||
if path == env.resolve("foo.torrent")
|
if path == env.resolve("foo.torrent")?
|
||||||
)
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1724,7 +1730,7 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_hidden() {
|
fn skip_hidden() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1745,17 +1751,17 @@ Content Size 9 bytes
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
Command::new("attrib")
|
Command::new("attrib")
|
||||||
.arg("+h")
|
.arg("+h")
|
||||||
.arg(env.resolve("foo/hidden"))
|
.arg(env.resolve("foo/hidden")?)
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
Command::new("chflags")
|
Command::new("chflags")
|
||||||
.arg("hidden")
|
.arg("hidden")
|
||||||
.arg(env.resolve("foo/hidden"))
|
.arg(env.resolve("foo/hidden")?)
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
fs::remove_file(env.resolve("foo/hidden")).unwrap();
|
fs::remove_file(env.resolve("foo/hidden")?).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
|
@ -1767,10 +1773,11 @@ Content Size 9 bytes
|
||||||
Mode::Multiple { files } if files.len() == 0
|
Mode::Multiple { files } if files.len() == 0
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::new());
|
assert_eq!(metainfo.info.pieces, PieceList::new());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn include_hidden() {
|
fn include_hidden() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1792,13 +1799,13 @@ Content Size 9 bytes
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
Command::new("attrib")
|
Command::new("attrib")
|
||||||
.arg("+h")
|
.arg("+h")
|
||||||
.arg(env.resolve("foo/hidden"))
|
.arg(env.resolve("foo/hidden")?)
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
Command::new("chflags")
|
Command::new("chflags")
|
||||||
.arg("hidden")
|
.arg("hidden")
|
||||||
.arg(env.resolve("foo/hidden"))
|
.arg(env.resolve("foo/hidden")?)
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -1810,12 +1817,13 @@ Content Size 9 bytes
|
||||||
Mode::Multiple { files } if files.len() == 2
|
Mode::Multiple { files } if files.len() == 2
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abcabc"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["abcabc"]));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_symlinks(env: &Env) {
|
fn populate_symlinks(env: &Env) -> Result<()> {
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo")?;
|
||||||
let file_src = env.resolve("bar");
|
let file_src = env.resolve("bar")?;
|
||||||
let dir_src = env.resolve("dir-src");
|
let dir_src = env.resolve("dir-src")?;
|
||||||
let dir_contents = dir_src.join("baz");
|
let dir_contents = dir_src.join("baz");
|
||||||
fs::create_dir(&dir_src).unwrap();
|
fs::create_dir(&dir_src).unwrap();
|
||||||
fs::write(dir_contents, "baz").unwrap();
|
fs::write(dir_contents, "baz").unwrap();
|
||||||
|
@ -1824,8 +1832,8 @@ Content Size 9 bytes
|
||||||
fs::write(file_src, "bar").unwrap();
|
fs::write(file_src, "bar").unwrap();
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let file_link = env.resolve("foo/bar");
|
let file_link = env.resolve("foo/bar")?;
|
||||||
let dir_link = env.resolve("foo/dir");
|
let dir_link = env.resolve("foo/dir")?;
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("../bar")
|
.arg("../bar")
|
||||||
|
@ -1840,10 +1848,12 @@ Content Size 9 bytes
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_symlinks() {
|
fn skip_symlinks() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1856,7 +1866,7 @@ Content Size 9 bytes
|
||||||
],
|
],
|
||||||
tree: {},
|
tree: {},
|
||||||
};
|
};
|
||||||
populate_symlinks(&env);
|
populate_symlinks(&env)?;
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
@ -1864,11 +1874,12 @@ Content Size 9 bytes
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::new());
|
assert_eq!(metainfo.info.pieces, PieceList::new());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn follow_symlinks() {
|
fn follow_symlinks() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1882,7 +1893,7 @@ Content Size 9 bytes
|
||||||
],
|
],
|
||||||
tree: {},
|
tree: {},
|
||||||
};
|
};
|
||||||
populate_symlinks(&env);
|
populate_symlinks(&env)?;
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
let mut pieces = PieceList::new();
|
let mut pieces = PieceList::new();
|
||||||
|
@ -1908,11 +1919,12 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
_ => panic!("Expected multi-file torrent"),
|
_ => panic!("Expected multi-file torrent"),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn symlink_root() {
|
fn symlink_root() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1926,8 +1938,8 @@ Content Size 9 bytes
|
||||||
tree: {},
|
tree: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_src = env.resolve("bar");
|
let file_src = env.resolve("bar")?;
|
||||||
let file_link = env.resolve("foo");
|
let file_link = env.resolve("foo")?;
|
||||||
|
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
|
@ -1937,6 +1949,7 @@ Content Size 9 bytes
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_matches!(env.run().unwrap_err(), Error::SymlinkRoot { root } if root == file_link);
|
assert_matches!(env.run().unwrap_err(), Error::SymlinkRoot { root } if root == file_link);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1967,9 +1980,8 @@ Content Size 9 bytes
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::new());
|
assert_eq!(metainfo.info.pieces, PieceList::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_hidden_attribute_dir_contents() {
|
fn skip_hidden_attribute_dir_contents() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -1990,7 +2002,7 @@ Content Size 9 bytes
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
env.write("foo/bar/baz", "baz");
|
env.write("foo/bar/baz", "baz");
|
||||||
let path = env.resolve("foo/bar");
|
let path = env.resolve("foo/bar")?;
|
||||||
Command::new("attrib")
|
Command::new("attrib")
|
||||||
.arg("+h")
|
.arg("+h")
|
||||||
.arg(&path)
|
.arg(&path)
|
||||||
|
@ -2001,7 +2013,7 @@ Content Size 9 bytes
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
env.write("foo/bar/baz", "baz");
|
env.write("foo/bar/baz", "baz");
|
||||||
let path = env.resolve("foo/bar");
|
let path = env.resolve("foo/bar")?;
|
||||||
Command::new("chflags")
|
Command::new("chflags")
|
||||||
.arg("hidden")
|
.arg("hidden")
|
||||||
.arg(&path)
|
.arg(&path)
|
||||||
|
@ -2016,6 +2028,7 @@ Content Size 9 bytes
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::new());
|
assert_eq!(metainfo.info.pieces, PieceList::new());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2255,7 +2268,7 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_progress_messages() {
|
fn create_progress_messages() -> Result<()> {
|
||||||
let mut env = TestEnvBuilder::new()
|
let mut env = TestEnvBuilder::new()
|
||||||
.arg_slice(&[
|
.arg_slice(&[
|
||||||
"imdl",
|
"imdl",
|
||||||
|
@ -2268,17 +2281,18 @@ Content Size 9 bytes
|
||||||
])
|
])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo")?, "").unwrap();
|
||||||
|
|
||||||
let want = format!(
|
let want = format!(
|
||||||
"[1/3] \u{1F9FF} Searching for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \u{1F4BE} \
|
"[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",
|
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();
|
env.run().unwrap();
|
||||||
|
|
||||||
assert_eq!(env.err(), want);
|
assert_eq!(env.err(), want);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2436,7 +2450,7 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dry_run_skips_torrent_file_creation() {
|
fn dry_run_skips_torrent_file_creation() -> Result<()> {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
"torrent",
|
"torrent",
|
||||||
|
@ -2450,9 +2464,10 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
assert_matches!(env.run(), Ok(()));
|
assert_matches!(env.run(), Ok(()));
|
||||||
let torrent = env.resolve("foo.torrent");
|
let torrent = env.resolve("foo.torrent")?;
|
||||||
let err = fs::read(torrent).unwrap_err();
|
let err = fs::read(torrent).unwrap_err();
|
||||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -11,9 +11,10 @@ pub(crate) struct Link {
|
||||||
long = "input",
|
long = "input",
|
||||||
short = "i",
|
short = "i",
|
||||||
value_name = "METAINFO",
|
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 \
|
help = "Generate magnet link from metainfo at `PATH`. If `PATH` is `-`, read metainfo from \
|
||||||
standard input.",
|
standard input.",
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
input: InputTarget,
|
input: InputTarget,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
|
|
@ -11,9 +11,10 @@ pub(crate) struct Show {
|
||||||
long = "input",
|
long = "input",
|
||||||
short = "i",
|
short = "i",
|
||||||
value_name = "PATH",
|
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 \
|
help = "Show information about torrent at `PATH`. If `Path` is `-`, read torrent metainfo \
|
||||||
from standard input.",
|
from standard input.",
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
input: InputTarget,
|
input: InputTarget,
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn output() {
|
fn output() -> Result<()> {
|
||||||
let metainfo = Metainfo {
|
let metainfo = Metainfo {
|
||||||
announce: Some("announce".into()),
|
announce: Some("announce".into()),
|
||||||
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
||||||
|
@ -66,7 +67,7 @@ mod tests {
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ Announce List Tier 1: announce
|
||||||
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -131,10 +132,12 @@ files\tfoo
|
||||||
|
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tier_list_with_main() {
|
fn tier_list_with_main() -> Result<()> {
|
||||||
let metainfo = Metainfo {
|
let metainfo = Metainfo {
|
||||||
announce: Some("a".into()),
|
announce: Some("a".into()),
|
||||||
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
|
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
|
||||||
|
@ -166,7 +169,7 @@ files\tfoo
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -203,7 +206,7 @@ Announce List Tier 1: x
|
||||||
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -231,10 +234,12 @@ files\tfoo
|
||||||
|
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tier_list_without_main() {
|
fn tier_list_without_main() -> Result<()> {
|
||||||
let metainfo = Metainfo {
|
let metainfo = Metainfo {
|
||||||
announce: Some("a".into()),
|
announce: Some("a".into()),
|
||||||
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".into()]]),
|
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".into()]]),
|
||||||
|
@ -266,7 +271,7 @@ files\tfoo
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -303,7 +308,7 @@ Announce List Tier 1: b
|
||||||
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -331,10 +336,12 @@ files\tfoo
|
||||||
|
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn trackerless() {
|
fn trackerless() -> Result<()> {
|
||||||
let metainfo = Metainfo {
|
let metainfo = Metainfo {
|
||||||
announce: None,
|
announce: None,
|
||||||
announce_list: None,
|
announce_list: None,
|
||||||
|
@ -366,7 +373,7 @@ files\tfoo
|
||||||
.out_is_term()
|
.out_is_term()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
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"])
|
.arg_slice(&["imdl", "torrent", "show", "--input", "foo.torrent"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let path = env.resolve("foo.torrent");
|
let path = env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
metainfo.dump(path).unwrap();
|
metainfo.dump(path).unwrap();
|
||||||
|
|
||||||
|
@ -425,5 +432,7 @@ files\tfoo
|
||||||
|
|
||||||
assert_eq!(have, want);
|
assert_eq!(have, want);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub(crate) struct Stats {
|
||||||
long = "extract-pattern",
|
long = "extract-pattern",
|
||||||
short = "e",
|
short = "e",
|
||||||
value_name = "REGEX",
|
value_name = "REGEX",
|
||||||
|
empty_values(false),
|
||||||
help = "Extract and display values under key paths that match `REGEX`. Subkeys of a \
|
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 \
|
bencodeded dictionary are delimited by `/`, and values of a bencoded list are \
|
||||||
delmited by `*`. For example, given the following bencoded dictionary `{\"foo\": \
|
delmited by `*`. For example, given the following bencoded dictionary `{\"foo\": \
|
||||||
|
@ -31,8 +32,9 @@ pub(crate) struct Stats {
|
||||||
long = "input",
|
long = "input",
|
||||||
short = "i",
|
short = "i",
|
||||||
value_name = "PATH",
|
value_name = "PATH",
|
||||||
help = "Search `PATH` for torrents. May be a directory or a single torrent file.",
|
empty_values(false),
|
||||||
parse(from_os_str)
|
parse(from_os_str),
|
||||||
|
help = "Search `PATH` for torrents. May be a directory or a single torrent file."
|
||||||
)]
|
)]
|
||||||
input: PathBuf,
|
input: PathBuf,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -47,7 +49,7 @@ impl Stats {
|
||||||
pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> {
|
pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> {
|
||||||
options.require_unstable("torrent stats subcommand")?;
|
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);
|
let mut extractor = Extractor::new(self.print, &self.extract_patterns);
|
||||||
|
|
||||||
|
|
|
@ -14,18 +14,20 @@ pub(crate) struct Verify {
|
||||||
long = "input",
|
long = "input",
|
||||||
short = "i",
|
short = "i",
|
||||||
value_name = "METAINFO",
|
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 `-`, \
|
help = "Verify torrent contents against torrent metainfo in `METAINFO`. If `METAINFO` is `-`, \
|
||||||
read metainfo from standard input.",
|
read metainfo from standard input.",
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
metainfo: InputTarget,
|
metainfo: InputTarget,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long = "content",
|
long = "content",
|
||||||
short = "c",
|
short = "c",
|
||||||
value_name = "PATH",
|
value_name = "PATH",
|
||||||
|
empty_values(false),
|
||||||
|
parse(from_os_str),
|
||||||
help = "Verify torrent content at `PATH` against torrent metainfo. Defaults to `name` field \
|
help = "Verify torrent content at `PATH` against torrent metainfo. Defaults to `name` field \
|
||||||
of torrent info dictionary.",
|
of torrent info dictionary."
|
||||||
parse(from_os_str)
|
|
||||||
)]
|
)]
|
||||||
content: Option<PathBuf>,
|
content: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -66,7 +68,7 @@ impl Verify {
|
||||||
|
|
||||||
VerifyStep::Verifying { content: &content }.print(env)?;
|
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)?;
|
status.print(env)?;
|
||||||
|
|
||||||
|
@ -119,7 +121,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
create_env.run()?;
|
||||||
|
|
||||||
let torrent = create_env.resolve("foo.torrent");
|
let torrent = create_env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
let mut verify_env = test_env! {
|
let mut verify_env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
|
@ -137,7 +139,7 @@ mod tests {
|
||||||
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from \
|
"[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",
|
`{}`…\n\u{2728}\u{2728} Verification succeeded! \u{2728}\u{2728}\n",
|
||||||
torrent.display(),
|
torrent.display(),
|
||||||
create_env.resolve("foo").display()
|
create_env.resolve("foo")?.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(verify_env.err(), want);
|
assert_eq!(verify_env.err(), want);
|
||||||
|
@ -170,7 +172,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.write("foo/a", "xyz");
|
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! {
|
let mut verify_env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
|
@ -191,7 +193,7 @@ mod tests {
|
||||||
),
|
),
|
||||||
&format!(
|
&format!(
|
||||||
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
||||||
create_env.resolve("foo").display()
|
create_env.resolve("foo")?.display()
|
||||||
),
|
),
|
||||||
"Pieces corrupted.",
|
"Pieces corrupted.",
|
||||||
"error: Torrent verification failed.",
|
"error: Torrent verification failed.",
|
||||||
|
@ -227,11 +229,11 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
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();
|
fs::rename(&foo, &bar).unwrap();
|
||||||
|
|
||||||
|
@ -284,8 +286,8 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
create_env.run()?;
|
||||||
|
|
||||||
let torrent = create_env.resolve("foo.torrent");
|
let torrent = create_env.resolve("foo.torrent")?;
|
||||||
let content = create_env.resolve("foo");
|
let content = create_env.resolve("foo")?;
|
||||||
|
|
||||||
let mut verify_env = test_env! {
|
let mut verify_env = test_env! {
|
||||||
args: [
|
args: [
|
||||||
|
@ -340,7 +342,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
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/a", "xyz");
|
||||||
create_env.write("foo/d", "efgg");
|
create_env.write("foo/d", "efgg");
|
||||||
|
@ -377,7 +379,7 @@ mod tests {
|
||||||
),
|
),
|
||||||
&format!(
|
&format!(
|
||||||
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
||||||
create_env.resolve("foo").display()
|
create_env.resolve("foo")?.display()
|
||||||
),
|
),
|
||||||
"a: MD5 checksum mismatch: d16fb36f0911f878998c136191af705e (expected \
|
"a: MD5 checksum mismatch: d16fb36f0911f878998c136191af705e (expected \
|
||||||
900150983cd24fb0d6963f7d28e17f72)",
|
900150983cd24fb0d6963f7d28e17f72)",
|
||||||
|
@ -425,7 +427,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
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/a", "xyz");
|
||||||
create_env.write("foo/d", "efgg");
|
create_env.write("foo/d", "efgg");
|
||||||
|
@ -482,7 +484,7 @@ mod tests {
|
||||||
style.dim().paint("[2/2]"),
|
style.dim().paint("[2/2]"),
|
||||||
style.message().paint(format!(
|
style.message().paint(format!(
|
||||||
"Verifying pieces from `{}`…",
|
"Verifying pieces from `{}`…",
|
||||||
create_env.resolve("foo").display()
|
create_env.resolve("foo")?.display()
|
||||||
))
|
))
|
||||||
),
|
),
|
||||||
&format!(
|
&format!(
|
||||||
|
@ -531,7 +533,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
create_env.run()?;
|
||||||
|
|
||||||
let torrent = create_env.resolve("foo.torrent");
|
let torrent = create_env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
create_env.write("foo", "abcxyz");
|
create_env.write("foo", "abcxyz");
|
||||||
|
|
||||||
|
@ -554,7 +556,7 @@ mod tests {
|
||||||
),
|
),
|
||||||
&format!(
|
&format!(
|
||||||
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
"[2/2] \u{1F9EE} Verifying pieces from `{}`…",
|
||||||
create_env.resolve("foo").display()
|
create_env.resolve("foo")?.display()
|
||||||
),
|
),
|
||||||
"3 bytes too long",
|
"3 bytes too long",
|
||||||
"Pieces corrupted.",
|
"Pieces corrupted.",
|
||||||
|
@ -591,7 +593,7 @@ mod tests {
|
||||||
|
|
||||||
create_env.run()?;
|
create_env.run()?;
|
||||||
|
|
||||||
let torrent = create_env.resolve("foo.torrent");
|
let torrent = create_env.resolve("foo.torrent")?;
|
||||||
|
|
||||||
let metainfo = fs::read(torrent).unwrap();
|
let metainfo = fs::read(torrent).unwrap();
|
||||||
|
|
||||||
|
@ -606,7 +608,7 @@ mod tests {
|
||||||
tree: {},
|
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(()));
|
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]>) {
|
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>) {
|
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>) {
|
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)]
|
#[cfg(unix)]
|
||||||
pub(crate) fn metadata(&self, path: impl AsRef<Path>) -> fs::Metadata {
|
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)]
|
#[cfg(unix)]
|
||||||
pub(crate) fn set_permissions(&self, path: impl AsRef<Path>, permissions: fs::Permissions) {
|
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) {
|
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 {
|
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()
|
Metainfo::from_input(&input).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ mod tests {
|
||||||
|
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
|
|
||||||
assert!(metainfo.verify(&env.resolve("foo"), None)?.good());
|
assert!(metainfo.verify(&env.resolve("foo")?, None)?.good());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ mod tests {
|
||||||
|
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
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);
|
assert_eq!(status.count_bad(), 0);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user