Web-to-App Communication: DirectInvoke

Note: This post is part of a series about Web-to-App Communication techniques.

Background

Typically, if you want your website to send a document to a client application, you simply send the file as a download. Your server indicates that a file should be treated as a download in one of a few simple ways:

  • Specifying a nonwebby type in the Content-Type response header.
  • Sending a Content-Disposition: attachment; filename=whatever.ext response header.
  • Setting a download attribute on the hyperlink pointing to the file.

These approaches are well-supported across browsers (via headers for decades, via the download attribute anywhere but IE since 2016).

The Trouble with Plain Downloads

However, there’s a downside to traditional downloads — unless the file itself contains the URL from which the download originated, the client application will not typically know where the file originated, which can be a problem for:

  • Security – “Do I trust the source of this file?
  • Functionality – “If the user makes a change to this file, to where should I save changes back?“, and
  • Performance – “If the user already had a copy of this 60mb slide deck, maybe skip downloading it again over our expensive trans-Pacific link?

Maybe AppProtocols?

Rather than sending a file download, a solution developer might instead just invoke a target application using an App Protocol. For instance, the Microsoft Office clients might support a syntax like:

ms-word:ofe|u|https://example.com/docx.docx

…which directs Microsoft Word to download the document from example.com.

However, the AppProtocol approach has a shortcoming– if the user doesn’t happen to have Microsoft Word installed, the protocol handler will fail to launch and either nothing will happen or the user may get a potentially confusing error message. That brokenness will occur even if they happen to have another client (e.g. WordPad) that could handle the document.

DirectInvoke

To address these shortcomings, we need a way to instruct the browser: “Download this file, unless the client’s handler application would prefer to just get its URL.” Internet Explorer and Microsoft Edge support such a technology.

While a poorly-documented precursor technology existed as early as the late 1990s[1], Windows 8 reintroduced this feature as DirectInvoke. When a client application registers itself indicating that it supports receiving URLs rather than local filenames, and when the server indicates that it would like to DirectInvoke the application using the X-MS-InvokeApp response header:

DirectInvoke

…then the download stream is aborted and the user is instead presented with a confirmation prompt:

UIPrompt

If the user accepts the prompt, the handler application is launched, passing the URL to the web content.

Mechanics of Launch

The browser launches the handler by calling ShellExecuteEx, passing in the SEE_MASK_CLASSKEY flag, with the hkeyClass set to the registry handle retrieved from IQueryAssociations::GetKey when passed ASSOCKEY_SHELLEXECCLASS for the DirectInvoke’d resource’s MIME type.

Note: This execution will fail if security software on the system breaks ShellExecuteEx‘s support for SEE_MASK_CLASSKEY. As of September 2021, “HP’s Wolf Security” software (version 4.3.0.3074) exhibits such a bug.

Application Opt-in

Apps can register to handle URLs via the SupportedProtocols declaration for their verbs. HKCR\Applications\<app.exe>\SupportedProtocols or HKCR\CLSID\<verb handler clsid>\SupportedProtocols can be populated using values that identify the Uniform Resource Identifier (URI) protocol schemes that the application supports or * to indicate all protocols. Windows Media Player’s verbs registration looks like this:

HKCR\CLSID\ 
{45597c98-80f6-4549-84ff-752cf55e2d29}\SupportedProtocols
    rtspt    REG_SZ
    rtspu    REG_SZ
    rtsp    REG_SZ
    mms    REG_SZ
    http    REG_SZ

Apps registered to handle URLs via the old UseUrls mechanism can be easily enumerated from the command line:

reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths" /f "UseUrl" /s

Now, for certain types, the server doesn’t even need to ask for DirectInvoke behavior via the X-MS-InvokeApp header. The FTA_AlwaysUseDirectInvoke bit can be set in the type’s EditFlags registry value. The bit is documented on MSDN as: 

FTA_AlwaysUseDirectInvoke 0x00400000
Introduced in Windows 8. Ensures that the verbs for the file type are invoked with a URL instead of a downloaded version of the file. Use this flag only if you’ve registered the file type’s verb to support DirectInvoke through the SupportedProtocols or UseUrl registration.

Microsoft’s ClickOnce deployment technology makes use of the FTA_AlwaysUseDirectInvoke flag.

