File: Base\MS\Internal\IO\Packaging\Compoundfile\ContainerUtilities.cs
Project: wpf\src\PresentationBuildTasks.csproj (PresentationBuildTasks)
//-----------------------------------------------------------------------------
//
// <copyright file="ContainerUtilities.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  Common container-related operations that can be shared among internal
//  components.
//
// History:
//  ??/??/2002: ?      : Initial implementation
//  03/10/2003: RogerCh: Added the path array <-> path string conversion.
//  05/29/2003: RogerCh: Ported to WCP tree.
//  11/21/2003: Sarjanas: Added a method to apply predefined fragments to a
//                        stream. 
//
//-----------------------------------------------------------------------------
 
using System;
using System.Collections;   // for IList
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;          // for StringBuilder
using System.Diagnostics;   // for Debug.Assert
using System.Security;
 
 
#if PBTCOMPILER
using MS.Utility;     // For SR.cs
using MS.Internal.PresentationBuildTasks;
#else
using System.Windows;
using MS.Internal.WindowsBase;
#endif
 
namespace MS.Internal.IO.Packaging.CompoundFile
{
    /// <summary>
    /// ContainerUtilities
    /// </summary>
    static internal class ContainerUtilities
    {
 
        static private readonly Int32 _int16Size = SizeOfInt16();
        static private readonly Int32 _int32Size = SizeOfInt32();
        static private readonly byte[] _paddingBuf = new byte[4];        // for writing DWORD padding
 
 
#if !PBTCOMPILER
        static private readonly Int32 _int64Size = SizeOfInt64();
 
        /// Used by ConvertBackSlashPathToStringArrayPath and 
        ///     ConvertStringArrayPathToBackSlashPath to separate path elements.
        static readonly internal char PathSeparator = '\\';
        static private readonly char[] _PathSeparatorArray = new char[] { PathSeparator };
        static readonly internal string PathSeparatorAsString = new string(ContainerUtilities.PathSeparator, 1);
 
        static private readonly CaseInsensitiveOrdinalStringComparer _stringCaseInsensitiveComparer = new CaseInsensitiveOrdinalStringComparer();
#endif
 
 
        /// <summary>
        /// Byte size of Int16 Type
        /// </summary>
        static internal Int32 Int16Size
        {
            get
            {
                return _int16Size;
            }
        }
 
#if !PBTCOMPILER
        /// <summary>
        /// Byte size of Int32 Type
        /// </summary>
        static internal Int32 Int32Size
        {
            get
            {
                return _int32Size;
            }
        }
 
        /// <summary>
        /// Byte size of Int64 Type
        /// </summary>
        static internal Int32 Int64Size
        {
            get
            {
                return _int64Size;
            }
        }
 
        static internal CaseInsensitiveOrdinalStringComparer StringCaseInsensitiveComparer
        {
            get
            {
                return _stringCaseInsensitiveComparer;
            }
        }
#endif
 
        //The length should be verified by the caller to be non-negative
        internal static int CalculateDWordPadBytesLength(int length)
        {
            Debug.Assert(length >= 0, "The length cannot be negative. Caller must verify this");
 
            int padLen = length & 3;
 
            if (padLen > 0)
            {
                padLen = 4 - padLen;
            }
 
            return (padLen);
        }
 
