File: winforms\Managed\System\WinForms\Help.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="Help.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
* Copyright (c) 1997, Microsoft Corporation. All Rights Reserved.
* Information Contained Herein is Proprietary and Confidential.
*/
 
namespace System.Windows.Forms {
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;    
    using System;
    using System.Security;
    using System.Security.Permissions;
    using System.Net;
    using System.Drawing;
    using System.ComponentModel;
    using System.IO;
    using System.Text;
    using Microsoft.Win32;
    using System.Globalization;
    
    /// <include file='doc\Help.uex' path='docs/doc[@for="Help"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Represents the HTML 1.0 Help engine.
    ///    </para>
    /// </devdoc>
    public class Help {
#if DEBUG        
        internal static readonly TraceSwitch WindowsFormsHelpTrace = new TraceSwitch("WindowsFormsHelpTrace", "Debug help system");
#else
        internal static readonly TraceSwitch WindowsFormsHelpTrace;
#endif
 
        private const int HH_DISPLAY_TOPIC        =0x0000;
        private const int HH_HELP_FINDER          =0x0000;  // WinHelp equivalent
        private const int HH_DISPLAY_TOC          =0x0001;  // not currently implemented
        private const int HH_DISPLAY_INDEX        =0x0002;  // not currently implemented
        private const int HH_DISPLAY_SEARCH       =0x0003;  // not currently implemented
        private const int HH_SET_WIN_TYPE         =0x0004;
        private const int HH_GET_WIN_TYPE         =0x0005;
        private const int HH_GET_WIN_HANDLE       =0x0006;
        private const int HH_ENUM_INFO_TYPE       =0x0007;  // Get Info type name, call repeatedly to enumerate, -1 at end
        private const int HH_SET_INFO_TYPE        =0x0008;  // Add Info type to filter.
        private const int HH_SYNC                 =0x0009;
        private const int HH_ADD_NAV_UI           =0x000A;  // not currently implemented
        private const int HH_ADD_BUTTON           =0x000B;  // not currently implemented
        private const int HH_GETBROWSER_APP       =0x000C;  // not currently implemented
        private const int HH_KEYWORD_LOOKUP       =0x000D;
        private const int HH_DISPLAY_TEXT_POPUP   =0x000E;  // display string resource id or text in a popup window
        private const int HH_HELP_CONTEXT         =0x000F;  // display mapped numeric value in dwData
        private const int HH_TP_HELP_CONTEXTMENU  =0x0010;  // text popup help, same as WinHelp HELP_CONTEXTMENU
        private const int HH_TP_HELP_WM_HELP      =0x0011;  // text popup help, same as WinHelp HELP_WM_HELP
        private const int HH_CLOSE_ALL            =0x0012;  // close all windows opened directly or indirectly by the caller
        private const int HH_ALINK_LOOKUP         =0x0013;  // ALink version of HH_KEYWORD_LOOKUP
        private const int HH_GET_LAST_ERROR       =0x0014;  // not currently implemented // See HHERROR.h
        private const int HH_ENUM_CATEGORY        =0x0015;   // Get category name, call repeatedly to enumerate, -1 at end
        private const int HH_ENUM_CATEGORY_IT     =0x0016;  // Get category info type members, call repeatedly to enumerate, -1 at end
        private const int HH_RESET_IT_FILTER      =0x0017;  // Clear the info type filter of all info types.
        private const int HH_SET_INCLUSIVE_FILTER =0x0018;  // set inclusive filtering method for untyped topics to be included in display
        private const int HH_SET_EXCLUSIVE_FILTER =0x0019;  // set exclusive filtering method for untyped topics to be excluded from display
        private const int HH_SET_GUID             =0x001A;  // For Microsoft Installer -- dwData is a pointer to the GUID string
 
        private const int HTML10HELP = 2;
        private const int HTMLFILE   = 3;
 
