1 /* 2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.jar; 27 28 import java.util.zip.*; 29 import java.io.*; 30 31 /** 32 * The <code>JarOutputStream</code> class is used to write the contents 33 * of a JAR file to any output stream. It extends the class 34 * <code>java.util.zip.ZipOutputStream</code> with support 35 * for writing an optional <code>Manifest</code> entry. The 36 * <code>Manifest</code> can be used to specify meta-information about 37 * the JAR file and its entries. 38 * 39 * @author David Connelly 40 * @see Manifest 41 * @see java.util.zip.ZipOutputStream 42 * @since 1.2 43 */ 44 public 45 class JarOutputStream extends ZipOutputStream { 46 private static final int JAR_MAGIC = 0xCAFE; 47 48 /** 49 * Creates a new <code>JarOutputStream</code> with the specified 50 * <code>Manifest</code>. The manifest is written as the first 51 * entry to the output stream. 52 * 53 * @param out the actual output stream 54 * @param man the optional <code>Manifest</code> 55 * @exception IOException if an I/O error has occurred 56 */ JarOutputStream(OutputStream out, Manifest man)57 public JarOutputStream(OutputStream out, Manifest man) throws IOException { 58 super(out); 59 if (man == null) { 60 throw new NullPointerException("man"); 61 } 62 ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME); 63 putNextEntry(e); 64 man.write(new BufferedOutputStream(this)); 65 closeEntry(); 66 } 67 68 /** 69 * Creates a new <code>JarOutputStream</code> with no manifest. 70 * @param out the actual output stream 71 * @exception IOException if an I/O error has occurred 72 */ JarOutputStream(OutputStream out)73 public JarOutputStream(OutputStream out) throws IOException { 74 super(out); 75 } 76 77 /** 78 * Begins writing a new JAR file entry and positions the stream 79 * to the start of the entry data. This method will also close 80 * any previous entry. The default compression method will be 81 * used if no compression method was specified for the entry. 82 * The current time will be used if the entry has no set modification 83 * time. 84 * 85 * @param ze the ZIP/JAR entry to be written 86 * @exception ZipException if a ZIP error has occurred 87 * @exception IOException if an I/O error has occurred 88 */ putNextEntry(ZipEntry ze)89 public void putNextEntry(ZipEntry ze) throws IOException { 90 if (firstEntry) { 91 // Make sure that extra field data for first JAR 92 // entry includes JAR magic number id. 93 byte[] edata = ze.getExtra(); 94 if (edata == null || !hasMagic(edata)) { 95 if (edata == null) { 96 edata = new byte[4]; 97 } else { 98 // Prepend magic to existing extra data 99 byte[] tmp = new byte[edata.length + 4]; 100 System.arraycopy(edata, 0, tmp, 4, edata.length); 101 edata = tmp; 102 } 103 set16(edata, 0, JAR_MAGIC); // extra field id 104 set16(edata, 2, 0); // extra field size 105 ze.setExtra(edata); 106 } 107 firstEntry = false; 108 } 109 super.putNextEntry(ze); 110 } 111 112 private boolean firstEntry = true; 113 114 /* 115 * Returns true if specified byte array contains the 116 * jar magic extra field id. 117 */ hasMagic(byte[] edata)118 private static boolean hasMagic(byte[] edata) { 119 try { 120 int i = 0; 121 while (i < edata.length) { 122 if (get16(edata, i) == JAR_MAGIC) { 123 return true; 124 } 125 i += get16(edata, i + 2) + 4; 126 } 127 } catch (ArrayIndexOutOfBoundsException e) { 128 // Invalid extra field data 129 } 130 return false; 131 } 132 133 /* 134 * Fetches unsigned 16-bit value from byte array at specified offset. 135 * The bytes are assumed to be in Intel (little-endian) byte order. 136 */ get16(byte[] b, int off)137 private static int get16(byte[] b, int off) { 138 return Byte.toUnsignedInt(b[off]) | ( Byte.toUnsignedInt(b[off+1]) << 8); 139 } 140 141 /* 142 * Sets 16-bit value at specified offset. The bytes are assumed to 143 * be in Intel (little-endian) byte order. 144 */ set16(byte[] b, int off, int value)145 private static void set16(byte[] b, int off, int value) { 146 b[off+0] = (byte)value; 147 b[off+1] = (byte)(value >> 8); 148 } 149 } 150