xcopy: Avoid a potential out of bounds access.
[wine/multimedia.git] / programs / cabarc / cabarc.c
blob04f2a9face39c91e5c80cc588082e505b3b9d019
1 /*
2 * Tool to manipulate cabinet files
4 * Copyright 2011 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include "config.h"
22 #include "wine/port.h"
24 #include <stdio.h>
25 #include <stdlib.h>
27 #define WIN32_LEAN_AND_MEAN
28 #include "windows.h"
29 #include "fci.h"
30 #include "fdi.h"
32 #include "wine/unicode.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
37 /* from msvcrt */
38 #ifndef _O_RDONLY
39 #define _O_RDONLY 0
40 #define _O_WRONLY 1
41 #define _O_RDWR 2
42 #define _O_APPEND 0x0008
43 #define _O_RANDOM 0x0010
44 #define _O_SEQUENTIAL 0x0020
45 #define _O_TEMPORARY 0x0040
46 #define _O_NOINHERIT 0x0080
47 #define _O_CREAT 0x0100
48 #define _O_TRUNC 0x0200
49 #define _O_EXCL 0x0400
50 #define _O_SHORT_LIVED 0x1000
51 #define _O_TEXT 0x4000
52 #define _O_BINARY 0x8000
53 #endif
55 #ifndef _O_ACCMODE
56 #define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
57 #endif
59 #ifndef _SH_COMPAT
60 #define _SH_COMPAT 0x00
61 #define _SH_DENYRW 0x10
62 #define _SH_DENYWR 0x20
63 #define _SH_DENYRD 0x30
64 #define _SH_DENYNO 0x40
65 #endif
67 #ifndef _A_RDONLY
68 #define _A_RDONLY 0x01
69 #define _A_HIDDEN 0x02
70 #define _A_SYSTEM 0x04
71 #define _A_ARCH 0x20
72 #endif
74 /* command-line options */
75 static int opt_cabinet_size = CB_MAX_DISK;
76 static int opt_cabinet_id;
77 static int opt_compression = tcompTYPE_MSZIP;
78 static BOOL opt_recurse;
79 static BOOL opt_preserve_paths;
80 static int opt_reserve_space;
81 static int opt_verbose;
82 static char *opt_cab_file;
83 static WCHAR *opt_dest_dir;
84 static WCHAR **opt_files;
86 static void * CDECL cab_alloc( ULONG size )
88 return HeapAlloc( GetProcessHeap(), 0, size );
91 static void CDECL cab_free( void *ptr )
93 HeapFree( GetProcessHeap(), 0, ptr );
96 static WCHAR *strdupAtoW( UINT cp, const char *str )
98 WCHAR *ret = NULL;
99 if (str)
101 DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
102 if ((ret = cab_alloc( len * sizeof(WCHAR) )))
103 MultiByteToWideChar( cp, 0, str, -1, ret, len );
105 return ret;
108 static char *strdupWtoA( UINT cp, const WCHAR *str )
110 char *ret = NULL;
111 if (str)
113 DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
114 if ((ret = cab_alloc( len )))
115 WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
117 return ret;
120 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
121 static BOOL format_cab_name( char *dest, int id, const char *name )
123 const char *num = strchr( name, '*' );
124 int len;
126 if (!num)
128 if (id == 1)
130 strcpy( dest, name );
131 return TRUE;
133 WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
134 return FALSE;
136 len = num - name;
137 memcpy( dest, name, len );
138 len += sprintf( dest + len, "%u", id );
139 lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
140 return TRUE;
143 static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
145 if (!continuation && opt_verbose) printf( "adding %s\n", file );
146 return 0;
149 static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
151 DWORD creation = 0, sharing = 0;
152 int ioflag = 0;
153 HANDLE handle;
155 switch (oflag & _O_ACCMODE)
157 case _O_RDONLY: ioflag |= GENERIC_READ; break;
158 case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
159 case _O_RDWR: ioflag |= GENERIC_READ | GENERIC_WRITE; break;
162 if (oflag & _O_CREAT)
164 if (oflag & _O_EXCL) creation = CREATE_NEW;
165 else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
166 else creation = OPEN_ALWAYS;
168 else
170 if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
171 else creation = OPEN_EXISTING;
174 switch (pmode & 0x70)
176 case _SH_DENYRW: sharing = 0; break;
177 case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
178 case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
179 default: sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
182 handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
183 if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
184 return (INT_PTR)handle;
187 static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
189 DWORD num_read;
191 if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
193 *err = GetLastError();
194 return -1;
196 return num_read;
199 static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
201 DWORD written;
203 if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
205 *err = GetLastError();
206 return -1;
208 return written;
211 static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
213 if (!CloseHandle( (HANDLE)hf ))
215 *err = GetLastError();
216 return -1;
218 return 0;
221 static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
223 DWORD ret;
225 ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
226 if (ret == INVALID_SET_FILE_POINTER && GetLastError())
228 *err = GetLastError();
229 return -1;
231 return ret;
234 static int CDECL fci_delete( char *file, int *err, void *ptr )
236 if (!DeleteFileA( file ))
238 *err = GetLastError();
239 return -1;
241 return 0;
244 static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
246 char path[MAX_PATH];
248 if (!GetTempPathA( MAX_PATH, path )) return FALSE;
249 if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
250 DeleteFileA( name );
251 return TRUE;
254 static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
256 return format_cab_name( cab->szCab, cab->iCab + 1, opt_cab_file );
259 static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
261 return 0;
264 static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
265 USHORT *attribs, int *err, void *ptr )
267 HANDLE handle;
268 BY_HANDLE_FILE_INFORMATION info;
269 WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
271 handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
272 NULL, OPEN_EXISTING, 0, NULL );
273 if (handle == INVALID_HANDLE_VALUE)
275 *err = GetLastError();
276 WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
277 cab_free( nameW );
278 return -1;
280 if (!GetFileInformationByHandle( handle, &info ))
282 *err = GetLastError();
283 CloseHandle( handle );
284 cab_free( nameW );
285 return -1;
287 FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
288 *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
289 for (p = nameW; *p; p++) if (*p >= 0x80) break;
290 if (*p) *attribs |= _A_NAME_IS_UTF;
291 cab_free( nameW );
292 return (INT_PTR)handle;
295 static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
297 int err;
298 return fci_open( file, oflag, pmode, &err, NULL );
301 static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
303 int err;
304 return fci_read( hf, pv, cb, &err, NULL );
307 static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
309 int err;
310 return fci_write( hf, pv, cb, &err, NULL );
313 static int CDECL fdi_close( INT_PTR hf )
315 int err;
316 return fci_close( hf, &err, NULL );
319 static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
321 int err;
322 return fci_lseek( hf, dist, whence, &err, NULL );
326 /* create directories leading to a given file */
327 static void create_directories( const WCHAR *name )
329 WCHAR *path, *p;
331 /* create the directory/directories */
332 path = cab_alloc( (strlenW(name) + 1) * sizeof(WCHAR) );
333 strcpyW(path, name);
335 p = strchrW(path, '\\');
336 while (p != NULL)
338 *p = 0;
339 if (!CreateDirectoryW( path, NULL ))
340 WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path), GetLastError());
341 *p = '\\';
342 p = strchrW(p+1, '\\');
344 cab_free( path );
347 /* check if file name matches against one of the files specification */
348 static BOOL match_files( const WCHAR *name )
350 int i;
352 if (!*opt_files) return TRUE;
353 for (i = 0; opt_files[i]; i++)
355 unsigned int len = strlenW( opt_files[i] );
356 /* FIXME: do smarter matching, and wildcards */
357 if (!len) continue;
358 if (strncmpiW( name, opt_files[i], len )) continue;
359 if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
361 return FALSE;
364 static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
366 WCHAR *nameW;
368 switch (fdint)
370 case fdintCABINET_INFO:
371 return 0;
372 case fdintCOPY_FILE:
373 nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
374 if (match_files( nameW ))
376 char *nameU = strdupWtoA( CP_UNIXCP, nameW );
377 if (opt_verbose)
379 char attrs[] = "rxash";
380 if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
381 if (!(pfdin->attribs & _A_EXEC)) attrs[1] = '-';
382 if (!(pfdin->attribs & _A_ARCH)) attrs[2] = '-';
383 if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
384 if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
385 printf( " %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs, pfdin->cb,
386 (pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
387 pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
389 printf( "%s\n", nameU );
390 cab_free( nameU );
392 cab_free( nameW );
393 return 0;
394 default:
395 WINE_FIXME( "Unexpected notification type %d.\n", fdint );
396 return 0;
400 static int list_cabinet( char *cab_dir )
402 ERF erf;
403 int ret = 0;
404 HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
405 fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
407 if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
408 FDIDestroy( fdi );
409 return ret;
412 static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
414 WCHAR *file, *nameW, *path = NULL;
415 INT_PTR ret;
417 switch (fdint)
419 case fdintCABINET_INFO:
420 return 0;
422 case fdintCOPY_FILE:
423 nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
424 if (opt_preserve_paths)
426 file = nameW;
427 while (*file == '\\') file++; /* remove leading backslashes */
429 else
431 if ((file = strrchrW( nameW, '\\' ))) file++;
432 else file = nameW;
435 if (opt_dest_dir)
437 path = cab_alloc( (strlenW(opt_dest_dir) + strlenW(file) + 1) * sizeof(WCHAR) );
438 strcpyW( path, opt_dest_dir );
439 strcatW( path, file );
441 else path = file;
443 if (match_files( file ))
445 if (opt_verbose)
447 char *nameU = strdupWtoA( CP_UNIXCP, path );
448 printf( "extracting %s\n", nameU );
449 cab_free( nameU );
451 create_directories( path );
452 /* FIXME: check for existing file and overwrite mode */
453 ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
454 NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
456 else ret = 0;
458 cab_free( nameW );
459 if (path != file) cab_free( path );
460 return ret;
462 case fdintCLOSE_FILE_INFO:
463 CloseHandle( (HANDLE)pfdin->hf );
464 return 0;
466 case fdintNEXT_CABINET:
467 WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin->fdie, pfdin->psz3, pfdin->psz1);
468 return pfdin->fdie == FDIERROR_NONE ? 0 : -1;
470 case fdintENUMERATE:
471 return 0;
473 default:
474 WINE_FIXME( "Unexpected notification type %d.\n", fdint );
475 return 0;
479 static int extract_cabinet( char *cab_dir )
481 ERF erf;
482 int ret = 0;
483 HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
484 fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
486 if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL ))
488 ret = GetLastError();
489 WINE_WARN("FDICopy() failed: code %u\n", ret);
491 FDIDestroy( fdi );
492 return ret;
495 static BOOL add_file( HFCI fci, WCHAR *name )
497 BOOL ret;
498 char *filename, *path = strdupWtoA( CP_UTF8, name );
500 if (!opt_preserve_paths)
502 if ((filename = strrchr( path, '\\' ))) filename++;
503 else filename = path;
505 else
507 filename = path;
508 while (*filename == '\\') filename++; /* remove leading backslashes */
510 ret = FCIAddFile( fci, path, filename, FALSE,
511 fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
512 cab_free( path );
513 return ret;
516 static BOOL add_directory( HFCI fci, WCHAR *dir )
518 static const WCHAR wildcardW[] = {'*',0};
519 WCHAR *p, *buffer;
520 HANDLE handle;
521 WIN32_FIND_DATAW data;
522 BOOL ret = TRUE;
524 if (!(buffer = cab_alloc( (strlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
525 strcpyW( buffer, dir );
526 p = buffer + strlenW( buffer );
527 if (p > buffer && p[-1] != '\\') *p++ = '\\';
528 strcpyW( p, wildcardW );
530 if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
534 if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
535 if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
536 if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
538 strcpyW( p, data.cFileName );
539 if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
540 ret = add_directory( fci, buffer );
541 else
542 ret = add_file( fci, buffer );
543 if (!ret) break;
544 } while (FindNextFileW( handle, &data ));
545 FindClose( handle );
547 cab_free( buffer );
548 return TRUE;
551 static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
553 DWORD attr = GetFileAttributesW( name );
555 if (attr == INVALID_FILE_ATTRIBUTES)
557 WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
558 return FALSE;
560 if (attr & FILE_ATTRIBUTE_DIRECTORY)
562 if (opt_recurse) return add_directory( fci, name );
563 WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
564 wine_dbgstr_w(name) );
565 return FALSE;
567 return add_file( fci, name );
570 static int new_cabinet( char *cab_dir )
572 static const WCHAR plusW[] = {'+',0};
573 WCHAR **file;
574 ERF erf;
575 BOOL ret = FALSE;
576 HFCI fci;
577 CCAB cab;
579 cab.cb = opt_cabinet_size;
580 cab.cbFolderThresh = CB_MAX_DISK;
581 cab.cbReserveCFHeader = opt_reserve_space;
582 cab.cbReserveCFFolder = 0;
583 cab.cbReserveCFData = 0;
584 cab.iCab = 0;
585 cab.iDisk = 0;
586 cab.setID = opt_cabinet_id;
587 cab.szDisk[0] = 0;
589 strcpy( cab.szCabPath, cab_dir );
590 strcat( cab.szCabPath, "\\" );
591 format_cab_name( cab.szCab, 1, opt_cab_file );
593 fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
594 fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
596 for (file = opt_files; *file; file++)
598 if (!strcmpW( *file, plusW ))
599 FCIFlushFolder( fci, fci_get_next_cab, fci_status );
600 else
601 if (!(ret = add_file_or_directory( fci, *file ))) break;
604 if (ret)
606 if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
607 WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
609 FCIDestroy( fci );
610 return !ret;
613 static void usage( void )
615 WINE_MESSAGE(
616 "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
617 "\nCommands:\n"
618 " L List the contents of the cabinet\n"
619 " N Create a new cabinet\n"
620 " X Extract files from the cabinet into dest_dir\n"
621 "\nOptions:\n"
622 " -d size Set maximum disk size\n"
623 " -h Display this help\n"
624 " -i id Set cabinet id\n"
625 " -m type Set compression type (mszip|none)\n"
626 " -p Preserve directory names\n"
627 " -r Recurse into directories\n"
628 " -s size Reserve space in the cabinet header\n"
629 " -v More verbose output\n" );
632 int wmain( int argc, WCHAR *argv[] )
634 static const WCHAR noneW[] = {'n','o','n','e',0};
635 static const WCHAR mszipW[] = {'m','s','z','i','p',0};
637 WCHAR *p, *command;
638 char buffer[MAX_PATH];
639 char filename[MAX_PATH];
640 char *cab_file, *file_part;
641 int i;
643 while (argv[1] && argv[1][0] == '-')
645 switch (argv[1][1])
647 case 'd':
648 argv++; argc--;
649 opt_cabinet_size = atoiW( argv[1] );
650 if (opt_cabinet_size < 50000)
652 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
653 return 1;
655 break;
656 case 'h':
657 usage();
658 return 0;
659 case 'i':
660 argv++; argc--;
661 opt_cabinet_id = atoiW( argv[1] );
662 break;
663 case 'm':
664 argv++; argc--;
665 if (!strcmpiW( argv[1], noneW )) opt_compression = tcompTYPE_NONE;
666 else if (!strcmpiW( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP;
667 else
669 char *arg = strdupWtoA( CP_ACP, argv[1] );
670 WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", arg);
671 return 1;
673 break;
674 case 'p':
675 opt_preserve_paths = TRUE;
676 break;
677 case 'r':
678 opt_recurse = TRUE;
679 break;
680 case 's':
681 argv++; argc--;
682 opt_reserve_space = atoiW( argv[1] );
683 break;
684 case 'v':
685 opt_verbose++;
686 break;
687 default:
688 usage();
689 return 1;
691 argv++; argc--;
694 command = argv[1];
695 if (argc < 3 || !command[0] || command[1])
697 usage();
698 return 1;
700 cab_file = strdupWtoA( CP_ACP, argv[2] );
701 argv += 2;
702 argc -= 2;
704 if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
706 WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
707 return 1;
709 strcpy(filename, file_part);
710 file_part[0] = 0;
712 /* map slash to backslash in all file arguments */
713 for (i = 1; i < argc; i++)
714 for (p = argv[i]; *p; p++)
715 if (*p == '/') *p = '\\';
716 opt_files = argv + 1;
717 opt_cab_file = filename;
719 switch (*command)
721 case 'l':
722 case 'L':
723 return list_cabinet( buffer );
724 case 'n':
725 case 'N':
726 return new_cabinet( buffer );
727 case 'x':
728 case 'X':
729 if (argc > 1) /* check for destination dir as last argument */
731 WCHAR *last = argv[argc - 1];
732 if (last[0] && last[strlenW(last) - 1] == '\\')
734 opt_dest_dir = last;
735 argv[--argc] = NULL;
738 WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file));
739 return extract_cabinet( buffer );
740 default:
741 usage();
742 return 1;