Reactos
at master 248 lines 7.5 kB view raw
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 13struct EXTRACTCABINETINTERNALDATA 14{ 15 LPCWSTR destination; 16 EXTRACTCALLBACK callback; 17 LPVOID cookie; 18}; 19 20static 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 45static 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 62FNFREE(CabMemFree) 63{ 64 SHFree(pv); 65} 66 67FNALLOC(CabMemAlloc) 68{ 69 return SHAlloc(cb); 70} 71 72FNCLOSE(CabClose) 73{ 74 return CloseHandle((HANDLE)hf) ? 0 : -1; 75} 76 77static 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 82FNOPEN(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 96FNREAD(CabRead) 97{ 98 DWORD dwBytesRead; 99 return ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) ? dwBytesRead : -1; 100} 101 102FNWRITE(CabWrite) 103{ 104 DWORD dwBytesWritten; 105 return WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) ? dwBytesWritten : -1; 106} 107 108FNSEEK(CabSeek) 109{ 110 return SetFilePointer((HANDLE)hf, dist, NULL, seektype); 111} 112 113static 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 120FNFDINOTIFY(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 189HRESULT 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}