/* 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/. */

/*
 *  JARFILE
 *
 *  Parsing of a Jar file
 */
#define JAR_SIZE 256

#include "jar.h"
#include "jarint.h"
#include "jarfile.h"

/* commercial compression */
#include "jzlib.h"

#if defined(XP_UNIX)
#include "sys/stat.h"
#endif

#include "sechash.h" /* for HASH_GetHashObject() */

PR_STATIC_ASSERT(46 == sizeof(struct ZipCentral));
PR_STATIC_ASSERT(30 == sizeof(struct ZipLocal));
PR_STATIC_ASSERT(22 == sizeof(struct ZipEnd));
PR_STATIC_ASSERT(512 == sizeof(union TarEntry));

/* extracting */
static int
jar_guess_jar(const char *filename, JAR_FILE fp);

static int
jar_inflate_memory(unsigned int method, long *length, long expected_out_len,
                   char **data);

static int
jar_physical_extraction(JAR_FILE fp, char *outpath, unsigned long offset,
                        unsigned long length);

static int
jar_physical_inflate(JAR_FILE fp, char *outpath, unsigned long offset,
                     unsigned long length, unsigned int method);

static int
jar_verify_extract(JAR *jar, char *path, char *physical_path);

static JAR_Physical *
jar_get_physical(JAR *jar, char *pathname);

static int
jar_extract_manifests(JAR *jar, jarArch format, JAR_FILE fp);

static int
jar_extract_mf(JAR *jar, jarArch format, JAR_FILE fp, char *ext);

/* indexing */
static int
jar_gen_index(JAR *jar, jarArch format, JAR_FILE fp);

static int
jar_listtar(JAR *jar, JAR_FILE fp);

static int
jar_listzip(JAR *jar, JAR_FILE fp);

/* conversions */
static int
dosdate(char *date, const char *s);

static int
dostime(char *time, const char *s);

#ifdef NSS_X86_OR_X64
/* The following macros throw up warnings. */
#if defined(__GNUC__) && !defined(NSS_NO_GCC48)
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
#define x86ShortToUint32(ii) ((const PRUint32) * ((const PRUint16 *)(ii)))
#define x86LongToUint32(ii) (*(const PRUint32 *)(ii))
#else
static PRUint32
x86ShortToUint32(const void *ii);

static PRUint32
x86LongToUint32(const void *ll);
#endif

static long
octalToLong(const char *s);

/*
 *  J A R _ p a s s _ a r c h i v e
 *
 *  For use by naive clients. Slam an entire archive file
 *  into this function. We extract manifests, parse, index
 *  the archive file, and do whatever nastiness.
 *
 */
int
JAR_pass_archive(JAR *jar, jarArch format, char *filename, const char *url)
{
    JAR_FILE fp;
    int status = 0;

    if (filename == NULL)
        return JAR_ERR_GENERAL;

    if ((fp = JAR_FOPEN(filename, "rb")) != NULL) {
        if (format == jarArchGuess)
            format = (jarArch)jar_guess_jar(filename, fp);

        jar->format = format;
        jar->url = url ? PORT_Strdup(url) : NULL;
        jar->filename = PORT_Strdup(filename);

        status = jar_gen_index(jar, format, fp);
        if (status == 0)
            status = jar_extract_manifests(jar, format, fp);

        JAR_FCLOSE(fp);
        if (status < 0)
            return status;

        /* people were expecting it this way */
        return jar->valid;
    }
    /* file not found */
    return JAR_ERR_FNF;
}

/*
 *  J A R _ p a s s _ a r c h i v e _ u n v e r i f i e d
 *
 * Same as JAR_pass_archive, but doesn't parse signatures.
 *
 */
