1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.util.jar; 19 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.util.HashMap; 25 import java.util.Locale; 26 import java.util.zip.ZipEntry; 27 import java.util.zip.ZipInputStream; 28 import libcore.io.Streams; 29 30 /** 31 * The input stream from which the JAR file to be read may be fetched. It is 32 * used like the {@code ZipInputStream}. 33 * 34 * @see ZipInputStream 35 */ 36 // TODO: The semantics provided by this class are really weird. The jar file 37 // spec does not impose any ordering constraints on the entries of a jar file. 38 // In particular, the Manifest and META-INF directory *need not appear first*. This 39 // class will silently skip certificate checks for jar files where the manifest 40 // isn't the first entry. To do this correctly, we need O(input_stream_length) memory. 41 public class JarInputStream extends ZipInputStream { 42 43 private Manifest manifest; 44 45 private boolean verified = false; 46 47 private JarEntry currentJarEntry; 48 49 private JarEntry pendingJarEntry; 50 51 private boolean isMeta; 52 53 private JarVerifier verifier; 54 55 private OutputStream verStream; 56 57 /** 58 * Constructs a new {@code JarInputStream} from an input stream. 59 * 60 * @param stream 61 * the input stream containing the JAR file. 62 * @param verify 63 * if the file should be verified with a {@code JarVerifier}. 64 * @throws IOException 65 * If an error occurs reading entries from the input stream. 66 * @see ZipInputStream#ZipInputStream(InputStream) 67 */ 68 public JarInputStream(InputStream stream, boolean verify) throws IOException { 69 super(stream); 70 71 verifier = null; 72 pendingJarEntry = null; 73 currentJarEntry = null; 74 75 if (getNextJarEntry() == null) { 76 return; 77 } 78 79 if (currentJarEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) { 80 // Fetch the next entry, in the hope that it's the manifest file. 81 closeEntry(); 82 getNextJarEntry(); 83 } 84 85 if (currentJarEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) { 86 final byte[] manifestBytes = Streams.readFullyNoClose(this); 87 manifest = new Manifest(manifestBytes, verify); 88 closeEntry(); 89 90 if (verify) { 91 HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(); 92 metaEntries.put(JarFile.MANIFEST_NAME, manifestBytes); 93 verifier = new JarVerifier("JarInputStream", manifest, metaEntries); 94 } 95 } 96 97 // There was no manifest available, so we should return the current 98 // entry the next time getNextEntry is called. 99 pendingJarEntry = currentJarEntry; 100 currentJarEntry = null; 101 102 // If the manifest isn't the first entry, we will not have enough 103 // information to perform verification on entries that precede it. 104 // 105 // TODO: Should we throw if verify == true in this case ? 106 // TODO: We need all meta entries to be placed before the manifest 107 // as well. 108 } 109 110 /** 111 * Constructs a new {@code JarInputStream} from an input stream. 112 * 113 * @param stream 114 * the input stream containing the JAR file. 115 * @throws IOException 116 * If an error occurs reading entries from the input stream. 117 * @see ZipInputStream#ZipInputStream(InputStream) 118 */ 119 public JarInputStream(InputStream stream) throws IOException { 120 this(stream, true); 121 } 122 123 /** 124 * Returns the {@code Manifest} object associated with this {@code 125 * JarInputStream} or {@code null} if no manifest entry exists. 126 * 127 * @return the MANIFEST specifying the contents of the JAR file. 128 */ 129 public Manifest getManifest() { 130 return manifest; 131 } 132 133 /** 134 * Returns the next {@code JarEntry} contained in this stream or {@code 135 * null} if no more entries are present. 136 * 137 * @return the next JAR entry. 138 * @throws IOException 139 * if an error occurs while reading the entry. 140 */ 141 public JarEntry getNextJarEntry() throws IOException { 142 return (JarEntry) getNextEntry(); 143 } 144 145 /** 146 * Reads up to {@code byteCount} bytes of decompressed data and stores it in 147 * {@code buffer} starting at {@code byteOffset}. Returns the number of uncompressed bytes read. 148 * 149 * @throws IOException 150 * if an IOException occurs. 151 */ 152 @Override 153 public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { 154 if (currentJarEntry == null) { 155 return -1; 156 } 157 158 int r = super.read(buffer, byteOffset, byteCount); 159 // verifier can be null if we've been asked not to verify or if 160 // the manifest wasn't found. 161 // 162 // verStream will be null if we're reading the manifest or if we have 163 // no signatures or if the digest for this entry isn't present in the 164 // manifest. 165 if (verifier != null && verStream != null && !verified) { 166 if (r == -1) { 167 // We've hit the end of this stream for the first time, so attempt 168 // a verification. 169 verified = true; 170 if (isMeta) { 171 verifier.addMetaEntry(currentJarEntry.getName(), 172 ((ByteArrayOutputStream) verStream).toByteArray()); 173 try { 174 verifier.readCertificates(); 175 } catch (SecurityException e) { 176 verifier = null; 177 throw e; 178 } 179 } else { 180 ((JarVerifier.VerifierEntry) verStream).verify(); 181 } 182 } else { 183 verStream.write(buffer, byteOffset, r); 184 } 185 } 186 187 return r; 188 } 189 190 /** 191 * Returns the next {@code ZipEntry} contained in this stream or {@code 192 * null} if no more entries are present. 193 * 194 * @return the next extracted ZIP entry. 195 * @throws IOException 196 * if an error occurs while reading the entry. 197 */ 198 @Override 199 public ZipEntry getNextEntry() throws IOException { 200 // NOTE: This function must update the value of currentJarEntry 201 // as a side effect. 202 203 if (pendingJarEntry != null) { 204 JarEntry pending = pendingJarEntry; 205 pendingJarEntry = null; 206 currentJarEntry = pending; 207 return pending; 208 } 209 210 currentJarEntry = (JarEntry) super.getNextEntry(); 211 if (currentJarEntry == null) { 212 return null; 213 } 214 215 if (verifier != null) { 216 isMeta = currentJarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR); 217 if (isMeta) { 218 final int entrySize = (int) currentJarEntry.getSize(); 219 verStream = new ByteArrayOutputStream(entrySize > 0 ? entrySize : 8192); 220 } else { 221 verStream = verifier.initEntry(currentJarEntry.getName()); 222 } 223 } 224 225 verified = false; 226 return currentJarEntry; 227 } 228 229 @Override 230 public void closeEntry() throws IOException { 231 // NOTE: This was the old behavior. A call to closeEntry() before the 232 // first call to getNextEntry should be a no-op. If we don't return early 233 // here, the super class will close pendingJarEntry for us and reads will 234 // fail. 235 if (pendingJarEntry != null) { 236 return; 237 } 238 239 super.closeEntry(); 240 currentJarEntry = null; 241 } 242 243 @Override 244 protected ZipEntry createZipEntry(String name) { 245 JarEntry entry = new JarEntry(name); 246 if (manifest != null) { 247 entry.setAttributes(manifest.getAttributes(name)); 248 } 249 return entry; 250 } 251 } 252