1 /*
2 * PROJECT: ReactOS CabView Shell Extension
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: FDI API wrapper
5 * COPYRIGHT: Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6 */
7
8 #include "precomp.h"
9 #include "cabview.h"
10 #include "util.h"
11 #include <fcntl.h>
12
13 struct EXTRACTCABINETINTERNALDATA
14 {
15 LPCWSTR destination;
16 EXTRACTCALLBACK callback;
17 LPVOID cookie;
18 };
19
BuildPath(LPCWSTR Dir,LPCSTR File,UINT Attr)20 static LPWSTR BuildPath(LPCWSTR Dir, LPCSTR File, UINT Attr)
21 {
22 UINT cp = Attr & _A_NAME_IS_UTF ? CP_UTF8 : CP_ACP;
23 UINT cchfile = MultiByteToWideChar(cp, 0, File, -1, 0, 0);
24 SIZE_T lendir = lstrlenW(Dir), cch = lendir + 1 + cchfile;
25 LPWSTR path = (LPWSTR)SHAlloc(cch * sizeof(*path));
26 if (path)
27 {
28 lstrcpyW(path, Dir);
29 if (lendir && !IsPathSep(path[lendir - 1]))
30 path[lendir++] = '\\';
31
32 LPWSTR dst = &path[lendir];
33 MultiByteToWideChar(cp, 0, File + IsPathSep(*File), -1, dst, cchfile);
34 for (SIZE_T i = 0; dst[i]; ++i)
35 {
36 if (dst[i] == L':' && lendir) // Don't allow absolute paths
37 dst[i] = L'_';
38 if (dst[i] == L'/') // Normalize
39 dst[i] = L'\\';
40 }
41 }
42 return path;
43 }
44
HResultFrom(const ERF & erf)45 static HRESULT HResultFrom(const ERF &erf)
46 {
47 switch (erf.fError ? erf.erfOper : FDIERROR_NONE)
48 {
49 case FDIERROR_NONE:
50 return erf.fError ? HRESULT_FROM_WIN32(erf.erfType) : S_OK;
51 case FDIERROR_CABINET_NOT_FOUND:
52 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
53 case FDIERROR_ALLOC_FAIL:
54 return E_OUTOFMEMORY;
55 case FDIERROR_USER_ABORT:
56 return S_FALSE;
57 default:
58 return erf.erfType ? HRESULT_FROM_WIN32(erf.erfType) : E_FAIL;
59 }
60 }
61
FNFREE(CabMemFree)62 FNFREE(CabMemFree)
63 {
64 SHFree(pv);
65 }
66
FNALLOC(CabMemAlloc)67 FNALLOC(CabMemAlloc)
68 {
69 return SHAlloc(cb);
70 }
71
FNCLOSE(CabClose)72 FNCLOSE(CabClose)
73 {
74 return CloseHandle((HANDLE)hf) ? 0 : -1;
75 }
76
CabOpenEx(LPCWSTR path,UINT access,UINT share,UINT disp,UINT attr)77 static INT_PTR CabOpenEx(LPCWSTR path, UINT access, UINT share, UINT disp, UINT attr)
78 {
79 return (INT_PTR)CreateFileW(path, access, share, NULL, disp, attr, NULL);
80 }
81
FNOPEN(CabOpen)82 FNOPEN(CabOpen)
83 {
84 UINT disp = (oflag & _O_CREAT) ? CREATE_ALWAYS : OPEN_EXISTING;
85 UINT access = GENERIC_READ;
86 if (oflag & _O_RDWR)
87 access = GENERIC_READ | GENERIC_WRITE;
88 else if (oflag & _O_WRONLY)
89 access = GENERIC_WRITE;
90 UNREFERENCED_PARAMETER(pmode);
91 WCHAR buf[MAX_PATH * 2];
92 MultiByteToWideChar(CP_UTF8, 0, pszFile, -1, buf, _countof(buf));
93 return CabOpenEx(buf, access, FILE_SHARE_READ, disp, FILE_ATTRIBUTE_NORMAL);
94 }
95
FNREAD(CabRead)96 FNREAD(CabRead)
97 {
98 DWORD dwBytesRead;
99 return ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) ? dwBytesRead : -1;
100 }
101
FNWRITE(CabWrite)102 FNWRITE(CabWrite)
103 {
104 DWORD dwBytesWritten;
105 return WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) ? dwBytesWritten : -1;
106 }
107
FNSEEK(CabSeek)108 FNSEEK(CabSeek)
109 {
110 return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
111 }
112
Init(HFDI & hfdi,ERF & erf)113 static HRESULT Init(HFDI &hfdi, ERF &erf)
114 {
115 const int cpu = cpuUNKNOWN;
116 hfdi = FDICreate(CabMemAlloc, CabMemFree, CabOpen, CabRead, CabWrite, CabClose, CabSeek, cpu, &erf);
117 return hfdi ? S_OK : HResultFrom(erf);
118 }
119
FNFDINOTIFY(ExtractCabinetCallback)120 FNFDINOTIFY(ExtractCabinetCallback)
121 {
122 const UINT attrmask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE;
123 EXTRACTCABINETINTERNALDATA &ecd = *(EXTRACTCABINETINTERNALDATA*)pfdin->pv;
124 EXTRACTCALLBACKDATA noti;
125 HRESULT hr;
126 FILETIME ft;
127
128 noti.pfdin = pfdin;
129 switch (fdint)
130 {
131 case fdintCOPY_FILE:
132 hr = ecd.callback(ECM_FILE, noti, ecd.cookie);
133 if (hr == S_OK)
134 {
135 hr = E_OUTOFMEMORY;
136 LPWSTR path = BuildPath(ecd.destination, pfdin->psz1, pfdin->attribs);
137 if (path)
138 {
139 // Callee is using SHPPFW_IGNOREFILENAME so we don't need to remove the name.
140 /*LPWSTR file = PathFindFileNameW(path);
141 if (file > path)
142 {
143 file[-1] = L'\0';*/
144 noti.Path = path;
145 ecd.callback(ECM_PREPAREPATH, noti, ecd.cookie);
146 /* file[-1] = L'\\';
147 }*/
148 UINT attr = pfdin->attribs & attrmask;
149 UINT access = GENERIC_READ | GENERIC_WRITE, share = FILE_SHARE_DELETE;
150 INT_PTR handle = CabOpenEx(path, access, share, CREATE_NEW, attr | FILE_FLAG_SEQUENTIAL_SCAN);
151 noti.hr = HResultFromWin32(GetLastError());
152 SHFree(path);
153 if (handle != (INT_PTR)-1)
154 return handle;
155 if (ecd.callback(ECM_ERROR, noti, ecd.cookie) != E_NOTIMPL)
156 hr = noti.hr;
157 }
158 }
159 return hr == S_FALSE ? 0 : -1;
160
161 case fdintCLOSE_FILE_INFO:
162 if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
163 SetFileTime((HANDLE)(pfdin->hf), NULL, NULL, &ft);
164 return !CabClose(pfdin->hf);
165
166 case fdintNEXT_CABINET:
167 if (pfdin->fdie && pfdin->fdie != FDIERROR_USER_ABORT)
168 {
169 if (pfdin->fdie == FDIERROR_CABINET_NOT_FOUND)
170 noti.hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
171 else
172 noti.hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
173 ecd.callback(ECM_ERROR, noti, ecd.cookie);
174 }
175 return pfdin->fdie ? -1 : 0;
176
177 case fdintPARTIAL_FILE:
178 return 0;
179
180 case fdintCABINET_INFO:
181 return 0;
182
183 case fdintENUMERATE:
184 return 0;
185 }
186 return -1;
187 }
188
ExtractCabinet(LPCWSTR cab,LPCWSTR destination,EXTRACTCALLBACK callback,LPVOID cookie)189 HRESULT ExtractCabinet(LPCWSTR cab, LPCWSTR destination, EXTRACTCALLBACK callback, LPVOID cookie)
190 {
191 BOOL quick = !destination;
192 if (!destination)
193 destination = L"?:"; // Dummy path for callers that enumerate without extracting
194 EXTRACTCABINETINTERNALDATA data = { destination, callback, cookie };
195 EXTRACTCALLBACKDATA noti;
196 ERF erf = { };
197 HFDI hfdi;
198 UINT total = 0, files = 0;
199 HRESULT hr = Init(hfdi, erf);
200 if (FAILED_UNEXPECTEDLY(hr))
201 return hr;
202
203 UINT share = FILE_SHARE_READ | FILE_SHARE_DELETE;
204 INT_PTR hf = quick ? -1 : CabOpenEx(cab, GENERIC_READ, share, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL);
205 if (hf != -1)
206 {
207 FDICABINETINFO ci;
208 if (FDIIsCabinet(hfdi, hf, &ci))
209 {
210 total = ci.cbCabinet;
211 files = ci.cFiles;
212 }
213 CabClose(hf);
214 }
215
216 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
217 char buf[MAX_PATH * 2], *name = 0;
218 if (!WideCharToMultiByte(CP_UTF8, 0, cab, -1, buf, _countof(buf), NULL, NULL))
219 {
220 *buf = '\0';
221 hr = E_INVALIDARG;
222 }
223 for (UINT i = 0; buf[i]; ++i)
224 {
225 if (buf[i] == '\\' || buf[i] == '/')
226 name = &buf[i + 1];
227 }
228 if (name > buf && *name)
229 {
230 // Format the name the way FDI likes it
231 name[-1] = ANSI_NULL;
232 char namebuf[MAX_PATH];
233 namebuf[0] = '\\';
234 lstrcpyA(namebuf + 1, name);
235 name = namebuf;
236
237 FDINOTIFICATION fdin;
238 fdin.cb = total;
239 fdin.hf = files;
240 noti.Path = cab;
241 noti.pfdin = &fdin;
242 callback(ECM_BEGIN, noti, cookie);
243
244 hr = FDICopy(hfdi, name, buf, 0, ExtractCabinetCallback, NULL, &data) ? S_OK : HResultFrom(erf);
245 }
246 FDIDestroy(hfdi);
247 return hr;
248 }
249