From c5025fd5a0518e9d27b2bef9a75742f7947fd0f8 Mon Sep 17 00:00:00 2001 From: NimBold Date: Fri, 10 Jul 2026 00:27:44 +0330 Subject: [PATCH] fix(media): quote forwarded cookie headers Keep yt-dlp config values intact and require explicit media metadata before downloads can start. --- src-tauri/src/lib.rs | 28 +++++++++++++++++++++------ src/components/AddDownloadsModal.tsx | 10 +++++++--- src/utils/addDownloadMetadata.test.ts | 9 ++++++++- src/utils/addDownloadMetadata.ts | 9 ++++++++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2abd0f5..949d88f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1110,8 +1110,13 @@ fn append_ytdlp_config_option(config: &mut String, option: &str, value: &str) { let safe_value = sanitize_ytdlp_config_value(value); if !safe_value.is_empty() { config.push_str(option); - config.push('\n'); - config.push_str(&safe_value); + config.push(' '); + // yt-dlp parses one configuration line at a time. Quoting keeps + // whitespace and cookie delimiters inside this option's single value, + // rather than turning them into additional input URLs. + config.push('\''); + config.push_str(&safe_value.replace('\'', "'\\''")); + config.push('\''); config.push('\n'); } } @@ -4676,7 +4681,8 @@ fn set_extension_frontend_ready(state: tauri::State<'_, AppState>, ready: bool) #[cfg(test)] mod tests { use super::{ - aggregate_media_fraction, append_ytdlp_http_headers, build_media_format_options, + aggregate_media_fraction, append_ytdlp_config_option, append_ytdlp_http_headers, + build_media_format_options, collect_download_uris, drain_media_output_lines, filename_from_content_disposition, filename_from_url_disposition_query, filename_from_url_path, is_excluded_yt_dlp_format, is_browser_cookie_extraction_error, json_lower, media_metadata_cache_key, @@ -4731,12 +4737,22 @@ mod tests { append_ytdlp_http_headers( &mut config, Some("Referer: https://example.com/video"), - Some("session=abc\r\n--proxy=http://bad.invalid"), + Some("session=abc; preference=high\r\n--proxy=http://bad.invalid"), ) .unwrap(); - assert!(config.contains("--add-header\nReferer: https://example.com/video\n")); - assert!(config.contains("--add-header\nCookie: session=abc--proxy=http://bad.invalid\n")); + assert_eq!( + config, + "--add-header 'Referer: https://example.com/video'\n--add-header 'Cookie: session=abc; preference=high--proxy=http://bad.invalid'\n" + ); + } + + #[test] + fn ytdlp_config_options_quote_embedded_single_quotes() { + let mut config = String::new(); + append_ytdlp_config_option(&mut config, "--username", "sam's account"); + + assert_eq!(config, "--username 'sam'\\''s account'\n"); } #[test] diff --git a/src/components/AddDownloadsModal.tsx b/src/components/AddDownloadsModal.tsx index 9e299f0..00296c6 100644 --- a/src/components/AddDownloadsModal.tsx +++ b/src/components/AddDownloadsModal.tsx @@ -791,6 +791,10 @@ export const AddDownloadsModal = () => { : 'Unknown'; const canSubmit = canSubmitMetadataRows(parsedItems); const failedMetadataCount = parsedItems.filter(item => item.status === 'metadata-error').length; + const failedMediaMetadataCount = parsedItems.filter( + item => item.status === 'metadata-error' && item.isMedia + ).length; + const fallbackMetadataCount = failedMetadataCount - failedMediaMetadataCount; return ( <> @@ -844,7 +848,7 @@ export const AddDownloadsModal = () => { />
- {parsedItems.filter(item => item.status === 'ready').length} ready, {failedMetadataCount} fallback + {parsedItems.filter(item => item.status === 'ready').length} ready, {fallbackMetadataCount} fallback, {failedMediaMetadataCount} media retry
) : ( item.status === 'metadata-error' - ? 'Fallback' + ? item.isMedia ? 'Metadata failed' : 'Fallback' : item.status === 'invalid' ? 'Invalid' : 'Ready' @@ -987,7 +991,7 @@ export const AddDownloadsModal = () => { ) : (
- Metadata unavailable. Default media format will be used. + Metadata unavailable. Refresh metadata before adding this media.
)} diff --git a/src/utils/addDownloadMetadata.test.ts b/src/utils/addDownloadMetadata.test.ts index deafceb..fd1afbd 100644 --- a/src/utils/addDownloadMetadata.test.ts +++ b/src/utils/addDownloadMetadata.test.ts @@ -187,11 +187,15 @@ describe('add download metadata workflow', () => { expect(updated[0]).toBe(current); }); - it('allows ready and failed rows but blocks loading and invalid rows', () => { + it('allows normal-download fallback but blocks unresolved explicit media', () => { expect(canSubmitMetadataRows([ row(), row({ id: 'fallback', status: 'metadata-error' }) ])).toBe(true); + expect(canSubmitMetadataRows([ + row(), + row({ id: 'media-fallback', status: 'metadata-error', isMedia: true }) + ])).toBe(false); expect(canSubmitMetadataRows([row({ status: 'loading' })])).toBe(false); expect(canSubmitMetadataRows([row({ status: 'invalid' })])).toBe(false); }); @@ -246,6 +250,9 @@ describe('add download metadata workflow', () => { expect(metadataSummaryMessage([ row({ status: 'metadata-error' }) ])).toContain('can still be added'); + expect(metadataSummaryMessage([ + row({ status: 'metadata-error', isMedia: true }) + ])).toContain('Refresh metadata before adding'); expect(metadataSummaryMessage([ row({ status: 'invalid' }) ])).toContain('Correct or remove 1 invalid URL'); diff --git a/src/utils/addDownloadMetadata.ts b/src/utils/addDownloadMetadata.ts index 96ef3ef..d63874e 100644 --- a/src/utils/addDownloadMetadata.ts +++ b/src/utils/addDownloadMetadata.ts @@ -146,7 +146,10 @@ export const refreshFailedMetadataRows = ( export const canSubmitMetadataRows = (rows: AddDownloadDraftRow[]): boolean => rows.length > 0 - && rows.every(row => row.status === 'ready' || row.status === 'metadata-error'); + && rows.every(row => + row.status === 'ready' + || (!row.isMedia && row.status === 'metadata-error') + ); export const mediaFormatSelectorForRow = ( row: AddDownloadDraftRow @@ -203,7 +206,11 @@ export const metadataSummaryMessage = (rows: AddDownloadDraftRow[]): string => { } const failed = rows.filter(row => row.status === 'metadata-error').length; + const failedMedia = rows.filter(row => row.status === 'metadata-error' && row.isMedia).length; const ready = rows.filter(row => row.status === 'ready').length; + if (failedMedia > 0) { + return `Media metadata is unavailable for ${failedMedia} item${failedMedia === 1 ? '' : 's'}. Refresh metadata before adding.`; + } if (failed === rows.length) { return 'Metadata is unavailable. Downloads can still be added using fallback details.'; }