added simple magnet parser

This commit is contained in:
hendrik 2024-10-08 00:40:09 +02:00
parent 0ed146f785
commit 4d134c18ff
7 changed files with 161 additions and 4 deletions

View File

@ -33,4 +33,4 @@ serde_urlencoded = "0.7.1" # for url enc
sha1 = "0.10.1" # hashing
tempfile = "3" # creating temporary directories
thiserror = "1.0.38" # error handling
tokio = { version = "1.23.0", features = ["full"] } # async http requests
tokio = { version = "1.23.0", features = ["full"] } # async http requests

View File

@ -1,6 +1,9 @@
use serde_urlencoded;
use sha1::{Digest, Sha1};
use std::{collections::HashMap, fmt::Write};
use std::{
collections::HashMap,
fmt::{self, Debug, Formatter, Write},
};
pub fn hash(input: &[u8]) -> HashValue {
let mut hasher = Sha1::new();
@ -23,6 +26,17 @@ impl HashValue {
&self.value
}
pub fn from_hex<I: Into<String>>(value: I) -> Self {
let mut value: &str = &value.into();
let mut out = Vec::new();
while !value.is_empty() {
out.extend_from_slice(&u8::from_str_radix(&value[..2], 16).unwrap().to_be_bytes());
value = &value[2..];
}
Self { value: out }
}
pub fn hex(&self) -> String {
self.value.iter().fold(String::new(), |mut out, b| {
let _ = write!(out, "{b:02x}");
@ -49,3 +63,9 @@ impl HashValue {
encoded_value
}
}
impl Debug for HashValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.hex())
}
}

View File

@ -1,4 +1,5 @@
pub mod encode;
pub mod magnet;
pub mod ouput_gen;
pub mod rand;
pub mod tcp;

59
src/magnet/data.rs Normal file
View File

@ -0,0 +1,59 @@
use std::fmt::Display;
use thiserror::Error;
use crate::encode::hash::HashValue;
#[derive(Error, Debug)]
pub enum MagnetParseError {
#[error("Invalid magnet link")]
InvalidMagnet,
#[error("Unexpected end of link")]
UnexpectedEol,
#[error("Missing field: {0}")]
MissingField(String),
#[error("Invalid format of field {0}")]
InvalidFieldFormat(String),
}
#[derive(Debug)]
pub struct Magnet {
urn: String,
info_hash: HashValue,
dn: String,
url: String,
}
impl Magnet {
pub fn new(urn: String, info_hash: HashValue, dn: String, url: String) -> Self {
Self {
urn,
info_hash,
dn,
url,
}
}
pub fn urn(&self) -> &String {
&self.urn
}
pub fn dn(&self) -> &String {
&self.dn
}
pub fn url(&self) -> &String {
&self.url
}
}
impl Display for Magnet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Tracker URL: {}\n Info Hash: {}",
self.url,
self.info_hash.hex(),
)
}
}

2
src/magnet/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod data;
pub mod parse;

64
src/magnet/parse.rs Normal file
View File

@ -0,0 +1,64 @@
use std::collections::HashMap;
use crate::encode::hash::HashValue;
use super::data::{Magnet, MagnetParseError};
//fn parse_xt(link: &str)
pub fn parse_magnet(link: &str) -> Result<Magnet, MagnetParseError> {
let magnet = link.split("magnet:?").collect::<Vec<&str>>();
if magnet.len() != 2 {
return Err(MagnetParseError::InvalidMagnet);
}
let magnet = magnet[1];
let mut magnet = magnet.split("&");
let Some(xt) = magnet.next() else {
return Err(MagnetParseError::MissingField("xt".to_string()));
};
let parts = xt.split("=").collect::<Vec<&str>>();
if parts.len() != 2 {
return Err(MagnetParseError::InvalidFieldFormat("xt".to_string()));
}
let xt_parts = parts[1].split(":").collect::<Vec<_>>();
if xt_parts.len() != 3 {
return Err(MagnetParseError::InvalidFieldFormat("xt".to_string()));
}
if xt_parts[0] != "urn" {
return Err(MagnetParseError::InvalidFieldFormat("xt".to_string()));
}
let urn = xt_parts[1].to_string();
let hash = HashValue::from_hex(xt_parts[2]);
let Some(dn_kv) = magnet.next() else {
return Err(MagnetParseError::MissingField("dn".to_string()));
};
let dn_kv = dn_kv.split("=").collect::<Vec<&str>>();
if dn_kv.len() != 2 {
return Err(MagnetParseError::InvalidFieldFormat("dn".to_string()));
}
if dn_kv[0] != "dn" {
return Err(MagnetParseError::InvalidFieldFormat("dn".to_string()));
}
let dn = parts[1].to_string();
let Some(url) = magnet.next() else {
return Err(MagnetParseError::MissingField("url".to_string()));
};
let decoded: HashMap<String, String> =
serde_urlencoded::from_str(&url).map_err(|_| MagnetParseError::InvalidMagnet)?;
if let Some(url_val) = decoded.get("tr") {
Ok(Magnet::new(urn, hash, dn, url_val.to_string()))
} else {
Err(MagnetParseError::MissingField("url".to_string()))
}
}

View File

@ -1,5 +1,6 @@
use bittorrent_starter_rust::{
encode::bedecode::decode_bencoded_single_value,
encode::{bedecode::decode_bencoded_single_value, hash::HashValue},
magnet::parse::parse_magnet,
ouput_gen::{display, display_ips, display_peer, set_logging_enabled},
tcp::{
downloader::{download_and_save_file, download_and_save_sing_piece},
@ -84,14 +85,24 @@ async fn run(command: &str, obj: &[String]) {
//.collect();
let _ = download_and_save_file(parsed_file, ips, obj[1].clone()).await;
}
"magnet_parse" => {
let magnet = &obj[0];
println!("{}", parse_magnet(magnet).unwrap());
}
_ => eprintln!("unknown command: {}", command),
}
}
#[tokio::main]
async fn main() {
set_logging_enabled(false);
//set_logging_enabled(false);
let args: Vec<String> = env::args().collect();
let command = &args[1];
run(command, &args[2..]).await;
}
pub fn main_f() {
let foo = "magnet:?xt=urn:btih:d69f91e6b2ae4c542468d1073a71d4ea13879a7f&dn=sample.torrent&tr=http%3A%2F%2Fbittorrent-test-tracker.codecrafters.io%2Fannounce";
println!("{:?}", parse_magnet(foo));
}