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

Conversation

breenbob
Copy link
Contributor

@breenbob breenbob commented Jun 1, 2022

Description of Change

Modified the MauiWebView control on Windows to map the Application AppX install directory to a virtual folder using the WebView2.CoreWebView.SetVirtualHostNameToFolderMapping method.

The existing implementation used the "ms-appx-web:///" URI scheme, which according to these issues is not being implemented in WebView2 for WinUI3.
microsoft/microsoft-ui-xaml#1967
MicrosoftEdge/WebView2Feedback#37

Issues Fixed

  • Maui WebView on Windows won't load local HTML files marked with Build action "MauiAsset". For me, the Maui GA release never loads even a simple HTML file via this method, and I have tested extensively on multiple Windows machines.

Fixes #5523

@dnfadmin
Copy link

dnfadmin commented Jun 1, 2022

CLA assistant check
All CLA requirements met.

@breenbob breenbob force-pushed the fix-5523 branch 2 times, most recently from 6f4ae3e to afb2939 Compare June 1, 2022 19:05
@breenbob
Copy link
Contributor Author

breenbob commented Jun 1, 2022

For the record I have noticed a related issue in WebView2 that still prevents this from working in all scenarios. In WinUI3 I've found that a complex client-side app will somtimes fail to load properly, no errors in console or failed network requests, but it behaves as though it has failed to load files being fetched via xhr requests.

After doing some digging, I discovered that this issue is resolved if you install Edge from the Beta channel, so I assume a fix for that is in the pipeline in WebView2 package and will be released in next cycle. I think what I've implemented here is independent of that and this shouldn't prevent it being merged?

@Eilon Eilon added the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label Jun 1, 2022
@mattleibow
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@rmarinho
Copy link
Member

rmarinho commented Jun 2, 2022

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@breenbob
Copy link
Contributor Author

breenbob commented Jun 3, 2022

Noticed on original issue @jsuarezruiz added an example of loading a local MauiAsset file that does work on Windows. Whilst this works, it requires code in the page codebehind to fetch the asset file contents and load as a string into the WebView. This PR would allow local files to work with no code required from the user, keeping it consistent with the other platforms, and the WebView documentation.

@samhouts samhouts added the partner/cat 😻 this is an issue that impacts one of our partners or a customer our advisory team is engaged with label Jun 14, 2022
@Redth Redth requested a review from mattleibow June 17, 2022 13:19
@Redth
Copy link
Member

Redth commented Jun 17, 2022

@mattleibow does this need any changes for unpackaged support?

@Redth Redth added this to the 6.0-sr2 milestone Jun 17, 2022
@mattleibow
Copy link
Member

mattleibow commented Jun 17, 2022

I think so as the Package type will throw.

This can easily be tested by setting the WindowsPackageType to None and editing the commandName value in the launchSettings.json from msixPackage to project.

I collected the types I know plus instructions here #3166

@breenbob
Copy link
Contributor Author

Thanks @mattleibow & @Redth. I will make the changes and push another commit for review.

@breenbob
Copy link
Contributor Author

@mattleibow Is there a way to check at runtime if the current Application is packaged or unpackaged? e.g. will this work?

