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