Index: .gitignore ================================================================== --- .gitignore +++ .gitignore @@ -45,11 +45,11 @@ tests/tests.3dsx tests/tests.arm9 tests/tests.exe tests/tests.nds utils/objfw-config +utils/ofarc/ofarc +utils/ofarc/ofarc.exe utils/ofhash/ofhash utils/ofhash/ofhash.exe utils/ofhttp/ofhttp utils/ofhttp/ofhttp.exe -utils/ofzip/ofzip -utils/ofzip/ofzip.exe Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -21,11 +21,11 @@ mkdir objfw-${PACKAGE_VERSION} git --work-tree=objfw-${PACKAGE_VERSION} checkout . rm objfw-${PACKAGE_VERSION}/.gitignore \ objfw-${PACKAGE_VERSION}/.travis.yml cp configure config.h.in objfw-${PACKAGE_VERSION}/ - ofzip -cq objfw-${PACKAGE_VERSION}.tar $$(find objfw-${PACKAGE_VERSION}) + ofarc -cq objfw-${PACKAGE_VERSION}.tar $$(find objfw-${PACKAGE_VERSION}) rm -fr objfw-${PACKAGE_VERSION} gzip -9 objfw-${PACKAGE_VERSION}.tar rm -f objfw-${PACKAGE_VERSION}.tar gpg -b objfw-${PACKAGE_VERSION}.tar.gz || true echo "Generating documentation..." @@ -33,11 +33,11 @@ doxygen >/dev/null rm -fr objfw-docs-${PACKAGE_VERSION} objfw-docs-${PACKAGE_VERSION}.tar \ objfw-docs-${PACKAGE_VERSION}.tar.gz mv docs objfw-docs-${PACKAGE_VERSION} echo "Generating docs tarball for version ${PACKAGE_VERSION}..." - ofzip -cq objfw-docs-${PACKAGE_VERSION}.tar \ + ofarc -cq objfw-docs-${PACKAGE_VERSION}.tar \ $$(find objfw-docs-${PACKAGE_VERSION}) rm -fr objfw-docs-${PACKAGE_VERSION} gzip -9 objfw-docs-${PACKAGE_VERSION}.tar rm -f objfw-docs-${PACKAGE_VERSION}.tar gpg -b objfw-docs-${PACKAGE_VERSION}.tar.gz || true Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1110,12 +1110,12 @@ AC_ARG_ENABLE(files, AS_HELP_STRING([--disable-files], [disable file support])) AS_IF([test x"$enable_files" != x"no"], [ AC_DEFINE(OF_HAVE_FILES, 1, [Whether we have files]) AC_SUBST(USE_SRCS_FILES, '${SRCS_FILES}') + AC_SUBST(OFARC, "ofarc") AC_SUBST(OFHASH, "ofhash") - AC_SUBST(OFZIP, "ofzip") case "$host_os" in msdosdjgpp*) dnl DJGPP has the type, but it's not really usable. ;; Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -49,10 +49,11 @@ LOOKUP_ASM_A = @LOOKUP_ASM_A@ LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LIB_A@ LOOKUP_ASM_LOOKUP_ASM_A = @LOOKUP_ASM_LOOKUP_ASM_A@ LOOKUP_ASM_LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LOOKUP_ASM_LIB_A@ MAP_LDFLAGS = @MAP_LDFLAGS@ +OFARC = @OFARC@ OFBLOCKTESTS_M = @OFBLOCKTESTS_M@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ OFHTTPCLIENTTESTS_M = @OFHTTPCLIENTTESTS_M@ OFHTTPCLIENT_M = @OFHTTPCLIENT_M@ @@ -61,11 +62,10 @@ OFKERNELEVENTOBSERVER_POLL_M = @OFKERNELEVENTOBSERVER_POLL_M@ OFKERNELEVENTOBSERVER_SELECT_M = @OFKERNELEVENTOBSERVER_SELECT_M@ OFPROCESS_M = @OFPROCESS_M@ OFSTDIOSTREAM_WIN32CONSOLE_M = @OFSTDIOSTREAM_WIN32CONSOLE_M@ OFURLHANDLER_HTTP_M = @OFURLHANDLER_HTTP_M@ -OFZIP = @OFZIP@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_FRAMEWORK_LIBS = @RUNTIME_FRAMEWORK_LIBS@ RUNTIME_LIBS = @RUNTIME_LIBS@ Index: utils/Makefile ================================================================== --- utils/Makefile +++ utils/Makefile @@ -1,10 +1,10 @@ include ../extra.mk -SUBDIRS += ${OFHASH} \ - ${OFHTTP} \ - ${OFZIP} +SUBDIRS += ${OFARC} \ + ${OFHASH} \ + ${OFHTTP} include ../buildsys.mk DISTCLEAN = objfw-config ADDED utils/ofarc/Archive.h Index: utils/ofarc/Archive.h ================================================================== --- utils/ofarc/Archive.h +++ utils/ofarc/Archive.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding; +- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding; +- (void)listFiles; +- (void)extractFiles: (OFArray OF_GENERIC(OFString *) *)files; +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files; +@optional +- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files; +@end ADDED utils/ofarc/GZIPArchive.h Index: utils/ofarc/GZIPArchive.h ================================================================== --- utils/ofarc/GZIPArchive.h +++ utils/ofarc/GZIPArchive.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFGZIPStream.h" + +#import "Archive.h" + +@interface GZIPArchive: OFObject +{ + OFGZIPStream *_stream; +} +@end ADDED utils/ofarc/GZIPArchive.m Index: utils/ofarc/GZIPArchive.m ================================================================== --- utils/ofarc/GZIPArchive.m +++ utils/ofarc/GZIPArchive.m @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFApplication.h" +#import "OFFileManager.h" +#import "OFStdIOStream.h" +#import "OFLocalization.h" + +#import "GZIPArchive.h" +#import "OFArc.h" + +static OFArc *app; + +static void +setPermissions(OFString *destination, OFString *source) +{ +#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS + OFFileManager *fileManager = [OFFileManager defaultManager]; + of_file_attributes_t attributes = + [fileManager attributesOfItemAtPath: source]; + of_file_attribute_key_t key = of_file_attribute_key_size; + of_file_attributes_t destinationAttributes = [OFDictionary + dictionaryWithObject: [attributes objectForKey: key] + forKey: key]; + + [fileManager setAttributes: destinationAttributes + ofItemAtPath: destination]; +#endif +} + +@implementation GZIPArchive ++ (void)initialize +{ + if (self == [GZIPArchive class]) + app = (OFArc *)[[OFApplication sharedApplication] delegate]; +} + ++ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + return [[[self alloc] initWithStream: stream + mode: mode + encoding: encoding] autorelease]; +} + +- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + self = [super init]; + + @try { + _stream = [[OFGZIPStream alloc] initWithStream: stream + mode: mode]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_stream release]; + + [super dealloc]; +} + +- (void)listFiles +{ + [of_stderr writeLine: OF_LOCALIZED(@"cannot_list_gz", + @"Cannot list files of a .gz archive!")]; + app->_exitStatus = 1; +} + +- (void)extractFiles: (OFArray OF_GENERIC(OFString *) *)files +{ + OFString *fileName; + OFFile *output; + + if ([files count] != 0) { + [of_stderr writeLine: + OF_LOCALIZED(@"cannot_extract_specific_file_from_gz", + @"Cannot extract a specific file of a .gz archive!")]; + app->_exitStatus = 1; + return; + } + + fileName = [[app->_archivePath lastPathComponent] + stringByDeletingPathExtension]; + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"extracting_file", + @"Extracting %[file]...", + @"file", fileName)]; + + if (![app shouldExtractFile: fileName + outFileName: fileName]) + return; + + output = [OFFile fileWithPath: fileName + mode: @"w"]; + setPermissions(fileName, app->_archivePath); + + while (![_stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: _stream + toStream: output + fileName: fileName]; + + if (length < 0) { + app->_exitStatus = 1; + return; + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED(@"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } +} + +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files +{ + OFString *fileName = [[app->_archivePath lastPathComponent] + stringByDeletingPathExtension]; + + if ([files count] > 0) { + [of_stderr writeLine: OF_LOCALIZED( + @"cannot_print_specific_file_from_gz", + @"Cannot print a specific file of a .gz archive!")]; + app->_exitStatus = 1; + return; + } + + while (![_stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: _stream + toStream: of_stdout + fileName: fileName]; + + if (length < 0) { + app->_exitStatus = 1; + return; + } + } +} +@end ADDED utils/ofarc/LHAArchive.h Index: utils/ofarc/LHAArchive.h ================================================================== --- utils/ofarc/LHAArchive.h +++ utils/ofarc/LHAArchive.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFLHAArchive.h" + +#import "Archive.h" + +@interface LHAArchive: OFObject +{ + OFLHAArchive *_archive; +} +@end ADDED utils/ofarc/LHAArchive.m Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFApplication.h" +#import "OFDate.h" +#import "OFFileManager.h" +#import "OFLocalization.h" +#import "OFNumber.h" +#import "OFSet.h" +#import "OFStdIOStream.h" +#import "OFString.h" + +#import "LHAArchive.h" +#import "OFArc.h" + +static OFArc *app; + +static void +setPermissions(OFString *path, OFLHAArchiveEntry *entry) +{ +#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS + OFNumber *mode = [entry mode]; + + if (mode == nil) + return; + + of_file_attributes_t attributes = [OFDictionary + dictionaryWithObject: mode + forKey: of_file_attribute_key_posix_permissions]; + + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +#endif +} + +@implementation LHAArchive ++ (void)initialize +{ + if (self == [LHAArchive class]) + app = (OFArc *)[[OFApplication sharedApplication] delegate]; +} + ++ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + return [[[self alloc] initWithStream: stream + mode: mode + encoding: encoding] autorelease]; +} + +- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + self = [super init]; + + @try { + _archive = [[OFLHAArchive alloc] initWithStream: stream + mode: mode]; + + if (encoding != OF_STRING_ENCODING_AUTODETECT) + [_archive setEncoding: encoding]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_archive release]; + + [super dealloc]; +} + +- (void)listFiles +{ + OFLHAArchiveEntry *entry; + + while ((entry = [_archive nextEntry]) != nil) { + void *pool = objc_autoreleasePoolPush(); + + [of_stdout writeLine: [entry fileName]]; + + if (app->_outputLevel >= 1) { + OFString *date = [[entry date] + localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; + OFString *compressedSize = [OFString stringWithFormat: + @"%" PRIu32, [entry compressedSize]]; + OFString *uncompressedSize = [OFString stringWithFormat: + @"%" PRIu32, [entry uncompressedSize]]; + OFString *CRC16 = [OFString stringWithFormat: + @"%04" PRIX16, [entry CRC16]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_compressed_size", + @"Compressed: %[size] bytes", + @"size", compressedSize)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_uncompressed_size", + @"Uncompressed: %[size] bytes", + @"size", uncompressedSize)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_compression_method", + @"Compression method: %[method]", + @"method", [entry compressionMethod])]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_crc16", + @"CRC16: %[crc16]", + @"crc16", CRC16)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_date", + @"Date: %[date]", + @"date", date)]; + + if ([entry mode] != nil) { + OFString *modeString = [OFString + stringWithFormat: + @"%" PRIo16, [[entry mode] uInt16Value]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_mode", + @"Mode: %[mode]", + @"mode", modeString)]; + } + if ([entry UID] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_uid", + @"UID: %[uid]", + @"uid", [entry UID])]; + } + if ([entry GID] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_gid", + @"GID: %[gid]", + @"gid", [entry GID])]; + } + if ([entry owner] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_owner", + @"Owner: %[owner]", + @"owner", [entry owner])]; + } + if ([entry group] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_group", + @"Group: %[group]", + @"group", [entry group])]; + } + + if (app->_outputLevel >= 2) { + OFString *headerLevel = [OFString + stringWithFormat: @"%" PRIu8, + [entry headerLevel]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_header_level", + @"Header level: %[level]", + @"level", headerLevel)]; + + if ([entry operatingSystemIdentifier] != '\0') { + OFString *OSID = + [OFString stringWithFormat: @"%c", + [entry operatingSystemIdentifier]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_osid", + @"Operating system identifier: " + "%[osid]", + @"osid", OSID)]; + } + } + } + + 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]; + OFLHAArchiveEntry *entry; + + while ((entry = [_archive nextEntry]) != nil) { + void *pool = objc_autoreleasePoolPush(); + OFString *fileName = [entry fileName]; + OFString *outFileName, *directory; + OFFile *output; + OFStream *stream; + uint64_t written = 0, size = [entry uncompressedSize]; + int8_t percent = -1, newPercent; + + if (!all && ![files containsObject: fileName]) + continue; + + [missing removeObject: fileName]; + + outFileName = [app safeLocalPathForPath: fileName]; + if (outFileName == nil) { + [of_stderr writeLine: OF_LOCALIZED( + @"refusing_to_extract_file", + @"Refusing to extract %[file]!", + @"file", fileName)]; + + app->_exitStatus = 1; + goto outer_loop_end; + } + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"extracting_file", + @"Extracting %[file]...", + @"file", fileName)]; + + if ([fileName hasSuffix: @"/"]) { + [fileManager createDirectoryAtPath: outFileName + createParents: true]; + setPermissions(outFileName, entry); + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } + + 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 streamForReadingCurrentEntry]; + output = [OFFile fileWithPath: outFileName + mode: @"w"]; + 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) { + OFString *percentString; + + percent = newPercent; + percentString = [OFString stringWithFormat: + @"%3u", percent]; + + [of_stdout writeString: @"\r"]; + [of_stdout writeString: OF_LOCALIZED( + @"extracting_file_percent", + @"Extracting %[file]... %[percent]%", + @"file", fileName, + @"percent", percentString)]; + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + if ([missing count] > 0) { + for (OFString *file in missing) + [of_stderr writeLine: OF_LOCALIZED( + @"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", file)]; + + app->_exitStatus = 1; + } +} + +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files_ +{ + OFMutableSet *files; + OFLHAArchiveEntry *entry; + + if ([files_ count] < 1) { + [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", + @"Need one or more files to print!")]; + app->_exitStatus = 1; + return; + } + + files = [OFMutableSet setWithArray: files_]; + + while ((entry = [_archive nextEntry]) != nil) { + OFString *fileName = [entry fileName]; + OFStream *stream; + + if (![files containsObject: fileName]) + continue; + + stream = [_archive streamForReadingCurrentEntry]; + + while (![stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: stream + toStream: of_stdout + fileName: fileName]; + + if (length < 0) { + app->_exitStatus = 1; + return; + } + } + + [files removeObject: fileName]; + [stream close]; + + if ([files count] == 0) + break; + } + + for (OFString *file in files) { + [of_stderr writeLine: OF_LOCALIZED(@"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", file)]; + app->_exitStatus = 1; + } +} +@end ADDED utils/ofarc/Makefile Index: utils/ofarc/Makefile ================================================================== --- utils/ofarc/Makefile +++ utils/ofarc/Makefile @@ -0,0 +1,27 @@ +include ../../extra.mk + +PROG = ofarc${PROG_SUFFIX} +SRCS = GZIPArchive.m \ + LHAArchive.m \ + OFArc.m \ + TarArchive.m \ + ZIPArchive.m +DATA = lang/de.json \ + lang/languages.json + +include ../../buildsys.mk + +PACKAGE_NAME = ofarc + +${PROG}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFW_RT_DEP_LVL2} + +CPPFLAGS += -I../../src \ + -I../../src/runtime \ + -I../../src/exceptions \ + -I../.. \ + -DLANGUAGE_DIR=\"${datadir}/ofarc/lang\" +LIBS := -L../../src -lobjfw \ + -L../../src/runtime -L../../src/runtime/linklib ${RUNTIME_LIBS} \ + ${LIBS} +LD = ${OBJC} +LDFLAGS += ${LDFLAGS_RPATH} ADDED utils/ofarc/OFArc.h Index: utils/ofarc/OFArc.h ================================================================== --- utils/ofarc/OFArc.h +++ utils/ofarc/OFArc.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFString.h" + +#import "Archive.h" + +OF_ASSUME_NONNULL_BEGIN + +#ifndef S_IRWXG +# define S_IRWXG 0 +#endif +#ifndef S_IRWXO +# define S_IRWXO 0 +#endif + +@interface OFArc: OFObject +{ + int8_t _overwrite; +@public + int8_t _outputLevel; + OFString *_archivePath; + int _exitStatus; +} + +- (id )openArchiveWithPath: (OFString *)path + type: (OFString *)type + mode: (char)mode + encoding: (of_string_encoding_t)encoding; +- (bool)shouldExtractFile: (OFString *)fileName + outFileName: (OFString *)outFileName; +- (ssize_t)copyBlockFromStream: (OFStream *)input + toStream: (OFStream *)output + fileName: (OFString *)fileName; +- (nullable OFString *)safeLocalPathForPath: (OFString *)path; +@end + +OF_ASSUME_NONNULL_END ADDED utils/ofarc/OFArc.m Index: utils/ofarc/OFArc.m ================================================================== --- utils/ofarc/OFArc.m +++ utils/ofarc/OFArc.m @@ -0,0 +1,680 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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" + +#include + +#import "OFApplication.h" +#import "OFArray.h" +#import "OFFile.h" +#import "OFFileManager.h" +#import "OFLocalization.h" +#import "OFOptionsParser.h" +#import "OFSandbox.h" +#import "OFStdIOStream.h" +#import "OFURL.h" + +#import "OFArc.h" +#import "GZIPArchive.h" +#import "LHAArchive.h" +#import "TarArchive.h" +#import "ZIPArchive.h" + +#import "OFCreateDirectoryFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" +#import "OFInvalidFormatException.h" +#import "OFNotImplementedException.h" +#import "OFOpenItemFailedException.h" +#import "OFReadFailedException.h" +#import "OFSeekFailedException.h" +#import "OFWriteFailedException.h" + +#define BUFFER_SIZE 4096 + +OF_APPLICATION_DELEGATE(OFArc) + +static void +help(OFStream *stream, bool full, int status) +{ + [stream writeLine: OF_LOCALIZED(@"usage", + @"Usage: %[prog] -[acCfhlnpqtvx] archive.zip [file1 file2 ...]", + @"prog", [OFApplication programName])]; + + if (full) { + [stream writeString: @"\n"]; + [stream writeLine: OF_LOCALIZED(@"full_usage", + @"Options:\n" + @" -a --append Append to archive\n" + @" -c --create Create archive\n" + @" -C --directory Extract into the specified " + @"directory\n" + @" -E --encoding The encoding used by the archive " + "(only tar files)\n" + @" -f --force Force / overwrite files\n" + @" -h --help Show this help\n" + @" -l --list List all files in the archive\n" + @" -n --no-clobber Never overwrite files\n" + @" -p --print Print one or more files from the " + @"archive\n" + @" -q --quiet Quiet mode (no output, except " + @"errors)\n" + @" -t --type Archive type (gz, lha, tar, tgz, " + @"zip)\n" + @" -v --verbose Verbose output for file list\n" + @" -x --extract Extract files")]; + } + + [OFApplication terminateWithStatus: status]; +} + +static void +mutuallyExclusiveError(of_unichar_t shortOption1, OFString *longOption1, + of_unichar_t shortOption2, OFString *longOption2) +{ + OFString *shortOption1Str = [OFString stringWithFormat: @"%C", + shortOption1]; + OFString *shortOption2Str = [OFString stringWithFormat: @"%C", + shortOption2]; + + [of_stderr writeLine: OF_LOCALIZED(@"2_options_mutually_exclusive", + @"Error: -%[shortopt1] / --%[longopt1] and " + @"-%[shortopt2] / --%[longopt2] " + @"are mutually exclusive!", + @"shortopt1", shortOption1Str, + @"longopt1", longOption1, + @"shortopt2", shortOption2Str, + @"longopt2", longOption2)]; + [OFApplication terminateWithStatus: 1]; +} + +static void +mutuallyExclusiveError5(of_unichar_t shortOption1, OFString *longOption1, + of_unichar_t shortOption2, OFString *longOption2, + of_unichar_t shortOption3, OFString *longOption3, + of_unichar_t shortOption4, OFString *longOption4, + of_unichar_t shortOption5, OFString *longOption5) +{ + OFString *shortOption1Str = [OFString stringWithFormat: @"%C", + shortOption1]; + OFString *shortOption2Str = [OFString stringWithFormat: @"%C", + shortOption2]; + OFString *shortOption3Str = [OFString stringWithFormat: @"%C", + shortOption3]; + OFString *shortOption4Str = [OFString stringWithFormat: @"%C", + shortOption4]; + OFString *shortOption5Str = [OFString stringWithFormat: @"%C", + shortOption5]; + + [of_stderr writeLine: OF_LOCALIZED(@"5_options_mutually_exclusive", + @"Error: -%[shortopt1] / --%[longopt1], " + @"-%[shortopt2] / --%[longopt2], -%[shortopt3] / --%[longopt3], " + @"-%[shortopt4] / --%[longopt4] and\n" + @" -%[shortopt5] / --%[longopt5] are mutually exclusive!", + @"shortopt1", shortOption1Str, + @"longopt1", longOption1, + @"shortopt2", shortOption2Str, + @"longopt2", longOption2, + @"shortopt3", shortOption3Str, + @"longopt3", longOption3, + @"shortopt4", shortOption4Str, + @"longopt4", longOption4, + @"shortopt5", shortOption5Str, + @"longopt5", longOption5)]; + [OFApplication terminateWithStatus: 1]; +} + +static void +writingNotSupported(OFString *type) +{ + [of_stderr writeLine: OF_LOCALIZED( + @"writing_not_supported", + @"Writing archives of type %[type] is not (yet) supported!", + @"type", type)]; +} + +@implementation OFArc +- (void)applicationDidFinishLaunching +{ + OFString *outputDir = nil, *encodingString = nil, *type = nil; + const of_options_parser_option_t options[] = { + { 'a', @"append", 0, NULL, NULL }, + { 'c', @"create", 0, NULL, NULL }, + { 'C', @"directory", 1, NULL, &outputDir }, + { 'E', @"encoding", 1, NULL, &encodingString }, + { 'f', @"force", 0, NULL, NULL }, + { 'h', @"help", 0, NULL, NULL }, + { 'l', @"list", 0, NULL, NULL }, + { 'n', @"no-clobber", 0, NULL, NULL }, + { 'p', @"print", 0, NULL, NULL }, + { 'q', @"quiet", 0, NULL, NULL }, + { 't', @"type", 1, NULL, &type }, + { 'v', @"verbose", 0, NULL, NULL }, + { 'x', @"extract", 0, NULL, NULL }, + { '\0', nil, 0, NULL, NULL } + }; + OFOptionsParser *optionsParser; + of_unichar_t option, mode = '\0'; + of_string_encoding_t encoding = OF_STRING_ENCODING_AUTODETECT; + OFArray OF_GENERIC(OFString *) *remainingArguments, *files; + id archive; + +#ifdef OF_HAVE_SANDBOX + OFSandbox *sandbox = [[OFSandbox alloc] init]; + @try { + [sandbox setAllowsStdIO: true]; + [sandbox setAllowsReadingFiles: true]; + [sandbox setAllowsWritingFiles: true]; + [sandbox setAllowsCreatingFiles: true]; + [sandbox setAllowsChangingFileAttributes: true]; + [sandbox setAllowsUserDatabaseReading: true]; + + [OFApplication activateSandbox: sandbox]; + } @finally { + [sandbox release]; + } +#endif + +#ifndef OF_AMIGAOS + [OFLocalization addLanguageDirectory: @LANGUAGE_DIR]; +#else + [OFLocalization addLanguageDirectory: @"PROGDIR:/share/ofarc/lang"]; +#endif + + optionsParser = [OFOptionsParser parserWithOptions: options]; + while ((option = [optionsParser nextOption]) != '\0') { + switch (option) { + case 'f': + if (_overwrite < 0) + mutuallyExclusiveError( + 'f', @"force", 'n', @"no-clobber"); + + _overwrite = 1; + break; + case 'n': + if (_overwrite > 0) + mutuallyExclusiveError( + 'f', @"force", 'n', @"no-clobber"); + + _overwrite = -1; + break; + case 'v': + if (_outputLevel < 0) + mutuallyExclusiveError( + 'q', @"quiet", 'v', @"verbose"); + + _outputLevel++; + break; + case 'q': + if (_outputLevel > 0) + mutuallyExclusiveError( + 'q', @"quiet", 'v', @"verbose"); + + _outputLevel--; + break; + case 'a': + case 'c': + case 'l': + case 'p': + case 'x': + if (mode != '\0') + mutuallyExclusiveError5( + 'a', @"append", + 'c', @"create", + 'l', @"list", + 'p', @"print", + 'x', @"extract"); + + mode = option; + break; + case 'h': + help(of_stdout, true, 0); + break; + case '=': + [of_stderr writeLine: OF_LOCALIZED( + @"option_takes_no_argument", + @"%[prog]: Option --%[opt] takes no argument", + @"prog", [OFApplication programName], + @"opt", [optionsParser lastLongOption])]; + + [OFApplication terminateWithStatus: 1]; + break; + case ':': + if ([optionsParser lastLongOption] != nil) + [of_stderr writeLine: OF_LOCALIZED( + @"long_option_requires_argument", + @"%[prog]: Option --%[opt] requires an " + @"argument", + @"prog", [OFApplication programName], + @"opt", [optionsParser lastLongOption])]; + else { + OFString *optStr = [OFString + stringWithFormat: @"%C", + [optionsParser lastOption]]; + [of_stderr writeLine: OF_LOCALIZED( + @"option_requires_argument", + @"%[prog]: Option -%[opt] requires an " + @"argument", + @"prog", [OFApplication programName], + @"opt", optStr)]; + } + + [OFApplication terminateWithStatus: 1]; + break; + case '?': + if ([optionsParser lastLongOption] != nil) + [of_stderr writeLine: OF_LOCALIZED( + @"unknown_long_option", + @"%[prog]: Unknown option: --%[opt]", + @"prog", [OFApplication programName], + @"opt", [optionsParser lastLongOption])]; + else { + OFString *optStr = [OFString + stringWithFormat: @"%C", + [optionsParser lastOption]]; + [of_stderr writeLine: OF_LOCALIZED( + @"unknown_option", + @"%[prog]: Unknown option: -%[opt]", + @"prog", [OFApplication programName], + @"opt", optStr)]; + } + + [OFApplication terminateWithStatus: 1]; + } + } + + @try { + if (encodingString != nil) + encoding = of_string_parse_encoding(encodingString); + } @catch (OFInvalidEncodingException *e) { + [of_stderr writeLine: OF_LOCALIZED( + @"invalid_encoding", + @"%[prog]: Invalid encoding: %[encoding]", + @"prog", [OFApplication programName], + @"encoding", encodingString)]; + + [OFApplication terminateWithStatus: 1]; + } + + remainingArguments = [optionsParser remainingArguments]; + archive = [self openArchiveWithPath: [remainingArguments firstObject] + type: type + mode: mode + encoding: encoding]; + + if (outputDir != nil) + [[OFFileManager defaultManager] + changeCurrentDirectoryPath: outputDir]; + + switch (mode) { + case 'a': + case 'c': + if ([remainingArguments count] < 1) + help(of_stderr, false, 1); + + files = [remainingArguments objectsInRange: + of_range(1, [remainingArguments count] - 1)]; + + [archive addFiles: files]; + break; + case 'l': + if ([remainingArguments count] != 1) + help(of_stderr, false, 1); + + [archive listFiles]; + break; + case 'p': + if ([remainingArguments count] < 1) + help(of_stderr, false, 1); + + files = [remainingArguments objectsInRange: + of_range(1, [remainingArguments count] - 1)]; + + [archive printFiles: files]; + break; + case 'x': + if ([remainingArguments count] < 1) + help(of_stderr, false, 1); + + files = [remainingArguments objectsInRange: + of_range(1, [remainingArguments count] - 1)]; + + @try { + [archive extractFiles: files]; + } @catch (OFCreateDirectoryFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stderr writeString: @"\r"]; + [of_stderr writeLine: OF_LOCALIZED( + @"failed_to_create_directory", + @"Failed to create directory %[dir]: %[error]", + @"dir", [[e URL] fileSystemRepresentation], + @"error", error)]; + _exitStatus = 1; + } @catch (OFOpenItemFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stderr writeString: @"\r"]; + [of_stderr writeLine: OF_LOCALIZED( + @"failed_to_open_file", + @"Failed to open file %[file]: %[error]", + @"file", [e path], + @"error", error)]; + _exitStatus = 1; + } + + break; + default: + help(of_stderr, true, 1); + break; + } + + [OFApplication terminateWithStatus: _exitStatus]; +} + +- (id )openArchiveWithPath: (OFString *)path + type: (OFString *)type + mode: (char)mode + encoding: (of_string_encoding_t)encoding +{ + OFString *modeString, *fileModeString; + OFFile *file = nil; + id archive = nil; + + [_archivePath release]; + _archivePath = [path copy]; + + if (path == nil) + return nil; + + switch (mode) { + case 'a': + modeString = @"a"; + fileModeString = @"r+"; + break; + case 'c': + modeString = fileModeString = @"w"; + break; + case 'l': + case 'p': + case 'x': + modeString = fileModeString = @"r"; + break; + default: + @throw [OFInvalidArgumentException exception]; + } + + @try { + file = [OFFile fileWithPath: path + mode: fileModeString]; + } @catch (OFOpenItemFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stderr writeString: @"\r"]; + [of_stderr writeLine: OF_LOCALIZED( + @"failed_to_open_file", + @"Failed to open file %[file]: %[error]", + @"file", [e path], + @"error", error)]; + [OFApplication terminateWithStatus: 1]; + } + + if (type == nil || [type isEqual: @"auto"]) { + /* This one has to be first for obvious reasons */ + if ([path hasSuffix: @".tar.gz"] || [path hasSuffix: @".tgz"] || + [path hasSuffix: @".TAR.GZ"] || [path hasSuffix: @".TGZ"]) + type = @"tgz"; + else if ([path hasSuffix: @".gz"] || [path hasSuffix: @".GZ"]) + type = @"gz"; + else if ([path hasSuffix: @".lha"] || [path hasSuffix: @".lzh"]) + type = @"lha"; + else if ([path hasSuffix: @".tar"] || [path hasSuffix: @".TAR"]) + type = @"tar"; + else + type = @"zip"; + } + + @try { + if ([type isEqual: @"gz"]) + archive = [GZIPArchive archiveWithStream: file + mode: modeString + encoding: encoding]; + else if ([type isEqual: @"lha"]) + archive = [LHAArchive archiveWithStream: file + mode: modeString + encoding: encoding]; + else if ([type isEqual: @"tar"]) + archive = [TarArchive archiveWithStream: file + mode: modeString + encoding: encoding]; + else if ([type isEqual: @"tgz"]) { + OFStream *GZIPStream = [OFGZIPStream + streamWithStream: file + mode: modeString]; + archive = [TarArchive archiveWithStream: GZIPStream + mode: modeString + encoding: encoding]; + } else if ([type isEqual: @"zip"]) + archive = [ZIPArchive archiveWithStream: file + mode: modeString + encoding: encoding]; + else { + [of_stderr writeLine: OF_LOCALIZED( + @"unknown_archive_type", + @"Unknown archive type: %[type]", + @"type", type)]; + goto error; + } + } @catch (OFNotImplementedException *e) { + if ((mode == 'a' || mode == 'c') && + sel_isEqual([e selector], + @selector(initWithStream:mode:))) { + writingNotSupported(type); + goto error; + } + + @throw e; + } @catch (OFReadFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stderr writeLine: OF_LOCALIZED(@"failed_to_read_file", + @"Failed to read file %[file]: %[error]", + @"file", path, + @"error", error)]; + goto error; + } @catch (OFSeekFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stderr writeLine: OF_LOCALIZED(@"failed_to_seek_in_file", + @"Failed to seek in file %[file]: %[error]", + @"file", path, + @"error", error)]; + goto error; + } @catch (OFInvalidFormatException *e) { + [of_stderr writeLine: OF_LOCALIZED( + @"file_is_not_a_valid_archive", + @"File %[file] is not a valid archive!", + @"file", path)]; + goto error; + } + + if ((mode == 'a' || mode == 'c') && + ![archive respondsToSelector: @selector(addFiles:)]) { + writingNotSupported(type); + goto error; + } + + return archive; + +error: + if (mode == 'c') + [[OFFileManager defaultManager] removeItemAtPath: path]; + + [OFApplication terminateWithStatus: 1]; + return nil; +} + +- (bool)shouldExtractFile: (OFString *)fileName + outFileName: (OFString *)outFileName +{ + OFString *line; + + if (_overwrite == 1 || + ![[OFFileManager defaultManager] fileExistsAtPath: outFileName]) + return true; + + if (_overwrite == -1) { + if (_outputLevel >= 0) { + [of_stdout writeString: @" "]; + [of_stdout writeLine: + OF_LOCALIZED(@"file_skipped", @"skipped")]; + } + return false; + } + + do { + [of_stderr writeString: @"\r"]; + [of_stderr writeString: OF_LOCALIZED(@"ask_overwrite", + @"Overwrite %[file]? [ynAN?]", + @"file", fileName)]; + [of_stderr writeString: @" "]; + + line = [of_stdin readLine]; + + if ([line isEqual: @"?"]) + [of_stderr writeLine: OF_LOCALIZED( + @"ask_overwrite_help", + @" y: yes\n" + @" n: no\n" + @" A: always\n" + @" N: never")]; + } while (![line isEqual: @"y"] && ![line isEqual: @"n"] && + ![line isEqual: @"N"] && ![line isEqual: @"A"]); + + if ([line isEqual: @"A"]) + _overwrite = 1; + else if ([line isEqual: @"N"]) + _overwrite = -1; + + if ([line isEqual: @"n"] || [line isEqual: @"N"]) { + if (_outputLevel >= 0) + [of_stdout writeLine: OF_LOCALIZED(@"skipping_file", + @"Skipping %[file]...", + @"file", fileName)]; + return false; + } + + if (_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"extracting_file", + @"Extracting %[file]...", + @"file", 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) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stdout writeString: @"\r"]; + [of_stderr writeLine: OF_LOCALIZED(@"failed_to_read_file", + @"Failed to read file %[file]: %[error]", + @"file", fileName, + @"error", error)]; + return -1; + } + + @try { + [output writeBuffer: buffer + length: length]; + } @catch (OFWriteFailedException *e) { + OFString *error = [OFString + stringWithCString: strerror([e errNo]) + encoding: [OFLocalization encoding]]; + [of_stdout writeString: @"\r"]; + [of_stderr writeLine: OF_LOCALIZED(@"failed_to_write_file", + @"Failed to write file %[file]: %[error]", + @"file", fileName, + @"error", error)]; + return -1; + } + + return length; +} + +- (OFString *)safeLocalPathForPath: (OFString *)path +{ + void *pool = objc_autoreleasePoolPush(); + + path = [path stringByStandardizingPath]; + +#if defined(OF_WINDOWS) || defined(OF_MSDOS) + if ([path containsString: @":"] || [path hasPrefix: @"\\"]) { +#elif defined(OF_AMIGAOS) + if ([path containsString: @":"] || [path hasPrefix: @"/"]) { +#else + if ([path hasPrefix: @"/"]) { +#endif + objc_autoreleasePoolPop(pool); + return nil; + } + + if ([path length] == 0) { + objc_autoreleasePoolPop(pool); + return nil; + } + + /* + * After -[stringByStandardizingPath], everything representing parent + * directory should be at the beginning, so in theory checking the + * first component should be enough. But it does not hurt being + * paranoid and checking all components, just in case. + */ + for (OFString *component in [path pathComponents]) { +#ifdef OF_AMIGAOS + if ([component length] == 0 || [component isEqual: @"/"]) { +#else + if ([component length] == 0 || [component isEqual: @".."]) { +#endif + objc_autoreleasePoolPop(pool); + return nil; + } + } + + [path retain]; + + objc_autoreleasePoolPop(pool); + + return [path autorelease]; +} +@end ADDED utils/ofarc/TarArchive.h Index: utils/ofarc/TarArchive.h ================================================================== --- utils/ofarc/TarArchive.h +++ utils/ofarc/TarArchive.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFTarArchive.h" + +#import "Archive.h" + +@interface TarArchive: OFObject +{ + OFTarArchive *_archive; +} +@end ADDED utils/ofarc/TarArchive.m Index: utils/ofarc/TarArchive.m ================================================================== --- utils/ofarc/TarArchive.m +++ utils/ofarc/TarArchive.m @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 "OFApplication.h" +#import "OFDate.h" +#import "OFFileManager.h" +#import "OFLocalization.h" +#import "OFNumber.h" +#import "OFSet.h" +#import "OFStdIOStream.h" +#import "OFString.h" + +#import "TarArchive.h" +#import "OFArc.h" + +static OFArc *app; + +static void +setPermissions(OFString *path, OFTarArchiveEntry *entry) +{ +#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS + of_file_attributes_t attributes = [OFDictionary + dictionaryWithObject: [OFNumber numberWithUInt16: [entry mode]] + forKey: of_file_attribute_key_posix_permissions]; + + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +#endif +} + +@implementation TarArchive ++ (void)initialize +{ + if (self == [TarArchive class]) + app = (OFArc *)[[OFApplication sharedApplication] delegate]; +} + ++ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + return [[[self alloc] initWithStream: stream + mode: mode + encoding: encoding] autorelease]; +} + +- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + self = [super init]; + + @try { + _archive = [[OFTarArchive alloc] initWithStream: stream + mode: mode]; + + if (encoding != OF_STRING_ENCODING_AUTODETECT) + [_archive setEncoding: encoding]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_archive release]; + + [super dealloc]; +} + +- (void)listFiles +{ + OFTarArchiveEntry *entry; + + while ((entry = [_archive nextEntry]) != nil) { + void *pool = objc_autoreleasePoolPush(); + + [of_stdout writeLine: [entry fileName]]; + + if (app->_outputLevel >= 1) { + OFString *date = [[entry modificationDate] + localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; + OFString *size = [OFString stringWithFormat: + @"%" PRIu64, [entry size]]; + OFString *mode = [OFString stringWithFormat: + @"%06o", [entry mode]]; + OFString *UID = [OFString stringWithFormat: + @"%u", [entry UID]]; + OFString *GID = [OFString stringWithFormat: + @"%u", [entry GID]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_size", + @"Size: %[size] bytes", + @"size", size)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_mode", + @"Mode: %[mode]", + @"mode", mode)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_uid", + @"UID: %[uid]", + @"uid", UID)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_gid", + @"GID: %[gid]", + @"gid", GID)]; + + if ([entry owner] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_owner", + @"Owner: %[owner]", + @"owner", [entry owner])]; + } + if ([entry group] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_group", + @"Group: %[group]", + @"group", [entry group])]; + } + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_modification_date", + @"Modification date: %[date]", + @"date", date)]; + } + + if (app->_outputLevel >= 2) { + [of_stdout writeString: @"\t"]; + + switch ([entry type]) { + case OF_TAR_ARCHIVE_ENTRY_TYPE_FILE: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_normal", + @"Type: Normal file")]; + break; + case OF_TAR_ARCHIVE_ENTRY_TYPE_LINK: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_hardlink", + @"Type: Hard link")]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_link_target", + @"Target file name: %[target]", + @"target", [entry targetFileName])]; + break; + case OF_TAR_ARCHIVE_ENTRY_TYPE_SYMLINK: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_symlink", + @"Type: Symbolic link")]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_link_target", + @"Target file name: %[target]", + @"target", [entry targetFileName])]; + break; + case OF_TAR_ARCHIVE_ENTRY_TYPE_CHARACTER_DEVICE: { + OFString *majorString = [OFString + stringWithFormat: @"%d", + [entry deviceMajor]]; + OFString *minorString = [OFString + stringWithFormat: @"%d", + [entry deviceMinor]]; + + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_character_device", + @"Type: Character device")]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_device_major", + @"Device major: %[major]", + @"major", majorString)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_device_minor", + @"Device minor: %[minor]", + @"minor", minorString)]; + break; + } + case OF_TAR_ARCHIVE_ENTRY_TYPE_BLOCK_DEVICE: { + OFString *majorString = [OFString + stringWithFormat: @"%d", + [entry deviceMajor]]; + OFString *minorString = [OFString + stringWithFormat: @"%d", + [entry deviceMinor]]; + + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_block_device", + @"Type: Block device")]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_device_major", + @"Device major: %[major]", + @"major", majorString)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_device_minor", + @"Device minor: %[minor]", + @"minor", minorString)]; + break; + } + case OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_directory", + @"Type: Directory")]; + break; + case OF_TAR_ARCHIVE_ENTRY_TYPE_FIFO: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_fifo", + @"Type: FIFO")]; + break; + case OF_TAR_ARCHIVE_ENTRY_TYPE_CONTIGUOUS_FILE: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_contiguous_file", + @"Type: Contiguous file")]; + break; + default: + [of_stdout writeLine: OF_LOCALIZED( + @"list_type_unknown", + @"Type: Unknown")]; + break; + } + } + + 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]; + OFTarArchiveEntry *entry; + + while ((entry = [_archive nextEntry]) != nil) { + void *pool = objc_autoreleasePoolPush(); + OFString *fileName = [entry fileName]; + of_tar_archive_entry_type_t type = [entry type]; + OFString *outFileName, *directory; + OFFile *output; + OFStream *stream; + uint64_t written = 0, size = [entry size]; + int8_t percent = -1, newPercent; + + if (!all && ![files containsObject: fileName]) + continue; + + if (type != OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && + type != OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY) { + if (app->_outputLevel >= 0) + [of_stdout writeLine: OF_LOCALIZED( + @"skipping_file", + @"Skipping %[file]...", + @"file", fileName)]; + continue; + } + + [missing removeObject: fileName]; + + outFileName = [app safeLocalPathForPath: fileName]; + if (outFileName == nil) { + [of_stderr writeLine: OF_LOCALIZED( + @"refusing_to_extract_file", + @"Refusing to extract %[file]!", + @"file", fileName)]; + + app->_exitStatus = 1; + goto outer_loop_end; + } + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"extracting_file", + @"Extracting %[file]...", + @"file", fileName)]; + + if (type == OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY || + (type == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && + [fileName hasSuffix: @"/"])) { + [fileManager createDirectoryAtPath: outFileName + createParents: true]; + setPermissions(outFileName, entry); + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } + + 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 streamForReadingCurrentEntry]; + output = [OFFile fileWithPath: outFileName + mode: @"w"]; + 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) { + OFString *percentString; + + percent = newPercent; + percentString = [OFString stringWithFormat: + @"%3u", percent]; + + [of_stdout writeString: @"\r"]; + [of_stdout writeString: OF_LOCALIZED( + @"extracting_file_percent", + @"Extracting %[file]... %[percent]%", + @"file", fileName, + @"percent", percentString)]; + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + if ([missing count] > 0) { + for (OFString *file in missing) + [of_stderr writeLine: OF_LOCALIZED( + @"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", file)]; + + app->_exitStatus = 1; + } +} + +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files_ +{ + OFMutableSet *files; + OFTarArchiveEntry *entry; + + if ([files_ count] < 1) { + [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", + @"Need one or more files to print!")]; + app->_exitStatus = 1; + return; + } + + files = [OFMutableSet setWithArray: files_]; + + while ((entry = [_archive nextEntry]) != nil) { + OFString *fileName = [entry fileName]; + OFStream *stream; + + if (![files containsObject: fileName]) + continue; + + stream = [_archive streamForReadingCurrentEntry]; + + while (![stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: stream + toStream: of_stdout + fileName: fileName]; + + if (length < 0) { + app->_exitStatus = 1; + return; + } + } + + [files removeObject: fileName]; + [stream close]; + + if ([files count] == 0) + break; + } + + for (OFString *file in files) { + [of_stderr writeLine: OF_LOCALIZED(@"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", file)]; + app->_exitStatus = 1; + } +} + +- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files +{ + OFFileManager *fileManager = [OFFileManager defaultManager]; + + if ([files count] < 1) { + [of_stderr writeLine: OF_LOCALIZED(@"add_no_file_specified", + @"Need one or more files to add!")]; + app->_exitStatus = 1; + return; + } + + for (OFString *fileName in files) { + void *pool = objc_autoreleasePoolPush(); + of_file_attributes_t attributes; + of_file_type_t type; + OFMutableTarArchiveEntry *entry; + OFStream *output; + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"adding_file", + @"Adding %[file]...", + @"file", fileName)]; + + attributes = [fileManager attributesOfItemAtPath: fileName]; + type = [attributes fileType]; + entry = [OFMutableTarArchiveEntry entryWithFileName: fileName]; + +#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS + [entry setMode: [attributes filePOSIXPermissions]]; +#endif + [entry setSize: [attributes fileSize]]; + [entry setModificationDate: [attributes fileModificationDate]]; + +#ifdef OF_FILE_MANAGER_SUPPORTS_OWNER + [entry setUID: [attributes filePOSIXUID]]; + [entry setGID: [attributes filePOSIXGID]]; + [entry setOwner: [attributes fileOwner]]; + [entry setGroup: [attributes fileGroup]]; +#endif + + if ([type isEqual: of_file_type_regular]) + [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_FILE]; + else if ([type isEqual: of_file_type_directory]) { + [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY]; + [entry setSize: 0]; + } else if ([type isEqual: of_file_type_symbolic_link]) { + [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_SYMLINK]; + [entry setTargetFileName: + [attributes fileSymbolicLinkDestination]]; + [entry setSize: 0]; + } + + [entry makeImmutable]; + + output = [_archive streamForWritingEntry: entry]; + + if ([entry type] == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE) { + uint64_t written = 0, size = [entry size]; + int8_t percent = -1, newPercent; + + OFFile *input = [OFFile fileWithPath: fileName + mode: @"r"]; + + while (![input isAtEndOfStream]) { + ssize_t length = [app + copyBlockFromStream: input + 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) { + OFString *percentString; + + percent = newPercent; + percentString = [OFString + stringWithFormat: @"%3u", percent]; + + [of_stdout writeString: @"\r"]; + [of_stdout writeString: OF_LOCALIZED( + @"adding_file_percent", + @"Adding %[file]... %[percent]%", + @"file", fileName, + @"percent", percentString)]; + } + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"adding_file_done", + @"Adding %[file]... done", + @"file", fileName)]; + } + + [output close]; + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + [_archive close]; +} +@end ADDED utils/ofarc/ZIPArchive.h Index: utils/ofarc/ZIPArchive.h ================================================================== --- utils/ofarc/ZIPArchive.h +++ utils/ofarc/ZIPArchive.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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/ofarc/ZIPArchive.m Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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" + +#include + +#import "OFApplication.h" +#import "OFDate.h" +#import "OFFileManager.h" +#import "OFLocalization.h" +#import "OFNumber.h" +#import "OFSet.h" +#import "OFStdIOStream.h" +#import "OFString.h" + +#import "ZIPArchive.h" +#import "OFArc.h" + +#import "OFInvalidFormatException.h" +#import "OFOpenItemFailedException.h" +#import "OFOutOfRangeException.h" + +static OFArc *app; + +static void +setPermissions(OFString *path, OFZIPArchiveEntry *entry) +{ +#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS + if (([entry versionMadeBy] >> 8) == + OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { + uint16_t mode = [entry versionSpecificAttributes] >> 16; + of_file_attribute_key_t key = + of_file_attribute_key_posix_permissions; + of_file_attributes_t attributes = [OFDictionary + dictionaryWithObject: [OFNumber numberWithUInt16: mode] + forKey: key]; + + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; + } +#endif +} + +@implementation ZIPArchive ++ (void)initialize +{ + if (self == [ZIPArchive class]) + app = (OFArc *)[[OFApplication sharedApplication] delegate]; +} + ++ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + return [[[self alloc] initWithStream: stream + mode: mode + encoding: encoding] autorelease]; +} + +- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream + mode: (OFString *)mode + encoding: (of_string_encoding_t)encoding +{ + self = [super init]; + + @try { + _archive = [[OFZIPArchive alloc] initWithStream: stream + mode: mode]; + } @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 *compressedSize = [OFString + stringWithFormat: @"%" PRIu64, + [entry compressedSize]]; + OFString *uncompressedSize = [OFString + stringWithFormat: @"%" PRIu64, + [entry uncompressedSize]]; + OFString *compressionMethod = + of_zip_archive_entry_compression_method_to_string( + [entry compressionMethod]); + OFString *CRC32 = [OFString + stringWithFormat: @"%08" PRIX32, [entry CRC32]]; + OFString *modificationDate = [[entry modificationDate] + localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_compressed_size", + @"Compressed: %[size] bytes", + @"size", compressedSize)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_uncompressed_size", + @"Uncompressed: %[size] bytes", + @"size", uncompressedSize)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_compression_method", + @"Compression method: %[method]", + @"method", compressionMethod)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_crc32", + @"CRC32: %[crc32]", + @"crc32", CRC32)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_modification_date", + @"Modification date: %[date]", + @"date", modificationDate)]; + + if (app->_outputLevel >= 2) { + uint16_t versionMadeBy = [entry versionMadeBy]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_version_made_by", + @"Version made by: %[version]", + @"version", + of_zip_archive_entry_version_to_string( + versionMadeBy))]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_min_version_needed", + @"Minimum version needed: %[version]", + @"version", + 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; + OFString *modeString = [OFString + stringWithFormat: @"%06o", mode]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_mode", + @"Mode: %[mode]", + @"mode", modeString)]; + } + } + + if (app->_outputLevel >= 3) { + OFString *GPBF = [OFString stringWithFormat: + @"%04" PRIx16, + [entry generalPurposeBitFlag]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_general_purpose_bit_flag", + @"General purpose bit flag: %[gpbf]", + @"gpbf", GPBF)]; + + if ([entry extraField] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_extra_field", + @"Extra field: %[extra]", + @"extra", [entry extraField])]; + } + } + + if ([[entry fileComment] length] > 0) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_comment", + @"Comment: %[comment]", + @"comment", [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, *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]; + + outFileName = [app safeLocalPathForPath: fileName]; + if (outFileName == nil) { + [of_stderr writeLine: OF_LOCALIZED( + @"refusing_to_extract_file", + @"Refusing to extract %[file]!", + @"file", fileName)]; + + app->_exitStatus = 1; + goto outer_loop_end; + } + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"extracting_file", + @"Extracting %[file]...", + @"file", fileName)]; + + if ([fileName hasSuffix: @"/"]) { + [fileManager createDirectoryAtPath: outFileName + createParents: true]; + setPermissions(outFileName, entry); + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done", + @"file", fileName)]; + } + + 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: @"w"]; + 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) { + OFString *percentString; + + percent = newPercent; + percentString = [OFString stringWithFormat: + @"%3u", percent]; + + [of_stdout writeString: @"\r"]; + [of_stdout writeString: OF_LOCALIZED( + @"extracting_file_percent", + @"Extracting %[file]... %[percent]%", + @"file", fileName, + @"percent", percentString)]; + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"extracting_file_done", + @"Extracting %[file]... done\n", + @"file", fileName)]; + } + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + if ([missing count] > 0) { + for (OFString *file in missing) + [of_stderr writeLine: OF_LOCALIZED( + @"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", file)]; + + app->_exitStatus = 1; + } +} + +- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files +{ + OFStream *stream; + + if ([files count] < 1) { + [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", + @"Need one or more files to print!")]; + app->_exitStatus = 1; + return; + } + + for (OFString *path in files) { + @try { + stream = [_archive streamForReadingFile: path]; + } @catch (OFOpenItemFailedException *e) { + if ([e errNo] == ENOENT) { + [of_stderr writeLine: OF_LOCALIZED( + @"file_not_in_archive", + @"File %[file] is not in the archive!", + @"file", [e path])]; + app->_exitStatus = 1; + continue; + } + + @throw e; + } + + while (![stream isAtEndOfStream]) { + ssize_t length = [app copyBlockFromStream: stream + toStream: of_stdout + fileName: path]; + + if (length < 0) { + app->_exitStatus = 1; + return; + } + } + + [stream close]; + } +} + +- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files +{ + OFFileManager *fileManager = [OFFileManager defaultManager]; + + if ([files count] < 1) { + [of_stderr writeLine: OF_LOCALIZED(@"add_no_file_specified", + @"Need one or more files to add!")]; + app->_exitStatus = 1; + return; + } + + for (OFString *localFileName in files) { + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC (OFString *) *components; + OFString *fileName; + of_file_attributes_t attributes; + bool isDirectory = false; + OFMutableZIPArchiveEntry *entry; + uintmax_t size; + OFStream *output; + + components = [localFileName pathComponents]; + fileName = [components componentsJoinedByString: @"/"]; + + attributes = [fileManager + attributesOfItemAtPath: localFileName]; + + if ([[attributes fileType] isEqual: of_file_type_directory]) { + isDirectory = true; + fileName = [fileName stringByAppendingString: @"/"]; + } + + if (app->_outputLevel >= 0) + [of_stdout writeString: OF_LOCALIZED(@"adding_file", + @"Adding %[file]...", + @"file", fileName)]; + + entry = [OFMutableZIPArchiveEntry entryWithFileName: fileName]; + + if (isDirectory) + size = 0; + else + size = [attributes fileSize]; + + if (size > INT64_MAX) + @throw [OFOutOfRangeException exception]; + + [entry setCompressedSize: (int64_t)size]; + [entry setUncompressedSize: (int64_t)size]; + + [entry setCompressionMethod: + OF_ZIP_ARCHIVE_ENTRY_COMPRESSION_METHOD_NONE]; + [entry setModificationDate: [attributes fileModificationDate]]; + + [entry makeImmutable]; + + output = [_archive streamForWritingEntry: entry]; + + if (!isDirectory) { + uintmax_t written = 0; + int8_t percent = -1, newPercent; + + OFFile *input = [OFFile fileWithPath: fileName + mode: @"r"]; + + while (![input isAtEndOfStream]) { + ssize_t length = [app + copyBlockFromStream: input + 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) { + OFString *percentString; + + percent = newPercent; + percentString = [OFString + stringWithFormat: @"%3u", percent]; + + [of_stdout writeString: @"\r"]; + [of_stdout writeString: OF_LOCALIZED( + @"adding_file_percent", + @"Adding %[file]... %[percent]%", + @"file", fileName, + @"percent", percentString)]; + } + } + } + + if (app->_outputLevel >= 0) { + [of_stdout writeString: @"\r"]; + [of_stdout writeLine: OF_LOCALIZED( + @"adding_file_done", + @"Adding %[file]... done", + @"file", fileName)]; + } + + [output close]; + +outer_loop_end: + objc_autoreleasePoolPop(pool); + } + + [_archive close]; +} +@end ADDED utils/ofarc/lang/de.json Index: utils/ofarc/lang/de.json ================================================================== --- utils/ofarc/lang/de.json +++ utils/ofarc/lang/de.json @@ -0,0 +1,110 @@ +{ + "usage": [ + "Benutzung: %[prog] -[acCfhlnpqtvx] archiv.zip [datei1 datei2 ...]" + ], + "full_usage": [ + "Optionen:\n", + " -a --append Zu Archiv hinzufügen\n", + " -c --create Archiv erstellen\n", + " -C --directory In angegebenes Verzeichnis entpacken\n", + " -E --encoding Das Encoding des Archivs (nur tar-Dateien)\n", + " -f --force Existierende Dateien überschreiben\n", + " -h --help Diese Hilfe anzeigen\n", + " -l --list Alle Dateien im Archiv auflisten\n", + " -n --no-clobber Dateien niemals überschreiben\n", + " -p --print Eine oder mehr Dateien aus dem Archiv ausgeben", + "\n", + " -q --quiet Ruhiger Modus (keine Ausgabe außer Fehler)\n", + " -t --type Archiv-Typ (gz, lha, tar, tgz, zip)\n", + " -v --verbose Ausführlicher Modus für Datei-Liste\n", + " -x --extract Dateien entpacken" + ], + "2_options_mutually_exclusive": [ + "Fehler: -%[shortopt1] / --%[longopt1] und ", + "-%[shortopt2] / --%[longopt2] schließen sich gegenseitig aus!" + ], + "5_options_mutually_exclusive": [ + "Fehler: -%[shortopt1] / --%[longopt1], -%[shortopt2] / ", + "--%[longopt2], -%[shortopt3] / --%[longopt3], ", + "-%[shortopt4] / --%[longopt4] und\n", + " -%[shortopt5] / --%[longopt5] schließen sich gegenseitig aus!" + ], + "option_takes_no_argument": "%[prog]: Option --%[opt] nimmt kein Argument", + "long_option_requires_argument": [ + "%[prog]: Option --%[opt] benötigt ein Argument" + ], + "option_requires_argument": "%[prog]: Option -%[opt] benötigt ein Argument", + "unknown_long_option": "%[prog]: Unbekannte Option: --%[opt]", + "unknown_option": "%[prog]: Unbekannte Option: -%[opt]", + "invalid_encoding": "%[prog]: Invalid encoding: %[encoding]", + "writing_not_supported": [ + "Schreiben von Dateien des Typs %[type] wird (noch) nicht unterstützt!" + ], + "failed_to_create_directory": [ + "Fehler beim Erstellen des Verzeichnis %[dir]: %[error]" + ], + "failed_to_open_file": "Fehler beim Öffnen der Datei %[file]: %[error]", + "unknown_archive_type": "Unbekannter Archivtyp: %[type]", + "failed_to_read_file": "Fehler beim Lesen der Datei %[file]: %[error]", + "failed_to_write_file": "Fehler beim Schreiben der Datei %[file]: %[error]", + "failed_to_seek_in_file": "Fehler beim Suchen in Datei %[file]: %[error]", + "file_is_not_a_valid_archive": "Datei %[file] ist kein gültiges Archiv!", + "file_skipped": "übersprungen", + "ask_overwrite": "%[file] überschreiben? [ynAN?]", + "ask_overwrite_help": [ + " y: Ja\n", + " n: Nein\n", + " A: Immer\n", + " N: Nie" + ], + "skipping_file": "Überspringe %[file]...", + "extracting_file": "Entpacke %[file]...", + "extracting_file_percent": "Entpacke %[file]... %[percent]%", + "extracting_file_done": "Entpacke %[file]... fertig", + "cannot_list_gz": "Kann Dateien eines .gz-Archivs nicht auflisten!", + "cannot_extract_specific_file_from_gz": [ + "Kann keine spezifische Datei aus einem .gz-Archiv entpacken!" + ], + "cannot_print_specific_file_from_gz": [ + "Kann keine spezifische Datei aus einem .gz-Archiv ausgeben!" + ], + "list_size": "Größe: %[size] Bytes", + "list_mode": "Modus: %[mode]", + "list_owner": "Besitzer: %[owner]", + "list_group": "Gruppe: %[group]", + "list_header_level": "Header-Level: %[level]", + "list_modification_date": "Änderungsdatum: %[date]", + "list_type_normal": "Typ: Normale Datei", + "list_type_hardlink": "Typ: Harter Link", + "list_type_symlink": "Typ: Symbolischer Link", + "list_link_target": "Zieldateiname: %[target]", + "list_type_character_device": "Typ: Zeichenorientiertes Gerät", + "list_type_block_device": "Typ: Blockorientiertes Gerät", + "list_device_major": "Major-Nummer des Geräts: %[major]", + "list_device_minor": "Minor-Nummer des Geräts: %[minor]", + "list_type_directory": "Typ: Verzeichnis", + "list_type_fifo": "Typ: FIFO", + "list_type_contiguous_file": "Typ: Zusammenhängende Datei", + "list_type_unknown": "Typ: Unbekannt", + "list_compressed_size": "Komprimierte Größe: %[size] Bytes", + "list_uncompressed_size": "Unkomprimierte Größe: %[size] Bytes", + "list_compression_method": "Kompressionsmethode: %[method]", + "list_date": "Datum: %[date]", + "list_osid": "Betriebssystem-Identifikator: %[osid]", + "list_version_made_by": "Erstellt mit Version: %[version]", + "list_min_version_needed": "Mindestens benötigte Version: %[version]", + "list_general_purpose_bit_flag": "General Purpose Bit Flag: %[gpbf]", + "list_extra_field": "Extra-Feld: %[extra]", + "list_comment": "Kommentar: %[comment]", + "refusing_to_extract_file": "Verweigere Entpacken von %[file]!", + "file_not_in_archive": "Datei %[file] ist nicht im Archiv!", + "print_no_file_specified": [ + "Benötige eine oder mehrere Dateien zum Ausgeben!" + ], + "add_no_file_specified": [ + "Benötige eine oder mehrere Dateien zum Hinzufügen!" + ], + "adding_file": "Füge %[file] hinzu...", + "adding_file_percent": "Füge %[file] hinzu... %[percent]%", + "adding_file_done": "Füge %[file] hinzu... fertig" +} ADDED utils/ofarc/lang/languages.json Index: utils/ofarc/lang/languages.json ================================================================== --- utils/ofarc/lang/languages.json +++ utils/ofarc/lang/languages.json @@ -0,0 +1,11 @@ +{ + "de": { + "": "de" + }, + "deutsch": { + "": "de" + }, + "german": { + "": "de" + } +} DELETED utils/ofzip/Archive.h Index: utils/ofzip/Archive.h ================================================================== --- utils/ofzip/Archive.h +++ utils/ofzip/Archive.h @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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)archiveWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding; -- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding; -- (void)listFiles; -- (void)extractFiles: (OFArray OF_GENERIC(OFString *) *)files; -- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files; -@optional -- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files; -@end DELETED utils/ofzip/GZIPArchive.h Index: utils/ofzip/GZIPArchive.h ================================================================== --- utils/ofzip/GZIPArchive.h +++ utils/ofzip/GZIPArchive.h @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFGZIPStream.h" - -#import "Archive.h" - -@interface GZIPArchive: OFObject -{ - OFGZIPStream *_stream; -} -@end DELETED utils/ofzip/GZIPArchive.m Index: utils/ofzip/GZIPArchive.m ================================================================== --- utils/ofzip/GZIPArchive.m +++ utils/ofzip/GZIPArchive.m @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFApplication.h" -#import "OFFileManager.h" -#import "OFStdIOStream.h" -#import "OFLocalization.h" - -#import "GZIPArchive.h" -#import "OFZIP.h" - -static OFZIP *app; - -static void -setPermissions(OFString *destination, OFString *source) -{ -#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS - OFFileManager *fileManager = [OFFileManager defaultManager]; - of_file_attributes_t attributes = - [fileManager attributesOfItemAtPath: source]; - of_file_attribute_key_t key = of_file_attribute_key_size; - of_file_attributes_t destinationAttributes = [OFDictionary - dictionaryWithObject: [attributes objectForKey: key] - forKey: key]; - - [fileManager setAttributes: destinationAttributes - ofItemAtPath: destination]; -#endif -} - -@implementation GZIPArchive -+ (void)initialize -{ - if (self == [GZIPArchive class]) - app = (OFZIP *)[[OFApplication sharedApplication] delegate]; -} - -+ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - return [[[self alloc] initWithStream: stream - mode: mode - encoding: encoding] autorelease]; -} - -- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - self = [super init]; - - @try { - _stream = [[OFGZIPStream alloc] initWithStream: stream - mode: mode]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_stream release]; - - [super dealloc]; -} - -- (void)listFiles -{ - [of_stderr writeLine: OF_LOCALIZED(@"cannot_list_gz", - @"Cannot list files of a .gz archive!")]; - app->_exitStatus = 1; -} - -- (void)extractFiles: (OFArray OF_GENERIC(OFString *) *)files -{ - OFString *fileName; - OFFile *output; - - if ([files count] != 0) { - [of_stderr writeLine: - OF_LOCALIZED(@"cannot_extract_specific_file_from_gz", - @"Cannot extract a specific file of a .gz archive!")]; - app->_exitStatus = 1; - return; - } - - fileName = [[app->_archivePath lastPathComponent] - stringByDeletingPathExtension]; - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"extracting_file", - @"Extracting %[file]...", - @"file", fileName)]; - - if (![app shouldExtractFile: fileName - outFileName: fileName]) - return; - - output = [OFFile fileWithPath: fileName - mode: @"w"]; - setPermissions(fileName, app->_archivePath); - - while (![_stream isAtEndOfStream]) { - ssize_t length = [app copyBlockFromStream: _stream - toStream: output - fileName: fileName]; - - if (length < 0) { - app->_exitStatus = 1; - return; - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED(@"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } -} - -- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files -{ - OFString *fileName = [[app->_archivePath lastPathComponent] - stringByDeletingPathExtension]; - - if ([files count] > 0) { - [of_stderr writeLine: OF_LOCALIZED( - @"cannot_print_specific_file_from_gz", - @"Cannot print a specific file of a .gz archive!")]; - app->_exitStatus = 1; - return; - } - - while (![_stream isAtEndOfStream]) { - ssize_t length = [app copyBlockFromStream: _stream - toStream: of_stdout - fileName: fileName]; - - if (length < 0) { - app->_exitStatus = 1; - return; - } - } -} -@end DELETED utils/ofzip/LHAArchive.h Index: utils/ofzip/LHAArchive.h ================================================================== --- utils/ofzip/LHAArchive.h +++ utils/ofzip/LHAArchive.h @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFLHAArchive.h" - -#import "Archive.h" - -@interface LHAArchive: OFObject -{ - OFLHAArchive *_archive; -} -@end DELETED utils/ofzip/LHAArchive.m Index: utils/ofzip/LHAArchive.m ================================================================== --- utils/ofzip/LHAArchive.m +++ utils/ofzip/LHAArchive.m @@ -1,374 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFApplication.h" -#import "OFDate.h" -#import "OFFileManager.h" -#import "OFLocalization.h" -#import "OFNumber.h" -#import "OFSet.h" -#import "OFStdIOStream.h" -#import "OFString.h" - -#import "LHAArchive.h" -#import "OFZIP.h" - -static OFZIP *app; - -static void -setPermissions(OFString *path, OFLHAArchiveEntry *entry) -{ -#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS - OFNumber *mode = [entry mode]; - - if (mode == nil) - return; - - of_file_attributes_t attributes = [OFDictionary - dictionaryWithObject: mode - forKey: of_file_attribute_key_posix_permissions]; - - [[OFFileManager defaultManager] setAttributes: attributes - ofItemAtPath: path]; -#endif -} - -@implementation LHAArchive -+ (void)initialize -{ - if (self == [LHAArchive class]) - app = (OFZIP *)[[OFApplication sharedApplication] delegate]; -} - -+ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - return [[[self alloc] initWithStream: stream - mode: mode - encoding: encoding] autorelease]; -} - -- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - self = [super init]; - - @try { - _archive = [[OFLHAArchive alloc] initWithStream: stream - mode: mode]; - - if (encoding != OF_STRING_ENCODING_AUTODETECT) - [_archive setEncoding: encoding]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_archive release]; - - [super dealloc]; -} - -- (void)listFiles -{ - OFLHAArchiveEntry *entry; - - while ((entry = [_archive nextEntry]) != nil) { - void *pool = objc_autoreleasePoolPush(); - - [of_stdout writeLine: [entry fileName]]; - - if (app->_outputLevel >= 1) { - OFString *date = [[entry date] - localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; - OFString *compressedSize = [OFString stringWithFormat: - @"%" PRIu32, [entry compressedSize]]; - OFString *uncompressedSize = [OFString stringWithFormat: - @"%" PRIu32, [entry uncompressedSize]]; - OFString *CRC16 = [OFString stringWithFormat: - @"%04" PRIX16, [entry CRC16]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_compressed_size", - @"Compressed: %[size] bytes", - @"size", compressedSize)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_uncompressed_size", - @"Uncompressed: %[size] bytes", - @"size", uncompressedSize)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_compression_method", - @"Compression method: %[method]", - @"method", [entry compressionMethod])]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_crc16", - @"CRC16: %[crc16]", - @"crc16", CRC16)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_date", - @"Date: %[date]", - @"date", date)]; - - if ([entry mode] != nil) { - OFString *modeString = [OFString - stringWithFormat: - @"%" PRIo16, [[entry mode] uInt16Value]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_mode", - @"Mode: %[mode]", - @"mode", modeString)]; - } - if ([entry UID] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_uid", - @"UID: %[uid]", - @"uid", [entry UID])]; - } - if ([entry GID] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_gid", - @"GID: %[gid]", - @"gid", [entry GID])]; - } - if ([entry owner] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_owner", - @"Owner: %[owner]", - @"owner", [entry owner])]; - } - if ([entry group] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_group", - @"Group: %[group]", - @"group", [entry group])]; - } - - if (app->_outputLevel >= 2) { - OFString *headerLevel = [OFString - stringWithFormat: @"%" PRIu8, - [entry headerLevel]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_header_level", - @"Header level: %[level]", - @"level", headerLevel)]; - - if ([entry operatingSystemIdentifier] != '\0') { - OFString *OSID = - [OFString stringWithFormat: @"%c", - [entry operatingSystemIdentifier]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_osid", - @"Operating system identifier: " - "%[osid]", - @"osid", OSID)]; - } - } - } - - 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]; - OFLHAArchiveEntry *entry; - - while ((entry = [_archive nextEntry]) != nil) { - void *pool = objc_autoreleasePoolPush(); - OFString *fileName = [entry fileName]; - OFString *outFileName, *directory; - OFFile *output; - OFStream *stream; - uint64_t written = 0, size = [entry uncompressedSize]; - int8_t percent = -1, newPercent; - - if (!all && ![files containsObject: fileName]) - continue; - - [missing removeObject: fileName]; - - outFileName = [app safeLocalPathForPath: fileName]; - if (outFileName == nil) { - [of_stderr writeLine: OF_LOCALIZED( - @"refusing_to_extract_file", - @"Refusing to extract %[file]!", - @"file", fileName)]; - - app->_exitStatus = 1; - goto outer_loop_end; - } - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"extracting_file", - @"Extracting %[file]...", - @"file", fileName)]; - - if ([fileName hasSuffix: @"/"]) { - [fileManager createDirectoryAtPath: outFileName - createParents: true]; - setPermissions(outFileName, entry); - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } - - 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 streamForReadingCurrentEntry]; - output = [OFFile fileWithPath: outFileName - mode: @"w"]; - 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) { - OFString *percentString; - - percent = newPercent; - percentString = [OFString stringWithFormat: - @"%3u", percent]; - - [of_stdout writeString: @"\r"]; - [of_stdout writeString: OF_LOCALIZED( - @"extracting_file_percent", - @"Extracting %[file]... %[percent]%", - @"file", fileName, - @"percent", percentString)]; - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - if ([missing count] > 0) { - for (OFString *file in missing) - [of_stderr writeLine: OF_LOCALIZED( - @"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", file)]; - - app->_exitStatus = 1; - } -} - -- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files_ -{ - OFMutableSet *files; - OFLHAArchiveEntry *entry; - - if ([files_ count] < 1) { - [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", - @"Need one or more files to print!")]; - app->_exitStatus = 1; - return; - } - - files = [OFMutableSet setWithArray: files_]; - - while ((entry = [_archive nextEntry]) != nil) { - OFString *fileName = [entry fileName]; - OFStream *stream; - - if (![files containsObject: fileName]) - continue; - - stream = [_archive streamForReadingCurrentEntry]; - - while (![stream isAtEndOfStream]) { - ssize_t length = [app copyBlockFromStream: stream - toStream: of_stdout - fileName: fileName]; - - if (length < 0) { - app->_exitStatus = 1; - return; - } - } - - [files removeObject: fileName]; - [stream close]; - - if ([files count] == 0) - break; - } - - for (OFString *file in files) { - [of_stderr writeLine: OF_LOCALIZED(@"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", file)]; - app->_exitStatus = 1; - } -} -@end DELETED utils/ofzip/Makefile Index: utils/ofzip/Makefile ================================================================== --- utils/ofzip/Makefile +++ utils/ofzip/Makefile @@ -1,27 +0,0 @@ -include ../../extra.mk - -PROG = ofzip${PROG_SUFFIX} -SRCS = GZIPArchive.m \ - LHAArchive.m \ - OFZIP.m \ - TarArchive.m \ - ZIPArchive.m -DATA = lang/de.json \ - lang/languages.json - -include ../../buildsys.mk - -PACKAGE_NAME = ofzip - -${PROG}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFW_RT_DEP_LVL2} - -CPPFLAGS += -I../../src \ - -I../../src/runtime \ - -I../../src/exceptions \ - -I../.. \ - -DLANGUAGE_DIR=\"${datadir}/ofzip/lang\" -LIBS := -L../../src -lobjfw \ - -L../../src/runtime -L../../src/runtime/linklib ${RUNTIME_LIBS} \ - ${LIBS} -LD = ${OBJC} -LDFLAGS += ${LDFLAGS_RPATH} DELETED utils/ofzip/OFZIP.h Index: utils/ofzip/OFZIP.h ================================================================== --- utils/ofzip/OFZIP.h +++ utils/ofzip/OFZIP.h @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFString.h" - -#import "Archive.h" - -OF_ASSUME_NONNULL_BEGIN - -#ifndef S_IRWXG -# define S_IRWXG 0 -#endif -#ifndef S_IRWXO -# define S_IRWXO 0 -#endif - -@interface OFZIP: OFObject -{ - int8_t _overwrite; -@public - int8_t _outputLevel; - OFString *_archivePath; - int _exitStatus; -} - -- (id )openArchiveWithPath: (OFString *)path - type: (OFString *)type - mode: (char)mode - encoding: (of_string_encoding_t)encoding; -- (bool)shouldExtractFile: (OFString *)fileName - outFileName: (OFString *)outFileName; -- (ssize_t)copyBlockFromStream: (OFStream *)input - toStream: (OFStream *)output - fileName: (OFString *)fileName; -- (nullable OFString *)safeLocalPathForPath: (OFString *)path; -@end - -OF_ASSUME_NONNULL_END DELETED utils/ofzip/OFZIP.m Index: utils/ofzip/OFZIP.m ================================================================== --- utils/ofzip/OFZIP.m +++ utils/ofzip/OFZIP.m @@ -1,680 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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" - -#include - -#import "OFApplication.h" -#import "OFArray.h" -#import "OFFile.h" -#import "OFFileManager.h" -#import "OFLocalization.h" -#import "OFOptionsParser.h" -#import "OFSandbox.h" -#import "OFStdIOStream.h" -#import "OFURL.h" - -#import "OFZIP.h" -#import "GZIPArchive.h" -#import "LHAArchive.h" -#import "TarArchive.h" -#import "ZIPArchive.h" - -#import "OFCreateDirectoryFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFInvalidEncodingException.h" -#import "OFInvalidFormatException.h" -#import "OFNotImplementedException.h" -#import "OFOpenItemFailedException.h" -#import "OFReadFailedException.h" -#import "OFSeekFailedException.h" -#import "OFWriteFailedException.h" - -#define BUFFER_SIZE 4096 - -OF_APPLICATION_DELEGATE(OFZIP) - -static void -help(OFStream *stream, bool full, int status) -{ - [stream writeLine: OF_LOCALIZED(@"usage", - @"Usage: %[prog] -[acCfhlnpqtvx] archive.zip [file1 file2 ...]", - @"prog", [OFApplication programName])]; - - if (full) { - [stream writeString: @"\n"]; - [stream writeLine: OF_LOCALIZED(@"full_usage", - @"Options:\n" - @" -a --append Append to archive\n" - @" -c --create Create archive\n" - @" -C --directory Extract into the specified " - @"directory\n" - @" -E --encoding The encoding used by the archive " - "(only tar files)\n" - @" -f --force Force / overwrite files\n" - @" -h --help Show this help\n" - @" -l --list List all files in the archive\n" - @" -n --no-clobber Never overwrite files\n" - @" -p --print Print one or more files from the " - @"archive\n" - @" -q --quiet Quiet mode (no output, except " - @"errors)\n" - @" -t --type Archive type (gz, lha, tar, tgz, " - @"zip)\n" - @" -v --verbose Verbose output for file list\n" - @" -x --extract Extract files")]; - } - - [OFApplication terminateWithStatus: status]; -} - -static void -mutuallyExclusiveError(of_unichar_t shortOption1, OFString *longOption1, - of_unichar_t shortOption2, OFString *longOption2) -{ - OFString *shortOption1Str = [OFString stringWithFormat: @"%C", - shortOption1]; - OFString *shortOption2Str = [OFString stringWithFormat: @"%C", - shortOption2]; - - [of_stderr writeLine: OF_LOCALIZED(@"2_options_mutually_exclusive", - @"Error: -%[shortopt1] / --%[longopt1] and " - @"-%[shortopt2] / --%[longopt2] " - @"are mutually exclusive!", - @"shortopt1", shortOption1Str, - @"longopt1", longOption1, - @"shortopt2", shortOption2Str, - @"longopt2", longOption2)]; - [OFApplication terminateWithStatus: 1]; -} - -static void -mutuallyExclusiveError5(of_unichar_t shortOption1, OFString *longOption1, - of_unichar_t shortOption2, OFString *longOption2, - of_unichar_t shortOption3, OFString *longOption3, - of_unichar_t shortOption4, OFString *longOption4, - of_unichar_t shortOption5, OFString *longOption5) -{ - OFString *shortOption1Str = [OFString stringWithFormat: @"%C", - shortOption1]; - OFString *shortOption2Str = [OFString stringWithFormat: @"%C", - shortOption2]; - OFString *shortOption3Str = [OFString stringWithFormat: @"%C", - shortOption3]; - OFString *shortOption4Str = [OFString stringWithFormat: @"%C", - shortOption4]; - OFString *shortOption5Str = [OFString stringWithFormat: @"%C", - shortOption5]; - - [of_stderr writeLine: OF_LOCALIZED(@"5_options_mutually_exclusive", - @"Error: -%[shortopt1] / --%[longopt1], " - @"-%[shortopt2] / --%[longopt2], -%[shortopt3] / --%[longopt3], " - @"-%[shortopt4] / --%[longopt4] and\n" - @" -%[shortopt5] / --%[longopt5] are mutually exclusive!", - @"shortopt1", shortOption1Str, - @"longopt1", longOption1, - @"shortopt2", shortOption2Str, - @"longopt2", longOption2, - @"shortopt3", shortOption3Str, - @"longopt3", longOption3, - @"shortopt4", shortOption4Str, - @"longopt4", longOption4, - @"shortopt5", shortOption5Str, - @"longopt5", longOption5)]; - [OFApplication terminateWithStatus: 1]; -} - -static void -writingNotSupported(OFString *type) -{ - [of_stderr writeLine: OF_LOCALIZED( - @"writing_not_supported", - @"Writing archives of type %[type] is not (yet) supported!", - @"type", type)]; -} - -@implementation OFZIP -- (void)applicationDidFinishLaunching -{ - OFString *outputDir = nil, *encodingString = nil, *type = nil; - const of_options_parser_option_t options[] = { - { 'a', @"append", 0, NULL, NULL }, - { 'c', @"create", 0, NULL, NULL }, - { 'C', @"directory", 1, NULL, &outputDir }, - { 'E', @"encoding", 1, NULL, &encodingString }, - { 'f', @"force", 0, NULL, NULL }, - { 'h', @"help", 0, NULL, NULL }, - { 'l', @"list", 0, NULL, NULL }, - { 'n', @"no-clobber", 0, NULL, NULL }, - { 'p', @"print", 0, NULL, NULL }, - { 'q', @"quiet", 0, NULL, NULL }, - { 't', @"type", 1, NULL, &type }, - { 'v', @"verbose", 0, NULL, NULL }, - { 'x', @"extract", 0, NULL, NULL }, - { '\0', nil, 0, NULL, NULL } - }; - OFOptionsParser *optionsParser; - of_unichar_t option, mode = '\0'; - of_string_encoding_t encoding = OF_STRING_ENCODING_AUTODETECT; - OFArray OF_GENERIC(OFString *) *remainingArguments, *files; - id archive; - -#ifdef OF_HAVE_SANDBOX - OFSandbox *sandbox = [[OFSandbox alloc] init]; - @try { - [sandbox setAllowsStdIO: true]; - [sandbox setAllowsReadingFiles: true]; - [sandbox setAllowsWritingFiles: true]; - [sandbox setAllowsCreatingFiles: true]; - [sandbox setAllowsChangingFileAttributes: true]; - [sandbox setAllowsUserDatabaseReading: true]; - - [OFApplication activateSandbox: sandbox]; - } @finally { - [sandbox release]; - } -#endif - -#ifndef OF_AMIGAOS - [OFLocalization addLanguageDirectory: @LANGUAGE_DIR]; -#else - [OFLocalization addLanguageDirectory: @"PROGDIR:/share/ofzip/lang"]; -#endif - - optionsParser = [OFOptionsParser parserWithOptions: options]; - while ((option = [optionsParser nextOption]) != '\0') { - switch (option) { - case 'f': - if (_overwrite < 0) - mutuallyExclusiveError( - 'f', @"force", 'n', @"no-clobber"); - - _overwrite = 1; - break; - case 'n': - if (_overwrite > 0) - mutuallyExclusiveError( - 'f', @"force", 'n', @"no-clobber"); - - _overwrite = -1; - break; - case 'v': - if (_outputLevel < 0) - mutuallyExclusiveError( - 'q', @"quiet", 'v', @"verbose"); - - _outputLevel++; - break; - case 'q': - if (_outputLevel > 0) - mutuallyExclusiveError( - 'q', @"quiet", 'v', @"verbose"); - - _outputLevel--; - break; - case 'a': - case 'c': - case 'l': - case 'p': - case 'x': - if (mode != '\0') - mutuallyExclusiveError5( - 'a', @"append", - 'c', @"create", - 'l', @"list", - 'p', @"print", - 'x', @"extract"); - - mode = option; - break; - case 'h': - help(of_stdout, true, 0); - break; - case '=': - [of_stderr writeLine: OF_LOCALIZED( - @"option_takes_no_argument", - @"%[prog]: Option --%[opt] takes no argument", - @"prog", [OFApplication programName], - @"opt", [optionsParser lastLongOption])]; - - [OFApplication terminateWithStatus: 1]; - break; - case ':': - if ([optionsParser lastLongOption] != nil) - [of_stderr writeLine: OF_LOCALIZED( - @"long_option_requires_argument", - @"%[prog]: Option --%[opt] requires an " - @"argument", - @"prog", [OFApplication programName], - @"opt", [optionsParser lastLongOption])]; - else { - OFString *optStr = [OFString - stringWithFormat: @"%C", - [optionsParser lastOption]]; - [of_stderr writeLine: OF_LOCALIZED( - @"option_requires_argument", - @"%[prog]: Option -%[opt] requires an " - @"argument", - @"prog", [OFApplication programName], - @"opt", optStr)]; - } - - [OFApplication terminateWithStatus: 1]; - break; - case '?': - if ([optionsParser lastLongOption] != nil) - [of_stderr writeLine: OF_LOCALIZED( - @"unknown_long_option", - @"%[prog]: Unknown option: --%[opt]", - @"prog", [OFApplication programName], - @"opt", [optionsParser lastLongOption])]; - else { - OFString *optStr = [OFString - stringWithFormat: @"%C", - [optionsParser lastOption]]; - [of_stderr writeLine: OF_LOCALIZED( - @"unknown_option", - @"%[prog]: Unknown option: -%[opt]", - @"prog", [OFApplication programName], - @"opt", optStr)]; - } - - [OFApplication terminateWithStatus: 1]; - } - } - - @try { - if (encodingString != nil) - encoding = of_string_parse_encoding(encodingString); - } @catch (OFInvalidEncodingException *e) { - [of_stderr writeLine: OF_LOCALIZED( - @"invalid_encoding", - @"%[prog]: Invalid encoding: %[encoding]", - @"prog", [OFApplication programName], - @"encoding", encodingString)]; - - [OFApplication terminateWithStatus: 1]; - } - - remainingArguments = [optionsParser remainingArguments]; - archive = [self openArchiveWithPath: [remainingArguments firstObject] - type: type - mode: mode - encoding: encoding]; - - if (outputDir != nil) - [[OFFileManager defaultManager] - changeCurrentDirectoryPath: outputDir]; - - switch (mode) { - case 'a': - case 'c': - if ([remainingArguments count] < 1) - help(of_stderr, false, 1); - - files = [remainingArguments objectsInRange: - of_range(1, [remainingArguments count] - 1)]; - - [archive addFiles: files]; - break; - case 'l': - if ([remainingArguments count] != 1) - help(of_stderr, false, 1); - - [archive listFiles]; - break; - case 'p': - if ([remainingArguments count] < 1) - help(of_stderr, false, 1); - - files = [remainingArguments objectsInRange: - of_range(1, [remainingArguments count] - 1)]; - - [archive printFiles: files]; - break; - case 'x': - if ([remainingArguments count] < 1) - help(of_stderr, false, 1); - - files = [remainingArguments objectsInRange: - of_range(1, [remainingArguments count] - 1)]; - - @try { - [archive extractFiles: files]; - } @catch (OFCreateDirectoryFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stderr writeString: @"\r"]; - [of_stderr writeLine: OF_LOCALIZED( - @"failed_to_create_directory", - @"Failed to create directory %[dir]: %[error]", - @"dir", [[e URL] fileSystemRepresentation], - @"error", error)]; - _exitStatus = 1; - } @catch (OFOpenItemFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stderr writeString: @"\r"]; - [of_stderr writeLine: OF_LOCALIZED( - @"failed_to_open_file", - @"Failed to open file %[file]: %[error]", - @"file", [e path], - @"error", error)]; - _exitStatus = 1; - } - - break; - default: - help(of_stderr, true, 1); - break; - } - - [OFApplication terminateWithStatus: _exitStatus]; -} - -- (id )openArchiveWithPath: (OFString *)path - type: (OFString *)type - mode: (char)mode - encoding: (of_string_encoding_t)encoding -{ - OFString *modeString, *fileModeString; - OFFile *file = nil; - id archive = nil; - - [_archivePath release]; - _archivePath = [path copy]; - - if (path == nil) - return nil; - - switch (mode) { - case 'a': - modeString = @"a"; - fileModeString = @"r+"; - break; - case 'c': - modeString = fileModeString = @"w"; - break; - case 'l': - case 'p': - case 'x': - modeString = fileModeString = @"r"; - break; - default: - @throw [OFInvalidArgumentException exception]; - } - - @try { - file = [OFFile fileWithPath: path - mode: fileModeString]; - } @catch (OFOpenItemFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stderr writeString: @"\r"]; - [of_stderr writeLine: OF_LOCALIZED( - @"failed_to_open_file", - @"Failed to open file %[file]: %[error]", - @"file", [e path], - @"error", error)]; - [OFApplication terminateWithStatus: 1]; - } - - if (type == nil || [type isEqual: @"auto"]) { - /* This one has to be first for obvious reasons */ - if ([path hasSuffix: @".tar.gz"] || [path hasSuffix: @".tgz"] || - [path hasSuffix: @".TAR.GZ"] || [path hasSuffix: @".TGZ"]) - type = @"tgz"; - else if ([path hasSuffix: @".gz"] || [path hasSuffix: @".GZ"]) - type = @"gz"; - else if ([path hasSuffix: @".lha"] || [path hasSuffix: @".lzh"]) - type = @"lha"; - else if ([path hasSuffix: @".tar"] || [path hasSuffix: @".TAR"]) - type = @"tar"; - else - type = @"zip"; - } - - @try { - if ([type isEqual: @"gz"]) - archive = [GZIPArchive archiveWithStream: file - mode: modeString - encoding: encoding]; - else if ([type isEqual: @"lha"]) - archive = [LHAArchive archiveWithStream: file - mode: modeString - encoding: encoding]; - else if ([type isEqual: @"tar"]) - archive = [TarArchive archiveWithStream: file - mode: modeString - encoding: encoding]; - else if ([type isEqual: @"tgz"]) { - OFStream *GZIPStream = [OFGZIPStream - streamWithStream: file - mode: modeString]; - archive = [TarArchive archiveWithStream: GZIPStream - mode: modeString - encoding: encoding]; - } else if ([type isEqual: @"zip"]) - archive = [ZIPArchive archiveWithStream: file - mode: modeString - encoding: encoding]; - else { - [of_stderr writeLine: OF_LOCALIZED( - @"unknown_archive_type", - @"Unknown archive type: %[type]", - @"type", type)]; - goto error; - } - } @catch (OFNotImplementedException *e) { - if ((mode == 'a' || mode == 'c') && - sel_isEqual([e selector], - @selector(initWithStream:mode:))) { - writingNotSupported(type); - goto error; - } - - @throw e; - } @catch (OFReadFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stderr writeLine: OF_LOCALIZED(@"failed_to_read_file", - @"Failed to read file %[file]: %[error]", - @"file", path, - @"error", error)]; - goto error; - } @catch (OFSeekFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stderr writeLine: OF_LOCALIZED(@"failed_to_seek_in_file", - @"Failed to seek in file %[file]: %[error]", - @"file", path, - @"error", error)]; - goto error; - } @catch (OFInvalidFormatException *e) { - [of_stderr writeLine: OF_LOCALIZED( - @"file_is_not_a_valid_archive", - @"File %[file] is not a valid archive!", - @"file", path)]; - goto error; - } - - if ((mode == 'a' || mode == 'c') && - ![archive respondsToSelector: @selector(addFiles:)]) { - writingNotSupported(type); - goto error; - } - - return archive; - -error: - if (mode == 'c') - [[OFFileManager defaultManager] removeItemAtPath: path]; - - [OFApplication terminateWithStatus: 1]; - return nil; -} - -- (bool)shouldExtractFile: (OFString *)fileName - outFileName: (OFString *)outFileName -{ - OFString *line; - - if (_overwrite == 1 || - ![[OFFileManager defaultManager] fileExistsAtPath: outFileName]) - return true; - - if (_overwrite == -1) { - if (_outputLevel >= 0) { - [of_stdout writeString: @" "]; - [of_stdout writeLine: - OF_LOCALIZED(@"file_skipped", @"skipped")]; - } - return false; - } - - do { - [of_stderr writeString: @"\r"]; - [of_stderr writeString: OF_LOCALIZED(@"ask_overwrite", - @"Overwrite %[file]? [ynAN?]", - @"file", fileName)]; - [of_stderr writeString: @" "]; - - line = [of_stdin readLine]; - - if ([line isEqual: @"?"]) - [of_stderr writeLine: OF_LOCALIZED( - @"ask_overwrite_help", - @" y: yes\n" - @" n: no\n" - @" A: always\n" - @" N: never")]; - } while (![line isEqual: @"y"] && ![line isEqual: @"n"] && - ![line isEqual: @"N"] && ![line isEqual: @"A"]); - - if ([line isEqual: @"A"]) - _overwrite = 1; - else if ([line isEqual: @"N"]) - _overwrite = -1; - - if ([line isEqual: @"n"] || [line isEqual: @"N"]) { - if (_outputLevel >= 0) - [of_stdout writeLine: OF_LOCALIZED(@"skipping_file", - @"Skipping %[file]...", - @"file", fileName)]; - return false; - } - - if (_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"extracting_file", - @"Extracting %[file]...", - @"file", 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) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stdout writeString: @"\r"]; - [of_stderr writeLine: OF_LOCALIZED(@"failed_to_read_file", - @"Failed to read file %[file]: %[error]", - @"file", fileName, - @"error", error)]; - return -1; - } - - @try { - [output writeBuffer: buffer - length: length]; - } @catch (OFWriteFailedException *e) { - OFString *error = [OFString - stringWithCString: strerror([e errNo]) - encoding: [OFLocalization encoding]]; - [of_stdout writeString: @"\r"]; - [of_stderr writeLine: OF_LOCALIZED(@"failed_to_write_file", - @"Failed to write file %[file]: %[error]", - @"file", fileName, - @"error", error)]; - return -1; - } - - return length; -} - -- (OFString *)safeLocalPathForPath: (OFString *)path -{ - void *pool = objc_autoreleasePoolPush(); - - path = [path stringByStandardizingPath]; - -#if defined(OF_WINDOWS) || defined(OF_MSDOS) - if ([path containsString: @":"] || [path hasPrefix: @"\\"]) { -#elif defined(OF_AMIGAOS) - if ([path containsString: @":"] || [path hasPrefix: @"/"]) { -#else - if ([path hasPrefix: @"/"]) { -#endif - objc_autoreleasePoolPop(pool); - return nil; - } - - if ([path length] == 0) { - objc_autoreleasePoolPop(pool); - return nil; - } - - /* - * After -[stringByStandardizingPath], everything representing parent - * directory should be at the beginning, so in theory checking the - * first component should be enough. But it does not hurt being - * paranoid and checking all components, just in case. - */ - for (OFString *component in [path pathComponents]) { -#ifdef OF_AMIGAOS - if ([component length] == 0 || [component isEqual: @"/"]) { -#else - if ([component length] == 0 || [component isEqual: @".."]) { -#endif - objc_autoreleasePoolPop(pool); - return nil; - } - } - - [path retain]; - - objc_autoreleasePoolPop(pool); - - return [path autorelease]; -} -@end DELETED utils/ofzip/TarArchive.h Index: utils/ofzip/TarArchive.h ================================================================== --- utils/ofzip/TarArchive.h +++ utils/ofzip/TarArchive.h @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFTarArchive.h" - -#import "Archive.h" - -@interface TarArchive: OFObject -{ - OFTarArchive *_archive; -} -@end DELETED utils/ofzip/TarArchive.m Index: utils/ofzip/TarArchive.m ================================================================== --- utils/ofzip/TarArchive.m +++ utils/ofzip/TarArchive.m @@ -1,547 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 "OFApplication.h" -#import "OFDate.h" -#import "OFFileManager.h" -#import "OFLocalization.h" -#import "OFNumber.h" -#import "OFSet.h" -#import "OFStdIOStream.h" -#import "OFString.h" - -#import "TarArchive.h" -#import "OFZIP.h" - -static OFZIP *app; - -static void -setPermissions(OFString *path, OFTarArchiveEntry *entry) -{ -#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS - of_file_attributes_t attributes = [OFDictionary - dictionaryWithObject: [OFNumber numberWithUInt16: [entry mode]] - forKey: of_file_attribute_key_posix_permissions]; - - [[OFFileManager defaultManager] setAttributes: attributes - ofItemAtPath: path]; -#endif -} - -@implementation TarArchive -+ (void)initialize -{ - if (self == [TarArchive class]) - app = (OFZIP *)[[OFApplication sharedApplication] delegate]; -} - -+ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - return [[[self alloc] initWithStream: stream - mode: mode - encoding: encoding] autorelease]; -} - -- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - self = [super init]; - - @try { - _archive = [[OFTarArchive alloc] initWithStream: stream - mode: mode]; - - if (encoding != OF_STRING_ENCODING_AUTODETECT) - [_archive setEncoding: encoding]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_archive release]; - - [super dealloc]; -} - -- (void)listFiles -{ - OFTarArchiveEntry *entry; - - while ((entry = [_archive nextEntry]) != nil) { - void *pool = objc_autoreleasePoolPush(); - - [of_stdout writeLine: [entry fileName]]; - - if (app->_outputLevel >= 1) { - OFString *date = [[entry modificationDate] - localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; - OFString *size = [OFString stringWithFormat: - @"%" PRIu64, [entry size]]; - OFString *mode = [OFString stringWithFormat: - @"%06o", [entry mode]]; - OFString *UID = [OFString stringWithFormat: - @"%u", [entry UID]]; - OFString *GID = [OFString stringWithFormat: - @"%u", [entry GID]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_size", - @"Size: %[size] bytes", - @"size", size)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_mode", - @"Mode: %[mode]", - @"mode", mode)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_uid", - @"UID: %[uid]", - @"uid", UID)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_gid", - @"GID: %[gid]", - @"gid", GID)]; - - if ([entry owner] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_owner", - @"Owner: %[owner]", - @"owner", [entry owner])]; - } - if ([entry group] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_group", - @"Group: %[group]", - @"group", [entry group])]; - } - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_modification_date", - @"Modification date: %[date]", - @"date", date)]; - } - - if (app->_outputLevel >= 2) { - [of_stdout writeString: @"\t"]; - - switch ([entry type]) { - case OF_TAR_ARCHIVE_ENTRY_TYPE_FILE: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_normal", - @"Type: Normal file")]; - break; - case OF_TAR_ARCHIVE_ENTRY_TYPE_LINK: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_hardlink", - @"Type: Hard link")]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_link_target", - @"Target file name: %[target]", - @"target", [entry targetFileName])]; - break; - case OF_TAR_ARCHIVE_ENTRY_TYPE_SYMLINK: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_symlink", - @"Type: Symbolic link")]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_link_target", - @"Target file name: %[target]", - @"target", [entry targetFileName])]; - break; - case OF_TAR_ARCHIVE_ENTRY_TYPE_CHARACTER_DEVICE: { - OFString *majorString = [OFString - stringWithFormat: @"%d", - [entry deviceMajor]]; - OFString *minorString = [OFString - stringWithFormat: @"%d", - [entry deviceMinor]]; - - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_character_device", - @"Type: Character device")]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_device_major", - @"Device major: %[major]", - @"major", majorString)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_device_minor", - @"Device minor: %[minor]", - @"minor", minorString)]; - break; - } - case OF_TAR_ARCHIVE_ENTRY_TYPE_BLOCK_DEVICE: { - OFString *majorString = [OFString - stringWithFormat: @"%d", - [entry deviceMajor]]; - OFString *minorString = [OFString - stringWithFormat: @"%d", - [entry deviceMinor]]; - - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_block_device", - @"Type: Block device")]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_device_major", - @"Device major: %[major]", - @"major", majorString)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_device_minor", - @"Device minor: %[minor]", - @"minor", minorString)]; - break; - } - case OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_directory", - @"Type: Directory")]; - break; - case OF_TAR_ARCHIVE_ENTRY_TYPE_FIFO: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_fifo", - @"Type: FIFO")]; - break; - case OF_TAR_ARCHIVE_ENTRY_TYPE_CONTIGUOUS_FILE: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_contiguous_file", - @"Type: Contiguous file")]; - break; - default: - [of_stdout writeLine: OF_LOCALIZED( - @"list_type_unknown", - @"Type: Unknown")]; - break; - } - } - - 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]; - OFTarArchiveEntry *entry; - - while ((entry = [_archive nextEntry]) != nil) { - void *pool = objc_autoreleasePoolPush(); - OFString *fileName = [entry fileName]; - of_tar_archive_entry_type_t type = [entry type]; - OFString *outFileName, *directory; - OFFile *output; - OFStream *stream; - uint64_t written = 0, size = [entry size]; - int8_t percent = -1, newPercent; - - if (!all && ![files containsObject: fileName]) - continue; - - if (type != OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && - type != OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY) { - if (app->_outputLevel >= 0) - [of_stdout writeLine: OF_LOCALIZED( - @"skipping_file", - @"Skipping %[file]...", - @"file", fileName)]; - continue; - } - - [missing removeObject: fileName]; - - outFileName = [app safeLocalPathForPath: fileName]; - if (outFileName == nil) { - [of_stderr writeLine: OF_LOCALIZED( - @"refusing_to_extract_file", - @"Refusing to extract %[file]!", - @"file", fileName)]; - - app->_exitStatus = 1; - goto outer_loop_end; - } - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"extracting_file", - @"Extracting %[file]...", - @"file", fileName)]; - - if (type == OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY || - (type == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && - [fileName hasSuffix: @"/"])) { - [fileManager createDirectoryAtPath: outFileName - createParents: true]; - setPermissions(outFileName, entry); - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } - - 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 streamForReadingCurrentEntry]; - output = [OFFile fileWithPath: outFileName - mode: @"w"]; - 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) { - OFString *percentString; - - percent = newPercent; - percentString = [OFString stringWithFormat: - @"%3u", percent]; - - [of_stdout writeString: @"\r"]; - [of_stdout writeString: OF_LOCALIZED( - @"extracting_file_percent", - @"Extracting %[file]... %[percent]%", - @"file", fileName, - @"percent", percentString)]; - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - if ([missing count] > 0) { - for (OFString *file in missing) - [of_stderr writeLine: OF_LOCALIZED( - @"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", file)]; - - app->_exitStatus = 1; - } -} - -- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files_ -{ - OFMutableSet *files; - OFTarArchiveEntry *entry; - - if ([files_ count] < 1) { - [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", - @"Need one or more files to print!")]; - app->_exitStatus = 1; - return; - } - - files = [OFMutableSet setWithArray: files_]; - - while ((entry = [_archive nextEntry]) != nil) { - OFString *fileName = [entry fileName]; - OFStream *stream; - - if (![files containsObject: fileName]) - continue; - - stream = [_archive streamForReadingCurrentEntry]; - - while (![stream isAtEndOfStream]) { - ssize_t length = [app copyBlockFromStream: stream - toStream: of_stdout - fileName: fileName]; - - if (length < 0) { - app->_exitStatus = 1; - return; - } - } - - [files removeObject: fileName]; - [stream close]; - - if ([files count] == 0) - break; - } - - for (OFString *file in files) { - [of_stderr writeLine: OF_LOCALIZED(@"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", file)]; - app->_exitStatus = 1; - } -} - -- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files -{ - OFFileManager *fileManager = [OFFileManager defaultManager]; - - if ([files count] < 1) { - [of_stderr writeLine: OF_LOCALIZED(@"add_no_file_specified", - @"Need one or more files to add!")]; - app->_exitStatus = 1; - return; - } - - for (OFString *fileName in files) { - void *pool = objc_autoreleasePoolPush(); - of_file_attributes_t attributes; - of_file_type_t type; - OFMutableTarArchiveEntry *entry; - OFStream *output; - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"adding_file", - @"Adding %[file]...", - @"file", fileName)]; - - attributes = [fileManager attributesOfItemAtPath: fileName]; - type = [attributes fileType]; - entry = [OFMutableTarArchiveEntry entryWithFileName: fileName]; - -#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS - [entry setMode: [attributes filePOSIXPermissions]]; -#endif - [entry setSize: [attributes fileSize]]; - [entry setModificationDate: [attributes fileModificationDate]]; - -#ifdef OF_FILE_MANAGER_SUPPORTS_OWNER - [entry setUID: [attributes filePOSIXUID]]; - [entry setGID: [attributes filePOSIXGID]]; - [entry setOwner: [attributes fileOwner]]; - [entry setGroup: [attributes fileGroup]]; -#endif - - if ([type isEqual: of_file_type_regular]) - [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_FILE]; - else if ([type isEqual: of_file_type_directory]) { - [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_DIRECTORY]; - [entry setSize: 0]; - } else if ([type isEqual: of_file_type_symbolic_link]) { - [entry setType: OF_TAR_ARCHIVE_ENTRY_TYPE_SYMLINK]; - [entry setTargetFileName: - [attributes fileSymbolicLinkDestination]]; - [entry setSize: 0]; - } - - [entry makeImmutable]; - - output = [_archive streamForWritingEntry: entry]; - - if ([entry type] == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE) { - uint64_t written = 0, size = [entry size]; - int8_t percent = -1, newPercent; - - OFFile *input = [OFFile fileWithPath: fileName - mode: @"r"]; - - while (![input isAtEndOfStream]) { - ssize_t length = [app - copyBlockFromStream: input - 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) { - OFString *percentString; - - percent = newPercent; - percentString = [OFString - stringWithFormat: @"%3u", percent]; - - [of_stdout writeString: @"\r"]; - [of_stdout writeString: OF_LOCALIZED( - @"adding_file_percent", - @"Adding %[file]... %[percent]%", - @"file", fileName, - @"percent", percentString)]; - } - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"adding_file_done", - @"Adding %[file]... done", - @"file", fileName)]; - } - - [output close]; - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - [_archive close]; -} -@end DELETED utils/ofzip/ZIPArchive.h Index: utils/ofzip/ZIPArchive.h ================================================================== --- utils/ofzip/ZIPArchive.h +++ utils/ofzip/ZIPArchive.h @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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 DELETED utils/ofzip/ZIPArchive.m Index: utils/ofzip/ZIPArchive.m ================================================================== --- utils/ofzip/ZIPArchive.m +++ utils/ofzip/ZIPArchive.m @@ -1,487 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018 - * 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" - -#include - -#import "OFApplication.h" -#import "OFDate.h" -#import "OFFileManager.h" -#import "OFLocalization.h" -#import "OFNumber.h" -#import "OFSet.h" -#import "OFStdIOStream.h" -#import "OFString.h" - -#import "ZIPArchive.h" -#import "OFZIP.h" - -#import "OFInvalidFormatException.h" -#import "OFOpenItemFailedException.h" -#import "OFOutOfRangeException.h" - -static OFZIP *app; - -static void -setPermissions(OFString *path, OFZIPArchiveEntry *entry) -{ -#ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS - if (([entry versionMadeBy] >> 8) == - OF_ZIP_ARCHIVE_ENTRY_ATTR_COMPAT_UNIX) { - uint16_t mode = [entry versionSpecificAttributes] >> 16; - of_file_attribute_key_t key = - of_file_attribute_key_posix_permissions; - of_file_attributes_t attributes = [OFDictionary - dictionaryWithObject: [OFNumber numberWithUInt16: mode] - forKey: key]; - - [[OFFileManager defaultManager] setAttributes: attributes - ofItemAtPath: path]; - } -#endif -} - -@implementation ZIPArchive -+ (void)initialize -{ - if (self == [ZIPArchive class]) - app = (OFZIP *)[[OFApplication sharedApplication] delegate]; -} - -+ (instancetype)archiveWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - return [[[self alloc] initWithStream: stream - mode: mode - encoding: encoding] autorelease]; -} - -- (instancetype)initWithStream: (OF_KINDOF(OFStream *))stream - mode: (OFString *)mode - encoding: (of_string_encoding_t)encoding -{ - self = [super init]; - - @try { - _archive = [[OFZIPArchive alloc] initWithStream: stream - mode: mode]; - } @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 *compressedSize = [OFString - stringWithFormat: @"%" PRIu64, - [entry compressedSize]]; - OFString *uncompressedSize = [OFString - stringWithFormat: @"%" PRIu64, - [entry uncompressedSize]]; - OFString *compressionMethod = - of_zip_archive_entry_compression_method_to_string( - [entry compressionMethod]); - OFString *CRC32 = [OFString - stringWithFormat: @"%08" PRIX32, [entry CRC32]]; - OFString *modificationDate = [[entry modificationDate] - localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_compressed_size", - @"Compressed: %[size] bytes", - @"size", compressedSize)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_uncompressed_size", - @"Uncompressed: %[size] bytes", - @"size", uncompressedSize)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_compression_method", - @"Compression method: %[method]", - @"method", compressionMethod)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED(@"list_crc32", - @"CRC32: %[crc32]", - @"crc32", CRC32)]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_modification_date", - @"Modification date: %[date]", - @"date", modificationDate)]; - - if (app->_outputLevel >= 2) { - uint16_t versionMadeBy = [entry versionMadeBy]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_version_made_by", - @"Version made by: %[version]", - @"version", - of_zip_archive_entry_version_to_string( - versionMadeBy))]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_min_version_needed", - @"Minimum version needed: %[version]", - @"version", - 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; - OFString *modeString = [OFString - stringWithFormat: @"%06o", mode]; - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_mode", - @"Mode: %[mode]", - @"mode", modeString)]; - } - } - - if (app->_outputLevel >= 3) { - OFString *GPBF = [OFString stringWithFormat: - @"%04" PRIx16, - [entry generalPurposeBitFlag]]; - - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_general_purpose_bit_flag", - @"General purpose bit flag: %[gpbf]", - @"gpbf", GPBF)]; - - if ([entry extraField] != nil) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_extra_field", - @"Extra field: %[extra]", - @"extra", [entry extraField])]; - } - } - - if ([[entry fileComment] length] > 0) { - [of_stdout writeString: @"\t"]; - [of_stdout writeLine: OF_LOCALIZED( - @"list_comment", - @"Comment: %[comment]", - @"comment", [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, *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]; - - outFileName = [app safeLocalPathForPath: fileName]; - if (outFileName == nil) { - [of_stderr writeLine: OF_LOCALIZED( - @"refusing_to_extract_file", - @"Refusing to extract %[file]!", - @"file", fileName)]; - - app->_exitStatus = 1; - goto outer_loop_end; - } - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"extracting_file", - @"Extracting %[file]...", - @"file", fileName)]; - - if ([fileName hasSuffix: @"/"]) { - [fileManager createDirectoryAtPath: outFileName - createParents: true]; - setPermissions(outFileName, entry); - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done", - @"file", fileName)]; - } - - 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: @"w"]; - 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) { - OFString *percentString; - - percent = newPercent; - percentString = [OFString stringWithFormat: - @"%3u", percent]; - - [of_stdout writeString: @"\r"]; - [of_stdout writeString: OF_LOCALIZED( - @"extracting_file_percent", - @"Extracting %[file]... %[percent]%", - @"file", fileName, - @"percent", percentString)]; - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"extracting_file_done", - @"Extracting %[file]... done\n", - @"file", fileName)]; - } - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - if ([missing count] > 0) { - for (OFString *file in missing) - [of_stderr writeLine: OF_LOCALIZED( - @"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", file)]; - - app->_exitStatus = 1; - } -} - -- (void)printFiles: (OFArray OF_GENERIC(OFString *) *)files -{ - OFStream *stream; - - if ([files count] < 1) { - [of_stderr writeLine: OF_LOCALIZED(@"print_no_file_specified", - @"Need one or more files to print!")]; - app->_exitStatus = 1; - return; - } - - for (OFString *path in files) { - @try { - stream = [_archive streamForReadingFile: path]; - } @catch (OFOpenItemFailedException *e) { - if ([e errNo] == ENOENT) { - [of_stderr writeLine: OF_LOCALIZED( - @"file_not_in_archive", - @"File %[file] is not in the archive!", - @"file", [e path])]; - app->_exitStatus = 1; - continue; - } - - @throw e; - } - - while (![stream isAtEndOfStream]) { - ssize_t length = [app copyBlockFromStream: stream - toStream: of_stdout - fileName: path]; - - if (length < 0) { - app->_exitStatus = 1; - return; - } - } - - [stream close]; - } -} - -- (void)addFiles: (OFArray OF_GENERIC(OFString *) *)files -{ - OFFileManager *fileManager = [OFFileManager defaultManager]; - - if ([files count] < 1) { - [of_stderr writeLine: OF_LOCALIZED(@"add_no_file_specified", - @"Need one or more files to add!")]; - app->_exitStatus = 1; - return; - } - - for (OFString *localFileName in files) { - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC (OFString *) *components; - OFString *fileName; - of_file_attributes_t attributes; - bool isDirectory = false; - OFMutableZIPArchiveEntry *entry; - uintmax_t size; - OFStream *output; - - components = [localFileName pathComponents]; - fileName = [components componentsJoinedByString: @"/"]; - - attributes = [fileManager - attributesOfItemAtPath: localFileName]; - - if ([[attributes fileType] isEqual: of_file_type_directory]) { - isDirectory = true; - fileName = [fileName stringByAppendingString: @"/"]; - } - - if (app->_outputLevel >= 0) - [of_stdout writeString: OF_LOCALIZED(@"adding_file", - @"Adding %[file]...", - @"file", fileName)]; - - entry = [OFMutableZIPArchiveEntry entryWithFileName: fileName]; - - if (isDirectory) - size = 0; - else - size = [attributes fileSize]; - - if (size > INT64_MAX) - @throw [OFOutOfRangeException exception]; - - [entry setCompressedSize: (int64_t)size]; - [entry setUncompressedSize: (int64_t)size]; - - [entry setCompressionMethod: - OF_ZIP_ARCHIVE_ENTRY_COMPRESSION_METHOD_NONE]; - [entry setModificationDate: [attributes fileModificationDate]]; - - [entry makeImmutable]; - - output = [_archive streamForWritingEntry: entry]; - - if (!isDirectory) { - uintmax_t written = 0; - int8_t percent = -1, newPercent; - - OFFile *input = [OFFile fileWithPath: fileName - mode: @"r"]; - - while (![input isAtEndOfStream]) { - ssize_t length = [app - copyBlockFromStream: input - 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) { - OFString *percentString; - - percent = newPercent; - percentString = [OFString - stringWithFormat: @"%3u", percent]; - - [of_stdout writeString: @"\r"]; - [of_stdout writeString: OF_LOCALIZED( - @"adding_file_percent", - @"Adding %[file]... %[percent]%", - @"file", fileName, - @"percent", percentString)]; - } - } - } - - if (app->_outputLevel >= 0) { - [of_stdout writeString: @"\r"]; - [of_stdout writeLine: OF_LOCALIZED( - @"adding_file_done", - @"Adding %[file]... done", - @"file", fileName)]; - } - - [output close]; - -outer_loop_end: - objc_autoreleasePoolPop(pool); - } - - [_archive close]; -} -@end DELETED utils/ofzip/lang/de.json Index: utils/ofzip/lang/de.json ================================================================== --- utils/ofzip/lang/de.json +++ utils/ofzip/lang/de.json @@ -1,110 +0,0 @@ -{ - "usage": [ - "Benutzung: %[prog] -[acCfhlnpqtvx] archiv.zip [datei1 datei2 ...]" - ], - "full_usage": [ - "Optionen:\n", - " -a --append Zu Archiv hinzufügen\n", - " -c --create Archiv erstellen\n", - " -C --directory In angegebenes Verzeichnis entpacken\n", - " -E --encoding Das Encoding des Archivs (nur tar-Dateien)\n", - " -f --force Existierende Dateien überschreiben\n", - " -h --help Diese Hilfe anzeigen\n", - " -l --list Alle Dateien im Archiv auflisten\n", - " -n --no-clobber Dateien niemals überschreiben\n", - " -p --print Eine oder mehr Dateien aus dem Archiv ausgeben", - "\n", - " -q --quiet Ruhiger Modus (keine Ausgabe außer Fehler)\n", - " -t --type Archiv-Typ (gz, lha, tar, tgz, zip)\n", - " -v --verbose Ausführlicher Modus für Datei-Liste\n", - " -x --extract Dateien entpacken" - ], - "2_options_mutually_exclusive": [ - "Fehler: -%[shortopt1] / --%[longopt1] und ", - "-%[shortopt2] / --%[longopt2] schließen sich gegenseitig aus!" - ], - "5_options_mutually_exclusive": [ - "Fehler: -%[shortopt1] / --%[longopt1], -%[shortopt2] / ", - "--%[longopt2], -%[shortopt3] / --%[longopt3], ", - "-%[shortopt4] / --%[longopt4] und\n", - " -%[shortopt5] / --%[longopt5] schließen sich gegenseitig aus!" - ], - "option_takes_no_argument": "%[prog]: Option --%[opt] nimmt kein Argument", - "long_option_requires_argument": [ - "%[prog]: Option --%[opt] benötigt ein Argument" - ], - "option_requires_argument": "%[prog]: Option -%[opt] benötigt ein Argument", - "unknown_long_option": "%[prog]: Unbekannte Option: --%[opt]", - "unknown_option": "%[prog]: Unbekannte Option: -%[opt]", - "invalid_encoding": "%[prog]: Invalid encoding: %[encoding]", - "writing_not_supported": [ - "Schreiben von Dateien des Typs %[type] wird (noch) nicht unterstützt!" - ], - "failed_to_create_directory": [ - "Fehler beim Erstellen des Verzeichnis %[dir]: %[error]" - ], - "failed_to_open_file": "Fehler beim Öffnen der Datei %[file]: %[error]", - "unknown_archive_type": "Unbekannter Archivtyp: %[type]", - "failed_to_read_file": "Fehler beim Lesen der Datei %[file]: %[error]", - "failed_to_write_file": "Fehler beim Schreiben der Datei %[file]: %[error]", - "failed_to_seek_in_file": "Fehler beim Suchen in Datei %[file]: %[error]", - "file_is_not_a_valid_archive": "Datei %[file] ist kein gültiges Archiv!", - "file_skipped": "übersprungen", - "ask_overwrite": "%[file] überschreiben? [ynAN?]", - "ask_overwrite_help": [ - " y: Ja\n", - " n: Nein\n", - " A: Immer\n", - " N: Nie" - ], - "skipping_file": "Überspringe %[file]...", - "extracting_file": "Entpacke %[file]...", - "extracting_file_percent": "Entpacke %[file]... %[percent]%", - "extracting_file_done": "Entpacke %[file]... fertig", - "cannot_list_gz": "Kann Dateien eines .gz-Archivs nicht auflisten!", - "cannot_extract_specific_file_from_gz": [ - "Kann keine spezifische Datei aus einem .gz-Archiv entpacken!" - ], - "cannot_print_specific_file_from_gz": [ - "Kann keine spezifische Datei aus einem .gz-Archiv ausgeben!" - ], - "list_size": "Größe: %[size] Bytes", - "list_mode": "Modus: %[mode]", - "list_owner": "Besitzer: %[owner]", - "list_group": "Gruppe: %[group]", - "list_header_level": "Header-Level: %[level]", - "list_modification_date": "Änderungsdatum: %[date]", - "list_type_normal": "Typ: Normale Datei", - "list_type_hardlink": "Typ: Harter Link", - "list_type_symlink": "Typ: Symbolischer Link", - "list_link_target": "Zieldateiname: %[target]", - "list_type_character_device": "Typ: Zeichenorientiertes Gerät", - "list_type_block_device": "Typ: Blockorientiertes Gerät", - "list_device_major": "Major-Nummer des Geräts: %[major]", - "list_device_minor": "Minor-Nummer des Geräts: %[minor]", - "list_type_directory": "Typ: Verzeichnis", - "list_type_fifo": "Typ: FIFO", - "list_type_contiguous_file": "Typ: Zusammenhängende Datei", - "list_type_unknown": "Typ: Unbekannt", - "list_compressed_size": "Komprimierte Größe: %[size] Bytes", - "list_uncompressed_size": "Unkomprimierte Größe: %[size] Bytes", - "list_compression_method": "Kompressionsmethode: %[method]", - "list_date": "Datum: %[date]", - "list_osid": "Betriebssystem-Identifikator: %[osid]", - "list_version_made_by": "Erstellt mit Version: %[version]", - "list_min_version_needed": "Mindestens benötigte Version: %[version]", - "list_general_purpose_bit_flag": "General Purpose Bit Flag: %[gpbf]", - "list_extra_field": "Extra-Feld: %[extra]", - "list_comment": "Kommentar: %[comment]", - "refusing_to_extract_file": "Verweigere Entpacken von %[file]!", - "file_not_in_archive": "Datei %[file] ist nicht im Archiv!", - "print_no_file_specified": [ - "Benötige eine oder mehrere Dateien zum Ausgeben!" - ], - "add_no_file_specified": [ - "Benötige eine oder mehrere Dateien zum Hinzufügen!" - ], - "adding_file": "Füge %[file] hinzu...", - "adding_file_percent": "Füge %[file] hinzu... %[percent]%", - "adding_file_done": "Füge %[file] hinzu... fertig" -} DELETED utils/ofzip/lang/languages.json Index: utils/ofzip/lang/languages.json ================================================================== --- utils/ofzip/lang/languages.json +++ utils/ofzip/lang/languages.json @@ -1,11 +0,0 @@ -{ - "de": { - "": "de" - }, - "deutsch": { - "": "de" - }, - "german": { - "": "de" - } -}