Restrict piece length
- Must be greater than zero - Must be a power of two (but can override with `--allow uneven-piece-length` - Must be greater than 16KiB (but can override with `--allow small-piece-length` - Must be less than u32 max type: changed
This commit is contained in:
parent
85f02d9f29
commit
6df45e0244
|
@ -12,6 +12,12 @@ const YI: u128 = ZI << 10;
|
|||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub(crate) struct Bytes(pub(crate) u128);
|
||||
|
||||
impl Bytes {
|
||||
pub(crate) fn is_power_of_two(self) -> bool {
|
||||
self.0 == 0 || self.0 & (self.0 - 1) == 0
|
||||
}
|
||||
}
|
||||
|
||||
fn float_to_int(x: f64) -> u128 {
|
||||
#![allow(
|
||||
clippy::as_conversions,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
pub(crate) use std::{
|
||||
borrow::Cow,
|
||||
cmp::{Ordering, Reverse},
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
convert::{Infallible, TryInto},
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
|
@ -44,7 +44,7 @@ pub(crate) use crate::{
|
|||
// structs and enums
|
||||
pub(crate) use crate::{
|
||||
bytes::Bytes, env::Env, error::Error, file_info::FileInfo, hasher::Hasher, info::Info,
|
||||
metainfo::Metainfo, mode::Mode, opt::Opt, platform::Platform, style::Style,
|
||||
lint::Lint, metainfo::Metainfo, mode::Mode, opt::Opt, platform::Platform, style::Style,
|
||||
subcommand::Subcommand, torrent::Torrent, use_color::UseColor,
|
||||
};
|
||||
|
||||
|
|
11
src/env.rs
11
src/env.rs
|
@ -91,6 +91,17 @@ impl Env {
|
|||
self.err_style.message().suffix(),
|
||||
)
|
||||
.ok();
|
||||
|
||||
if let Some(lint) = error.lint() {
|
||||
writeln!(
|
||||
&mut self.err,
|
||||
"{}: This check can be disabled with `--allow {}`.",
|
||||
self.err_style.message().paint("note"),
|
||||
lint.name()
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
Err(EXIT_FAILURE)
|
||||
}
|
||||
} else {
|
||||
|
|
20
src/error.rs
20
src/error.rs
|
@ -37,7 +37,13 @@ pub(crate) enum Error {
|
|||
bytes,
|
||||
Bytes(u32::max_value().into())
|
||||
))]
|
||||
PieceLength { bytes: Bytes },
|
||||
PieceLengthTooLarge { bytes: Bytes },
|
||||
#[snafu(display("Piece length `{}` is not an even power of two", bytes))]
|
||||
PieceLengthUneven { bytes: Bytes },
|
||||
#[snafu(display("Piece length must be at least 16 KiB"))]
|
||||
PieceLengthSmall,
|
||||
#[snafu(display("Piece length cannot be zero"))]
|
||||
PieceLengthZero,
|
||||
#[snafu(display("Serialization failed: {}", source))]
|
||||
Serialize { source: serde_bencode::Error },
|
||||
#[snafu(display("Failed to write to standard error: {}", source))]
|
||||
|
@ -51,6 +57,18 @@ pub(crate) enum Error {
|
|||
feature
|
||||
))]
|
||||
Unstable { feature: &'static str },
|
||||
#[snafu(display("Unknown lint: {}", text))]
|
||||
LintUnknown { text: String },
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn lint(&self) -> Option<Lint> {
|
||||
match self {
|
||||
Self::PieceLengthUneven { .. } => Some(Lint::UnevenPieceLength),
|
||||
Self::PieceLengthSmall { .. } => Some(Lint::SmallPieceLength),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<clap::Error> for Error {
|
||||
|
|
70
src/lint.rs
Normal file
70
src/lint.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use crate::common::*;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone, Ord, PartialOrd)]
|
||||
pub(crate) enum Lint {
|
||||
UnevenPieceLength,
|
||||
SmallPieceLength,
|
||||
}
|
||||
|
||||
const UNEVEN_PIECE_LENGTH: &str = "uneven-piece-length";
|
||||
const SMALL_PIECE_LENGTH: &str = "small-piece-length";
|
||||
|
||||
impl Lint {
|
||||
pub(crate) fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::UnevenPieceLength => UNEVEN_PIECE_LENGTH,
|
||||
Self::SmallPieceLength => SMALL_PIECE_LENGTH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Lint {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
match text.replace('_', "-").to_lowercase().as_str() {
|
||||
UNEVEN_PIECE_LENGTH => Ok(Self::UnevenPieceLength),
|
||||
SMALL_PIECE_LENGTH => Ok(Self::SmallPieceLength),
|
||||
_ => Err(Error::LintUnknown {
|
||||
text: text.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Lint {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_str_ok() {
|
||||
assert_eq!(
|
||||
Lint::UnevenPieceLength,
|
||||
"uneven_piece_length".parse().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Lint::UnevenPieceLength,
|
||||
"uneven-piece-length".parse().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Lint::UnevenPieceLength,
|
||||
"UNEVEN-piece-length".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_err() {
|
||||
assert_matches!(
|
||||
"foo".parse::<Lint>(),
|
||||
Err(Error::LintUnknown { text }) if text == "foo"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ mod hasher;
|
|||
mod info;
|
||||
mod into_u64;
|
||||
mod into_usize;
|
||||
mod lint;
|
||||
mod metainfo;
|
||||
mod mode;
|
||||
mod opt;
|
||||
|
|
|
@ -15,6 +15,13 @@ pub(crate) struct Create {
|
|||
long_help = "Use `ANNOUNCE` as the primary tracker announce URL. To supply multiple announce URLs, also use `--announce-tier`."
|
||||
)]
|
||||
announce: Url,
|
||||
#[structopt(
|
||||
name = "ALLOW",
|
||||
long = "allow",
|
||||
help = "Use `ANNOUNCE` as the primary tracker announce URL.",
|
||||
long_help = "Use `ANNOUNCE` as the primary tracker announce URL. To supply multiple announce URLs, also use `--announce-tier`."
|
||||
)]
|
||||
allowed_lints: Vec<Lint>,
|
||||
#[structopt(
|
||||
long = "announce-tier",
|
||||
name = "ANNOUNCE-TIER",
|
||||
|
@ -98,15 +105,57 @@ Note: Many BitTorrent clients do not implement the behavior described in BEP 12.
|
|||
private: bool,
|
||||
}
|
||||
|
||||
struct Linter {
|
||||
allowed: BTreeSet<Lint>,
|
||||
}
|
||||
|
||||
impl Linter {
|
||||
fn new() -> Linter {
|
||||
Linter {
|
||||
allowed: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn allow(&mut self, allowed: impl IntoIterator<Item = Lint>) {
|
||||
self.allowed.extend(allowed)
|
||||
}
|
||||
|
||||
fn is_allowed(&self, lint: Lint) -> bool {
|
||||
self.allowed.contains(&lint)
|
||||
}
|
||||
|
||||
fn is_denied(&self, lint: Lint) -> bool {
|
||||
!self.is_allowed(lint)
|
||||
}
|
||||
}
|
||||
|
||||
impl Create {
|
||||
pub(crate) fn run(self, env: &Env) -> Result<(), Error> {
|
||||
let piece_length: u32 = self
|
||||
.piece_length
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| Error::PieceLength {
|
||||
let mut linter = Linter::new();
|
||||
linter.allow(self.allowed_lints.iter().cloned());
|
||||
|
||||
if linter.is_denied(Lint::UnevenPieceLength) && !self.piece_length.is_power_of_two() {
|
||||
return Err(Error::PieceLengthUneven {
|
||||
bytes: self.piece_length,
|
||||
})?;
|
||||
});
|
||||
}
|
||||
|
||||
let piece_length: u32 =
|
||||
self
|
||||
.piece_length
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| Error::PieceLengthTooLarge {
|
||||
bytes: self.piece_length,
|
||||
})?;
|
||||
|
||||
if piece_length == 0 {
|
||||
return Err(Error::PieceLengthZero);
|
||||
}
|
||||
|
||||
if linter.is_denied(Lint::SmallPieceLength) && piece_length < 16 * 1024 {
|
||||
return Err(Error::PieceLengthSmall);
|
||||
}
|
||||
|
||||
let input = env.resolve(&self.input);
|
||||
|
||||
|
@ -405,14 +454,14 @@ mod tests {
|
|||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"1",
|
||||
"64KiB",
|
||||
]);
|
||||
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();
|
||||
assert_eq!(metainfo.info.piece_length, 1);
|
||||
assert_eq!(metainfo.info.piece_length, 64 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -441,7 +490,7 @@ mod tests {
|
|||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"1",
|
||||
"16KiB",
|
||||
]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
|
@ -459,7 +508,7 @@ mod tests {
|
|||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"1",
|
||||
"32KiB",
|
||||
]);
|
||||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).unwrap();
|
||||
|
@ -588,6 +637,8 @@ mod tests {
|
|||
"http://bar",
|
||||
"--piece-length",
|
||||
"1",
|
||||
"--allow",
|
||||
"small-piece-length",
|
||||
]);
|
||||
let contents = "bar";
|
||||
fs::write(env.resolve("foo"), contents).unwrap();
|
||||
|
@ -732,4 +783,80 @@ mod tests {
|
|||
|
||||
panic!("Failed to read `opened.txt`.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uneven_piece_length() {
|
||||
let mut env = environment(&[
|
||||
"--input",
|
||||
"foo",
|
||||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"17KiB",
|
||||
]);
|
||||
assert_matches!(
|
||||
env.run(),
|
||||
Err(Error::PieceLengthUneven { bytes }) if bytes.0 == 17 * 1024
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uneven_piece_length_allow() {
|
||||
let mut env = environment(&[
|
||||
"--input",
|
||||
"foo",
|
||||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"17KiB",
|
||||
"--allow",
|
||||
"uneven-piece-length",
|
||||
]);
|
||||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).unwrap();
|
||||
env.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_piece_length() {
|
||||
let mut env = environment(&[
|
||||
"--input",
|
||||
"foo",
|
||||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"0",
|
||||
]);
|
||||
assert_matches!(env.run(), Err(Error::PieceLengthZero));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_piece_length() {
|
||||
let mut env = environment(&[
|
||||
"--input",
|
||||
"foo",
|
||||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"8KiB",
|
||||
]);
|
||||
assert_matches!(env.run(), Err(Error::PieceLengthSmall));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_piece_length_allow() {
|
||||
let mut env = environment(&[
|
||||
"--input",
|
||||
"foo",
|
||||
"--announce",
|
||||
"http://bar",
|
||||
"--piece-length",
|
||||
"8KiB",
|
||||
"--allow",
|
||||
"small-piece-length",
|
||||
]);
|
||||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).unwrap();
|
||||
env.run().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user