@@ -1,7 +1,7 @@ /* - * Copyright (c) 2008-2023 Jonathan Schleifer + * Copyright (c) 2008-2024 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 @@ -24,10 +24,11 @@ #import "OFLHAArchiveEntry+Private.h" #import "OFArchiveIRIHandler.h" #import "OFCRC16.h" #import "OFIRI.h" #import "OFIRIHandler.h" +#import "OFKernelEventObserver.h" #import "OFLHADecompressingStream.h" #import "OFSeekableStream.h" #import "OFStream.h" #import "OFString.h" @@ -35,10 +36,11 @@ #import "OFInvalidArgumentException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" +#import "OFUnsupportedVersionException.h" #import "OFWriteFailedException.h" enum { modeRead, modeWrite, @@ -122,11 +124,17 @@ if ((_mode == modeWrite || _mode == modeAppend) && ![_stream isKindOfClass: [OFSeekableStream class]]) @throw [OFInvalidArgumentException exception]; if (_mode == modeAppend) - [(OFSeekableStream *)_stream seekToOffset: 0 + /* + * Only works with properly zero-terminated files that + * have no trailing garbage. Unfortunately there is no + * good way to check for this other than reading the + * entire archive. + */ + [(OFSeekableStream *)_stream seekToOffset: -1 whence: OFSeekEnd]; _encoding = OFStringEncodingISO8859_1; } @catch (id e) { [self release]; @@ -213,10 +221,20 @@ headerLen += [_stream readIntoBuffer: header + headerLen length: 21 - headerLen]; } + /* + * Some archives have trailing garbage after the single byte 0 + * termination. However, a level 2 header uses 2 bytes for the size, so + * could just have a header size that is a multiple of 256. Therefore, + * consider it only the end of the archive if what follows would not be + * a level 2 header. + */ + if (header[0] == 0 && header[20] != 2) + return nil; + _currentEntry = [[OFLHAArchiveEntry alloc] of_initWithHeader: header stream: _stream encoding: _encoding]; @@ -265,10 +283,11 @@ _lastReturnedStream = [[[OFLHAArchiveFileWriteStream alloc] of_initWithArchive: self stream: (OFSeekableStream *)_stream entry: entry encoding: _encoding] autorelease]; + _hasWritten = true; return _lastReturnedStream; } - (void)close @@ -277,12 +296,17 @@ @throw [OFNotOpenException exceptionWithObject: self]; @try { [_lastReturnedStream close]; } @catch (OFNotOpenException *e) { - /* Might have already been closed by the user - that's fine. */ + /* Might have already been closed by the user - that's fine */ } + + /* LHA archives should be terminated with a header of size 0 */ + if (_hasWritten) + [_stream writeBuffer: "" length: 1]; + _lastReturnedStream = nil; [_stream release]; _stream = nil; } @@ -317,12 +341,16 @@ else if ([compressionMethod isEqual: @"-lh7-"]) _decompressedStream = [[OFLHADecompressingStream alloc] of_initWithStream: stream distanceBits: 5 dictionaryBits: 17]; - else + else if ([compressionMethod isEqual: @"-lh0-"] || + [compressionMethod isEqual: @"-lhd-"]) _decompressedStream = [stream retain]; + else + @throw [OFUnsupportedVersionException + exceptionWithVersion: compressionMethod]; _entry = [entry copy]; _toRead = entry.uncompressedSize; } @catch (id e) { [self release]; @@ -332,11 +360,11 @@ return self; } - (void)dealloc { - if (_stream != nil || _decompressedStream != nil) + if (_stream != nil && _decompressedStream != nil) [self close]; [_entry release]; if (_archive->_lastReturnedStream == self)