xref: /reactos/dll/shellext/cabview/extract.cpp (revision 2d4c0b87)
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 
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 
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 
62 FNFREE(CabMemFree)
63 {
64     SHFree(pv);
65 }
66 
67 FNALLOC(CabMemAlloc)
68 {
69     return SHAlloc(cb);
70 }
71 
72 FNCLOSE(CabClose)
73 {
74     return CloseHandle((HANDLE)hf) ? 0 : -1;
75 }
76 
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 
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 
96 FNREAD(CabRead)
97 {
98     DWORD dwBytesRead;
99     return ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) ? dwBytesRead : -1;
100 }
101 
102 FNWRITE(CabWrite)
103 {
104     DWORD dwBytesWritten;
105     return WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) ? dwBytesWritten : -1;
106 }
107 
108 FNSEEK(CabSeek)
109 {
110     return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
111 }
112 
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 
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 
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