/* GZIO.c
 *
 * Based on gzio.c (part of zlib) written by Jean-loup Gailly.
 * Amiga/AsyncIO version by Magnus Holmgren <lear@algonet.se>
 *
 * Copyright notice for zlib:
 *
 *  (C) 1995-1998 Jean-loup Gailly and Mark Adler
 *
 *   This software is provided 'as-is', without any express or implied
 *   warranty.  In no event will the authors be held liable for any damages
 *   arising from the use of this software.
 *
 *   Permission is granted to anyone to use this software for any purpose,
 *   including commercial applications, and to alter it and redistribute it
 *   freely, subject to the following restrictions:
 *
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *   2. Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *   3. This notice may not be removed or altered from any source distribution.
 *
 *   Jean-loup Gailly        Mark Adler
 *   jloup@gzip.org          madler@alumni.caltech.edu
 */

#include <clib/asyncio_protos.h>
#include <exec/memory.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <stdio.h>
#include <string.h>
#include <zutil.h>
#include "untar.h"


/* GZip magic header */
LOCAL const UBYTE
GZMagic[ 2 ] =
{
	0x1f, 0x8b
};


/* GZip flag byte */
#define GZF_ASCII	0x01	/* bit 0 set: file probably ascii text */
#define GZF_HEAD_CRC	0x02	/* bit 1 set: header CRC present */
#define GZF_EXTRA_FIELD	0x04	/* bit 2 set: extra field present */
#define GZF_ORIG_NAME	0x08	/* bit 3 set: original file name present */
#define GZF_COMMENT	0x10	/* bit 4 set: file comment present */
#define GZF_RESERVED	0xE0	/* bits 5..7: reserved */


/* Using the alloc/free stubs in z_stream will still causes the standard C
 * calloc/free to be linked. This way we avoid that (and z.lib is the only
 * module using these functions), saving a bunch of bytes.
 */
void *
calloc( size_t items, size_t size )
{
	return( MemAlloc( ( ULONG ) ( items * size ) ) );
}


void
free( void *ptr )
{
	MemFree( ptr );
}


/* Read a byte from a gzStream; update next_in and avail_in. Return EOF for
 * end of file.
 */
LOCAL LONG
ReadChar( gzStream *s )
{
	if( s->Eof )
	{
		return( EOF );
	}

	if( !s->Stream.avail_in )
	{
		SetIoErr( 0 );
		s->Stream.avail_in = ReadAsync( s->File, s->Buffer, s->BufferSize );

		if( !s->Stream.avail_in )
		{
			s->Eof = TRUE;

			if( IoErr() )
			{
				s->Err = Z_ERRNO;
			}

			return( EOF );
		}

		s->Stream.next_in = s->Buffer;
	}

	--( s->Stream.avail_in );
	return( *( s->Stream.next_in )++ );
}


/* Reads a long in LSB order from the given gz_stream. */
LOCAL LONG
ReadLong( gzStream *s )
{
	LONG	x, c;

	/* This could probably be optimized on the Amiga, but it isn't used much anyway */
	x  = ReadChar( s );
	x |= ReadChar( s ) << 8;
	x |= ReadChar( s ) << 16;
	c =  ReadChar( s );
	x |= c << 24;

	if( c == EOF )
	{
		s->Err = Z_DATA_ERROR;
	}

	return( x );
}


/* Check the gzip header of a gzStream opened for reading. Set the stream
 * mode to transparent if the gzip magic header is not present; set s->Err
 * to Z_DATA_ERROR if the magic header is present but the rest of the
 * header is incorrect.
 */
