Make the /resolve_magnet HTTP endpoint return an actual torrent file, not info
This commit is contained in:
parent
cd0f38f0fb
commit
d54b67d2dc
6 changed files with 132 additions and 23 deletions
|
|
@ -1,4 +1,5 @@
|
|||
mod bencode_value;
|
||||
pub mod raw_value;
|
||||
mod serde_bencode_de;
|
||||
mod serde_bencode_ser;
|
||||
|
||||
|
|
|
|||
28
crates/bencode/src/raw_value.rs
Normal file
28
crates/bencode/src/raw_value.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use serde::Serialize;
|
||||
|
||||
pub struct RawValue<T>(pub T);
|
||||
|
||||
pub(crate) const TAG: &str = "::librqbit_bencode::RawValue";
|
||||
|
||||
impl<T> Serialize for RawValue<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
struct Wrapper<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> Serialize for Wrapper<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
serializer.serialize_newtype_struct(TAG, &Wrapper(self.0.as_ref()))
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ pub struct BencodeDeserializer<'de> {
|
|||
// This is a f**ing hack
|
||||
pub is_torrent_info: bool,
|
||||
pub torrent_info_digest: Option<[u8; 20]>,
|
||||
pub torrent_info_bytes: Option<&'de [u8]>,
|
||||
}
|
||||
|
||||
impl<'de> BencodeDeserializer<'de> {
|
||||
|
|
@ -20,6 +21,7 @@ impl<'de> BencodeDeserializer<'de> {
|
|||
parsing_key: false,
|
||||
is_torrent_info: false,
|
||||
torrent_info_digest: None,
|
||||
torrent_info_bytes: None,
|
||||
}
|
||||
}
|
||||
pub fn into_remaining(self) -> &'de [u8] {
|
||||
|
|
@ -542,9 +544,11 @@ impl<'a, 'de> serde::de::MapAccess<'de> for MapAccess<'a, 'de> {
|
|||
if self.de.is_torrent_info && self.de.field_context.as_slice() == [ByteBuf(b"info")] {
|
||||
let len = self.de.buf.as_ptr() as usize - buf_before.as_ptr() as usize;
|
||||
let mut hash = Sha1::new();
|
||||
hash.update(&buf_before[..len]);
|
||||
let torrent_info_bytes = &buf_before[..len];
|
||||
hash.update(torrent_info_bytes);
|
||||
let digest = hash.finish();
|
||||
self.de.torrent_info_digest = Some(digest)
|
||||
self.de.torrent_info_digest = Some(digest);
|
||||
self.de.torrent_info_bytes = Some(torrent_info_bytes);
|
||||
}
|
||||
self.de.field_context.pop();
|
||||
Ok(value)
|
||||
|
|
|
|||
|
|
@ -328,12 +328,18 @@ impl<'ser, W: std::io::Write> Serializer for &'ser mut BencodeSerializer<W> {
|
|||
|
||||
fn serialize_newtype_struct<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_value: &T,
|
||||
name: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: ?Sized + serde::Serialize,
|
||||
{
|
||||
if name == crate::raw_value::TAG {
|
||||
self.hack_no_bytestring_prefix = true;
|
||||
value.serialize(&mut *self)?;
|
||||
self.hack_no_bytestring_prefix = false;
|
||||
return Ok(());
|
||||
}
|
||||
Err(SerError::custom_with_ser(
|
||||
"bencode doesn't support newtype structs",
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -502,13 +502,23 @@ async fn create_tcp_listener(
|
|||
bail!("no free TCP ports in range {port_range:?}");
|
||||
}
|
||||
|
||||
fn torrent_file_from_info_and_bytes(
|
||||
info: &TorrentMetaV1Info<ByteBufOwned>,
|
||||
info_hash: &Id20,
|
||||
info_bytes: &[u8],
|
||||
trackers: &[String],
|
||||
) -> Bytes {
|
||||
todo!()
|
||||
fn torrent_file_from_info_bytes(info_bytes: &[u8], trackers: &[String]) -> anyhow::Result<Bytes> {
|
||||
#[derive(Serialize)]
|
||||
struct Tmp<'a> {
|
||||
announce: &'a str,
|
||||
#[serde(rename = "announce-list")]
|
||||
announce_list: &'a [&'a [String]],
|
||||
info: bencode::raw_value::RawValue<&'a [u8]>,
|
||||
}
|
||||
|
||||
let mut w = Vec::new();
|
||||
let v = Tmp {
|
||||
info: bencode::raw_value::RawValue(info_bytes),
|
||||
announce: trackers.first().map(|s| s.as_str()).unwrap_or(""),
|
||||
announce_list: &[trackers],
|
||||
};
|
||||
bencode_serialize_to_writer(&v, &mut w)?;
|
||||
Ok(w.into())
|
||||
}
|
||||
|
||||
pub(crate) struct CheckedIncomingConnection {
|
||||
|
|
@ -1003,12 +1013,10 @@ impl Session {
|
|||
let trackers = magnet.trackers.into_iter().unique().collect_vec();
|
||||
InternalAddResult {
|
||||
info_hash,
|
||||
torrent_bytes: torrent_file_from_info_and_bytes(
|
||||
&info,
|
||||
&info_hash,
|
||||
torrent_bytes: torrent_file_from_info_bytes(
|
||||
&info_bytes,
|
||||
&trackers,
|
||||
),
|
||||
)?,
|
||||
info,
|
||||
trackers,
|
||||
peer_rx: Some(rx),
|
||||
|
|
@ -1427,3 +1435,46 @@ impl tracker_comms::TorrentStatsProvider for PeerRxTorrentInfo {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
|
||||
use buffers::ByteBuf;
|
||||
use itertools::Itertools;
|
||||
use librqbit_core::torrent_metainfo::{torrent_from_bytes_ext, TorrentMetaV1};
|
||||
|
||||
use super::torrent_file_from_info_bytes;
|
||||
|
||||
#[test]
|
||||
fn test_torrent_file_from_info_and_bytes() {
|
||||
fn get_trackers(info: &TorrentMetaV1<ByteBuf>) -> Vec<String> {
|
||||
info.iter_announce()
|
||||
.filter_map(|t| std::str::from_utf8(t.as_ref()).ok().map(|t| t.to_owned()))
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
let orig_full_torrent =
|
||||
include_bytes!("../resources/ubuntu-21.04-desktop-amd64.iso.torrent");
|
||||
let parsed = torrent_from_bytes_ext::<ByteBuf>(&orig_full_torrent[..]).unwrap();
|
||||
let parsed_trackers = get_trackers(&parsed.meta);
|
||||
|
||||
let generated_torrent =
|
||||
torrent_file_from_info_bytes(parsed.info_bytes.as_ref(), &parsed_trackers).unwrap();
|
||||
{
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("/tmp/generated")
|
||||
.unwrap();
|
||||
f.write_all(&generated_torrent).unwrap();
|
||||
}
|
||||
let generated_parsed =
|
||||
torrent_from_bytes_ext::<ByteBuf>(generated_torrent.as_ref()).unwrap();
|
||||
assert_eq!(parsed.meta.info_hash, generated_parsed.meta.info_hash);
|
||||
assert_eq!(parsed.meta.info, generated_parsed.meta.info);
|
||||
assert_eq!(parsed.info_bytes, generated_parsed.info_bytes);
|
||||
assert_eq!(parsed_trackers, get_trackers(&generated_parsed.meta));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,37 @@ use crate::{hash_id::Id20, lengths::Lengths};
|
|||
pub type TorrentMetaV1Borrowed<'a> = TorrentMetaV1<ByteBuf<'a>>;
|
||||
pub type TorrentMetaV1Owned = TorrentMetaV1<ByteBufOwned>;
|
||||
|
||||
/// Parse torrent metainfo from bytes.
|
||||
pub fn torrent_from_bytes<'de, BufType: Deserialize<'de>>(
|
||||
pub struct ParsedTorrent<BufType> {
|
||||
/// The parsed torrent.
|
||||
pub meta: TorrentMetaV1<BufType>,
|
||||
|
||||
/// The raw bytes of the torrent's "info" dict.
|
||||
pub info_bytes: BufType,
|
||||
}
|
||||
|
||||
/// Parse torrent metainfo from bytes (includes additional fields).
|
||||
pub fn torrent_from_bytes_ext<'de, BufType: Deserialize<'de> + From<&'de [u8]>>(
|
||||
buf: &'de [u8],
|
||||
) -> anyhow::Result<TorrentMetaV1<BufType>> {
|
||||
) -> anyhow::Result<ParsedTorrent<BufType>> {
|
||||
let mut de = BencodeDeserializer::new_from_buf(buf);
|
||||
de.is_torrent_info = true;
|
||||
let mut t = TorrentMetaV1::deserialize(&mut de)?;
|
||||
t.info_hash = Id20::new(
|
||||
de.torrent_info_digest
|
||||
.ok_or_else(|| anyhow::anyhow!("programming error"))?,
|
||||
);
|
||||
Ok(t)
|
||||
let (digest, info_bytes) = match (de.torrent_info_digest, de.torrent_info_bytes) {
|
||||
(Some(digest), Some(info_bytes)) => (digest, info_bytes),
|
||||
_ => anyhow::bail!("programming error"),
|
||||
};
|
||||
t.info_hash = Id20::new(digest);
|
||||
Ok(ParsedTorrent {
|
||||
meta: t,
|
||||
info_bytes: BufType::from(info_bytes),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse torrent metainfo from bytes.
|
||||
pub fn torrent_from_bytes<'de, BufType: Deserialize<'de> + From<&'de [u8]>>(
|
||||
buf: &'de [u8],
|
||||
) -> anyhow::Result<TorrentMetaV1<BufType>> {
|
||||
torrent_from_bytes_ext(buf).map(|r| r.meta)
|
||||
}
|
||||
|
||||
/// A parsed .torrent file.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue