mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-31 11:12:36 +01:00
build: remove vendored OpenSSL dependency (#743)
* build: remove vendored OpenSSL dependency * remvoed unnecessary comments and clippy fixes
This commit is contained in:
@@ -193,6 +193,7 @@
|
||||
- **API User Category Selection**: Fixed API user category selection in the Web UI.
|
||||
- **Refactored Playlist And EPG Explorer**: Playlist Explorer and EPG Explorer have been refactored for improved reliability and UX.
|
||||
- HLS session info now reports accurate duration and total transferred data.
|
||||
- Removed open-ssl dependency
|
||||
|
||||
## ⚙️ New Settings
|
||||
|
||||
|
||||
Generated
+41
-70
@@ -8,6 +8,17 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -524,6 +535,16 @@ dependencies = [
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
@@ -763,6 +784,15 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -1219,21 +1249,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -2187,6 +2202,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@@ -2744,60 +2768,12 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.5.5+3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
@@ -4623,6 +4599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
name = "tuliprox"
|
||||
version = "3.3.27"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arc-swap",
|
||||
"async-compression",
|
||||
"axum",
|
||||
@@ -4635,6 +4612,7 @@ dependencies = [
|
||||
"clap",
|
||||
"crc32fast",
|
||||
"cron",
|
||||
"ctr",
|
||||
"dashmap",
|
||||
"deunicode",
|
||||
"enum-iterator",
|
||||
@@ -4663,7 +4641,6 @@ dependencies = [
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"notify",
|
||||
"openssl",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"path-clean",
|
||||
@@ -4817,12 +4794,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "9.1.0"
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ path-clean.workspace = true
|
||||
pest.workspace = true
|
||||
pest_derive.workspace = true
|
||||
enum-iterator.workspace = true
|
||||
openssl = { version = "0.10.76", features = ["vendored"] } #https://docs.rs/openssl/0.10.34/openssl/#vendored
|
||||
aes = "0.8.4"
|
||||
ctr = "0.9.2"
|
||||
deunicode.workspace = true
|
||||
mime = "0.3.17"
|
||||
log.workspace = true
|
||||
|
||||
@@ -240,11 +240,11 @@ impl ConfigFile {
|
||||
Ok(Some(api_proxy)) => {
|
||||
app_state.app_config.set_api_proxy(api_proxy)?;
|
||||
let paths = app_state.app_config.paths.load();
|
||||
info!("Loaded Api Proxy File: {:?}", &paths.api_proxy_file_path);
|
||||
info!("Loaded Api Proxy File: {:?}", paths.api_proxy_file_path);
|
||||
}
|
||||
Ok(None) => {
|
||||
let paths = app_state.app_config.paths.load();
|
||||
info!("Could not load Api Proxy File: {:?}", &paths.api_proxy_file_path);
|
||||
info!("Could not load Api Proxy File: {:?}", paths.api_proxy_file_path);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to load api-proxy file {err}");
|
||||
|
||||
@@ -597,7 +597,7 @@ async fn download_file(
|
||||
}
|
||||
Err(err) => DownloadExecutionResult::Failed(format!(
|
||||
"Error while creating directory for file: {} {}",
|
||||
&file_download.file_dir.to_str().unwrap_or("?"),
|
||||
file_download.file_dir.to_str().unwrap_or("?"),
|
||||
err
|
||||
)),
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ fn create_device(app_state: &Arc<HdHomerunAppState>) -> Option<Device> {
|
||||
} else {
|
||||
error!(
|
||||
"Failed to get credentials for username: {} for device: {} ",
|
||||
&app_state.device.t_username, &app_state.device.name
|
||||
app_state.device.t_username, app_state.device.name
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ pub(in crate::api) async fn handle_hls_stream_request(
|
||||
if custom_stream_response.as_ref().and_then(|c| c.channel_unavailable.as_ref()).is_some() {
|
||||
let url = format!(
|
||||
"{}/{CUSTOM_VIDEO_PREFIX}/{}/{}/{}.ts",
|
||||
&server_info.get_base_url(),
|
||||
server_info.get_base_url(),
|
||||
user.username,
|
||||
user.password,
|
||||
CustomVideoStreamType::ChannelUnavailable
|
||||
|
||||
@@ -10,8 +10,7 @@ use axum::{body::Body, http::Request, response::IntoResponse};
|
||||
use base64::Engine;
|
||||
use log::{debug, error};
|
||||
use lol_html::{element, RewriteStrSettings};
|
||||
//use base64::engine::general_purpose;
|
||||
use openssl::rand::rand_bytes;
|
||||
use rand::{rngs::OsRng, RngCore, TryRngCore};
|
||||
use serde_json::json;
|
||||
use shared::{
|
||||
model::{TokenResponse, UserCredential, TOKEN_NO_AUTH},
|
||||
@@ -21,7 +20,6 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
//use openssl::sha::{sha256};
|
||||
use tower::{Service, ServiceExt};
|
||||
use tower_http::services::ServeFile;
|
||||
|
||||
@@ -238,25 +236,11 @@ async fn index(
|
||||
|
||||
// ContentSecurityPolicy nonce
|
||||
let mut rnd = [0u8; 32];
|
||||
if let Err(e) = rand_bytes(&mut rnd) {
|
||||
error!("Failed to generate random bytes for nonce: {e}");
|
||||
// Fallback: without further manipulation back
|
||||
return try_unwrap_body!(axum::response::Response::builder()
|
||||
.header(axum::http::header::CONTENT_TYPE, mime::TEXT_HTML_UTF_8.as_ref())
|
||||
.body(new_content));
|
||||
if OsRng.try_fill_bytes(&mut rnd).is_err() {
|
||||
rand::rng().fill_bytes(&mut rnd);
|
||||
}
|
||||
let nonce_b64 = base64::engine::general_purpose::STANDARD.encode(rnd);
|
||||
|
||||
// let hash = sha256(&rnd);
|
||||
// let nonce_b64 = general_purpose::STANDARD_NO_PAD.encode(hash);
|
||||
|
||||
// Insert calculated nonce
|
||||
// let script_tag = r#"<script type="module">"#;
|
||||
// if new_content.contains(script_tag) {
|
||||
// let new_tag = format!(r#"<script type="module" nonce="{nonce_b64}">"#);
|
||||
// new_content = new_content.replacen(script_tag, &new_tag, 1);
|
||||
// }
|
||||
|
||||
new_content = inject_nonce_with_parser(new_content, &nonce_b64);
|
||||
|
||||
let mut builder = axum::response::Response::builder()
|
||||
|
||||
@@ -1417,7 +1417,7 @@ async fn xtream_player_api(fingerprint: &Fingerprint, api_req: UserApiRequest, a
|
||||
skip_series,
|
||||
xtream_load_rewrite_playlist(XtreamCluster::Series, app_state, &target, category_id, &user).await
|
||||
),
|
||||
_ => Some(Err(TuliproxError::ApiXtream(format!("Unknown api call: {action} for target: {}", &target.name)))),
|
||||
_ => Some(Err(TuliproxError::ApiXtream(format!("Unknown api call: {action} for target: {}", target.name)))),
|
||||
};
|
||||
|
||||
match result {
|
||||
@@ -1432,7 +1432,7 @@ async fn xtream_player_api(fingerprint: &Fingerprint, api_req: UserApiRequest, a
|
||||
.body(axum::body::Body::from_stream(content_stream)))
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed response for xtream target: {} action: {} error: {}", &target.name, action, err);
|
||||
error!("Failed response for xtream target: {} action: {} error: {}", target.name, action, err);
|
||||
get_user_info(&user, app_state).await.map_or_else(
|
||||
|| axum::http::StatusCode::SERVICE_UNAVAILABLE.into_response(),
|
||||
|info| axum::response::Json(info).into_response(),
|
||||
|
||||
@@ -615,7 +615,7 @@ pub async fn start_server(app_config: Arc<AppConfig>, targets: Arc<ProcessTarget
|
||||
}
|
||||
|
||||
let web_ui_path = cfg.web_ui.as_ref().and_then(|c| c.path.as_ref()).cloned().unwrap_or_default();
|
||||
infos.push(format!("Server running: http://{}:{}", &cfg.api.host, &cfg.api.port));
|
||||
infos.push(format!("Server running: http://{}:{}", cfg.api.host, cfg.api.port));
|
||||
for info in &infos {
|
||||
info!("{info}");
|
||||
}
|
||||
|
||||
@@ -1222,7 +1222,7 @@ struct ProcessTaskOutcome {
|
||||
impl InputWorker {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn run(mut self) {
|
||||
debug!("Metadata worker started for input {}", &self.input_name);
|
||||
debug!("Metadata worker started for input {}", self.input_name);
|
||||
|
||||
let mut processed_vod_count: usize = 0;
|
||||
let mut processed_series_count: usize = 0;
|
||||
|
||||
@@ -227,7 +227,7 @@ impl LibraryProcessor {
|
||||
can_extract_thumbnails: bool,
|
||||
) -> Result<ProcessAction, String> {
|
||||
match group {
|
||||
MediaGroup::Movie { file: _, .. } => {
|
||||
MediaGroup::Movie { .. } => {
|
||||
self.process_movie(group, existing_map, force_rescan, can_probe, can_extract_thumbnails).await
|
||||
}
|
||||
MediaGroup::Series { show_key: _, episodes: _ } => {
|
||||
|
||||
@@ -234,13 +234,13 @@ impl LibraryScanner {
|
||||
let path = Path::new(&scan_directory.path);
|
||||
|
||||
if !fs::try_exists(path).await.unwrap_or(false) {
|
||||
warn!("Directory does not exist or is not readable: {}", &scan_directory.path);
|
||||
warn!("Directory does not exist or is not readable: {}", scan_directory.path);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let dir_metadata = fs::metadata(path).await?;
|
||||
if !dir_metadata.is_dir() {
|
||||
warn!("Path is not a directory: {}", &scan_directory.path);
|
||||
warn!("Path is not a directory: {}", scan_directory.path);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -231,14 +231,14 @@ async fn print_info(app_config: &AppConfig) {
|
||||
let paths = <Arc<ArcSwap<ConfigPaths>> as Access<ConfigPaths>>::load(&app_config.paths);
|
||||
info!("Current time: {}", chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S"));
|
||||
info!("Temp dir: {}", tempfile::env::temp_dir().display());
|
||||
info!("Storage dir: {}", &config.storage_dir);
|
||||
info!("Config dir: {}", &paths.config_path);
|
||||
info!("Config file: {}", &paths.config_file_path);
|
||||
info!("Source file: {}", &paths.sources_file_path);
|
||||
info!("Api Proxy File: {}", &paths.api_proxy_file_path);
|
||||
info!("Mapping path: {}", &paths.mapping_file_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
info!("Template path: {}", &paths.template_file_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
info!("Resources path: {}", &paths.custom_stream_response_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
info!("Storage dir: {}", config.storage_dir);
|
||||
info!("Config dir: {}", paths.config_path);
|
||||
info!("Config file: {}", paths.config_file_path);
|
||||
info!("Source file: {}", paths.sources_file_path);
|
||||
info!("Api Proxy File: {}", paths.api_proxy_file_path);
|
||||
info!("Mapping path: {}", paths.mapping_file_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
info!("Template path: {}", paths.template_file_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
info!("Resources path: {}", paths.custom_stream_response_path.as_ref().map_or_else(|| "not used", |v| v.as_str()));
|
||||
|
||||
if let Some(mapping_paths) = paths.mapping_files_used.as_ref() {
|
||||
for mapping_path in mapping_paths {
|
||||
|
||||
@@ -99,7 +99,7 @@ impl WebAuthConfig {
|
||||
PathBuf::from(&userfile_name)
|
||||
};
|
||||
if !utils::path_exists(&userfile_path) {
|
||||
return Err(TuliproxError::Config(format!("Could not find userfile {}", &userfile_name)));
|
||||
return Err(TuliproxError::Config(format!("Could not find userfile {userfile_name}")));
|
||||
}
|
||||
|
||||
let Ok(file) = File::open(&userfile_path) else {
|
||||
|
||||
@@ -99,13 +99,11 @@ fn token_till(stack: &mut String, it: &mut std::str::Chars, stop_char: char, sta
|
||||
#[inline]
|
||||
fn skip_digit(it: &mut std::str::Chars) -> Option<char> {
|
||||
loop {
|
||||
match it.next() {
|
||||
Some(c) => {
|
||||
if !(c == '-' || c == '+' || c.is_ascii_digit()) {
|
||||
return Some(c);
|
||||
}
|
||||
{
|
||||
let c = it.next()?;
|
||||
if !(c == '-' || c == '+' || c.is_ascii_digit()) {
|
||||
return Some(c);
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ pub async fn process_group_watch(app_config: &Arc<AppConfig>, client: &reqwest::
|
||||
handle_watch_notification(app_config, client, &added_difference, &removed_difference, target_name, &pl.title).await;
|
||||
}
|
||||
} else {
|
||||
error!("failed to load watch_file {}", &path.to_str().unwrap_or_default());
|
||||
error!("failed to load watch_file {}", path.to_str().unwrap_or_default());
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
@@ -45,13 +45,13 @@ pub async fn process_group_watch(app_config: &Arc<AppConfig>, client: &reqwest::
|
||||
match save_watch_tree(save_path, &new_tree).await {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!("failed to write watch_file {}: {}", save_path.to_str().unwrap_or_default(), err);
|
||||
error!("failed to write watch_file {}: {err}", save_path.to_str().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("failed to write watch_file {}", &watch_filename);
|
||||
error!("failed to write watch_file {watch_filename}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ pub async fn process_playlist_epg(fp: &mut FetchedPlaylist<'_>, epg: &mut Vec<Ep
|
||||
id_cache.collect_epg_id(fp);
|
||||
|
||||
if id_cache.is_empty() && !id_cache.smart_match_enabled {
|
||||
debug!("No epg ids found for input {}", &fp.input.name);
|
||||
debug!("No epg ids found for input {}", fp.input.name);
|
||||
} else {
|
||||
assign_channel_epg(epg, fp, &mut id_cache).await;
|
||||
}
|
||||
|
||||
@@ -1106,7 +1106,7 @@ async fn process_playlist_for_target(
|
||||
|
||||
if new_playlist.is_empty() {
|
||||
step.stop("");
|
||||
info!("Playlist is empty: {}", &target.name);
|
||||
info!("Playlist is empty: {}", target.name);
|
||||
Ok(())
|
||||
} else {
|
||||
// Process Trakt categories
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn m3u_get_storage_path(cfg: &Config, target_name: &str) -> Option<PathBuf>
|
||||
pub async fn ensure_m3u_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliproxError> {
|
||||
if let Some(path) = m3u_get_storage_path(cfg, target_name) {
|
||||
if tokio::fs::create_dir_all(&path).await.is_err() {
|
||||
let msg = format!("Failed to save m3u data, can't create directory {}", &path.display());
|
||||
let msg = format!("Failed to save m3u data, can't create directory {}", path.display());
|
||||
return Err(TuliproxError::RepositoryM3u(msg));
|
||||
}
|
||||
Ok(path)
|
||||
@@ -337,7 +337,7 @@ pub async fn m3u_get_item_for_stream_id(
|
||||
|
||||
let cfg: &AppConfig = &app_state.app_config;
|
||||
let target_path = get_target_storage_path(&cfg.config.load(), target.name.as_str())
|
||||
.ok_or_else(|| string_to_io_error(format!("Could not find path for target {}", &target.name)))?;
|
||||
.ok_or_else(|| string_to_io_error(format!("Could not find path for target {}", target.name)))?;
|
||||
let m3u_path = m3u_get_file_path_for_db(&target_path);
|
||||
let file_lock = cfg.file_locks.read_lock(&m3u_path).await;
|
||||
let m3u_path_clone = m3u_path.clone();
|
||||
|
||||
@@ -333,7 +333,7 @@ async fn load_target_id_mapping_as_tree(app_config: &AppConfig, target_path: &Pa
|
||||
})
|
||||
.await
|
||||
.map_err(|e| TuliproxError::Config(format!("Blocking task failed: {e}")))?
|
||||
.map_err(|err| TuliproxError::Config(format!("Could not find path for target {} err:{err}", &target_name)))
|
||||
.map_err(|err| TuliproxError::Config(format!("Could not find path for target {target_name} err:{err}")))
|
||||
}
|
||||
|
||||
async fn load_xtream_playlist_as_tree(app_config: &AppConfig, storage_path: &Path, cluster: XtreamCluster) -> BPlusTree<u32, XtreamPlaylistItem> {
|
||||
@@ -353,7 +353,7 @@ async fn load_xtream_playlist_as_tree(app_config: &AppConfig, storage_path: &Pat
|
||||
async fn load_id_mapping_target_storage(app_config: &AppConfig, target: &ConfigTarget) -> Result<BPlusTree<VirtualId, VirtualIdRecord>, TuliproxError> {
|
||||
let config = app_config.config.load();
|
||||
let target_path = get_target_storage_path(&config, target.name.as_str()).ok_or_else(||
|
||||
TuliproxError::Config(format!("Could not find path for target {}", &target.name)))?;
|
||||
TuliproxError::Config(format!("Could not find path for target {}", target.name)))?;
|
||||
|
||||
load_target_id_mapping_as_tree(app_config, &target_path, target).await
|
||||
}
|
||||
@@ -362,7 +362,7 @@ async fn load_xtream_target_storage(app_config: &AppConfig, target: &ConfigTarge
|
||||
let config = app_config.config.load();
|
||||
|
||||
let storage_path = xtream_get_storage_path(&config, target.name.as_str()).ok_or_else(||
|
||||
TuliproxError::Config(format!("Could not find path for target {} xtream output", &target.name)))?;
|
||||
TuliproxError::Config(format!("Could not find path for target {} xtream output", target.name)))?;
|
||||
|
||||
let live_storage = load_xtream_playlist_as_tree(app_config, &storage_path, XtreamCluster::Live).await;
|
||||
let vod_storage = load_xtream_playlist_as_tree(app_config, &storage_path, XtreamCluster::Video).await;
|
||||
@@ -378,7 +378,7 @@ async fn load_xtream_target_storage(app_config: &AppConfig, target: &ConfigTarge
|
||||
async fn load_m3u_target_storage(app_config: &AppConfig, target: &ConfigTarget) -> Result<PlaylistM3uStorage, TuliproxError> {
|
||||
let config = app_config.config.load();
|
||||
let target_path = get_target_storage_path(&config, target.name.as_str()).ok_or_else(||
|
||||
TuliproxError::Config(format!("Could not find path for target {}", &target.name)))?;
|
||||
TuliproxError::Config(format!("Could not find path for target {}", target.name)))?;
|
||||
|
||||
let m3u_path = m3u_get_file_path_for_db(&target_path);
|
||||
let file_lock = app_config.file_locks.read_lock(&m3u_path).await;
|
||||
|
||||
@@ -1207,7 +1207,7 @@ async fn delete_empty_dirs_from_tree(root_path: &Path, tree_nodes: HashMap<PathB
|
||||
for node in tree_stack.into_iter().rev() {
|
||||
if !node.has_files && !node.is_root {
|
||||
if let Err(err) = remove_dir(&node.path).await {
|
||||
trace!("Could not delete empty dir: {}, {err}", &node.path.display());
|
||||
trace!("Could not delete empty dir: {}, {err}", node.path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ impl Drop for TargetIdMapping {
|
||||
fn drop(&mut self) {
|
||||
if self.has_pending_changes() {
|
||||
if let Err(err) = self.persist() {
|
||||
error!("Failed to persist target id mapping {} err:{err}", &self.path.display());
|
||||
error!("Failed to persist target id mapping {} err:{err}", self.path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ pub async fn backup_api_user_db_file(cfg: &AppConfig, path: &Path) {
|
||||
let copy_result = tokio::fs::copy(path, &backup_path).await;
|
||||
drop(lock);
|
||||
if let Err(err) = copy_result {
|
||||
error!("Could not backup file {}:{}", &backup_path.to_str().unwrap_or("?"), err);
|
||||
error!("Could not backup file {}:{}", backup_path.to_str().unwrap_or("?"), err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl XtreamPlaylistIterator {
|
||||
if let Some(storage_path) = xtream_get_storage_path(&config, target.name.as_str()) {
|
||||
let xtream_path = xtream_get_file_path(&storage_path, cluster);
|
||||
if !xtream_path.exists() {
|
||||
return Err(TuliproxError::Config(format!("No {cluster} entries found for target {}", &target.name)));
|
||||
return Err(TuliproxError::Config(format!("No {cluster} entries found for target {}", target.name)));
|
||||
}
|
||||
// Hold iter_lock for the stream lifetime (LockedReceiverStream), and bg_lock for the background reader.
|
||||
let iter_lock = app_config.file_locks.read_lock(&xtream_path).await;
|
||||
@@ -129,7 +129,7 @@ impl XtreamPlaylistIterator {
|
||||
|
||||
Ok(Self { inner: LockedReceiverStream::new(rx, iter_lock) })
|
||||
} else {
|
||||
Err(TuliproxError::Config(format!("Failed to find xtream storage for target {}", &target.name)))
|
||||
Err(TuliproxError::Config(format!("Failed to find xtream storage for target {}", target.name)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ pub async fn ensure_xtream_storage_path(cfg: &Config, target_name: &str) -> Resu
|
||||
if tokio::fs::create_dir_all(&path).await.is_err() {
|
||||
let msg = format!(
|
||||
"Failed to save xtream data, can't create directory {}",
|
||||
&path.display()
|
||||
path.display()
|
||||
);
|
||||
return Err(TuliproxError::RepositoryXtream(msg));
|
||||
}
|
||||
@@ -578,8 +578,8 @@ pub async fn xtream_get_item_for_stream_id(
|
||||
|
||||
let app_config: &AppConfig = &app_state.app_config;
|
||||
let config = app_config.config.load();
|
||||
let target_path = get_target_storage_path(&config, target.name.as_str()).ok_or_else(|| string_to_io_error(format!("Could not find path for target {}", &target.name)))?;
|
||||
let storage_path = xtream_get_storage_path(&config, target.name.as_str()).ok_or_else(|| string_to_io_error(format!("Could not find path for target {} xtream output", &target.name)))?;
|
||||
let target_path = get_target_storage_path(&config, target.name.as_str()).ok_or_else(|| string_to_io_error(format!("Could not find path for target {}", target.name)))?;
|
||||
let storage_path = xtream_get_storage_path(&config, target.name.as_str()).ok_or_else(|| string_to_io_error(format!("Could not find path for target {} xtream output", target.name)))?;
|
||||
{
|
||||
let result = if let Some(cluster) = xtream_cluster {
|
||||
xtream_read_item_for_stream_id(app_config, virtual_id, &storage_path, cluster).await
|
||||
|
||||
@@ -1,67 +1,85 @@
|
||||
use aes::Aes128;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use openssl::symm::{Cipher, Crypter, Mode};
|
||||
use rand::{RngCore, rngs::OsRng, TryRngCore};
|
||||
use ctr::cipher::{KeyIvInit, StreamCipher};
|
||||
use rand::{rngs::OsRng, RngCore, TryRngCore};
|
||||
use shared::error::TuliproxError;
|
||||
use shared::utils::encode_base64_string;
|
||||
|
||||
type Aes128Ctr = ctr::Ctr128BE<Aes128>;
|
||||
|
||||
pub fn encode_base64_hash(text: &str) -> String {
|
||||
let hash = blake3::hash(text.as_bytes());
|
||||
encode_base64_string(hash.as_bytes())
|
||||
}
|
||||
|
||||
pub fn obscure_text(secret: &[u8;16], url: &str) -> Result<String, TuliproxError> {
|
||||
fn apply_aes_128_ctr(secret: &[u8; 16], iv: &[u8], data: &mut [u8]) -> Result<(), TuliproxError> {
|
||||
let mut cipher = Aes128Ctr::new_from_slices(secret, iv)
|
||||
.map_err(|_| TuliproxError::Crypto("Can't create AES-CTR cipher".to_string()))?;
|
||||
cipher.apply_keystream(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn obscure_text(secret: &[u8; 16], url: &str) -> Result<String, TuliproxError> {
|
||||
let mut iv = [0u8; 16];
|
||||
if OsRng.try_fill_bytes(&mut iv).is_err() {
|
||||
rand::rng().fill_bytes(&mut iv);
|
||||
}
|
||||
|
||||
// AES-CTR
|
||||
let cipher = Cipher::aes_128_ctr();
|
||||
let mut crypter = Crypter::new(cipher, Mode::Encrypt, secret, Some(&iv)).map_err(|_| TuliproxError::Crypto("Can't create cipher".to_string()))?;
|
||||
let mut buf = vec![0u8; url.len() + cipher.block_size()];
|
||||
let mut count = crypter.update(url.as_bytes(), &mut buf).map_err(|_| TuliproxError::Crypto("Can't update encryption".to_string()))?;
|
||||
count += crypter.finalize(&mut buf[count..]).map_err(|_| TuliproxError::Crypto("Can't finalize encryption".to_string()))?;
|
||||
buf.truncate(count);
|
||||
let mut ciphertext = url.as_bytes().to_vec();
|
||||
apply_aes_128_ctr(secret, &iv, &mut ciphertext)?;
|
||||
|
||||
// IV + Ciphertext -> URL-safe Base64
|
||||
let mut out = Vec::with_capacity(iv.len() + buf.len());
|
||||
// IV + Ciphertext -> URL-safe Base64. Keep this format stable for existing resource links.
|
||||
let mut out = Vec::with_capacity(iv.len() + ciphertext.len());
|
||||
out.extend_from_slice(&iv);
|
||||
out.extend_from_slice(&buf);
|
||||
out.extend_from_slice(&ciphertext);
|
||||
Ok(general_purpose::URL_SAFE_NO_PAD.encode(out))
|
||||
}
|
||||
|
||||
pub fn deobscure_text(secret: &[u8; 16], encoded: &str) -> Result<String, TuliproxError> {
|
||||
let data = general_purpose::URL_SAFE_NO_PAD
|
||||
.decode(encoded)
|
||||
.map_err(|_| TuliproxError::Crypto("Can't decode base64".to_string()))?;
|
||||
|
||||
pub fn deobscure_text(secret: &[u8;16], encoded: &str) -> Result<String, TuliproxError> {
|
||||
let data = general_purpose::URL_SAFE_NO_PAD.decode(encoded).map_err(|_| TuliproxError::Crypto("Can't decode base64".to_string()))?;
|
||||
|
||||
if data.len() < 16 {
|
||||
return Err(TuliproxError::Crypto("Token too short to contain IV".to_string()));
|
||||
}
|
||||
if data.len() < 16 {
|
||||
return Err(TuliproxError::Crypto("Token too short to contain IV".to_string()));
|
||||
}
|
||||
|
||||
let (iv, ciphertext) = data.split_at(16);
|
||||
let mut plaintext = ciphertext.to_vec();
|
||||
apply_aes_128_ctr(secret, iv, &mut plaintext)?;
|
||||
|
||||
let cipher = Cipher::aes_128_ctr();
|
||||
let mut crypter = Crypter::new(cipher, Mode::Decrypt, secret, Some(iv)).map_err(|_| TuliproxError::Crypto("Can't create decrypt cipher".to_string()))?;
|
||||
let mut buf = vec![0u8; ciphertext.len() + cipher.block_size()];
|
||||
let mut count = crypter.update(ciphertext, &mut buf).map_err(|_| TuliproxError::Crypto("Can't decrypt".to_string()))?;
|
||||
count += crypter.finalize(&mut buf[count..]).map_err(|_| TuliproxError::Crypto("Can't finalize decrypt".to_string()))?;
|
||||
buf.truncate(count);
|
||||
|
||||
String::from_utf8(buf).map_err(|_| TuliproxError::Crypto("Can't create utf8 string from decrypted".to_string()))
|
||||
String::from_utf8(plaintext).map_err(|_| TuliproxError::Crypto("Can't create utf8 string from decrypted".to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::crypto_utils::{obscure_text, deobscure_text};
|
||||
use rand::{Rng};
|
||||
use crate::utils::crypto_utils::{apply_aes_128_ctr, deobscure_text, obscure_text};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use rand::Rng;
|
||||
|
||||
#[test]
|
||||
fn test_obscure() {
|
||||
let secret: [u8; 16] = rand::rng().random(); // Random IV (AES-CBC 16 Bytes)
|
||||
let secret: [u8; 16] = rand::rng().random();
|
||||
let plain = "hello world";
|
||||
let encrypted = obscure_text(&secret, plain).unwrap();
|
||||
let decrypted = deobscure_text(&secret, &encrypted).unwrap();
|
||||
|
||||
assert_eq!(decrypted, plain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_ctr_format_matches_existing_openssl_tokens() {
|
||||
let secret: [u8; 16] = std::array::from_fn(|i| i as u8);
|
||||
let iv: [u8; 16] = std::array::from_fn(|i| (i + 16) as u8);
|
||||
let plain = "hello world";
|
||||
let expected_token = "EBESExQVFhcYGRobHB0eH2-bgxiO9XQB4mKK";
|
||||
|
||||
let mut ciphertext = plain.as_bytes().to_vec();
|
||||
apply_aes_128_ctr(&secret, &iv, &mut ciphertext).unwrap();
|
||||
let mut token_bytes = iv.to_vec();
|
||||
token_bytes.extend_from_slice(&ciphertext);
|
||||
|
||||
assert_eq!(general_purpose::URL_SAFE_NO_PAD.encode(token_bytes), expected_token);
|
||||
assert_eq!(deobscure_text(&secret, expected_token).unwrap(), plain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,13 +768,13 @@ where
|
||||
match fs::copy(&path, &backup_path).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!("Could not backup file {}:{err}", &backup_path.to_str().unwrap_or("?"));
|
||||
error!("Could not backup file {}: {err}", backup_path.to_str().unwrap_or("?"));
|
||||
}
|
||||
}
|
||||
info!("Saving file to {}", &path.to_str().unwrap_or("?"));
|
||||
info!("Saving file to {}", path.to_str().unwrap_or("?"));
|
||||
}
|
||||
|
||||
let parent_dir = path.parent().ok_or_else(|| { TuliproxError::Config(format!("Could not write file {}: missing parent directory", &path.to_str().unwrap_or("?")))})?;
|
||||
let parent_dir = path.parent().ok_or_else(|| { TuliproxError::Config(format!("Could not write file {}: missing parent directory", path.to_str().unwrap_or("?")))})?;
|
||||
|
||||
let dest_file_name = path
|
||||
.file_name()
|
||||
@@ -784,7 +784,7 @@ where
|
||||
let mut tmp_path = parent_dir.to_path_buf();
|
||||
tmp_path.push(format!(".{dest_file_name}.tmp-{}-{}", std::process::id(), Local::now().timestamp_nanos_opt().unwrap_or_default()));
|
||||
|
||||
fs::write(&tmp_path, serialized).await.map_err(|err| { TuliproxError::Config(format!("Could not write temp file {}: {err}", &tmp_path.to_str().unwrap_or("?")))})?;
|
||||
fs::write(&tmp_path, serialized).await.map_err(|err| { TuliproxError::Config(format!("Could not write temp file {}: {err}", tmp_path.to_str().unwrap_or("?")))})?;
|
||||
|
||||
match fs::rename(&tmp_path, &path).await {
|
||||
Ok(()) => Ok(()),
|
||||
@@ -803,8 +803,8 @@ where
|
||||
let _ = fs::remove_file(&tmp_path).await;
|
||||
Err(TuliproxError::Config(format!(
|
||||
"Could not replace file {} with {}: {err}",
|
||||
&path.to_str().unwrap_or("?"),
|
||||
&tmp_path.to_str().unwrap_or("?")
|
||||
path.to_str().unwrap_or("?"),
|
||||
tmp_path.to_str().unwrap_or("?")
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ fn apply_logger_with_context(context: &LoggerContext, config_log_level: Option<&
|
||||
}
|
||||
log::set_max_level(effective_max_level);
|
||||
info!("Log timezone system localtime (TZ)");
|
||||
info!("Log Level {}", &log_levels.join(", "));
|
||||
info!("Log Level {}", log_levels.join(", "));
|
||||
}
|
||||
|
||||
pub fn init_logger(user_log_level: Option<&str>, config_file: &str) {
|
||||
|
||||
@@ -108,7 +108,7 @@ impl LRUResourceCache {
|
||||
{ // insert_to_cache
|
||||
let mut path = self.cache_dir.clone();
|
||||
path.push(&file_name);
|
||||
trace!("Added file to cache: {}", &path.to_string_lossy());
|
||||
trace!("Added file to cache: {}", path.to_string_lossy());
|
||||
let gen = self.next_generation;
|
||||
self.next_generation = self.next_generation.wrapping_add(1);
|
||||
self.cache.insert(key.clone(), (path.clone(), mime_type, file_size, gen));
|
||||
@@ -144,7 +144,7 @@ impl LRUResourceCache {
|
||||
|
||||
fn insert_to_cache(&mut self, key: String, mime_type: Option<String>, file_size: usize) -> PathBuf {
|
||||
let path = self.get_store_path(&key, mime_type.as_deref());
|
||||
debug!("Added file to cache: {}", &path.to_string_lossy());
|
||||
debug!("Added file to cache: {}", path.to_string_lossy());
|
||||
let gen = self.next_generation;
|
||||
self.next_generation = self.next_generation.wrapping_add(1);
|
||||
self.cache.insert(key.clone(), (path.clone(), mime_type, file_size, gen));
|
||||
|
||||
@@ -826,12 +826,12 @@ pub async fn get_input_text_content(
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to download input '{}': {}",
|
||||
&input.name,
|
||||
input.name,
|
||||
sanitize_sensitive_info(e.to_string().as_str())
|
||||
);
|
||||
Err(TuliproxError::RepositoryNetwork(format!(
|
||||
"Failed to download input '{}': {}",
|
||||
&input.name,
|
||||
input.name,
|
||||
sanitize_sensitive_info(e.to_string().as_str())
|
||||
)))
|
||||
}
|
||||
@@ -890,12 +890,12 @@ pub async fn get_input_text_content_as_stream(
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to download input '{}': {}",
|
||||
&input.name,
|
||||
input.name,
|
||||
sanitize_sensitive_info(e.to_string().as_str())
|
||||
);
|
||||
Err(TuliproxError::RepositoryNetwork(format!(
|
||||
"Failed to download input '{}': {}",
|
||||
&input.name,
|
||||
input.name,
|
||||
sanitize_sensitive_info(e.to_string().as_str())
|
||||
)))
|
||||
}
|
||||
@@ -1654,7 +1654,7 @@ pub fn create_client_with_redirect(cfg: &AppConfig, redirect_policy: Policy) ->
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Invalid proxy URL '{}': {e}", &proxy_cfg.url);
|
||||
error!("Invalid proxy URL '{}': {e}", proxy_cfg.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ pub async fn get_xtream_stream_info(client: &reqwest::Client,
|
||||
if let Ok(content) = get_xtream_stream_info_content(app_config, client, &input_source, false).await {
|
||||
if content.is_empty() {
|
||||
return Err(TuliproxError::ApiXtream(format!("Provider returned no response for stream with id: {}/{}/{}",
|
||||
target.name.replace(' ', "_").as_str(), &cluster, pli.get_virtual_id())));
|
||||
target.name.replace(' ', "_").as_str(), cluster, pli.get_virtual_id())));
|
||||
}
|
||||
if let Some(provider_id) = pli.get_provider_id() {
|
||||
match cluster {
|
||||
@@ -107,7 +107,7 @@ pub async fn get_xtream_stream_info(client: &reqwest::Client,
|
||||
|
||||
// persist input info
|
||||
if let Err(err) = persist_input_vod_info(&app_state.app_config, &storage_path, cluster, &input.name, provider_id, &video_stream_props).await {
|
||||
error!("Failed to persist video stream for input {}: {err}", &input.name);
|
||||
error!("Failed to persist video stream for input {}: {err}", input.name);
|
||||
}
|
||||
|
||||
// update target playlist
|
||||
@@ -143,7 +143,7 @@ pub async fn get_xtream_stream_info(client: &reqwest::Client,
|
||||
if let Ok(storage_path) = get_input_storage_path(&input.name, storage_dir).await {
|
||||
// update input db
|
||||
if let Err(err) = persists_input_series_info(app_config, &storage_path, cluster, &input.name, provider_id, &series_stream_props).await {
|
||||
error!("Failed to persist series info for input {}: {err}", &input.name);
|
||||
error!("Failed to persist series info for input {}: {err}", input.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ pub async fn get_xtream_stream_info(client: &reqwest::Client,
|
||||
let config = &app_state.app_config.config.load();
|
||||
match get_target_storage_path(config, target.name.as_str()) {
|
||||
None => {
|
||||
error!("Failed to get target storage path {}. Can't save episodes", &target.name);
|
||||
error!("Failed to get target storage path {}. Can't save episodes", target.name);
|
||||
}
|
||||
Some(target_path) => {
|
||||
let mut in_memory_updates = Vec::new();
|
||||
@@ -243,7 +243,7 @@ pub async fn get_xtream_stream_info(client: &reqwest::Client,
|
||||
}
|
||||
|
||||
Err(TuliproxError::ApiXtream(format!("Can't find stream with id: {}/{}/{}",
|
||||
target.name.replace(' ', "_").as_str(), &cluster, pli.get_virtual_id())))
|
||||
target.name.replace(' ', "_").as_str(), cluster, pli.get_virtual_id())))
|
||||
}
|
||||
|
||||
fn xtream_resolve_stream_info(app_state: &Arc<AppState>, user: &ProxyUserCredentials,
|
||||
@@ -273,7 +273,7 @@ pub(crate) fn get_skip_cluster(input: &ConfigInput) -> Vec<XtreamCluster> {
|
||||
skip_cluster.push(XtreamCluster::Series);
|
||||
}
|
||||
if skip_cluster.len() == 3 {
|
||||
info!("You have skipped all sections from xtream input {}", &input.name);
|
||||
info!("You have skipped all sections from xtream input {}", input.name);
|
||||
}
|
||||
skip_cluster
|
||||
}
|
||||
@@ -288,7 +288,7 @@ pub async fn xtream_login(app_config: &Arc<AppConfig>, client: &reqwest::Client,
|
||||
content
|
||||
} else {
|
||||
let input_source_account_info =
|
||||
input.with_url(format!("{}&action={}", &input.url, crate::model::XC_ACTION_GET_ACCOUNT_INFO));
|
||||
input.with_url(format!("{}&action={}", input.url, crate::model::XC_ACTION_GET_ACCOUNT_INFO));
|
||||
match request::get_input_json_content(app_config, client, &input_source_account_info, None, false).await {
|
||||
Ok(content) => content,
|
||||
Err(err) => {
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ ARG RUST_TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
# Install build dependencies for Debian (trixie)
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
pkg-config libssl-dev musl-tools && \
|
||||
pkg-config musl-tools && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -152,7 +152,7 @@ ENV TZ=${TZ}
|
||||
# 'build-base' contains gcc, make, etc.
|
||||
# 'gdb' is required for the debugger (lldb is often more complicated in Alpine repos)
|
||||
# 'ffmpeg' is required for the probe feature
|
||||
RUN apk add --no-cache bash build-base openssl-dev perl lldb gdb ffmpeg
|
||||
RUN apk add --no-cache bash build-base perl lldb gdb ffmpeg
|
||||
|
||||
# Update Rust toolchain and add the necessary target
|
||||
RUN rustup update && rustup target add $RUST_TARGET
|
||||
|
||||
@@ -22,7 +22,7 @@ FROM ghcr.io/cross-rs/x86_64-unknown-linux-musl:main AS rust-build
|
||||
# Get target architecture
|
||||
ARG RUST_TARGET
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends pkg-config musl-tools libssl-dev
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends pkg-config musl-tools
|
||||
|
||||
# Update Rust toolchain and add necessary target
|
||||
RUN rustup update && rustup target add $RUST_TARGET
|
||||
@@ -156,9 +156,8 @@ ENV RUST_TARGET=${RUST_TARGET}
|
||||
ENV TZ=${TZ}
|
||||
|
||||
# Install build dependencies and the lldb debugger for Alpine
|
||||
# Added 'perl' which is required to build the native OpenSSL library.
|
||||
# Added ffmpeg for probing
|
||||
RUN apk add --no-cache bash build-base openssl-dev lldb gdb perl ffmpeg
|
||||
RUN apk add --no-cache bash build-base lldb gdb perl ffmpeg
|
||||
|
||||
# Update Rust toolchain and add the necessary target
|
||||
RUN rustup update && rustup target add $RUST_TARGET
|
||||
|
||||
@@ -58,7 +58,7 @@ impl EventService {
|
||||
pub fn unsubscribe(&self, sub_id: usize) { self.subscribers.borrow_mut().remove(&sub_id); }
|
||||
|
||||
pub fn broadcast(&self, msg: EventMessage) {
|
||||
for (_, cb) in self.subscribers.borrow().iter() {
|
||||
for cb in self.subscribers.borrow().values() {
|
||||
cb(msg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +128,9 @@ impl Filter {
|
||||
};
|
||||
if log_enabled!(Level::Trace) {
|
||||
if is_match {
|
||||
trace!("Match found: {rewc:?} {} => {field}='{value}'", &rewc.restr);
|
||||
trace!("Match found: {rewc:?} {} => {field}='{value}'", rewc.restr);
|
||||
} else {
|
||||
trace!("Match failed: {self}: {rewc:?} {} => {field}='{value}'", &rewc.restr);
|
||||
trace!("Match failed: {self}: {rewc:?} {} => {field}='{value}'", rewc.restr);
|
||||
}
|
||||
}
|
||||
is_match
|
||||
@@ -337,14 +337,14 @@ fn get_parser_expression(
|
||||
Err(err) => errors.push(err.to_string()),
|
||||
}
|
||||
}
|
||||
Rule::comparison | Rule::expr => match get_parser_expression(pair, templates, errors) {
|
||||
Ok(expr) => handle_expr!(bop, uop, stmts, expr),
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
Rule::expr_group => match get_parser_expression(pair.into_inner().next().unwrap(), templates, errors) {
|
||||
Ok(expr) => handle_expr!(bop, uop, stmts, Filter::Group(Box::new(expr))),
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
Rule::comparison | Rule::expr => {
|
||||
let expr = get_parser_expression(pair, templates, errors)?;
|
||||
handle_expr!(bop, uop, stmts, expr)
|
||||
}
|
||||
Rule::expr_group => {
|
||||
let expr = get_parser_expression(pair.into_inner().next().unwrap(), templates, errors)?;
|
||||
handle_expr!(bop, uop, stmts, Filter::Group(Box::new(expr)))
|
||||
}
|
||||
Rule::not => {
|
||||
uop = Some(UnaryOperator::Not);
|
||||
}
|
||||
@@ -448,14 +448,12 @@ pub fn get_filter(filter_text: &str, templates: Option<&[PatternTemplate]>) -> R
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
errors.push(format!("Unable to parse filter: {}", &filter_text));
|
||||
errors.push(format!("Unable to parse filter: {}", filter_text));
|
||||
return Err(TuliproxError::FilterParse(errors.join("\n").to_string()));
|
||||
}
|
||||
|
||||
result.map_or_else(
|
||||
|| Err(TuliproxError::FilterParse(format!("Unable to parse filter: {}", &filter_text))),
|
||||
Ok,
|
||||
)
|
||||
result
|
||||
.map_or_else(|| Err(TuliproxError::FilterParse(format!("Unable to parse filter: {}", filter_text))), Ok)
|
||||
}
|
||||
Err(err) => Err(TuliproxError::FilterParse(format!("{err}"))),
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ impl ApiProxyConfigDto {
|
||||
errors.push(err.to_string());
|
||||
}
|
||||
if name_set.contains(server.name.as_str()) {
|
||||
errors.push(format!("Non-unique server info name found {}", &server.name));
|
||||
errors.push(format!("Non-unique server info name found {}", server.name));
|
||||
} else {
|
||||
name_set.insert(server.name.clone());
|
||||
}
|
||||
@@ -126,7 +126,7 @@ impl ApiProxyConfigDto {
|
||||
errors.push(err.to_string());
|
||||
}
|
||||
if usernames.contains(&user.username) {
|
||||
errors.push(format!("Non unique username found {}", &user.username));
|
||||
errors.push(format!("Non unique username found {}", user.username));
|
||||
} else {
|
||||
usernames.insert(user.username.to_string());
|
||||
}
|
||||
@@ -136,8 +136,8 @@ impl ApiProxyConfigDto {
|
||||
} else if tokens.contains(token) {
|
||||
errors.push(format!(
|
||||
"Non unique user token found {} for user {}",
|
||||
&user.token.as_ref().map_or_else(String::new, ToString::to_string),
|
||||
&user.username
|
||||
user.token.as_ref().map_or_else(String::new, ToString::to_string),
|
||||
user.username
|
||||
));
|
||||
} else {
|
||||
tokens.insert(token.to_string());
|
||||
@@ -148,7 +148,7 @@ impl ApiProxyConfigDto {
|
||||
if !&self.server.iter().any(|server_info| server_info.name.eq(server_info_name)) {
|
||||
errors.push(format!(
|
||||
"No server info with name {} found for user {}",
|
||||
server_info_name, &user.username
|
||||
server_info_name, user.username
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl ConfigRenameDto {
|
||||
}
|
||||
let resolved_pattern = apply_templates_to_pattern_single(&self.pattern, templates)?;
|
||||
if let Err(err) = crate::model::REGEX_CACHE.get_or_compile(&resolved_pattern) {
|
||||
return Err(TuliproxError::RegexCompile(format!("{} {err}", &resolved_pattern)));
|
||||
return Err(TuliproxError::RegexCompile(format!("{} {err}", resolved_pattern)));
|
||||
}
|
||||
self.t_pattern = Some(resolved_pattern);
|
||||
Ok(())
|
||||
|
||||
+10
-12
@@ -226,18 +226,16 @@ impl MappingDto {
|
||||
if !valid_property!(def.field.as_str(), COUNTER_FIELDS) {
|
||||
return Err(TuliproxError::Config(format!("Invalid counter field {}", def.field)));
|
||||
}
|
||||
match get_filter(&def.filter, templates) {
|
||||
Ok(flt) => {
|
||||
counters.push(MappingCounter {
|
||||
filter: flt,
|
||||
field: def.field.clone(),
|
||||
concat: def.concat.clone(),
|
||||
modifier: def.modifier,
|
||||
value: Arc::new(AtomicU32::new(def.value)),
|
||||
padding: def.padding,
|
||||
});
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
{
|
||||
let flt = get_filter(&def.filter, templates)?;
|
||||
counters.push(MappingCounter {
|
||||
filter: flt,
|
||||
field: def.field.clone(),
|
||||
concat: def.concat.clone(),
|
||||
modifier: def.modifier,
|
||||
value: Arc::new(AtomicU32::new(def.value)),
|
||||
padding: def.padding,
|
||||
});
|
||||
}
|
||||
}
|
||||
self.t_counter = Some(counters);
|
||||
|
||||
@@ -816,7 +816,7 @@ impl SeriesStreamProperties {
|
||||
SeriesStreamProperties {
|
||||
name: info.info.name.clone(),
|
||||
category_id: info.info.category_id.parse::<u32>().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse category_id {}", &info.info.category_id);
|
||||
warn!("Failed to parse category_id {}", info.info.category_id);
|
||||
0
|
||||
}),
|
||||
series_id,
|
||||
@@ -829,11 +829,11 @@ impl SeriesStreamProperties {
|
||||
last_modified: Some(info.info.last_modified.clone()),
|
||||
plot: Some(info.info.plot.clone()),
|
||||
rating: info.info.rating.parse::<f64>().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse rating {}", &info.info.rating);
|
||||
warn!("Failed to parse rating {}", info.info.rating);
|
||||
0.0
|
||||
}),
|
||||
rating_5based: info.info.rating_5based.parse::<f64>().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse rating_5based {}", &info.info.rating_5based);
|
||||
warn!("Failed to parse rating_5based {}", info.info.rating_5based);
|
||||
0.0
|
||||
}),
|
||||
release_date: Some(info.info.release_date.clone()),
|
||||
@@ -849,7 +849,7 @@ impl SeriesStreamProperties {
|
||||
name: s.name.clone(),
|
||||
season_number: s.season_number,
|
||||
episode_count: s.episode_count.parse::<u32>().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse episode_count {}", &s.episode_count);
|
||||
warn!("Failed to parse episode_count {}", s.episode_count);
|
||||
0
|
||||
}),
|
||||
overview: s.overview.clone(),
|
||||
@@ -870,7 +870,7 @@ impl SeriesStreamProperties {
|
||||
.flat_map(|(_, list)| list.iter())
|
||||
.map(|e| SeriesStreamDetailEpisodeProperties {
|
||||
id: e.id.parse::<u32>().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse episode id {}", &e.id);
|
||||
warn!("Failed to parse episode id {}", e.id);
|
||||
0
|
||||
}),
|
||||
episode_num: e.episode_num,
|
||||
|
||||
Reference in New Issue
Block a user