File: Core\CSharp\MS\Internal\FontCache\FontSource.cs
Project: wpf\src\PresentationCore.csproj (PresentationCore)
//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// Description: The FontSource class.
//
// History:  
//  08/04/2003 : mleonov - Created it
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections;
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 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 FontSourceFactory : IFontSourceFactory
    {
        public FontSourceFactory() { }
        
        /// <SecurityNote>
        ///     Critical - retreives security sensitive info about a FontSource like raw font data.
        ///     Safe     - does a demand before it gives out the information asked.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public IFontSource Create(string uriString)
        {
            return new FontSource(new Uri(uriString), false);
        }
    }
 
    /// <summary>
    /// FontSource class encapsulates the logic for dealing with fonts in memory or on the disk.
    /// It may or may not have a Uri associated with it, but there has to be some way to obtain its contents.
    /// </summary>
    internal class FontSource : IFontSource
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <SecurityNote>
        /// Critical - Calls Security Critical method Initialize().
        /// </SecurityNote>
        [SecurityCritical]
        public FontSource(Uri fontUri, bool skipDemand)
        {
            Initialize(fontUri, skipDemand, false);
        }
 
        /// <SecurityNote>
        /// Critical - Calls Security Critical method Initialize().
        /// </SecurityNote>
        [SecurityCritical]
        public FontSource(Uri fontUri, bool skipDemand, bool isComposite)
        {
            Initialize(fontUri, skipDemand, isComposite);
        }
 
        /// <SecurityNote>
        /// Critical - fontUri can contain information about local file system, skipDemand is used to make security decisions.
        /// </SecurityNote>
        [SecurityCritical]
        private void Initialize(Uri fontUri, bool skipDemand, bool isComposite)
        {
            _fontUri = fontUri;
            _skipDemand = skipDemand;
            _isComposite = isComposite;
            Invariant.Assert(_fontUri.IsAbsoluteUri);
            Debug.Assert(String.IsNullOrEmpty(_fontUri.Fragment));
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        public bool IsComposite
        {
            get
            {
                return _isComposite;
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this gives out full file path.
        /// </SecurityNote>
        [SecurityCritical]
        public string GetUriString()
        {
            return _fontUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
        }
 
        /// <SecurityNote>
        /// Critical - as this gives out full file path.
        /// </SecurityNote>
        [SecurityCritical]
        public string ToStringUpperInvariant()
        {
            return GetUriString().ToUpperInvariant();
        }
 
        /// <SecurityNote>
        /// Critical - fontUri can contain information about local file system.
        /// TreatAsSafe - we only compute its hash code.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public override int GetHashCode()
        {
            return HashFn.HashString(ToStringUpperInvariant(), 0);
        }
 
 
        /// <SecurityNote>
        /// Critical - as this gives out full file path.
        /// </SecurityNote>
        public Uri Uri
        {
            [SecurityCritical]
            get
            {
                return _fontUri;
            }
        }
 
        /// <SecurityNote>
        /// Critical - fontUri can contain information about local file system.
        /// TreatAsSafe - we only return a flag that says whether the Uri is app specific.
        /// </SecurityNote>
        public bool IsAppSpecific
        {
            [SecurityCritical, SecurityTreatAsSafe]
            get
            {
                return Util.IsAppSpecificUri(_fontUri);
            }
        }
 
        internal long SkipLastWriteTime()
        {
            // clients may choose to use this temporary method because GetLastWriteTime call
            // results in touching the file system
            // we need to resurrect this code when we come up with a complete solution
            // for updating fonts on the fly
            return -1; // any non-zero value will do here
        }
 
        /// <SecurityNote>
        /// Critical - elevates to obtain the last write time for %windir%\fonts.
        /// Also, fontUri can contain information about local file system.
        /// TreatAsSafe - we only use it to obtain the last write time.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public DateTime GetLastWriteTimeUtc()
        {
            if (_fontUri.IsFile)
            {
                bool revertAssert = false;
 
                // Assert FileIORead permission for installed fonts.
                if (_skipDemand)
                {
                    new FileIOPermission(FileIOPermissionAccess.Read, _fontUri.LocalPath).Assert(); //Blessed Assert
                    revertAssert = true;
                }
 
                try
                {
                    return Directory.GetLastWriteTimeUtc(_fontUri.LocalPath);
                }
                finally
                {
                    if (revertAssert)
                        CodeAccessPermission.RevertAssert();
                }
            }
 
            // Any special value will do here.
            return DateTime.MaxValue;
        }
 
        /// <SecurityNote>
        /// Critical - as this gives out UnmanagedMemoryStream content which is from a file.
        /// </SecurityNote>
        [SecurityCritical]
        public UnmanagedMemoryStream GetUnmanagedStream()
        {
            if (_fontUri.IsFile)
            {
                FileMapping fileMapping = new FileMapping();
 
                DemandFileIOPermission();
 
                fileMapping.OpenFile(_fontUri.LocalPath);
                return fileMapping;
            }
 
            byte[] bits;
            // Try our cache first.
            lock (_resourceCache)
            {
                bits = _resourceCache.Get(_fontUri);
            }
 
            if (bits == null)
            {
                WebResponse response = WpfWebRequestHelper.CreateRequestAndGetResponse(_fontUri);
                Stream fontStream = response.GetResponseStream();
                if (String.Equals(response.ContentType, ObfuscatedContentType, StringComparison.Ordinal))
                {
                    // The third parameter makes sure the original stream is closed
                    // when the deobfuscating stream is disposed.
                    fontStream = new DeobfuscatingStream(fontStream, _fontUri, false);
                }
 
                UnmanagedMemoryStream unmanagedStream = fontStream as UnmanagedMemoryStream;
                if (unmanagedStream != null)
                    return unmanagedStream;
 
                bits = StreamToByteArray(fontStream);
 
                fontStream.Close();
 
                lock (_resourceCache)
                {
                    _resourceCache.Add(_fontUri, bits, false);
                }
            }
            return ByteArrayToUnmanagedStream(bits);
        }
 
        /// <summary>
        /// Tries to open a file and throws exceptions in case of failures. This
        /// method is used to achieve the same exception throwing behavior after
        /// integrating DWrite.
        /// </summary>
        /// <SecurityNote>
        /// Critical    - accesses security critical method FileMapping.OpenFile
        /// TreatAsSafe - Does not give out sensitive info.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public void TestFileOpenable()
        {
            if (_fontUri.IsFile)
            {
                FileMapping fileMapping = new FileMapping();
 
                DemandFileIOPermission();
 
                fileMapping.OpenFile(_fontUri.LocalPath);
                fileMapping.Close();
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this gives out Stream content which is from a file.
        /// </SecurityNote>
        [SecurityCritical]
        public Stream GetStream()
        {
            if (_fontUri.IsFile)
            {
                FileMapping fileMapping = new FileMapping();
 
                DemandFileIOPermission();
 
                fileMapping.OpenFile(_fontUri.LocalPath);
                return fileMapping;
            }
 
            byte[] bits;
 
            // Try our cache first.
            lock (_resourceCache)
            {
                bits = _resourceCache.Get(_fontUri);
            }
 
            if (bits != null)
                return new MemoryStream(bits);
 
            WebRequest request = PackWebRequestFactory.CreateWebRequest(_fontUri);
            WebResponse response = request.GetResponse();
 
            Stream fontStream = response.GetResponseStream();
            if (String.Equals(response.ContentType, ObfuscatedContentType, StringComparison.Ordinal))
            {
                // The third parameter makes sure the original stream is closed
                // when the deobfuscating stream is disposed.
                fontStream = new DeobfuscatingStream(fontStream, _fontUri, false);
            }
            return fontStream;
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        private static UnmanagedMemoryStream ByteArrayToUnmanagedStream(byte[] bits)
        {
            return new PinnedByteArrayStream(bits);
        }
 
        private static byte [] StreamToByteArray(Stream fontStream)
        {
            byte[] memoryFont;
 
            if (fontStream.CanSeek)
            {
                checked
                {
                    memoryFont = new byte[(int)fontStream.Length];
                    PackagingUtilities.ReliableRead(fontStream, memoryFont, 0, (int)fontStream.Length);
                }
            }
            else
            {
                // this is inefficient, but works for now
                // we need to spend more time to implement a more performant
                // version of this code
                // ideally this should be a part of loader functionality
 
                // Initial file read buffer size is set to 1MB.
                int fileReadBufferSize = 1024 * 1024;
                byte[] fileReadBuffer = new byte[fileReadBufferSize];
 
                // Actual number of bytes read from the file.
                int memoryFontSize = 0;
 
                for (; ; )
                {
                    int availableBytes = fileReadBufferSize - memoryFontSize;
                    if (availableBytes < fileReadBufferSize / 3)
                    {
                        // grow the fileReadBuffer
                        fileReadBufferSize *= 2;
                        byte[] newBuffer = new byte[fileReadBufferSize];
                        Array.Copy(fileReadBuffer, newBuffer, memoryFontSize);
                        fileReadBuffer = newBuffer;
                        availableBytes = fileReadBufferSize - memoryFontSize;
                    }
                    int numberOfBytesRead = fontStream.Read(fileReadBuffer, memoryFontSize, availableBytes);
                    if (numberOfBytesRead == 0)
                        break;
 
                    memoryFontSize += numberOfBytesRead;
                }
 
                // Actual number of bytes read from the file is less or equal to the file read buffer size.
                Debug.Assert(memoryFontSize <= fileReadBufferSize);
 
                if (memoryFontSize == fileReadBufferSize)
                    memoryFont = fileReadBuffer;
                else
                {
                    // Trim the array if needed to that it contains the right length.
                    memoryFont = new byte[memoryFontSize];
                    Array.Copy(fileReadBuffer, memoryFont, memoryFontSize);
                }
            }
 
            return memoryFont;
        }
 
        /// <summary>
        /// Demand read permissions for all fonts except system ones.
        /// </summary>
        /// <SecurityNote>
        ///     Critical - as this function calls critical WindowsFontsUriObject.
        ///     TreatAsSafe - as the WindowsFontsUriObject is used to determine whether to demand permissions.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private void DemandFileIOPermission()
        {
            // Demand FileIORead permission for any non-system fonts.
            if (!_skipDemand)
            {
                SecurityHelper.DemandUriReadPermission(_fontUri);
            }
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Classes
        //
        //------------------------------------------------------
 
        #region Private Classes
 
        private class PinnedByteArrayStream : UnmanagedMemoryStream
        {
            /// <SecurityNote>
            ///     Critical - as this function calls GCHandle.Alloc and UnmanagedMemoryStream.Initialize methods
            ///         which cause an elevation.
            ///     TreatAsSafe - as this only pins and unpins an array of bytes.
            /// </SecurityNote>
            [SecurityCritical, SecurityTreatAsSafe]
            internal PinnedByteArrayStream(byte [] bits)
            {
                _memoryHandle = GCHandle.Alloc(bits, GCHandleType.Pinned);
                
                unsafe
                {
                    // Initialize() method demands UnmanagedCode permission, and PinnedByteArrayStream is already marked as critical.
 
                    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); //Blessed Assert
 
                    try
                    {
                        Initialize(
	                        (byte *)_memoryHandle.AddrOfPinnedObject(),
	                        bits.Length, 
	                        bits.Length, 
	                        FileAccess.Read
                        );
                    }
                    finally
                    {
                        SecurityPermission.RevertAssert();
                    }
                }
            }
 
            ~PinnedByteArrayStream()
            {
                Dispose(false);
            }
 
            /// <SecurityNote>
            ///     Critical: This code calls into GCHandle.Free which is link demanded
            ///     TreatAsSafe: This code is ok to call. In the worst case it destroys some
            ///     objects in the app
            /// </SecurityNote>
            [SecurityCritical,SecurityTreatAsSafe]
            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
 
                Debug.Assert(_memoryHandle.IsAllocated);
                _memoryHandle.Free();
            }
 
            private GCHandle    _memoryHandle;
        }
 
        #endregion Private Classes
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        private bool _isComposite;
 
        /// <SecurityNote>
        /// Critical - fontUri can contain information about local file system.
        /// </SecurityNote>
        [SecurityCritical]
        private Uri     _fontUri;
 
        /// <SecurityNote>
        /// Critical - determines whether the font source was constructed from internal data,
        /// in which case the permission demand should be skipped.
        /// </SecurityNote>
        [SecurityCritical]
        private bool    _skipDemand;
 
        private static SizeLimitedCache<Uri, byte[]> _resourceCache = new SizeLimitedCache<Uri, byte[]>(MaximumCacheItems);
 
        /// <summary>
        /// The maximum number of fonts downloaded from pack:// Uris.
        /// </summary>
        private const int MaximumCacheItems = 10;
        private const string ObfuscatedContentType = "application/vnd.ms-package.obfuscated-opentype";
 
        #endregion Private Fields
    }
}