int
JAR_pass_archive_unverified(JAR *jar, jarArch format, char *filename,
                            const char *url)
{
    JAR_FILE fp;
    int status = 0;

    if (filename == NULL) {
        return JAR_ERR_GENERAL;
    }

    if ((fp = JAR_FOPEN(filename, "rb")) != NULL) {
        if (format == jarArchGuess) {
            format = (jarArch)jar_guess_jar(filename, fp);
        }

        jar->format = format;
        jar->url = url ? PORT_Strdup(url) : NULL;
        jar->filename = PORT_Strdup(filename);

        status = jar_gen_index(jar, format, fp);
        if (status == 0) {
            status = jar_extract_mf(jar, format, fp, "mf");
        }

        JAR_FCLOSE(fp);
        if (status < 0) {
            return status;
        }

        /* people were expecting it this way */
        return jar->valid;
    }
    /* file not found */
    return JAR_ERR_FNF;
}

/*
 *  J A R _ v e r i f i e d _ e x t r a c t
 *
 *  Optimization: keep a file descriptor open
 *  inside the JAR structure, so we don't have to
 *  open the file 25 times to run java.
 *
 */

int
JAR_verified_extract(JAR *jar, char *path, char *outpath)
{
    int status = JAR_extract(jar, path, outpath);

    if (status >= 0)
        return jar_verify_extract(jar, path, outpath);
    return status;
}

int
JAR_extract(JAR *jar, char *path, char *outpath)
{
    int result;
    JAR_Physical *phy;

    if (jar->fp == NULL && jar->filename) {
        jar->fp = (FILE *)JAR_FOPEN(jar->filename, "rb");
    }
    if (jar->fp == NULL) {
        /* file not found */
        return JAR_ERR_FNF;
    }

    phy = jar_get_physical(jar, path);
    if (phy) {
        if (phy->compression == 0) {
            result = jar_physical_extraction((PRFileDesc *)jar->fp, outpath, phy->offset, phy->length);
        } else {
            /* compression methods other than 8 are unsupported,
             * but for historical reasons, jar_physical_inflate will be called for
             * unsupported compression method constants too. */
            result = jar_physical_inflate((PRFileDesc *)jar->fp, outpath,
                                          phy->offset, phy->length,
                                          (unsigned int)phy->compression);
        }

#if defined(XP_UNIX)
        if (phy->mode)
            chmod(outpath, 0400 | (mode_t)phy->mode);
#endif
    } else {
        /* pathname not found in archive */
        result = JAR_ERR_PNF;
    }
    return result;
}

/*
 *  p h y s i c a l _ e x t r a c t i o n
 *
 *  This needs to be done in chunks of say 32k, instead of
 *  in one bulk calloc. (Necessary under Win16 platform.)
 *  This is done for uncompressed entries only.
 *
 */

#define CHUNK 32768

static int
jar_physical_extraction(JAR_FILE fp, char *outpath, unsigned long offset,
                        unsigned long length)
{
    JAR_FILE out;
    char *buffer = (char *)PORT_ZAlloc(CHUNK);
    int status = 0;

    if (buffer == NULL)
        return JAR_ERR_MEMORY;

    if ((out = JAR_FOPEN(outpath, "wb")) != NULL) {
        unsigned long at = 0;

        JAR_FSEEK(fp, offset, (PRSeekWhence)0);
        while (at < length) {
            long chunk = (at + CHUNK <= length) ? CHUNK : length - at;
            if (JAR_FREAD(fp, buffer, chunk) != chunk) {
                status = JAR_ERR_DISK;
                break;
            }
            at += chunk;
            if (JAR_FWRITE(out, buffer, chunk) < chunk) {
                /* most likely a disk full error */
                status = JAR_ERR_DISK;
                break;
            }
        }
        JAR_FCLOSE(out);
    } else {
        /* error opening output file */
        status = JAR_ERR_DISK;
    }
    PORT_Free(buffer);
    return status;
}

/*
 *  j a r _ p h y s i c a l _ i n f l a t e
 *
 *  Inflate a range of bytes in a file, writing the inflated
 *  result to "outpath". Chunk based.
 *
 */
/* input and output chunks differ, assume 4x compression */

#define ICHUNK 8192
#define OCHUNK 32768

