/*
 * 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 <prmem.h>
#include <prio.h>
#include <prenv.h>

static int javascript_fn(char *relpath, char *basedir, char *reldir,
	char *filename, void *arg);
static int extract_js (char *filename);
static int copyinto (char *from, char *to);
static PRStatus ensureExists (char *base, char *path);
static int make_dirs(char *path, PRInt32 file_perms);

static char *jartree = NULL;
static int idOrdinal;
static PRBool dumpParse=PR_FALSE;

static char *event_handlers[] = {
"onAbort",
"onBlur",
"onChange",
"onClick",
"onDblClick",
"onDragDrop",
"onError",
"onFocus",
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onLoad",
"onMouseDown",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onMove",
"onReset",
"onResize",
"onSelect",
"onSubmit",
"onUnload"
};
static int num_handlers = 23;

/*
 *  I n l i n e J a v a S c r i p t
 *
 *  Javascript signing. Instead of passing an archive to signtool,
 *  a directory containing html files is given. Archives are created
 *  from the archive= and src= tag attributes inside the html,
 *  as appropriate. Then the archives are signed.
 *
 */
int
InlineJavaScript(char *dir, PRBool recurse)
{
	jartree = dir;
	if(verbosity >= 0) {
		PR_fprintf(outputFD, "\nGenerating inline signatures from HTML files in: %s\n", dir);
	}
	if(PR_GetEnv("SIGNTOOL_DUMP_PARSE")) {
		dumpParse = PR_TRUE;
	}

	return foreach(dir, "", javascript_fn, recurse, PR_FALSE /*include dirs*/,
		(void*)NULL);

}

/************************************************************************
 *
 * j a v a s c r i p t _ f n
 */
static int javascript_fn
     (char *relpath, char *basedir, char *reldir, char *filename, void *arg)
{
  char fullname [FNSIZE];

  /* only process inline scripts from .htm, .html, and .shtml*/

	if(! (PL_strcaserstr(filename, ".htm") == filename + strlen(filename) -4) &&
	   ! (PL_strcaserstr(filename, ".html") == filename + strlen(filename) -5)&&
	   ! (PL_strcaserstr(filename, ".shtml") == filename + strlen(filename)-6)){
		return 0;
	}

  /* don't process scripts that signtool has already
     extracted (those that are inside .arc directories) */

  if(PL_strcaserstr(filename, ".arc") == filename + strlen(filename) - 4)
    return 0;

	if(verbosity >= 0) {
		PR_fprintf(outputFD, "Processing HTML file: %s\n", relpath);
	}

  /* reset firstArchive at top of each HTML file */

  /* skip directories that contain extracted scripts */

  if(PL_strcaserstr(reldir, ".arc") == reldir + strlen(reldir) - 4)
    return 0;

  sprintf (fullname, "%s/%s", basedir, relpath);
  return extract_js (fullname);
}

/*===========================================================================
 =
 = D A T A   S T R U C T U R E S
 =
*/
typedef enum {
	TEXT_HTML_STATE=0,
	SCRIPT_HTML_STATE
} HTML_STATE ;

typedef enum {
	/* we start in the start state */
	START_STATE,

	/* We are looking for or reading in an attribute */
	GET_ATT_STATE,

	/* We're burning ws before finding an attribute */
	PRE_ATT_WS_STATE,

	/* We're burning ws after an attribute.  Looking for an '='. */
	POST_ATT_WS_STATE,

	/* We're burning ws after an '=', waiting for a value */
	PRE_VAL_WS_STATE,

	/* We're reading in a value */
	GET_VALUE_STATE,

	/* We're reading in a value that's inside quotes */
	GET_QUOTED_VAL_STATE,

	/* We've encountered the closing '>' */
	DONE_STATE,

	/* Error state */
	ERR_STATE
} TAG_STATE ;

typedef struct AVPair_Str {
	char *attribute;
	char *value;
	unsigned int valueLine; /* the line that the value ends on */
	struct AVPair_Str *next;
} AVPair;

typedef enum {
	APPLET_TAG,
	SCRIPT_TAG,
	LINK_TAG,
	STYLE_TAG,
	COMMENT_TAG,
	OTHER_TAG
} TAG_TYPE ;

typedef struct {
	TAG_TYPE type;
	AVPair *attList;
	AVPair *attListTail;
	char *text;
} TagItem;

typedef enum {
	TAG_ITEM,
	TEXT_ITEM
} ITEM_TYPE ;

typedef struct HTMLItem_Str{
	unsigned int startLine;
	unsigned int endLine;
	ITEM_TYPE type;
	union {
		TagItem *tag;
		char *text;
	} item;
	struct HTMLItem_Str *next;
} HTMLItem;

typedef struct {
	PRFileDesc *fd;
	PRInt32 curIndex;
	PRBool IsEOF;
#define FILE_BUFFER_BUFSIZE 512
	char buf[FILE_BUFFER_BUFSIZE];
	PRInt32 startOffset;
	PRInt32 maxIndex;
	unsigned int lineNum;
} FileBuffer;

/*===========================================================================
 =
 = F U N C T I O N S
 =
*/
static HTMLItem* CreateTextItem(char *text, unsigned int startline,
					unsigned int endline);
static HTMLItem* CreateTagItem(TagItem* ti, unsigned int startline,
					unsigned int endline);
static TagItem* ProcessTag(FileBuffer* fb, char **errStr);
static void DestroyHTMLItem(HTMLItem *item);
static void DestroyTagItem(TagItem* ti);
static TAG_TYPE GetTagType(char *att);
static FileBuffer* FB_Create(PRFileDesc* fd);
static int FB_GetChar(FileBuffer *fb);
static PRInt32 FB_GetPointer(FileBuffer *fb);
static PRInt32 FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end,
															char **buf);
static unsigned int FB_GetLineNum(FileBuffer *fb);
static void FB_Destroy(FileBuffer *fb);
static void PrintTagItem(PRFileDesc *fd, TagItem *ti);
static void PrintHTMLStream(PRFileDesc *fd, HTMLItem *head);

/************************************************************************
 *
 * C r e a t e T e x t I t e m
 */