        // not creatable
        //
        private Help() {
        }        
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHelp"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays
        ///       the contents of the Help file at located at a specified Url.
        ///    </para>
        /// </devdoc>
        public static void ShowHelp(Control parent, string url) {
            ShowHelp(parent, url, HelpNavigator.TableOfContents, null);
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHelp1"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays the contents of
        ///       the Help
        ///       file for a specific topic found at the specified Url.
        ///    </para>
        /// </devdoc>
        public static void ShowHelp(Control parent, string url, HelpNavigator navigator) {
            ShowHelp(parent, url, navigator, null);
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHelp2"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays the contents of
        ///       the Help
        ///       file for a specific topic found at the specified Url.
        ///    </para>
        /// </devdoc>
        public static void ShowHelp(Control parent, string url, string keyword) {
            if (keyword != null && keyword.Length != 0) {
                ShowHelp(parent, url, HelpNavigator.Topic, keyword);
            }
            else {
                ShowHelp(parent, url, HelpNavigator.TableOfContents, null);
            }
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHelp3"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays the contents of the Help file located at
        ///       the Url
        ///       supplied by the
        ///       user.
        ///    </para>
        /// </devdoc>
        public static void ShowHelp(Control parent, string url, HelpNavigator command, object parameter) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: ShowHelp");
 
            switch (GetHelpFileType(url)) {
                case HTML10HELP:
                    ShowHTML10Help(parent, url, command, parameter);
                    break;
                case HTMLFILE:
                    ShowHTMLFile(parent, url, command, parameter);
                    break;
            }
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHelpIndex"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays the index of the specified file.
        ///    </para>
        /// </devdoc>
        public static void ShowHelpIndex(Control parent, string url) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: ShowHelpIndex");
 
            ShowHelp(parent, url, HelpNavigator.Index, null);
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowPopup"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Displays a Help pop-up window.
        ///    </para>
        /// </devdoc>
        public static void ShowPopup(Control parent, string caption, Point location) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: ShowPopup");
 
            NativeMethods.HH_POPUP pop = new NativeMethods.HH_POPUP();
 
            // ASURT 148069 
            // We have to marshal the string ourselves to prevent access violations.
            IntPtr pszText = Marshal.StringToCoTaskMemAuto(caption);
  	    
            try {
                pop.pszText = pszText;
                pop.idString = 0;
                pop.pt = new NativeMethods.POINT(location.X, location.Y);
 
                // ASURT 108580
                // Looks like a windows bug causes the -1 value for clrBackground to not
                // do the right thing for High Contrast Black color scheme (and probably others)
                //
                pop.clrBackground = Color.FromKnownColor(KnownColor.Window).ToArgb() & 0x00ffffff;
 
                ShowHTML10Help(parent, null, HelpNavigator.Topic, pop);
            }
            finally {
                Marshal.FreeCoTaskMem(pszText);
            }
 
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHTML10Help"]/*' />
        /// <devdoc>
        ///     Displays HTML 1.0 Help with the specified parameters
        /// </devdoc>
        /// <internalonly/>
        private static void ShowHTML10Help(Control parent, string url, HelpNavigator command, object param) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: ShowHTML10Help:: " + url + ", " + command.ToString("G") + ", " + param);
 
            // See VSWhidbey #538252 for details on why we decided to add this demand.
            IntSecurity.UnmanagedCode.Demand();
 
            // VSWhidbey 106914: See if we can get a full path and file name and if that will
            // resolve the out of memory condition with file names that include spaces.
            // If we can't, though, we can't assume that the path's no good: it might be in
            // the Windows help directory (VSWhidbey 474069).
            Uri file = null;
            string pathAndFileName = url; //This is our best guess at the path yet.
            
            file = Resolve(url);
            if (file != null) { // VSWhidbey 271337: can't assume we have a good url
                pathAndFileName = file.AbsoluteUri;
            }
            if (file == null || file.IsFile) {
                StringBuilder newPath = new StringBuilder();
                string localPath = (file != null && file.IsFile) ? file.LocalPath : url;
 
                // If this is a local path, convert it to a short path name.  Pass 0 as the length the first time
                uint requiredStringSize = UnsafeNativeMethods.GetShortPathName(localPath, newPath, 0);
                if (requiredStringSize > 0) {
                    //It's able to make it a short path.  Happy day.
                    newPath.Capacity = (int)requiredStringSize;
                    requiredStringSize = UnsafeNativeMethods.GetShortPathName(localPath, newPath, requiredStringSize);
                    //If it can't make it a  short path, just leave the path we had.
                    pathAndFileName = newPath.ToString();
                }
            }
 
            HandleRef handle;
            if (parent != null) {
                handle = new HandleRef(parent, parent.Handle);
            }
            else {
                handle = new HandleRef(null, UnsafeNativeMethods.GetActiveWindow());
            }
 
            object htmlParam;
            string stringParam = param as string;
            if (stringParam != null) {
                int htmlCommand = MapCommandToHTMLCommand(command, stringParam, out htmlParam);
                
                string stringHtmlParam = htmlParam as string;
                if (stringHtmlParam != null) {
                    SafeNativeMethods.HtmlHelp(handle, pathAndFileName, htmlCommand, stringHtmlParam);
                }
                else if (htmlParam is int)  {
                    SafeNativeMethods.HtmlHelp(handle, pathAndFileName, htmlCommand, (int)htmlParam);
                }
                else if (htmlParam is NativeMethods.HH_FTS_QUERY) {
                    SafeNativeMethods.HtmlHelp(handle, pathAndFileName, htmlCommand, (NativeMethods.HH_FTS_QUERY)htmlParam);
                }
                else if (htmlParam is NativeMethods.HH_AKLINK) {
                    // According to MSDN documentation, we have to ensure that the help window is up
                    // before we call ALINK lookup.
                    //
                    SafeNativeMethods.HtmlHelp(NativeMethods.NullHandleRef, pathAndFileName, HH_DISPLAY_TOPIC, (string)null);
                    SafeNativeMethods.HtmlHelp(handle, pathAndFileName, htmlCommand, (NativeMethods.HH_AKLINK)htmlParam);
                }
                else {
                    Debug.Fail("Cannot handle HTML parameter of type: " + htmlParam.GetType());
                    SafeNativeMethods.HtmlHelp(handle, pathAndFileName, htmlCommand, (string)param);
                }
            }
            else if (param == null ) {
                SafeNativeMethods.HtmlHelp(handle, pathAndFileName, MapCommandToHTMLCommand(command, null, out htmlParam), 0);
            }
            else if (param is NativeMethods.HH_POPUP) {
                SafeNativeMethods.HtmlHelp(handle, pathAndFileName, HH_DISPLAY_TEXT_POPUP, (NativeMethods.HH_POPUP)param);
            }
            else if (param.GetType() == typeof(Int32)) {
                throw new ArgumentException(SR.GetString(SR.InvalidArgument, "param", "Integer"));
            }
        }
 
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.ShowHTMLFile"]/*' />
        /// <devdoc>
        ///     Displays HTMLFile with the specified parameters
        /// </devdoc>
        /// <internalonly/>
        /// SECREVIEW: ReviewImperativeSecurity
        ///   vulnerability to watch out for: A method uses imperative security and might be constructing the permission using state information or return values that can change while the demand is active.
        ///   reason for exclude: url is directly scrubbed in resolve, is a local member, will not change before it's passed into WebPermission.
        [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
        private static void ShowHTMLFile(Control parent, string url, HelpNavigator command, object param) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: ShowHTMLHelp:: " + url + ", " + command.ToString("G") + ", " + param);
 
            Uri file = Resolve(url);
 
            if (file == null) {
                throw new ArgumentException(SR.GetString(SR.HelpInvalidURL, url), "url");
            }
 
            switch (file.Scheme) {
                case "http":
                case "https":
                    Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "WebPermission Demanded");
                    new WebPermission(NetworkAccess.Connect, url).Demand();
                    break;
                default:
                    Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "UnmanagedCode Demanded");
                    IntSecurity.UnmanagedCode.Demand();
                    break;
            }
 
            switch (command) {
                case HelpNavigator.TableOfContents:
                case HelpNavigator.Find:
                case HelpNavigator.Index:
                    // nothing needed...
                    //
                    break;
                case HelpNavigator.Topic:
                    if (param != null && param is string) {
                        file = new Uri(file.ToString() + "#" + (string)param);
                    }
                    break;
            }
 
            HandleRef handle;
            if (parent != null) {
                handle = new HandleRef(parent, parent.Handle);
            }
            else {
                handle = new HandleRef(null, UnsafeNativeMethods.GetActiveWindow());
            }
 
 
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "\tExecuting '" + file.ToString() + "'");
            UnsafeNativeMethods.ShellExecute_NoBFM(handle, null, file.ToString(), null, null, NativeMethods.SW_NORMAL);
        }
 