static int
jar_physical_inflate(JAR_FILE fp, char *outpath, unsigned long offset, unsigned long length,
                     unsigned int method)
{
    char *inbuf, *outbuf;
    int status = 0;
    z_stream zs;
    JAR_FILE out;

    /* Raw inflate in zlib 1.1.4 needs an extra dummy byte at the end */
    if ((inbuf = (char *)PORT_ZAlloc(ICHUNK + 1)) == NULL)
        return JAR_ERR_MEMORY;

    if ((outbuf = (char *)PORT_ZAlloc(OCHUNK)) == NULL) {
        status = JAR_ERR_MEMORY;
        goto loser;
    }

    PORT_Memset(&zs, 0, sizeof(zs));
    status = inflateInit2(&zs, -MAX_WBITS);
    if (status != Z_OK) {
        status = JAR_ERR_GENERAL;
        goto loser;
    }

    if ((out = JAR_FOPEN(outpath, "wb")) != NULL) {
        int status2 = 0;
        unsigned long at = 0;

        JAR_FSEEK(fp, offset, (PRSeekWhence)0);
        while (at < length) {
            unsigned long chunk = (at + ICHUNK <= length) ? ICHUNK : length - at;
            unsigned long tin;

            if (JAR_FREAD(fp, inbuf, chunk) != chunk) {
                /* incomplete read */
                JAR_FCLOSE(out);
                status = JAR_ERR_CORRUPT;
                break;
            }
            at += chunk;
            if (at == length) {
                /* add an extra dummy byte at the end */
                inbuf[chunk++] = 0xDD;
            }
            zs.next_in = (Bytef *)inbuf;
            zs.avail_in = chunk;
            zs.avail_out = OCHUNK;
            tin = zs.total_in;
            while ((zs.total_in - tin < chunk) || (zs.avail_out == 0)) {
                unsigned long prev_total = zs.total_out;
                unsigned long ochunk;

                zs.next_out = (Bytef *)outbuf;
                zs.avail_out = OCHUNK;
                status = inflate(&zs, Z_NO_FLUSH);
                if (status != Z_OK && status != Z_STREAM_END) {
                    /* error during decompression */
                    JAR_FCLOSE(out);
                    status = JAR_ERR_CORRUPT;
                    break;
                }
                ochunk = zs.total_out - prev_total;
                if (JAR_FWRITE(out, outbuf, ochunk) < (long)ochunk) {
                    /* most likely a disk full error */
                    status = JAR_ERR_DISK;
                    break;
                }
                if (status == Z_STREAM_END)
                    break;
            }
            if (status != Z_OK) {
                break;
            }
        }
        JAR_FCLOSE(out);
        status2 = inflateEnd(&zs);
        if (status == Z_OK) {
            status = status2;
        }
    } else {
        /* error opening output file */
        status = JAR_ERR_DISK;
    }
loser:
    if (inbuf) {
        PORT_Free(inbuf);
    }
    if (outbuf) {
        PORT_Free(outbuf);
    }
    return status;
}

/*
 *  j a r _ i n f l a t e _ m e m o r y
 *
 *  Call zlib to inflate the given memory chunk. It is re-XP_ALLOC'd,
 *  and thus appears to operate inplace to the caller.
 *
 */
static int
jar_inflate_memory(unsigned int method, long *length, long expected_out_len,
                   char **data)
{
    char *inbuf = *data;
    char *outbuf = (char *)PORT_ZAlloc(expected_out_len);
    long insz = *length;
    int status;
    z_stream zs;

    if (outbuf == NULL)
        return JAR_ERR_MEMORY;

    PORT_Memset(&zs, 0, sizeof zs);
    status = inflateInit2(&zs, -MAX_WBITS);
    if (status < 0) {
        /* error initializing zlib stream */
        PORT_Free(outbuf);
        return JAR_ERR_GENERAL;
    }

    zs.next_in = (Bytef *)inbuf;
    zs.next_out = (Bytef *)outbuf;
    zs.avail_in = insz;
    zs.avail_out = expected_out_len;

    status = inflate(&zs, Z_FINISH);
    if (status != Z_OK && status != Z_STREAM_END) {
        /* error during deflation */
        PORT_Free(outbuf);
        return JAR_ERR_GENERAL;
    }

    status = inflateEnd(&zs);
    if (status != Z_OK) {
        /* error during deflation */
        PORT_Free(outbuf);
        return JAR_ERR_GENERAL;
    }
    PORT_Free(*data);
    *data = outbuf;
    *length = zs.total_out;
    return 0;
}

