diff --git a/src/errors.rs b/src/errors.rs index e8ef6712..b6f3385d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,6 +6,7 @@ use hyper::StatusCode; use crate::databases::database; use crate::models::torrent::MetadataError; +use crate::utils::parse_torrent::MetainfoFileDataError; pub type ServiceResult = Result; @@ -215,6 +216,16 @@ impl From for ServiceError { } } +impl From for ServiceError { + fn from(e: MetainfoFileDataError) -> Self { + eprintln!("{e}"); + match e { + MetainfoFileDataError::InvalidBencodeData => ServiceError::InvalidTorrentFile, + MetainfoFileDataError::InvalidTorrentPiecesLength => ServiceError::InvalidTorrentTitleLength, + } + } +} + #[must_use] pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { #[allow(clippy::match_same_arms)] diff --git a/src/services/torrent.rs b/src/services/torrent.rs index 7ae61848..dd13331f 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -17,7 +17,7 @@ use crate::models::torrent_file::{DbTorrent, Torrent, TorrentFile}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::user::UserId; use crate::tracker::statistics_importer::StatisticsImporter; -use crate::utils::parse_torrent; +use crate::utils::parse_torrent::decode_and_validate_torrent_file; use crate::{tracker, AsCSV}; pub struct Index { @@ -134,20 +134,10 @@ impl Index { let metadata = self.validate_and_build_metadata(&add_torrent_req).await?; - // Validate and build torrent file - - let original_info_hash = parse_torrent::calculate_info_hash(&add_torrent_req.torrent_buffer); - - let mut torrent = - parse_torrent::decode_torrent(&add_torrent_req.torrent_buffer).map_err(|_| ServiceError::InvalidTorrentFile)?; - - // Make sure that the pieces key has a length that is a multiple of 20 - if let Some(pieces) = torrent.info.pieces.as_ref() { - if pieces.as_ref().len() % 20 != 0 { - return Err(ServiceError::InvalidTorrentPiecesLength); - } - } + let (mut torrent, original_info_hash) = decode_and_validate_torrent_file(&add_torrent_req.torrent_buffer)?; + // Customize the announce URLs with the linked tracker URL + // and remove others if the torrent is private. torrent.set_announce_urls(&self.configuration).await; let canonical_info_hash = torrent.canonical_info_hash(); diff --git a/src/utils/parse_torrent.rs b/src/utils/parse_torrent.rs index 5644cd8d..e04c2549 100644 --- a/src/utils/parse_torrent.rs +++ b/src/utils/parse_torrent.rs @@ -1,5 +1,6 @@ use std::error; +use derive_more::{Display, Error}; use serde::{self, Deserialize, Serialize}; use serde_bencode::value::Value; use serde_bencode::{de, Error}; @@ -8,6 +9,43 @@ use sha1::{Digest, Sha1}; use crate::models::info_hash::InfoHash; use crate::models::torrent_file::Torrent; +#[derive(Debug, Display, PartialEq, Eq, Error)] +pub enum MetainfoFileDataError { + #[display(fmt = "Torrent data could not be decoded from the bencoded format.")] + InvalidBencodeData, + + #[display(fmt = "Torrent has an invalid pieces key length. It should be a multiple of 20.")] + InvalidTorrentPiecesLength, +} + +/// It decodes and validate an array of bytes containing a torrent file. +/// +/// It returns a tuple containing the decoded torrent and the original info hash. +/// The original info-hash migth not match the new one in the `Torrent` because +/// the info dictionary might have been modified. For example, ignoring some +/// non-standard fields. +/// +/// # Errors +/// +/// This function will return an error if +/// +/// - The torrent file is not a valid bencoded file. +/// - The pieces key has a length that is not a multiple of 20. +pub fn decode_and_validate_torrent_file(bytes: &[u8]) -> Result<(Torrent, InfoHash), MetainfoFileDataError> { + let original_info_hash = calculate_info_hash(bytes); + + let torrent = decode_torrent(bytes).map_err(|_| MetainfoFileDataError::InvalidBencodeData)?; + + // Make sure that the pieces key has a length that is a multiple of 20 + if let Some(pieces) = torrent.info.pieces.as_ref() { + if pieces.as_ref().len() % 20 != 0 { + return Err(MetainfoFileDataError::InvalidTorrentPiecesLength); + } + } + + Ok((torrent, original_info_hash)) +} + /// Decode a Torrent from Bencoded Bytes. /// /// # Errors