// 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.IO; using System.IO.Compression; using System.Reflection; using System.Text; /// /// Class that understands the standard file structure of the WiX toolset. /// public class WixOutput : IDisposable { private readonly Stream stream; private ZipArchive archive; private bool disposed; private WixOutput(Uri uri, ZipArchive archive, Stream stream) { this.Uri = uri; this.archive = archive; this.stream = stream; } /// /// /// public Uri Uri { get; } /// /// Creates a new file structure in memory. /// /// Newly created WixOutput. public static WixOutput Create() { var uri = new Uri("memorystream:"); var stream = new MemoryStream(); return WixOutput.Create(uri, stream); } /// /// Creates a new file structure on disk. /// /// Path to write file structure to. /// Newly created WixOutput. public static WixOutput Create(string path) { var fullPath = Path.GetFullPath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); var uri = new Uri(fullPath); var stream = File.Create(path); return WixOutput.Create(uri, stream); } /// /// Creates a new file structure. /// /// /// Stream to write the file structure to. /// Newly created WixOutput. public static WixOutput Create(Uri uri, Stream stream) { var archive = new ZipArchive(stream, ZipArchiveMode.Update, leaveOpen: true); return new WixOutput(uri, archive, stream); } /// /// Loads a wixout from a path on disk. /// /// Path to wixout file saved on disk. /// Loaded created WixOutput. public static WixOutput Read(string path) { var uri = new Uri(Path.GetFullPath(path)); var stream = File.OpenRead(path); return Read(uri, stream); } /// /// Loads a wixout from a path on disk or embedded resource in assembly. /// /// Uri with local path to wixout file saved on disk or embedded resource in assembly. /// Loaded created WixOutput. public static WixOutput Read(Uri baseUri) { // If the embedded files are stored in an assembly resource stream (usually // a .wixlib embedded in a WixExtension). if ("embeddedresource" == baseUri.Scheme) { var assemblyPath = Path.GetFullPath(baseUri.LocalPath); var resourceName = baseUri.Fragment.TrimStart('#'); var assembly = Assembly.LoadFile(assemblyPath); return WixOutput.Read(assembly, resourceName); } else // normal file (usually a binary .wixlib on disk). { var stream = File.OpenRead(baseUri.LocalPath); return WixOutput.Read(baseUri, stream); } } /// /// Loads a wixout from an assembly resource stream. /// /// /// /// Loaded created WixOutput. public static WixOutput Read(Assembly assembly, string resourceName) { var resourceStream = assembly.GetManifestResourceStream(resourceName); var uriBuilder = new UriBuilder(assembly.CodeBase) { Scheme = "embeddedresource", Fragment = resourceName }; return Read(uriBuilder.Uri, resourceStream); } /// /// Reads a file structure from an open stream. /// /// /// Stream to read from. /// Loaded created WixOutput. public static WixOutput Read(Uri uri, Stream stream) { try { var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true); return new WixOutput(uri, archive, stream); } catch (InvalidDataException) { throw new WixException(ErrorMessages.CorruptFileFormat(uri.AbsoluteUri, "wixout")); } } /// /// Reopen the underlying archive for read-only or read-write access. /// /// Indicates whether the output can be modified. Defaults to false. public void Reopen(bool writable = false) { this.archive?.Dispose(); this.archive = null; this.archive = new ZipArchive(this.stream, writable ? ZipArchiveMode.Update : ZipArchiveMode.Read, leaveOpen: true); } /// /// Extracts an embedded file. /// /// Id to the file to extract. /// Path to write the extracted file to. public void ExtractEmbeddedFile(string embeddedId, string outputPath) { var entry = this.archive.GetEntry(embeddedId); if (entry == null) { throw new ArgumentOutOfRangeException(nameof(embeddedId)); } var folder = Path.GetDirectoryName(outputPath); Directory.CreateDirectory(folder); entry.ExtractToFile(outputPath, overwrite: true); } /// /// Creates a data stream in the wixout. /// /// Stream to the data of the file. public Stream CreateDataStream(string name) { this.DeleteExistingEntry(name); var entry = this.archive.CreateEntry(name); return entry.Open(); } /// /// Imports a file from disk into the output. /// /// Name of the stream in the output. /// Path to file on disk to include in the output. public void ImportDataStream(string name, string path) { this.DeleteExistingEntry(name); this.archive.CreateEntryFromFile(path, name, System.IO.Compression.CompressionLevel.Optimal); } /// /// Gets a non-closing stream to the data of the file. /// /// Stream to the data of the file. public Stream GetDataStream(string name) { var entry = this.archive.GetEntry(name); if (entry == null) { throw new ArgumentOutOfRangeException(nameof(name)); } return entry.Open(); } /// /// Gets the data of the file as a string. /// /// String contents data of the file. public string GetData(string name) { var entry = this.archive.GetEntry(name); // Use StreamReader to "swallow" BOM if present. using (var stream = entry.Open()) using (var streamReader = new StreamReader(stream, Encoding.UTF8)) { return streamReader.ReadToEnd(); } } /// /// Disposes of the internal state of the file structure. /// public void Dispose() { this.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) { this.archive?.Dispose(); this.stream?.Dispose(); } } this.disposed = true; } private void DeleteExistingEntry(string name) { var entry = this.archive.GetEntry(name); if (entry != null) { entry.Delete(); } } } }