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, 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 }
321