A sample registry script for a type that should always be DirectInvoke’d might look like this:

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.fuzzle]
"Content Type"="application/x-fuzzle"
@="FuzzleProgID"
[HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/x-fuzzle]
"Extension"=".fuzzle"
[HKEY_CLASSES_ROOT\FuzzleProgID]
@="FakeMIME for Testing DirectInvoke"
"EditFlags"=dword:00410000
[HKEY_CLASSES_ROOT\FuzzleProgID\shell]
[HKEY_CLASSES_ROOT\FuzzleProgID\shell\open]
@=""
[HKEY_CLASSES_ROOT\FuzzleProgID\shell\open\command]
@="C:\\windows\\alert.exe \"%1\""
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\alert.exe]
"UseURL"=dword:00000001

To test it, first set up the registry, install a handler to C:\Windows, and then click this example link

TraditionalVsDI.png

Requiring ReadOnly Behavior

To accommodate scenarios where the server wants to communicate to the client that the target URL should be considered “read only” (for whatever meaning the client and server have for that concept), an additional token can be added to the X-MS-Invoke-App header, RequireReadOnly:

undefined

When this token is present, the Windows Shell verb used to invoke the handler for the URL is changed from Open to OpenAsReadOnly

However, crucially, the handler’s registration for that type must advertise support that verb– if it doesn’t, the DirectInvoke request will be ignored (even if FTA_AlwaysUseDirectInvoke was specified) and the file will be treated as a traditional download.

If you update your registry to claim support for that verb:

undefined

… you’d find that the scenario starts working again. Of course, for deployability reasons, it’s probably more straightforward to remove the RequireReadOnly directive if you do not expect your client application to support that verb.

Caveats

In order for this architecture to work reliably, you need to ensure a few things.

App Should Handle Traditional Files

First, your application needs to have some reasonable experience if the content is provided as a traditional (non-DI) file download, as it would be using Chrome or Firefox, or on a non-Windows operating system.

By way of example, it’s usually possible to construct a ClickOnce manifest that works correctly after download. Similarly, Office applications work fine with regular files, although the user must take care to reupload the files after making any edits.

App Should Avoid Depending On Browser State

If your download flow requires a cookie, the client application will not have access to that cookie and the download will fail. The client application probably will not be able to prompt the user to login to otherwise retrieve the file.

If your download flow requires HTTP Authentication or HTTPS Client Certificate Authentication, the client application might work (if it supports NTLM/Negotiate) or it might not (e.g. if the server requires Digest Auth and the client cannot show a credential prompt.

App Should Ensure Robust URL Support

Many client applications have limits in the sorts of URLs that they can support. For instance, the latest version of Microsoft Excel cannot handle a URL longer than 260 characters. If a .xlsx download from SharePoint site attempts to DirectInvoke, Excel will launch and complain that it cannot retrieve the file.

App Should Ensure Network Protocol Support

Similarly, if the client app registers for DirectInvoke of HTTPS URLs, you should ensure that it supports the same protocols as the browser. If a server requires a protocol version (e.g. TLS/1.2) that the client hasn’t yet enabled (say it only enables TLS/1.0), then the download will fail.

Server Must Not Send |Content-Disposition: attachment|

As noted in the documentation, a Content-Disposition: attachment response header takes precedence over DirectInvoke behavior. If a server specifies attachment, DirectInvoke will not be used.

Note: If you wish to use a Content-Disposition header to set the default name for the file, you can do so using Content-Disposition: inline; filename="fuzzle.fuzzle"

Conclusion

As you can see, there’s quite a long list of caveats around using the DirectInvoke WebToApp communication scheme, but it’s still a useful option for some scenarios.

In future posts, I’ll continue to explore some other alternatives for Web-to-App communication.

-Eric

Note: Edge 79 didn’t support FTA_AlwaysUseDirectInvoke, but this was fixed in a later release.

Note: I have a few test cases.

[1] Archaeology fun fact: fossils of the Win2K era mechanism are hard to find. SEE_MASK_FILEANDURL was once used by clients including InfoPath.

Published by ericlaw

Impatient optimist. Dad. Author/speaker. Created Fiddler & SlickRun. PM @ Microsoft 2001-2012, and 2018-, working on Office, IE, and Edge. Now a GPM for Microsoft Defender. My words are my own, I do not speak for any other entity.

One thought on “Web-to-App Communication: DirectInvoke

Leave a comment