        /// <summary>
        /// Write out a string via a BinaryWriter.  Prefixed with int32 length in 
        /// bytes and padded at the end to the next 32-bit (DWORD) boundary
        /// </summary>
        /// <param name="writer">BinaryWriter configured for Unicode characters</param>
        /// <param name="outputString">String to write out to BinaryWriter</param>
        /// <returns>Number of bytes written including Prefixed int32 length, Unicode string,
        /// and padding</returns>
        /// <remarks>If writer==null, it will return the number of bytes it will take
        /// to write it out; If outputString==null, it will write out 0 as length and
        /// an empty string; After this operation, the current position of BinaryWriter
        /// will be changed</remarks>
        internal static int WriteByteLengthPrefixedDWordPaddedUnicodeString(BinaryWriter writer, String outputString)
        {
            checked
            {
                Int32 strByteLen = 0;
 
                if (outputString != null)
                {
                    strByteLen = outputString.Length * 2;
                }
 
                if (writer != null)
                {
                    // Write length, in bytes, of the unicode string
                    writer.Write(strByteLen);
 
                    if (strByteLen != 0)
                    {
                        // Write the Unicode characters
                        writer.Write(outputString.ToCharArray());
                    }
                }
 
                if (strByteLen != 0)
                {
                    int padLength = CalculateDWordPadBytesLength(strByteLen);
 
                    if (padLength != 0)
                    {
                        strByteLen += padLength;
 
                        if (writer != null)
                        {
                            writer.Write(_paddingBuf, 0, padLength);
                        }
                    }
                }
 
                strByteLen += _int32Size;       // Size of the string length written at the beginning of this method
 
                return strByteLen;
            }
        }
 
#if !PBTCOMPILER
        /// <summary>
        /// Read a string whose length is specified by the first four bytes as a
        /// int32 and whose end is padded to the next 32-bit (DWORD) boundary.
        /// </summary>
        /// <param name="reader">A BinaryReader initialized for Unicode characters</param>
        /// <returns>Unicode string without padding; After this operation, the current
        /// position of BinaryWriter will be changed</returns>
        internal static String ReadByteLengthPrefixedDWordPaddedUnicodeString(BinaryReader reader)
        {
            int bytesRead;
 
            return ReadByteLengthPrefixedDWordPaddedUnicodeString(reader, out bytesRead);
        }
 
        /// <summary>
        /// Read a string whose length is specified by the first four bytes as a
        /// int32 and whose end is padded to the next 32-bit (DWORD) boundary.
        /// </summary>
        /// <param name="reader">A BinaryReader initialized for Unicode characters</param>
        /// <param name="bytesRead">Total bytes read including prefixed length and padding</param>
        /// <returns>Unicode string without padding</returns>
        /// <remarks>If the string length is 0, it returns an empty string; After this operation,
        /// the current position of BinaryWriter will be changed</remarks>
        internal static String ReadByteLengthPrefixedDWordPaddedUnicodeString(BinaryReader reader, out int bytesRead)
        {
            checked
            {
                bytesRead = 0;
 
                CheckAgainstNull(reader, "reader");
 
                bytesRead = reader.ReadInt32();   // Length of the string in bytes
 
                String inString = null;
 
                if (bytesRead > 0)
                {
                    try
                    {
                        if (reader.BaseStream.Length < bytesRead / 2)
                        {
#if !PBTCOMPILER
                            throw new FileFormatException(SR.Get(SRID.InvalidStringFormat));
#else
                        throw new SerializationException(SR.Get(SRID.InvalidStringFormat));
#endif
                        }
                    }
                    catch (NotSupportedException)
                    {
                        // if the stream does not support the Length operator, it will throw a NotSupportedException.
                    }
 
                    inString = new String(reader.ReadChars(bytesRead / 2));
 
                    // Make sure the length of string read matches the length specified
                    if (inString.Length != (bytesRead / 2))
                    {
#if !PBTCOMPILER
                        throw new FileFormatException(SR.Get(SRID.InvalidStringFormat));
#else
                    throw new SerializationException(SR.Get(SRID.InvalidStringFormat));
#endif
                    }
                }
                else if (bytesRead == 0)
                {
                    inString = String.Empty;
                }
                else
                {
#if !PBTCOMPILER
                    throw new FileFormatException(SR.Get(SRID.InvalidStringFormat));
#else
                throw new SerializationException(SR.Get(SRID.InvalidStringFormat));
#endif
                }
 
                // skip the padding
                int padLength = CalculateDWordPadBytesLength(bytesRead);
 
                if (padLength > 0)
                {
                    byte[] padding;
 
                    padding = reader.ReadBytes(padLength);
 
                    // Make sure the string is padded with the correct number of bytes
                    if (padding.Length != padLength)
                    {
#if !PBTCOMPILER
                        throw new FileFormatException(SR.Get(SRID.InvalidStringFormat));
#else
                    throw new SerializationException(SR.Get(SRID.InvalidStringFormat));
#endif
                    }
                    bytesRead += padLength;
                }
 
                bytesRead += _int32Size; //// Size of the string length read at the beginning of this method
 
                return inString;
            }
        }
#endif
 
