ADDED utils/ofzip/Archive.h Index: utils/ofzip/Archive.h ================================================================== --- utils/ofzip/Archive.h +++ utils/ofzip/Archive.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" +#import "OFFile.h" +#import "OFArray.h" + +@protocol Archive ++ (instancetype)archiveWithFile: (OFFile*)file; +- initWithFile: (OFFile*)file; +- (void)listFiles; +- (void)extractFiles: (OFArray OF_GENERIC(OFString*)*)files; +@end Index: utils/ofzip/Makefile ================================================================== --- utils/ofzip/Makefile +++ utils/ofzip/Makefile @@ -1,13 +1,14 @@ include ../../extra.mk PROG = ofzip${PROG_SUFFIX} -SRCS = OFZIP.m +SRCS = OFZIP.m \ + ZIPArchive.m include ../../buildsys.mk ${PROG}: ${LIBOBJFW_DEP_LVL2} CPPFLAGS += -I../../src -I../../src/runtime -I../../src/exceptions -I../.. LIBS := -L../../src -lobjfw ${LIBS} LD = ${OBJC} LDFLAGS += ${LDFLAGS_RPATH} ADDED utils/ofzip/OFZIP.h Index: utils/ofzip/OFZIP.h ================================================================== --- utils/ofzip/OFZIP.h +++ utils/ofzip/OFZIP.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" + +#import "Archive.h" + +@interface OFZIP: OFObject +{ + int8_t _override; +@public + int8_t _outputLevel; + int _exitStatus; +} + +- (id )openArchiveWithPath: (OFString*)path; +- (bool)shouldExtractFile: (OFString*)fileName + outFileName: (OFString*)outFileName; +- (ssize_t)copyBlockFromStream: (OFStream*)input + toStream: (OFStream*)output + fileName: (OFString*)fileName; +@end Index: utils/ofzip/OFZIP.m ================================================================== --- utils/ofzip/OFZIP.m +++ utils/ofzip/OFZIP.m @@ -18,46 +18,26 @@ #include #import "OFApplication.h" #import "OFArray.h" -#import "OFDate.h" #import "OFFile.h" #import "OFFileManager.h" #import "OFOptionsParser.h" -#import "OFSet.h" #import "OFStdIOStream.h" -#import "OFZIPArchive.h" -#import "OFZIPArchiveEntry.h" + +#import "OFZIP.h" +#import "ZIPArchive.h" #import "OFCreateDirectoryFailedException.h" #import "OFInvalidFormatException.h" #import "OFOpenItemFailedException.h" #import "OFReadFailedException.h" #import "OFWriteFailedException.h" #define BUFFER_SIZE 4096 -#ifndef S_IRWXG -# define S_IRWXG 0 -#endif -#ifndef S_IRWXO -# define S_IRWXO 0 -#endif - -@interface OFZIP: OFObject -{ - int8_t _override, _outputLevel; - int _exitStatus; -} - -- (OFZIPArchive*)openArchiveWithPath: (OFString*)path; -- (void)listFilesInArchive: (OFZIPArchive*)archive; -- (void)extractFiles: (OFArray OF_GENERIC(OFString*)*)files - fromArchive: (OFZIPArchive*)archive; -@end - OF_APPLICATION_DELEGATE(OFZIP) static void help(OFStream *stream, bool full, int status) { @@ -88,28 +68,10 @@ @"Error: -%C / --%@ and -%C / --%@ are mutually exclusive!\n", shortOption1, longOption1, shortOption2, longOption2]; [OFApplication terminateWithStatus: 1]; } -static void -setPermissions(OFString *path, OFZIPArchiveEntry *entry) -{ -#ifdef OF_HAVE_CHMOD - if (([entry versionMadeBy] >> 8) == - OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { - uint32_t mode = [entry versionSpecificAttributes] >> 16; - - /* Only allow modes that are safe */ - mode &= (S_IRWXU | S_IRWXG | S_IRWXO); - - [[OFFileManager defaultManager] - changePermissionsOfItemAtPath: path - permissions: mode]; - } -#endif -} - @implementation OFZIP - (void)applicationDidFinishLaunching { const of_options_parser_option_t options[] = { { 'f', @"force", 0, NULL, NULL }, @@ -123,11 +85,11 @@ }; OFOptionsParser *optionsParser = [OFOptionsParser parserWithOptions: options]; of_unichar_t option, mode = '\0'; OFArray OF_GENERIC(OFString*) *remainingArguments, *files; - OFZIPArchive *archive; + id archive; while ((option = [optionsParser nextOption]) != '\0') { switch (option) { case 'f': if (_override < 0) @@ -191,33 +153,28 @@ [OFApplication terminateWithStatus: 1]; } } remainingArguments = [optionsParser remainingArguments]; + archive = [self openArchiveWithPath: [remainingArguments firstObject]]; switch (mode) { case 'l': if ([remainingArguments count] != 1) help(of_stderr, false, 1); - archive = [self openArchiveWithPath: - [remainingArguments firstObject]]; - - [self listFilesInArchive: archive]; + [archive listFiles]; break; case 'x': if ([remainingArguments count] < 1) help(of_stderr, false, 1); files = [remainingArguments objectsInRange: of_range(1, [remainingArguments count] - 1)]; - archive = [self openArchiveWithPath: - [remainingArguments firstObject]]; @try { - [self extractFiles: files - fromArchive: archive]; + [archive extractFiles: files]; } @catch (OFCreateDirectoryFailedException *e) { [of_stderr writeFormat: @"\rFailed to create directory %@: %s\n", [e path], strerror([e errNo])]; _exitStatus = 1; @@ -235,20 +192,26 @@ } [OFApplication terminateWithStatus: _exitStatus]; } -- (OFZIPArchive*)openArchiveWithPath: (OFString*)path +- (id )openArchiveWithPath: (OFString*)path { - OFZIPArchive *archive = nil; + OFFile *file; + id archive; @try { - archive = [OFZIPArchive archiveWithPath: path]; + file = [OFFile fileWithPath: path + mode: @"rb"]; } @catch (OFOpenItemFailedException *e) { [of_stderr writeFormat: @"Failed to open file %@: %s\n", [e path], strerror([e errNo])]; [OFApplication terminateWithStatus: 1]; + } + + @try { + archive = [ZIPArchive archiveWithFile: file]; } @catch (OFReadFailedException *e) { [of_stderr writeFormat: @"Failed to read file %@: %s\n", path, strerror([e errNo])]; [OFApplication terminateWithStatus: 1]; } @catch (OFInvalidFormatException *e) { @@ -258,229 +221,79 @@ } return archive; } -- (void)listFilesInArchive: (OFZIPArchive*)archive -{ - for (OFZIPArchiveEntry *entry in [archive entries]) { - void *pool = objc_autoreleasePoolPush(); - - [of_stdout writeLine: [entry fileName]]; - - if (_outputLevel >= 1) { - OFString *date = [[entry modificationDate] - localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; - - [of_stdout writeFormat: - @"\tCompressed: %" PRIu64 @" bytes\n" - @"\tUncompressed: %" PRIu64 @" bytes\n" - @"\tCRC32: %08X\n" - @"\tModification date: %@\n", - [entry compressedSize], [entry uncompressedSize], - [entry CRC32], date]; - - if (_outputLevel >= 2) { - uint16_t versionMadeBy = [entry versionMadeBy]; - - [of_stdout writeFormat: - @"\tVersion made by: %@\n" - @"\tMinimum version needed: %@\n", - of_zip_archive_entry_version_to_string( - versionMadeBy), - of_zip_archive_entry_version_to_string( - [entry minVersionNeeded])]; - - if ((versionMadeBy >> 8) == - OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { - uint32_t mode = [entry - versionSpecificAttributes] >> 16; - [of_stdout writeFormat: - @"\tMode: %06o\n", mode]; - } - } - - if (_outputLevel >= 3) - [of_stdout writeFormat: @"\tExtra field: %@\n", - [entry extraField]]; - - if ([[entry fileComment] length] > 0) - [of_stdout writeFormat: @"\tComment: %@\n", - [entry fileComment]]; - } - - objc_autoreleasePoolPop(pool); - } -} - -- (void)extractFiles: (OFArray OF_GENERIC(OFString*)*)files - fromArchive: (OFZIPArchive*)archive -{ - OFFileManager *fileManager = [OFFileManager defaultManager]; - bool all = ([files count] == 0); - OFMutableSet OF_GENERIC(OFString*) *missing = - [OFMutableSet setWithArray: files]; - - for (OFZIPArchiveEntry *entry in [archive entries]) { - void *pool = objc_autoreleasePoolPush(); - OFString *fileName = [entry fileName]; - OFString *outFileName = [fileName stringByStandardizingPath]; - OFArray OF_GENERIC(OFString*) *pathComponents; - OFString *directory; - OFStream *stream; - OFFile *output; - char buffer[BUFFER_SIZE]; - uint64_t written = 0, size = [entry uncompressedSize]; - int8_t percent = -1, newPercent; - - if (!all && ![files containsObject: fileName]) - continue; - - [missing removeObject: fileName]; - -#if !defined(OF_WINDOWS) && !defined(OF_MSDOS) - if ([outFileName hasPrefix: @"/"]) { -#else - if ([outFileName hasPrefix: @"/"] || - [outFileName containsString: @":"]) { -#endif - [of_stderr writeFormat: @"Refusing to extract %@!\n", - fileName]; - - _exitStatus = 1; - goto outer_loop_end; - } - - pathComponents = [outFileName pathComponents]; - for (OFString *component in pathComponents) { - if ([component isEqual: OF_PATH_PARENT_DIRECTORY]) { - [of_stderr writeFormat: - @"Refusing to extract %@!\n", fileName]; - - _exitStatus = 1; - goto outer_loop_end; - } - } - outFileName = [OFString pathWithComponents: pathComponents]; - - if (_outputLevel >= 0) - [of_stdout writeFormat: @"Extracting %@...", fileName]; - - if ([fileName hasSuffix: @"/"]) { - [fileManager createDirectoryAtPath: outFileName - createParents: true]; - setPermissions(outFileName, entry); - - if (_outputLevel >= 0) - [of_stdout writeLine: @" done"]; - - goto outer_loop_end; - } - - directory = [outFileName stringByDeletingLastPathComponent]; - if (![fileManager directoryExistsAtPath: directory]) - [fileManager createDirectoryAtPath: directory - createParents: true]; - - if (_override != 1 && - [fileManager fileExistsAtPath: outFileName]) { - OFString *line; - - if (_override == -1) { - if (_outputLevel >= 0) - [of_stdout writeLine: @" skipped"]; - - goto outer_loop_end; - } - - do { - [of_stderr writeFormat: - @"\rOverride %@? [ynAN?] ", fileName]; - - line = [of_stdin readLine]; - - if ([line isEqual: @"?"]) - [of_stderr writeString: @" y: yes\n" - @" n: no\n" - @" A: always\n" - @" N: never\n"]; - } while (![line isEqual: @"y"] && - ![line isEqual: @"n"] && ![line isEqual: @"N"] && - ![line isEqual: @"A"]); - - if ([line isEqual: @"A"]) - _override = 1; - else if ([line isEqual: @"N"]) - _override = -1; - - if ([line isEqual: @"n"] || [line isEqual: @"N"]) { - [of_stdout writeFormat: @"Skipping %@...\n", - fileName]; - - goto outer_loop_end; - } - - [of_stdout writeFormat: @"Extracting %@...", fileName]; - } - - stream = [archive streamForReadingFile: fileName]; - output = [OFFile fileWithPath: outFileName - mode: @"wb"]; - setPermissions(outFileName, entry); - - while (![stream isAtEndOfStream]) { - size_t length; - - @try { - length = [stream readIntoBuffer: buffer - length: BUFFER_SIZE]; - } @catch (OFReadFailedException *e) { - [of_stderr writeFormat: - @"\rFailed to read file %@: %s\n", - fileName, strerror([e errNo])]; - - _exitStatus = 1; - goto outer_loop_end; - } - - @try { - [output writeBuffer: buffer - length: length]; - } @catch (OFWriteFailedException *e) { - [of_stderr writeFormat: - @"\rFailed to write file %@: %s\n", - fileName, strerror([e errNo])]; - - _exitStatus = 1; - goto outer_loop_end; - } - - written += length; - newPercent = (written == size - ? 100 : (int8_t)(written * 100 / size)); - - if (_outputLevel >= 0 && percent != newPercent) { - percent = newPercent; - - [of_stdout writeFormat: - @"\rExtracting %@... %3u%%", - fileName, percent]; - } - } - - if (_outputLevel >= 0) - [of_stdout writeFormat: @"\rExtracting %@... done\n", - fileName]; - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - if ([missing count] > 0) { - for (OFString *file in missing) - [of_stderr writeFormat: - @"File %@ is not in the archive!\n", file]; - - _exitStatus = 1; - } +- (bool)shouldExtractFile: (OFString*)fileName + outFileName: (OFString*)outFileName +{ + OFString *line; + + if (_override == 1 || + ![[OFFileManager defaultManager] fileExistsAtPath: outFileName]) + return true; + + + if (_override == -1) { + if (_outputLevel >= 0) + [of_stdout writeLine: @" skipped"]; + return false; + } + + do { + [of_stderr writeFormat: @"\rOverride %@? [ynAN?] ", fileName]; + + line = [of_stdin readLine]; + + if ([line isEqual: @"?"]) + [of_stderr writeString: @" y: yes\n" + @" n: no\n" + @" A: always\n" + @" N: never\n"]; + } while (![line isEqual: @"y"] && ![line isEqual: @"n"] && + ![line isEqual: @"N"] && ![line isEqual: @"A"]); + + if ([line isEqual: @"A"]) + _override = 1; + else if ([line isEqual: @"N"]) + _override = -1; + + if ([line isEqual: @"n"] || [line isEqual: @"N"]) { + if (_outputLevel >= 0) + [of_stdout writeFormat: @"Skipping %@...\n", fileName]; + return false; + } + + if (_outputLevel >= 0) + [of_stdout writeFormat: @"Extracting %@...", fileName]; + + return true; +} + +- (ssize_t)copyBlockFromStream: (OFStream*)input + toStream: (OFStream*)output + fileName: (OFString*)fileName +{ + char buffer[BUFFER_SIZE]; + size_t length; + + @try { + length = [input readIntoBuffer: buffer + length: BUFFER_SIZE]; + } @catch (OFReadFailedException *e) { + [of_stderr writeFormat: @"\rFailed to read file %@: %s\n", + fileName, strerror([e errNo])]; + return -1; + } + + @try { + [output writeBuffer: buffer + length: length]; + } @catch (OFWriteFailedException *e) { + [of_stderr writeFormat: @"\rFailed to write file %@: %s\n", + fileName, strerror([e errNo])]; + return -1; + } + + return length; } @end ADDED utils/ofzip/ZIPArchive.h Index: utils/ofzip/ZIPArchive.h ================================================================== --- utils/ofzip/ZIPArchive.h +++ utils/ofzip/ZIPArchive.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFZIPArchive.h" + +#import "Archive.h" + +@interface ZIPArchive: OFObject +{ + OFZIPArchive *_archive; +} +@end ADDED utils/ofzip/ZIPArchive.m Index: utils/ofzip/ZIPArchive.m ================================================================== --- utils/ofzip/ZIPArchive.m +++ utils/ofzip/ZIPArchive.m @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFDate.h" +#import "OFSet.h" +#import "OFApplication.h" +#import "OFFileManager.h" +#import "OFStdIOStream.h" + +#import "ZIPArchive.h" +#import "OFZIP.h" + +#import "OFInvalidFormatException.h" + +#ifndef S_IRWXG +# define S_IRWXG 0 +#endif +#ifndef S_IRWXO +# define S_IRWXO 0 +#endif + +static OFZIP *app; + +static void +setPermissions(OFString *path, OFZIPArchiveEntry *entry) +{ +#ifdef OF_HAVE_CHMOD + if (([entry versionMadeBy] >> 8) == + OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { + uint32_t mode = [entry versionSpecificAttributes] >> 16; + + /* Only allow modes that are safe */ + mode &= (S_IRWXU | S_IRWXG | S_IRWXO); + + [[OFFileManager defaultManager] + changePermissionsOfItemAtPath: path + permissions: mode]; + } +#endif +} + +@implementation ZIPArchive ++ (void)initialize +{ + if (self == [ZIPArchive class]) + app = [[OFApplication sharedApplication] delegate]; +} + ++ (instancetype)archiveWithFile: (OFFile*)file +{ + return [[[self alloc] initWithFile: file] autorelease]; +} + +- initWithFile: (OFFile*)file +{ + self = [super init]; + + @try { + _archive = [OFZIPArchive archiveWithSeekableStream: file]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_archive release]; + + [super dealloc]; +} + +- (void)listFiles +{ + for (OFZIPArchiveEntry *entry in [_archive entries]) { + void *pool = objc_autoreleasePoolPush(); + + [of_stdout writeLine: [entry fileName]]; + + if (app->_outputLevel >= 1) { + OFString *date = [[entry modificationDate] + localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; + + [of_stdout writeFormat: + @"\tCompressed: %" PRIu64 @" bytes\n" + @"\tUncompressed: %" PRIu64 @" bytes\n" + @"\tCRC32: %08X\n" + @"\tModification date: %@\n", + [entry compressedSize], [entry uncompressedSize], + [entry CRC32], date]; + + if (app->_outputLevel >= 2) { + uint16_t versionMadeBy = [entry versionMadeBy]; + + [of_stdout writeFormat: + @"\tVersion made by: %@\n" + @"\tMinimum version needed: %@\n", + of_zip_archive_entry_version_to_string( + versionMadeBy), + of_zip_archive_entry_version_to_string( + [entry minVersionNeeded])]; + + if ((versionMadeBy >> 8) == + OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { + uint32_t mode = [entry + versionSpecificAttributes] >> 16; + [of_stdout writeFormat: + @"\tMode: %06o\n", mode]; + } + } + + if (app->_outputLevel >= 3) + [of_stdout writeFormat: @"\tExtra field: %@\n", + [entry extraField]]; + + if ([[entry fileComment] length] > 0) + [of_stdout writeFormat: @"\tComment: %@\n", + [entry fileComment]]; + } + + objc_autoreleasePoolPop(pool); + } +} + +- (void)extractFiles: (OFArray OF_GENERIC(OFString*)*)files +{ + OFFileManager *fileManager = [OFFileManager defaultManager]; + bool all = ([files count] == 0); + OFMutableSet OF_GENERIC(OFString*) *missing = + [OFMutableSet setWithArray: files]; + + for (OFZIPArchiveEntry *entry in [_archive entries]) { + void *pool = objc_autoreleasePoolPush(); + OFString *fileName = [entry fileName]; + OFString *outFileName = [fileName stringByStandardizingPath]; + OFArray OF_GENERIC(OFString*) *pathComponents; + OFString *directory; + OFStream *stream; + OFFile *output; + uint64_t written = 0, size = [entry uncompressedSize]; + int8_t percent = -1, newPercent; + + if (!all && ![files containsObject: fileName]) + continue; + + [missing removeObject: fileName]; + +#if !defined(OF_WINDOWS) && !defined(OF_MSDOS) + if ([outFileName hasPrefix: @"/"]) { +#else + if ([outFileName hasPrefix: @"/"] || + [outFileName containsString: @":"]) { +#endif + [of_stderr writeFormat: @"Refusing to extract %@!\n", + fileName]; + + app->_exitStatus = 1; + goto outer_loop_end; + } + + pathComponents = [outFileName pathComponents]; + for (OFString *component in pathComponents) { + if ([component isEqual: OF_PATH_PARENT_DIRECTORY]) { + [of_stderr writeFormat: + @"Refusing to extract %@!\n", fileName]; + + app->_exitStatus = 1; + goto outer_loop_end; + } + } + outFileName = [OFString pathWithComponents: pathComponents]; + + if (app->_outputLevel >= 0) + [of_stdout writeFormat: @"Extracting %@...", fileName]; + + if ([fileName hasSuffix: @"/"]) { + [fileManager createDirectoryAtPath: outFileName + createParents: true]; + setPermissions(outFileName, entry); + + if (app->_outputLevel >= 0) + [of_stdout writeLine: @" done"]; + + goto outer_loop_end; + } + + directory = [outFileName stringByDeletingLastPathComponent]; + if (![fileManager directoryExistsAtPath: directory]) + [fileManager createDirectoryAtPath: directory + createParents: true]; + + if (![app shouldExtractFile: fileName + outFileName: outFileName]) + goto outer_loop_end; + + stream = [_archive streamForReadingFile: fileName]; + output = [OFFile fileWithPath: outFileName + mode: @"wb"]; + setPermissions(outFileName, entry); + + while (![stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: stream + toStream: output + fileName: fileName]; + + if (length < 0) { + app->_exitStatus = 1; + goto outer_loop_end; + } + + written += length; + newPercent = (written == size + ? 100 : (int8_t)(written * 100 / size)); + + if (app->_outputLevel >= 0 && percent != newPercent) { + percent = newPercent; + + [of_stdout writeFormat: + @"\rExtracting %@... %3u%%", + fileName, percent]; + } + } + + if (app->_outputLevel >= 0) + [of_stdout writeFormat: @"\rExtracting %@... done\n", + fileName]; + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + if ([missing count] > 0) { + for (OFString *file in missing) + [of_stderr writeFormat: + @"File %@ is not in the archive!\n", file]; + + app->_exitStatus = 1; + } +} +@end