File: Core\CSharp\MS\Internal\FontCache\FontSourceCollection.cs
Project: wpf\src\PresentationCore.csproj (PresentationCore)
//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// Description: The FontSourceCollection class represents a collection of font files.
//
// History:  
//  08/25/2005 : mleonov - Created it
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Packaging;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
 
using Microsoft.Win32;
 
using MS.Win32;
using MS.Utility;
using MS.Internal;
using MS.Internal.IO.Packaging;
 
using MS.Internal.PresentationCore;
using MS.Internal.Text.TextInterface;
 
namespace MS.Internal.FontCache
{
    internal class FontSourceCollectionFactory : IFontSourceCollectionFactory
    {
        public FontSourceCollectionFactory() { }
 
        /// <SecurityNote>
        /// Critical - Calls FontSourceCollection security critical ctor
        /// Safe     - The uriString is a user input and should not expose security critical info
        ///            since the user provides this info. Also we never call the ctor with 
        ///            isWindowsFonts = true. Thus it is safe to construct a FontSourceCollection 
        ///            from a user string because the user string is treated as untrusted.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public IFontSourceCollection Create(string uriString)
        {
            return new FontSourceCollection(new Uri(uriString), false);
        }
    }
 
    /// <summary>
    /// FontSourceCollection class represents a collection of font files.
    /// </summary>
    internal class FontSourceCollection : IFontSourceCollection
    {
        /// <SecurityNote>
        /// Critical - Calls into security critical Initialize() method.
        /// </SecurityNote>
        [SecurityCritical]
        public FontSourceCollection(Uri folderUri, bool isWindowsFonts)
        {
            Initialize(folderUri, isWindowsFonts, false);
        }
 
        /// <SecurityNote>
        /// Critical - Calls into security critical Initialize() method.
        /// </SecurityNote>
        [SecurityCritical]
        public FontSourceCollection(Uri folderUri, bool isWindowsFonts, bool tryGetCompositeFontsOnly)
        {
            Initialize(folderUri, isWindowsFonts, tryGetCompositeFontsOnly);
        }
 
        /// <SecurityNote>
        /// Critical - The folderUri parameter is critical as it may contain privileged information 
        ///            about the file system (i.e., location of Windows Fonts); it is assigned to 
        ///            the _uri field which is declared critical.
        /// 
        ///            The isWindowsFonts parameter is critical for set as it is used to make a 
        ///            security decision (i.e., whether to assert read access); it is assigned to 
        ///            the _isWindowsFonts field which is declared critical.
        /// </SecurityNote>
        [SecurityCritical]
        private void Initialize(Uri folderUri, bool isWindowsFonts, bool tryGetCompositeFontsOnly)
        {
            _uri = folderUri;
            _isWindowsFonts = isWindowsFonts;
            _tryGetCompositeFontsOnly = tryGetCompositeFontsOnly;
 
            bool isComposite = false;
 
            // Check whether the given uri is a font file. In some cases we will construct a DWrite Font Collection by passing
            // a file path and not a directory path. In this case we need to construct a FontCollection that only holds this
            // file.
            bool isSingleSupportedFile = Util.IsSupportedFontExtension(Util.GetUriExtension(_uri), out isComposite);
            if (isSingleSupportedFile || !Util.IsEnumerableFontUriScheme(_uri))
            {
                _fontSources = new List<Text.TextInterface.IFontSource>(1);                
                _fontSources.Add(new FontSource(_uri, false, isComposite));
            }
            else
            {
                InitializeDirectoryProperties();
            }
        }
 