LOCAL VOID
CheckHeader( gzStream *s )
{
	ULONG	len;
	LONG	c, method, flags;

	/* Check the gzip magic header */
	for( len = 0; len < 2; ++len )
	{
		c = ReadChar( s );

		if( c != GZMagic[ len ] )
		{
			if( len )
			{
				++( s->Stream.avail_in );
				--( s->Stream.next_in );
			}

			if( c != EOF )
			{
				++( s->Stream.avail_in );
				--( s->Stream.next_in );
				s->Transparent = TRUE;
			}

			s->Err = s->Stream.avail_in != 0 ? Z_OK : Z_STREAM_END;
			return;
		}
	}

	method = ReadChar( s );
	flags = ReadChar( s );

	if( ( method != Z_DEFLATED ) || ( flags & GZF_RESERVED ) )
	{
		s->Err = Z_DATA_ERROR;
		return;
	}

	/* Discard time, xflags and OS code: */
	for( len = 0; len < 6; ++len )
	{
		ReadChar( s );
	}

	if( ( flags & GZF_EXTRA_FIELD ) != 0 )
	{
		/* skip the extra field */
		len  = ReadChar( s );
		len |= ReadChar( s ) << 8;

		/* len is garbage if EOF but the loop below will quit anyway */
		while( len-- && ( ReadChar( s ) != EOF ) )
		{
		}
	}

	if( flags & GZF_ORIG_NAME )
	{
		/* skip the original file name */
		while( ( c = ReadChar( s ) ) && ( c != EOF ) )
		{
		}
	}

	if( flags & GZF_COMMENT )
	{
		/* skip the .gz file comment */
		while( ( c = ReadChar( s ) ) && ( c != EOF ) )
		{
		}
	}

	if( flags & GZF_HEAD_CRC )
	{
		/* skip the header crc */
		for( len = 0; len < 2; ++len )
		{
			ReadChar( s );
		}
	}

	s->Err = s->Eof ? Z_DATA_ERROR : Z_OK;
}


/* Clean up and free the given gzStream. Return a zlib error code. Try
 * freeing in the reverse order of allocations.
 */
LONG
CloseGZ( gzStream *s )
{
	LONG	err = Z_OK;

	MemFree( s->Msg );

	if( s->Stream.state )
	{
		err = inflateEnd( &( s->Stream ) );
	}

	CloseAsync( s->File );

	if( s->Err < 0 )
	{
		err = s->Err;
	}

	MemFree( s->Buffer );
	MemFree( s->Path );
	MemFree( s );
	return( err );
}


/* Opens a gzip (.gz) file for reading. The bufsize argument gives the size
 * of the AsyncIO and zlib buffers to allocate. Returns NULL if the file
 * could not be opened or if there was not enough memory to allocate the
 * decompression state. IoErr() can be used to distinguish the two cases.
 * ERROR_NO_FREE_STORE indicates lack of memory.
 */
gzStream *
OpenGZ( const STRPTR name, LONG bufsize )
{
	gzStream	*s;
	LONG	err;

	if( !( s = MemAlloc( sizeof( gzStream ) ) ) )
	{
		return( NULL );
	}

	if( !( s->Path = MemAlloc( ( ULONG ) ( strlen( name ) + 1 ) ) ) )
	{
		CloseGZ( s );
		return( NULL );
	}

	s->Err = Z_OK;
	s->Crc = crc32( 0L, Z_NULL, 0 );
	s->BufferSize = bufsize;
	strcpy( s->Path, name );
	s->Stream.next_in = s->Buffer = MemAlloc( bufsize );

	/* windowBits is passed < 0 to tell that there is no zlib header.
	 * Note that in this case inflate *requires* an extra "dummy" byte
	 * after the compressed stream in order to complete decompression
	 * and return Z_STREAM_END. Here the gzip CRC32 ensures that 4
	 * bytes are present after the compressed stream.
	 */
	err = inflateInit2( &( s->Stream ), -MAX_WBITS );

	if( ( err != Z_OK ) || !s->Buffer )
	{
		CloseGZ( s );
		return( NULL );
        }

	s->Stream.avail_out = bufsize;

	/* We use bufsize * 2, so that one ReadGZ call typically can be done
	 * within one AsyncIO buffer.
	 */
	if( !( s->File = OpenAsync( name, MODE_READ, bufsize * 2 ) ) )
	{
		CloseGZ( s );
		return( NULL );
	}

	CheckHeader( s );	/* skip the .gz header */
	return( s );
}


/* Reads the given number of uncompressed bytes from the compressed file.
 * Returns number of bytes actually read (0 for end of file, -1 for error).
 */