/*
 *  v e r i f y _ e x t r a c t
 *
 *  Validate signature on the freshly extracted file.
 *
 */
static int
jar_verify_extract(JAR *jar, char *path, char *physical_path)
{
    int status;
    JAR_Digest dig;

    PORT_Memset(&dig, 0, sizeof dig);
    status = JAR_digest_file(physical_path, &dig);
    if (!status)
        status = JAR_verify_digest(jar, path, &dig);
    return status;
}

/*
 *  g e t _ p h y s i c a l
 *
 *  Let's get physical.
 *  Obtains the offset and length of this file in the jar file.
 *
 */
static JAR_Physical *
jar_get_physical(JAR *jar, char *pathname)
{
    ZZLink *link;
    ZZList *list = jar->phy;

    if (ZZ_ListEmpty(list))
        return NULL;

    for (link = ZZ_ListHead(list);
         !ZZ_ListIterDone(list, link);
         link = link->next) {
        JAR_Item *it = link->thing;

        if (it->type == jarTypePhy &&
            it->pathname && !PORT_Strcmp(it->pathname, pathname)) {
            JAR_Physical *phy = (JAR_Physical *)it->data;
            return phy;
        }
    }
    return NULL;
}

/*
 *  j a r _ e x t r a c t _ m a n i f e s t s
 *
 *  Extract the manifest files and parse them,
 *  from an open archive file whose contents are known.
 *
 */
static int
jar_extract_manifests(JAR *jar, jarArch format, JAR_FILE fp)
{
    int status, signatures;

    if (format != jarArchZip && format != jarArchTar)
        return JAR_ERR_CORRUPT;

    if ((status = jar_extract_mf(jar, format, fp, "mf")) < 0)
        return status;
    if (!status)
        return JAR_ERR_ORDER;
    if ((status = jar_extract_mf(jar, format, fp, "sf")) < 0)
        return status;
    if (!status)
        return JAR_ERR_ORDER;
    if ((status = jar_extract_mf(jar, format, fp, "rsa")) < 0)
        return status;
    signatures = status;
    if ((status = jar_extract_mf(jar, format, fp, "dsa")) < 0)
        return status;
    if (!(signatures += status))
        return JAR_ERR_SIG;
    return 0;
}

/*
 *  j a r _ e x t r a c t _ m f
 *
 *  Extracts manifest files based on an extension, which
 *  should be .MF, .SF, .RSA, etc. Order of the files is now no
 *  longer important when zipping jar files.
 *
 */