static HTMLItem*
CreateTextItem(char *text, unsigned int startline, unsigned int endline)
{
	HTMLItem *item;

	item = PR_Malloc(sizeof(HTMLItem));
	if(!item) {
		return NULL;
	}

	item->type = TEXT_ITEM;
	item->item.text = text;
	item->next = NULL;
	item->startLine = startline;
	item->endLine = endline;

	return item;
}

/************************************************************************
 *
 * C r e a t e T a g I t e m
 */
static HTMLItem*
CreateTagItem(TagItem* ti, unsigned int startline, unsigned int endline)
{
	HTMLItem *item;

	item = PR_Malloc(sizeof(HTMLItem));
	if(!item) {
		return NULL;
	}

	item->type = TAG_ITEM;
	item->item.tag = ti;
	item->next = NULL;
	item->startLine = startline;
	item->endLine = endline;

	return item;
}

static PRBool
isAttChar(char c)
{
	return (isalnum(c) || c=='/' || c=='-');
}

/************************************************************************
 *
 * P r o c e s s T a g
 */
static TagItem*
ProcessTag(FileBuffer* fb, char **errStr)
{
	TAG_STATE state;
	PRInt32 startText, startID, curPos;
	PRBool firstAtt;
	int curchar;
	TagItem *ti=NULL;
	AVPair *curPair=NULL;
	char quotechar='\0';
	unsigned int linenum;
	unsigned int startline;

	state = START_STATE;

	startID = FB_GetPointer(fb);
	startText = startID;
	firstAtt = PR_TRUE;

	ti = (TagItem*) PR_Malloc(sizeof(TagItem));
	if(!ti) out_of_memory();
	ti->type = OTHER_TAG;
	ti->attList = NULL;
	ti->attListTail = NULL;
	ti->text = NULL;

	startline = FB_GetLineNum(fb);

	while(state != DONE_STATE && state != ERR_STATE) {
		linenum = FB_GetLineNum(fb);
		curchar = FB_GetChar(fb);
		if(curchar == EOF) {
			*errStr = PR_smprintf(
				"line %d: Unexpected end-of-file while parsing tag starting at line %d.\n", linenum, startline);
			state = ERR_STATE;
			continue;
		}

		switch(state) {
		case START_STATE:
			if(curchar=='!') {
				/*
				 * SGML tag or comment
				 * Here's the general rule for SGML tags.  Everything from
				 * <! to > is the tag.  Inside the tag, comments are
				 * delimited with --.  So we are looking for the first '>'
				 * that is not commented out, that is, not inside a pair
				 * of --: <!DOCTYPE --this is a comment >(psyche!)   -->
				 */

				PRBool inComment = PR_FALSE;
				short hyphenCount = 0; /* number of consecutive hyphens */

				while(1) {
					linenum = FB_GetLineNum(fb);
					curchar = FB_GetChar(fb);
					if(curchar == EOF) {
						/* Uh oh, EOF inside comment */
						*errStr = PR_smprintf(
							"line %d: Unexpected end-of-file inside comment starting at line %d.\n",
							linenum, startline);
						state = ERR_STATE;
						break;
					}
					if(curchar=='-') {
						if(hyphenCount==1) {
							/* This is a comment delimiter */
							inComment = !inComment;
							hyphenCount=0;
						} else {
							/* beginning of a comment delimiter? */
							hyphenCount=1;
						}
					} else if(curchar=='>') {
						if(!inComment) {
							/* This is the end of the tag */
							state = DONE_STATE;
							break;
						} else {
							/* The > is inside a comment, so it's not
							 * really the end of the tag */
							hyphenCount=0;
						}
					} else {
						hyphenCount = 0;
					}
				}
				ti->type = COMMENT_TAG;
				break;
			}
			/* fall through */
		case GET_ATT_STATE:
			if(isspace(curchar) || curchar=='=' || curchar=='>') {
				/* end of the current attribute */
				curPos = FB_GetPointer(fb)-2;
				if(curPos >= startID) {
					/* We have an attribute */
					curPair = (AVPair*)PR_Malloc(sizeof(AVPair));
					if(!curPair) out_of_memory();
					curPair->value = NULL;
					curPair->next = NULL;
					FB_GetRange(fb, startID, curPos, &curPair->attribute);

					/* Stick this attribute on the list */
					if(ti->attListTail) {
						ti->attListTail->next = curPair;
						ti->attListTail = curPair;
					} else {
						ti->attList = ti->attListTail = curPair;
					}
					
					/* If this is the first attribute, find the type of tag
					 * based on it. Also, start saving the text of the tag. */
					if(firstAtt) {
						ti->type = GetTagType(curPair->attribute);
						startText = FB_GetPointer(fb)-1;
						firstAtt = PR_FALSE;
					}
				} else {
					if(curchar=='=') {
						/* If we don't have any attribute but we do have an
						 * equal sign, that's an error */
						*errStr = PR_smprintf("line %d: Malformed tag starting at line %d.\n", linenum, startline);
						state = ERR_STATE;
						break;
					}
				}

				/* Compute next state */
				if(curchar=='=') {
					startID = FB_GetPointer(fb);
					state = PRE_VAL_WS_STATE;
				} else if(curchar=='>') {
					state = DONE_STATE;
				} else if(curPair) {
					state = POST_ATT_WS_STATE;
				} else {
					state = PRE_ATT_WS_STATE;
				}
			} else if(isAttChar(curchar)) {
				/* Just another char in the attribute. Do nothing */
				state = GET_ATT_STATE;
			} else {
				/* bogus char */
				*errStr= PR_smprintf("line %d: Bogus chararacter '%c' in tag.\n",
					linenum, curchar);
				state = ERR_STATE;
				break;
			}
			break;
		case PRE_ATT_WS_STATE:
			if(curchar=='>') {
				state = DONE_STATE;
			} else if(isspace(curchar)) {
				/* more whitespace, do nothing */
			} else if(isAttChar(curchar)) {
				/* starting another attribute */
				startID = FB_GetPointer(fb)-1;
				state = GET_ATT_STATE;
			} else {
				/* bogus char */
				*errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n",
					linenum, curchar);
				state = ERR_STATE;
				break;
			}
			break;
		case POST_ATT_WS_STATE:
			if(curchar=='>') {
				state = DONE_STATE;
			} else if(isspace(curchar)) {
				/* more whitespace, do nothing */
			} else if(isAttChar(curchar)) {
				/* starting another attribute */
				startID = FB_GetPointer(fb)-1;
				state = GET_ATT_STATE;
			} else if(curchar=='=') {
				/* there was whitespace between the attribute and its equal
				 * sign, which means there's a value coming up */
				state = PRE_VAL_WS_STATE;
			} else {
				/* bogus char */
				*errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n",
					linenum, curchar);
				state = ERR_STATE;
				break;
			}
			break;
		case PRE_VAL_WS_STATE:
			if(curchar=='>') {
				/* premature end-of-tag (sounds like a personal problem). */
				*errStr = PR_smprintf(
					"line %d: End of tag while waiting for value.\n", linenum);
				state = ERR_STATE;
				break;
			} else if(isspace(curchar)) {
				/* more whitespace, do nothing */
				break;
			} else {
				/* this must be some sort of value. Fall through
				 * to GET_VALUE_STATE */
				startID=FB_GetPointer(fb)-1;
				state = GET_VALUE_STATE;
			}
			/* Fall through if we didn't break on '>' or whitespace */
		case GET_VALUE_STATE:
			if(isspace(curchar) || curchar=='>') {
				/* end of value */
				curPos = FB_GetPointer(fb)-2;
				if(curPos >= startID) {
					/* Grab the value */
					FB_GetRange(fb, startID, curPos, &curPair->value);
					curPair->valueLine = linenum;
				} else {
					/* empty value, leave as NULL */
				}
				if(isspace(curchar)) {
					state = PRE_ATT_WS_STATE;
				} else {
					state = DONE_STATE;
				}
			} else if(curchar=='\"' || curchar=='\'') {
				/* quoted value.  Start recording the value inside the quote*/
				startID = FB_GetPointer(fb);
				state = GET_QUOTED_VAL_STATE;
				PORT_Assert(quotechar == '\0');
				quotechar = curchar; /* look for matching quote type */
			} else {
				/* just more value */
			}
			break;
		case GET_QUOTED_VAL_STATE:
			PORT_Assert(quotechar != '\0');
			if(curchar == quotechar) {
				/* end of quoted value */
				curPos = FB_GetPointer(fb)-2;
				if(curPos >= startID) {
					/* Grab the value */
					FB_GetRange(fb, startID, curPos, &curPair->value);
					curPair->valueLine = linenum;
				} else {
					/* empty value, leave it as NULL */
				}
				state = GET_ATT_STATE;
				quotechar = '\0';
				startID = FB_GetPointer(fb);
			} else {
				/* more quoted value, continue */
			}
			break;
		case DONE_STATE:
		case ERR_STATE:
		default:
			; /* should never get here */
		}
	}

	if(state == DONE_STATE) {
		/* Get the text of the tag */
		curPos = FB_GetPointer(fb)-1;
		FB_GetRange(fb, startText, curPos, &ti->text);

		/* Return the tag */
		return ti;
	}

	/* Uh oh, an error.  Kill the tag item*/
	DestroyTagItem(ti);
	return NULL;
}

