//
// $Id: GzipInStream.m,v 1.5 2005/05/18 06:46:52 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#import "GzipInStream.h"
#import "Map.h"
#import "Macros.h"
#import "ByteOrder.h"
#import "InStreamPackage.h"
#import <Foundation/NSString.h>
#import <Foundation/NSException.h>
#import <Foundation/NSData.h>
#import <zlib.h>
#import <string.h>
#import <limits.h>

@interface OLGzipInStream (PrivateMethods)

- (void) readExtraFieldsWithCRC: (unsigned long*)lcrc;
- (void) readHeader;
- (NSString*) readHeaderStringWithCRC: (unsigned long*)lcrc;
- (uint16_t) readLE16WithCRC: (unsigned long*)lcrc;
- (uint32_t) readLE32WithCRC: (unsigned long*)lcrc;
- (void) readTrailer;

@end

@implementation OLGzipInStream

+ (id) streamWithInStream: (OLInStream*)underStream
{
    OL_BEGIN_AUTO_CTOR(OLGzipInStream)
        initWithInStream: underStream
    OL_END_AUTO_CTOR;
}

- (id) initWithInStream: (OLInStream*)underStream bufferSize: (unsigned)size readZlibHeader: (BOOL)zlibHeader
{
    [super initWithInStream: underStream bufferSize: size readZlibHeader: NO];
    fileName = nil;
    comment = nil;
    extraFields = nil;
    modTime = 0;
    crc = crc32(0, Z_NULL, 0);
    endOfStream = NO;
    [self readHeader];
    return self;
}

- (void) dealloc
{
    [allocator deallocateObject: fileName];
    [allocator deallocateObject: comment];
    [allocator deallocateObject: extraFields];
    [super dealloc];
}

- (NSString*) comment
{
    return comment;
}

- (NSData*) extraField: (const char*)identifier
{
    NSString* idObj;
    NSData* found;

    if (extraFields == nil)
        return nil;
    if (strlen(identifier) != 2)
    {
        [NSException raise: OLInputOutputException
            format: @"Illegal extra field identifier"];
    }
    idObj = [[allocator allocateObject: [NSString class]]
        initWithBytes: (char*)identifier length: 2 encoding: NSASCIIStringEncoding];
    found = [extraFields valueForKey: idObj];
    [allocator deallocateObject: idObj];
    return found;
}

- (uint32_t) modificationTime
{
    return modTime;
}

- (NSString*) originalFileName
{
    return fileName;
}

- (unsigned) readBytes: (uint8_t*)dest count: (unsigned)max
{
    unsigned numRead;

    if (endOfStream)
        return UINT_MAX;
    numRead = [super readBytes: dest count: max];
    if (numRead == UINT_MAX)
    {
        [self readTrailer];
        endOfStream = YES;
    }
    else
    {
        crc = crc32(crc, dest, numRead);
    }
    return numRead;
}

@end

@implementation OLGzipInStream (PrivateMethods)

- (void) readExtraFieldsWithCRC: (unsigned long*)lcrc
{
    uint16_t xlen = [self readLE16WithCRC: lcrc];
    uint16_t total = 0;
    char idBuf[2];
    NSMutableData* data;
    NSString* key;
    uint16_t dataLen;

    extraFields = [[allocator allocateObject: [OLMap class]] init];
    while (total < xlen)
    {
        [stream completelyReadBytes: idBuf count: 2];
        *lcrc = crc32(*lcrc, idBuf, 2);
        total += 2;
        dataLen = [self readLE16WithCRC: lcrc];
        total += 2;
        data = [[allocator allocateObject: [NSMutableData class]]
            initWithLength: dataLen];
        [stream completelyReadBytes: [data mutableBytes] count: dataLen];
        *lcrc = crc32(*lcrc, [data mutableBytes], dataLen);
        total += dataLen;
        key = [[allocator allocateObject: [NSString class]]
            initWithBytes: idBuf length: 2 encoding: NSASCIIStringEncoding];
        [extraFields assignKey: key value: data];
        [allocator deallocateObject: key];
        [allocator deallocateObject: data];
    }
}

