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