Compare commits

...

4 Commits

Author SHA1 Message Date
Leilei Zhang
250d2df6f5 fix style 2026-01-08 12:19:58 +08:00
Leilei Zhang
f495604339 refine 2026-01-08 12:11:39 +08:00
Leilei Zhang
ec88896839 add warning 2026-01-08 11:39:27 +08:00
Leilei Zhang
2ea716ace7 make html support html 2026-01-07 14:35:07 +08:00
5 changed files with 127 additions and 24 deletions

View File

@@ -39,7 +39,7 @@ namespace Microsoft.PowerToys.FilePreviewCommon
var softlineBreak = new Markdig.Extensions.Hardlines.SoftlineBreakAsHardlineExtension();
MarkdownPipelineBuilder pipelineBuilder;
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics().DisableHtml();
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics();
pipelineBuilder.Extensions.Add(extension);
pipelineBuilder.Extensions.Add(softlineBreak);

View File

@@ -21,6 +21,19 @@
x:Name="OpenUriDialog"
x:Uid="OpenUriDialog"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
SecondaryButtonClick="OpenUriDialog_SecondaryButtonClick" />
SecondaryButtonClick="OpenUriDialog_SecondaryButtonClick">
<StackPanel Spacing="16">
<controls:InfoBar
x:Name="OpenUriWarningBanner"
x:Uid="OpenUriWarningBanner"
IsClosable="False"
IsOpen="True"
Severity="Warning" />
<TextBlock
x:Name="OpenUriDialogContent"
IsTextSelectionEnabled="True"
TextWrapping="Wrap" />
</StackPanel>
</ContentDialog>
</Grid>
</UserControl>

View File