        /// <summary>
        /// Subset of CheckStringAgainstNullAndEmpty - and just checks against null reference.
        /// </summary>
        static internal void CheckAgainstNull(object paramRef,
            string testStringIdentifier)
        {
            if (paramRef == null)
                throw new ArgumentNullException(testStringIdentifier);
        }
 
#if !PBTCOMPILER

        /// <SecurityNote>
        ///  Critical : Calls critical Marshal.SizeOf
        ///  Safe     : Calls method with trusted input (well known safe type)
        /// </SecurityNote>
        [SecuritySafeCritical]
#endif        
        private static int SizeOfInt16()
        {
            return Marshal.SizeOf(typeof(Int16));
        }
        
#if !PBTCOMPILER

        /// <SecurityNote>
        ///  Critical : Calls critical Marshal.SizeOf
        ///  Safe     : Calls method with trusted input (well known safe type)
        /// </SecurityNote>
        [SecuritySafeCritical]
#endif        
        private static int SizeOfInt32()
        {
            return Marshal.SizeOf(typeof(Int32));
        }
        
#if !PBTCOMPILER
        /// <SecurityNote>
        ///  Critical : Calls critical Marshal.SizeOf
        ///  Safe     : Calls method with trusted input (well known safe type)
        /// </SecurityNote>
        [SecuritySafeCritical]
        private static int SizeOfInt64()
        {
            return Marshal.SizeOf(typeof(Int64));
        }
#endif
        
#if !PBTCOMPILER
        /// <summary>
        ///     Interprets a single string by treating it as a set of names
        /// delimited by a special character.  The character is the backslash,
        /// serving the same role it has served since the original MS-DOS.
        ///     The individual names are extracted from this string and 
        /// returned as an array of string names.
        ///
        ///  string "images\button.jpg" -> string [] { "images", "button.jpg" }
        ///
        /// </summary>
        /// <param name="backSlashPath">
        ///     String path to be converted
        /// </param>
        /// <returns>
        ///     The elements of the path as a string array
        /// </returns>
        /// <remarks>
        ///     Mirror counterpart of ConvertStringArrayPathToBackSlashPath
        /// </remarks>
 
        // In theory, parsing strings should be done with regular expressions.
        // In practice, the RegEx class is too heavyweight for this application.
 
        // IMPORTANT: When updating this, make sure the counterpart is similarly
        //  updated.
        static internal string[] ConvertBackSlashPathToStringArrayPath(string backSlashPath)
        {
            // A null string will get a null array
            if ((null == backSlashPath) || (0 == backSlashPath.Length))
                return new string[0];
 
            // Reject leading/trailing whitespace
            if (Char.IsWhiteSpace(backSlashPath[0]) ||
                Char.IsWhiteSpace(backSlashPath[backSlashPath.Length - 1]))
            {
                throw new ArgumentException(SR.Get(SRID.MalformedCompoundFilePath));
            }
 
            // Build the array
            string[] splitArray =
                backSlashPath.Split(_PathSeparatorArray);
 
            // Look for empty strings in the array
            foreach (string arrayElement in splitArray)
            {
                if (0 == arrayElement.Length)
                    throw new ArgumentException(
                        SR.Get(SRID.PathHasEmptyElement), "backSlashPath");
            }
 
            // No empty strings, this array should be fine.
            return splitArray;
        }
 
        /// <summary>
        ///     Concatenates the names in an array of strings into a backslash-
        /// delimited string.
        ///
        ///  string[] { "images", "button.jpg" } -> string "images\button.jpg"
        ///
        /// </summary>
        /// <param name="arrayPath">
        ///     String array of names
        /// </param>
        /// <returns>
        ///     Concatenated path with all the names in the given array
        ///</returns>
        /// <remarks>
        ///     Mirror counterpart to ConvertBackSlashPathToStringArrayPath
        ///</remarks>
 
        // IMPORTANT: When updating this, make sure the counterpart is similarly
        //  updated.
 
