diff options
Diffstat (limited to 'src/WixToolset.Data/FileStructure.cs')
-rw-r--r-- | src/WixToolset.Data/FileStructure.cs | 294 |
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 | |||
3 | namespace 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 | } | ||