/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#include "signtool.h"
#include "zip.h"
#include "zlib.h"
#include "prmem.h"

static void inttox (int in, char *out);
static void longtox (long in, char *out);

/****************************************************************
 *
 * J z i p O p e n
 *
 * Opens a new ZIP file and creates a new ZIPfile structure to 
 * control the process of installing files into a zip.
 */
ZIPfile*
JzipOpen(char *filename, char *comment)
{
	ZIPfile *zipfile;
	PRExplodedTime prtime;

	zipfile = PORT_ZAlloc(sizeof(ZIPfile));
	if(!zipfile) out_of_memory();

	/* Construct time and date */
	PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prtime);
	zipfile->date =	((prtime.tm_year-1980) << 9) |
					((prtime.tm_month+1) << 5)   |
					prtime.tm_mday;
	zipfile->time =	(prtime.tm_hour<<11)   |
					(prtime.tm_min<<5)     |
					(prtime.tm_sec&0x3f);

	if (filename  &&
		(zipfile->fp = PR_Open(filename,
		  PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0777)) == NULL) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "%s: can't open output jar, %s.%s\n", PROGRAM_NAME,
			filename, nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit (ERRX);
	}

	zipfile->list = NULL;
	if(filename) {
		zipfile->filename = PORT_ZAlloc(strlen(filename)+1);
		if(!zipfile->filename) out_of_memory();
		PORT_Strcpy(zipfile->filename, filename);
	}
	if(comment) {
		zipfile->comment = PORT_ZAlloc(strlen(comment)+1);
		if(!zipfile->comment) out_of_memory();
		PORT_Strcpy(zipfile->comment, comment);
	}

	return zipfile;
}

static
void*
my_alloc_func(void* opaque, uInt items, uInt size)
{
	return PORT_Alloc(items*size);
}

static
void
my_free_func(void* opaque, void* address)
{
	PORT_Free(address);
}

static
void
handle_zerror(int err, char *msg)
{
	if(!msg) {
		msg = "";
	}

	errorCount++; /* unless Z_OK...see below */

	switch(err) {
	case Z_OK:
		PR_fprintf(errorFD, "No error: %s\n", msg);
		errorCount--; /* this was incremented above */
		break;
	case Z_MEM_ERROR:
		PR_fprintf(errorFD, "Deflation ran out of memory: %s\n", msg);
		break;
	case Z_STREAM_ERROR:
		PR_fprintf(errorFD, "Invalid compression level: %s\n", msg);
		break;
	case Z_VERSION_ERROR:
		PR_fprintf(errorFD, "Incompatible compression library version: %s\n", msg);
		break;
	case Z_DATA_ERROR:
		PR_fprintf(errorFD, "Compression data error: %s\n", msg);
		break;
	default:
		PR_fprintf(errorFD, "Unknown error in compression library: %s\n", msg);
		break;
	}
}
	

/****************************************************************
 *
 * J z i p A d d
 *
 * Adds a new file into a ZIP file.  The ZIP file must have already
 * been opened with JzipOpen.
 */