static int
jar_extract_mf(JAR *jar, jarArch format, JAR_FILE fp, char *ext)
{
    ZZLink *link;
    ZZList *list = jar->phy;
    int ret = 0;

    if (ZZ_ListEmpty(list))
        return JAR_ERR_PNF;

    for (link = ZZ_ListHead(list);
         ret >= 0 && !ZZ_ListIterDone(list, link);
         link = link->next) {
        JAR_Item *it = link->thing;

        if (it->type == jarTypePhy &&
            !PORT_Strncmp(it->pathname, "META-INF", 8)) {
            JAR_Physical *phy = (JAR_Physical *)it->data;
            char *fn = it->pathname + 8;
            char *e;
            char *manifest;
            long length;
            int num, status;

            if (PORT_Strlen(it->pathname) < 8)
                continue;

            if (*fn == '/' || *fn == '\\')
                fn++;
            if (*fn == 0) {
                /* just a directory entry */
                continue;
            }

            /* skip to extension */
            for (e = fn; *e && *e != '.'; e++)
                /* yip */;

            /* and skip dot */
            if (*e == '.')
                e++;
            if (PORT_Strcasecmp(ext, e)) {
                /* not the right extension */
                continue;
            }
            if (phy->length == 0 || phy->length > 0xFFFF) {
                /* manifest files cannot be zero length or too big! */
                /* the 0xFFFF limit is per J2SE SDK */
                return JAR_ERR_CORRUPT;
            }

            /* Read in the manifest and parse it */
            /* Raw inflate in zlib 1.1.4 needs an extra dummy byte at the end */
            manifest = (char *)PORT_ZAlloc(phy->length + 1);
            if (!manifest)
                return JAR_ERR_MEMORY;

            JAR_FSEEK(fp, phy->offset, (PRSeekWhence)0);
            num = JAR_FREAD(fp, manifest, phy->length);
            if (num != phy->length) {
                /* corrupt archive file */
                PORT_Free(manifest);
                return JAR_ERR_CORRUPT;
            }

            if (phy->compression == 8) {
                length = phy->length;
                /* add an extra dummy byte at the end */
                manifest[length++] = 0xDD;
                status = jar_inflate_memory((unsigned int)phy->compression,
                                            &length,
                                            phy->uncompressed_length,
                                            &manifest);
                if (status < 0) {
                    PORT_Free(manifest);
                    return status;
                }
            } else if (phy->compression) {
                /* unsupported compression method */
                PORT_Free(manifest);
                return JAR_ERR_CORRUPT;
            } else
                length = phy->length;

            status = JAR_parse_manifest(jar, manifest, length,
                                        it->pathname, "url");
            PORT_Free(manifest);
            if (status < 0)
                ret = status;
            else
                ++ret;
        } else if (it->type == jarTypePhy) {
            /* ordinary file */
        }
    }
    return ret;
}

/*
 *  j a r _ g e n _ i n d e x
 *
 *  Generate an index for the various types of
 *  known archive files. Right now .ZIP and .TAR
 *
 */
static int
jar_gen_index(JAR *jar, jarArch format, JAR_FILE fp)
{
    int result = JAR_ERR_CORRUPT;

    JAR_FSEEK(fp, 0, (PRSeekWhence)0);
    switch (format) {
        case jarArchZip:
            result = jar_listzip(jar, fp);
            break;

        case jarArchTar:
            result = jar_listtar(jar, fp);
            break;

        case jarArchGuess:
        case jarArchNone:
            return JAR_ERR_GENERAL;
    }
    JAR_FSEEK(fp, 0, (PRSeekWhence)0);
    return result;
}

/*
 *  j a r _ l i s t z i p
 *
 *  List the physical contents of a Phil Katz
 *  style .ZIP file into the JAR linked list.
 *
 */
