/*
   +----------------------------------------------------------------------+
   | PHP version 4.0                                                      |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2001 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.02 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | http://www.php.net/license/2_02.txt.                                 |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Sterling Hughes <sterling@php.net>                           |
   +----------------------------------------------------------------------+
 */
 
/* $Id: bz2.c,v 1.35.2.1 2002/03/29 15:28:04 sniper Exp $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_bz2.h"

#if HAVE_BZ2

/* PHP Includes */
#include "ext/standard/file.h"
#include "ext/standard/info.h"

/* for fileno() */
#include <stdio.h>

/* Bzip2 includes */
#include <bzlib.h>

/* Internal error constants */
#define PHP_BZ_ERRNO   0
#define PHP_BZ_ERRSTR  1
#define PHP_BZ_ERRBOTH 2

/* Blocksize of the decompression buffer */
#define PHP_BZ_DECOMPRESS_SIZE 4096

function_entry bz2_functions[] = {
	PHP_FE(bzopen,       NULL)
	PHP_FE(bzread,       NULL)
	PHP_FE(bzwrite,      NULL)
	PHP_FE(bzflush,      NULL)
	PHP_FE(bzclose,      NULL)
	PHP_FE(bzerrno,      NULL)
	PHP_FE(bzerrstr,     NULL)
	PHP_FE(bzerror,      NULL)
	PHP_FE(bzcompress,   NULL)
	PHP_FE(bzdecompress, NULL)
	{NULL, NULL, NULL}
};

zend_module_entry bz2_module_entry = {
	STANDARD_MODULE_HEADER,
	"bz2",
	bz2_functions,
	PHP_MINIT(bz2),
	NULL,
	NULL,
	NULL,
	PHP_MINFO(bz2),
	NO_VERSION_YET,
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_BZ2
ZEND_GET_MODULE(bz2)
#endif

static int  le_bz2;

static void php_bz2_close(zend_rsrc_list_entry *rsrc TSRMLS_DC);
static void php_bz2_error(INTERNAL_FUNCTION_PARAMETERS, int);

PHP_MINIT_FUNCTION(bz2)
{
	/* Register the resource, with destructor (arg 1) and text description (arg 3), the 
	   other arguments are just standard placeholders */
	le_bz2 = zend_register_list_destructors_ex(php_bz2_close, NULL, "BZip2", module_number);

	return SUCCESS;
}

PHP_MINFO_FUNCTION(bz2)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "BZip2 Support", "Enabled");
	php_info_print_table_row(2, "BZip2 Version", (char *) BZ2_bzlibVersion());
	php_info_print_table_end();
}

/* {{{ proto resource bzopen(string|int file|fp, string mode)
   Opens a new BZip2 stream */
