| /* |
| * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "jli_util.h" |
| |
| #include <zlib.h> |
| #include "manifest_info.h" |
| |
| static char *manifest; |
| |
| static const char *manifest_name = "META-INF/MANIFEST.MF"; |
| |
| /* |
| * Inflate the manifest file (or any file for that matter). |
| * |
| * fd: File descriptor of the jar file. |
| * entry: Contains the information necessary to perform the inflation |
| * (the compressed and uncompressed sizes and the offset in |
| * the file where the compressed data is located). |
| * size_out: Returns the size of the inflated file. |
| * |
| * Upon success, it returns a pointer to a NUL-terminated malloc'd buffer |
| * containing the inflated manifest file. When the caller is done with it, |
| * this buffer should be released by a call to free(). Upon failure, |
| * returns NULL. |
| */ |
| static char * |
| inflate_file(int fd, zentry *entry, int *size_out) |
| { |
| char *in; |
| char *out; |
| z_stream zs; |
| |
| if (entry->csize == (size_t) -1 || entry->isize == (size_t) -1 ) |
| return (NULL); |
| if (lseek(fd, entry->offset, SEEK_SET) < (off_t)0) |
| return (NULL); |
| if ((in = malloc(entry->csize + 1)) == NULL) |
| return (NULL); |
| if ((size_t)(read(fd, in, (unsigned int)entry->csize)) != entry->csize) { |
| free(in); |
| return (NULL); |
| } |
| if (entry->how == STORED) { |
| *(char *)((size_t)in + entry->csize) = '\0'; |
| if (size_out) { |
| *size_out = entry->csize; |
| } |
| return (in); |
| } else if (entry->how == DEFLATED) { |
| zs.zalloc = (alloc_func)Z_NULL; |
| zs.zfree = (free_func)Z_NULL; |
| zs.opaque = (voidpf)Z_NULL; |
| zs.next_in = (Byte*)in; |
| zs.avail_in = (uInt)entry->csize; |
| if (inflateInit2(&zs, -MAX_WBITS) < 0) { |
| free(in); |
| return (NULL); |
| } |
| if ((out = malloc(entry->isize + 1)) == NULL) { |
| free(in); |
| return (NULL); |
| } |
| zs.next_out = (Byte*)out; |
| zs.avail_out = (uInt)entry->isize; |
| if (inflate(&zs, Z_PARTIAL_FLUSH) < 0) { |
| free(in); |
| free(out); |
| return (NULL); |
| } |
| *(char *)((size_t)out + entry->isize) = '\0'; |
| free(in); |
| if (inflateEnd(&zs) < 0) { |
| free(out); |
| return (NULL); |
| } |
| if (size_out) { |
| *size_out = entry->isize; |
| } |
| return (out); |
| } else |
| return (NULL); |
| } |
| |
| /* |
| * A very little used routine to handle the case that zip file has |
| * a comment at the end. Believe it or not, the only way to find the |
| * END record is to walk backwards, byte by bloody byte looking for |
| * the END record signature. |
| * |
| * fd: File descriptor of the jar file. |
| * eb: Pointer to a buffer to receive a copy of the END header. |
| * |
| * Returns the offset of the END record in the file on success, |
| * -1 on failure. |
| */ |
| static off_t |
| find_end(int fd, Byte *eb) |
| { |
| off_t len; |
| off_t pos; |
| off_t flen; |
| int bytes; |
| Byte *cp; |
| Byte *endpos; |
| Byte *buffer; |
| |
| /* |
| * 99.44% (or more) of the time, there will be no comment at the |
| * end of the zip file. Try reading just enough to read the END |
| * record from the end of the file. |
| */ |
| if ((pos = lseek(fd, -ENDHDR, SEEK_END)) < (off_t)0) |
| return (-1); |
| if ((bytes = read(fd, eb, ENDHDR)) < 0) |
| return (-1); |
| if (GETSIG(eb) == ENDSIG) |
| return (pos); |
| |
| /* |
| * Shucky-Darn,... There is a comment at the end of the zip file. |
| * |
| * Allocate and fill a buffer with enough of the zip file |
| * to meet the specification for a maximal comment length. |
| */ |
| if ((flen = lseek(fd, 0, SEEK_END)) < (off_t)0) |
| return (-1); |
| len = (flen < END_MAXLEN) ? flen : END_MAXLEN; |
| if (lseek(fd, -len, SEEK_END) < (off_t)0) |
| return (-1); |
| if ((buffer = malloc(END_MAXLEN)) == NULL) |
| return (-1); |
| if ((bytes = read(fd, buffer, len)) < 0) { |
| free(buffer); |
| return (-1); |
| } |
| |
| /* |
| * Search backwards from the end of file stopping when the END header |
| * signature is found. (The first condition of the "if" is just a |
| * fast fail, because the GETSIG macro isn't always cheap. The |
| * final condition protects against false positives.) |
| */ |
| endpos = &buffer[bytes]; |
| for (cp = &buffer[bytes - ENDHDR]; cp >= &buffer[0]; cp--) |
| if ((*cp == (ENDSIG & 0xFF)) && (GETSIG(cp) == ENDSIG) && |
| (cp + ENDHDR + ENDCOM(cp) == endpos)) { |
| (void) memcpy(eb, cp, ENDHDR); |
| free(buffer); |
| return (flen - (endpos - cp)); |
| } |
| free(buffer); |
| return (-1); |
| } |
| |
| /* |
| * Locate the manifest file with the zip/jar file. |
| * |
| * fd: File descriptor of the jar file. |
| * entry: To be populated with the information necessary to perform |
| * the inflation (the compressed and uncompressed sizes and |
| * the offset in the file where the compressed data is located). |
| * |
| * Returns zero upon success. Returns a negative value upon failure. |
| * |
| * The buffer for reading the Central Directory if the zip/jar file needs |
| * to be large enough to accommodate the largest possible single record |
| * and the signature of the next record which is: |
| * |
| * 3*2**16 + CENHDR + SIGSIZ |
| * |
| * Each of the three variable sized fields (name, comment and extension) |
| * has a maximum possible size of 64k. |
| * |
| * Typically, only a small bit of this buffer is used with bytes shuffled |
| * down to the beginning of the buffer. It is one thing to allocate such |
| * a large buffer and another thing to actually start faulting it in. |
| * |
| * In most cases, all that needs to be read are the first two entries in |
| * a typical jar file (META-INF and META-INF/MANIFEST.MF). Keep this factoid |
| * in mind when optimizing this code. |
| */ |
| #define BUFSIZE (3 * 65536 + CENHDR + SIGSIZ) |
| #define MINREAD 1024 |
| |
| static int |
| find_file(int fd, zentry *entry, const char *file_name) |
| { |
| int bytes; |
| int res; |
| int entry_size; |
| int read_size; |
| int base_offset; |
| Byte *p; |
| Byte *bp; |
| Byte *buffer; |
| Byte locbuf[LOCHDR]; |
| |
| if ((buffer = (Byte*)malloc(BUFSIZE)) == NULL) { |
| return(-1); |
| } |
| |
| p = buffer; |
| bp = buffer; |
| |
| /* |
| * Read the END Header, which is the starting point for ZIP files. |
| * (Clearly designed to make writing a zip file easier than reading |
| * one. Now isn't that precious...) |
| */ |
| if ((base_offset = find_end(fd, bp)) == -1) { |
| free(buffer); |
| return (-1); |
| } |
| |
| /* |
| * There is a historical, but undocumented, ability to allow for |
| * additional "stuff" to be prepended to the zip/jar file. It seems |
| * that this has been used to prepend an actual java launcher |
| * executable to the jar on Windows. Although this is just another |
| * form of statically linking a small piece of the JVM to the |
| * application, we choose to continue to support it. Note that no |
| * guarantees have been made (or should be made) to the customer that |
| * this will continue to work. |
| * |
| * Therefore, calculate the base offset of the zip file (within the |
| * expanded file) by assuming that the central directory is followed |
| * immediately by the end record. |
| */ |
| base_offset = base_offset - ENDSIZ(p) - ENDOFF(p); |
| |
| /* |
| * The END Header indicates the start of the Central Directory |
| * Headers. Remember that the desired Central Directory Header (CEN) |
| * will almost always be the second one and the first one is a small |
| * directory entry ("META-INF/"). Keep the code optimized for |
| * that case. |
| * |
| * Begin by seeking to the beginning of the Central Directory and |
| * reading in the first buffer full of bits. |
| */ |
| if (lseek(fd, base_offset + ENDOFF(p), SEEK_SET) < (off_t)0) { |
| free(buffer); |
| return (-1); |
| } |
| if ((bytes = read(fd, bp, MINREAD)) < 0) { |
| free(buffer); |
| return (-1); |
| } |
| |
| /* |
| * Loop through the Central Directory Headers. Note that a valid zip/jar |
| * must have an ENDHDR (with ENDSIG) after the Central Directory. |
| */ |
| while (GETSIG(p) == CENSIG) { |
| |
| /* |
| * If a complete header isn't in the buffer, shift the contents |
| * of the buffer down and refill the buffer. Note that the check |
| * for "bytes < CENHDR" must be made before the test for the entire |
| * size of the header, because if bytes is less than CENHDR, the |
| * actual size of the header can't be determined. The addition of |
| * SIGSIZ guarantees that the next signature is also in the buffer |
| * for proper loop termination. |
| */ |
| if (bytes < CENHDR) { |
| p = memmove(bp, p, bytes); |
| if ((res = read(fd, bp + bytes, MINREAD)) <= 0) { |
| free(buffer); |
| return (-1); |
| } |
| bytes += res; |
| } |
| entry_size = CENHDR + CENNAM(p) + CENEXT(p) + CENCOM(p); |
| if (bytes < entry_size + SIGSIZ) { |
| if (p != bp) |
| p = memmove(bp, p, bytes); |
| read_size = entry_size - bytes + SIGSIZ; |
| read_size = (read_size < MINREAD) ? MINREAD : read_size; |
| if ((res = read(fd, bp + bytes, read_size)) <= 0) { |
| free(buffer); |
| return (-1); |
| } |
| bytes += res; |
| } |
| |
| /* |
| * Check if the name is the droid we are looking for; the jar file |
| * manifest. If so, build the entry record from the data found in |
| * the header located and return success. |
| */ |
| if (CENNAM(p) == JLI_StrLen(file_name) && |
| memcmp((p + CENHDR), file_name, JLI_StrLen(file_name)) == 0) { |
| if (lseek(fd, base_offset + CENOFF(p), SEEK_SET) < (off_t)0) { |
| free(buffer); |
| return (-1); |
| } |
| if (read(fd, locbuf, LOCHDR) < 0) { |
| free(buffer); |
| return (-1); |
| } |
| if (GETSIG(locbuf) != LOCSIG) { |
| free(buffer); |
| return (-1); |
| } |
| entry->isize = CENLEN(p); |
| entry->csize = CENSIZ(p); |
| entry->offset = base_offset + CENOFF(p) + LOCHDR + |
| LOCNAM(locbuf) + LOCEXT(locbuf); |
| entry->how = CENHOW(p); |
| free(buffer); |
| return (0); |
| } |
| |
| /* |
| * Point to the next entry and decrement the count of valid remaining |
| * bytes. |
| */ |
| bytes -= entry_size; |
| p += entry_size; |
| } |
| free(buffer); |
| return (-1); /* Fell off the end the loop without a Manifest */ |
| } |
| |
| /* |
| * Parse a Manifest file header entry into a distinct "name" and "value". |
| * Continuation lines are joined into a single "value". The documented |
| * syntax for a header entry is: |
| * |
| * header: name ":" value |
| * |
| * name: alphanum *headerchar |
| * |
| * value: SPACE *otherchar newline *continuation |
| * |
| * continuation: SPACE *otherchar newline |
| * |
| * newline: CR LF | LF | CR (not followed by LF) |
| * |
| * alphanum: {"A"-"Z"} | {"a"-"z"} | {"0"-"9"} |
| * |
| * headerchar: alphanum | "-" | "_" |
| * |
| * otherchar: any UTF-8 character except NUL, CR and LF |
| * |
| * Note that a manifest file may be composed of multiple sections, |
| * each of which may contain multiple headers. |
| * |
| * section: *header +newline |
| * |
| * nonempty-section: +header +newline |
| * |
| * (Note that the point of "nonempty-section" is unclear, because it isn't |
| * referenced elsewhere in the full specification for the Manifest file.) |
| * |
| * Arguments: |
| * lp pointer to a character pointer which points to the start |
| * of a valid header. |
| * name pointer to a character pointer which will be set to point |
| * to the name portion of the header (nul terminated). |
| * value pointer to a character pointer which will be set to point |
| * to the value portion of the header (nul terminated). |
| * |
| * Returns: |
| * 1 Successful parsing of an NV pair. lp is updated to point to the |
| * next character after the terminating newline in the string |
| * representing the Manifest file. name and value are updated to |
| * point to the strings parsed. |
| * 0 A valid end of section indicator was encountered. lp, name, and |
| * value are not modified. |
| * -1 lp does not point to a valid header. Upon return, the values of |
| * lp, name, and value are undefined. |
| */ |
| static int |
| parse_nv_pair(char **lp, char **name, char **value) |
| { |
| char *nl; |
| char *cp; |
| |
| /* |
| * End of the section - return 0. The end of section condition is |
| * indicated by either encountering a blank line or the end of the |
| * Manifest "string" (EOF). |
| */ |
| if (**lp == '\0' || **lp == '\n' || **lp == '\r') |
| return (0); |
| |
| /* |
| * Getting to here, indicates that *lp points to an "otherchar". |
| * Turn the "header" into a string on its own. |
| */ |
| nl = JLI_StrPBrk(*lp, "\n\r"); |
| if (nl == NULL) { |
| nl = JLI_StrChr(*lp, (int)'\0'); |
| } else { |
| cp = nl; /* For merging continuation lines */ |
| if (*nl == '\r' && *(nl+1) == '\n') |
| *nl++ = '\0'; |
| *nl++ = '\0'; |
| |
| /* |
| * Process any "continuation" line(s), by making them part of the |
| * "header" line. Yes, I know that we are "undoing" the NULs we |
| * just placed here, but continuation lines are the fairly rare |
| * case, so we shouldn't unnecessarily complicate the code above. |
| * |
| * Note that an entire continuation line is processed each iteration |
| * through the outer while loop. |
| */ |
| while (*nl == ' ') { |
| nl++; /* First character to be moved */ |
| while (*nl != '\n' && *nl != '\r' && *nl != '\0') |
| *cp++ = *nl++; /* Shift string */ |
| if (*nl == '\0') |
| return (-1); /* Error: newline required */ |
| *cp = '\0'; |
| if (*nl == '\r' && *(nl+1) == '\n') |
| *nl++ = '\0'; |
| *nl++ = '\0'; |
| } |
| } |
| |
| /* |
| * Separate the name from the value; |
| */ |
| cp = JLI_StrChr(*lp, (int)':'); |
| if (cp == NULL) |
| return (-1); |
| *cp++ = '\0'; /* The colon terminates the name */ |
| if (*cp != ' ') |
| return (-1); |
| *cp++ = '\0'; /* Eat the required space */ |
| *name = *lp; |
| *value = cp; |
| *lp = nl; |
| return (1); |
| } |
| |
| /* |
| * Read the manifest from the specified jar file and fill in the manifest_info |
| * structure with the information found within. |
| * |
| * Error returns are as follows: |
| * 0 Success |
| * -1 Unable to open jarfile |
| * -2 Error accessing the manifest from within the jarfile (most likely |
| * a manifest is not present, or this isn't a valid zip/jar file). |
| */ |
| int |
| JLI_ParseManifest(char *jarfile, manifest_info *info) |
| { |
| int fd; |
| zentry entry; |
| char *lp; |
| char *name; |
| char *value; |
| int rc; |
| char *splashscreen_name = NULL; |
| |
| if ((fd = open(jarfile, O_RDONLY |
| #ifdef O_BINARY |
| | O_BINARY /* use binary mode on windows */ |
| #endif |
| )) == -1) |
| return (-1); |
| |
| info->manifest_version = NULL; |
| info->main_class = NULL; |
| info->jre_version = NULL; |
| info->jre_restrict_search = 0; |
| info->splashscreen_image_file_name = NULL; |
| if (rc = find_file(fd, &entry, manifest_name) != 0) { |
| close(fd); |
| return (-2); |
| } |
| manifest = inflate_file(fd, &entry, NULL); |
| if (manifest == NULL) { |
| close(fd); |
| return (-2); |
| } |
| lp = manifest; |
| while ((rc = parse_nv_pair(&lp, &name, &value)) > 0) { |
| if (JLI_StrCaseCmp(name, "Manifest-Version") == 0) |
| info->manifest_version = value; |
| else if (JLI_StrCaseCmp(name, "Main-Class") == 0) |
| info->main_class = value; |
| else if (JLI_StrCaseCmp(name, "JRE-Version") == 0) |
| info->jre_version = value; |
| else if (JLI_StrCaseCmp(name, "JRE-Restrict-Search") == 0) { |
| if (JLI_StrCaseCmp(value, "true") == 0) |
| info->jre_restrict_search = 1; |
| } else if (JLI_StrCaseCmp(name, "Splashscreen-Image") == 0) { |
| info->splashscreen_image_file_name = value; |
| } |
| } |
| close(fd); |
| if (rc == 0) |
| return (0); |
| else |
| return (-2); |
| } |
| |
| /* |
| * Opens the jar file and unpacks the specified file from its contents. |
| * Returns NULL on failure. |
| */ |
| void * |
| JLI_JarUnpackFile(const char *jarfile, const char *filename, int *size) { |
| int fd; |
| zentry entry; |
| void *data = NULL; |
| |
| fd = open(jarfile, O_RDONLY |
| #ifdef O_BINARY |
| | O_BINARY /* use binary mode on windows */ |
| #endif |
| ); |
| if (fd != -1 && find_file(fd, &entry, filename) == 0) { |
| data = inflate_file(fd, &entry, size); |
| } |
| close(fd); |
| return (data); |
| } |
| |
| /* |
| * Specialized "free" function. |
| */ |
| void |
| JLI_FreeManifest() |
| { |
| if (manifest) |
| free(manifest); |
| } |
| |
| /* |
| * Iterate over the manifest of the specified jar file and invoke the provided |
| * closure function for each attribute encountered. |
| * |
| * Error returns are as follows: |
| * 0 Success |
| * -1 Unable to open jarfile |
| * -2 Error accessing the manifest from within the jarfile (most likely |
| * this means a manifest is not present, or it isn't a valid zip/jar file). |
| */ |
| int |
| JLI_ManifestIterate(const char *jarfile, attribute_closure ac, void *user_data) |
| { |
| int fd; |
| zentry entry; |
| char *mp; /* manifest pointer */ |
| char *lp; /* pointer into manifest, updated during iteration */ |
| char *name; |
| char *value; |
| int rc; |
| |
| if ((fd = open(jarfile, O_RDONLY |
| #ifdef O_BINARY |
| | O_BINARY /* use binary mode on windows */ |
| #endif |
| )) == -1) |
| return (-1); |
| |
| if (rc = find_file(fd, &entry, manifest_name) != 0) { |
| close(fd); |
| return (-2); |
| } |
| |
| mp = inflate_file(fd, &entry, NULL); |
| if (mp == NULL) { |
| close(fd); |
| return (-2); |
| } |
| |
| lp = mp; |
| while ((rc = parse_nv_pair(&lp, &name, &value)) > 0) { |
| (*ac)(name, value, user_data); |
| } |
| free(mp); |
| close(fd); |
| if (rc == 0) |
| return (0); |
| else |
| return (-2); |
| } |