static int
jar_listzip(JAR *jar, JAR_FILE fp)
{
    ZZLink *ent;
    JAR_Item *it = NULL;
    JAR_Physical *phy = NULL;
    struct ZipLocal *Local = PORT_ZNew(struct ZipLocal);
    struct ZipCentral *Central = PORT_ZNew(struct ZipCentral);
    struct ZipEnd *End = PORT_ZNew(struct ZipEnd);

    int err = 0;
    long pos = 0L;
    unsigned int compression;
    unsigned int filename_len, extra_len;

    char filename[JAR_SIZE];
    char date[9], time[9];
    char sig[4];

    if (!Local || !Central || !End) {
        /* out of memory */
        err = JAR_ERR_MEMORY;
        goto loser;
    }

    while (1) {
        PRUint32 sigVal;
        JAR_FSEEK(fp, pos, (PRSeekWhence)0);

        if (JAR_FREAD(fp, sig, sizeof sig) != sizeof sig) {
            /* zip file ends prematurely */
            err = JAR_ERR_CORRUPT;
            goto loser;
        }

        JAR_FSEEK(fp, pos, (PRSeekWhence)0);
        sigVal = x86LongToUint32(sig);
        if (sigVal == LSIG) {
            JAR_FREAD(fp, Local, sizeof *Local);

            filename_len = x86ShortToUint32(Local->filename_len);
            extra_len = x86ShortToUint32(Local->extrafield_len);
            if (filename_len >= JAR_SIZE) {
                /* corrupt zip file */
                err = JAR_ERR_CORRUPT;
                goto loser;
            }

            if (JAR_FREAD(fp, filename, filename_len) != filename_len) {
                /* truncated archive file */
                err = JAR_ERR_CORRUPT;
                goto loser;
            }
            filename[filename_len] = 0;
            /* Add this to our jar chain */
            phy = PORT_ZNew(JAR_Physical);
            if (phy == NULL) {
                err = JAR_ERR_MEMORY;
                goto loser;
            }

            /* We will index any file that comes our way, but when it comes
               to actually extraction, compression must be 0 or 8 */
            compression = x86ShortToUint32(Local->method);
            phy->compression = (compression <= 255) ? compression : 222;
            /* XXX 222 is bad magic. */

            phy->offset = pos + (sizeof *Local) + filename_len + extra_len;
            phy->length = x86LongToUint32(Local->size);
            phy->uncompressed_length = x86LongToUint32(Local->orglen);

            dosdate(date, Local->date);
            dostime(time, Local->time);

            it = PORT_ZNew(JAR_Item);
            if (it == NULL) {
                err = JAR_ERR_MEMORY;
                goto loser;
            }

            it->pathname = PORT_Strdup(filename);
            it->type = jarTypePhy;
            it->data = (unsigned char *)phy;
            it->size = sizeof(JAR_Physical);

            ent = ZZ_NewLink(it);
            if (ent == NULL) {
                err = JAR_ERR_MEMORY;
                goto loser;
            }

            ZZ_AppendLink(jar->phy, ent);
            pos = phy->offset + phy->length;
        } else if (sigVal == CSIG) {
#if defined(XP_UNIX)
            unsigned int attr = 0;
#endif
            if (JAR_FREAD(fp, Central, sizeof *Central) != sizeof *Central) {
                /* apparently truncated archive */
                err = JAR_ERR_CORRUPT;
                goto loser;
            }

#if defined(XP_UNIX)
            /* with unix we need to locate any bits from
               the protection mask in the external attributes. */
            attr = Central->external_attributes[2]; /* magic */
            if (attr) {
                /* we have to read the filename, again */
                filename_len = x86ShortToUint32(Central->filename_len);
                if (filename_len >= JAR_SIZE) {
                    /* corrupt in central directory */
                    err = JAR_ERR_CORRUPT;
                    goto loser;
                }

                if (JAR_FREAD(fp, filename, filename_len) != filename_len) {
                    /* truncated in central directory */
                    err = JAR_ERR_CORRUPT;
                    goto loser;
                }
                filename[filename_len] = 0;

                /* look up this name again */
                phy = jar_get_physical(jar, filename);
                if (phy) {
                    /* always allow access by self */
                    phy->mode = 0400 | attr;
                }
            }
#endif
            pos += sizeof(struct ZipCentral) +
                   x86ShortToUint32(Central->filename_len) +
                   x86ShortToUint32(Central->commentfield_len) +
                   x86ShortToUint32(Central->extrafield_len);
        } else if (sigVal == ESIG) {
            if (JAR_FREAD(fp, End, sizeof *End) != sizeof *End) {
                err = JAR_ERR_CORRUPT;
                goto loser;
            }
            break;
        } else {
            /* garbage in archive */
            err = JAR_ERR_CORRUPT;
            goto loser;
        }
    }

loser:
    if (Local)
        PORT_Free(Local);
    if (phy && it == NULL)
        PORT_Free(phy);
    if (Central)
        PORT_Free(Central);
    if (End)
        PORT_Free(End);
    return err;
}

/*
 *  j a r _ l i s t t a r
 *
 *  List the physical contents of a Unix
 *  .tar file into the JAR linked list.
 *
 */
