main
cpp 282 lines 8.97 KB
Raw
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 #include "precomp.h"
4
5 //---------------------------------------------------------------------
6 // CABINET EXTRACTION
7 //---------------------------------------------------------------------
8
9 // Globals make this code unsuited for multhreaded use,
10 // but FDI doesn't provide any other way to pass context.
11
12 // Handle to the FDI (cab extraction) engine. Need access to this in a callback.
13 static HFDI g_hfdi;
14
15 // FDI is not unicode-aware, so avoid passing these paths through the callbacks.
16 static const wchar_t* g_szExtractDir;
17 static const wchar_t* g_szCabFile;
18
19 // Offset into the source file where the cabinet really starts.
20 // Used to trick FDI into extracting from a concatenated cabinet.
21 static int g_lCabOffset;
22
23 // Use the secure CRT version of _wsopen if available.
24 #ifdef __GOT_SECURE_LIB__
25 #define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
26 #else
27 #define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
28 #endif
29
30 /// <summary>
31 /// FDI callback to open a cabinet file.
32 /// </summary>
33 /// <param name="pszFile">Name of the file to be opened. This parameter
34 /// is ignored since with our limited use this method is only ever called
35 /// to open the main cabinet file.</param>
36 /// <param name="oflag">Type of operations allowed.</param>
37 /// <param name="pmode">Permission setting.</param>
38 /// <returns>Integer file handle, or -1 if the file could not be opened.</returns>
39 /// <remarks>
40 /// To support reading from a cabinet that is concatenated onto
41 /// another file, this function first searches for the offset of the cabinet,
42 /// then saves that offset for use in recalculating later seeks.
43 /// </remarks>
44 static FNOPEN(CabOpen)
45 {
46 UNREFERENCED_PARAMETER(pszFile);
47 int hf;
48 _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
49 if (hf != -1)
50 {
51 FDICABINETINFO cabInfo;
52 int length = _lseek(hf, 0, SEEK_END);
53 for(int offset = 0; offset < length; offset += 256)
54 {
55 if (_lseek(hf, offset, SEEK_SET) != offset) break;
56 if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
57 {
58 g_lCabOffset = offset;
59 _lseek(hf, offset, SEEK_SET);
60 return hf;
61 }
62 }
63 _close(hf);
64 }
65 return -1;
66 }
67
68 /// <summary>
69 /// FDI callback to seek within a file.
70 /// </summary>
71 /// <param name="hf">File handle.</param>
72 /// <param name="dist">Seek distance</param>
73 /// <param name="seektype">Whether to seek relative to the
74 /// beginning, current position, or end of the file.</param>
75 /// <returns>Resultant position within the cabinet.</returns>
76 /// <remarks>
77 /// To support reading from a cabinet that is concatenated onto
78 /// another file, this function recalculates seeks based on the
79 /// offset that was determined when the cabinet was opened.
80 /// </remarks>
81 static FNSEEK(CabSeek)
82 {
83 if (seektype == SEEK_SET) dist += g_lCabOffset;
84 int pos = _lseek((int) hf, dist, seektype);
85 pos -= g_lCabOffset;
86 return pos;
87 }
88
89 /// <summary>
90 /// Ensures a directory and its parent directory path exists.
91 /// </summary>
92 /// <param name="szDirPath">Directory path, not including file name.</param>
93 /// <returns>0 if the directory exists or was successfully created, else nonzero.</returns>
94 /// <remarks>
95 /// This function modifies characters in szDirPath, but always restores them
96 /// regardless of error condition.
97 /// </remarks>
98 static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
99 {
100 int ret = 0;
101 if (!::CreateDirectoryW(szDirPath, NULL))
102 {
103 UINT err = ::GetLastError();
104 if (err != ERROR_ALREADY_EXISTS)
105 {
106 // Directory creation failed for some reason other than already existing.
107 // Try to create the parent directory first.
108 wchar_t* szLastSlash = NULL;
109 for (wchar_t* sz = szDirPath; *sz; sz++)
110 {
111 if (*sz == L'\\')
112 {
113 szLastSlash = sz;
114 }
115 }
116 if (szLastSlash)
117 {
118 // Temporarily take one directory off the path and recurse.
119 *szLastSlash = L'\0';
120 ret = EnsureDirectoryExists(szDirPath);
121 *szLastSlash = L'\\';
122
123 // Try to create the directory if all parents are created.
124 if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
125 {
126 err = ::GetLastError();
127 if (err != ERROR_ALREADY_EXISTS)
128 {
129 ret = -1;
130 }
131 }
132 }
133 else
134 {
135 ret = -1;
136 }
137 }
138 }
139 return ret;
140 }
141
142 /// <summary>
143 /// Ensures a file's directory and its parent directory path exists.
144 /// </summary>
145 /// <param name="szDirPath">Path including file name.</param>
146 /// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns>
147 /// <remarks>
148 /// This function modifies characters in szFilePath, but always restores them
149 /// regardless of error condition.
150 /// </remarks>
151 static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
152 {
153 int ret = 0;
154 wchar_t* szLastSlash = NULL;
155 for (wchar_t* sz = szFilePath; *sz; sz++)
156 {
157 if (*sz == L'\\')
158 {
159 szLastSlash = sz;
160 }
161 }
162 if (szLastSlash)
163 {
164 *szLastSlash = L'\0';
165 ret = EnsureDirectoryExists(szFilePath);
166 *szLastSlash = L'\\';
167 }
168 return ret;
169 }
170
171 /// <summary>
172 /// FDI callback for handling files in the cabinet.
173 /// </summary>
174 /// <param name="fdint">Type of notification.</param>
175 /// <param name="pfdin">Structure containing data about the notification.</param>
176 /// <remarks>
177 /// Refer to fdi.h for more comments on this notification callback.
178 /// </remarks>
179 static FNFDINOTIFY(CabNotification)
180 {
181 // fdintCOPY_FILE:
182 // Called for each file that *starts* in the current cabinet, giving
183 // the client the opportunity to request that the file be copied or
184 // skipped.
185 // Entry:
186 // pfdin->psz1 = file name in cabinet
187 // pfdin->cb = uncompressed size of file
188 // pfdin->date = file date
189 // pfdin->time = file time
190 // pfdin->attribs = file attributes
191 // pfdin->iFolder = file's folder index
192 // Exit-Success:
193 // Return non-zero file handle for destination file; FDI writes
194 // data to this file use the PFNWRITE function supplied to FDICreate,
195 // and then calls fdintCLOSE_FILE_INFO to close the file and set
196 // the date, time, and attributes.
197 // Exit-Failure:
198 // Returns 0 => Skip file, do not copy
199 // Returns -1 => Abort FDICopy() call
200 if (fdint == fdintCOPY_FILE)
201 {
202 size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
203 size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
204 wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
205 if (szFilePath == NULL) return -1;
206 StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
207 StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
208 MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
209 szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
210 int hf = -1;
211 if (EnsureFileDirectoryExists(szFilePath) == 0)
212 {
213 _wsopen__s(hf, szFilePath,
214 _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
215 _SH_DENYWR, _S_IREAD | _S_IWRITE);
216 }
217 return hf;
218 }
219
220 // fdintCLOSE_FILE_INFO:
221 // Called after all of the data has been written to a target file.
222 // This function must close the file and set the file date, time,
223 // and attributes.
224 // Entry:
225 // pfdin->psz1 = file name in cabinet
226 // pfdin->hf = file handle
227 // pfdin->date = file date
228 // pfdin->time = file time
229 // pfdin->attribs = file attributes
230 // pfdin->iFolder = file's folder index
231 // pfdin->cb = Run After Extract (0 - don't run, 1 Run)
232 // Exit-Success:
233 // Returns TRUE
234 // Exit-Failure:
235 // Returns FALSE, or -1 to abort
236 else if (fdint == fdintCLOSE_FILE_INFO)
237 {
238 _close((int) pfdin->hf);
239 return TRUE;
240 }
241 return 0;
242 }
243
244 /// <summary>
245 /// Extracts all contents of a cabinet file to a directory.
246 /// </summary>
247 /// <param name="szCabFile">Path to the cabinet file to be extracted.
248 /// The cabinet may actually start at some offset within the file,
249 /// as long as that offset is a multiple of 256.</param>
250 /// <param name="szExtractDir">Directory where files are to be extracted.
251 /// This directory must already exist, but should be empty.</param>
252 /// <returns>0 if the cabinet was extracted successfully,
253 /// or an error code if any error occurred.</returns>
254 /// <remarks>
255 /// The extraction will not overwrite any files in the destination
256 /// directory; extraction will be interrupted and fail if any files
257 /// with the same name already exist.
258 /// </remarks>
259 int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
260 {
261 ERF erf;
262 // Most of the FDI callbacks can be handled by existing CRT I/O functions.
263 // For our functionality we only need to handle the open and seek callbacks.
264 HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
265 (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
266 CabSeek, cpu80386, &erf);
267 if (hfdi != NULL)
268 {
269 g_hfdi = hfdi;
270 g_szCabFile = szCabFile;
271 g_szExtractDir = szExtractDir;
272 char szEmpty[1] = {0};
273 if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
274 {
275 FDIDestroy(hfdi);
276 return 0;
277 }
278 FDIDestroy(hfdi);
279 }
280
281 return erf.erfOper;
282 }