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

Local filesystem access ? #37

Closed
amoldeshpande opened this issue Aug 18, 2019 · 29 comments
Closed

Local filesystem access ? #37

amoldeshpande opened this issue Aug 18, 2019 · 29 comments
Labels
feature request feature request

Comments

@amoldeshpande
Copy link

amoldeshpande commented Aug 18, 2019

My use case is a Maps web control, displaying local pictures according to their GPS EXIF info. Will it be possible to use file:// URLs directly in WebView2 ?

AB#27073119

@liminzhu
Copy link
Member

File URL should work in WebView2

@verelpode
Copy link

In some environments, such as kiosks, local file URL's need to be disabled, so I suggest that WebView2 could have a setting to enable or disable local file URL's in the WebView2 equivalent of Windows.UI.Xaml.Controls.WebViewSettings (alongside IsIndexedDBEnabled and IsJavaScriptEnabled). However, this idea might be rejected as unnecessary because admittedly it is also possible to block URL schemes by setting WebViewNavigationStartingEventArgs.Cancel to true. A simple setting in WebViewSettings would be nice but people can also live without it.

@liminzhu
Copy link
Member

Can definitely visit the idea once there's enough use cases behind it. For now, I think navigationStarting event is exactly for this type of url blocking scenario :).

@amoldeshpande
Copy link
Author

I have verified that local filesystem does work. somewhat. I have to specify a full path.

I tried to specify a userDataDir to CreateWebView2EnvironmentWithDetails(), and then used "file://somefile.html" where somefile.html was in the EBWebView created by the control.

This failed to work, with a very useless WEBVIEW2_ERROR_UNKNOWN_ERROR in the navigationcompleted callback. I suppose the userDataDir is not meant to be a location for resources ?

Is there a way to bundle content in the "evergreen" version so that it is readble by the control ?

For example, I'd love the ability to specify the working directory of the control.

@verelpode
Copy link

verelpode commented Aug 21, 2019

@amoldeshpande

I'd love the ability to specify the working directory of the control.

What do you think of the following possible solution? Two new properties could be added to IWebView2Settings like this:

public HRESULT put_FileSchemeBaseUri(LPWSTR baseUri);
public HRESULT put_IsFileSchemeEnabled(BOOL isEnabled);

Regarding the choice of naming, I've tried to make it consistent with the following members of System.Uri:

public static bool TryCreate(System.Uri baseUri, string relativeUri, out System.Uri result);
public string Scheme { get; }
public bool IsFile { get; }

a very useless WEBVIEW2_ERROR_UNKNOWN_ERROR in the navigationcompleted callback.

I agree. It does make it frustrating/difficult to write code that correctly handles errors when the returned error codes are very vague or ambiguous. For example, with WebView 1, I wanted to make our app behave well when a WebView error is caused by a URI with invalid syntax or invalid characters, but the error code was just "unknown". I've not yet tested what error codes WebView2 returns in response to invalid URI's. The point is, yes, it'd be great if all occurrences of WEBVIEW2_ERROR_UNKNOWN_ERROR are replaced with specific error codes, before WebView2 exits the beta phase. But it's also completely understandable that the error codes are unfinished in the beta version.

@amoldeshpande
Copy link
Author

