aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Data/FileStructure.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Data/FileStructure.cs')
-rw-r--r--src/WixToolset.Data/FileStructure.cs294
1 files changed, 294 insertions, 0 deletions
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 @@
1// 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.
2
3namespace WixToolset.Data
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10
11 /// <summary>
12 /// Class that understands the standard file structures in the WiX toolset.
13 /// </summary>
14 public class FileStructure : IDisposable
15 {
16 private long dataStreamOffset;
17 private long[] embeddedFileSizes;
18 private Stream stream;
19 private bool disposed;
20
21 private static readonly Dictionary<string, FileFormat> SupportedFileFormats = new Dictionary<string, FileFormat>()
22 {
23 { "wixobj", FileFormat.Wixobj },
24 { "wixlib", FileFormat.Wixlib },
25 { "wixout", FileFormat.Wixout },
26 { "wixpdb", FileFormat.Wixpdb },
27 { "wixmst", FileFormat.Wixout },
28 { "wixmsp", FileFormat.Wixout },
29 };
30
31 /// <summary>
32 /// Use Create or Read to create a FileStructure.
33 /// </summary>
34 private FileStructure() { }
35
36 /// <summary>
37 /// Count of embedded files in the file structure.
38 /// </summary>
39 public int EmbeddedFileCount { get { return this.embeddedFileSizes.Length; } }
40
41 /// <summary>
42 /// File format of the file structure.
43 /// </summary>
44 public FileFormat FileFormat { get; private set; }
45
46 /// <summary>
47 /// Creates a new file structure.
48 /// </summary>
49 /// <param name="stream">Stream to write the file structure to.</param>
50 /// <param name="fileFormat">File format for the file structure.</param>
51 /// <param name="embedFilePaths">Paths to files to embedd in the file structure.</param>
52 /// <returns>Newly created file structure.</returns>
53 public static FileStructure Create(Stream stream, FileFormat fileFormat, List<string> embedFilePaths)
54 {
55 FileStructure fs = new FileStructure();
56 using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream))
57 using (BinaryWriter writer = new BinaryWriter(wrapper))
58 {
59 fs.WriteType(writer, fileFormat);
60
61 fs.WriteEmbeddedFiles(writer, embedFilePaths ?? new List<string>());
62
63 // Remember the data stream offset, which is right after the embedded files have been written.
64 fs.dataStreamOffset = stream.Position;
65 }
66
67 fs.stream = stream;
68
69 return fs;
70 }
71
72 /// <summary>
73 /// Reads a file structure from an open stream.
74 /// </summary>
75 /// <param name="stream">Stream to read from.</param>
76 /// <returns>File structure populated from the stream.</returns>
77 public static FileStructure Read(Stream stream)
78 {
79 FileStructure fs = new FileStructure();
80 using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream))
81 using (BinaryReader reader = new BinaryReader(wrapper))
82 {
83 fs.FileFormat = FileStructure.ReadFileFormat(reader);
84
85 if (FileFormat.Unknown != fs.FileFormat)
86 {
87 fs.embeddedFileSizes = FileStructure.ReadEmbeddedFileSizes(reader);
88
89 // Remember the data stream offset, which is right after the embedded files have been written.
90 fs.dataStreamOffset = stream.Position;
91 foreach (long size in fs.embeddedFileSizes)
92 {
93 fs.dataStreamOffset += size;
94 }
95 }
96 }
97
98 fs.stream = stream;
99
100 return fs;
101 }
102
103 /// <summary>
104 /// Guess at the file format based on the file extension.
105 /// </summary>
106 /// <param name="extension">File extension to guess the file format for.</param>
107 /// <returns>Best guess at file format.</returns>
108 public static FileFormat GuessFileFormatFromExtension(string extension)
109 {
110 FileFormat format;
111 return FileStructure.SupportedFileFormats.TryGetValue(extension.TrimStart('.').ToLowerInvariant(), out format) ? format : FileFormat.Unknown;
112 }
113
114 /// <summary>
115 /// Probes a stream to determine the file format.
116 /// </summary>
117 /// <param name="stream">Stream to test.</param>
118 /// <returns>The file format.</returns>
119 public static FileFormat TestFileFormat(Stream stream)
120 {
121 FileFormat format = FileFormat.Unknown;
122
123 long position = stream.Position;
124
125 try
126 {
127 using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(stream))
128 using (BinaryReader reader = new BinaryReader(wrapper))
129 {
130 format = FileStructure.ReadFileFormat(reader);
131 }
132 }
133 finally
134 {
135 stream.Seek(position, SeekOrigin.Begin);
136 }
137
138 return format;
139 }
140
141 /// <summary>
142 /// Extracts an embedded file.
143 /// </summary>
144 /// <param name="embeddedIndex">Index to the file to extract.</param>
145 /// <param name="outputPath">Path to write the extracted file to.</param>
146 public void ExtractEmbeddedFile(int embeddedIndex, string outputPath)
147 {
148 if (this.EmbeddedFileCount <= embeddedIndex)
149 {
150 throw new ArgumentOutOfRangeException("embeddedIndex");
151 }
152
153 long header = 6 + 4 + (this.embeddedFileSizes.Length * 8); // skip the type + the count of embedded files + all the sizes of embedded files.
154 long position = this.embeddedFileSizes.Take(embeddedIndex).Sum(); // skip to the embedded file we want.
155 long size = this.embeddedFileSizes[embeddedIndex];
156
157 this.stream.Seek(header + position, SeekOrigin.Begin);
158
159 Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
160
161 using (FileStream output = File.OpenWrite(outputPath))
162 {
163 int read;
164 int total = 0;
165 byte[] buffer = new byte[64 * 1024];
166 while (0 < (read = this.stream.Read(buffer, 0, (int)Math.Min(buffer.Length, size - total))))
167 {
168 output.Write(buffer, 0, read);
169 total += read;
170 }
171 }
172 }
173
174 /// <summary>
175 /// Gets a non-closing stream to the data of the file.
176 /// </summary>
177 /// <returns>Stream to the data of the file.</returns>
178 public Stream GetDataStream()
179 {
180 this.stream.Seek(this.dataStreamOffset, SeekOrigin.Begin);
181 return new NonClosingStreamWrapper(this.stream);
182 }
183
184 /// <summary>
185 /// Disposes of the internsl state of the file structure.
186 /// </summary>
187 public void Dispose()
188 {
189 Dispose(true);
190 GC.SuppressFinalize(this);
191 }
192
193 /// <summary>
194 /// Disposes of the internsl state of the file structure.
195 /// </summary>
196 /// <param name="disposing">True if disposing.</param>
197 protected virtual void Dispose(bool disposing)
198 {
199 if (!this.disposed)
200 {
201 if (disposing)
202 {
203 if (null != this.stream)
204 {
205 // We do not own the stream, so we don't close it. We're just resetting our internal state.
206 this.embeddedFileSizes = null;
207 this.dataStreamOffset = 0;
208 this.stream = null;
209 }
210 }
211 }
212
213 this.disposed = true;
214 }
215
216 private static FileFormat ReadFileFormat(BinaryReader reader)
217 {
218 FileFormat format = FileFormat.Unknown;
219
220 string type = new string(reader.ReadChars(6));
221 FileStructure.SupportedFileFormats.TryGetValue(type, out format);
222
223 return format;
224 }
225
226 private static long[] ReadEmbeddedFileSizes(BinaryReader reader)
227 {
228 uint count = reader.ReadUInt32();
229
230 long[] embeddedFileSizes = new long[count];
231
232 for (int i = 0; i < embeddedFileSizes.Length; ++i)
233 {
234 embeddedFileSizes[i] = (long)reader.ReadUInt64();
235 }
236
237 return embeddedFileSizes;
238 }
239
240 private BinaryWriter WriteType(BinaryWriter writer, FileFormat fileFormat)
241 {
242 string type = null;
243 foreach (var supported in FileStructure.SupportedFileFormats)
244 {
245 if (supported.Value.Equals(fileFormat))
246 {
247 type = supported.Key;
248 break;
249 }
250 }
251
252 if (String.IsNullOrEmpty(type))
253 {
254 throw new ArgumentException("Unknown file format type", "fileFormat");
255 }
256
257 this.FileFormat = fileFormat;
258
259 Debug.Assert(6 == type.ToCharArray().Length);
260 writer.Write(type.ToCharArray());
261 return writer;
262 }
263
264 private BinaryWriter WriteEmbeddedFiles(BinaryWriter writer, List<string> embedFilePaths)
265 {
266 // First write the count of embedded files as a Uint32;
267 writer.Write((uint)embedFilePaths.Count);
268
269 this.embeddedFileSizes = new long[embedFilePaths.Count];
270
271 // Next write out the size of each file as a Uint64 in order.
272 FileInfo[] files = new FileInfo[embedFilePaths.Count];
273 for (int i = 0; i < embedFilePaths.Count; ++i)
274 {
275 files[i] = new FileInfo(embedFilePaths[i]);
276
277 this.embeddedFileSizes[i] = files[i].Length;
278 writer.Write((ulong)this.embeddedFileSizes[i]);
279 }
280
281 // Next write out the content of each file *after* the sizes of
282 // *all* of the files were written.
283 foreach (FileInfo file in files)
284 {
285 using (FileStream stream = file.OpenRead())
286 {
287 stream.CopyTo(writer.BaseStream);
288 }
289 }
290
291 return writer;
292 }
293 }
294}