/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * 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) || defined(XP_BEOS) #include "sys/stat.h" #endif #include "sechash.h" /* for HASH_GetHashObject() */ /* extracting */ static int jar_guess_jar (char *filename, JAR_FILE fp); static int jar_inflate_memory (unsigned int method, long *length, long expected_out_len, char ZHUGEP **data); static int jar_physical_extraction (JAR_FILE fp, char *outpath, long offset, long length); static int jar_physical_inflate (JAR_FILE fp, char *outpath, long offset, 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, char *s); static int dostime (char *time, char *s); static unsigned int xtoint (unsigned char *ii); static unsigned long xtolong (unsigned char *ll); static long atoo (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; } else { /* 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; } else { /* 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; status = JAR_extract (jar, path, outpath); if (status >= 0) return jar_verify_extract (jar, path, outpath); else 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 && phy->compression != 8) { /* unsupported compression method */ result = JAR_ERR_CORRUPT; } if (phy->compression == 0) { result = jar_physical_extraction ((PRFileDesc*)jar->fp, outpath, phy->offset, phy->length); } else { result = jar_physical_inflate ((PRFileDesc*)jar->fp, outpath, phy->offset, phy->length, (unsigned int) phy->compression); } #if defined(XP_UNIX) || defined(XP_BEOS) 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, long offset, long length) { JAR_FILE out; char *buffer; long at, chunk; int status = 0; buffer = (char *) PORT_ZAlloc (CHUNK); if (buffer == NULL) return JAR_ERR_MEMORY; if ((out = JAR_FOPEN (outpath, "wb")) != NULL) { at = 0; JAR_FSEEK (fp, offset, (PRSeekWhence)0); while (at < length) { 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, long offset, long length, unsigned int method) { z_stream zs; JAR_FILE out; long at, chunk; char *inbuf, *outbuf; int status = 0; unsigned long prev_total, ochunk, tin; /* 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) { PORT_Free (inbuf); return JAR_ERR_MEMORY; } PORT_Memset (&zs, 0, sizeof (zs)); status = inflateInit2 (&zs, -MAX_WBITS); if (status != Z_OK) { PORT_Free (inbuf); PORT_Free (outbuf); return JAR_ERR_GENERAL; } if ((out = JAR_FOPEN (outpath, "wb")) != NULL) { at = 0; JAR_FSEEK (fp, offset, (PRSeekWhence)0); while (at < length) { chunk = (at + ICHUNK <= length) ? ICHUNK : length - at; if (JAR_FREAD (fp, inbuf, chunk) != chunk) { /* incomplete read */ JAR_FCLOSE (out); PORT_Free (inbuf); PORT_Free (outbuf); return JAR_ERR_CORRUPT; } 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)) { prev_total = zs.total_out; 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); PORT_Free (inbuf); PORT_Free (outbuf); return JAR_ERR_CORRUPT; } ochunk = zs.total_out - prev_total; if (JAR_FWRITE (out, outbuf, ochunk) < ochunk) { /* most likely a disk full error */ status = JAR_ERR_DISK; break; } if (status == Z_STREAM_END) break; } } JAR_FCLOSE (out); status = inflateEnd (&zs); } else { /* error opening output file */ status = JAR_ERR_DISK; } PORT_Free (inbuf); 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 ZHUGEP **data) { int status; z_stream zs; long insz, outsz; char *inbuf, *outbuf; inbuf = *data; insz = *length; outsz = expected_out_len; outbuf = (char*)PORT_ZAlloc (outsz); 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 = outsz; 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 (JAR_Digest)); 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) { JAR_Item *it; JAR_Physical *phy; ZZLink *link; ZZList *list; list = jar->phy; if (ZZ_ListEmpty (list)) return NULL; for (link = ZZ_ListHead (list); !ZZ_ListIterDone (list, link); link = link->next) { it = link->thing; if (it->type == jarTypePhy && it->pathname && !PORT_Strcmp (it->pathname, pathname)) { 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) { JAR_Item *it; JAR_Physical *phy; ZZLink *link; ZZList *list; char *fn, *e; char ZHUGEP *manifest; long length; int status, ret = 0, num; list = jar->phy; if (ZZ_ListEmpty (list)) return JAR_ERR_PNF; for (link = ZZ_ListHead (list); ret >= 0 && !ZZ_ListIterDone (list, link); link = link->next) { it = link->thing; if (it->type == jarTypePhy && !PORT_Strncmp (it->pathname, "META-INF", 8)) { phy = (JAR_Physical *) it->data; if (PORT_Strlen (it->pathname) < 8) continue; fn = it->pathname + 8; 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 ZHUGEP *) PORT_ZAlloc (phy->length + 1); if (manifest) { 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 return JAR_ERR_MEMORY; } 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) { int err = 0; long pos = 0L; char filename [JAR_SIZE]; char date [9], time [9]; char sig [4]; unsigned int compression; unsigned int filename_len, extra_len; struct ZipLocal *Local; struct ZipCentral *Central; struct ZipEnd *End; /* phy things */ ZZLink *ent; JAR_Item *it; JAR_Physical *phy; Local = (struct ZipLocal *) PORT_ZAlloc (30); Central = (struct ZipCentral *) PORT_ZAlloc (46); End = (struct ZipEnd *) PORT_ZAlloc (22); if (!Local || !Central || !End) { /* out of memory */ err = JAR_ERR_MEMORY; goto loser; } while (1) { JAR_FSEEK (fp, pos, (PRSeekWhence)0); if (JAR_FREAD (fp, (char *) sig, 4) != 4) { /* zip file ends prematurely */ err = JAR_ERR_CORRUPT; goto loser; } JAR_FSEEK (fp, pos, (PRSeekWhence)0); if (xtolong ((unsigned char *)sig) == LSIG) { JAR_FREAD (fp, (char *) Local, 30); filename_len = xtoint ((unsigned char *) Local->filename_len); extra_len = xtoint ((unsigned char *) 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 = (JAR_Physical *) PORT_ZAlloc (sizeof (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 = xtoint ((unsigned char *) Local->method); phy->compression = compression >= 0 && compression <= 255 ? compression : 222; phy->offset = pos + 30 + filename_len + extra_len; phy->length = xtolong ((unsigned char *) Local->size); phy->uncompressed_length = xtolong((unsigned char *) Local->orglen); dosdate (date, Local->date); dostime (time, Local->time); it = (JAR_Item*)PORT_ZAlloc (sizeof (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 (xtolong ( (unsigned char *)sig) == CSIG) { if (JAR_FREAD (fp, (char *) Central, 46) != 46) { /* apparently truncated archive */ err = JAR_ERR_CORRUPT; goto loser; } #if defined(XP_UNIX) || defined(XP_BEOS) /* with unix we need to locate any bits from the protection mask in the external attributes. */ { unsigned int attr; /* determined empirically */ attr = Central->external_attributes [2]; if (attr) { /* we have to read the filename, again */ filename_len = xtoint ((unsigned char *) 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 += 46 + xtoint ( (unsigned char *)Central->filename_len) + xtoint ( (unsigned char *)Central->commentfield_len) + xtoint ( (unsigned char *)Central->extrafield_len); } else if (xtolong ( (unsigned char *)sig) == ESIG) { if (JAR_FREAD (fp, (char *) End, 22) != 22) { err = JAR_ERR_CORRUPT; goto loser; } else break; } else { /* garbage in archive */ err = JAR_ERR_CORRUPT; goto loser; } } loser: if (Local) PORT_Free (Local); 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) { long pos = 0L; long sz, mode; time_t when; union TarEntry tarball; char *s; /* phy things */ JAR_Physical *phy; while (1) { JAR_FSEEK (fp, pos, (PRSeekWhence)0); if (JAR_FREAD (fp, (char *) &tarball, 512) < 512) break; if (!*tarball.val.filename) break; when = atoo (tarball.val.time); sz = atoo (tarball.val.size); mode = atoo (tarball.val.mode); /* Tag the end of filename */ s = tarball.val.filename; while (*s && *s != ' ') s++; *s = 0; /* Add to our linked list */ phy = (JAR_Physical *) PORT_ZAlloc (sizeof (JAR_Physical)); if (phy == NULL) return JAR_ERR_MEMORY; phy->compression = 0; phy->offset = pos + 512; phy->length = sz; ADDITEM (jar->phy, jarTypePhy, tarball.val.filename, phy, sizeof (JAR_Physical)); /* Advance to next file entry */ sz += 511; sz = (sz / 512) * 512; pos += sz + 512; } 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, char *s) { int num = xtoint ( (unsigned char *)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, char *s) { int num = xtoint ( (unsigned char *)s); PR_snprintf (time, 6, "%02d:%02d", ((num >> 11) & 0x1F), ((num >> 5) & 0x3F)); return 0; } /* * x t o i n t * * Converts a two byte ugly endianed integer * to our platform's integer. * */ static unsigned int xtoint (unsigned char *ii) { return (int) (ii [0]) | ((int) ii [1] << 8); } /* * x t o l o n g * * Converts a four byte ugly endianed integer * to our platform's integer. * */ static unsigned long xtolong (unsigned char *ll) { unsigned long ret; ret = ( (((unsigned long) ll [0]) << 0) | (((unsigned long) ll [1]) << 8) | (((unsigned long) ll [2]) << 16) | (((unsigned long) ll [3]) << 24) ); return ret; } /* * a t o o * * Ascii octal to decimal. * Used for integer encoding inside tar files. * */ static long atoo (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 (char *filename, JAR_FILE fp) { char *ext; ext = filename + PORT_Strlen (filename) - 4; if (!PORT_Strcmp (ext, ".tar")) return jarArchTar; return jarArchZip; }