|  | /******************************************************************************* | 
|  | * You may amend and distribute as you like, but don't remove this header! | 
|  | * | 
|  | * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | 
|  | * See http://www.codeplex.com/EPPlus for details. | 
|  | * | 
|  | * Copyright (C) 2011  Jan Källman | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  |  | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 
|  | * See the GNU Lesser General Public License for more details. | 
|  | * | 
|  | * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | 
|  | * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | 
|  | * | 
|  | * All code and executables are provided "as is" with no warranty either express or implied. | 
|  | * The author accepts no liability for any damage or loss of business that this product may cause. | 
|  | * | 
|  | * Code change notes: | 
|  | * | 
|  | * Author							Change						Date | 
|  | ******************************************************************************* | 
|  | * Jan Källman		Added		01-01-2012 | 
|  | * Jan Källman      Added compression support 27-03-2012 | 
|  | *******************************************************************************/ | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Linq; | 
|  | using System.Text; | 
|  | using System.Runtime.InteropServices; | 
|  | using comTypes = System.Runtime.InteropServices.ComTypes; | 
|  | using System.IO; | 
|  | using System.Security; | 
|  |  | 
|  | namespace OfficeOpenXml.Utils | 
|  | { | 
|  | #if !MONO | 
|  | internal class CompoundDocument | 
|  | { | 
|  | internal class StoragePart | 
|  | { | 
|  | public StoragePart() | 
|  | { | 
|  |  | 
|  | } | 
|  | internal Dictionary<string, StoragePart> SubStorage = new Dictionary<string, StoragePart>(); | 
|  | internal Dictionary<string, byte[]> DataStreams = new Dictionary<string, byte[]>(); | 
|  | } | 
|  | internal StoragePart Storage = null; | 
|  | internal CompoundDocument() | 
|  | { | 
|  | Storage = new CompoundDocument.StoragePart(); | 
|  | } | 
|  | internal CompoundDocument(FileInfo fi) | 
|  | { | 
|  | Read(fi); | 
|  | } | 
|  | internal CompoundDocument(ILockBytes lb) | 
|  | { | 
|  | Read(lb); | 
|  | } | 
|  | internal CompoundDocument(byte[] doc) | 
|  | { | 
|  | Read(doc); | 
|  | } | 
|  | internal void Read(FileInfo fi) | 
|  | { | 
|  | var b = File.ReadAllBytes(fi.FullName); | 
|  | Read(b); | 
|  | } | 
|  | [SecuritySafeCritical] | 
|  | internal void Read(byte[] doc) | 
|  | { | 
|  | ILockBytes lb; | 
|  | var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
|  |  | 
|  | IntPtr buffer = Marshal.AllocHGlobal(doc.Length); | 
|  | Marshal.Copy(doc, 0, buffer, doc.Length); | 
|  | UIntPtr readSize; | 
|  | lb.WriteAt(0, buffer, doc.Length, out readSize); | 
|  | Marshal.FreeHGlobal(buffer); | 
|  |  | 
|  | Read(lb); | 
|  | } | 
|  |  | 
|  | [SecuritySafeCritical] | 
|  | internal void Read(ILockBytes lb) | 
|  | { | 
|  | if (StgIsStorageILockBytes(lb) == 0) | 
|  | { | 
|  | IStorage storage = null; | 
|  | if (StgOpenStorageOnILockBytes( | 
|  | lb, | 
|  | null, | 
|  | STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE, | 
|  | IntPtr.Zero, | 
|  | 0, | 
|  | out storage) == 0) | 
|  | { | 
|  | Storage = new StoragePart(); | 
|  | ReadParts(storage, Storage); | 
|  | Marshal.ReleaseComObject(storage); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | throw (new InvalidDataException(string.Format("Part is not a compound document"))); | 
|  | } | 
|  | } | 
|  | #region  Compression | 
|  | /// <summary> | 
|  | /// Compression using a run length encoding algorithm. | 
|  | /// See MS-OVBA Section 2.4 | 
|  | /// </summary> | 
|  | /// <param name="part">Byte array to decompress</param> | 
|  | /// <returns></returns> | 
|  | internal static byte[] CompressPart(byte[] part) | 
|  | { | 
|  | MemoryStream ms = new MemoryStream(4096); | 
|  | BinaryWriter br = new BinaryWriter(ms); | 
|  | br.Write((byte)1); | 
|  |  | 
|  | int compStart = 1; | 
|  | int compEnd = 4098; | 
|  | int decompStart = 0; | 
|  | int decompEnd = part.Length < 4096 ? part.Length : 4096; | 
|  |  | 
|  | while (decompStart < decompEnd && compStart < compEnd) | 
|  | { | 
|  | byte[] chunk = CompressChunk(part, ref decompStart); | 
|  | ushort header; | 
|  | if (chunk == null || chunk.Length == 0) | 
|  | { | 
|  | header = 4096 | 0x600;  //B=011 A=0 | 
|  | } | 
|  | else | 
|  | { | 
|  | header = (ushort)(((chunk.Length - 1) & 0xFFF)); | 
|  | header |= 0xB000;   //B=011 A=1 | 
|  | br.Write(header); | 
|  | br.Write(chunk); | 
|  | } | 
|  | decompEnd = part.Length < decompStart + 4096 ? part.Length : decompStart+4096; | 
|  | } | 
|  |  | 
|  |  | 
|  | br.Flush(); | 
|  | return ms.ToArray(); | 
|  | } | 
|  | private static byte[] CompressChunk(byte[] buffer, ref int startPos) | 
|  | { | 
|  | var comprBuffer=new byte[4096]; | 
|  | int flagPos = 0; | 
|  | int cPos=1; | 
|  | int dPos = startPos; | 
|  | int dEnd=startPos+4096 < buffer.Length? startPos+4096 : buffer.Length; | 
|  | while(dPos<dEnd) | 
|  | { | 
|  | byte tokenFlags = 0; | 
|  | for (int i = 0; i < 8; i++) | 
|  | { | 
|  | if (dPos - startPos > 0) | 
|  | { | 
|  | int bestCandidate = -1; | 
|  | int bestLength = 0; | 
|  | int candidate = dPos - 1; | 
|  | int bitCount = GetLengthBits(dPos-startPos); | 
|  | int bits = (16 - bitCount); | 
|  | ushort lengthMask = (ushort)((0xFFFF) >> bits); | 
|  |  | 
|  | while (candidate >= startPos) | 
|  | { | 
|  | if (buffer[candidate] == buffer[dPos]) | 
|  | { | 
|  | int length = 1; | 
|  |  | 
|  | while (buffer.Length > dPos + length && buffer[candidate + length] == buffer[dPos + length] && length < lengthMask && dPos+length < dEnd) | 
|  | { | 
|  | length++; | 
|  | } | 
|  | if (length > bestLength) | 
|  | { | 
|  | bestCandidate = candidate; | 
|  | bestLength = length; | 
|  | if (bestLength == lengthMask) | 
|  | { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | candidate--; | 
|  | } | 
|  | if (bestLength >= 3)    //Copy token | 
|  | { | 
|  | tokenFlags |= (byte)(1 << i); | 
|  |  | 
|  | UInt16 offsetMask = (ushort)~lengthMask; | 
|  | ushort token = (ushort)(((ushort)(dPos - (bestCandidate+1))) << (bitCount) | (ushort)(bestLength - 3)); | 
|  | Array.Copy(BitConverter.GetBytes(token), 0, comprBuffer, cPos, 2); | 
|  | dPos = dPos + bestLength; | 
|  | cPos += 2; | 
|  | //SetCopy Token | 
|  | } | 
|  | else | 
|  | { | 
|  | comprBuffer[cPos++] = buffer[dPos++]; | 
|  | } | 
|  | } | 
|  |  | 
|  | else | 
|  | { | 
|  | comprBuffer[cPos++] = buffer[dPos++]; | 
|  | } | 
|  | if (dPos >= dEnd) break; | 
|  | } | 
|  | comprBuffer[flagPos] = tokenFlags; | 
|  | flagPos = cPos++; | 
|  | } | 
|  | var ret = new byte[cPos - 1]; | 
|  | Array.Copy(comprBuffer, ret, ret.Length); | 
|  | startPos = dEnd; | 
|  | return ret; | 
|  | } | 
|  | internal static byte[] DecompressPart(byte[] part) | 
|  | { | 
|  | return DecompressPart(part, 0); | 
|  | } | 
|  | /// <summary> | 
|  | /// Decompression using a run length encoding algorithm. | 
|  | /// See MS-OVBA Section 2.4 | 
|  | /// </summary> | 
|  | /// <param name="part">Byte array to decompress</param> | 
|  | /// <param name="startPos"></param> | 
|  | /// <returns></returns> | 
|  | internal static byte[] DecompressPart(byte[] part, int startPos) | 
|  | { | 
|  |  | 
|  | if (part[startPos] != 1) | 
|  | { | 
|  | return null; | 
|  | } | 
|  | MemoryStream ms = new MemoryStream(4096); | 
|  | int compressPos = startPos + 1; | 
|  | while(compressPos < part.Length-1) | 
|  | { | 
|  | DecompressChunk(ms, part, ref compressPos); | 
|  | } | 
|  | return ms.ToArray(); | 
|  | } | 
|  | private static void DecompressChunk(MemoryStream ms, byte[] compBuffer, ref int pos) | 
|  | { | 
|  | ushort header = BitConverter.ToUInt16(compBuffer, pos); | 
|  | int  decomprPos=0; | 
|  | byte[] buffer = new byte[4198]; //Add an extra 100 byte. Some workbooks have overflowing worksheets. | 
|  | int size = (int)(header & 0xFFF)+3; | 
|  | int endPos = pos+size; | 
|  | int a = (int)(header & 0x7000) >> 12; | 
|  | int b = (int)(header & 0x8000) >> 15; | 
|  | pos += 2; | 
|  | if (b == 1) //Compressed chunk | 
|  | { | 
|  | while (pos < compBuffer.Length && pos < endPos) | 
|  | { | 
|  | //Decompress token | 
|  | byte token = compBuffer[pos++]; | 
|  | if (pos >= endPos) | 
|  | break; | 
|  | for (int i = 0; i < 8; i++) | 
|  | { | 
|  | //Literal token | 
|  | if ((token & (1 << i)) == 0) | 
|  | { | 
|  | ms.WriteByte(compBuffer[pos]); | 
|  | buffer[decomprPos++] = compBuffer[pos++]; | 
|  | } | 
|  | else //copy token | 
|  | { | 
|  | var t = BitConverter.ToUInt16(compBuffer, pos); | 
|  | int bitCount = GetLengthBits(decomprPos); | 
|  | int bits = (16 - bitCount); | 
|  | ushort lengthMask = (ushort)((0xFFFF) >> bits); | 
|  | UInt16 offsetMask = (ushort)~lengthMask; | 
|  | var length = (lengthMask & t) + 3; | 
|  | var offset = (offsetMask & t) >> (bitCount); | 
|  | int source = decomprPos - offset - 1; | 
|  | if (decomprPos + length >= buffer.Length) | 
|  | { | 
|  | // Be lenient on decompression, so extend our decompression | 
|  | // buffer. Excel generated VBA projects do encounter this issue. | 
|  | // One would think (not surprisingly that the VBA project spec) | 
|  | // over emphasizes the size restrictions of a DecompressionChunk. | 
|  | var largerBuffer = new byte[buffer.Length + 4098]; | 
|  | Array.Copy(buffer, largerBuffer, decomprPos); | 
|  | buffer = largerBuffer; | 
|  | } | 
|  |  | 
|  | // Even though we've written to the MemoryStream, | 
|  | // We still should decompress the token into this buffer | 
|  | // in case a later token needs to use the bytes we're | 
|  | // about to decompress. | 
|  | for (int c = 0; c < length; c++) | 
|  | { | 
|  | ms.WriteByte(buffer[source]); //Must copy byte-wise because copytokens can overlap compressed buffer. | 
|  | buffer[decomprPos++] = buffer[source++]; | 
|  | } | 
|  |  | 
|  | pos += 2; | 
|  |  | 
|  | } | 
|  | if (pos >= endPos) | 
|  | break; | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  | else //Raw chunk | 
|  | { | 
|  | ms.Write(compBuffer, pos, size); | 
|  | pos += size; | 
|  | return; | 
|  | } | 
|  | } | 
|  | private static int GetLengthBits(int decompPos) | 
|  | { | 
|  | if (decompPos <= 16) | 
|  | { | 
|  | return 12; | 
|  | } | 
|  | else if (decompPos <= 32) | 
|  | { | 
|  | return 11; | 
|  | } | 
|  | else if (decompPos <= 64) | 
|  | { | 
|  | return 10; | 
|  | } | 
|  | else if (decompPos <= 128) | 
|  | { | 
|  | return 9; | 
|  | } | 
|  | else if (decompPos <= 256) | 
|  | { | 
|  | return 8; | 
|  | } | 
|  | else if (decompPos <= 512) | 
|  | { | 
|  | return 7; | 
|  | } | 
|  | else if (decompPos <= 1024) | 
|  | { | 
|  | return 6; | 
|  | } | 
|  | else if (decompPos <= 2048) | 
|  | { | 
|  | return 5; | 
|  | } | 
|  | else if (decompPos <= 4096) | 
|  | { | 
|  | return 4; | 
|  | } | 
|  | else | 
|  | { | 
|  | //We should never end up here, but if so this is the formula to calculate the bits... | 
|  | return 12 - (int)Math.Truncate(Math.Log(decompPos - 1 >> 4, 2) + 1); | 
|  | } | 
|  | } | 
|  | #endregion | 
|  | #region "API declare" | 
|  | [ComImport] | 
|  | [Guid("0000000d-0000-0000-C000-000000000046")] | 
|  | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | 
|  | internal interface IEnumSTATSTG | 
|  | { | 
|  | // The user needs to allocate an STATSTG array whose size is celt. | 
|  | [PreserveSig] | 
|  | uint Next( | 
|  | uint celt, | 
|  | [MarshalAs(UnmanagedType.LPArray), Out] | 
|  | System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt, | 
|  | out uint pceltFetched | 
|  | ); | 
|  |  | 
|  | void Skip(uint celt); | 
|  |  | 
|  | void Reset(); | 
|  |  | 
|  | [return: MarshalAs(UnmanagedType.Interface)] | 
|  | IEnumSTATSTG Clone(); | 
|  | } | 
|  |  | 
|  | [ComImport] | 
|  | [Guid("0000000b-0000-0000-C000-000000000046")] | 
|  | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | 
|  | interface IStorage | 
|  | { | 
|  | void CreateStream( | 
|  | /* [string][in] */ string pwcsName, | 
|  | /* [in] */ uint grfMode, | 
|  | /* [in] */ uint reserved1, | 
|  | /* [in] */ uint reserved2, | 
|  | /* [out] */ out comTypes.IStream ppstm); | 
|  |  | 
|  | void OpenStream( | 
|  | /* [string][in] */ string pwcsName, | 
|  | /* [unique][in] */ IntPtr reserved1, | 
|  | /* [in] */ uint grfMode, | 
|  | /* [in] */ uint reserved2, | 
|  | /* [out] */ out comTypes.IStream ppstm); | 
|  |  | 
|  | void CreateStorage( | 
|  | /* [string][in] */ string pwcsName, | 
|  | /* [in] */ uint grfMode, | 
|  | /* [in] */ uint reserved1, | 
|  | /* [in] */ uint reserved2, | 
|  | /* [out] */ out IStorage ppstg); | 
|  |  | 
|  | void OpenStorage( | 
|  | /* [string][unique][in] */ string pwcsName, | 
|  | /* [unique][in] */ IStorage pstgPriority, | 
|  | /* [in] */ STGM grfMode, | 
|  | /* [unique][in] */ IntPtr snbExclude, | 
|  | /* [in] */ uint reserved, | 
|  | /* [out] */ out IStorage ppstg); | 
|  |  | 
|  | void CopyTo( | 
|  | [InAttribute] uint ciidExclude, | 
|  | [InAttribute] Guid[] rgiidExclude, | 
|  | [InAttribute] IntPtr snbExclude, | 
|  | [InAttribute] IStorage pstgDest | 
|  | ); | 
|  |  | 
|  | void MoveElementTo( | 
|  | /* [string][in] */ string pwcsName, | 
|  | /* [unique][in] */ IStorage pstgDest, | 
|  | /* [string][in] */ string pwcsNewName, | 
|  | /* [in] */ uint grfFlags); | 
|  |  | 
|  | void Commit( | 
|  | /* [in] */ uint grfCommitFlags); | 
|  |  | 
|  | void Revert(); | 
|  |  | 
|  | void EnumElements( | 
|  | /* [in] */ uint reserved1, | 
|  | /* [size_is][unique][in] */ IntPtr reserved2, | 
|  | /* [in] */ uint reserved3, | 
|  | /* [out] */ out IEnumSTATSTG ppenum); | 
|  |  | 
|  | void DestroyElement( | 
|  | /* [string][in] */ string pwcsName); | 
|  |  | 
|  | void RenameElement( | 
|  | /* [string][in] */ string pwcsOldName, | 
|  | /* [string][in] */ string pwcsNewName); | 
|  |  | 
|  | void SetElementTimes( | 
|  | /* [string][unique][in] */ string pwcsName, | 
|  | /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pctime, | 
|  | /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME patime, | 
|  | /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pmtime); | 
|  |  | 
|  | void SetClass( | 
|  | /* [in] */ Guid clsid); | 
|  |  | 
|  | void SetStateBits( | 
|  | /* [in] */ uint grfStateBits, | 
|  | /* [in] */ uint grfMask); | 
|  |  | 
|  | void Stat( | 
|  | /* [out] */ out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, | 
|  | /* [in] */ uint grfStatFlag); | 
|  |  | 
|  | } | 
|  | [ComVisible(false)] | 
|  | [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000A-0000-0000-C000-000000000046")] | 
|  | internal interface ILockBytes | 
|  | { | 
|  | void ReadAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbRead); | 
|  | void WriteAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbWritten); | 
|  | void Flush(); | 
|  | void SetSize(long cb); | 
|  | void LockRegion(long libOffset, long cb, int dwLockType); | 
|  | void UnlockRegion(long libOffset, long cb, int dwLockType); | 
|  | void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag); | 
|  | } | 
|  | [Flags] | 
|  | internal enum STGM : int | 
|  | { | 
|  | DIRECT = 0x00000000, | 
|  | TRANSACTED = 0x00010000, | 
|  | SIMPLE = 0x08000000, | 
|  | READ = 0x00000000, | 
|  | WRITE = 0x00000001, | 
|  | READWRITE = 0x00000002, | 
|  | SHARE_DENY_NONE = 0x00000040, | 
|  | SHARE_DENY_READ = 0x00000030, | 
|  | SHARE_DENY_WRITE = 0x00000020, | 
|  | SHARE_EXCLUSIVE = 0x00000010, | 
|  | PRIORITY = 0x00040000, | 
|  | DELETEONRELEASE = 0x04000000, | 
|  | NOSCRATCH = 0x00100000, | 
|  | CREATE = 0x00001000, | 
|  | CONVERT = 0x00020000, | 
|  | FAILIFTHERE = 0x00000000, | 
|  | NOSNAPSHOT = 0x00200000, | 
|  | DIRECT_SWMR = 0x00400000, | 
|  | } | 
|  |  | 
|  | internal enum STATFLAG : uint | 
|  | { | 
|  | STATFLAG_DEFAULT = 0, | 
|  | STATFLAG_NONAME = 1, | 
|  | STATFLAG_NOOPEN = 2 | 
|  | } | 
|  |  | 
|  | internal enum STGTY : int | 
|  | { | 
|  | STGTY_STORAGE = 1, | 
|  | STGTY_STREAM = 2, | 
|  | STGTY_LOCKBYTES = 3, | 
|  | STGTY_PROPERTY = 4 | 
|  | } | 
|  |  | 
|  | [DllImport("ole32.dll")] | 
|  | private static extern int StgIsStorageFile( | 
|  | [MarshalAs(UnmanagedType.LPWStr)] string pwcsName); | 
|  | [DllImport("ole32.dll")] | 
|  | private static extern int StgIsStorageILockBytes( | 
|  | ILockBytes plkbyt); | 
|  |  | 
|  |  | 
|  | [DllImport("ole32.dll")] | 
|  | static extern int StgOpenStorage( | 
|  | [MarshalAs(UnmanagedType.LPWStr)] string pwcsName, | 
|  | IStorage pstgPriority, | 
|  | STGM grfMode, | 
|  | IntPtr snbExclude, | 
|  | uint reserved, | 
|  | out IStorage ppstgOpen); | 
|  |  | 
|  | [DllImport("ole32.dll")] | 
|  | static extern int StgOpenStorageOnILockBytes( | 
|  | ILockBytes plkbyt, | 
|  | IStorage pStgPriority, | 
|  | STGM grfMode, | 
|  | IntPtr snbEnclude, | 
|  | uint reserved, | 
|  | out IStorage ppstgOpen); | 
|  | [DllImport("ole32.dll")] | 
|  | static extern int CreateILockBytesOnHGlobal( | 
|  | IntPtr hGlobal, | 
|  | bool fDeleteOnRelease, | 
|  | out ILockBytes ppLkbyt); | 
|  |  | 
|  | [DllImport("ole32.dll")] | 
|  | static extern int StgCreateDocfileOnILockBytes(ILockBytes plkbyt, STGM grfMode, int reserved, out IStorage ppstgOpen); | 
|  |  | 
|  | #endregion | 
|  | [SecuritySafeCritical] | 
|  | internal static int IsStorageFile(string Name) | 
|  | { | 
|  | return StgIsStorageFile(Name); | 
|  | } | 
|  | [SecuritySafeCritical] | 
|  | internal static int IsStorageILockBytes(ILockBytes lb) | 
|  | { | 
|  | return StgIsStorageILockBytes(lb); | 
|  | } | 
|  | [SecuritySafeCritical] | 
|  | internal static ILockBytes GetLockbyte(MemoryStream stream) | 
|  | { | 
|  | ILockBytes lb; | 
|  | var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
|  | byte[] docArray = stream.GetBuffer(); | 
|  |  | 
|  | IntPtr buffer = Marshal.AllocHGlobal(docArray.Length); | 
|  | Marshal.Copy(docArray, 0, buffer, docArray.Length); | 
|  | UIntPtr readSize; | 
|  | lb.WriteAt(0, buffer, docArray.Length, out readSize); | 
|  | Marshal.FreeHGlobal(buffer); | 
|  |  | 
|  | return lb; | 
|  | } | 
|  | [SecuritySafeCritical] | 
|  | private MemoryStream ReadParts(IStorage storage, StoragePart storagePart) | 
|  | { | 
|  | MemoryStream ret = null; | 
|  | comTypes.STATSTG statstg; | 
|  |  | 
|  | storage.Stat(out statstg, (uint)STATFLAG.STATFLAG_DEFAULT); | 
|  |  | 
|  | IEnumSTATSTG pIEnumStatStg = null; | 
|  | storage.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg); | 
|  |  | 
|  | comTypes.STATSTG[] regelt = { statstg }; | 
|  | uint fetched = 0; | 
|  | uint res = pIEnumStatStg.Next(1, regelt, out fetched); | 
|  |  | 
|  | //if (regelt[0].pwcsName == "DataSpaces") | 
|  | //{ | 
|  | //    PrintStorage(storage, regelt[0],""); | 
|  | //} | 
|  | while (res != 1) | 
|  | { | 
|  | foreach (var item in regelt) | 
|  | { | 
|  | if (item.type == 1) | 
|  | { | 
|  | IStorage subStorage; | 
|  | storage.OpenStorage(item.pwcsName, null, STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE, IntPtr.Zero, 0, out subStorage); | 
|  | StoragePart subStoragePart=new StoragePart(); | 
|  | storagePart.SubStorage.Add(item.pwcsName, subStoragePart); | 
|  | ReadParts(subStorage, subStoragePart); | 
|  | } | 
|  | else | 
|  | { | 
|  | storagePart.DataStreams.Add(item.pwcsName, GetOleStream(storage, item)); | 
|  | } | 
|  | } | 
|  | res = pIEnumStatStg.Next(1, regelt, out fetched); | 
|  | } | 
|  | Marshal.ReleaseComObject(pIEnumStatStg); | 
|  | return ret; | 
|  | } | 
|  | // Help method to print a storage part binary to c:\temp | 
|  | //private void PrintStorage(IStorage storage, System.Runtime.InteropServices.ComTypes.STATSTG sTATSTG, string topName) | 
|  | //{ | 
|  | //    IStorage ds; | 
|  | //    if (topName.Length > 0) | 
|  | //    { | 
|  | //        topName = topName[0] < 'A' ? topName.Substring(1, topName.Length - 1) : topName; | 
|  | //    } | 
|  | //    storage.OpenStorage(sTATSTG.pwcsName, | 
|  | //        null, | 
|  | //        (uint)(STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE), | 
|  | //        IntPtr.Zero, | 
|  | //        0, | 
|  | //        out ds); | 
|  |  | 
|  | //    System.Runtime.InteropServices.ComTypes.STATSTG statstgSub; | 
|  | //    ds.Stat(out statstgSub, (uint)STATFLAG.STATFLAG_DEFAULT); | 
|  |  | 
|  | //    IEnumSTATSTG pIEnumStatStgSub = null; | 
|  | //    System.Runtime.InteropServices.ComTypes.STATSTG[] regeltSub = { statstgSub }; | 
|  | //    ds.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStgSub); | 
|  |  | 
|  | //    uint fetched = 0; | 
|  | //    while (pIEnumStatStgSub.Next(1, regeltSub, out fetched) == 0) | 
|  | //    { | 
|  | //        string sName = regeltSub[0].pwcsName[0] < 'A' ? regeltSub[0].pwcsName.Substring(1, regeltSub[0].pwcsName.Length - 1) : regeltSub[0].pwcsName; | 
|  | //        if (regeltSub[0].type == 1) | 
|  | //        { | 
|  | //            PrintStorage(ds, regeltSub[0], topName + sName + "_"); | 
|  | //        } | 
|  | //        else if(regeltSub[0].type==2) | 
|  | //        { | 
|  | //            File.WriteAllBytes(@"c:\temp\" + topName + sName + ".bin", GetOleStream(ds, regeltSub[0])); | 
|  | //        } | 
|  | //    } | 
|  | //}    } | 
|  | /// <summary> | 
|  | /// Read the stream and return it as a byte-array | 
|  | /// </summary> | 
|  | /// <param name="storage"></param> | 
|  | /// <param name="statstg"></param> | 
|  | /// <returns></returns> | 
|  | [SecuritySafeCritical] | 
|  | private byte[] GetOleStream(IStorage storage, comTypes.STATSTG statstg) | 
|  | { | 
|  | comTypes.IStream pIStream; | 
|  | storage.OpenStream(statstg.pwcsName, | 
|  | IntPtr.Zero, | 
|  | (uint)(STGM.READ | STGM.SHARE_EXCLUSIVE), | 
|  | 0, | 
|  | out pIStream); | 
|  |  | 
|  | byte[] data = new byte[statstg.cbSize]; | 
|  | pIStream.Read(data, (int)statstg.cbSize, IntPtr.Zero); | 
|  | Marshal.ReleaseComObject(pIStream); | 
|  |  | 
|  | return data; | 
|  | } | 
|  |  | 
|  | [SecuritySafeCritical] | 
|  | internal byte[] Save() | 
|  | { | 
|  | ILockBytes lb; | 
|  | var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); | 
|  |  | 
|  | IStorage storage = null; | 
|  | byte[] ret = null; | 
|  |  | 
|  | //Create the document in-memory | 
|  | if (StgCreateDocfileOnILockBytes(lb, | 
|  | STGM.CREATE | STGM.READWRITE | STGM.SHARE_EXCLUSIVE | STGM.TRANSACTED, | 
|  | 0, | 
|  | out storage)==0) | 
|  | { | 
|  | foreach(var store in this.Storage.SubStorage) | 
|  | { | 
|  | CreateStore(store.Key, store.Value, storage); | 
|  | } | 
|  | CreateStreams(this.Storage, storage); | 
|  | lb.Flush(); | 
|  |  | 
|  | //Now copy the unmanaged stream to a byte array --> memory stream | 
|  | var statstg = new comTypes.STATSTG(); | 
|  | lb.Stat(out statstg, 0); | 
|  | int size = (int)statstg.cbSize; | 
|  | IntPtr buffer = Marshal.AllocHGlobal(size); | 
|  | UIntPtr readSize; | 
|  | ret=new byte[size]; | 
|  | lb.ReadAt(0, buffer, size, out readSize); | 
|  | Marshal.Copy(buffer, ret, 0, size); | 
|  | Marshal.FreeHGlobal(buffer); | 
|  | } | 
|  | Marshal.ReleaseComObject(storage); | 
|  | Marshal.ReleaseComObject(lb); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | private void CreateStore(string name, StoragePart subStore, IStorage storage) | 
|  | { | 
|  | IStorage subStorage; | 
|  | storage.CreateStorage(name, (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out subStorage); | 
|  | storage.Commit(0); | 
|  | foreach (var store in subStore.SubStorage) | 
|  | { | 
|  | CreateStore(store.Key, store.Value, subStorage); | 
|  | } | 
|  |  | 
|  | CreateStreams(subStore, subStorage); | 
|  | } | 
|  |  | 
|  | private void CreateStreams(StoragePart subStore, IStorage subStorage) | 
|  | { | 
|  | foreach (var ds in subStore.DataStreams) | 
|  | { | 
|  | comTypes.IStream stream; | 
|  | subStorage.CreateStream(ds.Key, (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out stream); | 
|  | stream.Write(ds.Value, ds.Value.Length, IntPtr.Zero); | 
|  | } | 
|  | subStorage.Commit(0); | 
|  | } | 
|  |  | 
|  | } | 
|  | #endif | 
|  | } |