From b8ffdfc45b95604efe48f95724d51b9013538fd8 Mon Sep 17 00:00:00 2001 From: euzu Date: Thu, 10 Apr 2025 10:43:14 +0200 Subject: [PATCH] fixed clippy warnings --- Cargo.lock | 1 + Cargo.toml | 1 + src/api/api_utils.rs | 29 +++++++++-------- src/api/endpoints/hdhomerun_api.rs | 2 +- src/api/endpoints/hls_api.rs | 15 +++++++-- src/auth/user.rs | 8 ++--- src/model/config.rs | 2 +- src/processing/parser/hls.rs | 30 +++++++++++------- src/processing/parser/xmltv.rs | 50 ++++++++++++++++++------------ src/repository/user_repository.rs | 40 ++++++++++++------------ 10 files changed, 103 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da44e7414..d84836e3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,7 @@ dependencies = [ "url", "vergen", "winapi", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8d41df7dd..5392be6ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ paste = "1.0" tempfile = "3.19" ruzstd = "0" filetime = "0.2" +zeroize = "1.8.1" #[cfg(target_os = "macos")] libc = "0" #[cfg(target_os = "windows")] diff --git a/src/api/api_utils.rs b/src/api/api_utils.rs index 5ac94c915..08a2e8ac7 100644 --- a/src/api/api_utils.rs +++ b/src/api/api_utils.rs @@ -173,9 +173,9 @@ type StreamUrl = String; type ProviderName = String; enum StreamingOption { - CustomStream(ProviderStreamResponse), - AvailableStream(Option, StreamUrl), - GracePeriodStream(Option, StreamUrl), + Custom(ProviderStreamResponse), + Available(Option, StreamUrl), + GracePeriod(Option, StreamUrl), } pub struct StreamDetails { @@ -217,7 +217,7 @@ fn get_streaming_options(app_state: &AppState, stream_url: &str, input_opt: Opti let stream_response_params = match allocation { ProviderAllocation::Exhausted => { let stream = create_provider_connections_exhausted_stream(&app_state.config, &[]); - StreamingOption::CustomStream(stream) + StreamingOption::Custom(stream) } ProviderAllocation::Available(provider) | ProviderAllocation::GracePeriod(provider) => { @@ -229,15 +229,15 @@ fn get_streaming_options(app_state: &AppState, stream_url: &str, input_opt: Opti if matches!(allocation, ProviderAllocation::Available(_)) { - StreamingOption::AvailableStream(Some(provider), url) + StreamingOption::Available(Some(provider), url) } else { - StreamingOption::GracePeriodStream(Some(provider), url) + StreamingOption::GracePeriod(Some(provider), url) } } }; (stream_response_params, Some(input.headers.clone())) } else { - (StreamingOption::AvailableStream(None, stream_url.to_string()), None) + (StreamingOption::Available(None, stream_url.to_string()), None) } } @@ -246,9 +246,9 @@ async fn create_stream_response_details(app_state: &AppState, stream_options: &S item_type: PlaylistItemType, share_stream: bool) -> StreamDetails { let (stream_response_params, input_headers) = get_streaming_options(app_state, stream_url, input_opt); let config_grace_period_millis = app_state.config.reverse_proxy.as_ref().and_then(|r| r.stream.as_ref()).map_or_else(default_grace_period_millis, |s| s.grace_period_millis); - let grace_period_millis = if config_grace_period_millis > 0 && matches!(stream_response_params, StreamingOption::GracePeriodStream(_, _)) { config_grace_period_millis } else { 0 }; + let grace_period_millis = if config_grace_period_millis > 0 && matches!(stream_response_params, StreamingOption::GracePeriod(_, _)) { config_grace_period_millis } else { 0 }; match stream_response_params { - StreamingOption::CustomStream(provider_stream) => { + StreamingOption::Custom(provider_stream) => { let (stream, stream_info) = provider_stream; StreamDetails { stream, @@ -258,8 +258,8 @@ async fn create_stream_response_details(app_state: &AppState, stream_options: &S reconnect_flag: None, } } - StreamingOption::AvailableStream(provider_name, request_url) - | StreamingOption::GracePeriodStream(provider_name, request_url) => { + StreamingOption::Available(provider_name, request_url) + | StreamingOption::GracePeriod(provider_name, request_url) => { let parsed_url = Url::parse(&request_url); let ((stream, stream_info), reconnect_flag) = if let Ok(url) = parsed_url { if stream_options.pipe_provider_stream { @@ -303,6 +303,7 @@ async fn create_stream_response_details(app_state: &AppState, stream_options: &S } } +/// # Panics pub async fn stream_response(app_state: &AppState, stream_url: &str, req_headers: &HeaderMap, @@ -350,8 +351,7 @@ pub async fn stream_response(app_state: &AppState, response = response.header(key, value); } - let throttle_kbps = get_stream_throttle(app_state); - + let throttle_kbps = usize::try_from(get_stream_throttle(app_state)).unwrap_or_default(); let body_stream = if throttle_kbps > 0 && matches!(item_type, PlaylistItemType::Video | PlaylistItemType::Series | PlaylistItemType::SeriesInfo) { axum::body::Body::from_stream(ThrottledStream::new(stream.boxed(), throttle_kbps as usize)) } else { @@ -425,6 +425,7 @@ fn get_add_cache_content(res_url: &str, cache: &Arc) -> impl axum::response::IntoResponse + Send { if resource_url.is_empty() { return axum::http::StatusCode::NO_CONTENT.into_response(); @@ -483,6 +484,7 @@ pub fn separate_number_and_remainder(input: &str) -> (String, Option) { }) } +/// # Panics pub fn empty_json_list_response() -> impl axum::response::IntoResponse + Send { axum::response::Response::builder() .status(StatusCode::OK) @@ -509,6 +511,7 @@ pub fn get_username_from_auth_header( None } +/// # Panics pub fn redirect(url: &str) -> impl IntoResponse { axum::response::Response::builder() .status(StatusCode::FOUND) diff --git a/src/api/endpoints/hdhomerun_api.rs b/src/api/endpoints/hdhomerun_api.rs index 1918e797b..5e79f1ab9 100644 --- a/src/api/endpoints/hdhomerun_api.rs +++ b/src/api/endpoints/hdhomerun_api.rs @@ -224,7 +224,7 @@ async fn lineup_status() -> impl IntoResponse { async fn lineup_json(axum::extract::State(app_state): axum::extract::State>) -> impl IntoResponse { let cfg = Arc::clone(&app_state.app_state.config); if let Some((credentials, target)) = cfg.get_target_for_username(&app_state.device.t_username).await { - let use_output = target.get_hdhomerun_output().as_ref().and_then(|o| o.use_output.clone()); + let use_output = target.get_hdhomerun_output().as_ref().and_then(|o| o.use_output); let use_all = use_output.is_none(); let use_m3u = use_output.as_ref() == Some(&TargetType::M3u); let use_xtream = use_output.as_ref() == Some(&TargetType::Xtream); diff --git a/src/api/endpoints/hls_api.rs b/src/api/endpoints/hls_api.rs index 2ae553367..aaaaa67d5 100644 --- a/src/api/endpoints/hls_api.rs +++ b/src/api/endpoints/hls_api.rs @@ -5,7 +5,7 @@ use crate::api::model::streams::provider_stream::{create_custom_video_stream_res use crate::model::api_proxy::ProxyUserCredentials; use crate::model::config::{ConfigInput, TargetType}; use crate::model::playlist::{PlaylistItemType, XtreamCluster}; -use crate::processing::parser::hls::rewrite_hls; +use crate::processing::parser::hls::{rewrite_hls, RewriteHlsProps}; use crate::utils::network::request; use crate::utils::network::request::{is_hls_url, replace_url_extension, sanitize_sensitive_info, HLS_EXT}; use axum::response::IntoResponse; @@ -33,7 +33,16 @@ pub(in crate::api) async fn handle_hls_stream_request(app_state: &Arc, match request::download_text_content(Arc::clone(&app_state.http_client), input, &url, None).await { Ok(content) => { let hls_token = app_state.hls_cache.new_token(); - let (hls_entry, hls_content) = rewrite_hls(&server_info.get_base_url(), &content, hls_url, virtual_id, hls_token, user, &target_type, input.id); + let rewrite_hls_props = RewriteHlsProps { + base_url: &server_info.get_base_url(), + content: &content, + hls_url, + virtual_id, + token: hls_token, + target_type, + input_id: input.id, + }; + let (hls_entry, hls_content) = rewrite_hls(user, &rewrite_hls_props); app_state.hls_cache.add_entry(hls_entry).await; axum::response::Response::builder() .status(axum::http::StatusCode::OK) @@ -70,7 +79,7 @@ async fn hls_api_stream( let input = try_option_bad_request!(app_state.config.get_input_by_id(hls_entry.input_id), true, format!("Cant find input for target {target_name}, context {}, stream_id {virtual_id}", XtreamCluster::Live)); if is_hls_url(hls_url) { - return handle_hls_stream_request(&app_state, &user, hls_url, virtual_id, input, hls_entry.target_type.clone()).await.into_response(); + return handle_hls_stream_request(&app_state, &user, hls_url, virtual_id, input, hls_entry.target_type).await.into_response(); } // let (pli_url, input_name) = if hls_entry.target_type == TargetType::Xtream { diff --git a/src/auth/user.rs b/src/auth/user.rs index 0c19cefc7..ffbcd364a 100644 --- a/src/auth/user.rs +++ b/src/auth/user.rs @@ -1,4 +1,4 @@ -use std::ptr; +use zeroize::Zeroize; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct UserCredential { @@ -9,10 +9,6 @@ pub struct UserCredential { impl UserCredential { pub fn zeroize(&mut self) { - let password_ptr = self.password.as_mut_ptr(); - let password_len = self.password.len(); - unsafe { - ptr::write_bytes(password_ptr, 0, password_len); - } + self.password.zeroize(); } } diff --git a/src/model/config.rs b/src/model/config.rs index 8b2b5b526..f3c3c88af 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -69,7 +69,7 @@ use crate::utils::file::config_reader::csv_read_inputs; use crate::utils::network::request::{get_credentials_from_url, get_credentials_from_url_str}; use crate::utils::string_utils::get_trimmed_string; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)] pub enum TargetType { #[serde(rename = "m3u")] M3u, diff --git a/src/processing/parser/hls.rs b/src/processing/parser/hls.rs index 8bacf461d..8512892b8 100644 --- a/src/processing/parser/hls.rs +++ b/src/processing/parser/hls.rs @@ -8,36 +8,44 @@ use crate::utils::string_utils::replace_after_last_slash; pub const HLS_PREFIX: &str = "hls"; -pub fn rewrite_hls(base_url: &str, content: &str, hls_url: &str, virtual_id: u32, - token: u32, - user: &ProxyUserCredentials, - target_type: &TargetType, input_id: u16) -> (HlsEntry, String) { + +pub struct RewriteHlsProps<'a> { + pub base_url: &'a str, + pub content: &'a str, + pub hls_url: &'a str, + pub virtual_id: u32, + pub token: u32, + pub target_type: TargetType, + pub input_id: u16 +} + +pub fn rewrite_hls(user: &ProxyUserCredentials, props: &RewriteHlsProps ) -> (HlsEntry, String) { let username = &user.username; let password = &user.password; let mut chunk: u32 = 1; let mut chunks = HashMap::new(); let mut result = Vec::new(); - for line in content.lines() { + for line in props.content.lines() { if line.starts_with('#') { result.push(line.to_string()); } else { let url = if line.starts_with("http") { line.to_string() } else { - replace_after_last_slash(hls_url, line) + replace_after_last_slash(props.hls_url, line) }; chunks.insert(chunk, url); - result.push(format!("{base_url}/{HLS_PREFIX}/{token}/{username}/{password}/{virtual_id}/{chunk}")); + result.push(format!("{}/{HLS_PREFIX}/{}/{username}/{password}/{}/{chunk}", props.base_url, props.token, props.virtual_id)); chunk += 1; } } let hls = HlsEntry { ts: Instant::now(), - token, - target_type: target_type.clone(), - input_id, - virtual_id, + token: props.token, + target_type: props.target_type, + input_id: props.input_id, + virtual_id: props.virtual_id, chunk, chunks, }; diff --git a/src/processing/parser/xmltv.rs b/src/processing/parser/xmltv.rs index ba89a344f..186c70848 100644 --- a/src/processing/parser/xmltv.rs +++ b/src/processing/parser/xmltv.rs @@ -1,6 +1,6 @@ use crate::model::xmltv::{Epg, TVGuide, XmlTag, EPG_ATTRIB_CHANNEL, EPG_ATTRIB_ID, EPG_TAG_CHANNEL, EPG_TAG_DISPLAY_NAME, EPG_TAG_ICON, EPG_TAG_PROGRAMME, EPG_TAG_TV}; use crate::utils::compression::compressed_file_reader::CompressedFileReader; -use quick_xml::events::Event; +use quick_xml::events::{BytesStart, Event}; use quick_xml::Reader; use regex::Regex; use std::collections::{HashMap, HashSet}; @@ -94,25 +94,8 @@ where Ok(Event::Eof) => break, Ok(Event::Start(e)) => { let name = String::from_utf8_lossy(e.name().as_ref()).as_ref().to_owned(); - let (is_tv_tag, is_channel, is_program) = match name.as_str() { - EPG_TAG_TV => (true, false, false), - EPG_TAG_CHANNEL => (false, true, false), - EPG_TAG_PROGRAMME => (false, false, true), - _ => (false, false, false) - }; - let attributes = e.attributes().filter_map(Result::ok) - .filter_map(|a| { - let key = String::from_utf8_lossy(a.key.as_ref()).to_string(); - let mut value = String::from(a.unescape_value().unwrap().as_ref()); - if (is_channel && key == EPG_ATTRIB_ID) || (is_program && key == EPG_ATTRIB_CHANNEL) { - value = value.to_lowercase().to_string(); - } - if value.is_empty() { - None - } else { - Some((key, value)) - } - }).collect::>(); + let (is_tv_tag, is_channel, is_program) = get_tag_types(&name); + let attributes = collect_tag_attributes(&e, is_channel, is_program); let attribs = if attributes.is_empty() { None } else { Some(Arc::new(attributes)) }; let tag = XmlTag { name, @@ -193,6 +176,33 @@ where } } +fn get_tag_types(name: &str) -> (bool, bool, bool) { + let (is_tv_tag, is_channel, is_program) = match name { + EPG_TAG_TV => (true, false, false), + EPG_TAG_CHANNEL => (false, true, false), + EPG_TAG_PROGRAMME => (false, false, true), + _ => (false, false, false) + }; + (is_tv_tag, is_channel, is_program) +} + +fn collect_tag_attributes(e: &BytesStart, is_channel: bool, is_program: bool) -> HashMap { + let attributes = e.attributes().filter_map(Result::ok) + .filter_map(|a| { + let key = String::from_utf8_lossy(a.key.as_ref()).to_string(); + let mut value = String::from(a.unescape_value().unwrap().as_ref()); + if (is_channel && key == EPG_ATTRIB_ID) || (is_program && key == EPG_ATTRIB_CHANNEL) { + value = value.to_lowercase().to_string(); + } + if value.is_empty() { + None + } else { + Some((key, value)) + } + }).collect::>(); + attributes +} + pub fn flatten_tvguide(tv_guides: &[Epg]) -> Option { if tv_guides.is_empty() { None diff --git a/src/repository/user_repository.rs b/src/repository/user_repository.rs index 29b4391ff..8f20ed410 100644 --- a/src/repository/user_repository.rs +++ b/src/repository/user_repository.rs @@ -161,23 +161,23 @@ fn ensure_user_storage_path(cfg: &Config, username: &str) -> Option { } } -fn user_get_live_bouquet_path(user_storage_path: &Path, target: &TargetType) -> PathBuf { +fn user_get_live_bouquet_path(user_storage_path: &Path, target: TargetType) -> PathBuf { user_storage_path.join(PathBuf::from(format!("{}_{USER_LIVE_BOUQUET}", target.to_string().to_lowercase()))) } -fn user_get_vod_bouquet_path(user_storage_path: &Path, target: &TargetType) -> PathBuf { +fn user_get_vod_bouquet_path(user_storage_path: &Path, target: TargetType) -> PathBuf { user_storage_path.join(PathBuf::from(format!("{}_{USER_VOD_BOUQUET}", target.to_string().to_lowercase()))) } -fn user_get_series_bouquet_path(user_storage_path: &Path, target: &TargetType) -> PathBuf { +fn user_get_series_bouquet_path(user_storage_path: &Path, target: TargetType) -> PathBuf { user_storage_path.join(PathBuf::from(format!("{}_{USER_SERIES_BOUQUET}", target.to_string().to_lowercase()))) } async fn save_xtream_user_bouquet_for_target(config: &Config, target_name: &str, storage_path: &Path, cluster: XtreamCluster, bouquet: Option<&Vec>) -> Result<(), Error> { let bouquet_path = match cluster { - XtreamCluster::Live => user_get_live_bouquet_path(storage_path, &TargetType::Xtream), - XtreamCluster::Video => user_get_vod_bouquet_path(storage_path, &TargetType::Xtream), - XtreamCluster::Series => user_get_series_bouquet_path(storage_path, &TargetType::Xtream), + XtreamCluster::Live => user_get_live_bouquet_path(storage_path, TargetType::Xtream), + XtreamCluster::Video => user_get_vod_bouquet_path(storage_path, TargetType::Xtream), + XtreamCluster::Series => user_get_series_bouquet_path(storage_path, TargetType::Xtream), }; @@ -194,7 +194,7 @@ async fn save_xtream_user_bouquet_for_target(config: &Config, target_name: &str, Ok(()) } -fn save_m3u_user_bouquet_for_target(storage_path: &Path, target: &TargetType, cluster: XtreamCluster, bouquet: Option<&Vec>) -> Result<(), Error> { +fn save_m3u_user_bouquet_for_target(storage_path: &Path, target: TargetType, cluster: XtreamCluster, bouquet: Option<&Vec>) -> Result<(), Error> { let bouquet_path = match cluster { XtreamCluster::Live => user_get_live_bouquet_path(storage_path, target), XtreamCluster::Video => user_get_vod_bouquet_path(storage_path, target), @@ -218,9 +218,9 @@ async fn save_user_bouquet_for_target(config: &Config, target_name: &str, storag save_xtream_user_bouquet_for_target(config, target_name, storage_path, XtreamCluster::Video, bouquet.vod.as_ref()).await?; save_xtream_user_bouquet_for_target(config, target_name, storage_path, XtreamCluster::Series, bouquet.series.as_ref()).await?; } else { - save_m3u_user_bouquet_for_target(storage_path, &target, XtreamCluster::Live, bouquet.live.as_ref())?; - save_m3u_user_bouquet_for_target(storage_path, &target, XtreamCluster::Video, bouquet.vod.as_ref())?; - save_m3u_user_bouquet_for_target(storage_path, &target, XtreamCluster::Series, bouquet.series.as_ref())?; + save_m3u_user_bouquet_for_target(storage_path, target, XtreamCluster::Live, bouquet.live.as_ref())?; + save_m3u_user_bouquet_for_target(storage_path, target, XtreamCluster::Video, bouquet.vod.as_ref())?; + save_m3u_user_bouquet_for_target(storage_path, target, XtreamCluster::Series, bouquet.series.as_ref())?; } Ok(()) } @@ -253,9 +253,9 @@ fn convert_xtream_user_bouquet(bouquet_cluster: Option) -> Option Option { if let Some(storage_path) = get_user_storage_path(cfg, username) { if storage_path.exists() { - let live_content = load_user_bouquet_from_file(&user_get_live_bouquet_path(&storage_path, &target)).await; - let vod_content = load_user_bouquet_from_file(&user_get_vod_bouquet_path(&storage_path, &target)).await; - let series_content = load_user_bouquet_from_file(&user_get_series_bouquet_path(&storage_path, &target)).await; + let live_content = load_user_bouquet_from_file(&user_get_live_bouquet_path(&storage_path, target)).await; + let vod_content = load_user_bouquet_from_file(&user_get_vod_bouquet_path(&storage_path, target)).await; + let series_content = load_user_bouquet_from_file(&user_get_series_bouquet_path(&storage_path, target)).await; let (live, vod, series) = if target == TargetType::Xtream { (convert_xtream_user_bouquet(live_content), convert_xtream_user_bouquet(vod_content), @@ -273,7 +273,7 @@ pub async fn load_user_bouquet_as_json(cfg: &Config, username: &str, target: Tar None } -async fn user_get_cluster_bouquet(cfg: &Config, username: &str, target: &TargetType, cluster: XtreamCluster) -> Option { +async fn user_get_cluster_bouquet(cfg: &Config, username: &str, target: TargetType, cluster: XtreamCluster) -> Option { if let Some(storage_path) = get_user_storage_path(cfg, username) { if storage_path.exists() { return load_user_bouquet_from_file(&match cluster { @@ -286,15 +286,15 @@ async fn user_get_cluster_bouquet(cfg: &Config, username: &str, target: &TargetT None } -pub(crate) async fn user_get_live_bouquet(cfg: &Config, username: &str, target: &TargetType) -> Option { +pub(crate) async fn user_get_live_bouquet(cfg: &Config, username: &str, target: TargetType) -> Option { user_get_cluster_bouquet(cfg, username, target, XtreamCluster::Live).await } -pub(crate) async fn user_get_vod_bouquet(cfg: &Config, username: &str, target: &TargetType) -> Option { +pub(crate) async fn user_get_vod_bouquet(cfg: &Config, username: &str, target: TargetType) -> Option { user_get_cluster_bouquet(cfg, username, target, XtreamCluster::Video).await } -pub(crate) async fn user_get_series_bouquet(cfg: &Config, username: &str, target: &TargetType) -> Option { +pub(crate) async fn user_get_series_bouquet(cfg: &Config, username: &str, target: TargetType) -> Option { user_get_cluster_bouquet(cfg, username, target, XtreamCluster::Series).await } @@ -304,9 +304,9 @@ pub async fn user_get_bouquet_filter(config: &Config, username: &str, category_i } let bouquet = match cluster { - XtreamCluster::Live => user_get_live_bouquet(config, username, &target).await, - XtreamCluster::Video => user_get_vod_bouquet(config, username, &target).await, - XtreamCluster::Series => user_get_series_bouquet(config, username, &target).await, + XtreamCluster::Live => user_get_live_bouquet(config, username, target).await, + XtreamCluster::Video => user_get_vod_bouquet(config, username, target).await, + XtreamCluster::Series => user_get_series_bouquet(config, username, target).await, };