Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
other variants, and other rust types
- `TrackerScheme` no longer derives de/serialize because that's not actually
used in torrent files
- A torrent file with an invalid tracker URI (such as an unknown scheme) will now
fail to parse as a `DecodedTorrent` and therefore as a `TorrentFile`, unless the
`unknown_tracker_scheme` variant is enabled, in which case it will produce a
valid `TorrentFile` where the `TrackerScheme` is of the `Unknown` variant


### Added
Expand All @@ -31,6 +35,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
a parsed magnet link.
- `MagnetLink::trackers` lists the trackers in the magnet link
- `TrackerScheme` and `Tracker` implement `FromStr`
- `TorrentFile::to_vec` serializes to a bencoded byte slice, to save to
a .torrent file
- `DecodedTorrent::announce` and `DecodedTorrent::announce_list` list the
trackers contained in the torrent file
- `TrackerScheme::Unknown` stores unknown schemes instead of failing to parse,
when the tracker URL scheme is not recognized, and when the `unknown_tracker_scheme`
crate feature is anbled

### Fixed

Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ serde_json = "1"

[features]
magnet_force_name = []
unknown_tracker_scheme = []

[[test]]
name = "magnet_force_name"
path = "tests/magnet_force_name.rs"
required-features = [ "magnet_force_name" ]
test = true

[[test]]
name = "unknown_tracker_scheme"
path = "tests/unknown_tracker_scheme.rs"
required-features = [ "unknown_tracker_scheme" ]
test = true
52 changes: 30 additions & 22 deletions src/torrent_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use sha1::{Digest, Sha1};
use std::collections::HashMap;
use std::path::PathBuf;

use crate::{InfoHash, InfoHashError, PieceLength, TorrentContent, TorrentID};
use crate::{InfoHash, InfoHashError, PieceLength, TorrentContent, TorrentID, Tracker};

/// Error occurred during parsing a [`TorrentFile`](crate::torrent_file::TorrentFile).
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -89,11 +89,24 @@ pub struct TorrentFile {

/// A parsed bencode-decoded value, to ensure torrent-like structure.
///
/// In its present form, DecodedTorrent only cares about the info dict, but preserves other fields
/// In its present form, DecodedTorrent mostly cares about the info dict, but preserves other fields
/// as [`BencodeValue`](bt_bencode::BencodeValue) in an `extra` mapping so you can implement
/// your own extra parsing.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DecodedTorrent {
/// Main tracker
#[serde(skip_serializing_if = "Option::is_none")]
announce: Option<Tracker>,

/// Many alternative trackers.
/// TODO: what is this about the tiers?
#[serde(
rename = "announce-list",
default,
skip_serializing_if = "Vec::is_empty"
)]
announce_list: Vec<Vec<Tracker>>,

info: DecodedInfo,

// Rest of torrent dict
Expand Down Expand Up @@ -234,6 +247,13 @@ pub struct DecodedInfo {
}

impl TorrentFile {
/// Serialize to a .torrent file byte slice
pub fn to_vec(&self) -> Vec<u8> {
// This should not fail
bt_bencode::to_vec(&self.decoded).unwrap()
}

/// Deserialize (parse) from a .torrent file byte slice
pub fn from_slice(s: &[u8]) -> Result<TorrentFile, TorrentFileError> {
let torrent: DecodedTorrent = bt_bencode::from_slice(s).map_err(|e| {
// We store a stringy representation of the error because bt_encode::Error
Expand Down Expand Up @@ -309,7 +329,7 @@ mod tests {
use super::*;

#[test]
fn can_read_torrent_v1() {
fn can_read_torrent_v1_multifile() {
let slice = std::fs::read("tests/bittorrent-v1-emma-goldman.torrent").unwrap();
let res = TorrentFile::from_slice(&slice);
println!("{:?}", res);
Expand All @@ -327,29 +347,17 @@ mod tests {
}

#[test]
fn can_read_torrent_v1_multifile() {
#[cfg(not(feature = "unknown_tracker_scheme"))]
fn fail_no_torrent_scheme() {
let slice = std::fs::read("tests/libtorrent/good/sample.torrent").unwrap();
let res = TorrentFile::from_slice(&slice);
println!("{:?}", res);
assert!(res.is_ok());
let torrent = res.unwrap();
assert_eq!(&torrent.name, "sample");
assert_eq!(
torrent.hash,
InfoHash::V1("58d8d15a4eb3bd9afabc9cee2564f78192777edb".to_string())
);
assert!(res.is_err());
assert_eq!(
torrent.decoded.files().unwrap(),
vec!(
TorrentContent {
path: PathBuf::from("text_file.txt"),
size: 20,
},
TorrentContent {
path: PathBuf::from("text_file2.txt"),
size: 25,
}
),
res.unwrap_err(),
TorrentFileError::NotATorrent {
reason: "Invalid scheme: tracker.publicbt.com".to_string()
},
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ pub enum TrackerScheme {
Websocket,
Http,
Udp,
/// An unknown scheme is in the tracker URI.
///
/// This is also the case when there is no scheme, in which
/// case the domain name may be parsed as a scheme.
///
/// This is disabled by default and required the `unknown_tracker_scheme`
/// crate feature enabled if you really need to parse broken torrents.
#[cfg(feature = "unknown_tracker_scheme")]
Unknown(String),
}

impl FromStr for TrackerScheme {
Expand All @@ -98,6 +107,9 @@ impl FromStr for TrackerScheme {
"http" | "https" => Ok(Self::Http),
"ws" => Ok(Self::Websocket),
"udp" => Ok(Self::Udp),
#[cfg(feature = "unknown_tracker_scheme")]
_ => Ok(Self::Unknown(s.to_string())),
#[cfg(not(feature = "unknown_tracker_scheme"))]
_ => Err(TrackerError::InvalidScheme {
scheme: s.to_string(),
}),
Expand Down
31 changes: 31 additions & 0 deletions tests/unknown_tracker_scheme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use hightorrent::{InfoHash, TorrentContent, TorrentFile};

use std::path::PathBuf;

#[test]
fn can_parse_no_scheme_tracker() {
// This only works when the unknown_tracker_scheme crate feature is eanbled
let slice = std::fs::read("tests/libtorrent/good/sample.torrent").unwrap();
let res = TorrentFile::from_slice(&slice);
println!("{:?}", res);
assert!(res.is_ok());
let torrent = res.unwrap();
assert_eq!(&torrent.name, "sample");
assert_eq!(
torrent.hash,
InfoHash::V1("58d8d15a4eb3bd9afabc9cee2564f78192777edb".to_string())
);
assert_eq!(
torrent.decoded.files().unwrap(),
vec!(
TorrentContent {
path: PathBuf::from("text_file.txt"),
size: 20,
},
TorrentContent {
path: PathBuf::from("text_file2.txt"),
size: 25,
}
),
);
}
Loading