@@ -34,6 +34,11 @@ namespace Peek.FilePreviewer.Controls
private Color? _originalBackgroundColor;
/// <summary>
/// URI of the current source being previewed (for resource filtering)
/// </summary>
private Uri? _currentSourceUri;
public delegate void NavigationCompletedHandler(WebView2? sender, CoreWebView2NavigationCompletedEventArgs? args);
public delegate void DOMContentLoadedHandler(CoreWebView2? sender, CoreWebView2DOMContentLoadedEventArgs? args);
@@ -97,6 +102,7 @@ namespace Peek.FilePreviewer.Controls
{
this.InitializeComponent();
Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", TempFolderPath.Path, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--block-new-web-contents", EnvironmentVariableTarget.Process);
}
public void Dispose()
@@ -105,6 +111,7 @@ namespace Peek.FilePreviewer.Controls
{
PreviewBrowser.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.ContextMenuRequested -= CoreWebView2_ContextMenuRequested;
RemoveResourceFilter();
}
}
@@ -123,6 +130,14 @@ namespace Peek.FilePreviewer.Controls
{
/* CoreWebView2.Navigate() will always trigger a navigation even if the content/URI is the same.
* Use WebView2.Source to avoid re-navigating to the same content. */
_currentSourceUri = Source;
// Only apply resource filter for non-dev files
if (!IsDevFilePreview)
{
ApplyResourceFilter();
}
PreviewBrowser.CoreWebView2.Navigate(Source.ToString());
}
}
@@ -146,10 +161,14 @@ namespace Peek.FilePreviewer.Controls
if (IsDevFilePreview)
{
PreviewBrowser.CoreWebView2.SetVirtualHostNameToFolderMapping(Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.MonacoDirectory, CoreWebView2HostResourceAccessKind.Allow);
// Remove resource filter for dev files (Monaco needs to load resources)
RemoveResourceFilter();
}
else
{
PreviewBrowser.CoreWebView2.ClearVirtualHostNameToFolderMapping(Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName);
ApplyResourceFilter();
}
}
}
@@ -283,6 +302,66 @@ namespace Peek.FilePreviewer.Controls
}
}
/// <summary>
/// Applies strict resource filtering for non-dev files to block external resources.
/// This prevents XSS attacks and unwanted external content loading.
/// </summary>
private void ApplyResourceFilter()
{
// Remove existing handler to prevent duplicate subscriptions
RemoveResourceFilter();
// Add filter and subscribe to resource requests
PreviewBrowser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
PreviewBrowser.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
}
private void RemoveResourceFilter()
{
PreviewBrowser.CoreWebView2.WebResourceRequested -= CoreWebView2_WebResourceRequested;
}
private void CoreWebView2_WebResourceRequested(CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs args)
{
if (_currentSourceUri == null)
{
return;
}
var requestUri = new Uri(args.Request.Uri);
// Allow loading the source file itself
if (requestUri == _currentSourceUri)
{
return;
}
// For local file:// resources, allow same directory and subdirectories
if (requestUri.Scheme == "file" && _currentSourceUri.Scheme == "file")
{
try
{
var sourceDirectory = System.IO.Path.GetDirectoryName(_currentSourceUri.LocalPath);
var requestPath = requestUri.LocalPath;
// Allow resources in the same directory or subdirectories
if (!string.IsNullOrEmpty(sourceDirectory) &&
requestPath.StartsWith(sourceDirectory, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
catch
{
// If path processing fails, block for security
}
}
// Block all other resources including http(s) requests to prevent external tracking,
// data exfiltration, and XSS attacks
args.Response = PreviewBrowser.CoreWebView2.Environment.CreateWebResourceResponse(null, 403, "Forbidden", null);
}
private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
{
// If the file being previewed is HTML or HTM, reset the background color to its original state.
@@ -344,7 +423,7 @@ namespace Peek.FilePreviewer.Controls
private async Task ShowOpenUriDialogAsync(Uri uri)
{
OpenUriDialog.Content = uri.ToString();
OpenUriDialogContent.Text = uri.ToString();
var result = await OpenUriDialog.ShowAsync();
if (result == ContentDialogResult.Primary)
@@ -356,7 +435,7 @@ namespace Peek.FilePreviewer.Controls
private void OpenUriDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var dataPackage = new DataPackage();
dataPackage.SetText(sender.Content.ToString());
dataPackage.SetText(OpenUriDialogContent.Text);
Clipboard.SetContent(dataPackage);
}
}

View File

@@ -113,38 +113,41 @@ namespace Peek.FilePreviewer.Previewers
await Dispatcher.RunOnUiThread(async () =>
{
bool isHtml = File.Extension == ".html" || File.Extension == ".htm";
bool isMarkdown = File.Extension == ".md";
bool isSvg = File.Extension == ".svg";
string extension = File.Extension;
bool supportedByMonaco = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown && !isSvg;
// Default: non-dev file preview with standard context menu
IsDevFilePreview = false;
CustomContextMenu = false;
IsDevFilePreview = supportedByMonaco;
CustomContextMenu = useMonaco;
if (useMonaco)
// Determine preview strategy based on file type priority
if (extension == ".md")
{
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText, _previewSettings.SourceCodeStickyScroll, _previewSettings.SourceCodeFontSize, _previewSettings.SourceCodeMinimap));
}
else if (isMarkdown)
{
IsDevFilePreview = false;
// Markdown files use custom renderer
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MarkdownHelper.PreviewTempFile(raw, File.Path, TempFolderPath.Path));
}
else if (isSvg)
else if (extension == ".svg")
{
// SVG files are rendered directly by WebView2 for better compatibility
// with complex SVGs from Adobe Illustrator, Inkscape, etc.
IsDevFilePreview = false;
Preview = new Uri(File.Path);
}
else if (extension == ".html" || extension == ".htm")
{
// Simple html file to preview. Shouldn't do things like enabling scripts or using a virtual mapped directory.
Preview = new Uri(File.Path);
}
else if (MonacoHelper.SupportedMonacoFileTypes.Contains(extension))
{
// Source code files use Monaco editor
IsDevFilePreview = true;
CustomContextMenu = true;
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText, _previewSettings.SourceCodeStickyScroll, _previewSettings.SourceCodeFontSize, _previewSettings.SourceCodeMinimap));
}
else
{
// Simple html file to preview. Shouldn't do things like enabling scripts or using a virtual mapped directory.
IsDevFilePreview = false;
// Fallback for other supported file types (e.g., PDF)
Preview = new Uri(File.Path);
}
});

View File

@@ -250,9 +250,17 @@
<comment>Dialog showed when an URI is clicked. Button to copy the URI.</comment>
</data>
<data name="OpenUriDialog.Title" xml:space="preserve">
<value>Do you want Peek to open the external application?</value>
<value>Do you want Peek to open this link?</value>
<comment>Title of the dialog showed when an URI is clicked,"Peek" is the name of the utility. </comment>
</data>
<data name="OpenUriWarningBanner.Title" xml:space="preserve">
<value>Security Warning</value>
<comment>Title of the warning banner in the open URI dialog.</comment>
</data>
<data name="OpenUriWarningBanner.Message" xml:space="preserve">
<value>This link will open externally. Make sure you trust the source before proceeding.</value>
<comment>Warning message displayed in the banner when opening an external URI.</comment>
</data>
<data name="ReadableString_BytesString" xml:space="preserve">
<value> ({1:N0} bytes)</value>
<comment>Displays total number of bytes. Don't localize the "{1:N0}" part.</comment>