mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-12 20:47:22 +00:00
fix: harden media handoff and live logs
Reject stale extension media cookie headers before yt-dlp metadata work, preserve ordinary capture cookies, and advance the companion extension. Stream redacted diagnostic logs only while the visible Logs view is active, with bounded batched updates and race-safe snapshot handoff.
This commit is contained in:
@@ -315,10 +315,15 @@ fn normalize_download(payload: ExtensionRequest) -> Option<ExtensionDownload> {
|
||||
silent: payload.silent,
|
||||
filename,
|
||||
headers: payload.headers.filter(|value| !value.trim().is_empty()),
|
||||
// Keep the exact tab/container cookie context. The Add modal may retry
|
||||
// public media without this header when an upstream rejects its size,
|
||||
// but authenticated and container-scoped media must not lose it here.
|
||||
cookies: payload.cookies.filter(|value| !value.trim().is_empty()),
|
||||
// Explicit media is resolved by yt-dlp, which must use Firelink's
|
||||
// configured browser-cookie source. Forwarding a browser's complete
|
||||
// Cookie header can exceed upstream limits and makes old extension
|
||||
// builds pay for a doomed metadata request before retrying. Ordinary
|
||||
// captured downloads still need their exact request cookies.
|
||||
cookies: (!payload.media)
|
||||
.then_some(payload.cookies)
|
||||
.flatten()
|
||||
.filter(|value| !value.trim().is_empty()),
|
||||
media: payload.media,
|
||||
})
|
||||
}
|
||||
@@ -496,24 +501,41 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_media_preserves_the_extension_cookie_header() {
|
||||
fn explicit_media_drops_the_extension_cookie_header() {
|
||||
let download = normalize_download(ExtensionRequest {
|
||||
urls: vec!["https://www.youtube.com/watch?v=example".to_string()],
|
||||
referer: None,
|
||||
silent: false,
|
||||
filename: None,
|
||||
headers: Some("User-Agent: Firefox".to_string()),
|
||||
cookies: Some("large=browser-cookie-header".to_string()),
|
||||
cookies: Some(format!("large={}", "x".repeat(64 * 1024))),
|
||||
media: true,
|
||||
})
|
||||
.expect("valid media handoff");
|
||||
|
||||
assert!(download.media);
|
||||
assert!(download.cookies.is_none());
|
||||
assert_eq!(download.headers.as_deref(), Some("User-Agent: Firefox"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_capture_preserves_the_extension_cookie_header() {
|
||||
let download = normalize_download(ExtensionRequest {
|
||||
urls: vec!["https://example.com/private.zip".to_string()],
|
||||
referer: None,
|
||||
silent: true,
|
||||
filename: None,
|
||||
headers: None,
|
||||
cookies: Some("session=browser-cookie-header".to_string()),
|
||||
media: false,
|
||||
})
|
||||
.expect("valid download handoff");
|
||||
|
||||
assert!(!download.media);
|
||||
assert_eq!(
|
||||
download.cookies.as_deref(),
|
||||
Some("large=browser-cookie-header")
|
||||
Some("session=browser-cookie-header")
|
||||
);
|
||||
assert_eq!(download.headers.as_deref(), Some("User-Agent: Firefox"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+57
-27
@@ -4542,6 +4542,27 @@ fn redact_log_line(line: &str) -> String {
|
||||
query.replace_all(&redacted, "$1?[redacted]").into_owned()
|
||||
}
|
||||
|
||||
fn redact_log_line_for_output(line: &str) -> String {
|
||||
static HOME_PATHS: OnceLock<(String, String)> = OnceLock::new();
|
||||
let (home, escaped_home) = HOME_PATHS.get_or_init(|| {
|
||||
let home = std::env::var("USERPROFILE")
|
||||
.or_else(|_| std::env::var("HOME"))
|
||||
.unwrap_or_default();
|
||||
let home = if home.len() > 3 { home } else { String::new() };
|
||||
let escaped_home = home.replace('\\', "\\\\");
|
||||
(home, escaped_home)
|
||||
});
|
||||
|
||||
let mut redacted = line.to_string();
|
||||
if !home.is_empty() {
|
||||
redacted = redacted.replace(home, "<HOME>");
|
||||
}
|
||||
if !escaped_home.is_empty() && escaped_home != home {
|
||||
redacted = redacted.replace(escaped_home, "<HOME>");
|
||||
}
|
||||
redact_log_line(&redacted)
|
||||
}
|
||||
|
||||
fn redact_log_line_for_app(line: &str, app_handle: &tauri::AppHandle) -> String {
|
||||
use tauri::Manager;
|
||||
let without_home = app_handle
|
||||
@@ -4780,8 +4801,9 @@ mod tests {
|
||||
media_output_template, media_progress_args, media_progress_speed,
|
||||
normalize_speed_limit_for_aria2,
|
||||
parse_firelink_deep_link, parse_ffmpeg_version, parse_media_progress_line,
|
||||
redact_log_line, sanitize_ytdlp_config_value, should_cleanup_media_artifacts_after_failure,
|
||||
FirelinkDeepLink, MediaProgress, MediaSpeedSampler, MEDIA_PROGRESS_PREFIX,
|
||||
redact_log_line, redact_log_line_for_output, sanitize_ytdlp_config_value,
|
||||
should_cleanup_media_artifacts_after_failure, FirelinkDeepLink, MediaProgress,
|
||||
MediaSpeedSampler, MEDIA_PROGRESS_PREFIX,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -4979,6 +5001,15 @@ mod tests {
|
||||
assert!(redacted.contains("[redacted]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacts_live_log_output_before_webview_delivery() {
|
||||
let line = "Cookie: session=abc https://example.com/file?signature=secret";
|
||||
let redacted = redact_log_line_for_output(line);
|
||||
assert!(!redacted.contains("session=abc"));
|
||||
assert!(!redacted.contains("signature=secret"));
|
||||
assert!(redacted.contains("[redacted]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_primary_url_and_unique_mirrors_in_order() {
|
||||
let uris = collect_download_uris(
|
||||
@@ -5459,6 +5490,7 @@ mod tests {
|
||||
}
|
||||
|
||||
static LOG_PAUSED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
|
||||
static LOG_STREAM_ACTIVE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
#[tauri::command]
|
||||
fn toggle_log_pause(pause: bool) {
|
||||
@@ -5470,6 +5502,11 @@ fn is_log_paused() -> bool {
|
||||
LOG_PAUSED.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_log_stream_active(active: bool) {
|
||||
LOG_STREAM_ACTIVE.store(active, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
ensure_reqwest_crypto_provider();
|
||||
@@ -5881,7 +5918,13 @@ pub fn run() {
|
||||
tauri_plugin_log::Builder::new()
|
||||
.targets([
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { file_name: None }),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: None,
|
||||
}),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Webview)
|
||||
.filter(|_| {
|
||||
LOG_STREAM_ACTIVE.load(std::sync::atomic::Ordering::Acquire)
|
||||
}),
|
||||
])
|
||||
.level(if cfg!(debug_assertions) { log::LevelFilter::Debug } else { log::LevelFilter::Info })
|
||||
.filter(|metadata| {
|
||||
@@ -5899,29 +5942,15 @@ pub fn run() {
|
||||
.max_file_size(10_000_000)
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepSome(3))
|
||||
.timezone_strategy(tauri_plugin_log::TimezoneStrategy::UseLocal)
|
||||
.format({
|
||||
let home_dir = std::env::var("USERPROFILE").or_else(|_| std::env::var("HOME")).unwrap_or_default();
|
||||
let home_dir = if home_dir.len() > 3 { home_dir } else { String::new() };
|
||||
let escaped_home_dir = home_dir.replace("\\", "\\\\");
|
||||
move |out, message, record| {
|
||||
let msg = message.to_string();
|
||||
let redacted = if !home_dir.is_empty() {
|
||||
let mut s = msg.replace(&home_dir, "<HOME>");
|
||||
if !escaped_home_dir.is_empty() && escaped_home_dir != home_dir {
|
||||
s = s.replace(&escaped_home_dir, "<HOME>");
|
||||
}
|
||||
s
|
||||
} else {
|
||||
msg
|
||||
};
|
||||
out.finish(format_args!(
|
||||
"[{}][{}][{}] {}",
|
||||
chrono::Local::now().format("%Y-%m-%d][%H:%M:%S"),
|
||||
record.level(),
|
||||
record.target(),
|
||||
redacted
|
||||
))
|
||||
}
|
||||
.format(|out, message, record| {
|
||||
let redacted = redact_log_line_for_output(&message.to_string());
|
||||
out.finish(format_args!(
|
||||
"[{}][{}][{}] {}",
|
||||
chrono::Local::now().format("%Y-%m-%d][%H:%M:%S"),
|
||||
record.level(),
|
||||
record.target(),
|
||||
redacted
|
||||
))
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
@@ -5955,7 +5984,8 @@ pub fn run() {
|
||||
parity::create_category_directories,
|
||||
db_save_settings, db_load_settings, db_get_all_downloads, db_replace_downloads,
|
||||
db_get_all_queues, db_replace_queues,
|
||||
read_logs, export_logs, toggle_log_pause, is_log_paused, clear_logs
|
||||
read_logs, export_logs, toggle_log_pause, is_log_paused, clear_logs,
|
||||
set_log_stream_active
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
|
||||
Reference in New Issue
Block a user