int
JzipAdd(char *fullname, char *filename, ZIPfile *zipfile, int compression_level)
{
	ZIPentry *entry;
	PRFileDesc *readfp;
	PRFileDesc *zipfp;
	int num;
	Bytef inbuf[BUFSIZ], outbuf[BUFSIZ];
	unsigned long crc;
	z_stream zstream;
	int err;
	unsigned long local_size_pos;
	int deflate_percent;


	if( !fullname || !filename || !zipfile) {
		return -1;
	}

	zipfp = zipfile->fp;


	if( (readfp = PR_Open(fullname, PR_RDONLY, 0777)) == NULL) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "%s: %s\n", fullname, nsprErr ? nsprErr : "");
		errorCount++;
		if(nsprErr) PR_Free(nsprErr);
		exit(ERRX);
	}

	/*
	 * Make sure the input file is not the output file.
	 * Add a few bytes to the end of the JAR file and see if the input file
	 * twitches
	 */
	{
		PRInt32 endOfJar;
		PRInt32 inputSize;
		PRBool isSame;

		inputSize = PR_Available(readfp);

		endOfJar = PR_Seek(zipfp, 0L, PR_SEEK_CUR);

		if(PR_Write(zipfp, "abcde", 5) < 5) {
			char *nsprErr;

			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing to zip file: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}

		isSame = (PR_Available(readfp) != inputSize);
	
		PR_Seek(zipfp, endOfJar, PR_SEEK_SET);

		if(isSame) {
			/* It's the same file! Forget it! */
			PR_Close(readfp);
			return 0;
		}
	}

	if(verbosity >= 0) {
		PR_fprintf(outputFD, "adding %s to %s...", fullname, zipfile->filename);
	}

	entry = PORT_ZAlloc(sizeof(ZIPentry));
	if(!entry) out_of_memory();

	entry->filename = PORT_Strdup(filename);
	entry->comment = NULL;

	/* Set up local file header */
	longtox(LSIG, entry->local.signature);
	inttox(strlen(filename), entry->local.filename_len);
	inttox(zipfile->time, entry->local.time);
	inttox(zipfile->date, entry->local.date);
	inttox(Z_DEFLATED, entry->local.method);

	/* Set up central directory entry */
	longtox(CSIG, entry->central.signature);
	inttox(strlen(filename), entry->central.filename_len);
	if(entry->comment) {
		inttox(strlen(entry->comment), entry->central.commentfield_len);
	}
	longtox(PR_Seek(zipfile->fp, 0, PR_SEEK_CUR),
		entry->central.localhdr_offset);
	inttox(zipfile->time, entry->central.time);
	inttox(zipfile->date, entry->central.date);
	inttox(Z_DEFLATED, entry->central.method);

	/* Compute crc.  Too bad we have to process the whole file to do this*/
	crc = crc32(0L, NULL, 0);
	while( (num = PR_Read(readfp, inbuf, BUFSIZ)) > 0) {
		crc = crc32(crc, inbuf, num);
	}
	PR_Seek(readfp, 0L, PR_SEEK_SET);

	/* Store CRC */
	longtox(crc, entry->local.crc32);
	longtox(crc, entry->central.crc32);

	/* Stick this entry onto the end of the list */
	entry->next = NULL;
	if( zipfile->list == NULL ) {
		/* First entry */
		zipfile->list = entry;
	} else {
		ZIPentry *pe;

		pe = zipfile->list;
		while(pe->next != NULL) {
			pe = pe->next;
		}
		pe->next = entry;
	}

	/*
	 * Start writing stuff out
	 */

	local_size_pos = PR_Seek(zipfp, 0, PR_SEEK_CUR) + 18;
	/* File header */
	if(PR_Write(zipfp, &entry->local, sizeof(struct ZipLocal))
		< sizeof(struct ZipLocal)) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}

	/* File Name */
	if( PR_Write(zipfp, filename, strlen(filename)) < strlen(filename)) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}

	/*
	 * File data
	 */
	/* Initialize zstream */
	zstream.zalloc = my_alloc_func;
	zstream.zfree = my_free_func;
	zstream.opaque = NULL;
	zstream.next_in = inbuf;
	zstream.avail_in = BUFSIZ;
	zstream.next_out = outbuf;
	zstream.avail_out = BUFSIZ;
	/* Setting the windowBits to -MAX_WBITS is an undocumented feature of
	 * zlib (see deflate.c in zlib).  It is the same thing that Java does
	 * when you specify the nowrap option for deflation in java.util.zip.
	 * It causes zlib to leave out its headers and footers, which don't
	 * work in PKZIP files.
	 */
	err = deflateInit2(&zstream, compression_level, Z_DEFLATED,
		-MAX_WBITS, 8 /*default*/, Z_DEFAULT_STRATEGY);
	if(err != Z_OK) {
		handle_zerror(err, zstream.msg);
		exit(ERRX);
	}

	while( (zstream.avail_in = PR_Read(readfp, inbuf, BUFSIZ)) > 0) {
		zstream.next_in = inbuf;
		/* Process this chunk of data */
		while(zstream.avail_in > 0) {
			err = deflate(&zstream, Z_NO_FLUSH);
			if(err != Z_OK) {
				handle_zerror(err, zstream.msg);
				exit(ERRX);
			}
			if(zstream.avail_out <= 0) {
				if( PR_Write(zipfp, outbuf, BUFSIZ) < BUFSIZ) {
					char *nsprErr;
					if(PR_GetErrorTextLength()) {
						nsprErr = PR_Malloc(PR_GetErrorTextLength());
						PR_GetErrorText(nsprErr);
					} else {
						nsprErr = NULL;
					}
					PR_fprintf(errorFD, "Writing zip data: %s\n",
						nsprErr ? nsprErr : "");
					if(nsprErr) PR_Free(nsprErr);
					errorCount++;
					exit(ERRX);
				}
				zstream.next_out = outbuf;
				zstream.avail_out = BUFSIZ;
			}
		}
	}

	/* Now flush everything */
	while(1) {
		err = deflate(&zstream, Z_FINISH);
		if(err == Z_STREAM_END) {
			break;
		} else if(err == Z_OK) {
			/* output buffer full, repeat */
		} else {
			handle_zerror(err, zstream.msg);
			exit(ERRX);
		}
		if( PR_Write(zipfp, outbuf, BUFSIZ) < BUFSIZ) {
			char *nsprErr;
			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing zip data: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}
		zstream.avail_out = BUFSIZ;
		zstream.next_out = outbuf;
	}

	/* If there's any output left, write it out. */
	if(zstream.next_out != outbuf) {
		if( PR_Write(zipfp, outbuf, zstream.next_out-outbuf) < 
			zstream.next_out-outbuf) {
			char *nsprErr;
			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing zip data: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}
		zstream.avail_out = BUFSIZ;
		zstream.next_out = outbuf;
	}

	/* Now that we know the compressed size, write this to the headers */
	longtox(zstream.total_in, entry->local.orglen);
	longtox(zstream.total_out, entry->local.size);
	if(PR_Seek(zipfp, local_size_pos, PR_SEEK_SET) == -1) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Accessing zip file: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}
	if( PR_Write(zipfp, entry->local.size, 8) != 8) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}
	if(PR_Seek(zipfp, 0L, PR_SEEK_END) == -1) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Accessing zip file: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}
	longtox(zstream.total_in, entry->central.orglen);
	longtox(zstream.total_out, entry->central.size);

	/* Close out the deflation operation */
	err = deflateEnd(&zstream);
	if(err != Z_OK) {
		handle_zerror(err, zstream.msg);
		exit(ERRX);
	}

	PR_Close(readfp);

	if((zstream.total_in > zstream.total_out) && (zstream.total_in > 0)) {
		deflate_percent = (int) ( (zstream.total_in-zstream.total_out)*100 /
			zstream.total_in );
	} else {
		deflate_percent = 0;
	}
	if(verbosity >= 0) {
		PR_fprintf(outputFD, "(deflated %d%%)\n", deflate_percent);
	}

	return 0;
}