/************************************************************************
 *
 * D e s t r o y H T M L I t e m
 */
static void
DestroyHTMLItem(HTMLItem *item)
{
	if(item->type == TAG_ITEM) {
		DestroyTagItem(item->item.tag);
	} else {
		if(item->item.text) {
			PR_Free(item->item.text);
		}
	}
}

/************************************************************************
 *
 * D e s t r o y T a g I t e m
 */
static void
DestroyTagItem(TagItem* ti)
{
	AVPair *temp;

	if(ti->text) {
		PR_Free(ti->text); ti->text = NULL;
	}

	while(ti->attList) {
		temp = ti->attList;
		ti->attList = ti->attList->next;

		if(temp->attribute) {
			PR_Free(temp->attribute); temp->attribute = NULL;
		}
		if(temp->value) {
			PR_Free(temp->value); temp->value = NULL;
		}
		PR_Free(temp);
	}

	PR_Free(ti);
}

/************************************************************************
 *
 * G e t T a g T y p e
 */
static TAG_TYPE
GetTagType(char *att)
{
	if(!PORT_Strcasecmp(att, "APPLET")) {
		return APPLET_TAG;
	}
	if(!PORT_Strcasecmp(att, "SCRIPT")) {
		return SCRIPT_TAG;
	}
	if(!PORT_Strcasecmp(att, "LINK")) {
		return LINK_TAG;
	}
	if(!PORT_Strcasecmp(att, "STYLE")) {
		return STYLE_TAG;
	}
	return OTHER_TAG;
}

/************************************************************************
 *
 * F B _ C r e a t e
 */
static FileBuffer*
FB_Create(PRFileDesc* fd)
{
	FileBuffer *fb;
	PRInt32 amountRead;
	PRInt32 storedOffset;

	fb = (FileBuffer*) PR_Malloc(sizeof(FileBuffer));
	fb->fd = fd;
	storedOffset = PR_Seek(fd, 0, PR_SEEK_CUR);
	PR_Seek(fd, 0, PR_SEEK_SET);
	fb->startOffset = 0;
	amountRead = PR_Read(fd, fb->buf, FILE_BUFFER_BUFSIZE);
	if(amountRead == -1) goto loser;
	fb->maxIndex = amountRead-1;
	fb->curIndex = 0;
	fb->IsEOF = (fb->curIndex>fb->maxIndex) ? PR_TRUE : PR_FALSE;
	fb->lineNum = 1;

	PR_Seek(fd, storedOffset, PR_SEEK_SET);
	return fb;
loser:
	PR_Seek(fd, storedOffset, PR_SEEK_SET);
	PR_Free(fb);
	return NULL;
}

/************************************************************************
 *
 * F B _ G e t C h a r
 */
static int
FB_GetChar(FileBuffer *fb)
{
	PRInt32 storedOffset;
	PRInt32 amountRead;
	int retval=-1;

	if(fb->IsEOF) {
		return EOF;
	}

	storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR);

	retval = fb->buf[fb->curIndex++];
	if(retval=='\n') fb->lineNum++;

	if(fb->curIndex > fb->maxIndex) {
		/* We're at the end of the buffer. Try to get some new data from the
		 * file */
		fb->startOffset += fb->maxIndex+1;
		PR_Seek(fb->fd, fb->startOffset, PR_SEEK_SET);
		amountRead = PR_Read(fb->fd, fb->buf, FILE_BUFFER_BUFSIZE);
		if(amountRead==-1)  goto loser;
		fb->maxIndex = amountRead-1;
		fb->curIndex = 0;
	}

	fb->IsEOF = (fb->curIndex > fb->maxIndex) ? PR_TRUE : PR_FALSE;