There are a couple of issues I can see with that API design:

  1. Requires the application to discover its install location (to point to bundled resources, for example) Most applications start in their install location as working directory, so it seems like an unnecessary onerous step.

  2. Does this design eliminate the ability to specify any other paths ? If yes, then that would really not be good for me, because I want to load photos from anywhere on the person's computer. If no, then there is a potential ambiguity with the control's working directory vs the above base path (e.g., does "foo.html" refer to a path relative to my base path or the controls' working directory ?) .

I suppose the idea of an API to turn off/on file scheme URIs is also redundant with being able to cancel navigation as suggested in the comment above.

@verelpode
Copy link

Requires the application to discover its install location (to point to bundled resources, for example) Most applications start in their install location as working directory

Good point. What if we say that FileSchemeBaseUri is allowed to be either a relative or absolute URI? (I mean "absolute" in the same sense as System.Uri.IsAbsoluteUri.) If it is a relative URI, then WebView2 would convert it to an absolute URI relative to the application package directory. What do you think?

Does this design eliminate the ability to specify any other paths ?

My intention was that whenever WebView2 navigates to a "file://" URI, it would check whether the URI is an absolute or relative URI, and if it is a relative URI, then it would use FileSchemeBaseUri and combine the URI's in the same manner as System.Uri.TryCreate to produce an absolute URI. However, I just realized that I'm uncertain whether or not System.Uri.IsAbsoluteUri has the capability to say whether a given "file://" URI is absolute or not, therefore I'll have to test this with System.Uri and see how it behaves.

I suppose the idea of an API to turn off/on file scheme URIs is also redundant with being able to cancel navigation as suggested in the comment above.

Is that definitely correct? It depends on whether or not file scheme URIs can be used elsewhere in WebView2 in a way that does NOT trigger the NavigationStarting event. For example, frames. However, just now I found this statement in the documentation:

"For subframes inside WebView, the only navigation event fired is the NavigationStarting event which gives host the ability to block subframe navigations."

@liminzhu
Copy link
Member

liminzhu commented Aug 21, 2019

@amoldeshpande

I have verified that local filesystem does work. somewhat. I have to specify a full path.

I tried to specify a userDataDir to CreateWebView2EnvironmentWithDetails(), and then used "file://somefile.html" where somefile.html was in the EBWebView created by the control.

userDataDir is where the browser store user data such as cookies, not a project root. If you are navigating to a file url, relative url will not work because the browser doesn't know what that url is relative to (as opposed to navigating to file://absolutePath/index.html, which includes to a resource relative to the html file location, that'd work). The easy way to deal with this is just using absolute urls when navigating to a local resource or making a helper method to append path to your urls.

Is there a way to bundle content in the "evergreen" version so that it is readble by the control ?

Yes, totally. You can bundle web content with your webview app and use absolute file path to navigate to it.

For example, I'd love the ability to specify the working directory of the control.

We can ofc consider an API to let app specify a root, but that's probably not that different from just building your own helper method. I can totally see where you're coming from with regard to the relative path though, so I think we should either include some documentation or expose an API for specifying root.

@amoldeshpande
Copy link
Author

I can totally see where you're coming from with regard to the relative path though, so I think we should either include some documentation or expose an API for specifying root.

I don't know that such an API would help much. To discover the "root", I'd have to figure out my application install location anyway. At which point I can just cache that and use it to reference all my bundled content.