- (void) readHeader
{
    uint8_t flags;
    unsigned long hcrc = crc32(0, Z_NULL, 0);
    uint8_t hbuf[] = { GZ_ID1, GZ_ID2, Z_DEFLATED };
    uint16_t fcrc;

    if ([stream readByte] != GZ_ID1)
    {
        [NSException raise: OLInputOutputException
            format: @"Data not in gzip format"];
    }
    if ([stream readByte] != GZ_ID2)
    {
        [NSException raise: OLInputOutputException
            format: @"Data not in gzip format"];
    }
    if ([stream readByte] != Z_DEFLATED)
    {
        [NSException raise: OLInputOutputException
            format: @"Unsupported compression method in gzip data"];
    }
    hcrc = crc32(hcrc, hbuf, 3);
    flags = [stream readByte];
    hcrc = crc32(hcrc, &flags, 1);
    modTime = [self readLE32WithCRC: &hcrc];
    hbuf[0] = [stream readByte];
    hbuf[1] = [stream readByte];
    hcrc = crc32(hcrc, hbuf, 2);
    if (flags & GZ_FEXTRA)
        [self readExtraFieldsWithCRC: &hcrc];
    if (flags & GZ_FNAME)
        fileName = [self readHeaderStringWithCRC: &hcrc];
    if (flags & GZ_FCOMMENT)
        comment = [self readHeaderStringWithCRC: &hcrc];
    if (flags & GZ_FHCRC)
    {
        fcrc = [self readLE16WithCRC: NULL];
        if (fcrc != (hcrc & 0xFFFF))
        {
            [NSException raise: OLInputOutputException
                format: @"Corrupt gzip header"];
        }
    }
}

- (NSString*) readHeaderStringWithCRC: (unsigned long*)lcrc
{
    unsigned size = 0;
    unsigned capacity = 100;
    uint8_t* buf = [allocator allocateBytes: capacity];
    uint8_t* newBuffer;
    NSString* str;
    uint8_t b = [stream readByte];

    *lcrc = crc32(*lcrc, &b, 1);
    while (b != 0)
    {
        if (size == capacity)
        {
            capacity = capacity * 2;
            newBuffer = [allocator allocateBytes: capacity];
            memcpy(newBuffer, buf, size);
            [allocator deallocateBytes: buf];
            buf = newBuffer;
        }
        buf[size++] = b;
        b = [stream readByte];
        *lcrc = crc32(*lcrc, &b, 1);
    }
    str = [[allocator allocateObject: [NSString class]]
        initWithBytes: buf length: size encoding: NSISOLatin1StringEncoding];
    [allocator deallocateBytes: buf];
    return str;
}

- (uint16_t) readLE16WithCRC: (unsigned long*)lcrc
{
    uint16_t unswapped;

    [stream completelyReadBytes: (uint8_t*)&unswapped count: 2];
    if (lcrc != NULL)
        *lcrc = crc32(*lcrc, (uint8_t*)&unswapped, 2);
    return L16_TO_H(unswapped);
}

- (uint32_t) readLE32WithCRC: (unsigned long*)lcrc
{
    uint32_t unswapped;

    [stream completelyReadBytes: (uint8_t*)&unswapped count: 4];
    if (lcrc != NULL)
        *lcrc = crc32(*lcrc, (uint8_t*)&unswapped, 4);
    return L32_TO_H(unswapped);
}

- (void) readTrailer
{
    uint8_t trailerBuf[8];
    unsigned remainder = 8 - zstream->avail_in;

    if (remainder < 8)
        memcpy(trailerBuf, zstream->next_in, zstream->avail_in);
    [stream completelyReadBytes: trailerBuf + zstream->avail_in count: remainder];
    if (L32_TO_H(*(uint32_t*)trailerBuf) != crc)
    {
        [NSException raise: OLInputOutputException
            format: @"The CRC of the uncompressed data is inconsistent"];
    }
    if (L32_TO_H(*(uint32_t*)(trailerBuf + 4)) != zstream->total_out)
    {
        [NSException raise: OLInputOutputException
            format: @"The number of uncompressed bytes is inconsistent"];
    }
}

@end
