From 4ebc33174c02e1c9f5693b5ef38ecfe3292c687f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 11 Aug 2017 00:39:04 -0700 Subject: Move to .NET Core 2.0 --- src/WixToolset.Data/FileStructure.cs | 294 +++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 src/WixToolset.Data/FileStructure.cs (limited to 'src/WixToolset.Data/FileStructure.cs') diff --git a/src/WixToolset.Data/FileStructure.cs b/src/WixToolset.Data/FileStructure.cs new file mode 100644 index 00000000..7265a51d --- /dev/null +++ b/src/WixToolset.Data/FileStructure.cs @@ -0,0 +1,294 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Data +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + + /// + /// Class that understands the standard file structures in the WiX toolset. + /// + public class FileStructure : IDisposable + { + private long dataStreamOffset; + private long[] embeddedFileSizes; + private Stream stream; + private bool disposed; + + private static readonly Dictionary SupportedFileFormats = new Dictionary() + { + { "wixobj", FileFormat.Wixobj }, + { "wixlib", FileFormat.Wixlib }, + { "wixout", FileFormat.Wixout }, + { "wixpdb", FileFormat.Wixpdb }, + { "wixmst", FileFormat.Wixout }, + { "wixmsp", FileFormat.Wixout }, + }; + + /// + /// Use Create or Read to create a FileStructure. + /// + private FileStructure() { } + + /// + /// Count of embedded files in the file structure. + /// + public int EmbeddedFileCount { get { return this.embeddedFileSizes.Length; } } + + /// + /// File format of the file structure. + /// + public FileFormat FileFormat { get; private set; } + + /// + /// Creates a new file structure. + /// + /// Stream to write the file structure to. + /// File format for the file structure. + /// Paths to files to embedd in the file structure. + /// Newly created file structure. + public static FileStructure Create(Stream stream, FileFormat fileFormat, List embedFilePaths) + { + FileStructure fs = new FileStructure(); + using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream)) + using (BinaryWriter writer = new BinaryWriter(wrapper)) + { + fs.WriteType(writer, fileFormat); + + fs.WriteEmbeddedFiles(writer, embedFilePaths ?? new List()); + + // Remember the data stream offset, which is right after the embedded files have been written. + fs.dataStreamOffset = stream.Position; + } + + fs.stream = stream; + + return fs; + } + + /// + /// Reads a file structure from an open stream. + /// + /// Stream to read from. + /// File structure populated from the stream. + public static FileStructure Read(Stream stream) + { + FileStructure fs = new FileStructure(); + using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream)) + using (BinaryReader reader = new BinaryReader(wrapper)) + { + fs.FileFormat = FileStructure.ReadFileFormat(reader); + + if (FileFormat.Unknown != fs.FileFormat) + { + fs.embeddedFileSizes = FileStructure.ReadEmbeddedFileSizes(reader); + + // Remember the data stream offset, which is right after the embedded files have been written. + fs.dataStreamOffset = stream.Position; + foreach (long size in fs.embeddedFileSizes) + { + fs.dataStreamOffset += size; + } + } + } + + fs.stream = stream; + + return fs; + } + + /// + /// Guess at the file format based on the file extension. + /// + /// File extension to guess the file format for. + /// Best guess at file format. + public static FileFormat GuessFileFormatFromExtension(string extension) + { + FileFormat format; + return FileStructure.SupportedFileFormats.TryGetValue(extension.TrimStart('.').ToLowerInvariant(), out format) ? format : FileFormat.Unknown; + } + + /// + /// Probes a stream to determine the file format. + /// + /// Stream to test. + /// The file format. + public static FileFormat TestFileFormat(Stream stream) + { + FileFormat format = FileFormat.Unknown; + + long position = stream.Position; + + try + { + using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream)) + using (BinaryReader reader = new BinaryReader(wrapper)) + { + format = FileStructure.ReadFileFormat(reader); + } + } + finally + { + stream.Seek(position, SeekOrigin.Begin); + } + + return format; + } + + /// + /// Extracts an embedded file. + /// + /// Index to the file to extract. + /// Path to write the extracted file to. + public void ExtractEmbeddedFile(int embeddedIndex, string outputPath) + { + if (this.EmbeddedFileCount <= embeddedIndex) + { + throw new ArgumentOutOfRangeException("embeddedIndex"); + } + + long header = 6 + 4 + (this.embeddedFileSizes.Length * 8); // skip the type + the count of embedded files + all the sizes of embedded files. + long position = this.embeddedFileSizes.Take(embeddedIndex).Sum(); // skip to the embedded file we want. + long size = this.embeddedFileSizes[embeddedIndex]; + + this.stream.Seek(header + position, SeekOrigin.Begin); + + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + + using (FileStream output = File.OpenWrite(outputPath)) + { + int read; + int total = 0; + byte[] buffer = new byte[64 * 1024]; + while (0 < (read = this.stream.Read(buffer, 0, (int)Math.Min(buffer.Length, size - total)))) + { + output.Write(buffer, 0, read); + total += read; + } + } + } + + /// + /// Gets a non-closing stream to the data of the file. + /// + /// Stream to the data of the file. + public Stream GetDataStream() + { + this.stream.Seek(this.dataStreamOffset, SeekOrigin.Begin); + return new NonClosingStreamWrapper(this.stream); + } + + /// + /// Disposes of the internsl state of the file structure. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of the internsl state of the file structure. + /// + /// True if disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + if (null != this.stream) + { + // We do not own the stream, so we don't close it. We're just resetting our internal state. + this.embeddedFileSizes = null; + this.dataStreamOffset = 0; + this.stream = null; + } + } + } + + this.disposed = true; + } + + private static FileFormat ReadFileFormat(BinaryReader reader) + { + FileFormat format = FileFormat.Unknown; + + string type = new string(reader.ReadChars(6)); + FileStructure.SupportedFileFormats.TryGetValue(type, out format); + + return format; + } + + private static long[] ReadEmbeddedFileSizes(BinaryReader reader) + { + uint count = reader.ReadUInt32(); + + long[] embeddedFileSizes = new long[count]; + + for (int i = 0; i < embeddedFileSizes.Length; ++i) + { + embeddedFileSizes[i] = (long)reader.ReadUInt64(); + } + + return embeddedFileSizes; + } + + private BinaryWriter WriteType(BinaryWriter writer, FileFormat fileFormat) + { + string type = null; + foreach (var supported in FileStructure.SupportedFileFormats) + { + if (supported.Value.Equals(fileFormat)) + { + type = supported.Key; + break; + } + } + + if (String.IsNullOrEmpty(type)) + { + throw new ArgumentException("Unknown file format type", "fileFormat"); + } + + this.FileFormat = fileFormat; + + Debug.Assert(6 == type.ToCharArray().Length); + writer.Write(type.ToCharArray()); + return writer; + } + + private BinaryWriter WriteEmbeddedFiles(BinaryWriter writer, List embedFilePaths) + { + // First write the count of embedded files as a Uint32; + writer.Write((uint)embedFilePaths.Count); + + this.embeddedFileSizes = new long[embedFilePaths.Count]; + + // Next write out the size of each file as a Uint64 in order. + FileInfo[] files = new FileInfo[embedFilePaths.Count]; + for (int i = 0; i < embedFilePaths.Count; ++i) + { + files[i] = new FileInfo(embedFilePaths[i]); + + this.embeddedFileSizes[i] = files[i].Length; + writer.Write((ulong)this.embeddedFileSizes[i]); + } + + // Next write out the content of each file *after* the sizes of + // *all* of the files were written. + foreach (FileInfo file in files) + { + using (FileStream stream = file.OpenRead()) + { + stream.CopyTo(writer.BaseStream); + } + } + + return writer; + } + } +} -- cgit v1.2.3-55-g6feb