loser:
	PR_Seek(fb->fd, storedOffset, PR_SEEK_SET);
	return retval;
}

/************************************************************************
 *
 * F B _ G e t L i n e N u m
 *
 */
static unsigned int
FB_GetLineNum(FileBuffer *fb)
{
	return fb->lineNum;
}

/************************************************************************
 *
 * F B _ G e t P o i n t e r
 *
 */
static PRInt32
FB_GetPointer(FileBuffer *fb)
{
	return fb->startOffset + fb->curIndex;
}

/************************************************************************
 *
 * F B _ G e t R a n g e
 *
 */
static PRInt32
FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end, char **buf)
{
	PRInt32 amountRead;
	PRInt32 storedOffset;

	*buf = PR_Malloc(end-start+2);
	if(*buf == NULL) {
		return 0;
	}

	storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR);
	PR_Seek(fb->fd, start, PR_SEEK_SET);
	amountRead = PR_Read(fb->fd, *buf, end-start+1);
	PR_Seek(fb->fd, storedOffset, PR_SEEK_SET);
	if(amountRead == -1) {
		PR_Free(*buf);
		*buf = NULL;
		return 0;
	}

	(*buf)[end-start+1] = '\0';
	return amountRead;
}


/************************************************************************
 *
 * F B _ D e s t r o y
 *
 */
static void
FB_Destroy(FileBuffer *fb)
{
	if(fb) {
		PR_Free(fb);
	}
}

/************************************************************************
 *
 * P r i n t T a g I t e m
 *
 */
static void
PrintTagItem(PRFileDesc *fd, TagItem *ti)
{
	AVPair *pair;

	PR_fprintf(fd, "TAG:\n----\nType: ");
	switch(ti->type) {
	case APPLET_TAG:
		PR_fprintf(fd, "applet\n");
		break;
	case SCRIPT_TAG:
		PR_fprintf(fd, "script\n");
		break;
	case LINK_TAG:
		PR_fprintf(fd, "link\n");
		break;
	case STYLE_TAG:
		PR_fprintf(fd, "style\n");
		break;
	case COMMENT_TAG:
		PR_fprintf(fd, "comment\n");
		break;
	case OTHER_TAG:
	default:
		PR_fprintf(fd, "other\n");
		break;
	}

	PR_fprintf(fd, "Attributes:\n");
	for(pair = ti->attList; pair; pair=pair->next) {
		PR_fprintf(fd, "\t%s=%s\n", pair->attribute,
			pair->value ? pair->value : "");
	}
	PR_fprintf(fd, "Text:%s\n", ti->text ? ti->text : "");

	PR_fprintf(fd, "---End of tag---\n");
}


/************************************************************************
 *
 * P r i n t H T M L S t r e a m
 *
 */
static void
PrintHTMLStream(PRFileDesc *fd, HTMLItem *head)
{
	while(head) {
		if(head->type==TAG_ITEM) {
			PrintTagItem(fd, head->item.tag);
		} else {
			PR_fprintf(fd, "\nTEXT:\n-----\n%s\n-----\n\n", head->item.text);
		}
		head = head->next;
	}
}

/************************************************************************
 *
 * S a v e I n l i n e S c r i p t
 *
 */
static int
SaveInlineScript(char *text, char *id, char *basedir, char *archiveDir)
{
	char *filename=NULL;
	PRFileDesc *fd=NULL;
	int retval = -1;
	PRInt32 writeLen;
	char *ilDir=NULL;

	if(!text || !id || !archiveDir) {
		return -1;
	}

	if(dumpParse) {
		PR_fprintf(outputFD, "SaveInlineScript: text=%s, id=%s, \n"
			"basedir=%s, archiveDir=%s\n",
			text, id, basedir, archiveDir);
	}

	/* Make sure the archive directory is around */
	if(ensureExists(basedir, archiveDir) != PR_SUCCESS) {
		PR_fprintf(errorFD,
			"ERROR: Unable to create archive directory %s.\n", archiveDir);
		errorCount++;
		return -1;
	}

	/* Make sure the inline script directory is around */
	ilDir = PR_smprintf("%s/inlineScripts", archiveDir);
	scriptdir = "inlineScripts";
	if(ensureExists(basedir, ilDir) != PR_SUCCESS) {
		PR_fprintf(errorFD,
			"ERROR: Unable to create directory %s.\n", ilDir);
		errorCount++;
		return -1;
	}

	filename = PR_smprintf("%s/%s/%s", basedir, ilDir, id);

	/* If the file already exists, give a warning, then blow it away */
	if(PR_Access(filename, PR_ACCESS_EXISTS) == PR_SUCCESS) {
		PR_fprintf(errorFD,
			"warning: file \"%s\" already exists--will overwrite.\n",
			filename);
		warningCount++;
		if(rm_dash_r(filename)) {
			PR_fprintf(errorFD,
				"ERROR: Unable to delete %s.\n", filename);
			errorCount++;
			goto finish;
		}
	}

	/* Write text into file with name id */
	fd = PR_Open(filename, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0777);
	if(!fd) {
		PR_fprintf(errorFD, "ERROR: Unable to create file \"%s\".\n",
			filename);
		errorCount++;
		goto finish;
	}
	writeLen = strlen(text);
	if( PR_Write(fd, text, writeLen) != writeLen) {
		PR_fprintf(errorFD, "ERROR: Unable to write to file \"%s\".\n",
			filename);
		errorCount++;
		goto finish;
	}

	retval = 0;
finish:
	if(filename) {
		PR_smprintf_free(filename);
	}
	if(ilDir) {
		PR_smprintf_free(ilDir);
	}
	if(fd) {
		PR_Close(fd);
	}
	return retval;
}

/************************************************************************
 *
 * S a v e U n n a m a b l e S c r i p t
 *
 */