PHP_FUNCTION(bzopen)
{
	zval    **file,   /* The file to open */
	        **mode;   /* The mode to open the stream with */
	BZFILE   *bz;     /* The compressed file stream */
	FILE     *fp;     /* If filepointer is given, its placed in here */
	char	*path;
	
	if (ZEND_NUM_ARGS() != 2 ||
	    zend_get_parameters_ex(2, &file, &mode) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(mode);
	
	/* If it's not a resource its a string containing the filename to open */
	if (Z_TYPE_PP(file) != IS_RESOURCE) {
		convert_to_string_ex(file);

#ifdef VIRTUAL_DIR
	virtual_filepath(Z_STRVAL_PP(file), &path TSRMLS_CC);
#else
	path = Z_STRVAL_PP(file);
#endif  

		bz = BZ2_bzopen(path, Z_STRVAL_PP(mode));
	} 
	/* If it is a resource, than its a 'FILE *' resource */
	else {
		ZEND_FETCH_RESOURCE(fp, FILE *, file, -1, "File Handle", php_file_le_fopen());
		bz = BZ2_bzdopen(fileno(fp), Z_STRVAL_PP(mode));
	}

	if (bz == NULL) {
		php_error(E_WARNING, "bzopen(): Unable to open file");
		RETURN_FALSE;
	}
	
	ZEND_REGISTER_RESOURCE(return_value, bz, le_bz2);
}
/* }}} */

/* {{{ proto string bzread(resource bz [, int len])
   Reads len bytes from the BZip2 stream given by bz */
PHP_FUNCTION(bzread)
{
	zval      **bzp,                       /* BZip2 Resource Pointer */
	          **zlen;                      /* The (optional) length to read */
	BZFILE     *bz;                        /* BZip2 File pointer */
	void       *buf;                       /* Buffer to read data into */
	int         len  = 1024,               /* Length to read, passed to the BZ2_bzread function */
			    argc = ZEND_NUM_ARGS();    /* Argument count */

	if (argc < 1 || argc > 2 ||
	    zend_get_parameters_ex(argc, &bzp, &zlen) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ZEND_FETCH_RESOURCE(bz, BZFILE *, bzp, -1, "BZip2 File Handle", le_bz2);

	/* Optional second argument, the length to read, if not given, it 
	   defaults to 1024 bytes (why not? :) */
	if (argc > 1) {
		convert_to_long_ex(zlen);
		len = Z_LVAL_PP(zlen);
	}

	/* Allocate the buffer and read data into it */
	buf = emalloc(len + 1);
	BZ2_bzread(bz, buf, len);

	RETURN_STRINGL(buf, len, 0);
}
/* }}} */

/* {{{ proto int bzwrite(resource bz, string data [, int len])
   Writes data to the BZip2 stream given by bz */
PHP_FUNCTION(bzwrite)
{
	zval        **bzp,                        /* Bzip2 Resource Pointer */
	            **data,                       /* The data to write */
				**zlen;                       /* The (optional) length of the data to write */
	BZFILE       *bz;                         /* BZip2 File pointer */
	int           error,                      /* Error container */
	              len,                        /* Length to read, passed to the BZ2_bzwrite function */
				  argc = ZEND_NUM_ARGS();     /* Argument count */
	
	if (argc < 2 || argc > 3 ||
	    zend_get_parameters_ex(argc, &bzp, &data, &zlen) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(data);
	
	ZEND_FETCH_RESOURCE(bz, BZFILE *, bzp, -1, "BZip2 File Handle", le_bz2);
	
	/* If the length of the data is given, use that, otherwise, just use the
	   data's string length */
	if (argc > 2) {
		convert_to_long_ex(zlen);
		len = Z_LVAL_PP(zlen);
	}
	else { 
		len = Z_STRLEN_PP(data); 
	}
	
	/* Write the data and return the error */
	error = BZ2_bzwrite(bz, (void *) Z_STRVAL_PP(data), len);
	RETURN_LONG(error);
}
/* }}} */

/* {{{ proto int bzflush(resource bz)
   Flushes a BZip2 stream */
PHP_FUNCTION(bzflush)
{
	zval      **bzp;    /* BZip2 Resource Pointer */
	BZFILE     *bz;     /* BZip2 File pointer */
	int         error;  /* Error container */

	if (ZEND_NUM_ARGS() != 1 ||
	    zend_get_parameters_ex(1, &bzp) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(bz, BZFILE *, bzp, -1, "BZip2 File Handle", le_bz2);
	
	error = BZ2_bzflush(bz);
	RETURN_LONG(error);
}
/* }}} */

/* {{{ proto int bzclose(resource bz)
   Closes a BZip2 stream */
PHP_FUNCTION(bzclose)
{
	zval     **bzp=NULL;  /* BZip2 Resource Pointer */
	BZFILE    *bz;        /* BZip2 File pointer */
	
	if (ZEND_NUM_ARGS() != 1 ||
	    zend_get_parameters_ex(1, &bzp) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(bz, BZFILE *, bzp, -1, "BZip2 File Handle", le_bz2);
	zend_list_delete(Z_RESVAL_PP(bzp));
}
/* }}} */

/* {{{ proto int bzerrno(resource bz)
   Returns the error number */
PHP_FUNCTION(bzerrno)
{
	php_bz2_error(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_BZ_ERRNO);
}
/* }}} */

/* {{{ proto string bzerrstr(resource bz)
   Returns the error string */
PHP_FUNCTION(bzerrstr)
{
	php_bz2_error(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_BZ_ERRSTR);
}
/* }}} */

/* {{{ proto array bzerror(resource bz)
   Returns the error number and error string in an associative array */
PHP_FUNCTION(bzerror)
{
	php_bz2_error(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_BZ_ERRBOTH);
}
/* }}} */

/* {{{ proto string bzcompress(string source [, int blocksize100k [, int workfactor]])
   Compresses a string into BZip2 encoded data */
PHP_FUNCTION(bzcompress)
{
	zval            **source,          /* Source data to compress */
	                **zblock_size,     /* Optional block size to use */
					**zwork_factor;    /* Optional work factor to use */
	char             *dest = NULL;     /* Destination to place the compressed data into */
	int               error,           /* Error Container */
					  block_size  = 4, /* Block size for compression algorithm */
					  work_factor = 0, /* Work factor for compression algorithm */
					  argc;            /* Argument count */
	unsigned int      size,            /* The size to "realloc" to */
	                  source_len,      /* Length of the source data */
					  dest_len;        /* Length of the destination buffer */ 
	
	argc = ZEND_NUM_ARGS();

	if (argc < 1 || argc > 3 || 
	    zend_get_parameters_ex(argc, &source, &zblock_size, &zwork_factor) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(source);
	
	/* Assign them to easy to use variables, dest_len is initially the length of the data
	   + .01 x length of data + 600 which is the largest size the results of the compression 
	   could possibly be, at least that's what the libbz2 docs say (thanks to jeremy@nirvani.net 
	   for pointing this out).  */
	source_len = Z_STRLEN_PP(source);
	dest_len   = Z_STRLEN_PP(source) + (0.01 * Z_STRLEN_PP(source)) + 600;
	
	/* Allocate the destination buffer */
	dest = emalloc(dest_len + 1);
	
	/* Handle the optional arguments */
	if (argc > 1) {
		convert_to_long_ex(zblock_size);
		block_size = Z_LVAL_PP(zblock_size);
	}
	
	if (argc > 2) {
		convert_to_long_ex(zwork_factor);
		work_factor = Z_LVAL_PP(zwork_factor);
	}

	error = BZ2_bzBuffToBuffCompress(dest, 
									 &size, 
									 Z_STRVAL_PP(source), 
									 source_len, 
									 block_size, 
									 0, 
									 work_factor);
	if (error != BZ_OK) {
		RETURN_LONG(error);
	} else {
		/* Copy the buffer, we have perhaps allocate alot more than we need,
		   so we erealloc() the buffer to the proper size */
		dest = erealloc(dest, size + 1);
		dest[size] = 0;
		RETURN_STRINGL(dest, size, 0);
	}
}
/* }}} */

/* {{{ proto string bzdecompress(string source [, int small])
   Decompresses BZip2 compressed data */
PHP_FUNCTION(bzdecompress)
{
	zval    **source,                             /* Source data to decompress */
	        **zsmall;                             /* (Optional) user specified small */
	char     *dest;                               /* Destination buffer, initially allocated */
	int       error,                              /* Error container */
	          iter = 1,                           /* Iteration count for the compression loop */
			  size,                               /* Current size to realloc the dest buffer to */
			  dest_len = PHP_BZ_DECOMPRESS_SIZE,  /* Size of the destination length */
			  small    = 0,                       /* The actual small */
			  argc     = ZEND_NUM_ARGS();         /* Argument count */
	
	if (argc < 1 || argc > 2 ||
	    zend_get_parameters_ex(argc, &source, &zsmall) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(source);
	
	/* optional small argument handling */
	if (argc > 1) {
		convert_to_long_ex(zsmall);
		small = Z_LVAL_PP(zsmall);
	}

	
	/* Depending on the size of the source buffer, either allocate
	  the length of the source buffer or the a default decompression
	  size */
	dest = emalloc(
		PHP_BZ_DECOMPRESS_SIZE > Z_STRLEN_PP(source) ?
		PHP_BZ_DECOMPRESS_SIZE :
		Z_STRLEN_PP(source)
		);

	/* (de)Compression Loop */	
	do {
		/* Handle the (re)allocation of the buffer */
		size = dest_len * iter;
		if (iter > 1) {
			dest = erealloc(dest, size);
		}
		++iter;

		/* Perform the decompression */
		error = BZ2_bzBuffToBuffDecompress(dest, 
										   &size, 
										   Z_STRVAL_PP(source), 
										   Z_STRLEN_PP(source), 
										   small, 
										   0);
	} while (error == BZ_OUTBUFF_FULL);
	
	if (error != BZ_OK) {
		RETURN_LONG(error);
	} else {
		/* we might have allocated a little to much, so erealloc the buffer 
		 down to size, before returning it */
		dest = erealloc(dest, size + 1);
		dest[size] = 0;
		RETURN_STRINGL(dest, size, 0);
	}
}
/* }}} */

/* {{{ php_bz2_close() 
   Closes a BZip2 file pointer */
static void php_bz2_close(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	BZFILE *bz = (BZFILE *) rsrc->ptr;  /* The BZip2 File pointer */

	BZ2_bzclose(bz);
}
/* }}} */

/* {{{ php_bz2_error()
   The central error handling interface, does the work for bzerrno, bzerrstr and bzerror */
static void php_bz2_error(INTERNAL_FUNCTION_PARAMETERS, int opt)
{ 
	zval        **bzp;     /* BZip2 Resource Pointer */
	BZFILE       *bz;      /* BZip2 File pointer */
	const char   *errstr;  /* Error string */
	int           errnum;  /* Error number */
	
	if (ZEND_NUM_ARGS() != 1 ||
	    zend_get_parameters_ex(1, &bzp) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(bz, BZFILE *, bzp, -1, "BZip2 File pointer", le_bz2);
	
	/* Fetch the error information */
	errstr = BZ2_bzerror(bz, &errnum);
	
	/* Determine what to return */
	switch (opt) {
	case PHP_BZ_ERRNO:
		RETURN_LONG(errnum);
		break;
	case PHP_BZ_ERRSTR:
		RETURN_STRING((char*)errstr, 1);
		break;
	case PHP_BZ_ERRBOTH:
		array_init(return_value);
		
		add_assoc_long  (return_value, "errno",  errnum);
		add_assoc_string(return_value, "errstr", (char*)errstr, 1);
		
		break;
	}
}
/* }}} */

#endif

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
