Skip to content

Commit e534f80

Browse files
soso
authored andcommitted
Use local timezone logs and strict UA matching
1 parent 2b8f35d commit e534f80

4 files changed

Lines changed: 163 additions & 10 deletions

File tree

Cargo.lock

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ argon2 = "0.5"
1010
aes-gcm = "0.10"
1111
base64 = "0.22"
1212
bytes = "1"
13+
chrono = { version = "0.4", default-features = false, features = ["clock"] }
1314
moka = { version = "0.12", features = ["future"] }
1415
rand = "0.9"
1516
regex = "1"

src/client_control.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -832,12 +832,11 @@ pub fn matched_block_rule(
832832
if user_agent.is_empty() {
833833
return Ok(None);
834834
}
835-
let client_name = extract_client_name(headers, &user_agent);
836835

837836
Ok(config
838837
.records
839838
.iter()
840-
.find(|record| record.enabled && rule_matches(record, &user_agent, &client_name))
839+
.find(|record| record.enabled && rule_matches(record, &user_agent))
841840
.cloned())
842841
}
843842

@@ -1115,14 +1114,45 @@ fn normalize_value(value: &str) -> String {
11151114
}
11161115
}
11171116

1118-
fn rule_matches(record: &ClientRuleRecord, user_agent: &str, client_name: &str) -> bool {
1117+
fn rule_matches(record: &ClientRuleRecord, user_agent: &str) -> bool {
11191118
let rule_ua = record.user_agent.trim().to_ascii_lowercase();
11201119
if rule_ua.is_empty() {
11211120
return false;
11221121
}
11231122
let request_ua = user_agent.trim().to_ascii_lowercase();
1124-
let client = client_name.trim().to_ascii_lowercase();
1125-
request_ua.contains(&rule_ua)
1126-
|| rule_ua.contains(&request_ua)
1127-
|| (!client.is_empty() && client == record.client_name.trim().to_ascii_lowercase())
1123+
!request_ua.is_empty() && request_ua.contains(&rule_ua)
1124+
}
1125+
1126+
#[cfg(test)]
1127+
mod tests {
1128+
use super::*;
1129+
1130+
fn rule(user_agent: &str, client_name: &str) -> ClientRuleRecord {
1131+
ClientRuleRecord {
1132+
id: "rule-1".to_string(),
1133+
client_name: client_name.to_string(),
1134+
device_name: "--".to_string(),
1135+
user_name: "--".to_string(),
1136+
user_agent: user_agent.to_string(),
1137+
source: ClientRuleSource::Manual,
1138+
enabled: true,
1139+
created_at: "0".to_string(),
1140+
updated_at: "0".to_string(),
1141+
note: String::new(),
1142+
}
1143+
}
1144+
1145+
#[test]
1146+
fn ua_rule_matches_case_insensitive_keyword() {
1147+
let record = rule("infuse-library", "Infuse-Direct");
1148+
1149+
assert!(rule_matches(&record, "Mozilla/5.0 Infuse-Library/8.0"));
1150+
}
1151+
1152+
#[test]
1153+
fn ua_rule_does_not_match_client_name_only() {
1154+
let record = rule("infuse-library", "Infuse-Direct");
1155+
1156+
assert!(!rule_matches(&record, "Infuse-Direct/8.0"));
1157+
}
11281158
}

src/main.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod url_mapping;
1717

1818
use std::{
1919
collections::{HashMap, VecDeque},
20-
env,
20+
env, fmt,
2121
net::SocketAddr,
2222
sync::Arc,
2323
time::Duration,
@@ -40,13 +40,77 @@ use tokio::{
4040
task::JoinHandle,
4141
};
4242
use tower_http::services::{ServeDir, ServeFile};
43-
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
43+
use tracing_subscriber::{
44+
EnvFilter,
45+
fmt::{format::Writer, time::FormatTime},
46+
layer::SubscriberExt,
47+
util::SubscriberInitExt,
48+
};
4449

4550
use crate::cache::DirectLinkCache;
4651
use activity_log::{ActivityKind, ActivityLevel, ActivityLogStore, PlaybackLogRecord};
4752

4853
const MAX_PROXY_BODY_BYTES: usize = 64 * 1024 * 1024;
4954

55+
#[derive(Clone)]
56+
struct TzTimer {
57+
offset_seconds: i32,
58+
}
59+
60+
impl TzTimer {
61+
fn from_env() -> Self {
62+
Self {
63+
offset_seconds: tz_offset_seconds(&env::var("TZ").unwrap_or_default()),
64+
}
65+
}
66+
}
67+
68+
impl FormatTime for TzTimer {
69+
fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result {
70+
let now = chrono::Utc::now() + chrono::Duration::seconds(self.offset_seconds.into());
71+
write!(writer, "{}", now.format("%Y-%m-%d %H:%M:%S%.3f"))
72+
}
73+
}
74+
75+
fn tz_offset_seconds(tz: &str) -> i32 {
76+
let tz = tz.trim();
77+
if tz.eq_ignore_ascii_case("Asia/Shanghai")
78+
|| tz.eq_ignore_ascii_case("Asia/Chongqing")
79+
|| tz.eq_ignore_ascii_case("Asia/Harbin")
80+
|| tz.eq_ignore_ascii_case("Asia/Urumqi")
81+
|| tz.eq_ignore_ascii_case("PRC")
82+
{
83+
return 8 * 3600;
84+
}
85+
if tz.eq_ignore_ascii_case("UTC") || tz.eq_ignore_ascii_case("Etc/UTC") || tz == "Z" {
86+
return 0;
87+
}
88+
parse_utc_offset(tz).unwrap_or(0)
89+
}
90+
91+
fn parse_utc_offset(value: &str) -> Option<i32> {
92+
let value = value
93+
.strip_prefix("UTC")
94+
.or_else(|| value.strip_prefix("GMT"))
95+
.unwrap_or(value)
96+
.trim();
97+
let sign = match value.as_bytes().first()? {
98+
b'+' => 1,
99+
b'-' => -1,
100+
_ => return None,
101+
};
102+
let value = &value[1..];
103+
let (hours, minutes) = if let Some((hours, minutes)) = value.split_once(':') {
104+
(hours.parse::<i32>().ok()?, minutes.parse::<i32>().ok()?)
105+
} else {
106+
(value.parse::<i32>().ok()?, 0)
107+
};
108+
if !(0..=23).contains(&hours) || !(0..=59).contains(&minutes) {
109+
return None;
110+
}
111+
Some(sign * (hours * 3600 + minutes * 60))
112+
}
113+
50114
#[derive(Clone)]
51115
struct AppState {
52116
config: Arc<RwLock<Config>>,
@@ -211,7 +275,7 @@ impl ProxyManager {
211275
async fn main() -> AppResult<()> {
212276
tracing_subscriber::registry()
213277
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "emby302gateway_rs=info".into()))
214-
.with(tracing_subscriber::fmt::layer())
278+
.with(tracing_subscriber::fmt::layer().with_timer(TzTimer::from_env()))
215279
.init();
216280

217281
let settings_store = SettingsStore::open_default()?;
@@ -2052,6 +2116,13 @@ mod tests {
20522116
.as_nanos()
20532117
}
20542118

2119+
#[test]
2120+
fn log_timer_respects_common_tz_values() {
2121+
assert_eq!(tz_offset_seconds("Asia/Shanghai"), 8 * 3600);
2122+
assert_eq!(tz_offset_seconds("UTC+08:00"), 8 * 3600);
2123+
assert_eq!(tz_offset_seconds("UTC"), 0);
2124+
}
2125+
20552126
async fn spawn_mock_server<F, Fut>(handler: F) -> String
20562127
where
20572128
F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,

0 commit comments

Comments
 (0)