/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "install-ds.h"
#include <prmem.h>
#include <plstr.h>
#include <prprf.h>
#include <string.h>

#define PORT_Strcasecmp PL_strcasecmp

#define MODULE_FILE_STRING "ModuleFile"
#define MODULE_NAME_STRING "ModuleName"
#define MECH_FLAGS_STRING "DefaultMechanismFlags"
#define CIPHER_FLAGS_STRING "DefaultCipherFlags"
#define FILES_STRING "Files"
#define FORWARD_COMPATIBLE_STRING "ForwardCompatible"
#define PLATFORMS_STRING "Platforms"
#define RELATIVE_DIR_STRING "RelativePath"
#define ABSOLUTE_DIR_STRING "AbsolutePath"
#define FILE_PERMISSIONS_STRING "FilePermissions"
#define EQUIVALENT_PLATFORM_STRING "EquivalentPlatform"
#define EXECUTABLE_STRING "Executable"

#define DEFAULT_PERMISSIONS 0777

#define PLATFORM_SEPARATOR_CHAR ':'

/* Error codes */
enum {
	BOGUS_RELATIVE_DIR=0,
	BOGUS_ABSOLUTE_DIR,
	BOGUS_FILE_PERMISSIONS,
	NO_RELATIVE_DIR,
	NO_ABSOLUTE_DIR,
	EMPTY_PLATFORM_STRING,
	BOGUS_PLATFORM_STRING,
	REPEAT_MODULE_FILE,
	REPEAT_MODULE_NAME,
	BOGUS_MODULE_FILE,
	BOGUS_MODULE_NAME,
	REPEAT_MECH,
	BOGUS_MECH_FLAGS,
	REPEAT_CIPHER,
	BOGUS_CIPHER_FLAGS,
	REPEAT_FILES,
	REPEAT_EQUIV,
	BOGUS_EQUIV,
	EQUIV_TOO_MUCH_INFO,
	NO_FILES,
	NO_MODULE_FILE,
	NO_MODULE_NAME,
	NO_PLATFORMS,
	EQUIV_LOOP,
	UNKNOWN_MODULE_FILE
};

/* Indexed by the above error codes */
static const char *errString[] = {
	"%s: Invalid relative directory",
	"%s: Invalid absolute directory",
	"%s: Invalid file permissions",
	"%s: No relative directory specified",
	"%s: No absolute directory specified",
	"Empty string given for platform name",
	"%s: invalid platform string",
	"More than one ModuleFile entry given for platform %s",
	"More than one ModuleName entry given for platform %s",
	"Invalid ModuleFile specification for platform %s",
	"Invalid ModuleName specification for platform %s",
	"More than one DefaultMechanismFlags entry given for platform %s",
	"Invalid DefaultMechanismFlags specification for platform %s",
	"More than one DefaultCipherFlags entry given for platform %s",
	"Invalid DefaultCipherFlags entry given for platform %s",
	"More than one Files entry given for platform %s",
	"More than one EquivalentPlatform entry given for platform %s",
	"Invalid EquivalentPlatform specification for platform %s",
	"Module %s uses an EquivalentPlatform but also specifies its own"
		" information",
	"No Files specification in module %s",
	"No ModuleFile specification in module %s",
	"No ModuleName specification in module %s",
	"No Platforms specification in installer script",
	"Platform %s has an equivalency loop",
	"Module file \"%s\" in platform \"%s\" does not exist"
};

static char* PR_Strdup(const char* str);

#define PAD(x)  {int i; for(i=0;i<x;i++) printf(" ");}
#define PADINC 4

Pk11Install_File*
Pk11Install_File_new()
{
	Pk11Install_File* new_this;
	new_this = (Pk11Install_File*)PR_Malloc(sizeof(Pk11Install_File));
	Pk11Install_File_init(new_this);
	return new_this;
}

