Skip to content

Commit

Permalink
fix: [#488] torrent without tracker keys for open tracker
Browse files Browse the repository at this point in the history
Tracker HTTP URL: http://localhost:7070

- announce: "http://localhost:7070".
- announce_list: "http://localhost:7070" and keeps the original URLs in the uploaded torrent.

Tracker UDP URL: udp://localhost:6969

- announce: "udp://localhost:7070".
- announce_list: "udp://localhost:7070" and keeps the original URLs in the uploaded torrent.

Tracker HTTP URL: http://localhost:7070

- announce: "http://localhost:7070/**KEY**".
- announce_list: "http://localhost:7070/**KEY**" and keeps the original URLs in the uploaded torrent.

Tracker UDP URL: udp://localhost:6969

- announce: "udp://localhost:7070/**KEY**".
- announce_list: "udp://localhost:7070/**KEY**" and keeps the original URLs in the uploaded torrent.

It returns an 505 error if it can't get the user's tracker keys.

TODO:

- The application should not start with close tracker and UDP url in the
  configuration.
- The API should return 503 instead of 500 when it can't connect to the
  tracker.
  • Loading branch information
josecelano committed Feb 23, 2024
1 parent 70329ea commit b41488b
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 77 deletions.
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ impl Default for TrackerMode {
}
}

impl TrackerMode {
#[must_use]
pub fn is_open(&self) -> bool {
matches!(self, TrackerMode::Public | TrackerMode::Whitelisted)
}
}

/// Configuration for the associated tracker.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tracker {
Expand Down
9 changes: 9 additions & 0 deletions src/models/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ impl TorrentResponse {
encoding: torrent_listing.encoding,
}
}

/// It adds the tracker URL in the first position of the tracker list.
pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) {
// Remove any existing instances of tracker_url
self.trackers.retain(|tracker| tracker != tracker_url);

// Insert tracker_url at the first position
self.trackers.insert(0, tracker_url.to_owned());
}
}

#[allow(clippy::module_name_repetitions)]
Expand Down
34 changes: 34 additions & 0 deletions src/models/torrent_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,45 @@ impl Torrent {
}
}

/// Includes the tracker URL a the main tracker in the torrent.
///
/// It will be the URL in the `announce` field and also the first URL in the
/// `announce_list`.
pub fn include_url_as_main_tracker(&mut self, tracker_url: &str) {
self.set_announce_to(tracker_url);
self.add_url_to_front_of_announce_list(tracker_url);
}

/// Sets the announce url to the tracker url.
pub fn set_announce_to(&mut self, tracker_url: &str) {
self.announce = Some(tracker_url.to_owned());
}

/// Adds a new tracker URL to the front of the `announce_list`, removes duplicates,
/// and cleans up any empty inner lists.
///
/// In practice, it's common for the `announce_list` to include the URL from
/// the `announce` field as one of its entries, often in the first tier,
/// to ensure that this primary tracker is always used. However, this is not
/// a strict requirement of the `BitTorrent` protocol; it's more of a
/// convention followed by some torrent creators for redundancy and to
/// ensure better availability of trackers.
pub fn add_url_to_front_of_announce_list(&mut self, tracker_url: &str) {
if let Some(list) = &mut self.announce_list {
// Remove the tracker URL from existing lists
for inner_list in list.iter_mut() {
inner_list.retain(|url| url != tracker_url);
}

// Prepend a new vector containing the tracker_url
let vec = vec![tracker_url.to_owned()];
list.insert(0, vec);

// Remove any empty inner lists
list.retain(|inner_list| !inner_list.is_empty());
}
}

