/* 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 pad_i;                            \
        for (pad_i = 0; pad_i < (x); pad_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);

                /* 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);

                /* 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);
            }
        } 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);
    }
    if (subiter) {
        Pk11Install_ListIter_delete(&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;
    }
    if (copy) {
        PR_Free(copy);
    }

    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++) {
        snprintf(buf, sizeof(buf), "%s.", _this->verString[i]);
        strcat(tmp, buf);
    }
    if (i < _this->numDigits) {
        snprintf(buf, sizeof(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)
{
    char* str = NULL;
    PAD(pad);
    printf("OS: %s\n", _this->OS ? _this->OS : "<NULL>");
    PAD(pad);
    printf("Digits: ");
    if (_this->numDigits == 0) {
        printf("None\n");
    } else {
        str = Pk11Install_PlatformName_GetVerString(_this);
        printf("%s\n", str);
        PR_Free(str);
    }
    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);
                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);
                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);
                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);
                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);
                    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);
            } 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);
            }
        }
    }

    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);
    }
    if (subiter) {
        Pk11Install_ListIter_delete(&subiter);
    }
    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;
    PR_Free(*_this);
    *_this = 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);
}

/*************************************************************************/
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);
    }
}
