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