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