Drop serde_bencode in favor of bendy

For now depend on my branch on Github, until serde support and the value
type land in the main repo.

type: reform
This commit is contained in:
Casey Rodarmor 2020-02-11 03:08:57 -08:00
parent 9b696f78a7
commit b0f449b6ae
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
14 changed files with 362 additions and 515 deletions

106
Cargo.lock generated
View File

@ -44,6 +44,38 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "backtrace"
version = "0.3.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f80256bc78f67e7df7e36d77366f636ed976895d91fe2ab9efa3973e8fe8c4f"
dependencies = [
"backtrace-sys",
"cfg-if",
"libc",
"rustc-demangle",
]
[[package]]
name = "backtrace-sys"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "bendy"
version = "0.2.2"
source = "git+https://github.com/casey/bendy.git?branch=value#8009217c7c753e0c3e1b6685bd9e9f980e5d38de"
dependencies = [
"failure",
"serde",
"serde_bytes 0.11.3",
]
[[package]]
name = "bitflags"
version = "1.2.1"
@ -68,6 +100,12 @@ dependencies = [
"ppv-lite86",
]
[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -136,6 +174,28 @@ dependencies = [
"termcolor",
]
[[package]]
name = "failure"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "fnv"
version = "1.0.6"
@ -216,6 +276,7 @@ version = "0.0.1"
dependencies = [
"ansi_term 0.12.1",
"atty",
"bendy",
"chrono",
"env_logger",
"globset",
@ -226,10 +287,12 @@ dependencies = [
"serde",
"serde_bencode",
"serde_bytes 0.11.3",
"serde_with",
"sha1",
"snafu",
"static_assertions",
"structopt",
"syn",
"tempfile",
"unicode-width",
"url",
@ -461,6 +524,12 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
[[package]]
name = "rustversion"
version = "1.0.2"
@ -492,9 +561,9 @@ dependencies = [
[[package]]
name = "serde_bencode"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b79ce11369638af2fea08e00390316304c5b5e1b7a53d58ace925152b657443"
checksum = "315c49c11b6b10acc209df75b757ee70957b911ecd0e29bcbf2b735ebd580d45"
dependencies = [
"serde",
"serde_bytes 0.10.5",
@ -529,6 +598,27 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_with"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d3d595d64120bbbc70b7f6d5ae63298b62a3d9f373ec2f56acf5365ca8a444"
dependencies = [
"serde",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4070d2c9b9d258465ad1d82aabb985b84cd9a3afa94da25ece5a9938ba5f1606"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha1"
version = "0.6.0"
@ -620,6 +710,18 @@ dependencies = [
"syn",
]
[[package]]
name = "synstructure"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.1.0"

View File

@ -24,14 +24,22 @@ pretty_assertions = "0.6"
regex = "1"
serde_bencode = "0.2"
serde_bytes = "0.11"
serde_with = "1.4"
sha1 = "0.6"
snafu = "0.6"
static_assertions = "1"
syn = "1.0.14"
tempfile = "3"
unicode-width = "0.1"
url = "2"
walkdir = "2.1"
[dependencies.bendy]
version = "0.2.2"
git = "https://github.com/casey/bendy.git"
branch = "value"
features = ["serde"]
[dependencies.serde]
version = "1.0.103"
features = ["derive"]

View File

@ -1,378 +0,0 @@
use crate::common::*;
use self::Error::*;
#[derive(Clone)]
pub(crate) enum Value<'buffer> {
Int(&'buffer str),
List(Vec<Value<'buffer>>),
Dict(Vec<(&'buffer [u8], Value<'buffer>)>),
Str(&'buffer [u8]),
}
impl<'buffer> Value<'buffer> {
pub(crate) fn decode(buffer: &'buffer [u8]) -> Result<Value<'buffer>, Error> {
Parser::parse(buffer)
}
pub(crate) fn encode(&self) -> Vec<u8> {
let mut buffer = Vec::new();
self.encode_into(&mut buffer);
buffer
}
pub(crate) fn encode_into(&self, buffer: &mut Vec<u8>) {
match self {
Self::Int(value) => {
buffer.push(b'i');
buffer.extend_from_slice(value.as_bytes());
buffer.push(b'e');
}
Self::List(values) => {
buffer.push(b'l');
for value in values {
value.encode_into(buffer);
}
buffer.push(b'e');
}
Self::Dict(items) => {
buffer.push(b'd');
for (key, value) in items {
Self::encode_str(buffer, key);
value.encode_into(buffer);
}
buffer.push(b'e');
}
Self::Str(contents) => Self::encode_str(buffer, contents),
}
}
fn encode_str(buffer: &mut Vec<u8>, contents: &[u8]) {
buffer.extend_from_slice(contents.len().to_string().as_bytes());
buffer.push(b':');
buffer.extend_from_slice(contents);
}
fn fmt_str(f: &mut Formatter, contents: &[u8]) -> fmt::Result {
if let Ok(text) = str::from_utf8(contents) {
write!(f, "\"{}\"", text)
} else {
write!(f, "<")?;
for byte in contents {
write!(f, "{:X}", byte)?;
}
write!(f, ">")
}
}
}
impl<'buffer> Display for Value<'buffer> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Int(value) => write!(f, "{}", value),
Self::List(values) => {
write!(f, "[")?;
for (i, value) in values.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", value)?;
}
write!(f, "]")
}
Self::Dict(items) => {
write!(f, "{{")?;
for (i, (key, value)) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
Value::fmt_str(f, key)?;
write!(f, ": {}", value)?;
}
write!(f, "}}")
}
Self::Str(contents) => Value::fmt_str(f, contents),
}
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum Error {
TrailingData { start: usize },
UnexpectedEndOfBuffer,
UnexpectedByte { found: u8 },
UnsortedKey,
DuplicateKey,
EmptyInteger,
NegativeZero,
LeadingZero,
}
pub(crate) struct Parser<'buffer> {
index: usize,
buffer: &'buffer [u8],
}
impl<'buffer> Parser<'buffer> {
pub(crate) fn parse(buffer: &'buffer [u8]) -> Result<Value<'buffer>, Error> {
let parser = Parser { index: 0, buffer };
Ok(parser.root()?)
}
fn root(mut self) -> Result<Value<'buffer>, Error> {
let root = self.value()?;
if self.index != self.buffer.len() {
return Err(TrailingData { start: self.index });
}
Ok(root)
}
fn value(&mut self) -> Result<Value<'buffer>, Error> {
match self.next()? {
b'i' => self.int(),
b'l' => self.list(),
b'd' => self.dict(),
b'0'..=b'9' => self.string(),
found => Err(UnexpectedByte { found }),
}
}
fn extract_digits(&mut self) -> Result<&'buffer [u8], Error> {
let start = self.index;
while let b'0'..=b'9' = self.next()? {
self.advance()?;
}
Ok(&self.buffer[start..self.index])
}
fn parse_digits(digits: &[u8]) -> Result<u64, Error> {
if digits.is_empty() {
return Err(EmptyInteger);
}
if digits == b"0" {
return Ok(0);
}
let mut i = 0;
for digit in digits {
let value: u64 = (digit - b'0').into();
if value == 0 && i == 0 {
return Err(LeadingZero);
}
i = i * 10 + value;
}
Ok(i)
}
fn int(&mut self) -> Result<Value<'buffer>, Error> {
self.expect(b'i')?;
let start = self.index;
let negative = self.accept(b'-')?;
let digits = self.extract_digits()?;
let end = self.index;
self.expect(b'e')?;
let value = Self::parse_digits(digits)?;
if value == 0 && negative {
return Err(NegativeZero);
}
Ok(Value::Int(
str::from_utf8(&self.buffer[start..end]).unwrap(),
))
}
fn list(&mut self) -> Result<Value<'buffer>, Error> {
self.expect(b'l')?;
let mut values = Vec::new();
while self.next()? != b'e' {
values.push(self.value()?);
}
self.expect(b'e')?;
Ok(Value::List(values))
}
fn dict(&mut self) -> Result<Value<'buffer>, Error> {
self.expect(b'd')?;
let mut values: Vec<(&[u8], Value)> = Vec::new();
while self.next()? != b'e' {
let key = self.key()?;
if let Some((last_key, _)) = values.last() {
match key.cmp(last_key) {
Ordering::Equal => return Err(DuplicateKey),
Ordering::Less => return Err(UnsortedKey),
Ordering::Greater => {}
}
}
let value = self.value()?;
values.push((key, value));
}
self.expect(b'e')?;
Ok(Value::Dict(values))
}
fn string(&mut self) -> Result<Value<'buffer>, Error> {
Ok(Value::Str(self.key()?))
}
fn key(&mut self) -> Result<&'buffer [u8], Error> {
let digits = self.extract_digits()?;
self.expect(b':')?;
let len = Self::parse_digits(digits)?;
let start = self.index;
for _ in 0..len {
self.advance()?;
}
Ok(&self.buffer[start..self.index])
}
fn next(&self) -> Result<u8, Error> {
self
.buffer
.get(self.index)
.cloned()
.ok_or(UnexpectedEndOfBuffer)
}
fn advance(&mut self) -> Result<(), Error> {
if self.index == self.buffer.len() {
Err(UnexpectedEndOfBuffer)
} else {
self.index += 1;
Ok(())
}
}
fn expect(&mut self, expected: u8) -> Result<(), Error> {
let found = self.next()?;
if found != expected {
return Err(UnexpectedByte { found });
}
self.advance()?;
Ok(())
}
fn accept(&mut self, acceptable: u8) -> Result<bool, Error> {
if self.next()? == acceptable {
self.advance()?;
Ok(true)
} else {
Ok(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn err(input: impl AsRef<[u8]>, expected: Error) {
let buffer = input.as_ref();
let text = String::from_utf8_lossy(buffer);
match Parser::parse(buffer) {
Ok(_) => panic!(
"Input `{}` passed validation, expected: {:?}",
text, expected,
),
Err(error) => assert_eq!(error, expected, "Unexpected error for input `{}`", text),
}
}
fn ok(input: impl AsRef<[u8]>) {
let buffer = input.as_ref();
match Value::decode(buffer) {
Err(_) => {
panic!(
"Input failed to validate: `{}`",
String::from_utf8_lossy(buffer)
);
}
Ok(value) => {
let round_trip = value.encode();
assert_eq!(round_trip, buffer);
}
}
}
#[test]
fn misc() {
err("", UnexpectedEndOfBuffer);
err("i20efoo", TrailingData { start: 4 });
err("defoo", TrailingData { start: 2 });
err("lefoo", TrailingData { start: 2 });
err("1:afoo", TrailingData { start: 3 });
}
#[test]
fn int() {
err("ie", EmptyInteger);
err("i-0e", NegativeZero);
err("i00e", LeadingZero);
err("iae", UnexpectedByte { found: b'a' });
ok("i0e");
ok("i-100e");
}
#[test]
fn list() {
ok("le");
ok("llelelee");
ok("li20ee");
ok("li20edelee");
}
#[test]
fn dict() {
ok("de");
ok("d0:0:e");
err("di0elee", UnexpectedByte { found: b'i' });
err("d0:i0ei0ei0ee", UnexpectedByte { found: b'i' });
err("d0:e", UnexpectedByte { found: b'e' });
err("d0:de0:dee", DuplicateKey);
err("d1:ade0:dee", UnsortedKey);
err("d1:ade0:dee", UnsortedKey);
ok("d1:ade1:bde1:cdee");
}
#[test]
fn string() {
ok("0:");
ok("5:hello");
err("1:", UnexpectedEndOfBuffer);
err("2:a", UnexpectedEndOfBuffer);
}
}

View File

@ -1,7 +1,7 @@
// stdlib types
pub(crate) use std::{
borrow::Cow,
cmp::{Ordering, Reverse},
cmp::Reverse,
collections::{BTreeMap, BTreeSet, HashMap},
convert::{Infallible, TryInto},
env,
@ -20,11 +20,13 @@ pub(crate) use std::{
};
// dependencies
pub(crate) use bendy::{decoding::FromBencode, encoding::ToBencode, value::Value};
pub(crate) use chrono::{TimeZone, Utc};
pub(crate) use globset::{Glob, GlobMatcher};
pub(crate) use libc::EXIT_FAILURE;
pub(crate) use regex::{Regex, RegexSet};
pub(crate) use serde::{Deserialize, Serialize};
pub(crate) use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub(crate) use serde_with::skip_serializing_none;
pub(crate) use sha1::Sha1;
pub(crate) use snafu::{ResultExt, Snafu};
pub(crate) use static_assertions::const_assert;
@ -37,7 +39,7 @@ pub(crate) use url::Url;
pub(crate) use walkdir::WalkDir;
// modules
pub(crate) use crate::{bencode, consts, error, use_color};
pub(crate) use crate::{consts, error, inner, use_color};
// traits
pub(crate) use crate::{
@ -70,3 +72,6 @@ pub(crate) use crate::testing;
// test structs and enums
#[cfg(test)]
pub(crate) use crate::{capture::Capture, test_env::TestEnv, test_env_builder::TestEnvBuilder};
// type aliases
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;

View File

@ -11,11 +11,11 @@ pub(crate) enum Error {
AnnounceUrlParse { source: url::ParseError },
#[snafu(display("Failed to deserialize torrent metainfo from `{}`: {}", path.display(), source))]
MetainfoLoad {
source: serde_bencode::Error,
source: bendy::serde::Error,
path: PathBuf,
},
#[snafu(display("Failed to serialize torrent metainfo: {}", source))]
MetainfoSerialize { source: serde_bencode::Error },
MetainfoSerialize { source: bendy::serde::Error },
#[snafu(display("Failed to parse byte count `{}`: {}", text, source))]
ByteParse {
text: String,

View File

@ -1,8 +1,10 @@
use crate::common::*;
#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub(crate) struct FileInfo {
pub(crate) length: u64,
pub(crate) md5sum: Option<String>,
pub(crate) path: FilePath,
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) md5sum: Option<String>,
}

View File

@ -1,11 +1,14 @@
use crate::common::*;
#[derive(Deserialize, Serialize)]
#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub(crate) struct Info {
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) private: Option<u8>,
#[serde(rename = "piece length")]
pub(crate) piece_length: u32,
pub(crate) name: String,
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) source: Option<String>,
#[serde(with = "serde_bytes")]
pub(crate) pieces: Vec<u8>,

52
src/inner.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::common::*;
pub(crate) fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
value.as_ref().unwrap().serialize(serializer)
}
pub(crate) fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Ok(Some(T::deserialize(deserializer)?))
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::fmt::Debug;
fn case<T>(value: T, expected: impl AsRef<[u8]>)
where
T: Serialize + DeserializeOwned + PartialEq + Debug,
{
let serialized = bendy::serde::ser::to_bytes(&value).unwrap();
assert_eq!(serialized, expected.as_ref());
let deserialized = bendy::serde::de::from_bytes(&serialized).unwrap();
assert_eq!(value, deserialized);
}
#[test]
fn serialize() {
#[derive(Deserialize, Serialize, Debug, PartialEq)]
struct Foo {
#[serde(skip_serializing_if = "Option::is_none", default, with = "super")]
pub(crate) bar: Option<u8>,
}
let none = Foo { bar: None };
case(none, b"de");
let some = Foo { bar: Some(1) };
case(some, b"d3:bari1ee");
}
}