static int
SaveUnnamableScript(char *text, char *basedir, char *archiveDir,
	char *HTMLfilename)
{
	char *id=NULL;
	char *ext=NULL;
	char *start=NULL;
	int retval = -1;

	if(!text || !archiveDir || !HTMLfilename) {
		return -1;
	}

	if(dumpParse) {
		PR_fprintf(outputFD, "SaveUnnamableScript: text=%s, basedir=%s,\n"
			"archiveDir=%s, filename=%s\n", text, basedir, archiveDir,
			HTMLfilename);
	}

	/* Construct the filename */
	ext = PL_strrchr(HTMLfilename, '.');
	if(ext) {
		*ext = '\0';
	}
	for(start=HTMLfilename; strpbrk(start, "/\\");
			start=strpbrk(start, "/\\")+1);
	if(*start=='\0') start = HTMLfilename;
	id = PR_smprintf("_%s%d", start, idOrdinal++);
	if(ext) {
		*ext = '.';
	}

	/* Now call SaveInlineScript to do the work */
	retval = SaveInlineScript(text, id, basedir, archiveDir);

	PR_Free(id);

	return retval;
}

/************************************************************************
 *
 * S a v e S o u r c e
 *
 */
static int
SaveSource(char *src, char *codebase, char *basedir, char *archiveDir)
{
	char *from=NULL, *to=NULL;
	int retval = -1;
	char *arcDir=NULL;

	if(!src || !archiveDir) {
		return -1;
	}

	if(dumpParse) {
		PR_fprintf(outputFD, "SaveSource: src=%s, codebase=%s, basedir=%s,\n"
			"archiveDir=%s\n", src, codebase, basedir, archiveDir);
	}

	if(codebase) {
		arcDir = PR_smprintf("%s/%s/%s/", basedir, codebase, archiveDir);
	} else {
		arcDir = PR_smprintf("%s/%s/", basedir, archiveDir);
	}

	if(codebase) {
		from = PR_smprintf("%s/%s/%s", basedir, codebase, src);
		to = PR_smprintf("%s%s", arcDir, src);
	} else {
		from = PR_smprintf("%s/%s", basedir, src);
		to = PR_smprintf("%s%s", arcDir, src);
	}

	if(make_dirs(to, 0777)) {
		PR_fprintf(errorFD,
			"ERROR: Unable to create archive directory %s.\n", archiveDir);
		errorCount++;
		goto finish;
	}

	retval = copyinto(from, to);
finish:
	if(from) PR_Free(from);
	if(to) PR_Free(to);
	if(arcDir) PR_Free(arcDir);
	return retval;
}

/************************************************************************
 *
 * T a g T y p e T o S t r i n g
 *
 */
char *
TagTypeToString(TAG_TYPE type)
{
	switch(type) {
	case APPLET_TAG:
		return "APPLET";
	case SCRIPT_TAG:
		return "SCRIPT";
	case LINK_TAG:
		return "LINK";
	case STYLE_TAG:
		return "STYLE";
	default:
		break;
	}
	return "unknown";
}

/************************************************************************
 *
 * e x t r a c t _ j s
 *
 */