ULONG
ReadGZ( gzStream *file, APTR buf, ULONG len )
{
	UBYTE	*start = buf;	/* starting point for crc computation */

	if( ( file->Err == Z_DATA_ERROR ) || ( file->Err == Z_ERRNO ) )
	{
		return( -1 );
	}

	if( file->Err == Z_STREAM_END )
	{
		return( 0 );	/* EOF */
	}

	file->Stream.next_out  = buf;
	file->Stream.avail_out = len;

	while( file->Stream.avail_out )
	{
		if( file->Transparent )
		{
			/* Copy the lookahead bytes: */
			ULONG	n;

			n = file->Stream.avail_in;

			if( n > file->Stream.avail_out )
			{
				n = file->Stream.avail_out;
			}

			if( n )
			{
				CopyMem( file->Stream.next_in, file->Stream.next_out, n );
				file->Stream.next_out  += n;
				file->Stream.next_in   += n;
				file->Stream.avail_out -= n;
				file->Stream.avail_in  -= n;
			}

			/* Read the rest */
			if( file->Stream.avail_out > 0 )
			{
				file->Stream.avail_out -=
					ReadAsync( file->File, file->Stream.next_out, ( ULONG ) file->Stream.avail_out );
			}

			len -= file->Stream.avail_out;
			file->Stream.total_in  += len;
			file->Stream.total_out += len;

			if( !len )
			{
				file->Eof = TRUE;
			}

			return( len );
		}

		/* Not transparent */

		if( !file->Stream.avail_in && !file->Eof )
		{
			SetIoErr( 0 );

			if( 0 >= ( file->Stream.avail_in
				= ReadAsync( file->File, file->Buffer, file->BufferSize ) ) )
			{
				file->Eof = TRUE;

				if( IoErr() )
				{
					file->Err = Z_ERRNO;
					break;
				}
			}

			file->Stream.next_in = file->Buffer;
		}

		file->Err = inflate( &( file->Stream ), Z_NO_FLUSH );

		if( file->Err == Z_STREAM_END )
		{
			/* Check CRC and original size */
			file->Crc = crc32( file->Crc, start, ( uInt ) ( file->Stream.next_out - start ) );
			start = file->Stream.next_out;

			if( ReadLong( file ) != file->Crc )
			{
				file->Err = Z_DATA_ERROR;
			}
			else
			{
				ReadLong( file );

				/* The uncompressed length returned by
				 * above ReadLong() may be different from
				 * file->Stream.total_out in case of
				 * concatenated .gz files. Check for such
				 * files:
				 */

				CheckHeader( file );

				if( file->Err == Z_OK )
				{
					ULONG	total_in = file->Stream.total_in;
					ULONG	total_out = file->Stream.total_out;

					inflateReset( &( file->Stream ) );
					file->Stream.total_in  = total_in;
					file->Stream.total_out = total_out;
					file->Crc = crc32( 0L, Z_NULL, 0 );
				}
			}
		}

		if( ( file->Err != Z_OK ) || file->Eof )
		{
			break;
		}
	}

	file->Crc = crc32( file->Crc, start, ( uInt ) ( file->Stream.next_out - start ) );

	/* Return error if appropriate. */
	return( ( file->Err && ( file->Err != Z_STREAM_END ) )
		? -1 : ( len - file->Stream.avail_out ) );
}


/* Returns the error message for the last error which occured on the given
 * compressed file.
 */
STRPTR
ErrorGZ( gzStream *file )
{
	STRPTR	msg;

	if( file->Err == Z_OK )
	{
		return( "" );
	}

	if( file->Err == Z_ERRNO )
	{
		MemFree( file->Msg );

		if( file->Msg = MemAlloc( 80 ) )
		{
			Fault( IoErr(), NULL, file->Msg, 80 );
		}
	}
	else
	{
		msg = file->Stream.msg;

		if( !msg || !( *msg ) )
		{
			msg = ERR_MSG( file->Err );
		}

		MemFree( file->Msg );
		file->Msg = MemAlloc( ( ULONG ) ( strlen( msg ) + 1 ) );
		strcpy( file->Msg, msg );
	}

	return( file->Msg );
}