void
Pk11Install_File_init(Pk11Install_File* _this)
{
	_this->jarPath=NULL;
	_this->relativePath=NULL;
	_this->absolutePath=NULL;
	_this->executable=PR_FALSE;
	_this->permissions=0;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	~Pk11Install_File
// Class:	Pk11Install_File
// Notes:	Destructor.
*/
void
Pk11Install_File_delete(Pk11Install_File* _this)
{
	Pk11Install_File_Cleanup(_this);
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Cleanup
// Class:	Pk11Install_File
*/
void
Pk11Install_File_Cleanup(Pk11Install_File* _this)
{
	if(_this->jarPath) {
		PR_Free(_this->jarPath);
		_this->jarPath = NULL;
	}
	if(_this->relativePath) {
		PR_Free(_this->relativePath);
		_this->relativePath = NULL;
	}
	if(_this->absolutePath) {
		PR_Free(_this->absolutePath);
		_this->absolutePath = NULL;
	}

	_this->permissions = 0;
	_this->executable = PR_FALSE;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Generate
// Class:	Pk11Install_File
// Notes:	Creates a file data structure from a syntax tree.
// Returns:	NULL for success, otherwise an error message.
*/
char*
Pk11Install_File_Generate(Pk11Install_File* _this,
                          const Pk11Install_Pair *pair)
{
	Pk11Install_ListIter *iter;
	Pk11Install_Value *val;
	Pk11Install_Pair *subpair;
	Pk11Install_ListIter *subiter;
	Pk11Install_Value *subval;
	char* errStr;
	char *endp;
	PRBool gotPerms;

	iter=NULL;
	subiter=NULL;
	errStr=NULL;
	gotPerms=PR_FALSE;

	/* Clear out old values */
	Pk11Install_File_Cleanup(_this);

	_this->jarPath = PR_Strdup(pair->key);

	/* Go through all the pairs under this file heading */
	iter = Pk11Install_ListIter_new(pair->list);
	for( ; (val = iter->current); Pk11Install_ListIter_nextItem(iter)) {
		if(val->type == PAIR_VALUE) {
			subpair = val->pair;

			/* Relative directory */
			if(!PORT_Strcasecmp(subpair->key, RELATIVE_DIR_STRING)) {
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)){
					errStr = PR_smprintf(errString[BOGUS_RELATIVE_DIR], 
                                    _this->jarPath);
					goto loser;
				}
				_this->relativePath = PR_Strdup(subval->string);
				Pk11Install_ListIter_delete(subiter);
				subiter = NULL;

				/* Absolute directory */
			} else if( !PORT_Strcasecmp(subpair->key, ABSOLUTE_DIR_STRING)) {
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)){
					errStr = PR_smprintf(errString[BOGUS_ABSOLUTE_DIR], 
                                    _this->jarPath);
					goto loser;
				}
				_this->absolutePath = PR_Strdup(subval->string);
				Pk11Install_ListIter_delete(subiter);
				subiter = NULL;

			/* file permissions */
			} else if( !PORT_Strcasecmp(subpair->key,
                                     FILE_PERMISSIONS_STRING)) {
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE) ||
				   !subval->string || !subval->string[0]){
					errStr = PR_smprintf(errString[BOGUS_FILE_PERMISSIONS],
                                    _this->jarPath);
					goto loser;
				}
				_this->permissions = (int) strtol(subval->string, &endp, 8);
				if(*endp != '\0') {
					errStr = PR_smprintf(errString[BOGUS_FILE_PERMISSIONS],
                                    _this->jarPath);
					goto loser;
				}
				gotPerms = PR_TRUE;
				Pk11Install_ListIter_delete(subiter);
				subiter = NULL;
			}
		} else {
			if(!PORT_Strcasecmp(val->string, EXECUTABLE_STRING)) {
				_this->executable = PR_TRUE;
			}
		}
	}

	/* Default permission value */
	if(!gotPerms) {
		_this->permissions = DEFAULT_PERMISSIONS;
	}

	/* Make sure we got all the information */
	if(!_this->relativePath && !_this->absolutePath) {
		errStr = PR_smprintf(errString[NO_ABSOLUTE_DIR], _this->jarPath);
		goto loser;
	}
#if 0
	if(!_this->relativePath ) {
		errStr = PR_smprintf(errString[NO_RELATIVE_DIR], _this->jarPath);
		goto loser;
	}
	if(!_this->absolutePath) {
		errStr = PR_smprintf(errString[NO_ABSOLUTE_DIR], _this->jarPath);
		goto loser;
	}
#endif

loser:
	if(iter) {
		Pk11Install_ListIter_delete(iter);
		PR_Free(iter);
	}
	if(subiter) {
		Pk11Install_ListIter_delete(subiter);
		PR_Free(subiter);
	}
	return errStr;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Print
// Class:	Pk11Install_File
*/
void
Pk11Install_File_Print(Pk11Install_File* _this, int pad)
{
	PAD(pad); printf("jarPath: %s\n", 
                    _this->jarPath ? _this->jarPath : "<NULL>");
	PAD(pad); printf("relativePath: %s\n",
				_this->relativePath ? _this->relativePath: "<NULL>");
	PAD(pad); printf("absolutePath: %s\n",
				_this->absolutePath ? _this->absolutePath: "<NULL>");
	PAD(pad); printf("permissions: %o\n", _this->permissions);
}

Pk11Install_PlatformName*
Pk11Install_PlatformName_new()
{
	Pk11Install_PlatformName* new_this;
	new_this = (Pk11Install_PlatformName*)
               PR_Malloc(sizeof(Pk11Install_PlatformName));
	Pk11Install_PlatformName_init(new_this);
	return new_this;
}