static int
extract_js(char *filename)
{
	PRFileDesc *fd=NULL;
	FileBuffer *fb=NULL;
	HTML_STATE state;
	int curchar;
	HTMLItem *head = NULL;
	HTMLItem *tail = NULL;
	PRInt32 textStart;
	PRInt32 curOffset;
	TagItem *tagp=NULL;
	char *text=NULL;
	HTMLItem *curitem=NULL;
	int retval = -1;
	char *tagerr=NULL;
	unsigned int linenum, startLine;
	char *archiveDir=NULL, *firstArchiveDir=NULL;
	HTMLItem *styleList, *styleListTail;
	HTMLItem *entityList, *entityListTail;
	char *basedir=NULL;

	styleList = entityList = styleListTail = entityListTail = NULL;

	/* Initialize the implicit ID counter for each file */
	idOrdinal = 0;

	/*
	 * First, parse the HTML into a stream of tags and text.
	 */

	fd = PR_Open(filename, PR_RDONLY, 0);
	if(!fd) {
		PR_fprintf(errorFD, "Unable to open %s for reading.\n", filename);
		errorCount++;
		return -1;
	}

	/* Construct base directory of filename. */
	{
		char *cp;

		basedir = PL_strdup(filename);

		/* Remove trailing slashes */
		while( (cp = PL_strprbrk(basedir, "/\\")) ==
				(basedir + strlen(basedir) - 1)) {
			*cp = '\0';
		}

		/* Now remove everything from the last slash (which will be followed
		 * by a filename) to the end */
		cp = PL_strprbrk(basedir, "/\\");
		if(cp) {
			*cp = '\0';
		}
	}

	state = TEXT_HTML_STATE;

	fb = FB_Create(fd);

	textStart=0;
	startLine = 0;
	while(linenum=FB_GetLineNum(fb), (curchar = FB_GetChar(fb)) != EOF) {
		switch(state) {
		case TEXT_HTML_STATE:
			if(curchar == '<') {
				/*
				 * Found a tag
				 */
				/* Save the text so far to a new text item */
				curOffset = FB_GetPointer(fb)-2;
				if(curOffset >= textStart) {
					if(FB_GetRange(fb, textStart, curOffset, &text) !=
					  curOffset-textStart+1)  {
						PR_fprintf(errorFD,
						  "Unable to read from %s.\n", filename);
						errorCount++;
						goto loser;
					}
					/* little fudge here.  If the first character on a line
					 * is '<', meaning a new tag, the preceding text item
					 * actually ends on the previous line.  In this case
					 * we will be saying that the text segment ends on the
					 * next line. I don't think this matters for text items. */
					curitem = CreateTextItem(text, startLine, linenum);
					text = NULL;
					if(tail == NULL) {
						head = tail = curitem;
					} else {
						tail->next = curitem;
						tail = curitem;
					}
				}

				/* Process the tag */
				tagp = ProcessTag(fb, &tagerr);
				if(!tagp) {
					if(tagerr) {
						PR_fprintf(errorFD, "Error in file %s: %s\n",
						  filename, tagerr);
						errorCount++;
					} else {
						PR_fprintf(errorFD,
							"Error in file %s, in tag starting at line %d\n",
							  filename, linenum);
						errorCount++;
					}
					goto loser;
				}
				/* Add the tag to the list */
				curitem = CreateTagItem(tagp, linenum, FB_GetLineNum(fb));
				if(tail == NULL) {
					head = tail = curitem;
				} else {
					tail->next = curitem;
					tail = curitem;
				}

				/* What's the next state */
				if(tagp->type == SCRIPT_TAG) {
					state = SCRIPT_HTML_STATE;
				}

				/* Start recording text from the new offset */
				textStart = FB_GetPointer(fb);
				startLine = FB_GetLineNum(fb);
			} else {
				/* regular character.  Next! */
			}
			break;
		case SCRIPT_HTML_STATE:
			if(curchar == '<') {
				char *cp;
				/*
				 * If this is a </script> tag, then we're at the end of the
				 * script.  Otherwise, ignore
				 */
				curOffset = FB_GetPointer(fb)-1;
				cp = NULL;
				if(FB_GetRange(fb, curOffset, curOffset+8, &cp) != 9) {
					if(cp) { PR_Free(cp); cp = NULL; }
				} else {
					/* compare the strings */
					if( !PORT_Strncasecmp(cp, "</script>", 9) ) {
						/* This is the end of the script. Record the text. */
						curOffset--;
						if(curOffset >= textStart) {
							if(FB_GetRange(fb, textStart, curOffset, &text) !=
							  curOffset-textStart+1) {
								PR_fprintf(errorFD,
									"Unable to read from %s.\n", filename);
								errorCount++;
								goto loser;
							}
							curitem = CreateTextItem(text, startLine, linenum);
							text = NULL;
							if(tail == NULL) {
								head = tail = curitem;
							} else {
								tail->next = curitem;
								tail = curitem;
							}
						}
						
						/* Now parse the /script tag and put it on the list */
						tagp = ProcessTag(fb, &tagerr);
						if(!tagp) {
							if(tagerr) {
								PR_fprintf(errorFD,
									"Error in file %s: %s\n", filename, tagerr);
							} else {
								PR_fprintf(errorFD,
									"Error in file %s, in tag starting at"
									" line %d\n", filename, linenum);
							}
							errorCount++;
							goto loser;
						}
						curitem = CreateTagItem(tagp, linenum,
										FB_GetLineNum(fb));
						if(tail == NULL) {
							head = tail = curitem;
						} else {
							tail->next = curitem;
							tail = curitem;
						}

						/* go back to text state */
						state = TEXT_HTML_STATE;

						textStart = FB_GetPointer(fb);
						startLine = FB_GetLineNum(fb);
					}
				}
			}
			break;
		}
	}

	/* End of the file.  Wrap up any remaining text */
	if(state == SCRIPT_HTML_STATE) {
		if(tail && tail->type==TAG_ITEM) {
			PR_fprintf(errorFD, "ERROR: <SCRIPT> tag at %s:%d is not followed "
			"by a </SCRIPT> tag.\n", filename, tail->startLine);
		} else {
			PR_fprintf(errorFD, "ERROR: <SCRIPT> tag in file %s is not followed"
			" by a </SCRIPT tag.\n", filename);
		}
		errorCount++;
		goto loser;
	}
	curOffset = FB_GetPointer(fb)-1;
	if(curOffset >= textStart) {
		text = NULL;
		if( FB_GetRange(fb, textStart, curOffset, &text) !=
			curOffset-textStart+1) {
			PR_fprintf(errorFD, "Unable to read from %s.\n", filename);
			errorCount++;
			goto loser;
		}
		curitem = CreateTextItem(text, startLine, linenum);
		text = NULL;
		if(tail == NULL) {
			head = tail = curitem;
		} else  {
			tail->next = curitem;
			tail = curitem;
		}
	}

	if(dumpParse) {
		PrintHTMLStream(outputFD, head);
	}




	/*
	 * Now we have a stream of tags and text.  Go through and deal with each.
	 */
	for(curitem = head; curitem; curitem = curitem->next) {
		TagItem *tagp=NULL;
		AVPair *pairp=NULL;
		char *src=NULL, *id=NULL, *codebase=NULL;
		PRBool hasEventHandler=PR_FALSE;
		int i;

		/* Reset archive directory for each tag */
		if(archiveDir) {
			PR_Free(archiveDir); archiveDir = NULL;
		}

		/* We only analyze tags */
		if(curitem->type != TAG_ITEM) {
			continue;
		}

		tagp = curitem->item.tag;

		/* go through the attributes to get information */
		for(pairp=tagp->attList; pairp; pairp=pairp->next) {

			/* ARCHIVE= */
			if( !PL_strcasecmp(pairp->attribute, "archive")) {
				if(archiveDir) {	
					/* Duplicate attribute.  Print warning */
					PR_fprintf(errorFD,
					  "warning: \"%s\" attribute overwrites previous attribute"
					  " in tag starting at %s:%d.\n",
					  pairp->attribute, filename, curitem->startLine);
					warningCount++;
					PR_Free(archiveDir);
				}
				archiveDir = PL_strdup(pairp->value);

				/* Substiture ".arc" for ".jar" */
				if( (PL_strlen(archiveDir)<4) ||
					PL_strcasecmp((archiveDir+strlen(archiveDir)-4), ".jar")){
					PR_fprintf(errorFD,
					  "warning: ARCHIVE attribute should end in \".jar\" in tag"
					  " starting on %s:%d.\n", filename, curitem->startLine);
					warningCount++;
					PR_Free(archiveDir);
					archiveDir = PR_smprintf("%s.arc", archiveDir);
				} else {
					PL_strcpy(archiveDir+strlen(archiveDir)-4, ".arc");
				}

				/* Record the first archive.  This will be used later if
				 * the archive is not specified */
				if(firstArchiveDir == NULL) {
					firstArchiveDir = PL_strdup(archiveDir);
				}
			}

			/* CODEBASE= */
			else if( !PL_strcasecmp(pairp->attribute, "codebase")) {
				if(codebase) {
					/* Duplicate attribute.  Print warning */
					PR_fprintf(errorFD,
					  "warning: \"%s\" attribute overwrites previous attribute"
					  " in tag staring at %s:%d.\n",
					  pairp->attribute, filename, curitem->startLine);
					warningCount++;
				}
				codebase = pairp->value;
			}

			/* SRC= and HREF= */
			else if( !PORT_Strcasecmp(pairp->attribute, "src") ||
				!PORT_Strcasecmp(pairp->attribute, "href") ) {
				if(src) {
					/* Duplicate attribute.  Print warning */
					PR_fprintf(errorFD,
					  "warning: \"%s\" attribute overwrites previous attribute"
					  " in tag staring at %s:%d.\n",
					  pairp->attribute, filename, curitem->startLine);
					warningCount++;
				}
				src = pairp->value;
			}

			/* CODE= */
			else if(!PORT_Strcasecmp(pairp->attribute, "code") ) {
				/*!!!XXX Change PORT to PL all over this code !!! */
				if(src) {
					/* Duplicate attribute.  Print warning */
					PR_fprintf(errorFD,
					  "warning: \"%s\" attribute overwrites previous attribute"
					  " ,in tag staring at %s:%d.\n",
					  pairp->attribute, filename, curitem->startLine);
					warningCount++;
				}
				src = pairp->value;

				/* Append a .class if one is not already present */
				if( (PL_strlen(src)<6) ||
					PL_strcasecmp( (src + PL_strlen(src) - 6), ".class") ) {
					src = PR_smprintf("%s.class", src);
					/* Put this string back into the data structure so it
					 * will be deallocated properly */
					PR_Free(pairp->value);
					pairp->value = src;
				}
			}

			/* ID= */
			else if (!PL_strcasecmp(pairp->attribute, "id") ) {
				if(id) {
					/* Duplicate attribute.  Print warning */
					PR_fprintf(errorFD,
					  "warning: \"%s\" attribute overwrites previous attribute"
					  " in tag staring at %s:%d.\n",
					  pairp->attribute, filename, curitem->startLine);
					warningCount++;
				}
				id = pairp->value;
			}

			/* STYLE= */
			/* style= attributes, along with JS entities, are stored into
			 * files with dynamically generated names. The filenames are
			 * based on the order in which the text is found in the file.
			 * All JS entities on all lines up to and including the line
			 * containing the end of the tag that has this style= attribute
			 * will be processed before this style=attribute.  So we need
			 * to record the line that this _tag_ (not the attribute) ends on.
			 */
			else if(!PL_strcasecmp(pairp->attribute, "style") && pairp->value) {
				HTMLItem *styleItem;
				/* Put this item on the style list */
				styleItem = CreateTextItem(PL_strdup(pairp->value),
										curitem->startLine, curitem->endLine);
				if(styleListTail == NULL) {
					styleList = styleListTail = styleItem;
				} else {
					styleListTail->next = styleItem;
					styleListTail = styleItem;
				}
			}

			/* Event handlers */
			else {
				for(i=0; i < num_handlers; i++) {
					if(!PL_strcasecmp(event_handlers[i], pairp->attribute)) {
						hasEventHandler = PR_TRUE;
						break;
					}
				}
			}

			/* JS Entity */
			{
			char *entityStart, *entityEnd;
			HTMLItem *entityItem;

			/* go through each JavaScript entity ( &{...}; ) and store it
			 * in the entityList.  The important thing is to record what
			 * line number it's on, so we can get it in the right order
			 * in relation to style= attributes.
			 * Apparently, these can't flow across lines, so the start and
			 * end line will be the same.  That helps matters.
			 */
			entityEnd = pairp->value;
			while( entityEnd &&
					(entityStart = PL_strstr(entityEnd, "&{")) != NULL) {
				entityStart +=2; /* point at beginning of actual entity */
				entityEnd = PL_strstr(entityStart, "}");
				if(entityEnd) {
					/* Put this item on the entity list */
					*entityEnd = '\0';
					entityItem = CreateTextItem(PL_strdup(entityStart),
										pairp->valueLine, pairp->valueLine);
					*entityEnd = '}';
					if(entityListTail) {
						entityListTail->next = entityItem;
						entityListTail = entityItem;
					} else {
						entityList = entityListTail = entityItem;
					}
				}
			}
			}

		}

		/* If no archive was supplied, we use the first one of the file */
		if(!archiveDir && firstArchiveDir) {
			archiveDir = PL_strdup(firstArchiveDir);
		}

		/* If we have an event handler, we need to archive this tag */
		if(hasEventHandler) {
			if(!id) {
				PR_fprintf(errorFD,
					"warning: tag starting at %s:%d has event handler but"
					" no ID attribute.  The tag will not be signed.\n",
					filename, curitem->startLine);
					warningCount++;
			} else if(!archiveDir) {
				PR_fprintf(errorFD,
					"warning: tag starting at %s:%d has event handler but"
					" no ARCHIVE attribute.  The tag will not be signed.\n",
					filename, curitem->startLine);
					warningCount++;
			} else {
				if(SaveInlineScript(tagp->text, id, basedir, archiveDir)) {
					goto loser;
				}
			}
		}

		switch(tagp->type) {
		case APPLET_TAG:
			if(!src) {
				PR_fprintf(errorFD,
					"error: APPLET tag starting on %s:%d has no CODE "
					"attribute.\n", filename, curitem->startLine);
				errorCount++;
				goto loser;
			} else if(!archiveDir) {
				PR_fprintf(errorFD,
					"error: APPLET tag starting on %s:%d has no ARCHIVE "
					"attribute.\n", filename, curitem->startLine);
				errorCount++;
				goto loser;
			} else {
				if(SaveSource(src, codebase, basedir, archiveDir)) {
					goto loser;
				}
			}
			break;
		case SCRIPT_TAG:
		case LINK_TAG:
		case STYLE_TAG:
			if(!archiveDir) {
				PR_fprintf(errorFD,
					"error: %s tag starting on %s:%d has no ARCHIVE "
					"attribute.\n", TagTypeToString(tagp->type),
					filename, curitem->startLine);
				errorCount++;
				goto loser;
			} else if(src) {
				if(SaveSource(src, codebase, basedir, archiveDir)) {
					goto loser;
				}
			} else if(id) {
				/* Save the next text item */
				if(!curitem->next || (curitem->next->type != TEXT_ITEM)) {
					PR_fprintf(errorFD,
						"warning: %s tag starting on %s:%d is not followed"
						" by script text.\n", TagTypeToString(tagp->type),
						filename, curitem->startLine);
					warningCount++;
					/* just create empty file */
					if(SaveInlineScript("", id, basedir, archiveDir)) {
						goto loser;
					}
				} else {
					curitem = curitem->next;
					if(SaveInlineScript(curitem->item.text, id, basedir,
					  archiveDir)){
						goto loser;
					}
				}
			} else {
				/* No src or id tag--warning */
				PR_fprintf(errorFD,
					"warning: %s tag starting on %s:%d has no SRC or"
					" ID attributes.  Will not sign.\n",
					TagTypeToString(tagp->type), filename, curitem->startLine);
				warningCount++;
			}
			break;
		default:
			/* do nothing for other tags */
			break;
		}

	}

	/* Now deal with all the unnamable scripts */
	if(firstArchiveDir) {
		HTMLItem *style, *entity;

		/* Go through the lists of JS entities and style attributes.  Do them
		 * in chronological order within a list.  Pick the list with the lower
		 * endLine. In case of a tie, entities come first.
		 */
		style = styleList; entity = entityList;
		while(style || entity) {
			if(!entity || (style && (style->endLine < entity->endLine))) {
				/* Process style */
				SaveUnnamableScript(style->item.text, basedir, firstArchiveDir,
					filename);
				style=style->next;
			} else {
				/* Process entity */
				SaveUnnamableScript(entity->item.text, basedir, firstArchiveDir,
					filename);
				entity=entity->next;
			}
		}
	}


	retval = 0;
loser:
	/* Blow away the stream */
	while(head) {
		curitem = head;
		head = head->next;
		DestroyHTMLItem(curitem);
	}
	while(styleList) {
		curitem = styleList;
		styleList = styleList->next;
		DestroyHTMLItem(curitem);
	}
	while(entityList) {
		curitem = entityList;
		entityList = entityList->next;
		DestroyHTMLItem(curitem);
	}
	if(text) {
		PR_Free(text); text=NULL;
	}
	if(fb) {
		FB_Destroy(fb); fb=NULL;
	}
	if(fd) {
		PR_Close(fd);
	}
	if(tagerr) {
		PR_smprintf_free(tagerr); tagerr=NULL;
	}
	if(archiveDir) {
		PR_Free(archiveDir); archiveDir=NULL;
	}
	if(firstArchiveDir) {
		PR_Free(firstArchiveDir); firstArchiveDir=NULL;
	}
	return retval;
}

