aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression.Cab
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs5
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs164
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs154
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs141
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs82
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs653
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs566
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs337
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resourcesbin0 -> 1465 bytes
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt35
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs76
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs407
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj26
13 files changed, 2646 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs
new file mode 100644
index 00000000..aea5bf2e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs
@@ -0,0 +1,5 @@
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
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Cab")]
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs
new file mode 100644
index 00000000..ab135fd8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs
@@ -0,0 +1,164 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8
9 /// <summary>
10 /// Engine capable of packing and unpacking archives in the cabinet format.
11 /// </summary>
12 public class CabEngine : CompressionEngine
13 {
14 private CabPacker packer;
15 private CabUnpacker unpacker;
16
17 /// <summary>
18 /// Creates a new instance of the cabinet engine.
19 /// </summary>
20 public CabEngine()
21 : base()
22 {
23 }
24
25 /// <summary>
26 /// Disposes of resources allocated by the cabinet engine.
27 /// </summary>
28 /// <param name="disposing">If true, the method has been called directly
29 /// or indirectly by a user's code, so managed and unmanaged resources
30 /// will be disposed. If false, the method has been called by the runtime
31 /// from inside the finalizer, and only unmanaged resources will be
32 /// disposed.</param>
33 protected override void Dispose(bool disposing)
34 {
35 if (disposing)
36 {
37 if (packer != null)
38 {
39 packer.Dispose();
40 packer = null;
41 }
42 if (unpacker != null)
43 {
44 unpacker.Dispose();
45 unpacker = null;
46 }
47 }
48
49 base.Dispose(disposing);
50 }
51
52 private CabPacker Packer
53 {
54 get
55 {
56 if (this.packer == null)
57 {
58 this.packer = new CabPacker(this);
59 }
60
61 return this.packer;
62 }
63 }
64
65 private CabUnpacker Unpacker
66 {
67 get
68 {
69 if (this.unpacker == null)
70 {
71 this.unpacker = new CabUnpacker(this);
72 }
73
74 return this.unpacker;
75 }
76 }
77
78 /// <summary>
79 /// Creates a cabinet or chain of cabinets.
80 /// </summary>
81 /// <param name="streamContext">A context interface to handle opening
82 /// and closing of cabinet and file streams.</param>
83 /// <param name="files">The paths of the files in the archive (not
84 /// external file paths).</param>
85 /// <param name="maxArchiveSize">The maximum number of bytes for one
86 /// cabinet before the contents are chained to the next cabinet, or zero
87 /// for unlimited cabinet size.</param>
88 /// <exception cref="ArchiveException">The cabinet could not be
89 /// created.</exception>
90 /// <remarks>
91 /// The stream context implementation may provide a mapping from the
92 /// file paths within the cabinet to the external file paths.
93 /// <para>Smaller folder sizes can make it more efficient to extract
94 /// individual files out of large cabinet packages.</para>
95 /// </remarks>
96 public override void Pack(
97 IPackStreamContext streamContext,
98 IEnumerable<string> files,
99 long maxArchiveSize)
100 {
101 this.Packer.CompressionLevel = this.CompressionLevel;
102 this.Packer.UseTempFiles = this.UseTempFiles;
103 this.Packer.Pack(streamContext, files, maxArchiveSize);
104 }
105
106 /// <summary>
107 /// Checks whether a Stream begins with a header that indicates
108 /// it is a valid cabinet file.
109 /// </summary>
110 /// <param name="stream">Stream for reading the cabinet file.</param>
111 /// <returns>True if the stream is a valid cabinet file
112 /// (with no offset); false otherwise.</returns>
113 public override bool IsArchive(Stream stream)
114 {
115 return this.Unpacker.IsArchive(stream);
116 }
117
118 /// <summary>
119 /// Gets information about files in a cabinet or cabinet chain.
120 /// </summary>
121 /// <param name="streamContext">A context interface to handle opening
122 /// and closing of cabinet and file streams.</param>
123 /// <param name="fileFilter">A predicate that can determine
124 /// which files to process, optional.</param>
125 /// <returns>Information about files in the cabinet stream.</returns>
126 /// <exception cref="ArchiveException">The cabinet provided
127 /// by the stream context is not valid.</exception>
128 /// <remarks>
129 /// The <paramref name="fileFilter"/> predicate takes an internal file
130 /// path and returns true to include the file or false to exclude it.
131 /// </remarks>
132 public override IList<ArchiveFileInfo> GetFileInfo(
133 IUnpackStreamContext streamContext,
134 Predicate<string> fileFilter)
135 {
136 return this.Unpacker.GetFileInfo(streamContext, fileFilter);
137 }
138
139 /// <summary>
140 /// Extracts files from a cabinet or cabinet chain.
141 /// </summary>
142 /// <param name="streamContext">A context interface to handle opening
143 /// and closing of cabinet and file streams.</param>
144 /// <param name="fileFilter">An optional predicate that can determine
145 /// which files to process.</param>
146 /// <exception cref="ArchiveException">The cabinet provided
147 /// by the stream context is not valid.</exception>
148 /// <remarks>
149 /// The <paramref name="fileFilter"/> predicate takes an internal file
150 /// path and returns true to include the file or false to exclude it.
151 /// </remarks>
152 public override void Unpack(
153 IUnpackStreamContext streamContext,
154 Predicate<string> fileFilter)
155 {
156 this.Unpacker.Unpack(streamContext, fileFilter);
157 }
158
159 internal void ReportProgress(ArchiveProgressEventArgs e)
160 {
161 base.OnProgress(e);
162 }
163 }
164}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs
new file mode 100644
index 00000000..e03f9f3a
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs
@@ -0,0 +1,154 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Resources;
8 using System.Globalization;
9 using System.Runtime.Serialization;
10
11 /// <summary>
12 /// Exception class for cabinet operations.
13 /// </summary>
14 [Serializable]
15 public class CabException : ArchiveException
16 {
17 private static ResourceManager errorResources;
18 private int error;
19 private int errorCode;
20
21 /// <summary>
22 /// Creates a new CabException with a specified error message and a reference to the
23 /// inner exception that is the cause of this exception.
24 /// </summary>
25 /// <param name="message">The message that describes the error.</param>
26 /// <param name="innerException">The exception that is the cause of the current exception. If the
27 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
28 /// is raised in a catch block that handles the inner exception.</param>
29 public CabException(string message, Exception innerException)
30 : this(0, 0, message, innerException) { }
31
32 /// <summary>
33 /// Creates a new CabException with a specified error message.
34 /// </summary>
35 /// <param name="message">The message that describes the error.</param>
36 public CabException(string message)
37 : this(0, 0, message, null) { }
38
39 /// <summary>
40 /// Creates a new CabException.
41 /// </summary>
42 public CabException()
43 : this(0, 0, null, null) { }
44
45 internal CabException(int error, int errorCode, string message, Exception innerException)
46 : base(message, innerException)
47 {
48 this.error = error;
49 this.errorCode = errorCode;
50 }
51
52 internal CabException(int error, int errorCode, string message)
53 : this(error, errorCode, message, null) { }
54
55 /// <summary>
56 /// Initializes a new instance of the CabException class with serialized data.
57 /// </summary>
58 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
59 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
60 protected CabException(SerializationInfo info, StreamingContext context) : base(info, context)
61 {
62 if (info == null)
63 {
64 throw new ArgumentNullException("info");
65 }
66
67 this.error = info.GetInt32("cabError");
68 this.errorCode = info.GetInt32("cabErrorCode");
69 }
70
71 /// <summary>
72 /// Gets the FCI or FDI cabinet engine error number.
73 /// </summary>
74 /// <value>A cabinet engine error number, or 0 if the exception was
75 /// not related to a cabinet engine error number.</value>
76 public int Error
77 {
78 get
79 {
80 return this.error;
81 }
82 }
83
84 /// <summary>
85 /// Gets the Win32 error code.
86 /// </summary>
87 /// <value>A Win32 error code, or 0 if the exception was
88 /// not related to a Win32 error.</value>
89 public int ErrorCode
90 {
91 get
92 {
93 return this.errorCode;
94 }
95 }
96
97 internal static ResourceManager ErrorResources
98 {
99 get
100 {
101 if (errorResources == null)
102 {
103 errorResources = new ResourceManager(
104 typeof(CabException).Namespace + ".Errors",
105 typeof(CabException).Assembly);
106 }
107 return errorResources;
108 }
109 }
110
111 /// <summary>
112 /// Sets the SerializationInfo with information about the exception.
113 /// </summary>
114 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 {
118 if (info == null)
119 {
120 throw new ArgumentNullException("info");
121 }
122
123 info.AddValue("cabError", this.error);
124 info.AddValue("cabErrorCode", this.errorCode);
125 base.GetObjectData(info, context);
126 }
127
128 internal static string GetErrorMessage(int error, int errorCode, bool extracting)
129 {
130 const int FCI_ERROR_RESOURCE_OFFSET = 1000;
131 const int FDI_ERROR_RESOURCE_OFFSET = 2000;
132 int resourceOffset = (extracting ? FDI_ERROR_RESOURCE_OFFSET : FCI_ERROR_RESOURCE_OFFSET);
133
134 string msg = CabException.ErrorResources.GetString(
135 (resourceOffset + error).ToString(CultureInfo.InvariantCulture.NumberFormat),
136 CultureInfo.CurrentCulture);
137
138 if (msg == null)
139 {
140 msg = CabException.ErrorResources.GetString(
141 resourceOffset.ToString(CultureInfo.InvariantCulture.NumberFormat),
142 CultureInfo.CurrentCulture);
143 }
144
145 if (errorCode != 0)
146 {
147 const string GENERIC_ERROR_RESOURCE = "1";
148 string msg2 = CabException.ErrorResources.GetString(GENERIC_ERROR_RESOURCE, CultureInfo.CurrentCulture);
149 msg = String.Format(CultureInfo.InvariantCulture, "{0} " + msg2, msg, errorCode);
150 }
151 return msg;
152 }
153 }
154}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs
new file mode 100644
index 00000000..ad4a813c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs
@@ -0,0 +1,141 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a compressed file within a cabinet package; provides operations for getting
11 /// the file properties and extracting the file.
12 /// </summary>
13 [Serializable]
14 public class CabFileInfo : ArchiveFileInfo
15 {
16 private int cabFolder;
17
18 /// <summary>
19 /// Creates a new CabinetFileInfo object representing a file within a cabinet in a specified path.
20 /// </summary>
21 /// <param name="cabinetInfo">An object representing the cabinet containing the file.</param>
22 /// <param name="filePath">The path to the file within the cabinet. Usually, this is a simple file
23 /// name, but if the cabinet contains a directory structure this may include the directory.</param>
24 public CabFileInfo(CabInfo cabinetInfo, string filePath)
25 : base(cabinetInfo, filePath)
26 {
27 if (cabinetInfo == null)
28 {
29 throw new ArgumentNullException("cabinetInfo");
30 }
31
32 this.cabFolder = -1;
33 }
34
35 /// <summary>
36 /// Creates a new CabinetFileInfo object with all parameters specified,
37 /// used internally when reading the metadata out of a cab.
38 /// </summary>
39 /// <param name="filePath">The internal path and name of the file in the cab.</param>
40 /// <param name="cabFolder">The folder number containing the file.</param>
41 /// <param name="cabNumber">The cabinet number where the file starts.</param>
42 /// <param name="attributes">The stored attributes of the file.</param>
43 /// <param name="lastWriteTime">The stored last write time of the file.</param>
44 /// <param name="length">The uncompressed size of the file.</param>
45 internal CabFileInfo(
46 string filePath,
47 int cabFolder,
48 int cabNumber,
49 FileAttributes attributes,
50 DateTime lastWriteTime,
51 long length)
52 : base(filePath, cabNumber, attributes, lastWriteTime, length)
53 {
54 this.cabFolder = cabFolder;
55 }
56
57 /// <summary>
58 /// Initializes a new instance of the CabinetFileInfo class with serialized data.
59 /// </summary>
60 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
61 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
62 protected CabFileInfo(SerializationInfo info, StreamingContext context)
63 : base(info, context)
64 {
65 this.cabFolder = info.GetInt32("cabFolder");
66 }
67
68 /// <summary>
69 /// Sets the SerializationInfo with information about the archive.
70 /// </summary>
71 /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
72 /// <param name="context">The StreamingContext that contains contextual information
73 /// about the source or destination.</param>
74 public override void GetObjectData(SerializationInfo info, StreamingContext context)
75 {
76 base.GetObjectData(info, context);
77 info.AddValue("cabFolder", this.cabFolder);
78 }
79
80 /// <summary>
81 /// Gets or sets the cabinet that contains this file.
82 /// </summary>
83 /// <value>
84 /// The CabinetInfo instance that retrieved this file information -- this
85 /// may be null if the CabinetFileInfo object was returned directly from a
86 /// stream.
87 /// </value>
88 public CabInfo Cabinet
89 {
90 get
91 {
92 return (CabInfo) this.Archive;
93 }
94 }
95
96 /// <summary>
97 /// Gets the full path of the cabinet that contains this file.
98 /// </summary>
99 /// <value>The full path of the cabinet that contains this file.</value>
100 public string CabinetName
101 {
102 get
103 {
104 return this.ArchiveName;
105 }
106 }
107
108 /// <summary>
109 /// Gets the number of the folder containing this file.
110 /// </summary>
111 /// <value>The number of the cabinet folder containing this file.</value>
112 /// <remarks>A single folder or the first folder of a cabinet
113 /// (or chain of cabinets) is numbered 0.</remarks>
114 public int CabinetFolderNumber
115 {
116 get
117 {
118 if (this.cabFolder < 0)
119 {
120 this.Refresh();
121 }
122 return this.cabFolder;
123 }
124 }
125
126 /// <summary>
127 /// Refreshes the information in this object with new data retrieved
128 /// from an archive.
129 /// </summary>
130 /// <param name="newFileInfo">Fresh instance for the same file just
131 /// read from the archive.</param>
132 /// <remarks>
133 /// This implementation refreshes the <see cref="CabinetFolderNumber"/>.
134 /// </remarks>
135 protected override void Refresh(ArchiveFileInfo newFileInfo)
136 {
137 base.Refresh(newFileInfo);
138 this.cabFolder = ((CabFileInfo) newFileInfo).cabFolder;
139 }
140 }
141}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs
new file mode 100644
index 00000000..969dcbef
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs
@@ -0,0 +1,82 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a cabinet file on disk; provides access to
11 /// file-based operations on the cabinet file.
12 /// </summary>
13 /// <remarks>
14 /// Generally, the methods on this class are much easier to use than the
15 /// stream-based interfaces provided by the <see cref="CabEngine"/> class.
16 /// </remarks>
17 [Serializable]
18 public class CabInfo : ArchiveInfo
19 {
20 /// <summary>
21 /// Creates a new CabinetInfo object representing a cabinet file in a specified path.
22 /// </summary>
23 /// <param name="path">The path to the cabinet file. When creating a cabinet file, this file does not
24 /// necessarily exist yet.</param>
25 public CabInfo(string path)
26 : base(path)
27 {
28 }
29
30 /// <summary>
31 /// Initializes a new instance of the CabinetInfo class with serialized data.
32 /// </summary>
33 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 protected CabInfo(SerializationInfo info, StreamingContext context)
36 : base(info, context)
37 {
38 }
39
40 /// <summary>
41 /// Creates a compression engine that does the low-level work for
42 /// this object.
43 /// </summary>
44 /// <returns>A new <see cref="CabEngine"/> instance.</returns>
45 /// <remarks>
46 /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 /// immediately after use.
48 /// </remarks>
49 protected override CompressionEngine CreateCompressionEngine()
50 {
51 return new CabEngine();
52 }
53
54 /// <summary>
55 /// Gets information about the files contained in the archive.
56 /// </summary>
57 /// <returns>A list of <see cref="CabFileInfo"/> objects, each
58 /// containing information about a file in the archive.</returns>
59 public new IList<CabFileInfo> GetFiles()
60 {
61 IList<ArchiveFileInfo> files = base.GetFiles();
62 List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
63 foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
64 return cabFiles.AsReadOnly();
65 }
66
67 /// <summary>
68 /// Gets information about the certain files contained in the archive file.
69 /// </summary>
70 /// <param name="searchPattern">The search string, such as
71 /// &quot;*.txt&quot;.</param>
72 /// <returns>A list of <see cref="CabFileInfo"/> objects, each containing
73 /// information about a file in the archive.</returns>
74 public new IList<CabFileInfo> GetFiles(string searchPattern)
75 {
76 IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
78 foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
79 return cabFiles.AsReadOnly();
80 }
81 }
82}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
new file mode 100644
index 00000000..ec6e3bda
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
@@ -0,0 +1,653 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class CabPacker : CabWorker
14 {
15 private const string TempStreamName = "%%TEMP%%";
16
17 private NativeMethods.FCI.Handle fciHandle;
18
19 // These delegates need to be saved as member variables
20 // so that they don't get GC'd.
21 private NativeMethods.FCI.PFNALLOC fciAllocMemHandler;
22 private NativeMethods.FCI.PFNFREE fciFreeMemHandler;
23 private NativeMethods.FCI.PFNOPEN fciOpenStreamHandler;
24 private NativeMethods.FCI.PFNREAD fciReadStreamHandler;
25 private NativeMethods.FCI.PFNWRITE fciWriteStreamHandler;
26 private NativeMethods.FCI.PFNCLOSE fciCloseStreamHandler;
27 private NativeMethods.FCI.PFNSEEK fciSeekStreamHandler;
28 private NativeMethods.FCI.PFNFILEPLACED fciFilePlacedHandler;
29 private NativeMethods.FCI.PFNDELETE fciDeleteFileHandler;
30 private NativeMethods.FCI.PFNGETTEMPFILE fciGetTempFileHandler;
31
32 private NativeMethods.FCI.PFNGETNEXTCABINET fciGetNextCabinet;
33 private NativeMethods.FCI.PFNSTATUS fciCreateStatus;
34 private NativeMethods.FCI.PFNGETOPENINFO fciGetOpenInfo;
35
36 private IPackStreamContext context;
37
38 private FileAttributes fileAttributes;
39 private DateTime fileLastWriteTime;
40
41 private int maxCabBytes;
42
43 private long totalFolderBytesProcessedInCurrentCab;
44
45 private CompressionLevel compressionLevel;
46 private bool dontUseTempFiles;
47 private IList<Stream> tempStreams;
48
49 public CabPacker(CabEngine cabEngine)
50 : base(cabEngine)
51 {
52 this.fciAllocMemHandler = this.CabAllocMem;
53 this.fciFreeMemHandler = this.CabFreeMem;
54 this.fciOpenStreamHandler = this.CabOpenStreamEx;
55 this.fciReadStreamHandler = this.CabReadStreamEx;
56 this.fciWriteStreamHandler = this.CabWriteStreamEx;
57 this.fciCloseStreamHandler = this.CabCloseStreamEx;
58 this.fciSeekStreamHandler = this.CabSeekStreamEx;
59 this.fciFilePlacedHandler = this.CabFilePlaced;
60 this.fciDeleteFileHandler = this.CabDeleteFile;
61 this.fciGetTempFileHandler = this.CabGetTempFile;
62 this.fciGetNextCabinet = this.CabGetNextCabinet;
63 this.fciCreateStatus = this.CabCreateStatus;
64 this.fciGetOpenInfo = this.CabGetOpenInfo;
65 this.tempStreams = new List<Stream>();
66 this.compressionLevel = CompressionLevel.Normal;
67 }
68
69 public bool UseTempFiles
70 {
71 get
72 {
73 return !this.dontUseTempFiles;
74 }
75
76 set
77 {
78 this.dontUseTempFiles = !value;
79 }
80 }
81
82 public CompressionLevel CompressionLevel
83 {
84 get
85 {
86 return this.compressionLevel;
87 }
88
89 set
90 {
91 this.compressionLevel = value;
92 }
93 }
94
95 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
96 private void CreateFci(long maxArchiveSize)
97 {
98 NativeMethods.FCI.CCAB ccab = new NativeMethods.FCI.CCAB();
99 if (maxArchiveSize > 0 && maxArchiveSize < ccab.cb)
100 {
101 ccab.cb = Math.Max(
102 NativeMethods.FCI.MIN_DISK, (int) maxArchiveSize);
103 }
104
105 object maxFolderSizeOption = this.context.GetOption(
106 "maxFolderSize", null);
107 if (maxFolderSizeOption != null)
108 {
109 long maxFolderSize = Convert.ToInt64(
110 maxFolderSizeOption, CultureInfo.InvariantCulture);
111 if (maxFolderSize > 0 && maxFolderSize < ccab.cbFolderThresh)
112 {
113 ccab.cbFolderThresh = (int) maxFolderSize;
114 }
115 }
116
117 this.maxCabBytes = ccab.cb;
118 ccab.szCab = this.context.GetArchiveName(0);
119 if (ccab.szCab == null)
120 {
121 throw new FileNotFoundException(
122 "Cabinet name not provided by stream context.");
123 }
124 ccab.setID = (short) new Random().Next(
125 Int16.MinValue, Int16.MaxValue + 1);
126 this.CabNumbers[ccab.szCab] = 0;
127 this.currentArchiveName = ccab.szCab;
128 this.totalArchives = 1;
129 this.CabStream = null;
130
131 this.Erf.Clear();
132 this.fciHandle = NativeMethods.FCI.Create(
133 this.ErfHandle.AddrOfPinnedObject(),
134 this.fciFilePlacedHandler,
135 this.fciAllocMemHandler,
136 this.fciFreeMemHandler,
137 this.fciOpenStreamHandler,
138 this.fciReadStreamHandler,
139 this.fciWriteStreamHandler,
140 this.fciCloseStreamHandler,
141 this.fciSeekStreamHandler,
142 this.fciDeleteFileHandler,
143 this.fciGetTempFileHandler,
144 ccab,
145 IntPtr.Zero);
146 this.CheckError(false);
147 }
148
149 public void Pack(
150 IPackStreamContext streamContext,
151 IEnumerable<string> files,
152 long maxArchiveSize)
153 {
154 if (streamContext == null)
155 {
156 throw new ArgumentNullException("streamContext");
157 }
158
159 if (files == null)
160 {
161 throw new ArgumentNullException("files");
162 }
163
164 lock (this)
165 {
166 try
167 {
168 this.context = streamContext;
169
170 this.ResetProgressData();
171
172 this.CreateFci(maxArchiveSize);
173
174 foreach (string file in files)
175 {
176 FileAttributes attributes;
177 DateTime lastWriteTime;
178 Stream fileStream = this.context.OpenFileReadStream(
179 file,
180 out attributes,
181 out lastWriteTime);
182 if (fileStream != null)
183 {
184 this.totalFileBytes += fileStream.Length;
185 this.totalFiles++;
186 this.context.CloseFileReadStream(file, fileStream);
187 }
188 }
189
190 long uncompressedBytesInFolder = 0;
191 this.currentFileNumber = -1;
192
193 foreach (string file in files)
194 {
195 FileAttributes attributes;
196 DateTime lastWriteTime;
197 Stream fileStream = this.context.OpenFileReadStream(
198 file, out attributes, out lastWriteTime);
199 if (fileStream == null)
200 {
201 continue;
202 }
203
204 if (fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER)
205 {
206 throw new NotSupportedException(String.Format(
207 CultureInfo.InvariantCulture,
208 "File {0} exceeds maximum file size " +
209 "for cabinet format.",
210 file));
211 }
212
213 if (uncompressedBytesInFolder > 0)
214 {
215 // Automatically create a new folder if this file
216 // won't fit in the current folder.
217 bool nextFolder = uncompressedBytesInFolder
218 + fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER;
219
220 // Otherwise ask the client if it wants to
221 // move to the next folder.
222 if (!nextFolder)
223 {
224 object nextFolderOption = streamContext.GetOption(
225 "nextFolder",
226 new object[] { file, this.currentFolderNumber });
227 nextFolder = Convert.ToBoolean(
228 nextFolderOption, CultureInfo.InvariantCulture);
229 }
230
231 if (nextFolder)
232 {
233 this.FlushFolder();
234 uncompressedBytesInFolder = 0;
235 }
236 }
237
238 if (this.currentFolderTotalBytes > 0)
239 {
240 this.currentFolderTotalBytes = 0;
241 this.currentFolderNumber++;
242 uncompressedBytesInFolder = 0;
243 }
244
245 this.currentFileName = file;
246 this.currentFileNumber++;
247
248 this.currentFileTotalBytes = fileStream.Length;
249 this.currentFileBytesProcessed = 0;
250 this.OnProgress(ArchiveProgressType.StartFile);
251
252 uncompressedBytesInFolder += fileStream.Length;
253
254 this.AddFile(
255 file,
256 fileStream,
257 attributes,
258 lastWriteTime,
259 false,
260 this.CompressionLevel);
261 }
262
263 this.FlushFolder();
264 this.FlushCabinet();
265 }
266 finally
267 {
268 if (this.CabStream != null)
269 {
270 this.context.CloseArchiveWriteStream(
271 this.currentArchiveNumber,
272 this.currentArchiveName,
273 this.CabStream);
274 this.CabStream = null;
275 }
276
277 if (this.FileStream != null)
278 {
279 this.context.CloseFileReadStream(
280 this.currentFileName, this.FileStream);
281 this.FileStream = null;
282 }
283 this.context = null;
284
285 if (this.fciHandle != null)
286 {
287 this.fciHandle.Dispose();
288 this.fciHandle = null;
289 }
290 }
291 }
292 }
293
294 internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
295 {
296 if (this.CabNumbers.ContainsKey(path))
297 {
298 Stream stream = this.CabStream;
299 if (stream == null)
300 {
301 short cabNumber = this.CabNumbers[path];
302
303 this.currentFolderTotalBytes = 0;
304
305 stream = this.context.OpenArchiveWriteStream(cabNumber, path, true, this.CabEngine);
306 if (stream == null)
307 {
308 throw new FileNotFoundException(
309 String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
310 }
311 this.currentArchiveName = path;
312
313 this.currentArchiveTotalBytes = Math.Min(
314 this.totalFolderBytesProcessedInCurrentCab, this.maxCabBytes);
315 this.currentArchiveBytesProcessed = 0;
316
317 this.OnProgress(ArchiveProgressType.StartArchive);
318 this.CabStream = stream;
319 }
320 path = CabWorker.CabStreamName;
321 }
322 else if (path == CabPacker.TempStreamName)
323 {
324 // Opening memory stream for a temp file.
325 Stream stream = new MemoryStream();
326 this.tempStreams.Add(stream);
327 int streamHandle = this.StreamHandles.AllocHandle(stream);
328 err = 0;
329 return streamHandle;
330 }
331 else if (path != CabWorker.CabStreamName)
332 {
333 // Opening a file on disk for a temp file.
334 path = Path.Combine(Path.GetTempPath(), path);
335 Stream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
336 this.tempStreams.Add(stream);
337 stream = new DuplicateStream(stream);
338 int streamHandle = this.StreamHandles.AllocHandle(stream);
339 err = 0;
340 return streamHandle;
341 }
342 return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
343 }
344
345 internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
346 {
347 int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
348 if (count > 0 && err == 0)
349 {
350 Stream stream = this.StreamHandles[streamHandle];
351 if (DuplicateStream.OriginalStream(stream) ==
352 DuplicateStream.OriginalStream(this.CabStream))
353 {
354 this.currentArchiveBytesProcessed += cb;
355 if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
356 {
357 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
358 }
359 }
360 }
361 return count;
362 }
363
364 internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
365 {
366 Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
367
368 if (stream == DuplicateStream.OriginalStream(this.FileStream))
369 {
370 this.context.CloseFileReadStream(this.currentFileName, stream);
371 this.FileStream = null;
372 long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
373 this.currentFileBytesProcessed += remainder;
374 this.fileBytesProcessed += remainder;
375 this.OnProgress(ArchiveProgressType.FinishFile);
376
377 this.currentFileTotalBytes = 0;
378 this.currentFileBytesProcessed = 0;
379 this.currentFileName = null;
380 }
381 else if (stream == DuplicateStream.OriginalStream(this.CabStream))
382 {
383 if (stream.CanWrite)
384 {
385 stream.Flush();
386 }
387
388 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
389 this.OnProgress(ArchiveProgressType.FinishArchive);
390 this.currentArchiveNumber++;
391 this.totalArchives++;
392
393 this.context.CloseArchiveWriteStream(
394 this.currentArchiveNumber,
395 this.currentArchiveName,
396 stream);
397
398 this.currentArchiveName = this.NextCabinetName;
399 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
400 this.totalFolderBytesProcessedInCurrentCab = 0;
401
402 this.CabStream = null;
403 }
404 else // Must be a temp stream
405 {
406 stream.Close();
407 this.tempStreams.Remove(stream);
408 }
409 return base.CabCloseStreamEx(streamHandle, out err, pv);
410 }
411
412 /// <summary>
413 /// Disposes of resources allocated by the cabinet engine.
414 /// </summary>
415 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
416 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
417 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
418 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
419 protected override void Dispose(bool disposing)
420 {
421 try
422 {
423 if (disposing)
424 {
425 if (this.fciHandle != null)
426 {
427 this.fciHandle.Dispose();
428 this.fciHandle = null;
429 }
430 }
431 }
432 finally
433 {
434 base.Dispose(disposing);
435 }
436 }
437
438 private static NativeMethods.FCI.TCOMP GetCompressionType(CompressionLevel compLevel)
439 {
440 if (compLevel < CompressionLevel.Min)
441 {
442 return NativeMethods.FCI.TCOMP.TYPE_NONE;
443 }
444 else
445 {
446 if (compLevel > CompressionLevel.Max)
447 {
448 compLevel = CompressionLevel.Max;
449 }
450
451 int lzxWindowMax =
452 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_HI >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW) -
453 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW);
454 int lzxWindow = lzxWindowMax *
455 (compLevel - CompressionLevel.Min) / (CompressionLevel.Max - CompressionLevel.Min);
456
457 return (NativeMethods.FCI.TCOMP) ((int) NativeMethods.FCI.TCOMP.TYPE_LZX |
458 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO +
459 (lzxWindow << (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW)));
460 }
461 }
462
463 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
464 private void AddFile(
465 string name,
466 Stream stream,
467 FileAttributes attributes,
468 DateTime lastWriteTime,
469 bool execute,
470 CompressionLevel compLevel)
471 {
472 this.FileStream = stream;
473 this.fileAttributes = attributes &
474 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
475 this.fileLastWriteTime = lastWriteTime;
476 this.currentFileName = name;
477
478 NativeMethods.FCI.TCOMP tcomp = CabPacker.GetCompressionType(compLevel);
479
480 IntPtr namePtr = IntPtr.Zero;
481 try
482 {
483 Encoding nameEncoding = Encoding.ASCII;
484 if (Encoding.UTF8.GetByteCount(name) > name.Length)
485 {
486 nameEncoding = Encoding.UTF8;
487 this.fileAttributes |= FileAttributes.Normal; // _A_NAME_IS_UTF
488 }
489
490 byte[] nameBytes = nameEncoding.GetBytes(name);
491 namePtr = Marshal.AllocHGlobal(nameBytes.Length + 1);
492 Marshal.Copy(nameBytes, 0, namePtr, nameBytes.Length);
493 Marshal.WriteByte(namePtr, nameBytes.Length, 0);
494
495 this.Erf.Clear();
496 NativeMethods.FCI.AddFile(
497 this.fciHandle,
498 String.Empty,
499 namePtr,
500 execute,
501 this.fciGetNextCabinet,
502 this.fciCreateStatus,
503 this.fciGetOpenInfo,
504 tcomp);
505 }
506 finally
507 {
508 if (namePtr != IntPtr.Zero)
509 {
510 Marshal.FreeHGlobal(namePtr);
511 }
512 }
513
514 this.CheckError(false);
515 this.FileStream = null;
516 this.currentFileName = null;
517 }
518
519 private void FlushFolder()
520 {
521 this.Erf.Clear();
522 NativeMethods.FCI.FlushFolder(this.fciHandle, this.fciGetNextCabinet, this.fciCreateStatus);
523 this.CheckError(false);
524 }
525
526 private void FlushCabinet()
527 {
528 this.Erf.Clear();
529 NativeMethods.FCI.FlushCabinet(this.fciHandle, false, this.fciGetNextCabinet, this.fciCreateStatus);
530 this.CheckError(false);
531 }
532
533 private int CabGetOpenInfo(
534 string path,
535 out short date,
536 out short time,
537 out short attribs,
538 out int err,
539 IntPtr pv)
540 {
541 CompressionEngine.DateTimeToDosDateAndTime(this.fileLastWriteTime, out date, out time);
542 attribs = (short) this.fileAttributes;
543
544 Stream stream = this.FileStream;
545 this.FileStream = new DuplicateStream(stream);
546 int streamHandle = this.StreamHandles.AllocHandle(stream);
547 err = 0;
548 return streamHandle;
549 }
550
551 private int CabFilePlaced(
552 IntPtr pccab,
553 string filePath,
554 long fileSize,
555 int continuation,
556 IntPtr pv)
557 {
558 return 0;
559 }
560
561 private int CabGetNextCabinet(IntPtr pccab, uint prevCabSize, IntPtr pv)
562 {
563 NativeMethods.FCI.CCAB nextCcab = new NativeMethods.FCI.CCAB();
564 Marshal.PtrToStructure(pccab, nextCcab);
565
566 nextCcab.szDisk = String.Empty;
567 nextCcab.szCab = this.context.GetArchiveName(nextCcab.iCab);
568 this.CabNumbers[nextCcab.szCab] = (short) nextCcab.iCab;
569 this.NextCabinetName = nextCcab.szCab;
570
571 Marshal.StructureToPtr(nextCcab, pccab, false);
572 return 1;
573 }
574
575 private int CabCreateStatus(NativeMethods.FCI.STATUS typeStatus, uint cb1, uint cb2, IntPtr pv)
576 {
577 switch (typeStatus)
578 {
579 case NativeMethods.FCI.STATUS.FILE:
580 if (cb2 > 0 && this.currentFileBytesProcessed < this.currentFileTotalBytes)
581 {
582 if (this.currentFileBytesProcessed + cb2 > this.currentFileTotalBytes)
583 {
584 cb2 = (uint) this.currentFileTotalBytes - (uint) this.currentFileBytesProcessed;
585 }
586 this.currentFileBytesProcessed += cb2;
587 this.fileBytesProcessed += cb2;
588
589 this.OnProgress(ArchiveProgressType.PartialFile);
590 }
591 break;
592
593 case NativeMethods.FCI.STATUS.FOLDER:
594 if (cb1 == 0)
595 {
596 this.currentFolderTotalBytes = cb2 - this.totalFolderBytesProcessedInCurrentCab;
597 this.totalFolderBytesProcessedInCurrentCab = cb2;
598 }
599 else if (this.currentFolderTotalBytes == 0)
600 {
601 this.OnProgress(ArchiveProgressType.PartialArchive);
602 }
603 break;
604
605 case NativeMethods.FCI.STATUS.CABINET:
606 break;
607 }
608 return 0;
609 }
610
611 private int CabGetTempFile(IntPtr tempNamePtr, int tempNameSize, IntPtr pv)
612 {
613 string tempFileName;
614 if (this.UseTempFiles)
615 {
616 tempFileName = Path.GetFileName(Path.GetTempFileName());
617 }
618 else
619 {
620 tempFileName = CabPacker.TempStreamName;
621 }
622
623 byte[] tempNameBytes = Encoding.ASCII.GetBytes(tempFileName);
624 if (tempNameBytes.Length >= tempNameSize)
625 {
626 return -1;
627 }
628
629 Marshal.Copy(tempNameBytes, 0, tempNamePtr, tempNameBytes.Length);
630 Marshal.WriteByte(tempNamePtr, tempNameBytes.Length, 0); // null-terminator
631 return 1;
632 }
633
634 private int CabDeleteFile(string path, out int err, IntPtr pv)
635 {
636 try
637 {
638 // Deleting a temp file - don't bother if it is only a memory stream.
639 if (path != CabPacker.TempStreamName)
640 {
641 path = Path.Combine(Path.GetTempPath(), path);
642 File.Delete(path);
643 }
644 }
645 catch (IOException)
646 {
647 // Failure to delete a temp file is not fatal.
648 }
649 err = 0;
650 return 1;
651 }
652 }
653}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs
new file mode 100644
index 00000000..b0be4a15
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs
@@ -0,0 +1,566 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class CabUnpacker : CabWorker
14 {
15 private NativeMethods.FDI.Handle fdiHandle;
16
17 // These delegates need to be saved as member variables
18 // so that they don't get GC'd.
19 private NativeMethods.FDI.PFNALLOC fdiAllocMemHandler;
20 private NativeMethods.FDI.PFNFREE fdiFreeMemHandler;
21 private NativeMethods.FDI.PFNOPEN fdiOpenStreamHandler;
22 private NativeMethods.FDI.PFNREAD fdiReadStreamHandler;
23 private NativeMethods.FDI.PFNWRITE fdiWriteStreamHandler;
24 private NativeMethods.FDI.PFNCLOSE fdiCloseStreamHandler;
25 private NativeMethods.FDI.PFNSEEK fdiSeekStreamHandler;
26
27 private IUnpackStreamContext context;
28
29 private List<ArchiveFileInfo> fileList;
30
31 private int folderId;
32
33 private Predicate<string> filter;
34
35 public CabUnpacker(CabEngine cabEngine)
36 : base(cabEngine)
37 {
38 this.fdiAllocMemHandler = this.CabAllocMem;
39 this.fdiFreeMemHandler = this.CabFreeMem;
40 this.fdiOpenStreamHandler = this.CabOpenStream;
41 this.fdiReadStreamHandler = this.CabReadStream;
42 this.fdiWriteStreamHandler = this.CabWriteStream;
43 this.fdiCloseStreamHandler = this.CabCloseStream;
44 this.fdiSeekStreamHandler = this.CabSeekStream;
45
46 this.fdiHandle = NativeMethods.FDI.Create(
47 this.fdiAllocMemHandler,
48 this.fdiFreeMemHandler,
49 this.fdiOpenStreamHandler,
50 this.fdiReadStreamHandler,
51 this.fdiWriteStreamHandler,
52 this.fdiCloseStreamHandler,
53 this.fdiSeekStreamHandler,
54 NativeMethods.FDI.CPU_80386,
55 this.ErfHandle.AddrOfPinnedObject());
56 if (this.Erf.Error)
57 {
58 int error = this.Erf.Oper;
59 int errorCode = this.Erf.Type;
60 this.ErfHandle.Free();
61 throw new CabException(
62 error,
63 errorCode,
64 CabException.GetErrorMessage(error, errorCode, true));
65 }
66 }
67
68 public bool IsArchive(Stream stream)
69 {
70 if (stream == null)
71 {
72 throw new ArgumentNullException("stream");
73 }
74
75 lock (this)
76 {
77 short id;
78 int folderCount, fileCount;
79 return this.IsCabinet(stream, out id, out folderCount, out fileCount);
80 }
81 }
82
83 public IList<ArchiveFileInfo> GetFileInfo(
84 IUnpackStreamContext streamContext,
85 Predicate<string> fileFilter)
86 {
87 if (streamContext == null)
88 {
89 throw new ArgumentNullException("streamContext");
90 }
91
92 lock (this)
93 {
94 this.context = streamContext;
95 this.filter = fileFilter;
96 this.NextCabinetName = String.Empty;
97 this.fileList = new List<ArchiveFileInfo>();
98 bool tmpSuppress = this.SuppressProgressEvents;
99 this.SuppressProgressEvents = true;
100 try
101 {
102 for (short cabNumber = 0;
103 this.NextCabinetName != null;
104 cabNumber++)
105 {
106 this.Erf.Clear();
107 this.CabNumbers[this.NextCabinetName] = cabNumber;
108
109 NativeMethods.FDI.Copy(
110 this.fdiHandle,
111 this.NextCabinetName,
112 String.Empty,
113 0,
114 this.CabListNotify,
115 IntPtr.Zero,
116 IntPtr.Zero);
117 this.CheckError(true);
118 }
119
120 List<ArchiveFileInfo> tmpFileList = this.fileList;
121 this.fileList = null;
122 return tmpFileList.AsReadOnly();
123 }
124 finally
125 {
126 this.SuppressProgressEvents = tmpSuppress;
127
128 if (this.CabStream != null)
129 {
130 this.context.CloseArchiveReadStream(
131 this.currentArchiveNumber,
132 this.currentArchiveName,
133 this.CabStream);
134 this.CabStream = null;
135 }
136
137 this.context = null;
138 }
139 }
140 }
141
142 public void Unpack(
143 IUnpackStreamContext streamContext,
144 Predicate<string> fileFilter)
145 {
146 lock (this)
147 {
148 IList<ArchiveFileInfo> files =
149 this.GetFileInfo(streamContext, fileFilter);
150
151 this.ResetProgressData();
152
153 if (files != null)
154 {
155 this.totalFiles = files.Count;
156
157 for (int i = 0; i < files.Count; i++)
158 {
159 this.totalFileBytes += files[i].Length;
160 if (files[i].ArchiveNumber >= this.totalArchives)
161 {
162 int totalArchives = files[i].ArchiveNumber + 1;
163 this.totalArchives = (short) totalArchives;
164 }
165 }
166 }
167
168 this.context = streamContext;
169 this.fileList = null;
170 this.NextCabinetName = String.Empty;
171 this.folderId = -1;
172 this.currentFileNumber = -1;
173
174 try
175 {
176 for (short cabNumber = 0;
177 this.NextCabinetName != null;
178 cabNumber++)
179 {
180 this.Erf.Clear();
181 this.CabNumbers[this.NextCabinetName] = cabNumber;
182
183 NativeMethods.FDI.Copy(
184 this.fdiHandle,
185 this.NextCabinetName,
186 String.Empty,
187 0,
188 this.CabExtractNotify,
189 IntPtr.Zero,
190 IntPtr.Zero);
191 this.CheckError(true);
192 }
193 }
194 finally
195 {
196 if (this.CabStream != null)
197 {
198 this.context.CloseArchiveReadStream(
199 this.currentArchiveNumber,
200 this.currentArchiveName,
201 this.CabStream);
202 this.CabStream = null;
203 }
204
205 if (this.FileStream != null)
206 {
207 this.context.CloseFileWriteStream(this.currentFileName, this.FileStream, FileAttributes.Normal, DateTime.Now);
208 this.FileStream = null;
209 }
210
211 this.context = null;
212 }
213 }
214 }
215
216 internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
217 {
218 if (this.CabNumbers.ContainsKey(path))
219 {
220 Stream stream = this.CabStream;
221 if (stream == null)
222 {
223 short cabNumber = this.CabNumbers[path];
224
225 stream = this.context.OpenArchiveReadStream(cabNumber, path, this.CabEngine);
226 if (stream == null)
227 {
228 throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
229 }
230 this.currentArchiveName = path;
231 this.currentArchiveNumber = cabNumber;
232 if (this.totalArchives <= this.currentArchiveNumber)
233 {
234 int totalArchives = this.currentArchiveNumber + 1;
235 this.totalArchives = (short) totalArchives;
236 }
237 this.currentArchiveTotalBytes = stream.Length;
238 this.currentArchiveBytesProcessed = 0;
239
240 if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
241 {
242 this.OnProgress(ArchiveProgressType.StartArchive);
243 }
244 this.CabStream = stream;
245 }
246 path = CabWorker.CabStreamName;
247 }
248 return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
249 }
250
251 internal override int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
252 {
253 int count = base.CabReadStreamEx(streamHandle, memory, cb, out err, pv);
254 if (err == 0 && this.CabStream != null)
255 {
256 if (this.fileList == null)
257 {
258 Stream stream = this.StreamHandles[streamHandle];
259 if (DuplicateStream.OriginalStream(stream) ==
260 DuplicateStream.OriginalStream(this.CabStream))
261 {
262 this.currentArchiveBytesProcessed += cb;
263 if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
264 {
265 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
266 }
267 }
268 }
269 }
270 return count;
271 }
272
273 internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
274 {
275 int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
276 if (count > 0 && err == 0)
277 {
278 this.currentFileBytesProcessed += cb;
279 this.fileBytesProcessed += cb;
280 this.OnProgress(ArchiveProgressType.PartialFile);
281 }
282 return count;
283 }
284
285 internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
286 {
287 Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
288
289 if (stream == DuplicateStream.OriginalStream(this.CabStream))
290 {
291 if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
292 {
293 this.OnProgress(ArchiveProgressType.FinishArchive);
294 }
295
296 this.context.CloseArchiveReadStream(this.currentArchiveNumber, this.currentArchiveName, stream);
297
298 this.currentArchiveName = this.NextCabinetName;
299 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
300
301 this.CabStream = null;
302 }
303 return base.CabCloseStreamEx(streamHandle, out err, pv);
304 }
305
306 /// <summary>
307 /// Disposes of resources allocated by the cabinet engine.
308 /// </summary>
309 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
310 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
311 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
312 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
313 protected override void Dispose(bool disposing)
314 {
315 try
316 {
317 if (disposing)
318 {
319 if (this.fdiHandle != null)
320 {
321 this.fdiHandle.Dispose();
322 this.fdiHandle = null;
323 }
324 }
325 }
326 finally
327 {
328 base.Dispose(disposing);
329 }
330 }
331
332 private static string GetFileName(NativeMethods.FDI.NOTIFICATION notification)
333 {
334 bool utf8Name = (notification.attribs & (ushort) FileAttributes.Normal) != 0; // _A_NAME_IS_UTF
335
336 // Non-utf8 names should be completely ASCII. But for compatibility with
337 // legacy tools, interpret them using the current (Default) ANSI codepage.
338 Encoding nameEncoding = utf8Name ? Encoding.UTF8 : Encoding.Default;
339
340 // Find how many bytes are in the string.
341 // Unfortunately there is no faster way.
342 int nameBytesCount = 0;
343 while (Marshal.ReadByte(notification.psz1, nameBytesCount) != 0)
344 {
345 nameBytesCount++;
346 }
347
348 byte[] nameBytes = new byte[nameBytesCount];
349 Marshal.Copy(notification.psz1, nameBytes, 0, nameBytesCount);
350 string name = nameEncoding.GetString(nameBytes);
351 if (Path.IsPathRooted(name))
352 {
353 name = name.Replace("" + Path.VolumeSeparatorChar, "");
354 }
355
356 return name;
357 }
358
359 private bool IsCabinet(Stream cabStream, out short id, out int cabFolderCount, out int fileCount)
360 {
361 int streamHandle = this.StreamHandles.AllocHandle(cabStream);
362 try
363 {
364 this.Erf.Clear();
365 NativeMethods.FDI.CABINFO fdici;
366 bool isCabinet = 0 != NativeMethods.FDI.IsCabinet(this.fdiHandle, streamHandle, out fdici);
367
368 if (this.Erf.Error)
369 {
370 if (((NativeMethods.FDI.ERROR) this.Erf.Oper) == NativeMethods.FDI.ERROR.UNKNOWN_CABINET_VERSION)
371 {
372 isCabinet = false;
373 }
374 else
375 {
376 throw new CabException(
377 this.Erf.Oper,
378 this.Erf.Type,
379 CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, true));
380 }
381 }
382
383 id = fdici.setID;
384 cabFolderCount = (int) fdici.cFolders;
385 fileCount = (int) fdici.cFiles;
386 return isCabinet;
387 }
388 finally
389 {
390 this.StreamHandles.FreeHandle(streamHandle);
391 }
392 }
393
394 private int CabListNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
395 {
396 switch (notificationType)
397 {
398 case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
399 {
400 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
401 this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
402 return 0; // Continue
403 }
404 case NativeMethods.FDI.NOTIFICATIONTYPE.PARTIAL_FILE:
405 {
406 // This notification can occur when examining the contents of a non-first cab file.
407 return 0; // Continue
408 }
409 case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
410 {
411 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
412
413 string name = CabUnpacker.GetFileName(notification);
414
415 if (this.filter == null || this.filter(name))
416 {
417 if (this.fileList != null)
418 {
419 FileAttributes attributes = (FileAttributes) notification.attribs &
420 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
421 if (attributes == (FileAttributes) 0)
422 {
423 attributes = FileAttributes.Normal;
424 }
425 DateTime lastWriteTime;
426 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
427 long length = notification.cb;
428
429 CabFileInfo fileInfo = new CabFileInfo(
430 name,
431 notification.iFolder,
432 notification.iCabinet,
433 attributes,
434 lastWriteTime,
435 length);
436 this.fileList.Add(fileInfo);
437 this.currentFileNumber = this.fileList.Count - 1;
438 this.fileBytesProcessed += notification.cb;
439 }
440 }
441
442 this.totalFiles++;
443 this.totalFileBytes += notification.cb;
444 return 0; // Continue
445 }
446 }
447 return 0;
448 }
449
450 private int CabExtractNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
451 {
452 switch (notificationType)
453 {
454 case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
455 {
456 if (this.NextCabinetName != null && this.NextCabinetName.StartsWith("?", StringComparison.Ordinal))
457 {
458 // We are just continuing the copy of a file that spanned cabinets.
459 // The next cabinet name needs to be preserved.
460 this.NextCabinetName = this.NextCabinetName.Substring(1);
461 }
462 else
463 {
464 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
465 this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
466 }
467 return 0; // Continue
468 }
469 case NativeMethods.FDI.NOTIFICATIONTYPE.NEXT_CABINET:
470 {
471 string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
472 this.CabNumbers[nextCab] = (short) notification.iCabinet;
473 this.NextCabinetName = "?" + this.NextCabinetName;
474 return 0; // Continue
475 }
476 case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
477 {
478 return this.CabExtractCopyFile(notification);
479 }
480 case NativeMethods.FDI.NOTIFICATIONTYPE.CLOSE_FILE_INFO:
481 {
482 return this.CabExtractCloseFile(notification);
483 }
484 }
485 return 0;
486 }
487
488 private int CabExtractCopyFile(NativeMethods.FDI.NOTIFICATION notification)
489 {
490 if (notification.iFolder != this.folderId)
491 {
492 if (notification.iFolder != -3) // -3 is a special folderId used when continuing a folder from a previous cab
493 {
494 if (this.folderId != -1) // -1 means we just started the extraction sequence
495 {
496 this.currentFolderNumber++;
497 }
498 }
499 this.folderId = notification.iFolder;
500 }
501
502 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
503
504 string name = CabUnpacker.GetFileName(notification);
505
506 if (this.filter == null || this.filter(name))
507 {
508 this.currentFileNumber++;
509 this.currentFileName = name;
510
511 this.currentFileBytesProcessed = 0;
512 this.currentFileTotalBytes = notification.cb;
513 this.OnProgress(ArchiveProgressType.StartFile);
514
515 DateTime lastWriteTime;
516 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
517
518 Stream stream = this.context.OpenFileWriteStream(name, notification.cb, lastWriteTime);
519 if (stream != null)
520 {
521 this.FileStream = stream;
522 int streamHandle = this.StreamHandles.AllocHandle(stream);
523 return streamHandle;
524 }
525 else
526 {
527 this.fileBytesProcessed += notification.cb;
528 this.OnProgress(ArchiveProgressType.FinishFile);
529 this.currentFileName = null;
530 }
531 }
532 return 0; // Continue
533 }
534
535 private int CabExtractCloseFile(NativeMethods.FDI.NOTIFICATION notification)
536 {
537 Stream stream = this.StreamHandles[notification.hf];
538 this.StreamHandles.FreeHandle(notification.hf);
539
540 //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
541
542 string name = CabUnpacker.GetFileName(notification);
543
544 FileAttributes attributes = (FileAttributes) notification.attribs &
545 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
546 if (attributes == (FileAttributes) 0)
547 {
548 attributes = FileAttributes.Normal;
549 }
550 DateTime lastWriteTime;
551 CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
552
553 stream.Flush();
554 this.context.CloseFileWriteStream(name, stream, attributes, lastWriteTime);
555 this.FileStream = null;
556
557 long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
558 this.currentFileBytesProcessed += remainder;
559 this.fileBytesProcessed += remainder;
560 this.OnProgress(ArchiveProgressType.FinishFile);
561 this.currentFileName = null;
562
563 return 1; // Continue
564 }
565 }
566}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs
new file mode 100644
index 00000000..cb2a7263
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs
@@ -0,0 +1,337 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.IO.IsolatedStorage;
8 using System.Text;
9 using System.Security;
10 using System.Collections.Generic;
11 using System.Runtime.InteropServices;
12 using System.Diagnostics.CodeAnalysis;
13
14 internal abstract class CabWorker : IDisposable
15 {
16 internal const string CabStreamName = "%%CAB%%";
17
18 private CabEngine cabEngine;
19
20 private HandleManager<Stream> streamHandles;
21 private Stream cabStream;
22 private Stream fileStream;
23
24 private NativeMethods.ERF erf;
25 private GCHandle erfHandle;
26
27 private IDictionary<string, short> cabNumbers;
28 private string nextCabinetName;
29
30 private bool suppressProgressEvents;
31
32 private byte[] buf;
33
34 // Progress data
35 protected string currentFileName;
36 protected int currentFileNumber;
37 protected int totalFiles;
38 protected long currentFileBytesProcessed;
39 protected long currentFileTotalBytes;
40 protected short currentFolderNumber;
41 protected long currentFolderTotalBytes;
42 protected string currentArchiveName;
43 protected short currentArchiveNumber;
44 protected short totalArchives;
45 protected long currentArchiveBytesProcessed;
46 protected long currentArchiveTotalBytes;
47 protected long fileBytesProcessed;
48 protected long totalFileBytes;
49
50 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
51 protected CabWorker(CabEngine cabEngine)
52 {
53 this.cabEngine = cabEngine;
54 this.streamHandles = new HandleManager<Stream>();
55 this.erf = new NativeMethods.ERF();
56 this.erfHandle = GCHandle.Alloc(this.erf, GCHandleType.Pinned);
57 this.cabNumbers = new Dictionary<string, short>(1);
58
59 // 32K seems to be the size of the largest chunks processed by cabinet.dll.
60 // But just in case, this buffer will auto-enlarge.
61 this.buf = new byte[32768];
62 }
63
64 ~CabWorker()
65 {
66 this.Dispose(false);
67 }
68
69 public CabEngine CabEngine
70 {
71 get
72 {
73 return this.cabEngine;
74 }
75 }
76
77 internal NativeMethods.ERF Erf
78 {
79 get
80 {
81 return this.erf;
82 }
83 }
84
85 internal GCHandle ErfHandle
86 {
87 get
88 {
89 return this.erfHandle;
90 }
91 }
92
93 internal HandleManager<Stream> StreamHandles
94 {
95 get
96 {
97 return this.streamHandles;
98 }
99 }
100
101 internal bool SuppressProgressEvents
102 {
103 get
104 {
105 return this.suppressProgressEvents;
106 }
107
108 set
109 {
110 this.suppressProgressEvents = value;
111 }
112 }
113
114 internal IDictionary<string, short> CabNumbers
115 {
116 get
117 {
118 return this.cabNumbers;
119 }
120 }
121
122 internal string NextCabinetName
123 {
124 get
125 {
126 return this.nextCabinetName;
127 }
128
129 set
130 {
131 this.nextCabinetName = value;
132 }
133 }
134
135 internal Stream CabStream
136 {
137 get
138 {
139 return this.cabStream;
140 }
141
142 set
143 {
144 this.cabStream = value;
145 }
146 }
147
148 internal Stream FileStream
149 {
150 get
151 {
152 return this.fileStream;
153 }
154
155 set
156 {
157 this.fileStream = value;
158 }
159 }
160
161 public void Dispose()
162 {
163 this.Dispose(true);
164 GC.SuppressFinalize(this);
165 }
166
167 protected void ResetProgressData()
168 {
169 this.currentFileName = null;
170 this.currentFileNumber = 0;
171 this.totalFiles = 0;
172 this.currentFileBytesProcessed = 0;
173 this.currentFileTotalBytes = 0;
174 this.currentFolderNumber = 0;
175 this.currentFolderTotalBytes = 0;
176 this.currentArchiveName = null;
177 this.currentArchiveNumber = 0;
178 this.totalArchives = 0;
179 this.currentArchiveBytesProcessed = 0;
180 this.currentArchiveTotalBytes = 0;
181 this.fileBytesProcessed = 0;
182 this.totalFileBytes = 0;
183 }
184
185 protected void OnProgress(ArchiveProgressType progressType)
186 {
187 if (!this.suppressProgressEvents)
188 {
189 ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
190 progressType,
191 this.currentFileName,
192 this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
193 this.totalFiles,
194 this.currentFileBytesProcessed,
195 this.currentFileTotalBytes,
196 this.currentArchiveName,
197 this.currentArchiveNumber,
198 this.totalArchives,
199 this.currentArchiveBytesProcessed,
200 this.currentArchiveTotalBytes,
201 this.fileBytesProcessed,
202 this.totalFileBytes);
203 this.CabEngine.ReportProgress(e);
204 }
205 }
206
207 internal IntPtr CabAllocMem(int byteCount)
208 {
209 IntPtr memPointer = Marshal.AllocHGlobal((IntPtr) byteCount);
210 return memPointer;
211 }
212
213 internal void CabFreeMem(IntPtr memPointer)
214 {
215 Marshal.FreeHGlobal(memPointer);
216 }
217
218 internal int CabOpenStream(string path, int openFlags, int shareMode)
219 {
220 int err; return this.CabOpenStreamEx(path, openFlags, shareMode, out err, IntPtr.Zero);
221 }
222
223 internal virtual int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
224 {
225 path = path.Trim();
226 Stream stream = this.cabStream;
227 this.cabStream = new DuplicateStream(stream);
228 int streamHandle = this.streamHandles.AllocHandle(stream);
229 err = 0;
230 return streamHandle;
231 }
232
233 internal int CabReadStream(int streamHandle, IntPtr memory, int cb)
234 {
235 int err; return this.CabReadStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
236 }
237
238 internal virtual int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
239 {
240 Stream stream = this.streamHandles[streamHandle];
241 int count = (int) cb;
242 if (count > this.buf.Length)
243 {
244 this.buf = new byte[count];
245 }
246 count = stream.Read(this.buf, 0, count);
247 Marshal.Copy(this.buf, 0, memory, count);
248 err = 0;
249 return count;
250 }
251
252 internal int CabWriteStream(int streamHandle, IntPtr memory, int cb)
253 {
254 int err; return this.CabWriteStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
255 }
256
257 internal virtual int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
258 {
259 Stream stream = this.streamHandles[streamHandle];
260 int count = (int) cb;
261 if (count > this.buf.Length)
262 {
263 this.buf = new byte[count];
264 }
265 Marshal.Copy(memory, this.buf, 0, count);
266 stream.Write(this.buf, 0, count);
267 err = 0;
268 return cb;
269 }
270
271 internal int CabCloseStream(int streamHandle)
272 {
273 int err; return this.CabCloseStreamEx(streamHandle, out err, IntPtr.Zero);
274 }
275
276 internal virtual int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
277 {
278 this.streamHandles.FreeHandle(streamHandle);
279 err = 0;
280 return 0;
281 }
282
283 internal int CabSeekStream(int streamHandle, int offset, int seekOrigin)
284 {
285 int err; return this.CabSeekStreamEx(streamHandle, offset, seekOrigin, out err, IntPtr.Zero);
286 }
287
288 internal virtual int CabSeekStreamEx(int streamHandle, int offset, int seekOrigin, out int err, IntPtr pv)
289 {
290 Stream stream = this.streamHandles[streamHandle];
291 offset = (int) stream.Seek(offset, (SeekOrigin) seekOrigin);
292 err = 0;
293 return offset;
294 }
295
296 /// <summary>
297 /// Disposes of resources allocated by the cabinet engine.
298 /// </summary>
299 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
300 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
301 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
302 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
303 protected virtual void Dispose(bool disposing)
304 {
305 if (disposing)
306 {
307 if (this.cabStream != null)
308 {
309 this.cabStream.Close();
310 this.cabStream = null;
311 }
312
313 if (this.fileStream != null)
314 {
315 this.fileStream.Close();
316 this.fileStream = null;
317 }
318 }
319
320 if (this.erfHandle.IsAllocated)
321 {
322 this.erfHandle.Free();
323 }
324 }
325
326 protected void CheckError(bool extracting)
327 {
328 if (this.Erf.Error)
329 {
330 throw new CabException(
331 this.Erf.Oper,
332 this.Erf.Type,
333 CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, extracting));
334 }
335 }
336 }
337}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources
new file mode 100644
index 00000000..d53d263c
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources
Binary files differ
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt
new file mode 100644
index 00000000..df5a95d3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt
@@ -0,0 +1,35 @@
1;
2; Cabinet Error Messages
3;
4
5; Generic error message.
61=Error code: {1}
7
8;
9; Cabinet creation messages - offset by 1000
10;
111000=Unknown error creating cabinet.
121001=Failure opening file to be stored in cabinet.
131002=Failure reading file to be stored in cabinet.
141003=Could not allocate enough memory to create cabinet.
151004=Could not create a temporary file.
161005=Unknown compression type.
171006=Could not create cabinet file.
181007=Client requested abort.
191008=Failure compressing data.
20
21;
22; Cabinet extraction messages - offset by 2000
23;
242000=Unknown error extracting cabinet.
252001=Cabinet not found.
262002=Cabinet file does not have the correct format.
272003=Cabinet file has an unknown version number.
282004=Cabinet file is corrupt.
292005=Could not allocate enough memory to extract cabinet.
302006=Unknown compression type in a cabinet folder.
312007=Failure decompressing data from a cabinet file.
322008=Failure writing to target file.
332009=Cabinets in a set do not have the same RESERVE sizes.
342010=Cabinet returned on NEXT_CABINET is incorrect.
352011=Client requested abort.
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs
new file mode 100644
index 00000000..aad9a317
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs
@@ -0,0 +1,76 @@
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.Dtf.Compression.Cab
4{
5 using System;
6 using System.Collections.Generic;
7
8 /// <summary>
9 /// Generic class for managing allocations of integer handles
10 /// for objects of a certain type.
11 /// </summary>
12 /// <typeparam name="T">The type of objects the handles refer to.</typeparam>
13 internal sealed class HandleManager<T> where T : class
14 {
15 /// <summary>
16 /// Auto-resizing list of objects for which handles have been allocated.
17 /// Each handle is just an index into this list. When a handle is freed,
18 /// the list item at that index is set to null.
19 /// </summary>
20 private List<T> handles;
21
22 /// <summary>
23 /// Creates a new HandleManager instance.
24 /// </summary>
25 public HandleManager()
26 {
27 this.handles = new List<T>();
28 }
29
30 /// <summary>
31 /// Gets the object of a handle, or null if the handle is invalid.
32 /// </summary>
33 /// <param name="handle">The integer handle previously allocated
34 /// for the desired object.</param>
35 /// <returns>The object for which the handle was allocated.</returns>
36 public T this[int handle]
37 {
38 get
39 {
40 if (handle > 0 && handle <= this.handles.Count)
41 {
42 return this.handles[handle - 1];
43 }
44 else
45 {
46 return null;
47 }
48 }
49 }
50
51 /// <summary>
52 /// Allocates a new handle for an object.
53 /// </summary>
54 /// <param name="obj">Object that the handle will refer to.</param>
55 /// <returns>New handle that can be later used to retrieve the object.</returns>
56 public int AllocHandle(T obj)
57 {
58 this.handles.Add(obj);
59 int handle = this.handles.Count;
60 return handle;
61 }
62
63 /// <summary>
64 /// Frees a handle that was previously allocated. Afterward the handle
65 /// will be invalid and the object it referred to can no longer retrieved.
66 /// </summary>
67 /// <param name="handle">Handle to be freed.</param>
68 public void FreeHandle(int handle)
69 {
70 if (handle > 0 && handle <= this.handles.Count)
71 {
72 this.handles[handle - 1] = null;
73 }
74 }
75 }
76}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs
new file mode 100644
index 00000000..562e96dd
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs
@@ -0,0 +1,407 @@
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.Dtf.Compression.Cab
4{
5using System;
6using System.Text;
7using System.Security;
8using System.Runtime.InteropServices;
9using System.Diagnostics.CodeAnalysis;
10
11/// <summary>
12/// Native DllImport methods and related structures and constants used for
13/// cabinet creation and extraction via cabinet.dll.
14/// </summary>
15internal static class NativeMethods
16{
17 /// <summary>
18 /// A direct import of constants, enums, structures, delegates, and functions from fci.h.
19 /// Refer to comments in fci.h for documentation.
20 /// </summary>
21 internal static class FCI
22 {
23 internal const int MIN_DISK = 32768;
24 internal const int MAX_DISK = Int32.MaxValue;
25 internal const int MAX_FOLDER = 0x7FFF8000;
26 internal const int MAX_FILENAME = 256;
27 internal const int MAX_CABINET_NAME = 256;
28 internal const int MAX_CAB_PATH = 256;
29 internal const int MAX_DISK_NAME = 256;
30
31 internal const int CPU_80386 = 1;
32
33 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
34 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
35
36 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode, out int err, IntPtr pv);
37 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
38 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
39 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int fileHandle, out int err, IntPtr pv);
40 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int fileHandle, int dist, int seekType, out int err, IntPtr pv);
41 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNDELETE(string path, out int err, IntPtr pv);
42
43 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETNEXTCABINET(IntPtr pccab, uint cbPrevCab, IntPtr pv);
44 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNFILEPLACED(IntPtr pccab, string path, long fileSize, int continuation, IntPtr pv);
45 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETOPENINFO(string path, out short date, out short time, out short pattribs, out int err, IntPtr pv);
46 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSTATUS(STATUS typeStatus, uint cb1, uint cb2, IntPtr pv);
47 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETTEMPFILE(IntPtr tempNamePtr, int tempNameSize, IntPtr pv);
48
49 /// <summary>
50 /// Error codes that can be returned by FCI.
51 /// </summary>
52 internal enum ERROR : int
53 {
54 NONE,
55 OPEN_SRC,
56 READ_SRC,
57 ALLOC_FAIL,
58 TEMP_FILE,
59 BAD_COMPR_TYPE,
60 CAB_FILE,
61 USER_ABORT,
62 MCI_FAIL,
63 }
64
65 /// <summary>
66 /// FCI compression algorithm types and parameters.
67 /// </summary>
68 internal enum TCOMP : ushort
69 {
70 MASK_TYPE = 0x000F,
71 TYPE_NONE = 0x0000,
72 TYPE_MSZIP = 0x0001,
73 TYPE_QUANTUM = 0x0002,
74 TYPE_LZX = 0x0003,
75 BAD = 0x000F,
76
77 MASK_LZX_WINDOW = 0x1F00,
78 LZX_WINDOW_LO = 0x0F00,
79 LZX_WINDOW_HI = 0x1500,
80 SHIFT_LZX_WINDOW = 0x0008,
81
82 MASK_QUANTUM_LEVEL = 0x00F0,
83 QUANTUM_LEVEL_LO = 0x0010,
84 QUANTUM_LEVEL_HI = 0x0070,
85 SHIFT_QUANTUM_LEVEL = 0x0004,
86
87 MASK_QUANTUM_MEM = 0x1F00,
88 QUANTUM_MEM_LO = 0x0A00,
89 QUANTUM_MEM_HI = 0x1500,
90 SHIFT_QUANTUM_MEM = 0x0008,
91
92 MASK_RESERVED = 0xE000,
93 }
94
95 /// <summary>
96 /// Reason for FCI status callback.
97 /// </summary>
98 internal enum STATUS : uint
99 {
100 FILE = 0,
101 FOLDER = 1,
102 CABINET = 2,
103 }
104
105 [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments")]
106 [DllImport("cabinet.dll", EntryPoint = "FCICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
107 internal static extern Handle Create(IntPtr perf, PFNFILEPLACED pfnfcifp, PFNALLOC pfna, PFNFREE pfnf, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, PFNDELETE pfndelete, PFNGETTEMPFILE pfnfcigtf, [MarshalAs(UnmanagedType.LPStruct)] CCAB pccab, IntPtr pv);
108
109 [DllImport("cabinet.dll", EntryPoint = "FCIAddFile", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
110 internal static extern int AddFile(Handle hfci, string pszSourceFile, IntPtr pszFileName, [MarshalAs(UnmanagedType.Bool)] bool fExecute, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis, PFNGETOPENINFO pfnfcigoi, TCOMP typeCompress);
111
112 [DllImport("cabinet.dll", EntryPoint = "FCIFlushCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
113 internal static extern int FlushCabinet(Handle hfci, [MarshalAs(UnmanagedType.Bool)] bool fGetNextCab, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
114
115 [DllImport("cabinet.dll", EntryPoint = "FCIFlushFolder", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
116 internal static extern int FlushFolder(Handle hfci, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
117
118 [SuppressUnmanagedCodeSecurity]
119 [DllImport("cabinet.dll", EntryPoint = "FCIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
120 [return: MarshalAs(UnmanagedType.Bool)]
121 internal static extern bool Destroy(IntPtr hfci);
122
123 /// <summary>
124 /// Cabinet information structure used for FCI initialization and GetNextCabinet callback.
125 /// </summary>
126 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
127 internal class CCAB
128 {
129 internal int cb = MAX_DISK;
130 internal int cbFolderThresh = MAX_FOLDER;
131 internal int cbReserveCFHeader;
132 internal int cbReserveCFFolder;
133 internal int cbReserveCFData;
134 internal int iCab;
135 internal int iDisk;
136 internal int fFailOnIncompressible;
137 internal short setID;
138 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_DISK_NAME )] internal string szDisk = String.Empty;
139 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CABINET_NAME)] internal string szCab = String.Empty;
140 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CAB_PATH )] internal string szCabPath = String.Empty;
141 }
142
143 /// <summary>
144 /// Ensures that the FCI handle is safely released.
145 /// </summary>
146 internal class Handle : SafeHandle
147 {
148 /// <summary>
149 /// Creates a new unintialized handle. The handle will be initialized
150 /// when it is marshalled back from native code.
151 /// </summary>
152 internal Handle()
153 : base(IntPtr.Zero, true)
154 {
155 }
156
157 /// <summary>
158 /// Checks if the handle is invalid. An FCI handle is invalid when it is zero.
159 /// </summary>
160 public override bool IsInvalid
161 {
162 get
163 {
164 return this.handle == IntPtr.Zero;
165 }
166 }
167
168 /// <summary>
169 /// Releases the handle by calling FDIDestroy().
170 /// </summary>
171 /// <returns>True if the release succeeded.</returns>
172 protected override bool ReleaseHandle()
173 {
174 return FCI.Destroy(this.handle);
175 }
176 }
177 }
178
179 /// <summary>
180 /// A direct import of constants, enums, structures, delegates, and functions from fdi.h.
181 /// Refer to comments in fdi.h for documentation.
182 /// </summary>
183 internal static class FDI
184 {
185 internal const int MAX_DISK = Int32.MaxValue;
186 internal const int MAX_FILENAME = 256;
187 internal const int MAX_CABINET_NAME = 256;
188 internal const int MAX_CAB_PATH = 256;
189 internal const int MAX_DISK_NAME = 256;
190
191 internal const int CPU_80386 = 1;
192
193 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
194 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
195
196 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode);
197 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int hf, IntPtr pv, int cb);
198 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int hf, IntPtr pv, int cb);
199 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int hf);
200 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int hf, int dist, int seektype);
201
202 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNNOTIFY(NOTIFICATIONTYPE fdint, NOTIFICATION fdin);
203
204 /// <summary>
205 /// Error codes that can be returned by FDI.
206 /// </summary>
207 internal enum ERROR : int
208 {
209 NONE,
210 CABINET_NOT_FOUND,
211 NOT_A_CABINET,
212 UNKNOWN_CABINET_VERSION,
213 CORRUPT_CABINET,
214 ALLOC_FAIL,
215 BAD_COMPR_TYPE,
216 MDI_FAIL,
217 TARGET_FILE,
218 RESERVE_MISMATCH,
219 WRONG_CABINET,
220 USER_ABORT,
221 }
222
223 /// <summary>
224 /// Type of notification message for the FDI Notify callback.
225 /// </summary>
226 internal enum NOTIFICATIONTYPE : int
227 {
228 CABINET_INFO,
229 PARTIAL_FILE,
230 COPY_FILE,
231 CLOSE_FILE_INFO,
232 NEXT_CABINET,
233 ENUMERATE,
234 }
235
236 [DllImport("cabinet.dll", EntryPoint = "FDICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
237 internal static extern Handle Create([MarshalAs(UnmanagedType.FunctionPtr)] PFNALLOC pfnalloc, [MarshalAs(UnmanagedType.FunctionPtr)] PFNFREE pfnfree, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, int cpuType, IntPtr perf);
238
239 [DllImport("cabinet.dll", EntryPoint = "FDICopy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
240 internal static extern int Copy(Handle hfdi, string pszCabinet, string pszCabPath, int flags, PFNNOTIFY pfnfdin, IntPtr pfnfdid, IntPtr pvUser);
241
242 [SuppressUnmanagedCodeSecurity]
243 [DllImport("cabinet.dll", EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
244 [return: MarshalAs(UnmanagedType.Bool)]
245 internal static extern bool Destroy(IntPtr hfdi);
246
247 [DllImport("cabinet.dll", EntryPoint = "FDIIsCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
248 [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", Justification="FDI file handles definitely remain 4 bytes on 64bit platforms.")]
249 internal static extern int IsCabinet(Handle hfdi, int hf, out CABINFO pfdici);
250
251 /// <summary>
252 /// Cabinet information structure filled in by FDI IsCabinet.
253 /// </summary>
254 [StructLayout(LayoutKind.Sequential)]
255 internal struct CABINFO
256 {
257 internal int cbCabinet;
258 internal short cFolders;
259 internal short cFiles;
260 internal short setID;
261 internal short iCabinet;
262 internal int fReserve;
263 internal int hasprev;
264 internal int hasnext;
265 }
266
267 /// <summary>
268 /// Cabinet notification details passed to the FDI Notify callback.
269 /// </summary>
270 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
271 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
272 internal class NOTIFICATION
273 {
274 internal int cb;
275 internal IntPtr psz1;
276 internal IntPtr psz2;
277 internal IntPtr psz3;
278 internal IntPtr pv;
279
280 internal IntPtr hf_ptr;
281
282 internal short date;
283 internal short time;
284 internal short attribs;
285 internal short setID;
286 internal short iCabinet;
287 internal short iFolder;
288 internal int fdie;
289
290 // Unlike all the other file handles in FCI/FDI, this one is
291 // actually pointer-sized. Use a property to pretend it isn't.
292 internal int hf
293 {
294 get { return (int) this.hf_ptr; }
295 }
296 }
297
298 /// <summary>
299 /// Ensures that the FDI handle is safely released.
300 /// </summary>
301 internal class Handle : SafeHandle
302 {
303 /// <summary>
304 /// Creates a new unintialized handle. The handle will be initialized
305 /// when it is marshalled back from native code.
306 /// </summary>
307 internal Handle()
308 : base(IntPtr.Zero, true)
309 {
310 }
311
312 /// <summary>
313 /// Checks if the handle is invalid. An FDI handle is invalid when it is zero.
314 /// </summary>
315 public override bool IsInvalid
316 {
317 get
318 {
319 return this.handle == IntPtr.Zero;
320 }
321 }
322
323 /// <summary>
324 /// Releases the handle by calling FDIDestroy().
325 /// </summary>
326 /// <returns>True if the release succeeded.</returns>
327 protected override bool ReleaseHandle()
328 {
329 return FDI.Destroy(this.handle);
330 }
331 }
332 }
333
334 /// <summary>
335 /// Error info structure for FCI and FDI.
336 /// </summary>
337 /// <remarks>Before being passed to FCI or FDI, this structure is
338 /// pinned in memory via a GCHandle. The pinning is necessary
339 /// to be able to read the results, since the ERF structure doesn't
340 /// get marshalled back out after an error.</remarks>
341 [StructLayout(LayoutKind.Sequential)]
342 internal class ERF
343 {
344 private int erfOper;
345 private int erfType;
346 private int fError;
347
348 /// <summary>
349 /// Gets or sets the cabinet error code.
350 /// </summary>
351 internal int Oper
352 {
353 get
354 {
355 return this.erfOper;
356 }
357
358 set
359 {
360 this.erfOper = value;
361 }
362 }
363
364 /// <summary>
365 /// Gets or sets the Win32 error code.
366 /// </summary>
367 internal int Type
368 {
369 get
370 {
371 return this.erfType;
372 }
373
374 set
375 {
376 this.erfType = value;
377 }
378 }
379
380 /// <summary>
381 /// GCHandle doesn't like the bool type, so use an int underneath.
382 /// </summary>
383 internal bool Error
384 {
385 get
386 {
387 return this.fError != 0;
388 }
389
390 set
391 {
392 this.fError = value ? 1 : 0;
393 }
394 }
395
396 /// <summary>
397 /// Clears the error information.
398 /// </summary>
399 internal void Clear()
400 {
401 this.Oper = 0;
402 this.Type = 0;
403 this.Error = false;
404 }
405 }
406}
407}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj b/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj
new file mode 100644
index 00000000..6b2c8cf8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Compression.Cab</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Compression.Cab</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Managed libraries for cabinet archive packing and unpacking</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <None Include="Errors.txt" />
15 <EmbeddedResource Include="Errors.resources" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
20 </ItemGroup>
21
22 <ItemGroup>
23 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 </ItemGroup>
26</Project>