Compare commits
No commits in common. "fd4a7e3589994edb813e4940553ad966d4e5e107" and "7556471090352d534720d06b7d36e22a4521427a" have entirely different histories.
fd4a7e3589
...
7556471090
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -865,15 +865,6 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_bytes"
|
|
||||||
version = "0.11.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.204"
|
version = "1.0.204"
|
||||||
@ -1079,7 +1070,6 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
|
||||||
"sha1",
|
"sha1",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,6 @@ reqwest = { version = "0.12.5", features = ["blocking"] }
|
|||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
serde_bytes = "0.11.15"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
@ -81,18 +81,12 @@ impl<'de> Deserializer<'de> {
|
|||||||
impl <'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
impl <'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
|
fn deserialize_any<V>(self, _: V) -> Result<V::Value>
|
||||||
where
|
where
|
||||||
V: Visitor<'de>
|
V: Visitor<'de>
|
||||||
{
|
{
|
||||||
let b = self.peek_byte()? as char;
|
println!("i don't get this API!");
|
||||||
match b {
|
Err(Error::WontImplement)
|
||||||
'i' => self.deserialize_i64(visitor),
|
|
||||||
'l' => self.deserialize_seq(visitor),
|
|
||||||
'd' => self.deserialize_map(visitor),
|
|
||||||
'0'..='9' => self.deserialize_str(visitor),
|
|
||||||
_ => Err(Error::Syntax(format!("any: invalid start char: {}", b))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_bool<V>(self, _: V) -> Result<V::Value>
|
fn deserialize_bool<V>(self, _: V) -> Result<V::Value>
|
||||||
@ -280,15 +274,14 @@ impl <'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>
|
V: Visitor<'de>
|
||||||
{
|
{
|
||||||
let b = self.peek_byte()? as char;
|
match self.peek_byte()? as char{
|
||||||
match b {
|
|
||||||
'l' => self.deserialize_seq(visitor),
|
'l' => self.deserialize_seq(visitor),
|
||||||
'0'..='9' => {
|
'0'..='9' => {
|
||||||
let str = self.parse_byte_string()?;
|
let str = self.parse_byte_string()?;
|
||||||
let ba = ByteAccess::new(self, str);
|
let ba = ByteAccess::new(self, str);
|
||||||
visitor.visit_seq(ba)
|
visitor.visit_seq(ba)
|
||||||
},
|
},
|
||||||
_ => Err(Error::Syntax(format!("tuple: invalid start char: {}", b))),
|
_ => Err(Error::Syntax),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,11 +329,12 @@ impl <'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
|||||||
self.deserialize_bytes(visitor)
|
self.deserialize_bytes(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
|
fn deserialize_ignored_any<V>(self, _: V) -> Result<V::Value>
|
||||||
where
|
where
|
||||||
V: Visitor<'de>
|
V: Visitor<'de>
|
||||||
{
|
{
|
||||||
self.deserialize_any(visitor)
|
println!("i really don't get this API!");
|
||||||
|
Err(Error::WontImplement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,13 +394,12 @@ impl<'a, 'de> SeqAccess<'de> for RegularAccess<'a, 'de> {
|
|||||||
where
|
where
|
||||||
T: DeserializeSeed<'de>
|
T: DeserializeSeed<'de>
|
||||||
{
|
{
|
||||||
let b = self.de.peek_byte()? as char;
|
match self.de.peek_byte()? as char {
|
||||||
match b {
|
|
||||||
'd' | 'i' | 'l' | '0'..='9' => {
|
'd' | 'i' | 'l' | '0'..='9' => {
|
||||||
seed.deserialize(&mut *self.de).map(Some)
|
seed.deserialize(&mut *self.de).map(Some)
|
||||||
},
|
},
|
||||||
'e' => Ok(None),
|
'e' => Ok(None),
|
||||||
_ => Err(Error::Syntax(format!("seq: next_element: invalid start/end char: {}", b)))
|
_ => Err(Error::Syntax)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,12 +424,11 @@ impl<'a, 'de> MapAccess<'de> for RegularAccess<'a, 'de> {
|
|||||||
where
|
where
|
||||||
V: DeserializeSeed<'de>
|
V: DeserializeSeed<'de>
|
||||||
{
|
{
|
||||||
let b = self.de.peek_byte()? as char;
|
match self.de.peek_byte()? as char {
|
||||||
match b {
|
|
||||||
'd' | 'i' | 'l' | '0'..='9' => {
|
'd' | 'i' | 'l' | '0'..='9' => {
|
||||||
seed.deserialize(&mut *self.de)
|
seed.deserialize(&mut *self.de)
|
||||||
},
|
},
|
||||||
_ => Err(Error::Syntax(format!("map: next_value: invalid start char: {}", b)))
|
_ => Err(Error::Syntax)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,14 +516,4 @@ mod test {
|
|||||||
assert_eq!(de.l, vec![23, 17]);
|
assert_eq!(de.l, vec![23, 17]);
|
||||||
assert_eq!(de.o, Some(42));
|
assert_eq!(de.o, Some(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_struct_extra() {
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct A {
|
|
||||||
a: i64,
|
|
||||||
}
|
|
||||||
let de: A = from_str("d1:ai42e1:bi18ee").unwrap();
|
|
||||||
assert_eq!(de.a, 42);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ pub enum Error {
|
|||||||
|
|
||||||
WontImplement,
|
WontImplement,
|
||||||
Eof,
|
Eof,
|
||||||
Syntax(String),
|
Syntax,
|
||||||
InvalidUtf8,
|
InvalidUtf8,
|
||||||
ExpectedBytes,
|
ExpectedBytes,
|
||||||
ExpectedBytesSep,
|
ExpectedBytesSep,
|
||||||
@ -48,8 +48,8 @@ impl Display for Error {
|
|||||||
Error::Message(msg) => formatter.write_str(msg),
|
Error::Message(msg) => formatter.write_str(msg),
|
||||||
Error::WontImplement => formatter.write_str("there is no reasonable way to (de)serialize this type"),
|
Error::WontImplement => formatter.write_str("there is no reasonable way to (de)serialize this type"),
|
||||||
Error::Eof => formatter.write_str("unexpected end of input"),
|
Error::Eof => formatter.write_str("unexpected end of input"),
|
||||||
Error::Syntax(msg) => formatter.write_fmt(format_args!("syntax error: {}", msg)),
|
Error::Syntax => formatter.write_str("syntax error"),
|
||||||
Error::InvalidUtf8 => formatter.write_str("could not decode as UTF-8"),
|
Error::InvalidUtf8 => formatter.write_str("could not decoded as UTF-8"),
|
||||||
Error::ExpectedBytes => formatter.write_str("expected byte string start char: any number"),
|
Error::ExpectedBytes => formatter.write_str("expected byte string start char: any number"),
|
||||||
Error::ExpectedBytesSep => formatter.write_str("expected byte separator char: ':'"),
|
Error::ExpectedBytesSep => formatter.write_str("expected byte separator char: ':'"),
|
||||||
Error::ExpectedInteger => formatter.write_str("expected integer start char 'i'"),
|
Error::ExpectedInteger => formatter.write_str("expected integer start char 'i'"),
|
||||||
|
209
src/main.rs
209
src/main.rs
@ -2,106 +2,168 @@
|
|||||||
#![allow(uncommon_codepoints)]
|
#![allow(uncommon_codepoints)]
|
||||||
|
|
||||||
use std::{env, fs, path};
|
use std::{env, fs, path};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::bencode::de::from_bytes;
|
|
||||||
use crate::bencode::ser::to_bytes;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use serde::ser::SerializeSeq;
|
use crate::bencode::custom::{Bencode, ByteString};
|
||||||
use serde_bytes::ByteBuf;
|
|
||||||
use sha1::{Digest, Sha1};
|
|
||||||
|
|
||||||
mod bencode;
|
mod bencode;
|
||||||
mod torrent;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug)]
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
length: i64,
|
length: i64,
|
||||||
#[serde(deserialize_with = "deserialize_path", serialize_with = "serialize_path")]
|
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_path<'de, D>(deserializer: D) -> Result<String, D::Error>
|
#[derive(Debug)]
|
||||||
where D: Deserializer<'de> {
|
|
||||||
let parts: Vec<String> = Vec::deserialize(deserializer)?;
|
|
||||||
for p in parts.iter() {
|
|
||||||
let res = p.find(path::MAIN_SEPARATOR_STR);
|
|
||||||
if let Some(_) = res {
|
|
||||||
return Err(de::Error::custom(format!("Unable to deal with {} (platform path separator in path parts", path::MAIN_SEPARATOR_STR)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(parts.join(path::MAIN_SEPARATOR_STR))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_path<S>(v: &String, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where S: Serializer {
|
|
||||||
let parts: Vec<&str> = v.split(path::MAIN_SEPARATOR_STR).collect();
|
|
||||||
let mut seq = serializer.serialize_seq(Some(parts.len()))?;
|
|
||||||
for p in parts {
|
|
||||||
seq.serialize_element(p)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct TorrentInfo {
|
struct TorrentInfo {
|
||||||
files: Option<Vec<FileInfo>>,
|
files: Option<Vec<FileInfo>>,
|
||||||
length: Option<i64>,
|
length: Option<i64>,
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(rename(serialize = "piece length", deserialize = "piece length"))]
|
|
||||||
piece_length: i64,
|
piece_length: i64,
|
||||||
#[serde(deserialize_with = "deserialize_pieces", serialize_with = "serialize_pieces")]
|
|
||||||
pieces: Vec<[u8; 20]>,
|
pieces: Vec<[u8; 20]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_pieces<'de, D>(deserializer: D) -> Result<Vec<[u8; 20]>, D::Error>
|
#[derive(Debug)]
|
||||||
where D: Deserializer<'de> {
|
|
||||||
let all_pieces = ByteBuf::deserialize(deserializer)?.into_vec();
|
|
||||||
if all_pieces.len() % 20 != 0 {
|
|
||||||
return Err(de::Error::custom("Pieces string length not a multiple of 20"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO maybe handle the error, even though it should be checked above
|
|
||||||
let pieces = all_pieces.chunks(20).map(|x| x.try_into().unwrap()).collect();
|
|
||||||
Ok(pieces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_pieces<S>(v: &Vec<[u8; 20]>, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where S: Serializer
|
|
||||||
{
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
for i in v {
|
|
||||||
buf.extend(i)
|
|
||||||
}
|
|
||||||
serializer.serialize_bytes(&buf[..])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct Torrent {
|
struct Torrent {
|
||||||
announce: String,
|
announce: String,
|
||||||
info: TorrentInfo,
|
info: TorrentInfo,
|
||||||
#[serde(skip)]
|
|
||||||
info_hash: [u8; 20],
|
info_hash: [u8; 20],
|
||||||
|
_extra: HashMap<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Torrent {
|
impl Torrent {
|
||||||
fn from(input: &[u8]) -> Result<Self> {
|
pub fn from(input: &Bencode) -> Result<Self> {
|
||||||
let mut torrent: Self = from_bytes(input)?;
|
let Bencode::Dict(decoded) = input else {
|
||||||
torrent.compute_info_hash()?;
|
return Err(anyhow!("Torrent has wrong type. Not a dict"))
|
||||||
Ok(torrent)
|
};
|
||||||
}
|
let b_info = decoded.get(&ByteString::from_str("info"))
|
||||||
|
.ok_or(anyhow!("Could not find key \"info\""))?;
|
||||||
|
let Bencode::Dict(info_dict) = b_info else {
|
||||||
|
return Err(anyhow!("Info has wrong type. Not a dict"))
|
||||||
|
};
|
||||||
|
|
||||||
fn compute_info_hash(&mut self) -> Result<()> {
|
let Bencode::Bytes(name) = info_dict.get(&ByteString::from_str("name"))
|
||||||
let mut hasher = Sha1::new();
|
.ok_or(anyhow!("Could not find key \"name\""))? else {
|
||||||
|
return Err(anyhow!("name not a string"))
|
||||||
|
};
|
||||||
|
|
||||||
let info_str = to_bytes(&self.info)?;
|
let Bencode::Integer(piece_length) = *info_dict.get(&ByteString::from_str("piece length"))
|
||||||
hasher.update(info_str);
|
.ok_or(anyhow!("Could not find key \"piece length\""))? else {
|
||||||
|
return Err(anyhow!("piece length not an integer"))
|
||||||
|
};
|
||||||
|
|
||||||
self.info_hash = hasher.finalize().into();
|
let Bencode::Bytes(all_pieces) = info_dict.get(&ByteString::from_str("pieces"))
|
||||||
Ok(())
|
.ok_or(anyhow!("Could not find key \"pieces\""))? else {
|
||||||
|
return Err(anyhow!("pieces not an string"))
|
||||||
|
};
|
||||||
|
|
||||||
|
if all_pieces.to_vec().len() % 20 != 0 {
|
||||||
|
return Err(anyhow!("Pieces string length not a multiple of 20"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO maybe handle the error, even though it should be checked above
|
||||||
|
let pieces = all_pieces.to_vec().chunks(20).map(|x| x.try_into().unwrap()).collect();
|
||||||
|
|
||||||
|
let info;
|
||||||
|
if let Some(b_length) = info_dict.get(&ByteString::from_str("length")) {
|
||||||
|
let Bencode::Integer(length) = b_length else {
|
||||||
|
return Err(anyhow!("length not an integer"))
|
||||||
|
};
|
||||||
|
info = TorrentInfo {
|
||||||
|
files: None,
|
||||||
|
length: Some(*length),
|
||||||
|
name: name.to_string()?,
|
||||||
|
piece_length,
|
||||||
|
pieces,
|
||||||
|
}
|
||||||
|
} else if let Some(b_files) = info_dict.get(&ByteString::from_str("files")) {
|
||||||
|
let Bencode::List(files_list) = b_files else {
|
||||||
|
return Err(anyhow!("files not a list"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut files = Vec::new();
|
||||||
|
for b_file_info in files_list {
|
||||||
|
let Bencode::Dict(file_info) = b_file_info else {
|
||||||
|
return Err(anyhow!("file_info not a dict"))
|
||||||
|
};
|
||||||
|
let Bencode::List(b_path) = file_info.get(&ByteString::from_str("path"))
|
||||||
|
.ok_or(anyhow!("Could not find key \"path\""))? else {
|
||||||
|
return Err(anyhow!("path not a list"))
|
||||||
|
};
|
||||||
|
let mut v_path = Vec::new();
|
||||||
|
for b_path_part in b_path {
|
||||||
|
let Bencode::Bytes(path_part) = b_path_part else {
|
||||||
|
return Err(anyhow!("path part not a string"))
|
||||||
|
};
|
||||||
|
v_path.push(path_part.to_string()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Bencode::Integer(length) = *file_info.get(&ByteString::from_str("length"))
|
||||||
|
.ok_or(anyhow!("Could not find key \"length\""))? else {
|
||||||
|
return Err(anyhow!("length not an integer"))
|
||||||
|
};
|
||||||
|
files.push(FileInfo{
|
||||||
|
path: v_path.join(path::MAIN_SEPARATOR_STR),
|
||||||
|
length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
info = TorrentInfo {
|
||||||
|
files: Some(files),
|
||||||
|
length: None,
|
||||||
|
name: name.to_string()?,
|
||||||
|
piece_length,
|
||||||
|
pieces,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("Could not find key \"length\" or \"files\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let b_announce = decoded.get(&ByteString::from_str("announce"))
|
||||||
|
.ok_or(anyhow!("Could not find key \"announce\""))?;
|
||||||
|
let Bencode::Bytes(announce) = b_announce else {
|
||||||
|
return Err(anyhow!("announce not a string"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut _extra = HashMap::new();
|
||||||
|
for (k, v) in decoded {
|
||||||
|
if k == &ByteString::from_str("announce") || k == &ByteString::from_str("info") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let Ok(k_str) = k.to_string() else {
|
||||||
|
println!("Ignoring non-UTF-8 key {k:?}");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match v {
|
||||||
|
Bencode::Integer(i) => {
|
||||||
|
_extra.insert(k_str, format!("{}", i));
|
||||||
|
}
|
||||||
|
Bencode::Bytes(s) => {
|
||||||
|
let Ok(s_str) = s.to_string() else {
|
||||||
|
println!("Ignoring non-UTF-8 value of key {k_str}");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
_extra.insert(k_str, s_str);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Ignoring extra of type list or dict")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Self {
|
||||||
|
announce: announce.to_string()?,
|
||||||
|
info_hash: b_info.sha1(),
|
||||||
|
info,
|
||||||
|
_extra,
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +176,12 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let torrent_bytes = fs::read(&file_path)?;
|
let torrent_bytes = fs::read(&file_path)?;
|
||||||
|
|
||||||
let torrent = Torrent::from(&torrent_bytes)?;
|
let torrent_parsed = Bencode::parse(&torrent_bytes)?;
|
||||||
|
|
||||||
|
println!("torrent file parsed");
|
||||||
|
// println!("parsed file: {torrent_parsed}");
|
||||||
|
|
||||||
|
let torrent = Torrent::from(&torrent_parsed)?;
|
||||||
|
|
||||||
// println!("torrent decoded: {torrent:?}");
|
// println!("torrent decoded: {torrent:?}");
|
||||||
|
|
||||||
@ -153,7 +220,7 @@ fn main() -> Result<()> {
|
|||||||
let resp = client.get(url).send()?;
|
let resp = client.get(url).send()?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
let body = resp.text()?;
|
// let body = resp.text()?;
|
||||||
println!("Response: {status}");
|
println!("Response: {status}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user