/**********************************************************************
 *
 * e n s u r e E x i s t s
 *
 * Check for existence of indicated directory.  If it doesn't exist,
 * it will be created.
 * Returns PR_SUCCESS if the directory is present, PR_FAILURE otherwise.
 */
static PRStatus
ensureExists (char *base, char *path)
{
	char fn [FNSIZE];
	PRDir *dir;
	sprintf (fn, "%s/%s", base, path);

	/*PR_fprintf(outputFD, "Trying to open directory %s.\n", fn);*/

	if( (dir=PR_OpenDir(fn)) ) {
		PR_CloseDir(dir);
		return PR_SUCCESS;
	}
	return PR_MkDir(fn, 0777);
}

/***************************************************************************
 *
 * m a k e _ d i r s
 *
 * Ensure that the directory portion of the path exists.  This may require
 * making the directory, and its parent, and its parent's parent, etc.
 */
static int
make_dirs(char *path, int file_perms)
{
	char *Path;
	char *start;
	char *sep;
	int ret = 0;
	PRFileInfo info;

	if(!path) {
		return 0;
	}

	Path = PL_strdup(path);
	start = strpbrk(Path, "/\\");
	if(!start) {
		return 0;
	}
	start++; /* start right after first slash */

	/* Each time through the loop add one more directory. */
	while( (sep=strpbrk(start, "/\\")) ) {
		*sep = '\0';

		if( PR_GetFileInfo(Path, &info) != PR_SUCCESS) {
			/* No such dir, we have to create it */
			if( PR_MkDir(Path, file_perms) != PR_SUCCESS) {
				PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n",
					Path);
				errorCount++;
				ret = -1;
				goto loser;
			}
		} else {
			/* something exists by this name, make sure it's a directory */
			if( info.type != PR_FILE_DIRECTORY ) {
				PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n",
					Path);
				errorCount++;
				ret = -1;
				goto loser;
			}
		}

		start = sep+1; /* start after the next slash */
		*sep = '/';
	}

