You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
8.0 KiB
Rust

use crate::infohash::v1::U160;
use crate::infohash::InfoHashCapable;
use bytes::Bytes;
use num_traits::cast::ToPrimitive;
use ring::digest::{digest, SHA1_FOR_LEGACY_USE_ONLY};
use serde::export::fmt::Debug;
use serde::export::Formatter;
use std::collections::HashSet;
use std::fmt::Display;
use std::string::FromUtf8Error;
use torment_bencode::{BencodeError, BencodeValue};
use url::Url;
#[derive(Debug)]
pub enum MetaInfoParsingError {
DecodingFailure(BencodeError),
Utf8Error(FromUtf8Error),
WrongType,
MissingEntry,
}
impl From<BencodeError> for MetaInfoParsingError {
fn from(err: BencodeError) -> Self {
MetaInfoParsingError::DecodingFailure(err)
}
}
impl From<std::string::FromUtf8Error> for MetaInfoParsingError {
fn from(err: FromUtf8Error) -> Self {
MetaInfoParsingError::Utf8Error(err)
}
}
impl Display for MetaInfoParsingError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone)]
pub struct Torrent {
name: String,
announce_list: Vec<HashSet<Url>>,
info: MetaInfo,
bencoded: BencodeValue,
}
impl Debug for Torrent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Torrent")
.field("name", &self.name)
.field("announce_list", &self.announce_list)
.field("info", &self.info)
.finish()
}
}
impl Torrent {
pub fn from_bytes<T: AsRef<[u8]>>(
bytes: T,
name: Option<String>,
) -> Result<Self, MetaInfoParsingError> {
let b = Bytes::from(bytes.as_ref().to_vec());
let bencoded = BencodeValue::decode(b.clone())?;
let dict = bencoded.dict().ok_or(MetaInfoParsingError::WrongType)?;
let info = dict
.get(b"info")
.ok_or(MetaInfoParsingError::MissingEntry)?
.dict()
.ok_or(MetaInfoParsingError::WrongType)?;
let data = info.buffer();
let info = MetaInfo::from_bytes(data)?;
let name = dict
.get(b"name")
.and_then(|name| name.bytes())
.map(|str| String::from_utf8(str.to_vec()))
.transpose()?
.or(name)
.unwrap_or(info.info_hash.to_string());
let mut announce_list = if let Some(announce) = dict.get(b"announce-list") {
announce
.list()
.map(|list| -> Result<Vec<HashSet<Url>>, MetaInfoParsingError> {
let mut items = vec![];
for i in 0..list.len() {
let mut urls = HashSet::new();
let item = list.get(i).unwrap();
let url_list = item.list().unwrap();
for url_i in 0..url_list.len() {
urls.insert(
Url::parse(
&*url_list.get(url_i).unwrap().string().transpose()?.unwrap(),
)
.unwrap(),
);
}
items.push(urls);
}
Ok(items)
})
.transpose()?
.unwrap_or(vec![])
} else {
vec![]
};
if let Some(item) = dict.get(b"announce") {
if let Some(Ok(url)) = item.string() {
if let Ok(url) = Url::parse(&url) {
if announce_list.is_empty() {
announce_list.push(HashSet::new());
}
announce_list[0].insert(url);
}
}
}
Ok(Torrent {
name,
announce_list,
info,
bencoded,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn info_hash(&self) -> U160 {
self.info.info_hash()
}
pub fn meta_info(&self) -> &MetaInfo {
&self.info
}
pub fn announce_list(&self) -> &Vec<HashSet<Url>> {
self.announce_list.as_ref()
}
}
#[derive(Clone)]
pub struct MetaInfo {
info_hash: U160,
bencoded: BencodeValue,
piece_length: usize,
pieces: Bytes,
object: MetaInfoObject,
}
impl MetaInfo {
pub fn info_hash(&self) -> U160 {
self.info_hash
}
pub fn piece_length(&self) -> usize {
self.piece_length
}
pub fn pieces(&self) -> usize {
self.pieces.len() / 20
}
pub fn hash(&self, index: usize) -> Bytes {
self.pieces.slice(index * 20..(index + 1) * 20)
}
pub fn object(&self) -> &MetaInfoObject {
&self.object
}
}
#[derive(Clone, Debug)]
pub enum MetaInfoObject {
Files(Vec<MetaFile>),
File(usize),
}
impl Debug for MetaInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaInfo")
.field("info_hash", &self.info_hash)
.field("piece_length", &self.piece_length)
.field("object", &self.object)
.finish()
}
}
impl MetaInfo {
pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, MetaInfoParsingError> {
let b = Bytes::from(bytes.as_ref().to_vec());
let bencoded = BencodeValue::decode(b)?;
let dict = bencoded.dict().ok_or(MetaInfoParsingError::WrongType)?;
let piece_length = dict
.get(b"piece length")
.ok_or(MetaInfoParsingError::MissingEntry)?
.int()
.ok_or(MetaInfoParsingError::WrongType)?
.to_usize()
.ok_or(MetaInfoParsingError::WrongType)?;
let object = if let Some(files) = dict.get(b"files") {
let mut meta_files = vec![];
let files = files.list().ok_or(MetaInfoParsingError::WrongType)?;
for file_i in 0..files.len() {
let file = files.get(file_i).unwrap();
meta_files.push(MetaFile::from_bencoded(file)?);
}
MetaInfoObject::Files(meta_files)
} else {
MetaInfoObject::File(
dict.get(b"length")
.ok_or(MetaInfoParsingError::MissingEntry)?
.int()
.ok_or(MetaInfoParsingError::WrongType)?
.to_usize()
.ok_or(MetaInfoParsingError::WrongType)?,
)
};
let pieces = dict
.get(b"pieces")
.ok_or(MetaInfoParsingError::MissingEntry)?
.bytes()
.ok_or(MetaInfoParsingError::WrongType)?;
let info_hash =
U160::from_bytes(digest(&SHA1_FOR_LEGACY_USE_ONLY, dict.buffer().as_ref())).unwrap();
Ok(MetaInfo {
info_hash,
bencoded,
piece_length,
pieces,
object,
})
}
}
#[derive(Debug, Clone)]
pub struct MetaFile {
length: usize,
path: Vec<String>,
}
impl MetaFile {
fn from_bencoded(bencoded: &BencodeValue) -> Result<Self, MetaInfoParsingError> {
let dict = bencoded.dict().ok_or(MetaInfoParsingError::WrongType)?;
let path = dict
.get(b"path")
.ok_or(MetaInfoParsingError::MissingEntry)?
.list()
.ok_or(MetaInfoParsingError::WrongType)?;
let mut path_ele = vec![];
for path_i in 0..path.len() {
let path_item = path.get(path_i).unwrap();
path_ele.push(
path_item
.string()
.ok_or(MetaInfoParsingError::WrongType)??,
)
}
Ok(MetaFile {
path: path_ele,
length: dict
.get(b"length")
.ok_or(MetaInfoParsingError::MissingEntry)?
.int()
.ok_or(MetaInfoParsingError::WrongType)?
.to_usize()
.ok_or(MetaInfoParsingError::WrongType)?,
})
}
pub fn path(&self) -> &[String] {
&self.path
}
pub fn length(&self) -> usize {
self.length
}
}