diff --git a/QuickLook/Helpers/ExtensionFilterHelper.cs b/QuickLook/Helpers/ExtensionFilterHelper.cs new file mode 100644 index 000000000..0027e2919 --- /dev/null +++ b/QuickLook/Helpers/ExtensionFilterHelper.cs @@ -0,0 +1,198 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Common.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace QuickLook.Helpers; + +/// +/// Helper class for managing file extension allowlist/blocklist filtering. +/// +/// Blocklist mode (default): All extensions are allowed except those in the blocklist. +/// If the blocklist is empty, all files are allowed. +/// +/// +/// Allowlist mode: Only extensions in the allowlist can be previewed. +/// If the allowlist is empty in allowlist mode, all files are allowed (no filtering). +/// +/// +/// Directories and files without extensions are always allowed regardless of the mode. +/// +/// +public static class ExtensionFilterHelper +{ + private const string AllowlistKey = "ExtensionAllowlist"; + private const string BlocklistKey = "ExtensionBlocklist"; + private const string UseAllowlistModeKey = "UseExtensionAllowlistMode"; + private static readonly char[] ExtensionSeparators = [';', ',']; + + private static HashSet _allowlistCache; + private static HashSet _blocklistCache; + private static bool? _useAllowlistModeCache; + + /// + /// Gets or sets whether to use allowlist mode. + /// When true, only extensions in the allowlist can be previewed. + /// When false (default), extensions in the blocklist are blocked from preview. + /// + public static bool UseAllowlistMode + { + get + { + _useAllowlistModeCache ??= SettingHelper.Get(UseAllowlistModeKey, false); + return _useAllowlistModeCache.Value; + } + set + { + _useAllowlistModeCache = value; + SettingHelper.Set(UseAllowlistModeKey, value); + } + } + + /// + /// Gets the current allowlist of file extensions. + /// Extensions should be in the format ".ext" (with leading dot). + /// + public static HashSet Allowlist + { + get + { + if (_allowlistCache == null) + { + var list = SettingHelper.Get(AllowlistKey, string.Empty); + _allowlistCache = ParseExtensionList(list); + } + return _allowlistCache; + } + } + + /// + /// Gets the current blocklist of file extensions. + /// Extensions should be in the format ".ext" (with leading dot). + /// + public static HashSet Blocklist + { + get + { + if (_blocklistCache == null) + { + var list = SettingHelper.Get(BlocklistKey, string.Empty); + _blocklistCache = ParseExtensionList(list); + } + return _blocklistCache; + } + } + + /// + /// Sets the allowlist of file extensions. + /// + /// Collection of extensions in the format ".ext" (with leading dot). + public static void SetAllowlist(IEnumerable extensions) + { + var normalized = NormalizeExtensions(extensions); + _allowlistCache = normalized; + SettingHelper.Set(AllowlistKey, string.Join(";", normalized)); + } + + /// + /// Sets the blocklist of file extensions. + /// + /// Collection of extensions in the format ".ext" (with leading dot). + public static void SetBlocklist(IEnumerable extensions) + { + var normalized = NormalizeExtensions(extensions); + _blocklistCache = normalized; + SettingHelper.Set(BlocklistKey, string.Join(";", normalized)); + } + + /// + /// Checks if a file path is allowed for preview based on the current filter settings. + /// + /// The file path to check. + /// True if the file is allowed for preview, false if it should be blocked. + public static bool IsExtensionAllowed(string path) + { + if (string.IsNullOrEmpty(path)) + return true; + + var extension = Path.GetExtension(path); + + // Files without extensions are always allowed (includes directories) + if (string.IsNullOrEmpty(extension)) + return true; + + extension = extension.ToLowerInvariant(); + + if (UseAllowlistMode) + { + // In allowlist mode: only allow if extension is in the allowlist + // If allowlist is empty, allow all (no filtering) + return Allowlist.Count == 0 || Allowlist.Contains(extension); + } + else + { + // In blocklist mode: block if extension is in the blocklist + return !Blocklist.Contains(extension); + } + } + + /// + /// Clears the cached settings, forcing a reload from the config file. + /// + public static void ClearCache() + { + _allowlistCache = null; + _blocklistCache = null; + _useAllowlistModeCache = null; + } + + private static HashSet ParseExtensionList(string list) + { + if (string.IsNullOrWhiteSpace(list)) + return new HashSet(StringComparer.OrdinalIgnoreCase); + + return new HashSet( + list.Split(ExtensionSeparators, StringSplitOptions.RemoveEmptyEntries) + .Select(NormalizeExtension) + .Where(e => !string.IsNullOrEmpty(e)), + StringComparer.OrdinalIgnoreCase); + } + + private static HashSet NormalizeExtensions(IEnumerable extensions) + { + return new HashSet( + extensions.Select(NormalizeExtension).Where(e => !string.IsNullOrEmpty(e)), + StringComparer.OrdinalIgnoreCase); + } + + private static string NormalizeExtension(string ext) + { + if (string.IsNullOrWhiteSpace(ext)) + return null; + + ext = ext.Trim().ToLowerInvariant(); + if (!ext.StartsWith(".")) + ext = "." + ext; + + return ext; + } +} + diff --git a/QuickLook/ViewWindowManager.cs b/QuickLook/ViewWindowManager.cs index ee1ce2bdd..563ead67b 100644 --- a/QuickLook/ViewWindowManager.cs +++ b/QuickLook/ViewWindowManager.cs @@ -151,10 +151,15 @@ public void InvokePreview(string path = null) if (_viewerWindow.IsVisible && path == _invokedPath) return; - if (!Directory.Exists(path) && !File.Exists(path)) + var isDirectory = Directory.Exists(path); + if (!isDirectory && !File.Exists(path)) if (!path.StartsWith("::")) // CLSID return; + // Check extension filtering before proceeding (skip for directories) + if (!isDirectory && !ExtensionFilterHelper.IsExtensionAllowed(path)) + return; + _invokedPath = path; RunFocusMonitor(); @@ -172,7 +177,12 @@ public void InvokePluginPreview(string plugin, string path = null) if (string.IsNullOrEmpty(path)) return; - if (!Directory.Exists(path) && !File.Exists(path)) + var isDirectory = Directory.Exists(path); + if (!isDirectory && !File.Exists(path)) + return; + + // Check extension filtering before proceeding (skip for directories) + if (!isDirectory && !ExtensionFilterHelper.IsExtensionAllowed(path)) return; RunFocusMonitor();