1/*
2
3  ZipArchive.m
4  Zipper
5
6  Copyright (C) 2012 Free Software Foundation, Inc
7
8  Authors: Dirk Olmes <dirk@xanthippe.ping.de>
9           Riccardo Mottola <rm@gnu.org>
10
11  This application is free software; you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by the Free
13  Software Foundation; either version 2 of the License, or (at your option)
14  any later version.
15
16  This program is distributed in the hope that it will be useful, but
17  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18  or FITNESS FOR A PARTICULAR PURPOSE.
19  See the GNU General Public License for more details
20
21 */
22
23#import <Foundation/Foundation.h>
24#import "ZipArchive.h"
25#import "FileInfo.h"
26#import "NSString+Custom.h"
27#import "Preferences.h"
28#import "NSArray+Custom.h"
29
30// if the output contains this string in the first line, we have to use a different
31// parsing routine
32#define MINI_UNZIP_IDENTIFIER @"MiniUnz"
33
34static NSData *_magicBytes = nil;
35
36@interface ZipArchive (PrivateAPI)
37- (NSData *)dataByRunningUnzip;
38- (NSArray *)listUnzipContents:(NSArray *)lines;
39@end
40
41@implementation ZipArchive : Archive
42
43/**
44 * register our supported file extensions with superclass.
45 */
46+ (void)initialize
47{
48	// zip files start with 'P K 0x003 0x004'
49	char zipBytes[] = { 'P', 'K', 0x003, 0x004 };
50	_magicBytes = [[NSData dataWithBytes:zipBytes length:4] retain];
51
52	[self registerFileExtension:@"zip" forArchiveClass:self];
53	[self registerFileExtension:@"jar" forArchiveClass:self];
54}
55
56+ (NSString *)archiveExecutable
57{
58	return [Preferences zipExecutable];
59}
60+ (NSString *)unarchiveExecutable
61{
62	return [Preferences unzipExecutable];
63}
64
65
66+ (BOOL)hasRatio;
67{
68	// unzip does provide info about the compression ratio
69	return YES;
70}
71
72+ (ArchiveType)archiveType
73{
74	return ZIP;
75}
76
77+ (NSData *)magicBytes
78{
79	return _magicBytes;
80}
81
82//------------------------------------------------------------------------------
83// expanding the archive
84//------------------------------------------------------------------------------
85- (int)expandFiles:(NSArray *)files withPathInfo:(BOOL)usePathInfo toPath:(NSString *)path
86{
87	FileInfo *fileInfo;
88	NSMutableArray *args;
89
90	args = [NSMutableArray array];
91	// be really quiet
92	[args addObject:@"-qq"];
93	// overwrite without warning
94	[args addObject:@"-o"];
95	if (usePathInfo == NO)
96	{
97		// junk paths
98		[args addObject:@"-j"];
99	}
100
101	// destination dir
102	[args addObject:@"-d"];
103	[args addObject:path];
104
105	// protect against archives and files starting with -
106	[args addObject:@"--"];
107
108	[args addObject:[self path]];
109
110	if (files != nil)
111	{
112		NSEnumerator *cursor = [files objectEnumerator];
113		while ((fileInfo = [cursor nextObject]) != nil)
114		{
115			[args addObject:[fileInfo fullPath]];
116		}
117	}
118
119	return [self runUnarchiverWithArguments:args];
120}
121
122- (NSArray *)listContents
123{
124    NSData *data = [self dataByRunningUnzip];
125    NSString *string = [[[NSString alloc] initWithData:data
126        encoding:NSASCIIStringEncoding] autorelease];
127    NSArray *lines = [string componentsSeparatedByString:@"\n"];
128
129    if ([[lines objectAtIndex:0] containsString:MINI_UNZIP_IDENTIFIER])
130    {
131		// take out the first 6 lines (header)
132		lines = [lines subarrayWithRange:NSMakeRange(6, [lines count] - 6)];
133    }
134
135    return [self listUnzipContents:lines];
136}
137
138- (NSArray *)listUnzipContents:(NSArray *)lines
139{
140  NSEnumerator *cursor;
141  NSString *line;
142  NSMutableArray *results = [NSMutableArray array];
143
144  cursor = [lines objectEnumerator];
145  while ((line = [cursor nextObject]) != nil)
146    {
147      int length, index;
148      NSString *path, *date, *time, *ratio, *checksum;
149      NSCalendarDate *calendarDate;
150      NSArray *components;
151
152      if (line == nil || [line length] == 0)
153	continue;
154
155      components = [line componentsSeparatedByString:@" "];
156      components = [components arrayByRemovingEmptyStrings];
157
158      length = [[components objectAtIndex:0] intValue];
159      ratio = [components objectAtIndex:3];
160
161      // extract the path. The checksum is the last token before the full path
162      // (which can contain blanks)
163      checksum = [components objectAtIndex:6];
164      index = [line rangeOfString:checksum].location;
165      index += [checksum length];
166      path = [[line substringFromIndex:index] stringByRemovingWhitespaceFromBeginning];
167
168      date = [components objectAtIndex:4];
169      time = [components objectAtIndex:5];
170      date = [NSString stringWithFormat:@"%@ %@", date, time];
171      calendarDate = [NSCalendarDate dateWithString:date calendarFormat:@"%m-%d-%Y %H:%M"];
172
173      // we skip plain directory entries
174      if ([path hasSuffix:@"/"] == NO)
175	{
176	  FileInfo *info;
177
178	  info = [FileInfo newWithPath:path date:calendarDate
179				  size:[NSNumber numberWithInt:length] ratio:ratio];
180	  if (info)
181	    [results addObject:info];
182	  [info release];
183	}
184    }
185  return results;
186}
187
188//------------------------------------------------------------------------------
189// creating archives
190//------------------------------------------------------------------------------
191+ (void)createArchive:(NSString *)archivePath withFiles:(NSArray *)filenames archiveType: (ArchiveType) archiveType
192{
193        NSEnumerator *filenameCursor;
194        NSString *filename;
195        NSString *workdir;
196        NSMutableArray *arguments;
197
198        // make sure archivePath has the correct suffix
199        if ([archivePath hasSuffix:@".zip"] == NO)
200          {
201            archivePath = [archivePath stringByAppendingString:@".zip"];
202          }
203        // build arguments for commandline: zip -r filename <list of files>
204        arguments = [NSMutableArray array];
205	[arguments addObject:@"-r"];
206        [arguments addObject:archivePath];
207
208        // filenames contains absolute paths, convert them to relative paths. This works
209        // because you can select only files/directories below a current directory in
210        // GWorkspace so all the files *have* to have a common filesystem root.
211        filenameCursor = [filenames objectEnumerator];
212        while ((filename = [filenameCursor nextObject]) != nil)
213        {
214                [arguments addObject:[filename lastPathComponent]];
215        }
216
217        // change into this directory when running the task
218        workdir = [[filenames objectAtIndex:0] stringByDeletingLastPathComponent];
219
220        [self runArchiverWithArguments:arguments inDirectory:workdir];
221}
222
223//------------------------------------------------------------------------------
224// private API
225//------------------------------------------------------------------------------
226- (NSData *)dataByRunningUnzip
227{
228	// l = list
229	// v = display all zip infos (Ratio etc.)
230	// qq = quiet, this is important for skipping comments in archives and for skipping
231	//      the nice headers for readable output
232	NSArray *args = [NSArray arrayWithObjects:@"-lvqq", @"--", [self path], nil];
233	return [self dataByRunningUnachiverWithArguments:args];
234}
235
236@end
237