/// Removes all other trackers if the torrent is private.
pub fn reset_announce_list_if_private(&mut self) {
if self.is_private() {
Expand Down
74 changes: 42 additions & 32 deletions src/services/torrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde_derive::{Deserialize, Serialize};

use super::category::DbCategoryRepository;
use super::user::DbUserRepository;
use crate::config::Configuration;
use crate::config::{Configuration, TrackerMode};
use crate::databases::database::{Database, Error, Sorting};
use crate::errors::ServiceError;
use crate::models::category::CategoryId;
Expand Down Expand Up @@ -257,24 +257,18 @@ impl Index {
let mut torrent = self.torrent_repository.get_by_info_hash(info_hash).await?;

let tracker_url = self.get_tracker_url().await;
let tracker_mode = self.get_tracker_mode().await;

// Add personal tracker url or default tracker url
match opt_user_id {
Some(user_id) => {
let personal_announce_url = self
.tracker_service
.get_personal_announce_url(user_id)
.await
.unwrap_or(tracker_url);
torrent.announce = Some(personal_announce_url.clone());
if let Some(list) = &mut torrent.announce_list {
let vec = vec![personal_announce_url];
list.insert(0, vec);
}
}
None => {
torrent.announce = Some(tracker_url);
}
// code-review: should we remove all tracker URLs in the `announce_list`
// when the tracker is not open?

if tracker_mode.is_open() {
torrent.include_url_as_main_tracker(&tracker_url);
} else if let Some(authenticated_user_id) = opt_user_id {
let personal_announce_url = self.tracker_service.get_personal_announce_url(authenticated_user_id).await?;
torrent.include_url_as_main_tracker(&personal_announce_url);
} else {
torrent.include_url_as_main_tracker(&tracker_url);
}

Ok(torrent)
Expand Down Expand Up @@ -358,24 +352,35 @@ impl Index {

// Add trackers

// code-review: duplicate logic. We have to check the same in the
// download torrent file endpoint. Here he have only one list of tracker
// like the `announce_list` in the torrent file.

torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?;

let tracker_url = self.get_tracker_url().await;
let tracker_mode = self.get_tracker_mode().await;

// add tracker url
match opt_user_id {
Some(user_id) => {
// if no user owned tracker key can be found, use default tracker url
let personal_announce_url = self
.tracker_service
.get_personal_announce_url(user_id)
.await
.unwrap_or(tracker_url);
// add personal tracker url to front of vec
torrent_response.trackers.insert(0, personal_announce_url);
}
None => {
torrent_response.trackers.insert(0, tracker_url);
if tracker_mode.is_open() {
torrent_response.include_url_as_main_tracker(&tracker_url);
} else {
// Add main tracker URL
match opt_user_id {
Some(user_id) => {
// If no user owned tracker key can be found, use default tracker url
// code-review: for downloading the torrent file it returns an error
// instead of defaulting to the default tracker URL.
let personal_announce_url = self
.tracker_service
.get_personal_announce_url(user_id)
.await
.unwrap_or(tracker_url);

torrent_response.include_url_as_main_tracker(&personal_announce_url);
}
None => {
torrent_response.include_url_as_main_tracker(&tracker_url);
}
}
}

Expand Down Expand Up @@ -513,6 +518,11 @@ impl Index {
let settings = self.configuration.settings.read().await;
settings.tracker.url.clone()
}

async fn get_tracker_mode(&self) -> TrackerMode {
let settings = self.configuration.settings.read().await;
settings.tracker.mode.clone()
}
}

pub struct DbTorrentRepository {
Expand Down
45 changes: 0 additions & 45 deletions tests/e2e/web/api/v1/contexts/torrent/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,15 +473,6 @@ mod for_guests {

mod for_authenticated_users {

use torrust_index::utils::parse_torrent::decode_torrent;
use torrust_index::web::api;

use crate::common::client::Client;
use crate::e2e::environment::TestEnv;
use crate::e2e::web::api::v1::contexts::torrent::asserts::{build_announce_url, get_user_tracker_key};
use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index;
use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user;

mod uploading_a_torrent {

use torrust_index::web::api;
Expand Down Expand Up @@ -779,42 +770,6 @@ mod for_authenticated_users {
}
}

#[tokio::test]
async fn it_should_allow_authenticated_users_to_download_a_torrent_with_a_personal_announce_url() {
let mut env = TestEnv::new();
env.start(api::Version::V1).await;

if !env.provides_a_tracker() {
println!("test skipped. It requires a tracker to be running.");
return;
}

// Given a previously uploaded torrent
let uploader = new_logged_in_user(&env).await;
let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await;

// And a logged in user who is going to download the torrent
let downloader = new_logged_in_user(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &downloader.token);

// When the user downloads the torrent
let response = client.download_torrent(&test_torrent.file_info_hash()).await;

let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent");

// Then the torrent should have the personal announce URL
let tracker_key = get_user_tracker_key(&downloader, &env)
.await
.expect("uploader should have a valid tracker key");

let tracker_url = env.server_settings().unwrap().tracker.url;

assert_eq!(
torrent.announce.unwrap(),
build_announce_url(&tracker_url, &Some(tracker_key))
);
}

mod and_non_admins {

use torrust_index::web::api;
Expand Down

0 comments on commit b41488b

Please sign in to comment.