/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 * Jonathan Schleifer <js@heap.zone> * * 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 "OFGZIPStream.h" #import "OFDeflateStream.h" #import "OFDate.h" #import "crc32.h" #import "OFChecksumFailedException.h" #import "OFInvalidFormatException.h" @implementation OFGZIPStream + (instancetype)streamWithStream: (OFStream *)stream { return [[[self alloc] initWithStream: stream] autorelease]; } - init { OF_INVALID_INIT_METHOD } - initWithStream: (OFStream *)stream { self = [super init]; @try { _stream = [stream retain]; _CRC32 = ~0; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_stream release]; [_inflateStream release]; [_modificationDate release]; [super dealloc]; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { uint8_t byte; for (;;) { switch (_state) { case OF_GZIP_STREAM_ID1: case OF_GZIP_STREAM_ID2: case OF_GZIP_STREAM_COMPRESSION_METHOD: if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; if ((_state == OF_GZIP_STREAM_ID1 && byte != 0x1F) || (_state == OF_GZIP_STREAM_ID2 && byte != 0x8B) || (_state == OF_GZIP_STREAM_COMPRESSION_METHOD && byte != 8)) @throw [OFInvalidFormatException exception]; _state++; break; case OF_GZIP_STREAM_FLAGS: if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; _flags = byte; _state++; break; case OF_GZIP_STREAM_MODIFICATION_TIME: _bytesRead += [_stream readIntoBuffer: _buffer + _bytesRead length: 4 - _bytesRead]; if (_bytesRead < 4) return 0; _modificationDate = [[OFDate alloc] initWithTimeIntervalSince1970: (_buffer[3] << 24) | (_buffer[2] << 16) | (_buffer[1] << 8) | _buffer[0]]; _bytesRead = 0; _state++; break; case OF_GZIP_STREAM_EXTRA_FLAGS: if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; _extraFlags = byte; _state++; break; case OF_GZIP_STREAM_OS: if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; _OS = byte; _state++; break; case OF_GZIP_STREAM_EXTRA_LENGTH: if (!(_flags & OF_GZIP_STREAM_FLAG_EXTRA)) { _state += 2; break; } _bytesRead += [_stream readIntoBuffer: _buffer + _bytesRead length: 2 - _bytesRead]; if (_bytesRead < 2) return 0; _extraLength = (_buffer[1] << 8) | _buffer[0]; _bytesRead = 0; _state++; break; case OF_GZIP_STREAM_EXTRA: { char tmp[512]; size_t toRead = _extraLength - _bytesRead; if (toRead > 512) toRead = 512; _bytesRead += [_stream readIntoBuffer: tmp length: toRead]; } if (_bytesRead < _extraLength) return 0; _bytesRead = 0; _state++; break; case OF_GZIP_STREAM_NAME: if (!(_flags & OF_GZIP_STREAM_FLAG_NAME)) { _state++; break; } do { if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; } while (byte != 0); _state++; break; case OF_GZIP_STREAM_COMMENT: if (!(_flags & OF_GZIP_STREAM_FLAG_COMMENT)) { _state++; break; } do { if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; } while (byte != 0); _state++; break; case OF_GZIP_STREAM_HEADER_CRC16: if (!(_flags & OF_GZIP_STREAM_FLAG_HEADER_CRC16)) { _state++; break; } _bytesRead += [_stream readIntoBuffer: _buffer + _bytesRead length: 2 - _bytesRead]; if (_bytesRead < 2) return 0; /* * Header CRC16 is not checked, as I could not find a * single file in the wild that actually has a header * CRC16 - and thus no file to test against. */ _bytesRead = 0; _state++; break; case OF_GZIP_STREAM_DATA: if (_inflateStream == nil) _inflateStream = [[OFDeflateStream alloc] initWithStream: _stream]; if (![_inflateStream isAtEndOfStream]) { size_t bytesRead = [_inflateStream readIntoBuffer: buffer length: length]; _CRC32 = of_crc32(_CRC32, buffer, bytesRead); _uncompressedSize += bytesRead; return bytesRead; } [_inflateStream release]; _inflateStream = nil; _state++; break; case OF_GZIP_STREAM_CRC32: _bytesRead += [_stream readIntoBuffer: _buffer length: 4 - _bytesRead]; if (_bytesRead < 4) return 0; if ((((uint32_t)_buffer[3] << 24) | (_buffer[2] << 16) | (_buffer[1] << 8) | _buffer[0]) != ~_CRC32) @throw [OFChecksumFailedException exception]; _bytesRead = 0; _CRC32 = ~0; _state++; break; case OF_GZIP_STREAM_UNCOMPRESSED_SIZE: _bytesRead += [_stream readIntoBuffer: _buffer length: 4 - _bytesRead]; if ((((uint32_t)_buffer[3] << 24) | (_buffer[2] << 16) | (_buffer[1] << 8) | _buffer[0]) != _uncompressedSize) @throw [OFChecksumFailedException exception]; _bytesRead = 0; _uncompressedSize = 0; _state = OF_GZIP_STREAM_ID1; break; } } } - (bool)lowlevelIsAtEndOfStream { return [_stream isAtEndOfStream]; } - (bool)hasDataInReadBuffer { if (_state == OF_GZIP_STREAM_DATA) return ([super hasDataInReadBuffer] || [_inflateStream hasDataInReadBuffer]); return ([super hasDataInReadBuffer] || [_stream hasDataInReadBuffer]); } @end