View File

@ -54,7 +54,6 @@ mod test_env_builder;
#[cfg(test)]
mod capture;
mod bencode;
mod bytes;
mod common;
mod consts;
@ -65,6 +64,7 @@ mod file_path;
mod files;
mod hasher;
mod info;
mod inner;
mod into_u64;
mod into_usize;
mod lint;

View File

@ -1,21 +1,28 @@
use crate::common::*;
#[derive(Deserialize, Serialize)]
#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub(crate) struct Metainfo {
pub(crate) announce: String,
#[serde(rename = "announce-list")]
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) announce_list: Option<Vec<Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) comment: Option<String>,
#[serde(rename = "created by")]
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) created_by: Option<String>,
#[serde(rename = "creation date")]
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) creation_date: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
pub(crate) encoding: Option<String>,
pub(crate) info: Info,
}
impl Metainfo {
pub(crate) fn _load(path: impl AsRef<Path>) -> Result<Metainfo, Error> {
#[cfg(test)]
pub(crate) fn load(path: impl AsRef<Path>) -> Result<Metainfo, Error> {
let path = path.as_ref();
let bytes = fs::read(path).context(error::Filesystem { path })?;
Self::deserialize(path, &bytes)
@ -24,17 +31,45 @@ impl Metainfo {
#[cfg(test)]
pub(crate) fn dump(&self, path: impl AsRef<Path>) -> Result<(), Error> {
let path = path.as_ref();
let bytes = serde_bencode::ser::to_bytes(&self).context(error::MetainfoSerialize)?;
fs::write(path, &bytes).context(error::Filesystem { path })?;
let bendy = bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)?;
let serde_bencode = serde_bencode::ser::to_bytes(&self).unwrap();
if bendy != serde_bencode {
panic!(
"Serialize bendy != serde_bencode:\n{}\n{}",
String::from_utf8_lossy(&bendy),
String::from_utf8_lossy(&serde_bencode)
);
}
fs::write(path, &bendy).context(error::Filesystem { path })?;
Ok(())
}
pub(crate) fn deserialize(path: impl AsRef<Path>, bytes: &[u8]) -> Result<Metainfo, Error> {
let path = path.as_ref();
serde_bencode::de::from_bytes(&bytes).context(error::MetainfoLoad { path })
let bendy = bendy::serde::de::from_bytes(&bytes).context(error::MetainfoLoad { path })?;
let serde_bencode = serde_bencode::de::from_bytes(&bytes).unwrap();
assert_eq!(bendy, serde_bencode);
Ok(bendy)
}
pub(crate) fn serialize(&self) -> Result<Vec<u8>, Error> {
serde_bencode::ser::to_bytes(&self).context(error::MetainfoSerialize)
let bendy = bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)?;
let serde_bencode = serde_bencode::ser::to_bytes(&self).unwrap();
if bendy != serde_bencode {
panic!(
"Serialize bendy != serde_bencode:\n{}\n{}",
String::from_utf8_lossy(&bendy),
String::from_utf8_lossy(&serde_bencode)
);
}
Ok(bendy)
}
#[cfg(test)]
pub(crate) fn from_bytes(bytes: &[u8]) -> Metainfo {
let bendy = bendy::serde::de::from_bytes(bytes).unwrap();
let serde_bencode = serde_bencode::de::from_bytes(bytes).unwrap();
assert_eq!(bendy, serde_bencode);
bendy
}
}

View File

@ -1,10 +1,17 @@
use crate::common::*;
#[derive(Deserialize, Serialize, Debug, PartialEq)]
#[skip_serializing_none]
#[serde(untagged)]
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub(crate) enum Mode {
Single { length: u64, md5sum: Option<String> },
Multiple { files: Vec<FileInfo> },
Single {
length: u64,
#[serde(skip_serializing_if = "Option::is_none", default, with = "inner")]
md5sum: Option<String>,
},
Multiple {
files: Vec<FileInfo>,
},
}
impl Mode {

View File

@ -321,8 +321,8 @@ mod tests {
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let value = bencode::Value::decode(&bytes).unwrap();
assert!(matches!(value, bencode::Value::Dict(_)));
let value = Value::from_bencode(&bytes).unwrap();
assert!(matches!(value, Value::Dict(_)));
}
#[test]
@ -331,8 +331,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.private, None);
}
@ -342,8 +341,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.private, Some(1));
}
@ -360,8 +358,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.announce, "http://bar/");
assert!(metainfo.announce_list.is_none());
}
@ -377,8 +374,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(
metainfo.announce,
"udp://tracker.opentrackr.org:1337/announce"
@ -392,8 +388,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.announce, "wss://tracker.btorrent.xyz/");
assert!(metainfo.announce_list.is_none());
}
@ -411,8 +406,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.announce, "http://bar/");
assert_eq!(
metainfo.announce_list,
@ -435,8 +429,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.announce, "http://bar/");
assert_eq!(
metainfo.announce_list,
@ -453,8 +446,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.comment, None);
}
@ -471,8 +463,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.comment.unwrap(), "Hello, world!");
}
@ -482,8 +473,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.piece_length, 16 * 2u32.pow(10));
}
@ -500,8 +490,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.piece_length, 64 * 1024);
}
@ -518,8 +507,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.piece_length, 512 * 1024);
}
@ -536,8 +524,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.name, "foo");
}
@ -556,8 +543,7 @@ mod tests {
fs::write(dir.join("bar"), "").unwrap();
env.run().unwrap();
let torrent = dir.join("bar.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.name, "bar");
}
@ -574,8 +560,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("x.torrent");
let bytes = fs::read(torrent).unwrap();
serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
Metainfo::load(torrent).unwrap();
}
#[test]
@ -584,8 +569,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.created_by.unwrap(), consts::CREATED_BY_DEFAULT);
}
@ -601,8 +585,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.created_by, None);
}
@ -612,8 +595,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.encoding, Some("UTF-8".into()));
}
@ -627,8 +609,7 @@ mod tests {
.as_secs();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert!(metainfo.creation_date.unwrap() < now + 10);
assert!(metainfo.creation_date.unwrap() > now - 10);
}
@ -645,8 +626,7 @@ mod tests {
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.creation_date, None);
}
@ -657,8 +637,7 @@ mod tests {
fs::write(env.resolve("foo"), contents).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
assert_eq!(
metainfo.info.mode,
@ -685,8 +664,7 @@ mod tests {
fs::write(env.resolve("foo"), contents).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
let pieces = Sha1::from("b")
.digest()
.bytes()
@ -713,8 +691,7 @@ mod tests {
fs::write(env.resolve("foo"), contents).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces.len(), 0);
assert_eq!(
metainfo.info.mode,
@ -732,8 +709,7 @@ mod tests {
fs::create_dir(&dir).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces.len(), 0);
assert_eq!(metainfo.info.mode, Mode::Multiple { files: Vec::new() })
}
@ -748,8 +724,7 @@ mod tests {
fs::write(file, contents).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
match metainfo.info.mode {
Mode::Multiple { files } => {
@ -776,8 +751,7 @@ mod tests {
fs::write(file, contents).unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
match metainfo.info.mode {
Mode::Multiple { files } => {
@ -804,8 +778,7 @@ mod tests {
fs::write(dir.join("h"), "hij").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(
metainfo.info.pieces,
Sha1::from("abchijxyz").digest().bytes()
@ -1028,8 +1001,7 @@ Content Size 9 bytes
fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();
let bytes = env.out_bytes();
let value = bencode::Value::decode(&bytes).unwrap();
assert!(matches!(value, bencode::Value::Dict(_)));
Metainfo::from_bytes(&bytes);
}
#[test]
@ -1052,8 +1024,8 @@ Content Size 9 bytes
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let value = bencode::Value::decode(&bytes).unwrap();
assert!(matches!(value, bencode::Value::Dict(_)));
let value = Value::from_bencode(&bytes).unwrap();
assert!(matches!(value, Value::Dict(_)));
}
#[test]
@ -1065,8 +1037,7 @@ Content Size 9 bytes
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.is_empty()
@ -1089,8 +1060,7 @@ Content Size 9 bytes
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 2
@ -1126,8 +1096,7 @@ Content Size 9 bytes
}
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 0
@ -1149,8 +1118,7 @@ Content Size 9 bytes
fs::write(dir.join(".hidden"), "abc").unwrap();
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 1
@ -1194,8 +1162,7 @@ Content Size 9 bytes
populate_symlinks(&env);
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.is_empty()
@ -1217,8 +1184,7 @@ Content Size 9 bytes
populate_symlinks(&env);
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_eq!(metainfo.info.pieces, Sha1::from("barbaz").digest().bytes());
match metainfo.info.mode {
Mode::Multiple { files } => {
@ -1266,8 +1232,7 @@ Content Size 9 bytes
env.create_file("foo/.bar/baz", "baz");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.is_empty()
@ -1301,8 +1266,7 @@ Content Size 9 bytes
}
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.is_empty()
@ -1319,8 +1283,7 @@ Content Size 9 bytes
env.create_file("foo/c", "c");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 2
@ -1337,8 +1300,7 @@ Content Size 9 bytes
env.create_file("foo/c", "c");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 3
@ -1362,8 +1324,7 @@ Content Size 9 bytes
env.create_file("foo/c", "c");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 2
@ -1380,8 +1341,7 @@ Content Size 9 bytes
env.create_file("foo/c", "c");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.is_empty()
@ -1409,8 +1369,7 @@ Content Size 9 bytes
env.create_file("foo/c", "c");
env.run().unwrap();
let torrent = env.resolve("foo.torrent");
let bytes = fs::read(torrent).unwrap();
let metainfo = serde_bencode::de::from_bytes::<Metainfo>(&bytes).unwrap();
let metainfo = Metainfo::load(torrent).unwrap();
assert_matches!(
metainfo.info.mode,
Mode::Multiple { files } if files.len() == 1

View File

@ -160,55 +160,106 @@ impl Extractor {
return;
};
let value = if let Ok(value) = bencode::Value::decode(&contents) {
value
if let Ok(value) = Value::from_bencode(&contents) {
self.extract(&value);
if self.print {
eprintln!("{}:\n{}", path.display(), Self::pretty_print(&value));
}
} else {
self.bencode_decode_errors += 1;
return;
};
if self.print {
eprintln!("{}:\n{}", path.display(), value);
}
self.extract(&value);
}
fn extract(&mut self, value: &bencode::Value) {
use bencode::Value::*;
fn extract(&mut self, value: &Value) {
let matches = self.regex_set.matches(&self.current_path);
for i in matches.iter() {
let pattern = &self.regex_set.patterns()[i];
if let Some(values) = self.values.get_mut(pattern) {
values.push(value.to_string());
values.push(Self::pretty_print(value));
} else {
self.values.insert(pattern.clone(), vec![value.to_string()]);
self
.values
.insert(pattern.clone(), vec![Self::pretty_print(value)]);
}
}
let starting_length = self.current_path.len();
if let Dict(items) = value {
for (key, value) in items {
match String::from_utf8_lossy(key) {
Cow::Borrowed(s) => self.current_path.push_str(s),
Cow::Owned(s) => self.current_path.push_str(&s),
match value {
Value::List(list) => {
if self.current_path.pop().is_some() {
self.current_path.push('*');
}
for value in list {
self.extract(value);
}
self.paths.increment_ref(&self.current_path);
self.current_path.push('/');
self.extract(value);
self.current_path.truncate(starting_length);
}
} else if let List(values) = value {
if self.current_path.pop().is_some() {
self.current_path.push('*');
Value::Dict(dict) => {
for (key, value) in dict {
match String::from_utf8_lossy(key) {
Cow::Borrowed(s) => self.current_path.push_str(s),
Cow::Owned(s) => self.current_path.push_str(&s),
}
self.paths.increment_ref(&self.current_path);
self.current_path.push('/');
self.extract(value);
self.current_path.truncate(starting_length);
}
}
for value in values {
self.extract(value);
Value::Integer(_) | Value::Bytes(_) => {}
}
}
fn pretty_print(value: &Value) -> String {
let mut buffer = String::new();
Self::pretty_print_inner(value, &mut buffer);
buffer
}
fn pretty_print_inner(value: &Value, buffer: &mut String) {
match value {
Value::List(list) => {
buffer.push('[');
for (i, value) in list.iter().enumerate() {
if i > 0 {
buffer.push_str(", ");
}
Self::pretty_print_inner(value, buffer);
}
buffer.push(']');
}
self.current_path.truncate(starting_length);
Value::Dict(dict) => {
buffer.push('{');
for (i, (key, value)) in dict.iter().enumerate() {
if i > 0 {
buffer.push_str(", ");
}
Self::pretty_print_string(key, buffer);
buffer.push_str(": ");
Self::pretty_print_inner(value, buffer);
}
buffer.push('}');
}
Value::Integer(integer) => buffer.push_str(&integer.to_string()),
Value::Bytes(bytes) => {
Self::pretty_print_string(bytes, buffer);
}
}
}
fn pretty_print_string(string: &[u8], buffer: &mut String) {
if let Ok(text) = str::from_utf8(string) {
buffer.push('\"');
buffer.push_str(text);
buffer.push('\"');
} else {
buffer.push('<');
for byte in string {
buffer.push_str(&format!("{:X}", byte));
}
buffer.push('>');
}
}
}

View File

@ -8,15 +8,16 @@ pub(crate) struct TorrentSummary {
impl TorrentSummary {
fn new(bytes: &[u8], metainfo: Metainfo) -> Result<Self, Error> {
let value = bencode::Value::decode(&bytes).unwrap();
let value = Value::from_bencode(&bytes).unwrap();
let infohash = if let bencode::Value::Dict(items) = value {
let infohash = if let Value::Dict(items) = value {
let info = items
.iter()
.find(|(key, _value)| key == b"info")
.find(|pair: &(&Vec<u8>, &Value)| pair.0 == b"info")
.unwrap()
.1
.encode();
.to_bencode()
.unwrap();
Sha1::from(info).digest()
} else {
unreachable!()