Sync with trunk r63502.
[reactos.git] / base / applications / extrac32 / extrac32.c
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 #define WIN32_LEAN_AND_MEAN
23 #include <windows.h>
24 #include <shellapi.h>
25 #include <setupapi.h>
26 #include <shlwapi.h>
27 #include <shlobj.h>
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, sizeof(msg)/sizeof(msg[0]), 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 }