2023-10-23 09:58:36 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
2023-10-13 13:59:22 +02:00
|
|
|
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
|
2023-10-09 00:17:54 +02:00
|
|
|
|
2023-10-07 16:10:50 +02:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
|
|
|
pub(crate) struct UserCredentials {
|
|
|
|
|
pub username: String,
|
|
|
|
|
pub password: String,
|
2023-10-23 09:58:36 +02:00
|
|
|
pub token: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UserCredentials {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 16:23:32 +02:00
|
|
|
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)]
|
|
|
|
|
pub(crate) struct TargetUser {
|
2023-10-08 19:54:59 +02:00
|
|
|
pub target: String,
|
2023-10-07 16:10:50 +02:00
|
|
|
pub credentials: Vec<UserCredentials>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TargetUser {
|
2023-10-25 09:57:44 +02:00
|
|
|
pub fn get_target_name(&self, username: &str, password: &str) -> Option<(&UserCredentials, &str)> {
|
|
|
|
|
self.credentials.iter().find(|c| c.matches(username, password))
|
|
|
|
|
.map(|credentials| (credentials, self.target.as_str()))
|
2023-10-23 09:58:36 +02:00
|
|
|
}
|
2023-10-25 09:57:44 +02:00
|
|
|
pub fn get_target_name_by_token(&self, token: &str) -> Option<(&UserCredentials, &str)> {
|
|
|
|
|
self.credentials.iter().find(|c| c.matches_token(token))
|
|
|
|
|
.map(|credentials| (credentials, self.target.as_str()))
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 19:50:35 +02:00
|
|
|
fn default_as_443() -> String { "443".to_string() }
|
2023-10-23 09:58:36 +02:00
|
|
|
|
2023-10-26 19:50:35 +02:00
|
|
|
fn default_as_1935() -> String { "1935".to_string() }
|
2023-10-20 12:18:44 +02:00
|
|
|
|
2023-10-07 16:10:50 +02:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
2023-10-09 00:17:54 +02:00
|
|
|
pub(crate) struct ServerInfo {
|
|
|
|
|
pub protocol: String,
|
|
|
|
|
pub ip: String,
|
2023-10-26 19:50:35 +02:00
|
|
|
pub http_port: String,
|
2023-10-20 12:18:44 +02:00
|
|
|
#[serde(default = "default_as_443")]
|
2023-10-26 19:50:35 +02:00
|
|
|
pub https_port: String,
|
2023-10-20 12:18:44 +02:00
|
|
|
#[serde(default = "default_as_1935")]
|
2023-10-26 19:50:35 +02:00
|
|
|
pub rtmp_port: String,
|
2023-10-09 00:17:54 +02:00
|
|
|
pub timezone: String,
|
|
|
|
|
pub message: String,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-27 08:56:32 +02:00
|
|
|
impl ServerInfo {
|
|
|
|
|
pub fn is_valid(&mut self) -> bool {
|
|
|
|
|
self.protocol = self.protocol.trim().to_string();
|
|
|
|
|
if self.protocol.is_empty() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.ip = self.ip.trim().to_string();
|
|
|
|
|
if self.ip.is_empty() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.http_port = self.http_port.trim().to_string();
|
|
|
|
|
if self.http_port.is_empty() {
|
|
|
|
|
self.http_port = "80".to_string();
|
|
|
|
|
} else if self.http_port.parse::<u16>().is_err() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.https_port = self.https_port.trim().to_string();
|
|
|
|
|
if self.https_port.is_empty() {
|
|
|
|
|
self.https_port = "443".to_string();
|
|
|
|
|
} else if self.https_port.parse::<u16>().is_err() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
self.rtmp_port = self.rtmp_port.trim().to_string();
|
|
|
|
|
if self.rtmp_port.is_empty() {
|
|
|
|
|
self.rtmp_port = "1953".to_string();
|
|
|
|
|
} else if self.rtmp_port.parse::<u16>().is_err() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-09 00:17:54 +02:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
|
|
|
pub(crate) struct ApiProxyConfig {
|
|
|
|
|
pub server: ServerInfo,
|
2023-10-08 19:54:59 +02:00
|
|
|
pub user: Vec<TargetUser>,
|
2023-10-26 16:23:32 +02:00
|
|
|
#[serde(skip_serializing, skip_deserializing)]
|
|
|
|
|
pub _file_path: String,
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 00:17:54 +02:00
|
|
|
impl ApiProxyConfig {
|
2023-10-23 09:58:36 +02:00
|
|
|
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
|
2023-10-09 00:17:54 +02:00
|
|
|
let mut usernames = HashSet::new();
|
2023-10-23 09:58:36 +02:00
|
|
|
let mut tokens = HashSet::new();
|
2023-10-12 19:17:25 +02:00
|
|
|
let mut errors = Vec::new();
|
2023-10-23 09:58:36 +02:00
|
|
|
for target_user in &mut self.user {
|
|
|
|
|
for user in &mut target_user.credentials {
|
2023-10-09 00:17:54 +02:00
|
|
|
if usernames.contains(&user.username) {
|
2023-10-12 19:17:25 +02:00
|
|
|
errors.push(format!("Non unique username found {}", &user.username));
|
2023-10-09 00:17:54 +02:00
|
|
|
} else {
|
|
|
|
|
usernames.insert(user.username.to_string());
|
|
|
|
|
}
|
2023-10-23 09:58:36 +02:00
|
|
|
if let Some(token) = &user.token {
|
|
|
|
|
if token.is_empty() {
|
|
|
|
|
user.token = None
|
|
|
|
|
} else if tokens.contains(token) {
|
|
|
|
|
errors.push(format!("Non unique token found {}", &user.username));
|
|
|
|
|
} else {
|
|
|
|
|
tokens.insert(token.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-12 19:17:25 +02:00
|
|
|
if errors.is_empty() {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
2023-10-13 13:59:22 +02:00
|
|
|
Err(M3uFilterError::new(M3uFilterErrorKind::Info, errors.join("\n")))
|
2023-10-12 19:17:25 +02:00
|
|
|
}
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-26 16:23:32 +02:00
|
|
|
pub fn get_target_name(&self, username: &str, password: &str) -> Option<(UserCredentials, String)> {
|
2023-10-08 19:54:59 +02:00
|
|
|
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(username, password) {
|
2023-10-26 16:23:32 +02:00
|
|
|
return Some((credentials.clone(), target_name.to_string()));
|
2023-10-08 19:54:59 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
None
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
2023-10-23 09:58:36 +02:00
|
|
|
|
2023-10-26 16:23:32 +02:00
|
|
|
pub fn get_target_name_by_token(&self, token: &str) -> Option<(UserCredentials, String)> {
|
2023-10-23 09:58:36 +02:00
|
|
|
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) {
|
2023-10-26 16:23:32 +02:00
|
|
|
return Some((credentials.clone(), target_name.to_string()));
|
2023-10-23 09:58:36 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|