1 /* 2 * Extract - Wine-compatible program for extract *.cab files. 3 * 4 * Copyright 2007 Etersoft (Lyutin Anatoly) 5 * Copyright 2009 Ilya Shpigor 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 */ 21 22 #include <windows.h> 23 #include <shellapi.h> 24 #include <setupapi.h> 25 #include <shlwapi.h> 26 #include <shlobj.h> 27 28 #include "wine/unicode.h" 29 #include "wine/debug.h" 30 31 WINE_DEFAULT_DEBUG_CHANNEL(extrac32); 32 33 static BOOL force_mode; 34 static BOOL show_content; 35 36 static void create_target_directory(LPWSTR Target) 37 { 38 WCHAR dir[MAX_PATH]; 39 int res; 40 41 strcpyW(dir, Target); 42 *PathFindFileNameW(dir) = 0; /* Truncate file name */ 43 if(!PathIsDirectoryW(dir)) 44 { 45 res = SHCreateDirectoryExW(NULL, dir, NULL); 46 if(res != ERROR_SUCCESS && res != ERROR_ALREADY_EXISTS) 47 WINE_ERR("Can't create directory: %s\n", wine_dbgstr_w(dir)); 48 } 49 } 50 51 static UINT WINAPI ExtCabCallback(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2) 52 { 53 FILE_IN_CABINET_INFO_W *pInfo; 54 FILEPATHS_W *pFilePaths; 55 56 switch(Notification) 57 { 58 case SPFILENOTIFY_FILEINCABINET: 59 pInfo = (FILE_IN_CABINET_INFO_W*)Param1; 60 if(show_content) 61 { 62 FILETIME ft; 63 SYSTEMTIME st; 64 CHAR date[12], time[12], buf[2 * MAX_PATH]; 65 int count; 66 DWORD dummy; 67 68 /* DosDate and DosTime already represented at local time */ 69 DosDateTimeToFileTime(pInfo->DosDate, pInfo->DosTime, &ft); 70 FileTimeToSystemTime(&ft, &st); 71 GetDateFormatA(0, 0, &st, "MM'-'dd'-'yyyy", date, sizeof date); 72 GetTimeFormatA(0, 0, &st, "HH':'mm':'ss", time, sizeof time); 73 count = wsprintfA(buf, "%s %s %c%c%c%c %15u %S\n", date, time, 74 pInfo->DosAttribs & FILE_ATTRIBUTE_ARCHIVE ? 'A' : '-', 75 pInfo->DosAttribs & FILE_ATTRIBUTE_HIDDEN ? 'H' : '-', 76 pInfo->DosAttribs & FILE_ATTRIBUTE_READONLY ? 'R' : '-', 77 pInfo->DosAttribs & FILE_ATTRIBUTE_SYSTEM ? 'S' : '-', 78 pInfo->FileSize, pInfo->NameInCabinet); 79 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, count, &dummy, NULL); 80 return FILEOP_SKIP; 81 } 82 else 83 { 84 lstrcpyW(pInfo->FullTargetName, (LPCWSTR)Context); 85 lstrcatW(pInfo->FullTargetName, pInfo->NameInCabinet); 86 /* SetupIterateCabinet() doesn't create full path to target by itself, 87 so we should do it manually */ 88 create_target_directory(pInfo->FullTargetName); 89 return FILEOP_DOIT; 90 } 91 case SPFILENOTIFY_FILEEXTRACTED: 92 pFilePaths = (FILEPATHS_W*)Param1; 93 WINE_TRACE("Extracted %s\n", wine_dbgstr_w(pFilePaths->Target)); 94 return NO_ERROR; 95 } 96 return NO_ERROR; 97 } 98 99 static void extract(LPCWSTR cabfile, LPWSTR destdir) 100 { 101 if (!SetupIterateCabinetW(cabfile, 0, ExtCabCallback, destdir)) 102 WINE_ERR("Could not extract cab file %s\n", wine_dbgstr_w(cabfile)); 103 } 104 105 static void copy_file(LPCWSTR source, LPCWSTR destination) 106 { 107 WCHAR destfile[MAX_PATH]; 108 109 /* append source filename if destination is a directory */ 110 if (PathIsDirectoryW(destination)) 111 { 112 PathCombineW(destfile, destination, PathFindFileNameW(source)); 113 destination = destfile; 114 } 115 116 if (PathFileExistsW(destination) && !force_mode) 117 { 118 static const WCHAR overwriteMsg[] = {'O','v','e','r','w','r','i','t','e',' ','"','%','s','"','?',0}; 119 static const WCHAR titleMsg[] = {'E','x','t','r','a','c','t',0}; 120 WCHAR msg[MAX_PATH+100]; 121 snprintfW(msg, ARRAY_SIZE(msg), overwriteMsg, destination); 122 if (MessageBoxW(NULL, msg, titleMsg, MB_YESNO | MB_ICONWARNING) != IDYES) 123 return; 124 } 125 126 WINE_TRACE("copying %s to %s\n", wine_dbgstr_w(source), wine_dbgstr_w(destination)); 127 CopyFileW(source, destination, FALSE); 128 } 129 130 static LPWSTR *get_extrac_args(LPWSTR cmdline, int *pargc) 131 { 132 enum {OUTSIDE_ARG, INSIDE_ARG, INSIDE_QUOTED_ARG} state; 133 LPWSTR str; 134 int argc; 135 LPWSTR *argv; 136 int max_argc = 16; 137 BOOL new_arg; 138 139 WINE_TRACE("cmdline: %s\n", wine_dbgstr_w(cmdline)); 140 str = HeapAlloc(GetProcessHeap(), 0, (strlenW(cmdline) + 1) * sizeof(WCHAR)); 141 if(!str) return NULL; 142 strcpyW(str, cmdline); 143 argv = HeapAlloc(GetProcessHeap(), 0, (max_argc + 1) * sizeof(LPWSTR)); 144 if(!argv) 145 { 146 HeapFree(GetProcessHeap(), 0, str); 147 return NULL; 148 } 149 150 /* Split command line to separate arg-strings and fill argv */ 151 state = OUTSIDE_ARG; 152 argc = 0; 153 while(*str) 154 { 155 new_arg = FALSE; 156 /* Check character */ 157 if(isspaceW(*str)) /* white space */ 158 { 159 if(state == INSIDE_ARG) 160 { 161 state = OUTSIDE_ARG; 162 *str = 0; 163 } 164 } 165 else if(*str == '"') /* double quote */ 166 switch(state) 167 { 168 case INSIDE_QUOTED_ARG: 169 state = OUTSIDE_ARG; 170 *str = 0; 171 break; 172 case INSIDE_ARG: 173 *str = 0; 174 /* Fall through */ 175 case OUTSIDE_ARG: 176 if(!*++str) continue; 177 state = INSIDE_QUOTED_ARG; 178 new_arg = TRUE; 179 break; 180 } 181 else /* regular character */ 182 if(state == OUTSIDE_ARG) 183 { 184 state = INSIDE_ARG; 185 new_arg = TRUE; 186 } 187 188 /* Add new argv entry, if need */ 189 if(new_arg) 190 { 191 if(argc >= max_argc - 1) 192 { 193 /* Realloc argv here because there always should be 194 at least one reserved cell for terminating NULL */ 195 max_argc *= 2; 196 argv = HeapReAlloc(GetProcessHeap(), 0, argv, 197 (max_argc + 1) * sizeof(LPWSTR)); 198 if(!argv) 199 { 200 HeapFree(GetProcessHeap(), 0, str); 201 return NULL; 202 } 203 } 204 argv[argc++] = str; 205 } 206 207 str++; 208 } 209 210 argv[argc] = NULL; 211 *pargc = argc; 212 213 if(TRACE_ON(extrac32)) 214 { 215 int i; 216 for(i = 0; i < argc; i++) 217 WINE_TRACE("arg %d: %s\n", i, wine_dbgstr_w(argv[i])); 218 } 219 return argv; 220 } 221 222 int PASCAL wWinMain(HINSTANCE hInstance, HINSTANCE prev, LPWSTR cmdline, int show) 223 { 224 LPWSTR *argv; 225 int argc; 226 int i; 227 WCHAR check, cmd = 0; 228 WCHAR path[MAX_PATH]; 229 LPCWSTR cabfile = NULL; 230 231 path[0] = 0; 232 233 /* Do not use CommandLineToArgvW() or __wgetmainargs() to parse 234 * command line for this program. It should treat each quote as argument 235 * delimiter. This doesn't match with behavior of mentioned functions. 236 * Do not use args provided by wmain() for the same reason. 237 */ 238 argv = get_extrac_args(cmdline, &argc); 239 240 if(!argv) 241 { 242 WINE_ERR("Command line parsing failed\n"); 243 return 0; 244 } 245 246 /* Parse arguments */ 247 for(i = 0; i < argc; i++) 248 { 249 /* Get cabfile */ 250 if (argv[i][0] != '/' && argv[i][0] != '-') 251 { 252 if (!cabfile) 253 { 254 cabfile = argv[i]; 255 continue; 256 } else 257 break; 258 } 259 /* Get parameters for commands */ 260 check = toupperW( argv[i][1] ); 261 switch(check) 262 { 263 case 'A': 264 WINE_FIXME("/A not implemented\n"); 265 break; 266 case 'Y': 267 force_mode = TRUE; 268 break; 269 case 'L': 270 if ((i + 1) >= argc) return 0; 271 if (!GetFullPathNameW(argv[++i], MAX_PATH, path, NULL)) 272 return 0; 273 break; 274 case 'C': 275 case 'E': 276 case 'D': 277 if (cmd) return 0; 278 cmd = check; 279 break; 280 default: 281 return 0; 282 } 283 } 284 285 if (!cabfile) 286 return 0; 287 288 if (cmd == 'C') 289 { 290 if ((i + 1) != argc) return 0; 291 if (!GetFullPathNameW(argv[i], MAX_PATH, path, NULL)) 292 return 0; 293 } 294 else if (!cmd) 295 /* Use extraction by default if names of required files presents */ 296 cmd = i < argc ? 'E' : 'D'; 297 298 if (cmd == 'E' && !path[0]) 299 GetCurrentDirectoryW(MAX_PATH, path); 300 301 PathAddBackslashW(path); 302 303 /* Execute the specified command */ 304 switch(cmd) 305 { 306 case 'C': 307 /* Copy file */ 308 copy_file(cabfile, path); 309 break; 310 case 'D': 311 /* Display CAB archive */ 312 show_content = TRUE; 313 /* Fall through */ 314 case 'E': 315 /* Extract CAB archive */ 316 extract(cabfile, path); 317 break; 318 } 319 return 0; 320 } 321