Storing torrent name in ManagedTorrentShared

This commit is contained in:
Igor Katson 2024-12-06 12:57:26 +00:00
parent 0a92cf1d65
commit 0fabb453aa
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
4 changed files with 55 additions and 6 deletions

View file

@ -209,10 +209,7 @@ impl Api {
let mut r = TorrentDetailsResponse { let mut r = TorrentDetailsResponse {
id: Some(id), id: Some(id),
info_hash: mgr.shared().info_hash.as_string(), info_hash: mgr.shared().info_hash.as_string(),
name: mgr name: mgr.name(),
.with_metadata(|r| r.info.name.as_ref().map(|n| n.to_string()))
.ok()
.flatten(),
output_folder: mgr output_folder: mgr
.shared() .shared()
.options .options
@ -249,6 +246,7 @@ impl Api {
Some(handle.id()), Some(handle.id()),
&info_hash, &info_hash,
handle.metadata.load().as_ref().map(|r| &r.info), handle.metadata.load().as_ref().map(|r| &r.info),
handle.name().as_deref(),
only_files.as_deref(), only_files.as_deref(),
output_folder, output_folder,
) )
@ -383,6 +381,7 @@ impl Api {
Some(id), Some(id),
&handle.info_hash(), &handle.info_hash(),
handle.metadata.load().as_ref().map(|r| &r.info), handle.metadata.load().as_ref().map(|r| &r.info),
handle.name().as_deref(),
handle.only_files().as_deref(), handle.only_files().as_deref(),
handle handle
.shared() .shared()
@ -419,6 +418,7 @@ impl Api {
None, None,
&info_hash, &info_hash,
Some(&info), Some(&info),
None,
only_files.as_deref(), only_files.as_deref(),
output_folder.to_string_lossy().into_owned().to_string(), output_folder.to_string_lossy().into_owned().to_string(),
) )
@ -429,6 +429,7 @@ impl Api {
Some(id), Some(id),
&handle.info_hash(), &handle.info_hash(),
handle.metadata.load().as_ref().map(|r| &r.info), handle.metadata.load().as_ref().map(|r| &r.info),
handle.name().as_deref(),
handle.only_files().as_deref(), handle.only_files().as_deref(),
handle handle
.shared() .shared()
@ -532,6 +533,7 @@ fn make_torrent_details(
id: Option<TorrentId>, id: Option<TorrentId>,
info_hash: &Id20, info_hash: &Id20,
info: Option<&TorrentMetaV1Info<ByteBufOwned>>, info: Option<&TorrentMetaV1Info<ByteBufOwned>>,
name: Option<&str>,
only_files: Option<&[usize]>, only_files: Option<&[usize]>,
output_folder: String, output_folder: String,
) -> Result<TorrentDetailsResponse> { ) -> Result<TorrentDetailsResponse> {
@ -564,7 +566,9 @@ fn make_torrent_details(
Ok(TorrentDetailsResponse { Ok(TorrentDetailsResponse {
id, id,
info_hash: info_hash.as_string(), info_hash: info_hash.as_string(),
name: info.and_then(|i| i.name.as_ref().map(|b| b.to_string())), name: name
.map(|s| s.to_owned())
.or_else(|| info.and_then(|i| i.name.as_ref().map(|b| b.to_string()))),
files: Some(files), files: Some(files),
output_folder, output_folder,
stats: None, stats: None,

View file

@ -477,6 +477,7 @@ struct InternalAddResult {
info_hash: Id20, info_hash: Id20,
metadata: Option<TorrentMetadata>, metadata: Option<TorrentMetadata>,
trackers: Vec<String>, trackers: Vec<String>,
name: Option<String>,
} }
impl Session { impl Session {
@ -899,6 +900,7 @@ impl Session {
info_hash, info_hash,
trackers: magnet.trackers, trackers: magnet.trackers,
metadata: None, metadata: None,
name: magnet.name,
} }
} }
other => { other => {
@ -943,6 +945,7 @@ impl Session {
torrent.info_bytes, torrent.info_bytes,
)?), )?),
trackers, trackers,
name: None,
} }
} }
}; };
@ -956,6 +959,7 @@ impl Session {
fn get_default_subfolder_for_torrent( fn get_default_subfolder_for_torrent(
&self, &self,
info: &TorrentMetaV1Info<ByteBufOwned>, info: &TorrentMetaV1Info<ByteBufOwned>,
magnet_name: Option<&str>,
) -> anyhow::Result<Option<PathBuf>> { ) -> anyhow::Result<Option<PathBuf>> {
let files = info let files = info
.iter_file_details()? .iter_file_details()?
@ -964,11 +968,23 @@ impl Session {
if files.len() < 2 { if files.len() < 2 {
return Ok(None); return Ok(None);
} }
fn check_valid(name: &str) -> anyhow::Result<()> {
if name.contains("/") || name.contains("\\") || name.contains("..") {
bail!("path traversal in torrent name detected")
}
Ok(())
}
if let Some(name) = &info.name { if let Some(name) = &info.name {
let s = let s =
std::str::from_utf8(name.as_slice()).context("invalid UTF-8 in torrent name")?; std::str::from_utf8(name.as_slice()).context("invalid UTF-8 in torrent name")?;
check_valid(s)?;
return Ok(Some(PathBuf::from(s))); return Ok(Some(PathBuf::from(s)));
}; };
if let Some(name) = magnet_name {
check_valid(name)?;
return Ok(Some(PathBuf::from(name)));
}
// Let the subfolder name be the longest filename // Let the subfolder name be the longest filename
let longest = files let longest = files
.iter() .iter()
@ -989,6 +1005,7 @@ impl Session {
info_hash, info_hash,
metadata, metadata,
trackers, trackers,
name,
} = add_res; } = add_res;
let peer_stream_permanent = !opts.paused && !opts.list_only; let peer_stream_permanent = !opts.paused && !opts.list_only;
@ -1045,7 +1062,7 @@ impl Session {
let output_folder = match (opts.output_folder, opts.sub_folder) { let output_folder = match (opts.output_folder, opts.sub_folder) {
(None, None) => self.output_folder.join( (None, None) => self.output_folder.join(
self.get_default_subfolder_for_torrent(&metadata.info)? self.get_default_subfolder_for_torrent(&metadata.info, name.as_deref())?
.unwrap_or_default(), .unwrap_or_default(),
), ),
(Some(o), None) => PathBuf::from(o), (Some(o), None) => PathBuf::from(o),
@ -1118,6 +1135,7 @@ impl Session {
}, },
connector: self.connector.clone(), connector: self.connector.clone(),
session: Arc::downgrade(self), session: Arc::downgrade(self),
magnet_name: name,
}); });
let initializing = Arc::new(TorrentStateInitializing::new( let initializing = Arc::new(TorrentStateInitializing::new(

View file

@ -141,6 +141,7 @@ pub struct TorrentMetadata {
pub info_bytes: Bytes, pub info_bytes: Bytes,
pub lengths: Lengths, pub lengths: Lengths,
pub file_infos: FileInfos, pub file_infos: FileInfos,
pub name: Option<String>,
} }
impl TorrentMetadata { impl TorrentMetadata {
@ -162,12 +163,18 @@ impl TorrentMetadata {
}) })
}) })
.collect::<anyhow::Result<Vec<FileInfo>>>()?; .collect::<anyhow::Result<Vec<FileInfo>>>()?;
let name = info
.name
.as_ref()
.and_then(|n| std::str::from_utf8(n.as_ref()).ok())
.map(|s| s.to_owned());
Ok(Self { Ok(Self {
info, info,
torrent_bytes, torrent_bytes,
info_bytes, info_bytes,
lengths, lengths,
file_infos, file_infos,
name,
}) })
} }
} }
@ -188,6 +195,9 @@ pub struct ManagedTorrentShared {
pub(crate) connector: Arc<StreamConnector>, pub(crate) connector: Arc<StreamConnector>,
pub(crate) storage_factory: BoxStorageFactory, pub(crate) storage_factory: BoxStorageFactory,
pub(crate) session: Weak<Session>, pub(crate) session: Weak<Session>,
// "dn" from magnet link
pub(crate) magnet_name: Option<String>,
} }
pub struct ManagedTorrent { pub struct ManagedTorrent {
@ -204,6 +214,13 @@ impl ManagedTorrent {
self.shared.id self.shared.id
} }
pub fn name(&self) -> Option<String> {
if let Some(m) = &*self.metadata.load() {
return m.name.clone().or_else(|| self.shared.magnet_name.clone());
}
self.shared.magnet_name.clone()
}
pub fn shared(&self) -> &ManagedTorrentShared { pub fn shared(&self) -> &ManagedTorrentShared {
&self.shared &self.shared
} }

View file

@ -9,6 +9,7 @@ pub struct Magnet {
id20: Option<Id20>, id20: Option<Id20>,
id32: Option<Id32>, id32: Option<Id32>,
pub trackers: Vec<String>, pub trackers: Vec<String>,
pub name: Option<String>,
select_only: Option<Vec<usize>>, select_only: Option<Vec<usize>>,
} }
@ -29,6 +30,7 @@ impl Magnet {
id20: Some(id20), id20: Some(id20),
id32: None, id32: None,
trackers, trackers,
name: None,
select_only, select_only,
} }
} }
@ -40,6 +42,7 @@ impl Magnet {
return Ok(Magnet { return Ok(Magnet {
id20: Some(id20), id20: Some(id20),
id32: None, id32: None,
name: None,
trackers: vec![], trackers: vec![],
select_only: None, select_only: None,
}); });
@ -52,6 +55,7 @@ impl Magnet {
let mut info_hash_found = false; let mut info_hash_found = false;
let mut id20: Option<Id20> = None; let mut id20: Option<Id20> = None;
let mut id32: Option<Id32> = None; let mut id32: Option<Id32> = None;
let mut name: Option<String> = None;
let mut trackers = Vec::<String>::new(); let mut trackers = Vec::<String>::new();
let mut files = Vec::<usize>::new(); let mut files = Vec::<usize>::new();
for (key, value) in url.query_pairs() { for (key, value) in url.query_pairs() {
@ -70,6 +74,11 @@ impl Magnet {
} }
} }
"tr" => trackers.push(value.into()), "tr" => trackers.push(value.into()),
"dn" => {
if !value.is_empty() {
name = Some(value.into_owned())
}
}
"so" => { "so" => {
// Process 'so' values, but silently ignore any which fail parsing // Process 'so' values, but silently ignore any which fail parsing
for file_desc in value.split(',') { for file_desc in value.split(',') {
@ -100,6 +109,7 @@ impl Magnet {
id20, id20,
id32, id32,
trackers, trackers,
name,
select_only: if files.is_empty() { None } else { Some(files) }, select_only: if files.is_empty() { None } else { Some(files) },
}), }),
false => { false => {