        private static Uri Resolve(string partialUri) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: Resolve " + partialUri);
            Debug.Indent();
 
            Uri file = null;
            // VSW 426515: Catch specific exceptions
            if (!string.IsNullOrEmpty(partialUri)) {
                try {
                    file = new Uri(partialUri);
                }
                catch (UriFormatException) {
                    // eat URI parse exceptions...
                    //
                }   
                catch (ArgumentNullException) {
                    // VSW 426515: Catch specific exceptions
                    // Shouldnt get here...
                }
            }
 
            if (file != null && file.Scheme == "file") {
                string localPath = NativeMethods.GetLocalPath(partialUri);
                Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "file, check for existence");
 
                // SECREVIEW : Checking for file here is safe.
                //
                new FileIOPermission(FileIOPermissionAccess.Read, localPath).Assert();
                try {
                    if (!File.Exists(localPath)) {
                        // clear, and try relative to AppBase...
                        //
                        file = null;
                    }
                }
                finally {
                    CodeAccessPermission.RevertAssert();
                }
            }
 
            if (file == null) {
                Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "try appbase relative");
                try {
                    // try relative to AppBase...
                    //
                    file = new Uri(new Uri(AppDomain.CurrentDomain.SetupInformation.ApplicationBase), 
                                   partialUri);
                }
                catch (UriFormatException) {
                    // VSW 426515: Catch specific exceptions
                    // eat URI parse exceptions...
                    //
                }
                catch (ArgumentNullException) {
                    // VSW 426515: Catch specific exceptions
                    // Shouldnt get here...                    
                }
 
                if (file != null && file.Scheme == "file") {
                    string localPath = file.LocalPath + file.Fragment;
                    Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "file, check for existence");
                    new FileIOPermission(FileIOPermissionAccess.Read, localPath).Assert();
                    try {
                        if (!File.Exists(localPath)) {
                            // clear - file isn't there...
                            //
                            file = null;
                        }
                    }
                    finally {
                        CodeAccessPermission.RevertAssert();
                    }
                }
            }
 
 
 
            Debug.Unindent();
            return file;
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.GetHelpFileType"]/*' />
        /// <internalonly/>
        private static int GetHelpFileType(string url) {
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "Help:: GetHelpFileType " + url);
 
            if (url == null) {
                Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "\tnull, must be Html File");
                return HTMLFILE;
            }
 
            Uri file = Resolve(url);
 
            if (file == null || file.Scheme == "file") {
                Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "\tfile");
 
                string ext = Path.GetExtension(file == null ? url : file.LocalPath + file.Fragment).ToLower(CultureInfo.InvariantCulture);
                if (ext == ".chm" || ext == ".col") {
                    Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "\tchm or col, HtmlHelp 1.0 file");
                    return HTML10HELP;
                }
            }
 
            Debug.WriteLineIf(Help.WindowsFormsHelpTrace.TraceVerbose, "\tnot file, or odd extension, but be HTML");
            return HTMLFILE;
        }
 
        /// <include file='doc\Help.uex' path='docs/doc[@for="Help.MapCommandToHTMLCommand"]/*' />
        /// <devdoc>
        ///     Maps one of the COMMAND_* constants to the HTML 1.0 Help equivalent.
        /// </devdoc>
        /// <internalonly/>
        private static int MapCommandToHTMLCommand(HelpNavigator command, string param, out object htmlParam) {
            htmlParam = param;
 
            if ((String.IsNullOrEmpty(param)) &&
                (command == HelpNavigator.AssociateIndex || command == HelpNavigator.KeywordIndex)) {
                return HH_DISPLAY_INDEX;
            }
 
            switch (command) {
                case HelpNavigator.Topic:
                    return HH_DISPLAY_TOPIC;
                
                case HelpNavigator.TableOfContents:
                    return HH_DISPLAY_TOC;
                
                case HelpNavigator.Index:
                    return HH_DISPLAY_INDEX;
                
                case HelpNavigator.Find: {
                    NativeMethods.HH_FTS_QUERY ftsQuery = new NativeMethods.HH_FTS_QUERY();
                    ftsQuery.pszSearchQuery = param;
                    htmlParam = ftsQuery;
                    return HH_DISPLAY_SEARCH;
                }
	        case HelpNavigator.TopicId: {
                    try {
                        htmlParam = int.Parse(param, CultureInfo.InvariantCulture);
                        return HH_HELP_CONTEXT;
                    }
                    catch {
                        // default to just showing the index
                        return HH_DISPLAY_INDEX;
                    }
                }
                case HelpNavigator.KeywordIndex:
                case HelpNavigator.AssociateIndex: {
                    NativeMethods.HH_AKLINK alink = new NativeMethods.HH_AKLINK();
                    alink.pszKeywords = param;
                    alink.fIndexOnFail = true;
                    alink.fReserved = false;
                    htmlParam = alink;
                    return (command == HelpNavigator.KeywordIndex) ? HH_KEYWORD_LOOKUP : HH_ALINK_LOOKUP;
                }
                
                default:
                    return (int)command;
            }
        }
    }
}