        /// <SecurityNote>
        ///     Critical: accesses critical _uri and WindowsFontsUriObject
        ///     TreatAsSafe: only does comparisons to determine if _uri is the system dir
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private void InitializeDirectoryProperties()
        {
            _isFileSystemFolder = false;
 
            if (_uri.IsFile)
            {
                if (_isWindowsFonts)
                {
                    if (object.ReferenceEquals(_uri, Util.WindowsFontsUriObject))
                    {
                        // We know the local path and that it's a folder
                        _isFileSystemFolder = true;
                    }
                    else
                    {
                        // It's a file within the Windows Fonts folder
                        _isFileSystemFolder = false;
                    }
                }
                else
                {
                    // Get the local path
                    string localPath = _uri.LocalPath;
 
                    // Decide if it's a file or folder based on syntax, not contents of file system
                    _isFileSystemFolder = localPath[localPath.Length - 1] == Path.DirectorySeparatorChar;
                }
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this allows you list files in windows font directory
        ///            and returns the file list.
        /// Safe     - This only does an elevation for WindowsFont directory which is safe,
        ///            and relevant data is already protected via FontSource methods.       
        ///            
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private void SetFontSources()
        {
            if (_fontSources != null)
                return;
 
            lock (this)
            {
                List<Text.TextInterface.IFontSource> fontSources;
                if (_uri.IsFile)
                {
                    ICollection<string> files;
                    bool isOnlyCompositeFontFiles = false;
                    if (_isFileSystemFolder)
                    {
                        if (_isWindowsFonts)
                        {
                            PermissionSet permissionSet = new PermissionSet(null);
 
                            // Read and path discovery permission for the %windir%\font path.
                            permissionSet.AddPermission(new FileIOPermission(
                                FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery,
                                Util.WindowsFontsUriObject.LocalPath));
 
                            // Registry read permissions for the Fonts system registry entry.
                            permissionSet.AddPermission(new RegistryPermission(
                                RegistryPermissionAccess.Read,
                                InstalledWindowsFontsRegistryKeyFullPath));
 
                            permissionSet.Assert(); // BlessedAssert
 
                            try
                            {
                                if (_tryGetCompositeFontsOnly)
                                {
                                    files = Directory.GetFiles(_uri.LocalPath, "*" + Util.CompositeFontExtension);
                                    isOnlyCompositeFontFiles = true;
                                }
                                else
                                {
                                    // fontPaths accumulates font file paths obtained from the registry and the file system
                                    // This collection is a set, i.e. only keys matter, not values.
                                    Dictionary<string, object> fontPaths = new Dictionary<string, object>(512, StringComparer.OrdinalIgnoreCase);
 
                                    using (RegistryKey fontsKey = Registry.LocalMachine.OpenSubKey(InstalledWindowsFontsRegistryKey))
                                    {
                                        // The registry key should be present on a valid Windows installation.
                                        Invariant.Assert(fontsKey != null);
 
                                        foreach (string fontValue in fontsKey.GetValueNames())
                                        {
                                            string fileName = fontsKey.GetValue(fontValue) as string;
                                            if (fileName != null)
                                            {
                                                // See if the path doesn't contain any directory information.
                                                // Shell uses the same method to determine whether to prepend the path with %windir%\fonts.
                                                if (Path.GetFileName(fileName) == fileName)
                                                    fileName = Path.Combine(Util.WindowsFontsLocalPath, fileName);
 
                                                fontPaths[fileName] = null;
                                            }
                                        }
                                    }
 
                                    foreach (string file in Directory.GetFiles(_uri.LocalPath))
                                    {
                                        fontPaths[file] = null;
                                    }
                                    files = fontPaths.Keys;
                                }
                            }
                            finally
                            {
                                if (_isWindowsFonts)
                                    CodeAccessPermission.RevertAssert();
                            }
 
                        }
                        else
                        {
                            if (_tryGetCompositeFontsOnly)
                            {
                                files = Directory.GetFiles(_uri.LocalPath, "*" + Util.CompositeFontExtension);
                                isOnlyCompositeFontFiles = true;                               
                            }
                            else
                            {
                                files = Directory.GetFiles(_uri.LocalPath);
                            }
                        }
                    }
                    else
                    {
                        files = new string[1] {_uri.LocalPath};
                    }
 
                    fontSources = new List<Text.TextInterface.IFontSource>(files.Count);
                    if (isOnlyCompositeFontFiles)
                    {
                        foreach (string file in files)
                        {
                            fontSources.Add(new FontSource(new Uri(file, UriKind.Absolute), _isWindowsFonts, true));
                        }
                    }
                    else
                    {
                        bool isComposite;
                        foreach (string file in files)
                        {                            
                            if (Util.IsSupportedFontExtension(Path.GetExtension(file), out isComposite))
                                fontSources.Add(new FontSource(new Uri(file, UriKind.Absolute), _isWindowsFonts, isComposite));
                        }
                    }
                }
                else
                {
                    List<string> resourceEntries = FontResourceCache.LookupFolder(_uri);
 
                    if (resourceEntries == null)
                        fontSources = new List<Text.TextInterface.IFontSource>(0);
                    else
                    {
                        bool isComposite = false;
 
                        // Enumerate application resources, content files and container structure.
                        fontSources = new List<Text.TextInterface.IFontSource>(resourceEntries.Count);
 
                        foreach (string resourceName in resourceEntries)
                        {
                            // If resourceName is an empty string, this means that the _uri is a full file name;
                            // otherwise resourceName is a file name within a folder.
                            if (String.IsNullOrEmpty(resourceName))
                            {
                                isComposite = Util.IsCompositeFont(Path.GetExtension(_uri.AbsoluteUri));
                                fontSources.Add(new FontSource(_uri, _isWindowsFonts, isComposite));
                            }
                            else
                            {
                                isComposite = Util.IsCompositeFont(Path.GetExtension(resourceName));
                                fontSources.Add(new FontSource(new Uri(_uri, resourceName), _isWindowsFonts, isComposite));
                            }
                        }
                    }
                }
 
                _fontSources = fontSources;
            }
        }
 
        
        #region IEnumerable<FontSource> Members
 
        IEnumerator<Text.TextInterface.IFontSource> System.Collections.Generic.IEnumerable<Text.TextInterface.IFontSource>.GetEnumerator()
        {
            SetFontSources();
            return (IEnumerator<Text.TextInterface.IFontSource>)_fontSources.GetEnumerator();
        }
 
        #endregion
 
        #region IEnumerable Members
 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            SetFontSources();
            return _fontSources.GetEnumerator();
        }
 
        #endregion
    
 
        /// <SecurityNote>
        /// Critical - fontUri can contain information about local file system.
        /// </SecurityNote>
        [SecurityCritical]
        private Uri                         _uri;
 
        /// <SecurityNote>
        /// Critical - this value is used to make security decisions (i.e., whether to do an Assert)
        ///            so it can only be set by critical code.
        /// </SecurityNote>
        [SecurityCritical]
        private bool                        _isWindowsFonts;
 
        // _isFileSystemFolder flag makes sense only when _uri.IsFile is set to true.
        private bool                                           _isFileSystemFolder;
        private volatile IList<Text.TextInterface.IFontSource> _fontSources;
 
        // Flag to indicate that only composite fonts in the provided URI location should be retrieved.        
        private bool                               _tryGetCompositeFontsOnly;
 
        private const string InstalledWindowsFontsRegistryKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts";
        private const string InstalledWindowsFontsRegistryKeyFullPath = @"HKEY_LOCAL_MACHINE\" + InstalledWindowsFontsRegistryKey;
 
    }
}