ADDED utils/ofzip/LHAArchive.h Index: utils/ofzip/LHAArchive.h ================================================================== --- utils/ofzip/LHAArchive.h +++ utils/ofzip/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/ofzip/LHAArchive.m Index: utils/ofzip/LHAArchive.m ================================================================== --- utils/ofzip/LHAArchive.m +++ utils/ofzip/LHAArchive.m @@ -0,0 +1,359 @@ +/* + * 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 + of_file_attributes_t attributes = [OFDictionary + dictionaryWithObject: [entry 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]; + + [_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 compressedSize]]; + 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_crc16", + @"CRC16: %[crc16]", + @"crc16", CRC16)]; + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_date", + @"Date: %[date]", + @"date", date)]; + + if ([entry mode] != nil) { + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED(@"list_mode", + @"Mode: %[mode]", + @"mode", [entry mode])]; + } + 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 *level = [OFString stringWithFormat: + @"%" PRIu8, [entry level]]; + OFString *OSID = [OFString stringWithFormat: + @"%c", [entry operatingSystemIdentifier]]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_level", + @"Level: %[level]", + @"level", level)]; + + [of_stdout writeString: @"\t"]; + [of_stdout writeLine: OF_LOCALIZED( + @"list_method", + @"Method: %[method]", + @"method", [entry method])]; + + [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 Index: utils/ofzip/Makefile ================================================================== --- utils/ofzip/Makefile +++ utils/ofzip/Makefile @@ -1,9 +1,10 @@ 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 Index: utils/ofzip/OFZIP.m ================================================================== --- utils/ofzip/OFZIP.m +++ utils/ofzip/OFZIP.m @@ -29,10 +29,11 @@ #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" @@ -71,11 +72,12 @@ @" -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, tar, tgz, zip)\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]; @@ -440,10 +442,12 @@ 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"; } @@ -451,10 +455,14 @@ @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"]) { Index: utils/ofzip/TarArchive.m ================================================================== --- utils/ofzip/TarArchive.m +++ utils/ofzip/TarArchive.m @@ -322,16 +322,15 @@ if (![app shouldExtractFile: fileName outFileName: outFileName]) goto outer_loop_end; + stream = [_archive streamForReadingCurrentEntry]; output = [OFFile fileWithPath: outFileName mode: @"w"]; setPermissions(outFileName, entry); - stream = [_archive streamForReadingCurrentEntry]; - while (![stream isAtEndOfStream]) { ssize_t length = [app copyBlockFromStream: stream toStream: output fileName: fileName]; Index: utils/ofzip/ZIPArchive.m ================================================================== --- utils/ofzip/ZIPArchive.m +++ utils/ofzip/ZIPArchive.m @@ -109,11 +109,11 @@ [entry compressedSize]]; OFString *uncompressedSize = [OFString stringWithFormat: @"%" PRIu64, [entry uncompressedSize]]; OFString *CRC32 = [OFString - stringWithFormat: @"%08X", [entry CRC32]]; + 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( Index: utils/ofzip/lang/de.json ================================================================== --- utils/ofzip/lang/de.json +++ utils/ofzip/lang/de.json @@ -13,11 +13,11 @@ " -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, tar, tgz, zip)\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 ", @@ -85,10 +85,13 @@ "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_date": "Datum: %[date]", + "list_method": "Methode: %[method]", + "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]",