void
Pk11Install_PlatformName_init(Pk11Install_PlatformName* _this)
{
	_this->OS = NULL;
	_this->verString = NULL;
	_this->numDigits = 0;
	_this->arch = NULL;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	~Pk11Install_PlatformName
// Class:	Pk11Install_PlatformName
*/
void
Pk11Install_PlatformName_delete(Pk11Install_PlatformName* _this)
{
	Pk11Install_PlatformName_Cleanup(_this);
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Cleanup
// Class:	Pk11Install_PlatformName
*/
void
Pk11Install_PlatformName_Cleanup(Pk11Install_PlatformName* _this)
{
	if(_this->OS) {
		PR_Free(_this->OS);
		_this->OS = NULL;
	}
	if(_this->verString) {
		int i;
		for (i=0; i<_this->numDigits; i++) {
			PR_Free(_this->verString[i]);
		}
		PR_Free(_this->verString);
		_this->verString = NULL;
	}
	if(_this->arch) {
		PR_Free(_this->arch);
		_this->arch = NULL;
	}
	_this->numDigits = 0;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Generate
// Class:	Pk11Install_PlatformName
// Notes:	Extracts the information from a platform string.
*/
char*
Pk11Install_PlatformName_Generate(Pk11Install_PlatformName* _this,
                                  const char *str)
{
	char *errStr;
	char *copy;
	char *end, *start; /* start and end of a section (OS, version, arch)*/
	char *pend, *pstart; /* start and end of one portion of version*/
	char *endp; /* used by strtol*/
	int periods, i;

	errStr=NULL;
	copy=NULL;

	if(!str) {
		errStr = PR_smprintf(errString[EMPTY_PLATFORM_STRING]);
		goto loser;
	}
	copy = PR_Strdup(str);

	/*
	// Get the OS
	*/
	end = strchr(copy, PLATFORM_SEPARATOR_CHAR);
	if(!end || end==copy) {
		errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
		goto loser;
	}
	*end = '\0';

	_this->OS = PR_Strdup(copy);

	/*
	// Get the digits of the version of form: x.x.x (arbitrary number of digits)
	*/

	start = end+1;
	end = strchr(start, PLATFORM_SEPARATOR_CHAR);
	if(!end) {
		errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
		goto loser;
	}
	*end = '\0';

	if(end!=start) { 
		/* Find out how many periods*/
		periods = 0;
		pstart = start;
		while( (pend=strchr(pstart, '.')) ) {
			periods++;
			pstart = pend+1;
		}
		_this->numDigits= 1+ periods;
		_this->verString = (char**)PR_Malloc(sizeof(char*)*_this->numDigits);

		pstart = start;
		i = 0;
		/* Get the digits before each period*/
		while( (pend=strchr(pstart, '.')) ) {
			if(pend == pstart) {
				errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
				goto loser;
			}
			*pend = '\0';
			_this->verString[i] = PR_Strdup(pstart);
			endp = pend;
		if(endp==pstart || (*endp != '\0')) {
				errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
				goto loser;
			}
			pstart = pend+1;
			i++;
		}
		/* Last digit comes after the last period*/
		if(*pstart == '\0') {
			errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
			goto loser;
		}
		_this->verString[i] = PR_Strdup(pstart);
		/*
		if(endp==pstart || (*endp != '\0')) {
			errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
			goto loser;
		}
		*/
	} else {
		_this->verString = NULL;
		_this->numDigits = 0;
	}

	/*
	// Get the architecture
	*/
	start = end+1;
	if( strchr(start, PLATFORM_SEPARATOR_CHAR) ) {
		errStr = PR_smprintf(errString[BOGUS_PLATFORM_STRING], str);
		goto loser;
	}
	_this->arch = PR_Strdup(start);

	if(copy) {
		PR_Free(copy);
	}
	return NULL;
loser:
	if(_this->OS) {
		PR_Free(_this->OS);
		_this->OS = NULL;
	}
	if(_this->verString) {
		for (i=0; i<_this->numDigits; i++) {
			PR_Free(_this->verString[i]);
		}
		PR_Free(_this->verString);
		_this->verString = NULL;
	}
	_this->numDigits = 0;
	if(_this->arch) {
		PR_Free(_this->arch);
		_this->arch = NULL;
	}

	return errStr;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	operator ==
// Class:	Pk11Install_PlatformName
// Returns:	PR_TRUE if the platform have the same OS, arch, and version
*/
PRBool
Pk11Install_PlatformName_equal(Pk11Install_PlatformName* _this,
                               Pk11Install_PlatformName* cmp) 
{
	int i;

	if(!_this->OS || !_this->arch || !cmp->OS || !cmp->arch) {
		return PR_FALSE;
	}

	if(	PORT_Strcasecmp(_this->OS, cmp->OS) ||
		PORT_Strcasecmp(_this->arch, cmp->arch) ||
		_this->numDigits != cmp->numDigits ) {
			return PR_FALSE;
	}

	for(i=0; i < _this->numDigits; i++) {
		if(PORT_Strcasecmp(_this->verString[i], cmp->verString[i])) {
			return PR_FALSE;
		}
	}
	return PR_TRUE;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	operator <=
// Class:	Pk11Install_PlatformName
// Returns:	PR_TRUE if the platform have the same OS and arch and a lower
//			or equal release.
*/
PRBool
Pk11Install_PlatformName_lteq(Pk11Install_PlatformName* _this,
                              Pk11Install_PlatformName* cmp)
{
	return (Pk11Install_PlatformName_equal(_this,cmp) ||
          Pk11Install_PlatformName_lt(_this,cmp)) ? PR_TRUE : PR_FALSE;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	operator <
// Class:	Pk11Install_PlatformName
// Returns:	PR_TRUE if the platform have the same OS and arch and a greater
//			release.
*/
PRBool
Pk11Install_PlatformName_lt(Pk11Install_PlatformName* _this,
                            Pk11Install_PlatformName* cmp)
{
	int i, scmp;

	if(!_this->OS || !_this->arch || !cmp->OS || !cmp->arch) {
		return PR_FALSE;
	}

	if( PORT_Strcasecmp(_this->OS, cmp->OS) ) {
		return PR_FALSE;
	}
	if( PORT_Strcasecmp(_this->arch, cmp->arch) ) {
		return PR_FALSE;
	}

	for(i=0; (i < _this->numDigits) && (i < cmp->numDigits); i++) {
		scmp = PORT_Strcasecmp(_this->verString[i], cmp->verString[i]);
		if (scmp > 0) {
			return PR_FALSE;
		} else if (scmp < 0) {
			return PR_TRUE;
		}
	}
	/* All the digits they have in common are the same. */
	if(_this->numDigits < cmp->numDigits) {
		return  PR_TRUE;
	} 

	return PR_FALSE;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	GetString
// Class:	Pk11Install_PlatformName
// Returns:	String composed of OS, release, and architecture separated
//			by the separator char.  Memory is allocated by this function
//			but is the responsibility of the caller to de-allocate.
*/
char*
Pk11Install_PlatformName_GetString(Pk11Install_PlatformName* _this) 
{
	char *ret;
	char *ver;
	char *OS_;
	char *arch_;

	OS_=NULL;
	arch_=NULL;

	OS_ = _this->OS ? _this->OS : "";
	arch_ = _this->arch ? _this->arch : "";

	ver = Pk11Install_PlatformName_GetVerString(_this);
	ret = PR_smprintf("%s%c%s%c%s", OS_, PLATFORM_SEPARATOR_CHAR, ver,
					PLATFORM_SEPARATOR_CHAR, arch_);

	PR_Free(ver);

	return ret;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	GetVerString
// Class:	Pk11Install_PlatformName
// Returns:	The version string for this platform, in the form x.x.x with an
//			arbitrary number of digits.  Memory allocated by function,
//			must be de-allocated by caller.
*/
char*
Pk11Install_PlatformName_GetVerString(Pk11Install_PlatformName* _this) 
{
	char *tmp;
	char *ret;
	int i;
	char buf[80];

	tmp = (char*)PR_Malloc(80*_this->numDigits+1);
	tmp[0] = '\0';

	for(i=0; i < _this->numDigits-1; i++) {
		sprintf(buf, "%s.", _this->verString[i]);
		strcat(tmp, buf);
	}
	if(i < _this->numDigits) {
		sprintf(buf, "%s", _this->verString[i]);
		strcat(tmp, buf);
	}

	ret = PR_Strdup(tmp);
	free(tmp);

	return ret;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Print
// Class:	Pk11Install_PlatformName
*/
void
Pk11Install_PlatformName_Print(Pk11Install_PlatformName* _this, int pad)
{
	PAD(pad); printf("OS: %s\n", _this->OS ? _this->OS : "<NULL>");
	PAD(pad); printf("Digits: ");
	if(_this->numDigits == 0) {
		printf("None\n");
	} else {
		printf("%s\n", Pk11Install_PlatformName_GetVerString(_this));
	}
	PAD(pad); printf("arch: %s\n", _this->arch ? _this->arch : "<NULL>");
}

Pk11Install_Platform*
Pk11Install_Platform_new()
{
	Pk11Install_Platform* new_this;
	new_this = (Pk11Install_Platform*)PR_Malloc(sizeof(Pk11Install_Platform));
	Pk11Install_Platform_init(new_this);
	return new_this;
}

void
Pk11Install_Platform_init(Pk11Install_Platform* _this)
{
	Pk11Install_PlatformName_init(&_this->name);
	Pk11Install_PlatformName_init(&_this->equivName);
	_this->equiv = NULL;
	_this->usesEquiv = PR_FALSE;
	_this->moduleFile = NULL;
	_this->moduleName = NULL;
	_this->modFile = -1;
	_this->mechFlags = 0;
	_this->cipherFlags = 0;
	_this->files = NULL;
	_this->numFiles = 0;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	~Pk11Install_Platform
// Class:	Pk11Install_Platform
*/
void
Pk11Install_Platform_delete(Pk11Install_Platform* _this)
{
	Pk11Install_Platform_Cleanup(_this);
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Cleanup
// Class:	Pk11Install_Platform
*/
void
Pk11Install_Platform_Cleanup(Pk11Install_Platform* _this)
{
	int i;
	if(_this->moduleFile) {
		PR_Free(_this->moduleFile);
		_this->moduleFile = NULL;
	}
	if(_this->moduleName) {
		PR_Free(_this->moduleName);
		_this->moduleName = NULL;
	}
	if(_this->files) {
		for (i=0;i<_this->numFiles;i++) {
			Pk11Install_File_delete(&_this->files[i]);
		}
		PR_Free(_this->files);
		_this->files = NULL;
	}
	_this->equiv = NULL;
	_this->usesEquiv = PR_FALSE;
	_this->modFile = -1;
	_this->numFiles = 0;
	_this->mechFlags = _this->cipherFlags = 0;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:	Generate
// Class:	Pk11Install_Platform
// Notes:	Creates a platform data structure from a syntax tree.
// Returns:	NULL for success, otherwise an error message.
*/
char*
Pk11Install_Platform_Generate(Pk11Install_Platform* _this,
                              const Pk11Install_Pair *pair)
{
	char* errStr;
	char* endptr;
	char* tmp;
	int i;
	Pk11Install_ListIter *iter;
	Pk11Install_Value *val;
	Pk11Install_Value *subval;
	Pk11Install_Pair *subpair;
	Pk11Install_ListIter *subiter;
	PRBool gotModuleFile, gotModuleName, gotMech, 
          gotCipher, gotFiles, gotEquiv;

	errStr=NULL;
	iter=subiter=NULL;
	val=subval=NULL;
	subpair=NULL;
	gotModuleFile=gotModuleName=gotMech=gotCipher=gotFiles=gotEquiv=PR_FALSE;
	Pk11Install_Platform_Cleanup(_this);

	errStr = Pk11Install_PlatformName_Generate(&_this->name,pair->key);
	if(errStr) {
		tmp = PR_smprintf("%s: %s", pair->key, errStr);
		PR_smprintf_free(errStr);
		errStr = tmp;
		goto loser;
	}

	iter = Pk11Install_ListIter_new(pair->list);
	for( ; (val=iter->current); Pk11Install_ListIter_nextItem(iter)) {
		if(val->type==PAIR_VALUE) {
			subpair = val->pair;

			if( !PORT_Strcasecmp(subpair->key, MODULE_FILE_STRING)) {
				if(gotModuleFile) {
					errStr = PR_smprintf(errString[REPEAT_MODULE_FILE],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)) {
					errStr = PR_smprintf(errString[BOGUS_MODULE_FILE],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				_this->moduleFile = PR_Strdup(subval->string);
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter = NULL;
				gotModuleFile = PR_TRUE;
			} else if(!PORT_Strcasecmp(subpair->key, MODULE_NAME_STRING)){
				if(gotModuleName) {
					errStr = PR_smprintf(errString[REPEAT_MODULE_NAME],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)) {
					errStr = PR_smprintf(errString[BOGUS_MODULE_NAME],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				_this->moduleName = PR_Strdup(subval->string);
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter = NULL;
				gotModuleName = PR_TRUE;
			} else if(!PORT_Strcasecmp(subpair->key, MECH_FLAGS_STRING)) {
				endptr=NULL;

				if(gotMech) {
					errStr = PR_smprintf(errString[REPEAT_MECH],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)) {
					errStr = PR_smprintf(errString[BOGUS_MECH_FLAGS],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				_this->mechFlags = strtol(subval->string, &endptr, 0);
				if(*endptr!='\0' || (endptr==subval->string) ) {
					errStr = PR_smprintf(errString[BOGUS_MECH_FLAGS],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter=NULL;
				gotMech = PR_TRUE;
			} else if(!PORT_Strcasecmp(subpair->key,CIPHER_FLAGS_STRING)) {
				endptr=NULL;

				if(gotCipher) {
					errStr = PR_smprintf(errString[REPEAT_CIPHER],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE)) {
					errStr = PR_smprintf(errString[BOGUS_CIPHER_FLAGS],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				_this->cipherFlags = strtol(subval->string, &endptr, 0);
				if(*endptr!='\0' || (endptr==subval->string) ) {
					errStr = PR_smprintf(errString[BOGUS_CIPHER_FLAGS],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter=NULL;
				gotCipher = PR_TRUE;
			} else if(!PORT_Strcasecmp(subpair->key, FILES_STRING)) {
				if(gotFiles) {
					errStr = PR_smprintf(errString[REPEAT_FILES],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				_this->numFiles = subpair->list->numPairs;
				_this->files = (Pk11Install_File*)
                            PR_Malloc(sizeof(Pk11Install_File)*_this->numFiles);
				for(i=0; i < _this->numFiles; i++, 
                                   Pk11Install_ListIter_nextItem(subiter)) {
					Pk11Install_File_init(&_this->files[i]);
					val = subiter->current;
					if(val && (val->type==PAIR_VALUE)) {
						errStr = Pk11Install_File_Generate(&_this->files[i],val->pair);
						if(errStr) {
							tmp = PR_smprintf("%s: %s", 
                                       Pk11Install_PlatformName_GetString(&_this->name),errStr);
							PR_smprintf_free(errStr);
							errStr = tmp;
							goto loser;
						}
					}
				}
				gotFiles = PR_TRUE;
			} else if(!PORT_Strcasecmp(subpair->key,
                                    EQUIVALENT_PLATFORM_STRING)) {
				if(gotEquiv) {
					errStr = PR_smprintf(errString[REPEAT_EQUIV],
                                    Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				subiter = Pk11Install_ListIter_new(subpair->list);
				subval = subiter->current;
				if(!subval || (subval->type != STRING_VALUE) ) {
					errStr = PR_smprintf(errString[BOGUS_EQUIV],
                               Pk11Install_PlatformName_GetString(&_this->name));
					goto loser;
				}
				errStr = Pk11Install_PlatformName_Generate(&_this->equivName,
                                                       subval->string);
				if(errStr) {
					tmp = PR_smprintf("%s: %s", 
                            Pk11Install_PlatformName_GetString(&_this->name), errStr);
					tmp = PR_smprintf("%s: %s", 
                            Pk11Install_PlatformName_GetString(&_this->name), errStr);
					PR_smprintf_free(errStr);
					errStr = tmp;
					goto loser;
				}
				_this->usesEquiv = PR_TRUE;
			}
		}
	}

	/* Make sure we either have an EquivalentPlatform or all the other info */
	if(_this->usesEquiv &&
		(gotFiles || gotModuleFile || gotModuleName || gotMech || gotCipher)) {
		errStr = PR_smprintf(errString[EQUIV_TOO_MUCH_INFO], 
                           Pk11Install_PlatformName_GetString(&_this->name));
		goto loser;
	}
	if(!gotFiles && !_this->usesEquiv) {
		errStr = PR_smprintf(errString[NO_FILES], 
                           Pk11Install_PlatformName_GetString(&_this->name));
		goto loser;
	}
	if(!gotModuleFile && !_this->usesEquiv) {
		errStr= PR_smprintf(errString[NO_MODULE_FILE], 
                          Pk11Install_PlatformName_GetString(&_this->name));
		goto loser;
	}
	if(!gotModuleName && !_this->usesEquiv) {
		errStr = PR_smprintf(errString[NO_MODULE_NAME], 
                          Pk11Install_PlatformName_GetString(&_this->name));
		goto loser;
	}

	/* Point the modFile pointer to the correct file */
	if(gotModuleFile) {
		for(i=0; i < _this->numFiles; i++) {
			if(!PORT_Strcasecmp(_this->moduleFile, _this->files[i].jarPath) ) {
				_this->modFile = i;
				break;
			}
		}
		if(_this->modFile==-1) {
			errStr = PR_smprintf(errString[UNKNOWN_MODULE_FILE], 
                              _this->moduleFile,
                              Pk11Install_PlatformName_GetString(&_this->name));
			goto loser;
		}
	}
	
loser:
	if(iter) {
		PR_Free(iter);
	}
	if(subiter) {
		PR_Free(subiter);
	}
	return errStr;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		Print
// Class:		Pk11Install_Platform
*/
void
Pk11Install_Platform_Print(Pk11Install_Platform* _this, int pad)
{
	int i;

	PAD(pad); printf("Name:\n"); 
	Pk11Install_PlatformName_Print(&_this->name,pad+PADINC);
	PAD(pad); printf("equivName:\n"); 
	Pk11Install_PlatformName_Print(&_this->equivName,pad+PADINC);
	PAD(pad);
	if(_this->usesEquiv) {
		printf("Uses equiv, which points to:\n");
		Pk11Install_Platform_Print(_this->equiv,pad+PADINC);
	} else {
		printf("Doesn't use equiv\n");
	}
	PAD(pad); 
	printf("Module File: %s\n", _this->moduleFile ? _this->moduleFile 
                                                 : "<NULL>");
	PAD(pad); printf("mechFlags: %lx\n", _this->mechFlags);
	PAD(pad); printf("cipherFlags: %lx\n", _this->cipherFlags);
	PAD(pad); printf("Files:\n");
	for(i=0; i < _this->numFiles; i++) {
		Pk11Install_File_Print(&_this->files[i],pad+PADINC);
		PAD(pad); printf("--------------------\n");
	}
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		Pk11Install_Info
// Class:		Pk11Install_Info
*/
Pk11Install_Info*
Pk11Install_Info_new()
{
	Pk11Install_Info* new_this;
	new_this = (Pk11Install_Info*)PR_Malloc(sizeof(Pk11Install_Info));
	Pk11Install_Info_init(new_this);
	return new_this;
}

void
Pk11Install_Info_init(Pk11Install_Info* _this)
{
	_this->platforms = NULL;
	_this->numPlatforms = 0;
	_this->forwardCompatible = NULL;
	_this->numForwardCompatible = 0;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		~Pk11Install_Info
// Class:		Pk11Install_Info
*/
void
Pk11Install_Info_delete(Pk11Install_Info* _this)
{
	Pk11Install_Info_Cleanup(_this);
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		Cleanup
// Class:		Pk11Install_Info
*/
void
Pk11Install_Info_Cleanup(Pk11Install_Info* _this)
{
	int i;
	if(_this->platforms) {
		for (i=0;i<_this->numPlatforms;i++) {
			Pk11Install_Platform_delete(&_this->platforms[i]);
		}
		PR_Free(&_this->platforms);
		_this->platforms = NULL;
		_this->numPlatforms = 0;
	}

	if(_this->forwardCompatible) {
		for (i=0;i<_this->numForwardCompatible;i++) {
			Pk11Install_PlatformName_delete(&_this->forwardCompatible[i]);
		}
		PR_Free(&_this->forwardCompatible);
		_this->numForwardCompatible = 0;
	}
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		Generate
// Class:		Pk11Install_Info
// Takes:		Pk11Install_ValueList *list, the top-level list
//				resulting from parsing an installer file.
// Returns:		char*, NULL if successful, otherwise an error string.
//				Caller is responsible for freeing memory.
*/
char*
Pk11Install_Info_Generate(Pk11Install_Info* _this,
                          const Pk11Install_ValueList *list)
{
	char *errStr;
	Pk11Install_ListIter *iter;
	Pk11Install_Value *val;
	Pk11Install_Pair *pair;
	Pk11Install_ListIter *subiter;
	Pk11Install_Value *subval;
	Pk11Install_Platform *first, *second;
	int i, j;

	errStr=NULL;
	iter=subiter=NULL;
	Pk11Install_Info_Cleanup(_this);

	iter = Pk11Install_ListIter_new(list);
	for( ; (val=iter->current); Pk11Install_ListIter_nextItem(iter)) {
		if(val->type == PAIR_VALUE) {
			pair = val->pair;

			if(!PORT_Strcasecmp(pair->key, FORWARD_COMPATIBLE_STRING)) {
				subiter = Pk11Install_ListIter_new(pair->list);
				_this->numForwardCompatible = pair->list->numStrings;
				_this->forwardCompatible = (Pk11Install_PlatformName*)
                                        PR_Malloc(sizeof(Pk11Install_PlatformName)*
                                               _this->numForwardCompatible);
				for(i=0; i < _this->numForwardCompatible; i++, 
                       Pk11Install_ListIter_nextItem(subiter)) {
					subval = subiter->current;
					if(subval->type == STRING_VALUE) {
						errStr = Pk11Install_PlatformName_Generate(
                              &_this->forwardCompatible[i], subval->string);
						if(errStr) {
							goto loser;
						}
					}
				}
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter = NULL;
			} else if(!PORT_Strcasecmp(pair->key, PLATFORMS_STRING)) {
				subiter = Pk11Install_ListIter_new(pair->list);
				_this->numPlatforms = pair->list->numPairs;
				_this->platforms = (Pk11Install_Platform*)
                            PR_Malloc(sizeof(Pk11Install_Platform)*
                            _this->numPlatforms);
				for(i=0; i < _this->numPlatforms; i++, 
                       Pk11Install_ListIter_nextItem(subiter)) {
					 Pk11Install_Platform_init(&_this->platforms[i]);
					subval = subiter->current;
					if(subval->type == PAIR_VALUE) {
						errStr = Pk11Install_Platform_Generate(&_this->platforms[i],subval->pair);
						if(errStr) {
							goto loser;
						}
					}
				}
				Pk11Install_ListIter_delete(subiter);
				PR_Free(subiter);
				subiter = NULL;
			}
		}
	}

	if(_this->numPlatforms == 0) {
		errStr = PR_smprintf(errString[NO_PLATFORMS]);
		goto loser;
	}

/*
	//
	// Now process equivalent platforms
	//

	// First the naive pass
*/
	for(i=0; i < _this->numPlatforms; i++) {
		if(_this->platforms[i].usesEquiv) {
			_this->platforms[i].equiv = NULL;
			for(j=0; j < _this->numPlatforms; j++) {
				if (Pk11Install_PlatformName_equal(&_this->platforms[i].equivName,
                                           &_this->platforms[j].name)) {
					if(i==j) {
						errStr = PR_smprintf(errString[EQUIV_LOOP],
                              Pk11Install_PlatformName_GetString(&_this->platforms[i].name));
						goto loser;
					}
					_this->platforms[i].equiv = &_this->platforms[j];
					break;
				}
			}
			if(_this->platforms[i].equiv == NULL) {
				errStr = PR_smprintf(errString[BOGUS_EQUIV],
                       Pk11Install_PlatformName_GetString(&_this->platforms[i].name));
				goto loser;
			}
		}
	}

/*
	// Now the intelligent pass, which will also detect loops.
	// We will send two pointers through the linked list of equivalent
	// platforms. Both start with the current node.  "first" traverses
	// two nodes for each iteration.  "second" lags behind, only traversing
	// one node per iteration.  Eventually one of two things will happen:
	// first will hit the end of the list (a platform that doesn't use
	// an equivalency), or first will equal second if there is a loop.
*/
	for(i=0; i < _this->numPlatforms; i++) {
		if(_this->platforms[i].usesEquiv) {
			second = _this->platforms[i].equiv;
			if(!second->usesEquiv) {
				/* The first link is the terminal node */
				continue;
			}
			first = second->equiv;
			while(first->usesEquiv) {
				if(first == second) {
					errStr = PR_smprintf(errString[EQUIV_LOOP],
                         Pk11Install_PlatformName_GetString(&_this->platforms[i].name));
					goto loser;
				}
				first = first->equiv;
				if(!first->usesEquiv) {
					break;
				}
				if(first == second) {
					errStr = PR_smprintf(errString[EQUIV_LOOP],
                       Pk11Install_PlatformName_GetString(&_this->platforms[i].name));
					goto loser;
				}
				second = second->equiv;
				first = first->equiv;
			}
			_this->platforms[i].equiv = first;
		}
	}

loser:
	if(iter) {
		Pk11Install_ListIter_delete(iter);
		PR_Free(iter);
		iter = NULL;
	}
	if(subiter) {
		Pk11Install_ListIter_delete(subiter);
		PR_Free(subiter);
		subiter = NULL;
	}
	return errStr;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		GetBestPlatform
// Class:		Pk11Install_Info
// Takes:		char *myPlatform, the platform we are currently running
//				on.
*/
Pk11Install_Platform*
Pk11Install_Info_GetBestPlatform(Pk11Install_Info* _this, char *myPlatform)
{
	Pk11Install_PlatformName plat;
	char *errStr;
	int i, j;

	errStr=NULL;

	Pk11Install_PlatformName_init(&plat);
	if( (errStr=Pk11Install_PlatformName_Generate(&plat, myPlatform)) ) {
		PR_smprintf_free(errStr);
		return NULL;
	}

	/* First try real platforms */
	for(i=0; i < _this->numPlatforms; i++) {
		if(Pk11Install_PlatformName_equal(&_this->platforms[i].name,&plat)) {
			if(_this->platforms[i].equiv) {
				return _this->platforms[i].equiv;
			}
			else {
				return &_this->platforms[i];
			}
		}
	}

	/* Now try forward compatible platforms */
	for(i=0; i < _this->numForwardCompatible; i++) {
		if(Pk11Install_PlatformName_lteq(&_this->forwardCompatible[i],&plat)) {
			break;
		}
	}
	if(i == _this->numForwardCompatible) {
		return NULL;
	}

	/* Got a forward compatible name, find the actual platform. */
	for(j=0; j < _this->numPlatforms; j++) {
		if(Pk11Install_PlatformName_equal(&_this->platforms[j].name,
         &_this->forwardCompatible[i])) {
			if(_this->platforms[j].equiv) {
				return _this->platforms[j].equiv;
			} else {
				return &_this->platforms[j];
			}
		}
	}

	return NULL;
}

/*
//////////////////////////////////////////////////////////////////////////
// Method:		Print
// Class:		Pk11Install_Info
*/
void
Pk11Install_Info_Print(Pk11Install_Info* _this, int pad)
{
	int i;

	PAD(pad); printf("Forward Compatible:\n");
	for(i = 0; i < _this->numForwardCompatible; i++) {
		Pk11Install_PlatformName_Print(&_this->forwardCompatible[i],pad+PADINC);
		PAD(pad); printf("-------------------\n");
	}
	PAD(pad); printf("Platforms:\n");
	for( i = 0; i < _this->numPlatforms; i++) {
		Pk11Install_Platform_Print(&_this->platforms[i],pad+PADINC);
		PAD(pad); printf("-------------------\n");
	}
}

/*
//////////////////////////////////////////////////////////////////////////
*/
static char*
PR_Strdup(const char* str)
{
	char *tmp;
	tmp = (char*) PR_Malloc((unsigned int)(strlen(str)+1));
	strcpy(tmp, str);
	return tmp;
}

/* The global value list, the top of the tree */
Pk11Install_ValueList* Pk11Install_valueList=NULL;

/****************************************************************************/
void
Pk11Install_ValueList_AddItem(Pk11Install_ValueList* _this,
                              Pk11Install_Value *item)
{
	_this->numItems++;
	if (item->type == STRING_VALUE) {
		_this->numStrings++;
	} else {
		_this->numPairs++;
	}
	item->next = _this->head;
	_this->head = item;
}

/****************************************************************************/
Pk11Install_ListIter*
Pk11Install_ListIter_new_default()
{
	Pk11Install_ListIter* new_this;
	new_this = (Pk11Install_ListIter*)
                    PR_Malloc(sizeof(Pk11Install_ListIter));
	Pk11Install_ListIter_init(new_this);
	return new_this;
}

/****************************************************************************/
void
Pk11Install_ListIter_init(Pk11Install_ListIter* _this)
{
	_this->list = NULL;
	_this->current = NULL;
}

/****************************************************************************/
Pk11Install_ListIter*
Pk11Install_ListIter_new(const Pk11Install_ValueList *_list)
{
	Pk11Install_ListIter* new_this;
	new_this = (Pk11Install_ListIter*)
                    PR_Malloc(sizeof(Pk11Install_ListIter));
	new_this->list = _list;
	new_this->current = _list->head;
	return new_this;
}

/****************************************************************************/
void
Pk11Install_ListIter_delete(Pk11Install_ListIter* _this)
{
	_this->list=NULL;
	_this->current=NULL;
}

/****************************************************************************/
void
Pk11Install_ListIter_reset(Pk11Install_ListIter* _this)
{
	if(_this->list) {
		_this->current = _this->list->head;
	}
}

/*************************************************************************/
Pk11Install_Value*
Pk11Install_ListIter_nextItem(Pk11Install_ListIter* _this)
{
	if(_this->current) {
		_this->current = _this->current->next;
	}

	return _this->current;
}

/****************************************************************************/
Pk11Install_ValueList*
Pk11Install_ValueList_new()
{
	Pk11Install_ValueList* new_this;
	new_this = (Pk11Install_ValueList*)
                    PR_Malloc(sizeof(Pk11Install_ValueList));
	new_this->numItems = 0;
	new_this->numPairs = 0;
	new_this->numStrings = 0;
	new_this->head = NULL;
	return new_this;
}

/****************************************************************************/
void
Pk11Install_ValueList_delete(Pk11Install_ValueList* _this)
{

	Pk11Install_Value *tmp;
	Pk11Install_Value *list;
	list = _this->head;
	
	while(list != NULL) {
		tmp = list;
		list = list->next;
		PR_Free(tmp);
	}
	PR_Free(_this);
}

/****************************************************************************/
Pk11Install_Value*
Pk11Install_Value_new_default()
{
	Pk11Install_Value* new_this;
	new_this = (Pk11Install_Value*)PR_Malloc(sizeof(Pk11Install_Value));
	new_this->type = STRING_VALUE;
	new_this->string = NULL;
	new_this->pair = NULL;
	new_this->next = NULL;
	return new_this;
}

/****************************************************************************/
Pk11Install_Value*
Pk11Install_Value_new(ValueType _type, Pk11Install_Pointer ptr)
{
	Pk11Install_Value* new_this;
	new_this = Pk11Install_Value_new_default();
	new_this->type = _type;
	if(_type == STRING_VALUE) {
		new_this->pair = NULL;
		new_this->string = ptr.string;
	} else {
		new_this->string = NULL;
		new_this->pair = ptr.pair;
	}
	return new_this;
}

/****************************************************************************/
void
Pk11Install_Value_delete(Pk11Install_Value* _this)
{
	if(_this->type == STRING_VALUE) {
		PR_Free(_this->string);
	} else {
		PR_Free(_this->pair);
	}
}

/****************************************************************************/
Pk11Install_Pair*
Pk11Install_Pair_new_default()
{
	return Pk11Install_Pair_new(NULL,NULL);
}

/****************************************************************************/
Pk11Install_Pair*
Pk11Install_Pair_new(char *_key, Pk11Install_ValueList *_list)
{
	Pk11Install_Pair* new_this;
	new_this = (Pk11Install_Pair*)PR_Malloc(sizeof(Pk11Install_Pair));
	new_this->key = _key;
	new_this->list = _list;
	return new_this;
}

/****************************************************************************/
void
Pk11Install_Pair_delete(Pk11Install_Pair* _this)
{
	PR_Free(_this->key);
	Pk11Install_ValueList_delete(_this->list);
	PR_Free(_this->list);
}

/*************************************************************************/
void
Pk11Install_Pair_Print(Pk11Install_Pair* _this, int pad)
{
	while (_this) {
		/*PAD(pad); printf("**Pair\n");
		PAD(pad); printf("***Key====\n");*/
		PAD(pad); printf("%s {\n", _this->key);
		/*PAD(pad); printf("====\n");*/
		/*PAD(pad); printf("***ValueList\n");*/
		Pk11Install_ValueList_Print(_this->list,pad+PADINC);
		PAD(pad); printf("}\n");
	}
}

/*************************************************************************/
void
Pk11Install_ValueList_Print(Pk11Install_ValueList* _this, int pad)
{
	Pk11Install_Value *v;

	/*PAD(pad);printf("**Value List**\n");*/
	for(v = _this->head; v != NULL; v=v->next) {
		Pk11Install_Value_Print(v,pad);
	}
}

/*************************************************************************/
void
Pk11Install_Value_Print(Pk11Install_Value* _this, int pad)
{
	/*PAD(pad); printf("**Value, type=%s\n",
		type==STRING_VALUE ? "string" : "pair");*/
	if(_this->type==STRING_VALUE) {
		/*PAD(pad+PADINC); printf("====\n");*/
		PAD(pad); printf("%s\n", _this->string);
		/*PAD(pad+PADINC); printf("====\n");*/
	} else {
		Pk11Install_Pair_Print(_this->pair,pad+PADINC);
	}
}