if (Package.Current == null)
{  //use AppContext.BaseDirectory }

@mattleibow
Copy link
Member

That will throw an exception, so you catch that.

We do this in essentials: https://github.com/dotnet/maui/blob/main/src/Essentials/src/AppInfo/AppInfo.uwp.cs#L46-L64

@breenbob breenbob requested a review from a team as a code owner June 19, 2022 09:48
@breenbob
Copy link
Contributor Author

OK have made the changes for Unpackaged app support and successfully tested in both packaged/unpackaged samples. To test, I hijacked the credits page on the TestRunner project to show a local sample HTML page from /Resources/Raw folder, set as MauiAsset.
devenv_Tw0i9NnGGm

  <ItemGroup>
    ...
    <!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
  </ItemGroup>

I also removed the Navigating event, as for some reason this was cancelling navigation and trying to open the URI provided in the external browser in the codebehind. This means the regular credits.html embedded resource in dotnet maui/main never loads, by the way.
image

Packaged app:
image
image

Unpackaged app:
image
image

Had some trouble getting the Unpackaged app to run following steps at your link as kept running into this issue but finally got it work with the following configuration using an X64 build configuration (as can't run Any CPU in self contained mode):

  <PropertyGroup Condition="$(TargetFramework.Contains('-windows'))">
    <WindowsPackageType>None</WindowsPackageType>
    <AppxPackage>false</AppxPackage>
    <PublishAppxPackage>false</PublishAppxPackage>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
  </PropertyGroup>

@mattleibow can you review, please?

@mattleibow
Copy link
Member

Come thing is iffy with this PR. Billions of changes.

I am wondering where it is supposed to go and maybe a merge happened from a wrong branch?

@breenbob
Copy link
Contributor Author

breenbob commented Jun 20, 2022

@mattleibow Have tidied it up.

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?

@MollsAndHersh
Copy link

MollsAndHersh commented Jun 20, 2022

@breenbob Quick question: will your fix also allow local .mp4 files to be loaded into <video src="myvideo.mp4"></video> tags? At the moment, I get nothing when trying to do this no matter where I put the .mp4 file into any standard location (ie: /resources/raw or /resources or /resources/assets, etc.) and setting file to use "mauiAsset". Thoughts? Basically what I've ended up doing is to loadup the webview with content I grab using the file system via stream reader into a HtmlWebViewSource... that html code has the referenced <video> element set the src="myvideo.mp4". Unfortunately, I get nothing into the video player.

@breenbob
Copy link
Contributor Author

breenbob commented Jun 20, 2022

@breenbob Quick question: will your fix also allow local .mp4 files to be loaded into <video src="myvideo.mp4"></video> tags? At the moment, I get nothing when trying to do this no matter where I put the .mp4 file into any standard location (ie: /resources/raw or /resources or /resources/assets, etc.) and setting file to use "mauiAsset". Thoughts? Basically what I've ended up doing is to loadup the webview with content I grab using the file system via stream reader into a HtmlWebViewSource... that html code has the referenced <video> element set the src="myvideo.mp4". Unfortunately, I get nothing into the video player.

@MollsAndHersh Yes this PR should allow that to succeed, provided the HTML file that is trying to load the video is also local, not just the video file itself. Also important is that the local HTML page doesn't have a <base /> element pointing to some other URL, as existing code in MauiWebView checks for this and won't map the app install directory for serving files if it finds it.

As I understand it, anything with MauiAsset set on its Build Action will copy to the root of the app directory on compilation, and so could be loaded in the WebView with a relative URL after this PR goes in. What you have to be careful with is if you have a folder structure for your files. If so, you should really use the /Resources/Raw folder to maintain that folder structure, by ensuring this entry is in your Maui app csproj:

<ItemGroup>
    <!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
  </ItemGroup>

This copies anything from /Raw folder to the app installation root directory, but maintains the subfolder structure.

This PR has been tagged for SR2 release which has a target completion date of Wednesday, so with any luck, will be available soon!

@rmarinho
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@rmarinho
Copy link
Member

If we want this on sr2 it should be target net6.0 branch

@breenbob breenbob changed the base branch from main to net6.0 June 21, 2022 11:02
@breenbob
Copy link
Contributor Author

@rmarinho Done - now up to date with the net6.0 branch.

@rmarinho
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@breenbob breenbob requested a review from Eilon June 21, 2022 16:43
Copy link
Member

@Eilon Eilon left a comment

Choose a reason for hiding this comment

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

Pretty sure this looks good, but I'm not an expert on MAUI's WebView.

@rmarinho / @Redth - any concern about getting this PR in for SR2 for our partner?

@MollsAndHersh
Copy link

MollsAndHersh commented Jun 21, 2022

Thanks for getting back so quickly to me @breenbob. I'm excited to try this out :) I'm wondering exactly "how" I pull down the latest changes into my project so I get the new updated WebView code? Do I just grab the newest Maui on a Nuget feed? Should I just install the latest Visual Studio preview? (Currently I'm on Microsoft Visual Studio Community 2022 (64-bit) - Preview Version 17.3.0 Preview 2.0.)

Or do I use Maui-Check?

I'm sorta lost on the correct way forward. Can you provide me any clues?

Thanks.

@rmarinho rmarinho merged commit 8eba70e into dotnet:net6.0 Jun 21, 2022
@breenbob
Copy link
Contributor Author

breenbob commented Jun 22, 2022

@MollsAndHersh When the SR2 package is released yeah it's just a matter of updating the Maui Nuget packages to the latest version. SR1 is version 6.0.400, so it will be the next version after that.

You don't need to wait for this though, you can work around this in your code by using the handlers:

In xaml of your page:

<WebView
    HandlerChanged="WebView_HandlerChanged"
    Source="MyLocalMauiAssetPage.html" />

You should continue setting Source on the WebView in Xaml so that your page continues to load on the Android/iOS. Adding the below code to your page code behind will then allow it to also load on Windows:

#if WINDOWS
    private static readonly System.Lazy<bool> _isPackagedAppLazy = new System.Lazy<bool>(() =>
    {
        try
        {
            if (Windows.ApplicationModel.Package.Current != null)
                return true;
        }
        catch
        {
            // no-op
        }

        return false;
    });

    private static bool IsPackagedApp => _isPackagedAppLazy.Value;

    // Allow for packaged/unpackaged app support
    string ApplicationPath => IsPackagedApp
        ? Windows.ApplicationModel.Package.Current.InstalledLocation.Path
        : System.AppContext.BaseDirectory;
#endif

    private async void WebView_HandlerChanged(object sender, EventArgs e)
    {
#if WINDOWS
        await ((sender as WebView).Handler.PlatformView as Microsoft.Maui.Platform.MauiWebView).EnsureCoreWebView2Async();
        ((sender as WebView).Handler.PlatformView as Microsoft.Maui.Platform.MauiWebView).CoreWebView2.SetVirtualHostNameToFolderMapping(
                "localhost",
                ApplicationPath,
                Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
        ((sender as WebView).Handler.PlatformView as Microsoft.Maui.Platform.MauiWebView).LoadUrl($"https://localhost/MyLocalMauiAssetPage.html");
#endif
    }

Just replace MyLocalMauiAssetPage.html with the name of your HTML page you are trying to load your video from and that should be it. This page must be marked as Build Action = MauiAsset, along with any other dependent files such as your video file - I refer to this bit from further up regarding having this in your single project .csproj file to maintain a folder structure for copied files:

<ItemGroup>
    <!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

Once the new Nugets are released and you update to them, you should be able to remove this code and just stick with Source="mypage.html" on your WebView.

@MollsAndHersh
Copy link

MollsAndHersh commented Jun 22, 2022

@breenbob Beautiful... this works now.

Thank you so much!!!

For anyone else reading this, I had to make two small changes in order to get the code to compile...

  1. had to use the fully qualified path in: "if (Windows.ApplicationModel.Package.Current != null)"
  2. had to remove the await in: "await ((sender as WebView).Handler.PlatformView as Microsoft.Maui.Platform.MauiWebView).CoreWebView2.SetVirtualHostNameToFolderMapping("localhost", ApplicationPath, Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);
    "

Thanks again Bob... very grateful.

@veikkoeeva
Copy link

veikkoeeva commented Jul 2, 2022

Looking at the PR comments regarding security, would it be possible to modify the files locally and so consequently make the application load this modified file? That is, I wonder how the files are stored in the systems and signed in a way or another.

I know this is a more general question. I'm in fact trying to find documentation regarding this on:

  • How a Maui application stores files, any files? I suppose tamper detection (checking hashes f.ex.) needs to be done by the app developer.

I would've actually asked also about the same thing as @Eilon hadn't it been asked already. :) So, I don't intend to hijack this thread, but wonder where could one check these things. I can't seem to find documentation regarding these issues. I can imagine these guestions are out of scope of Maui documentation. (I have found various soures explaining tampering in Android, detecting debugger/debuggability, SafetyNet etc.)

@Eilon
Copy link
Member

Eilon commented Jul 5, 2022

@veikkoeeva in general, if the application's files can be modified, then that's a different problem, if it's even a problem at all. There's usually no concern about a user modifying their own app because they could just do whatever they want to begin with! They don't have to "hack" their own app if they could just do the "malicious" thing to begin with. The main concern is if an app can be "tricked" into running someone else's code, or if you deliberately have the app run someone else's code with limited privileges, you want to prevent that code from elevating its privileges and gaining access to more than you intended.

@github-actions github-actions bot locked and limited conversation to collaborators Dec 21, 2023
@Eilon Eilon removed the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label May 10, 2024
@samhouts samhouts added the fixed-in-6.0.408 Look for this fix in 6.0.408! label Aug 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-controls-webview WebView fixed-in-6.0.408 Look for this fix in 6.0.408! partner/cat 😻 this is an issue that impacts one of our partners or a customer our advisory team is engaged with platform/windows 🪟
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Html from Local File in Windows