diff --git a/crates/bencode/src/bencode_value.rs b/crates/bencode/src/bencode_value.rs index 7e0ab98..352730e 100644 --- a/crates/bencode/src/bencode_value.rs +++ b/crates/bencode/src/bencode_value.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, marker::PhantomData}; +use std::{collections::HashMap, fmt::Display, marker::PhantomData}; use buffers::{ByteBuf, ByteBufOwned}; use bytes::Bytes; use clone_to_owned::CloneToOwned; -use serde::Deserializer; +use serde::{Deserialize, Deserializer, Serialize}; use crate::serde_bencode_de::from_bytes; @@ -136,6 +136,44 @@ where pub type BencodeValueBorrowed<'a> = BencodeValue>; pub type BencodeValueOwned = BencodeValue; +// A wrapper to deserialize dyn values as strings. +#[derive(PartialEq, Eq, Hash)] +pub struct AsDisplay(T); + +impl<'de, T> From<&'de [u8]> for AsDisplay +where + T: From<&'de [u8]>, +{ + fn from(value: &'de [u8]) -> Self { + Self(T::from(value)) + } +} + +impl Serialize for AsDisplay +where + T: Display, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{}", self.0)) + } +} + +impl<'de, T> Deserialize<'de> for AsDisplay +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = T::deserialize(deserializer)?; + Ok(AsDisplay(value)) + } +} + #[cfg(test)] mod tests { use crate::serde_bencode_ser::bencode_serialize_to_writer; diff --git a/crates/buffers/src/lib.rs b/crates/buffers/src/lib.rs index e62510c..814e843 100644 --- a/crates/buffers/src/lib.rs +++ b/crates/buffers/src/lib.rs @@ -42,6 +42,9 @@ impl<'a> std::fmt::Display for HexBytes<'a> { } fn debug_bytes(b: &[u8], f: &mut std::fmt::Formatter<'_>, debug_strings: bool) -> std::fmt::Result { + if b.is_empty() { + return Ok(()); + } if b.iter().all(|b| *b == 0) { return write!(f, "<{} bytes, all zeroes>", b.len()); } diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index 4fbeacd..49de3b4 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -3,6 +3,8 @@ use axum::body::Bytes; use axum::extract::{Path, Query, State}; use axum::response::IntoResponse; use axum::routing::{get, post}; +use bencode::AsDisplay; +use buffers::ByteBuf; use futures::future::BoxFuture; use futures::{FutureExt, TryStreamExt}; use http::{HeaderMap, HeaderValue, StatusCode}; @@ -190,6 +192,7 @@ impl HttpApi { async fn resolve_magnet( State(state): State, + inp_headers: HeaderMap, url: String, ) -> Result { let added = state @@ -219,7 +222,21 @@ impl HttpApi { )) } }; + let mut headers = HeaderMap::new(); + + if inp_headers + .get("Accept") + .and_then(|v| std::str::from_utf8(v.as_bytes()).ok()) + == Some("application/json") + { + let data = bencode::dyn_from_bytes::>(&content) + .context("error decoding .torrent file content")?; + let data = serde_json::to_string(&data).context("error serializing")?; + headers.insert("Content-Type", HeaderValue::from_static("application/json")); + return Ok((headers, data).into_response()); + } + headers.insert( "Content-Type", HeaderValue::from_static("application/x-bittorrent"), @@ -234,7 +251,7 @@ impl HttpApi { } } } - Ok((headers, content)) + Ok((headers, content).into_response()) } async fn torrent_playlist(