|
//---------------------------------------------------------------------------
//
// <copyright file="OleCmdHelper.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
// This is a helper class used for interop to process the
// IOleCommandTarget calls in browser hosting scenario
//
// History:
// 06/09/03: kusumav Moved from Application.cs to separate file.
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Threading;
using System.Windows;
using System.Security;
using System.Security.Permissions;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Navigation;
using System.Windows.Controls;
using MS.Internal.Documents; // DocumentApplicationDocumentViewer
using MS.Internal.PresentationFramework; // SecurityHelper
using MS.Internal.KnownBoxes;
using MS.Win32;
namespace MS.Internal.AppModel
{
#region OleCmdHelper class
// <summary>
// OleCmd helper class for processing IOleCommandTarget calls in browser hosting scenario
// </summary>
internal sealed class OleCmdHelper : MarshalByRefObject
{
internal const int
OLECMDERR_E_NOTSUPPORTED = unchecked((int)0x80040100),
OLECMDERR_E_DISABLED = unchecked((int)0x80040101),
OLECMDERR_E_UNKNOWNGROUP = unchecked((int)0x80040104);
internal const uint CommandUnsupported = 0;
internal const uint CommandEnabled = (uint)(UnsafeNativeMethods.OLECMDF.OLECMDF_ENABLED | UnsafeNativeMethods.OLECMDF.OLECMDF_SUPPORTED);
internal const uint CommandDisabled = (uint)UnsafeNativeMethods.OLECMDF.OLECMDF_SUPPORTED;
// IMPORTANT: Keep this in sync with wcp\host\inc\hostservices.idl
internal static readonly Guid CGID_ApplicationCommands = new Guid(0xebbc8a63, 0x8559, 0x4892, 0x97, 0xa8, 0x31, 0xe9, 0xb0, 0xe9, 0x85, 0x91);
internal static readonly Guid CGID_EditingCommands = new Guid(0xc77ce45, 0xd1c, 0x4f2a, 0xb2, 0x93, 0xed, 0xd5, 0xe2, 0x7e, 0xba, 0x47);
internal OleCmdHelper()
{
}
/// <SecurityNote>
/// Critical: This code calls into _DoqueryStatus
/// </SecurityNote>
/// <remarks>
/// The native code passes queries here only for the recognized command groups:
/// standard (NULL), ApplicaitonCommands, EditingCommands.
/// </remarks>
[SecurityCritical]
internal void QueryStatus(Guid guidCmdGroup, uint cmdId, ref uint flags)
{
/***IMPORTANT:
Make sure to return allowed and appropriate values according to the specification of
IOleCommandTarget::QueryStatus(). In particular:
- OLECMDF_SUPPORTED without OLECMDF_ENABLED should not be blindly returned for
unrecognized commands.
- Some code in IE treats OLECMDERR_E_xxx differently from generic failures.
- E_NOTIMPL is not an acceptable return value.
*/
if (Application.Current == null || Application.IsShuttingDown == true)
{
Marshal.ThrowExceptionForHR(NativeMethods.E_FAIL);
}
// Get values from mapping here else mark them as disabled ==>
// i.e "supported but not enabled" and is the equivalent of disabled since
// there is no explicit "disabled" OLECMD flag
IDictionary oleCmdMappingTable = GetOleCmdMappingTable(guidCmdGroup);
if (oleCmdMappingTable == null)
{
Marshal.ThrowExceptionForHR(OleCmdHelper.OLECMDERR_E_UNKNOWNGROUP);
}
CommandWithArgument command = oleCmdMappingTable[cmdId] as CommandWithArgument;
if (command == null)
{
flags = CommandUnsupported;
return;
}
// Go through the Dispatcher in order to use its SynchronizationContext and also
// so that any application exception caused during event routing is reported via
// Dispatcher.UnhandledException.
// The above code is not in the callback, because it throws, and we don't want the
// application to get these exceptions. (The COM Interop layer turns them into HRESULTs.)
bool enabled = (bool)Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send, new DispatcherOperationCallback(QueryEnabled), command);
flags = enabled ? CommandEnabled : CommandDisabled;
}
/// <SecurityNote>
/// Critical: Calls the critical CommandWithArgument.QueryEnabled().
/// </SecurityNote>
[SecurityCritical]
private object QueryEnabled(object command)
{
if (Application.Current.MainWindow == null)
return false;
IInputElement target = FocusManager.GetFocusedElement(Application.Current.MainWindow);
if (target == null)
{
// This will always succeed because Window is IInputElement
target = (IInputElement)Application.Current.MainWindow;
}
return BooleanBoxes.Box(((CommandWithArgument)command).QueryEnabled(target, null));
}
/// <SecurityNote>
/// Critical: This code calls into ExecCommandCallback helper
/// </SecurityNote>
/// <remarks>
/// The native code passes here only commands of the recognized command groups:
/// standard (NULL), ApplicaitonCommands, EditingCommands.
/// </remarks>
[SecurityCritical]
internal void ExecCommand(Guid guidCmdGroup, uint commandId, object arg)
{
if (Application.Current == null || Application.IsShuttingDown == true)
{
Marshal.ThrowExceptionForHR(NativeMethods.E_FAIL);
}
int hresult = (int)Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send,
new DispatcherOperationCallback(ExecCommandCallback),
new object[] { guidCmdGroup, commandId, arg });
// Note: ExecCommandCallback() returns an HRESULT instead of throwing for the reason
// explained in QueryStatus().
if(hresult < 0)
{
Marshal.ThrowExceptionForHR(hresult);
}
}
/// <SecurityNote>
/// Critical:This API calls into Execute
/// </SecurityNote>
[SecurityCritical]
private object ExecCommandCallback(object arguments)
{
object[] args = (object[])arguments;
Invariant.Assert(args.Length == 3);
Guid guidCmdGroup = (Guid)args[0];
uint commandId = (uint)args[1];
object arg = args[2];
IDictionary oleCmdMappingTable = GetOleCmdMappingTable(guidCmdGroup);
if (oleCmdMappingTable == null)
return OLECMDERR_E_UNKNOWNGROUP;
CommandWithArgument command = oleCmdMappingTable[commandId] as CommandWithArgument;
if (command == null)
return OLECMDERR_E_NOTSUPPORTED;
if (Application.Current.MainWindow == null)
return OLECMDERR_E_DISABLED;
IInputElement target = FocusManager.GetFocusedElement(Application.Current.MainWindow);
if (target == null)
{
// This will always succeed because Window is IInputElement
target = (IInputElement)Application.Current.MainWindow;
}
return command.Execute(target, arg) ? NativeMethods.S_OK : OLECMDERR_E_DISABLED;
}
/// <SecurityNote>
/// Critical:This API accesses the commandmapping table and returns it
/// TreatAsSafe: It returns a copy which is safe
/// </SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
private IDictionary GetOleCmdMappingTable(Guid guidCmdGroup)
{
IDictionary mappingTable = null;
if (guidCmdGroup.Equals(CGID_ApplicationCommands))
{
EnsureApplicationCommandsTable();
mappingTable = _applicationCommandsMappingTable.Value;
}
else if(guidCmdGroup.Equals(Guid.Empty))
{
EnsureOleCmdMappingTable();
mappingTable = _oleCmdMappingTable.Value;
}
else if (guidCmdGroup.Equals(CGID_EditingCommands))
{
EnsureEditingCommandsTable();
mappingTable = _editingCommandsMappingTable.Value;
}
return mappingTable;
}
/// <SecurityNote>
/// Critical: This code initializes the OleCmdMappingTable which is a critical for
/// set data structure
/// TreatAsSafe: All the values that it adds are predefined handlers in this class
/// no external values
/// </SecurityNote>
[SecurityCritical,SecurityTreatAsSafe]
private void EnsureOleCmdMappingTable()
{
if (_oleCmdMappingTable.Value == null)
{
_oleCmdMappingTable.Value = new SortedList(10);
//Add applevel commands here
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_SAVE, new CommandWithArgument(ApplicationCommands.Save));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_SAVEAS, new CommandWithArgument(ApplicationCommands.SaveAs));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_PRINT, new CommandWithArgument(ApplicationCommands.Print));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_CUT, new CommandWithArgument(ApplicationCommands.Cut));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_COPY, new CommandWithArgument(ApplicationCommands.Copy));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_PASTE, new CommandWithArgument(ApplicationCommands.Paste));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_PROPERTIES, new CommandWithArgument(ApplicationCommands.Properties));
//Set the Enabled property of Stop and Refresh commands correctly
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_REFRESH, new CommandWithArgument(NavigationCommands.Refresh));
_oleCmdMappingTable.Value.Add((uint)UnsafeNativeMethods.OLECMDID.OLECMDID_STOP, new CommandWithArgument(NavigationCommands.BrowseStop));
}
}
/// <SecurityNote>
/// Critical: This code initializes the OleCmdMappingTable which is a critical for
/// set data structure
/// TreatAsSafe: All the values that it adds are predefined handlers in this class
/// no external values
/// </SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
private void EnsureApplicationCommandsTable()
{
if (_applicationCommandsMappingTable.Value == null)
{
/* we want to possible add 26 entries, so the capacity should be
* 26/0.72 = 19 for default of 1.0 load factor*/
_applicationCommandsMappingTable.Value = new Hashtable(19);
//Add applevel commands here
// Note: The keys are added as uint type so that the default container comparer works
// when we try to look up a command by a uint cmdid.
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Cut, new CommandWithArgument(ApplicationCommands.Cut));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Copy, new CommandWithArgument(ApplicationCommands.Copy));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Paste, new CommandWithArgument(ApplicationCommands.Paste));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_SelectAll, new CommandWithArgument(ApplicationCommands.SelectAll));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Find, new CommandWithArgument(ApplicationCommands.Find));
// Add standard navigation commands
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Refresh, new CommandWithArgument(NavigationCommands.Refresh));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Stop, new CommandWithArgument(NavigationCommands.BrowseStop));
// add document viewer commands
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Digitalsignatures_SignDocument, new CommandWithArgument(DocumentApplicationDocumentViewer.Sign));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Digitalsignatures_RequestSignature, new CommandWithArgument(DocumentApplicationDocumentViewer.RequestSigners));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Digitalsignatures_ViewSignature, new CommandWithArgument(DocumentApplicationDocumentViewer.ShowSignatureSummary));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Permission_Set, new CommandWithArgument(DocumentApplicationDocumentViewer.ShowRMPublishingUI));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Permission_View, new CommandWithArgument(DocumentApplicationDocumentViewer.ShowRMPermissions));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.Edit_Permission_Restrict, new CommandWithArgument(DocumentApplicationDocumentViewer.ShowRMCredentialManager));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_In, new CommandWithArgument(NavigationCommands.IncreaseZoom));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_Out, new CommandWithArgument(NavigationCommands.DecreaseZoom));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_400, new CommandWithArgument(NavigationCommands.Zoom, 400));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_250, new CommandWithArgument(NavigationCommands.Zoom, 250));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_150, new CommandWithArgument(NavigationCommands.Zoom, 150));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_100, new CommandWithArgument(NavigationCommands.Zoom, 100));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_75, new CommandWithArgument(NavigationCommands.Zoom, 75));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_50, new CommandWithArgument(NavigationCommands.Zoom, 50));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_25, new CommandWithArgument(NavigationCommands.Zoom, 25));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_PageWidth, new CommandWithArgument(DocumentViewer.FitToWidthCommand));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_WholePage, new CommandWithArgument(DocumentViewer.FitToHeightCommand));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_TwoPages, new CommandWithArgument(DocumentViewer.FitToMaxPagesAcrossCommand, 2));
_applicationCommandsMappingTable.Value.Add((uint)AppCommands.View_Zoom_Thumbnails, new CommandWithArgument(DocumentViewer.ViewThumbnailsCommand));
}
}
/// <SecurityNote>
/// Critical: Initializes _editingCommandsMappingTable, which is a critical for set.
/// TreatAsSafe: Only predefined commands are used. EditingCommands are enabled in partial trust.
/// </SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
private void EnsureEditingCommandsTable()
{
if (_editingCommandsMappingTable.Value == null)
{
_editingCommandsMappingTable.Value = new SortedList(2);
// Note: The keys are added as uint type so that the default container comparer works
// when we try to look up a command by a uint cmdid.
_editingCommandsMappingTable.Value.Add((uint)EditingCommandIds.Backspace,
new CommandWithArgument(System.Windows.Documents.EditingCommands.Backspace));
_editingCommandsMappingTable.Value.Add((uint)EditingCommandIds.Delete,
new CommandWithArgument(System.Windows.Documents.EditingCommands.Delete));
}
}
private SecurityCriticalDataForSet<SortedList> _oleCmdMappingTable;
private SecurityCriticalDataForSet<Hashtable> _applicationCommandsMappingTable;
private SecurityCriticalDataForSet<SortedList> _editingCommandsMappingTable;
}
#endregion OleCmdHelper class
#region CommandAndArgument class
/// <summary>
/// This wrapper class helps store default arguments for commands.
/// The primary scenario for this class is the Zoom command where we
/// have multiple menu items and want to fire a single event with an
/// argument. We cannot attach an argument value to the native menu
/// item so when we do the translation we add it.
/// </summary>
internal class CommandWithArgument
{
/// <SecurityNote>
/// Critical: This can be used to spoof paste command
/// </SecurityNote>
[SecurityCritical]
public CommandWithArgument(RoutedCommand command) : this(command, null)
{ }
/// <SecurityNote>
/// Critical: This can be used to spoof paste command
/// </SecurityNote>
[SecurityCritical]
public CommandWithArgument(RoutedCommand command, object argument)
{
_command = new SecurityCriticalDataForSet<RoutedCommand>(command);
_argument = argument;
}
/// <SecurityNote>
/// Critical:This API calls into ExecuteCore and CriticalCanExecute
/// </SecurityNote>
[SecurityCritical]
public bool Execute(IInputElement target, object argument)
{
if (argument == null)
{
argument = _argument;
}
// ISecureCommand is used to enforce user-initiated invocation. Cut, Copy and Paste
// are marked as such. See ApplicationCommands.GetRequiredPermissions.
if (_command.Value is ISecureCommand)
{
bool unused;
if (_command.Value.CriticalCanExecute(argument, target, /* trusted: */ true, out unused))
{
_command.Value.ExecuteCore(argument, target, /* userInitiated: */ true);
return true;
}
return false;
}
if (_command.Value.CanExecute(argument, target))
{
_command.Value.Execute(argument, target);
return true;
}
return false;
}
/// <SecurityNote>
/// Critical: This code calls into Routedcommand.QueryStatus
/// with a trusted bit, that can be used to cause an elevation.
/// </SecurityNote>
[SecurityCritical]
public bool QueryEnabled(IInputElement target, object argument)
{
if (argument == null)
{
argument = _argument;
}
// ISecureCommand is used to enforce user-initiated invocation. Cut, Copy and Paste
// are marked as such. See ApplicationCommands.GetRequiredPermissions.
if (_command.Value is ISecureCommand)
{
bool unused;
return _command.Value.CriticalCanExecute(argument, target, /* trusted: */ true, out unused);
}
return _command.Value.CanExecute(argument, target);
}
public RoutedCommand Command
{
get
{
return _command.Value;
}
}
private object _argument;
/// <SecurityNote>
/// Critical: This data is critical for set since it is used to make a security decision
/// </SecurityNote>
private SecurityCriticalDataForSet<RoutedCommand> _command;
}
#endregion
}
|