mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-12 23:36:19 +01:00
Compare commits
4 Commits
stable
...
leilzh/pee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
250d2df6f5 | ||
|
|
f495604339 | ||
|
|
ec88896839 | ||
|
|
2ea716ace7 |
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user