Files
tuliprox/src/model/api_proxy.rs
T

288 lines
8.9 KiB
Rust
Raw Normal View History

use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use enum_iterator::Sequence;
2024-12-05 19:54:34 +01:00
use log::debug;
2025-02-05 20:01:49 +01:00
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error_result, info_err};
2025-02-06 10:49:34 +01:00
use crate::utils::file::config_reader;
2024-11-04 18:46:56 +01:00
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)]
pub enum ProxyType {
#[serde(rename = "reverse")]
Reverse,
#[serde(rename = "redirect")]
Redirect,
}
2024-05-10 10:13:50 +02:00
impl Default for ProxyType {
fn default() -> Self {
Self::Redirect
}
}
impl ProxyType {
const REVERSE: &'static str = "reverse";
const REDIRECT: &'static str = "redirect";
}
impl Display for ProxyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2025-02-06 10:49:34 +01:00
write!(f, "{}", match self {
2024-12-01 10:07:24 +01:00
Self::Reverse => Self::REVERSE,
Self::Redirect => Self::REDIRECT,
}
)
}
}
impl FromStr for ProxyType {
type Err = M3uFilterError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
if s.eq("reverse") {
2024-05-10 10:13:50 +02:00
Ok(Self::Reverse)
} else if s.eq("redirect") {
2024-05-10 10:13:50 +02:00
Ok(Self::Redirect)
} else {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s)
}
}
}
2023-10-07 16:10:50 +02:00
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2024-11-04 18:46:56 +01:00
pub struct ProxyUserCredentials {
pub username: String,
pub password: String,
2025-01-21 10:47:28 +01:00
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
#[serde(default = "ProxyType::default")]
pub proxy: ProxyType,
2025-01-21 10:47:28 +01:00
#[serde(skip_serializing_if = "Option::is_none")]
pub server: Option<String>,
2025-01-21 10:47:28 +01:00
#[serde(skip_serializing_if = "Option::is_none")]
2024-08-23 16:37:48 +02:00
pub epg_timeshift: Option<String>,
}
2024-05-03 01:47:41 +02:00
impl ProxyUserCredentials {
pub fn prepare(&mut self, resolve_var: bool) {
if resolve_var {
2024-12-01 10:07:24 +01:00
self.username = config_reader::resolve_env_var(&self.username);
self.password = config_reader::resolve_env_var(&self.password);
if let Some(tkn) = &self.token {
2024-05-10 12:01:48 +02:00
self.token = Some(config_reader::resolve_env_var(tkn));
}
self.trim();
}
}
pub fn matches_token(&self, token: &str) -> bool {
if let Some(tkn) = &self.token {
return tkn.eq(token);
}
false
}
pub fn matches(&self, username: &str, password: &str) -> bool {
self.username.eq(username) && self.password.eq(password)
}
pub fn trim(&mut self) {
self.username = self.username.trim().to_string();
self.password = self.password.trim().to_string();
match &self.token {
None => {}
Some(tkn) => {
self.token = Some(tkn.trim().to_string());
}
}
}
2023-10-07 16:10:50 +02:00
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2024-11-04 18:46:56 +01:00
pub struct TargetUser {
2023-10-08 19:54:59 +02:00
pub target: String,
2024-05-03 01:47:41 +02:00
pub credentials: Vec<ProxyUserCredentials>,
2023-10-07 16:10:50 +02:00
}
impl TargetUser {
2024-12-01 10:07:24 +01:00
pub fn get_target_name(
&self,
username: &str,
password: &str,
) -> Option<(&ProxyUserCredentials, &str)> {
self.credentials
.iter()
.find(|c| c.matches(username, password))
2023-10-25 09:57:44 +02:00
.map(|credentials| (credentials, self.target.as_str()))
}
2024-05-03 01:47:41 +02:00
pub fn get_target_name_by_token(&self, token: &str) -> Option<(&ProxyUserCredentials, &str)> {
2024-12-01 10:07:24 +01:00
self.credentials
.iter()
.find(|c| c.matches_token(token))
2023-10-25 09:57:44 +02:00
.map(|credentials| (credentials, self.target.as_str()))
2023-10-07 16:10:50 +02:00
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2024-11-04 18:46:56 +01:00
pub struct ApiProxyServerInfo {
pub name: String,
pub protocol: String,
pub host: String,
2025-01-30 17:23:41 +01:00
pub port: String,
pub timezone: String,
pub message: String,
2025-01-30 17:23:41 +01:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
impl ApiProxyServerInfo {
pub fn is_valid(&mut self) -> bool {
self.protocol = self.protocol.trim().to_string();
if self.protocol.is_empty() {
return false;
}
self.host = self.host.trim().to_string();
if self.host.is_empty() {
return false;
}
2025-01-30 17:23:41 +01:00
self.port = self.port.trim().to_string();
if self.port.is_empty() {
if self.protocol == "http" {
self.port = "80".to_string();
} else {
self.port = "443".to_string();
}
} else if self.port.parse::<u16>().is_err() {
return false;
}
2025-01-30 17:23:41 +01:00
self.timezone = self.timezone.trim().to_string();
if self.timezone.is_empty() {
self.timezone = "UTC".to_string();
}
if self.message.is_empty() {
self.message = "Welcome to m3u-filter".to_string();
}
true
}
2024-11-04 18:46:56 +01:00
pub fn get_base_url(&self) -> String {
2025-01-30 17:23:41 +01:00
let base_url = if self.port.is_empty() {
format!("{}://{}", self.protocol, self.host)
2024-12-01 10:07:24 +01:00
} else {
2025-01-30 17:23:41 +01:00
format!("{}://{}:{}", self.protocol, self.host, self.port)
2024-12-01 10:07:24 +01:00
};
2025-01-30 17:23:41 +01:00
match &self.path {
None => base_url,
Some(path) => format!("{base_url}/{}", path.trim_matches('/'))
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2024-11-04 18:46:56 +01:00
pub struct ApiProxyConfig {
pub server: Vec<ApiProxyServerInfo>,
2023-10-08 19:54:59 +02:00
pub user: Vec<TargetUser>,
2023-10-07 16:10:50 +02:00
}
impl ApiProxyConfig {
pub fn prepare(&mut self, resolve_var: bool) -> Result<(), M3uFilterError> {
let mut usernames = HashSet::new();
let mut tokens = HashSet::new();
let mut errors = Vec::new();
if self.server.is_empty() {
2025-02-05 20:01:49 +01:00
errors.push("No server info defined".to_string());
} else {
let mut name_set = HashSet::new();
for server in &self.server {
if server.name.trim().is_empty() {
errors.push("Server info name is empty ".to_owned());
} else if name_set.contains(server.name.as_str()) {
2024-12-01 10:07:24 +01:00
errors.push(format!(
"Non unique server info name found {}",
&server.name
));
} else {
name_set.insert(server.name.clone());
}
}
}
for target_user in &mut self.user {
for user in &mut target_user.credentials {
user.prepare(resolve_var);
if usernames.contains(&user.username) {
errors.push(format!("Non unique username found {}", &user.username));
} else {
usernames.insert(user.username.to_string());
}
if let Some(token) = &user.token {
if token.is_empty() {
2024-05-10 12:01:48 +02:00
user.token = None;
} else if tokens.contains(token) {
errors.push(format!("Non unique token found {}", &user.username));
} else {
tokens.insert(token.to_string());
}
}
if let Some(server_info_name) = &user.server {
2024-12-01 10:07:24 +01:00
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
));
}
}
}
}
if errors.is_empty() {
Ok(())
} else {
2024-12-23 11:30:33 +01:00
Err(info_err!(errors.join("\n")))
}
2023-10-07 16:10:50 +02:00
}
2024-12-01 10:07:24 +01:00
pub fn get_target_name(
&self,
username: &str,
password: &str,
) -> Option<(ProxyUserCredentials, String)> {
2023-10-08 19:54:59 +02:00
for target_user in &self.user {
2024-12-01 10:07:24 +01:00
if let Some((credentials, target_name)) =
target_user.get_target_name(username, password)
{
return Some((credentials.clone(), target_name.to_string()));
2023-10-08 19:54:59 +02:00
};
}
2024-12-05 19:54:34 +01:00
debug!("Could not find any target for user {username}");
2023-10-08 19:54:59 +02:00
None
2023-10-07 16:10:50 +02:00
}
2024-05-03 01:47:41 +02:00
pub fn get_target_name_by_token(&self, token: &str) -> Option<(ProxyUserCredentials, String)> {
for target_user in &self.user {
2023-10-25 09:57:44 +02:00
if let Some((credentials, target_name)) = target_user.get_target_name_by_token(token) {
return Some((credentials.clone(), target_name.to_string()));
};
}
None
}
2024-12-30 12:40:22 +01:00
pub fn get_user_credentials(&self,username: &str) -> Option<ProxyUserCredentials> {
let result = self.user.iter()
.flat_map(|target_user| &target_user.credentials)
.find(|credential| credential.username == username)
.cloned();
if result.is_none() {
debug!("Could not find any user {username}");
}
result
}
2024-12-01 10:07:24 +01:00
}