Which is why being able to set a working directory for the browser process would be better. If the browser process always set its working directory (or the root for file:// URIs) to the working directory of the parent process, then users can specify either full paths or paths relative to their install.

@verelpode
Copy link

then users can specify either full paths or paths relative to their install.

I would like this feature, but it only works if there exists a way to determine whether a given "file://" URI is relative or absolute, and this is not quite as simple as I first thought, so my suggestion might have to be rejected, maybe. System.Uri doesn't report whether the file URI is relative or absolute. (I realize that WebView2 is internally written in C++ not C# but nevertheless a comparison with System.Uri is helpful.) Have a look at these results from .NET Framework 4.8:

System.Uri testUri;
bool isAbs;
testUri = new System.Uri("file://test-folder/hello.html");
isAbs = testUri.IsAbsoluteUri;   // Returns TRUE.

testUri = new System.Uri("file://C:/test-folder/hello.html");
isAbs = testUri.IsAbsoluteUri;   // Returns TRUE.

// Note the triple slash following.
testUri = new System.Uri("file:///test-folder/hello.html");
isAbs = testUri.IsAbsoluteUri;   // Returns TRUE.

testUri = new System.Uri("file://test-folder/hello.html", UriKind.RelativeOrAbsolute);
isAbs = testUri.IsAbsoluteUri;   // Returns TRUE.

// Following throws System.UriFormatException: "A relative URI cannot be created because the 'uriString' parameter represents an absolute URI."
testUri = new System.Uri("file://test-folder/hello.html", UriKind.Relative);

// Following throws System.UriFormatException: "Invalid URI: The hostname could not be parsed."
testUri = new System.Uri("file://./test-folder/hello.html");

However, considering that WebView2's internal code doesn't/can't use System.Uri anyway, WebView2 could interpret URI's however it wants to. Should WebView2 interpret file URI's as follows?

  • "file://test-folder/hello.html" = RELATIVE
  • "file://C:/test-folder/hello.html" = ABSOLUTE

Cross-platform issues come into consideration. So, maybe what @liminzhu said is the best solution:

The easy way to deal with this is just using absolute urls when navigating to a local resource or making a helper method to append path to your urls.

@verelpode
Copy link

verelpode commented Aug 21, 2019

What will happen if the UWP wrapper of WebView2 is navigated to a "ms-appx://" or "ms-appdata://" URI? Will it return an error or will it support these UWP URI's?
In the current UWP API's, a URI such as ms-appx:///myFile.png is treated as being relative to Windows.ApplicationModel.Package.InstalledLocation.

Various special folders are also accessible, for example ms-appdata:///temp/myFile.png refers to Windows.Storage.ApplicationData.TemporaryFolder.

There are 2 URI schemes there: "ms-appx" and "ms-appdata".

@amoldeshpande
Copy link
Author

However, considering that WebView2's internal code doesn't/can't use System.Uri anyway, WebView2 could interpret URI's however it wants to. Should WebView2 interpret file URI's as follows?

I think you're overthinking it. as long as WebView2 clearly documents what the working directory is the path does not matter.
If you pass the file path down to any OS function (CreateFile, for example), it is always relative to the current directory, unless you specify the OS's version of absolute paths. The OS knows how to open a given path.

Why would WebView2 need to figure out if the path is relative or absolute ? That could actually be dangerous (as path canonicalization can be a can of worms)
If the browser process is running in the right security context, none of that is an issue.

One other thing I am curious about is what happens when I bundle WebView2 in my app (instead of using the installed version). I presume now my paths will be relative to the install directory, which is not consistent with the other way.

@verelpode
Copy link

verelpode commented Aug 21, 2019

Why would WebView2 need to figure out if the path is relative or absolute ?

For example, if you pass the URI file://test-folder/hello.html to the IWebView2WebView.Navigate method, then it could respond in any one of these ways:

  1. Always treat the URI as absolute. In this case, it returns an error when you try to navigate to file://test-folder/hello.html because this URI doesn't specify any Windows drive letter or otherwise. Alternatively:
  2. Try to determine whether the URI is relative, and if it is relative, then WebView2 must convert the URI to an absolute path by prepending a base path. For example file://test-folder/hello.html might be converted to file://C:/ProgramData/xxxx/test-folder/hello.html in order to make it usable.
  3. Always treat the URI as relative. Always prepend a base path. Disallow absolute URI's.

That could actually be dangerous (as path canonicalization can be a can of worms)

Then I suppose that @liminzhu was right.

@liminzhu
Copy link
Member

@amoldeshpande

I don't know that such an API would help much. To discover the "root", I'd have to figure out my application install location anyway. At which point I can just cache that and use it to reference all my bundled content.

Which is why being able to set a working directory for the browser process would be better. If the browser process always set its working directory (or the root for file:// URIs) to the working directory of the parent process, then users can specify either full paths or paths relative to their install.

I might be missing something here. Setting the project root as I mentioned or the working directory for the browser process as you mentioned sound the same thing to me. The end result is app developers have to tell WebView where that root is, so that developer can use relative path for file navigation. Either way, it doesn't offer a lot of value considering a helper method is fairly easy.

@verelpode

What will happen if the UWP wrapper of WebView2 is navigated to a "ms-appx://" or "ms-appdata://" URI? Will it return an error or will it support these UWP URI's?

@david-risney and I were talking this earlier actually. In UWP world webview can know for sure where the app is installed, so maybe we can make UWP URI work. I will consult with the UWP team implementing the wrapper as well.

@liminzhu liminzhu added feature request feature request and removed question labels Aug 22, 2019
@amoldeshpande
Copy link
Author

I might be missing something here. Setting the project root as I mentioned or the working directory for the browser process as you mentioned sound the same thing to me.

To clarify my line of thought:
Setting the working directory implies that relative paths are relative to this directory. As I suggested above, this should be done by the WebView2 control, not the application. Because, again, asking each application to write code to do that is not great IMO. Absolute paths would still work in this case as I'm not implying any other change in URI handling.

Setting the project root would be equivalent if it only affects relative paths, and let's absolute pass behave as they currently do.

You can probably see that the two cases are not equivalent, though. When you set the working directory and don't do anything else, you leave the path resolution to the OS. If you have a custom API, and still want to support all paths, you will have to figure out if a path is relative or absolute, because you will presumably pass a canonicalized path to the browser control. This implies more code to write and test, which I'm always morally opposed to :)

If setting the project root acts as a filter for file:// URIs, then I'd rather we keep the current behavior. It's not great, but at least it doesn't block absolute paths.

@verelpode
Copy link

@liminzhu wrote:

david-risney and I were talking this earlier actually. In UWP world webview can know for sure where the app is installed, so maybe we can make UWP URI work.

I guess you already have the following technique in the list of possible solutions:

  1. When UWP WebView2.Navigate(System.Uri) is invoked, the UWP wrapper checks if Uri.Scheme equals ms-appx or ms-appdata.
  2. If it does equal either of those, then the UWP wrapper converts the URI to a file:// URI that Win32/C++ WebView2 supports.
  3. The UWP wrapper invokes IWebView2WebView.Navigate(LPCWSTR) and passes along the file:// URI.
  4. Thus, in this scenario, the Win32/C++ WebView2 wouldn't support ms-appx:// and ms-appdata:// but it'd never be given these URI's anyway because the UWP wrapper would convert these URI's to file:// URI's before passing the URI's along to Win32/C++ WebView2.

Obviously the more difficult question is how to handle the case where the ms-appx:// or ms-appdata:// URI appears inside a ".html" file. I suppose one of the possible solutions is the following:

  1. When the UWP wrapper is informed that the WebResourceRequested event is triggered, the UWP wrapper checks if the URI scheme equals ms-appx or ms-appdata.
  2. If it does equal either of those, then the UWP wrapper converts the URI to a file:// URI, and invokes IWebView2WebResourceRequestedEventArgs.put_Response with a Response that specifies Windows.Web.Http.HttpStatusCode.PermanentRedirect (308) in order to redirect to the file:// URI.
  3. Alternatively, instead of invoking put_Response, the UWP wrapper could invoke a new member of IWebView2WebResourceRequestedEventArgs named something like put_InternalRedirectUri.
  4. When Win32/C++ WebView2 receives the InternalRedirectUri string, it performs the redirection/translation in an "invisible" manner that doesn't cause IWebView2NavigationStartingEventArgs.IsRedirected to become true whenever ms-appx:// or ms-appdata:// URI's are used, and so forth.

Considering the details of how to make the above redirection operate "invisibly" and reliably, I wonder whether a simpler solution would be to make Win32/C++ WebView2 support ms-appx:// and ms-appdata:// but only when the necessary folder paths are supplied via IWebView2Settings (as absolute path strings). Thus the following properties (all of type LPWSTR) could be added to IWebView2Settings:

  • ApplicationPackagePath == Windows.ApplicationModel.Package.InstalledLocation == ms-appx:///
  • ApplicationDataPath_RoamingSettings == Windows.Storage.ApplicationData.RoamingFolder == ms-appdata:///roaming/
  • ApplicationDataPath_NonRoamingSettings == Windows.Storage.ApplicationData.LocalFolder == ms-appdata:///local/
  • ApplicationDataPath_Temporary = Windows.Storage.ApplicationData.TemporaryFolder == ms-appdata:///temp/

@verelpode
Copy link

@amoldeshpande -- When WebView2 is out of beta, or when the wrappers are released, are you planning to use WebView2 in a UWP, WPF, or WinForms app? I ask because if you're planning to use it in UWP, then you could get what you want by using ms-appx:/// instead of file://.

If you're not planning to use UWP, then you might still be able to use ms-appx:/// anyway, depending on whether WebView2's code for processing ms-appx:/// URI's will be located in the Win32/C++ WebView2 component or only in the UWP wrapper.

asking each application to write code to do that is not great IMO.

Although I agree in general, I'd also find it understandable and reasonable if the Win32/C++ version of WebView2 requires more manual configuration than the UWP version of WebView2. The UWP wrapper is expected to add an extra layer of friendliness and convenience atop the lower-level Win32/C++ WebView2 layer.

@amoldeshpande
Copy link
Author

@verelpode I'm writing a purely C++, Win32(for now) app. You're right, if I was using UWP I wouldn't have this problem.

@amoldeshpande
Copy link
Author

amoldeshpande commented Aug 22, 2019

BTW I don't know if there is anything sensitive info in the MSEdge install directory, but a WebView2 control can basically read anything under C:\Program Files (x86)\Microsoft\Edge Dev\Application\78.0.249.1 on my computer.
ETA: I suppose local filesystem access gives it access to anything, but perhaps this is worth thinking about, especially compared to CEF. I'm not sure I'd want random websites to be able to access any content on my drive.

@james-wang-atx
Copy link

would love support for
https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.webview.navigatetolocalstreamuri?view=winrt-18362

@james-wang-atx
Copy link

based on example:
////////////////////////////////////////////////////////
// Step 4 - Navigation events
// register an ICoreWebView2NavigationStartingEventHandler to cancel any non-https navigation
EventRegistrationToken token;
webviewWindow->add_NavigationStarting(Callback(
[](ICoreWebView2* webview, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT {
PWSTR uri;
args->get_Uri(&uri);
std::wstring source(uri);
if (source.substr(0, 5) != L"https") {
args->put_Cancel(true);
}
CoTaskMemFree(uri);
return S_OK;
}).Get(), &token);

...this could allow the host application to "intercept"(hook) the navigation and respond with special response... e.g., something like args->put_Result(string_value).
for us, this would be a viable substitute for "navigateToLocalStreamUri"

@david-risney
Copy link
Contributor

There's a lot of great feature requests in this issue.

We will want to look into some kind of ms-appdata/ms-appx URI scheme likely with a configurable root path and that should also work in Win32.

We will also want something like NavigateToLocalStreamUri where you can implement a callback or event to resolve custom URI schemes into data streams.

In the interim as a workaround you can use file URIs and WebResourceRequested.

Regarding relative file URIs: Rather than change our interpretation of file URIs we'd prefer a solution like ms-appdata/ms-appx where we can have a default root and the path of the URI is relative to that. We might vary the default depending on UWP vs Win32 and make the root configurable.

Regarding access to file URIs: The current behavior matches the browser. If you're not running in an app container and your process has access to most files on the disk, the WebView2 will also. This is just like the browser, but just like the browser, websites cannot read the contents of one another or arbitrary files on your disk (without explicit intervention by the end user via a file control and so on) from the same origin policy.

@dominikhasiwar
Copy link

NavigateToLocalStreamUri in combination with a IUriToStreamResolver would be really nice!

@carlsound
Copy link

Is it possible to use the Win32 C++ NavigateToString(LPCWSTR htmlContent) method to load an HTML file embedded as a .rc resource?

@champnic
Copy link
Member

champnic commented Nov 2, 2020

@carlsound You should be able to, assuming you can get the html string out of resources in your host app to send to the WebView2. Are you running into a problem? If so, could you open a new issue about it?

@carlsound
Copy link

carlsound commented Nov 3, 2020

This worked:
`HRSRC hSrc = FindResource(NULL, MAKEINTRESOURCE(IDR_HTML1), RT_HTML);
if (hSrc != NULL)
{
HGLOBAL hHeader = LoadResource(NULL, hSrc);
if (hHeader != NULL)
{
char* rString = (char*)(LockResource(hHeader));
size_t rStringSizeT = strlen(rString) + 1;

    wchar_t*  wcstring = new wchar_t[rStringSizeT];
	size_t convertedChars = 0;
	
    mbstowcs_s(&convertedChars, wcstring, rStringSizeT, rString, _TRUNCATE);
	
	if(wcstring != NULL)
	{
		//webviewWindow->NavigateToString(lpcwHtml);
		webviewWindow->NavigateToString(wcstring);
	}
	UnlockResource(hHeader);
}
FreeResource(hHeader);

}`

@carlsound
Copy link

Is it possible for HTML to reference a .rc embedded image resource in a Win32 C++ application?

@champnic
Copy link
Member

champnic commented Nov 3, 2020

Hm, unfortunately I'm not sure if this is possible currently. The regular answer would be to capture the request for the image through the WebResourceRequested event and then provide the image from the resource, but there seems to be a bug where WebResourceRequested event doesn't get fired for HTML in NavigateToString. That issue is tracked in #530.

Do you have control over the HTML page that you are loading? As a workaround, you may be able to pass the image to the HTML through other mechanisms, such as WebMessageReceived/postMessage + ExecuteScript, and/or by using a host object.

@champnic
Copy link
Member

champnic commented Jan 30, 2021

We have improved support for local file access using the SetVirtualHostNameToFolderMapping API available in 1.0.781-prerelease SDK. Please give it a try and let us know if it isn't working for you. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request feature request
Projects
None yet
Development

No branches or pull requests

8 participants