Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #5523 MauiWebView not loading local files on Windows #7672

Merged
merged 4 commits into from
Jun 21, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions src/Core/src/Platform/Windows/MauiWebView.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.Maui.ApplicationModel;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel;

namespace Microsoft.Maui.Platform
{
public class MauiWebView : WebView2, IWebViewDelegate
{
public MauiWebView()
{
NavigationStarting += (sender, args) =>
{
// Auto map local virtual app dir host, e.g. if navigating back to local site from a link to an external site
if (args?.Uri?.ToLowerInvariant().StartsWith(LocalScheme.TrimEnd('/').ToLowerInvariant()) == true)
{
CoreWebView2.SetVirtualHostNameToFolderMapping(
LocalHostName,
ApplicationPath,
Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
}
// Auto unmap local virtual app dir host if navigating to any other potentially unsafe domain
else
{
CoreWebView2.ClearVirtualHostNameToFolderMapping(LocalHostName);
}
};
}

WebView2? _internalWebView;

const string LocalScheme = "ms-appx-web:///";
// Arbitrary local host name for virtual folder mapping
const string LocalHostName = "appdir";
breenbob marked this conversation as resolved.
Show resolved Hide resolved
const string LocalScheme = $"https://{LocalHostName}/";

// Script to insert a <base> tag into an HTML document
const string BaseInsertionScript = @"
Expand All @@ -19,11 +43,19 @@ public class MauiWebView : WebView2, IWebViewDelegate
head.innerHTML = 'baseTag' + head.innerHTML;
}";

// Allow for packaged/unpackaged app support
string ApplicationPath => AppInfoUtils.IsPackagedApp
? Package.Current.InstalledLocation.Path
: AppContext.BaseDirectory;

public async void LoadHtml(string? html, string? baseUrl)
{
var mapBaseDirectory = false;

if (string.IsNullOrEmpty(baseUrl))
{
baseUrl = LocalScheme;
mapBaseDirectory = true;
}

// Generate a base tag for the document
Expand All @@ -36,7 +68,7 @@ public async void LoadHtml(string? html, string? baseUrl)
_internalWebView = new WebView2();

// TODO: For now, the CoreWebView2 won't be created without either setting Source or
// calling EnsureCoreWebView2Async().
// calling EnsureCoreWebView2Async().
await _internalWebView.EnsureCoreWebView2Async();

// When the 'navigation' to the original HTML string is done, we can modify it to include our <base> tag
Expand All @@ -55,6 +87,14 @@ public async void LoadHtml(string? html, string? baseUrl)

await EnsureCoreWebView2Async();

if (mapBaseDirectory)
{
CoreWebView2.SetVirtualHostNameToFolderMapping(
LocalHostName,
ApplicationPath,
Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a potential security problem here.

Consider this scenario:

  1. App uses MAUI WebView and calls LoadHtml("some html blah blah", baseUrl: null);
  2. This causes MAUI to load the specified HTML, and enable local file mapping for the app, thus enabling the WebView to load arbitrary files from within the app contents. This part is pretty much fine, because it's generally assumed that if you're loading HTML yourself, you know what's in it, and that it's considered sanitized and safe
  3. Later, the app calls some Navigate API on the WebView, causing the WebView to go to some other URL (say, https://malware.example.com
  4. But because the mapping is still enabled, I believe this could cause the malicious site to load arbitrary content from within the app. I'm not sure what access the malicious site would have to the contents of what it loaded, but it could potentially cause unintended things to be invoked.

So, I think this change needs to be aware of navigation events and that the access/mapping be changed when the WebView navigates away from one of the "safe" locations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I'll change it to hook into Navigation events, check target URL for the local hostname OnNavigating, and call CoreWebView2.ClearVirtualHostNameToFolderMapping(..) if attempting to navigate to any other URL. I guess I will need to re-enable that mapping on any attempt to navigate back to the local host URL, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@breenbob yeah I think some logic like that would fix this. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, have added this and tested. Looks like a) it works and b) it was needed.

To test I created a sample web page that I hosted somewhere, with the following body:

<body>
    <h3>Test Page</h3>
    <p>I am a remote page.</p>
	<iframe src="https://appdir/simple.html" height="200" width="300" title="App Dir"></iframe>
    <br />
    <a href="https://appdir/simple.html">Link back</a>
</body>

I added a link to this URL to my simple.html test page.

With the code changes applied, my test page loads fine, as before. I click the link to the external site, that loads fine, but the iframe pointing to local appdir simple html file fails to load:
image

Good so far. I click the link to take me back to https://appdir/simple.html (i.e. so I am navigating forwards, not using back button) and it works fine.

I am conscious that there are potentially multiple calls to Map the virtual hostname now, as it is done in LoadUrl/Html and in NavigationStarted, but it doesn't seem to cause any issue, and unmapping once still unmaps. I had tried to track its status with a boolean flag but got a bit unwieldy, and WebView2 has no method to check what hosts are mapped.

Only peculiar thing I noticed, as you can see in this gif, is that when navigating back both the NavigationFailed and NavigationSucceeded events fire in the WebViewHandler.Windows.cs. Failed first, with host name unknown, then succeeded. Actual navigation works fine.
devenv_yMvWWWanve

Out of curisoity, I tried loading the external page with the call to ClearVirtualHostNameToFolderMapping in NavigationStarted commented out, just to see, and the iframe is now able to load the appdir simple html page:
image

So clearing the virtual host mapping is 100% necessary. Hopefully good to go now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we good to go?

}

// Set the HTML for the 'real' WebView to the updated HTML
NavigateToString(!string.IsNullOrEmpty(htmlWithBaseTag) ? htmlWithBaseTag : html);

Expand All @@ -66,12 +106,19 @@ public async void LoadHtml(string? html, string? baseUrl)
_internalWebView.NavigateToString(html);
}

public void LoadUrl(string? url)
public async void LoadUrl(string? url)
{
Uri uri = new Uri(url ?? string.Empty, UriKind.RelativeOrAbsolute);

if (!uri.IsAbsoluteUri)
{
await EnsureCoreWebView2Async();

CoreWebView2.SetVirtualHostNameToFolderMapping(
LocalHostName,
ApplicationPath,
Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);

uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
}

Expand Down