        static internal string ConvertStringArrayPathToBackSlashPath(IList arrayPath)
        {
            // Null array gets a null string
            if ((null == arrayPath) || (1 > arrayPath.Count))
                return String.Empty;
 
            // Length of one gets that element returned
            if (1 == arrayPath.Count)
                return (string)arrayPath[0];
 
            // More than one - OK it's time to build something.
            CheckStringForEmbeddedPathSeparator((string)arrayPath[0], "Path array element");
            StringBuilder pathBuilder =
                new StringBuilder((string)arrayPath[0]);
            for (int counter = 1; counter < arrayPath.Count; counter++)
            {
                CheckStringForEmbeddedPathSeparator((String)arrayPath[counter], "Path array element");
                pathBuilder.Append(PathSeparator);
                pathBuilder.Append((String)arrayPath[counter]);
            }
 
            return pathBuilder.ToString();
        }
 
        /// <summary>
        /// Convert to path when storage array and stream name are separate
        /// </summary>
        /// <param name="storages">storage collection (strings)</param>
        /// <param name="streamName">stream name</param>
        static internal string ConvertStringArrayPathToBackSlashPath(IList storages, string streamName)
        {
            string result = ConvertStringArrayPathToBackSlashPath(storages);
            if (result.Length > 0)
                return result + PathSeparator + streamName;
            else
                return streamName;
        }
 
        /// <summary>
        ///     Utility function to check a string (presumably an input to the
        /// caller function) against null, then against empty.  Throwing an
        /// exception if either is true.
        /// </summary>
        /// <param name="testString">
        ///     The string to be tested
        /// </param>
        /// <param name="testStringIdentifier">
        ///     A label for the test string to be used in the exception message.
        /// </param>
        /// <returns>
        ///     No return value
        ///</returns>
        /// <remarks>
        ///
        ///     CheckStringAgainstNullAndEmpty( fooName, "The name parameter ");
        ///
        ///     may get:
        ///
        ///         ArgumentNullException "The name parameter cannot be a null string"
        ///     or
        ///         ArgumentException "The name parameter cannot be an empty string"
        ///
        ///</remarks>
        static internal void CheckStringAgainstNullAndEmpty(string testString,
            string testStringIdentifier)
        {
            if (testString == null)
                throw new ArgumentNullException(testStringIdentifier);
 
            if (testString.Length == 0)
                throw new ArgumentException(SR.Get(SRID.StringEmpty), testStringIdentifier);
        }
 
        /// <summary>
        /// Checks if the given string fits within the range of reserved
        /// names and throws an ArgumentException if it does.
        /// The 0x01 through 0x1F characters, serving as the first character of the stream/storage name, 
        /// are reserved for use by OLE. This is a compound file restriction. 
        /// </summary>
        static internal void CheckStringAgainstReservedName(string nameString,
            string nameStringIdentifier)
        {
            if (IsReservedName(nameString))
                throw new ArgumentException(
                    SR.Get(SRID.StringCanNotBeReservedName, nameStringIdentifier));
        }
 
        /// <summary>
        /// A certain subset of compound file storage and stream names are 
        /// reserved for use be OLE. These are names that start with a character in the
        /// range 0x01 to 0x1F.  (1-31)
        /// This is a compound file restriction
        /// </summary>
        static internal bool IsReservedName(string nameString)
        {
            CheckStringAgainstNullAndEmpty(nameString, "nameString");
 
            return (nameString[0] >= '\x0001'
                    &&
                    nameString[0] <= '\x001F');
        }
 
        /// <summary>
        /// Checks for embedded delimiter in the string (as well as Null and Empty)
        /// </summary>
        /// <param name="testString">string to test</param>
        /// <param name="testStringIdentifier">message for exception</param>
        static internal void CheckStringForEmbeddedPathSeparator(string testString,
            string testStringIdentifier)
        {
            CheckStringAgainstNullAndEmpty(testString, testStringIdentifier);
 
            if (testString.IndexOf(PathSeparator) != -1)
                throw new ArgumentException(
                    SR.Get(SRID.NameCanNotHaveDelimiter,
                        testStringIdentifier,
                        PathSeparator), "testString");
        }
 
#endif
 
    }
}