intermodal/src/infohash.rs
Casey Rodarmor 498549b35c
Allow reading torrent metainfo from stdin
Torrent metainfo can be read from standard input by passing `-`:

    cat a.torrent | imdl torrent verify --input -
    cat a.torrent | imdl torrent link --input -
    cat a.torrent | imdl torrent show --input -

type: added
2020-04-07 19:01:29 -07:00

135 lines
2.9 KiB
Rust

use crate::*;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub(crate) struct Infohash {
inner: Sha1Digest,
}
impl Infohash {
pub(crate) fn from_input(input: &Input) -> Result<Infohash, Error> {
let value = Value::from_bencode(input.data()).map_err(|error| Error::MetainfoDecode {
input: input.source().clone(),
error,
})?;
match value {
Value::Dict(metainfo) => {
let info = metainfo
.iter()
.find(|pair: &(&Cow<[u8]>, &Value)| pair.0.as_ref() == b"info")
.ok_or_else(|| Error::MetainfoValidate {
input: input.source().clone(),
source: MetainfoError::InfoMissing,
})?
.1;
if let Value::Dict(_) = info {
let encoded = info.to_bencode().map_err(|error| {
Error::internal(format!("Failed to re-encode info dictionary: {}", error))
})?;
Ok(Self::from_bencoded_info_dict(&encoded))
} else {
Err(Error::MetainfoValidate {
input: input.source().clone(),
source: MetainfoError::InfoType,
})
}
}
_ => Err(Error::MetainfoValidate {
input: input.source().clone(),
source: MetainfoError::Type,
}),
}
}
pub(crate) fn from_bencoded_info_dict(info: &[u8]) -> Infohash {
Infohash {
inner: Sha1Digest::from_data(info),
}
}
#[cfg(test)]
pub(crate) fn load(path: &Path) -> Result<Infohash, Error> {
let input = Input::from_path(path)?;
Self::from_input(&input)
}
}
impl Into<Sha1Digest> for Infohash {
fn into(self) -> Sha1Digest {
self.inner
}
}
impl Display for Infohash {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_invalid() {
let tempdir = temptree! {
foo: "x",
};
let path = tempdir.path().join("foo");
assert_matches!(
Infohash::load(&path),
Err(Error::MetainfoDecode{input, .. })
if input == path
);
}
#[test]
fn load_wrong_type() {
let tempdir = temptree! {
foo: "i0e",
};
let path = tempdir.path().join("foo");
assert_matches!(
Infohash::load(&path),
Err(Error::MetainfoValidate{input, source: MetainfoError::Type})
if input == path
);
}
#[test]
fn load_no_info() {
let tempdir = temptree! {
foo: "de",
};
let path = tempdir.path().join("foo");
assert_matches!(
Infohash::load(&path),
Err(Error::MetainfoValidate{input, source: MetainfoError::InfoMissing})
if input == path
);
}
#[test]
fn load_info_type() {
let tempdir = temptree! {
foo: "d4:infoi0ee",
};
let path = tempdir.path().join("foo");
assert_matches!(
Infohash::load(&path),
Err(Error::MetainfoValidate{input, source: MetainfoError::InfoType})
if input == path
);
}
}