/********************************************************************
 * J z i p C l o s e
 *
 * Finishes the ZipFile. ALSO DELETES THE ZIPFILE STRUCTURE PASSED IN!!
 */
int
JzipClose(ZIPfile *zipfile)
{
	ZIPentry *pe, *dead;
	PRFileDesc *zipfp;
	struct ZipEnd	zipend;
	unsigned int entrycount = 0;

	if(!zipfile) {
		return -1;
	}

	if(!zipfile->filename) {
		/* bogus */
		return 0;
	}

	zipfp = zipfile->fp;
	zipfile->central_start = PR_Seek(zipfp, 0L, PR_SEEK_CUR);

	/* Write out all the central directories */
	pe = zipfile->list;
	while(pe) {
		entrycount++;

		/* Write central directory info */
		if( PR_Write(zipfp, &pe->central, sizeof(struct ZipCentral)) 
			< sizeof(struct ZipCentral)) {
			char *nsprErr;
			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing zip data: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}

		/* Write filename */
		if( PR_Write(zipfp, pe->filename, strlen(pe->filename)) 
			< strlen(pe->filename)) {
			char *nsprErr;
			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing zip data: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}

		/* Write file comment */
		if(pe->comment) {
			if( PR_Write(zipfp, pe->comment, strlen(pe->comment)) 
				< strlen(pe->comment)) {
				char *nsprErr;
				if(PR_GetErrorTextLength()) {
					nsprErr = PR_Malloc(PR_GetErrorTextLength());
					PR_GetErrorText(nsprErr);
				} else {
					nsprErr = NULL;
				}
				PR_fprintf(errorFD, "Writing zip data: %s\n",
					nsprErr ? nsprErr : "");
				if(nsprErr) PR_Free(nsprErr);
				errorCount++;
				exit(ERRX);
			}
		}

		/* Delete the structure */
		dead = pe;
		pe = pe->next;
		if(dead->filename) {
			PORT_Free(dead->filename);
		}
		if(dead->comment) {
			PORT_Free(dead->comment);
		}
		PORT_Free(dead);
	}
	zipfile->central_end = PR_Seek(zipfile->fp, 0L, PR_SEEK_CUR);
	
	/* Create the ZipEnd structure */
	PORT_Memset(&zipend, 0, sizeof(zipend));
	longtox(ESIG, zipend.signature);
	inttox(entrycount, zipend.total_entries_disk);
	inttox(entrycount, zipend.total_entries_archive);
	longtox(zipfile->central_end-zipfile->central_start,
		zipend.central_dir_size);
	longtox(zipfile->central_start, zipend.offset_central_dir);
	if(zipfile->comment) {
		inttox(strlen(zipfile->comment), zipend.commentfield_len);
	}

	/* Write out ZipEnd xtructure */
	if( PR_Write(zipfp, &zipend, sizeof(zipend)) < sizeof(zipend)) {
		char *nsprErr;
		if(PR_GetErrorTextLength()) {
			nsprErr = PR_Malloc(PR_GetErrorTextLength());
			PR_GetErrorText(nsprErr);
		} else {
			nsprErr = NULL;
		}
		PR_fprintf(errorFD, "Writing zip data: %s\n", nsprErr ? nsprErr : "");
		if(nsprErr) PR_Free(nsprErr);
		errorCount++;
		exit(ERRX);
	}

	/* Write out Zipfile comment */
	if(zipfile->comment) {
		if( PR_Write(zipfp, zipfile->comment, strlen(zipfile->comment)) 
			< strlen(zipfile->comment)) {
			char *nsprErr;
			if(PR_GetErrorTextLength()) {
				nsprErr = PR_Malloc(PR_GetErrorTextLength());
				PR_GetErrorText(nsprErr);
			} else {
				nsprErr = NULL;
			}
			PR_fprintf(errorFD, "Writing zip data: %s\n",
				nsprErr ? nsprErr : "");
			if(nsprErr) PR_Free(nsprErr);
			errorCount++;
			exit(ERRX);
		}
	}

	PR_Close(zipfp);

	/* Free the memory of the zipfile structure */
	if(zipfile->filename) {
		PORT_Free(zipfile->filename);
	}
	if(zipfile->comment) {
		PORT_Free(zipfile->comment);
	}
	PORT_Free(zipfile);

	return 0;
}

/**********************************************
 *  i n t t o x
 *
 *  Converts a two byte ugly endianed integer
 *  to our platform's integer.
 *
 */

static void inttox (int in, char *out)
{
  out [0] = (in & 0xFF);
  out [1] = (in & 0xFF00) >> 8;
}

/*********************************************
 *  l o n g t o x
 *
 *  Converts a four byte ugly endianed integer
 *  to our platform's integer.
 *
 */

static void longtox (long in, char *out)
{
  out [0] = (in & 0xFF);
  out [1] = (in & 0xFF00) >> 8;
  out [2] = (in & 0xFF0000) >> 16;
  out [3] = (in & 0xFF000000) >> 24;
}