static int
jar_listtar(JAR *jar, JAR_FILE fp)
{
    char *s;
    JAR_Physical *phy;
    long pos = 0L;
    long sz;
    union TarEntry tarball;

    while (1) {
        JAR_FSEEK(fp, pos, (PRSeekWhence)0);

        if (JAR_FREAD(fp, &tarball, sizeof tarball) < sizeof tarball)
            break;

        /* Force NUL-termination within the fixed-size filename field so
         * the unbounded string scan below cannot walk past it. */
        tarball.val.filename[sizeof tarball.val.filename - 1] = 0;

        if (!*tarball.val.filename)
            break;

        sz = octalToLong(tarball.val.size);

        /* Tag the end of filename */
        s = tarball.val.filename;
        while (*s && *s != ' ')
            s++;
        *s = 0;

        /* Add to our linked list */
        phy = PORT_ZNew(JAR_Physical);
        if (phy == NULL)
            return JAR_ERR_MEMORY;

        phy->compression = 0;
        phy->offset = pos + sizeof tarball;
        phy->length = sz;

        ADDITEM(jar->phy, jarTypePhy, tarball.val.filename, phy,
                sizeof *phy);

        /* Advance to next file entry */
        sz = PR_ROUNDUP(sz, sizeof tarball);
        pos += sz + sizeof tarball;
    }

    return 0;
}

/*
 *  d o s d a t e
 *
 *  Not used right now, but keep it in here because
 *  it will be needed.
 *
 */
static int
dosdate(char *date, const char *s)
{
    PRUint32 num = x86ShortToUint32(s);

    PR_snprintf(date, 9, "%02d-%02d-%02d", ((num >> 5) & 0x0F), (num & 0x1F),
                ((num >> 9) + 80));
    return 0;
}

/*
 *  d o s t i m e
 *
 *  Not used right now, but keep it in here because
 *  it will be needed.
 *
 */
static int
dostime(char *time, const char *s)
{
    PRUint32 num = x86ShortToUint32(s);

    PR_snprintf(time, 6, "%02d:%02d", ((num >> 11) & 0x1F),
                ((num >> 5) & 0x3F));
    return 0;
}

#ifndef NSS_X86_OR_X64
/*
 *  Simulates an x86 (little endian, unaligned) ushort fetch from any address.
 */
static PRUint32
x86ShortToUint32(const void *v)
{
    const unsigned char *ii = (const unsigned char *)v;
    PRUint32 ret = (PRUint32)(ii[0]) | ((PRUint32)(ii[1]) << 8);
    return ret;
}

/*
 *  Simulates an x86 (little endian, unaligned) uint fetch from any address.
 */
static PRUint32
x86LongToUint32(const void *v)
{
    const unsigned char *ll = (const unsigned char *)v;
    PRUint32 ret;

    ret = ((((PRUint32)(ll[0])) << 0) |
           (((PRUint32)(ll[1])) << 8) |
           (((PRUint32)(ll[2])) << 16) |
           (((PRUint32)(ll[3])) << 24));
    return ret;
}
#endif

/*
 *  ASCII octal to binary long.
 *  Used for integer encoding inside tar files.
 *
 */
static long
octalToLong(const char *s)
{
    long num = 0L;

    while (*s == ' ')
        s++;
    while (*s >= '0' && *s <= '7') {
        num <<= 3;
        num += *s++ - '0';
    }
    return num;
}

/*
 *  g u e s s _ j a r
 *
 *  Try to guess what kind of JAR file this is.
 *  Maybe tar, maybe zip. Look in the file for magic
 *  or at its filename.
 *
 */
static int
jar_guess_jar(const char *filename, JAR_FILE fp)
{
    PRInt32 len = PORT_Strlen(filename);
    const char *ext = filename + len - 4; /* 4 for ".tar" */

    if (len >= 4 && !PL_strcasecmp(ext, ".tar"))
        return jarArchTar;
    return jarArchZip;
}