loser:
	PR_Free(Path);
	return ret;
}

/*
 *  c o p y i n t o
 *
 *  Function to copy file "from" to path "to".
 *
 */
static int
copyinto (char *from, char *to)
{
	PRInt32 num;
	char buf [BUFSIZ];
	PRFileDesc *infp=NULL, *outfp=NULL;
	int retval = -1;

	if ((infp = PR_Open(from, PR_RDONLY, 0777)) == NULL) {
		PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for reading.\n",
			from);
		errorCount++;
		goto finish;
	}

	/* If to already exists, print a warning before deleting it */
	if(PR_Access(to, PR_ACCESS_EXISTS) == PR_SUCCESS) {
		PR_fprintf(errorFD, "warning: %s already exists--will overwrite\n",
			to);
		warningCount++;
		if(rm_dash_r(to)) {
			PR_fprintf(errorFD,
				"ERROR: Unable to remove %s.\n", to);
			errorCount++;
			goto finish;
		}
	}

	if ((outfp = PR_Open(to, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0777))
					== NULL) {
		char *errBuf=NULL;

		errBuf = PR_Malloc(PR_GetErrorTextLength());
		PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for writing.\n",
			to);
		if(PR_GetErrorText(errBuf)) {
			PR_fprintf(errorFD, "Cause: %s\n", errBuf);
		}
		if(errBuf) {
			PR_Free(errBuf);
		}
		errorCount++;
		goto finish;
	}

	while( (num = PR_Read(infp, buf, BUFSIZ)) >0) {
		if(PR_Write(outfp, buf, num) != num) {
			PR_fprintf(errorFD, "ERROR: Error writing to %s.\n", to);
			errorCount++;
			goto finish;
		}
    }

	retval = 0;
finish:
	if(infp) PR_Close(infp);
 	if(outfp) PR_Close(outfp);

	return retval;
}
