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.ByteArrayInputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.lang.reflect.Field; 25 import java.nio.ByteBuffer; 26 import java.nio.CharBuffer; 27 import java.nio.charset.CharsetEncoder; 28 import java.nio.charset.CoderResult; 29 import java.nio.charset.StandardCharsets; 30 import java.util.HashMap; 31 import java.util.Iterator; 32 import java.util.Map; 33 import libcore.io.Streams; 34 35 /** 36 * The {@code Manifest} class is used to obtain attribute information for a 37 * {@code JarFile} and its entries. 38 */ 39 public class Manifest implements Cloneable { 40 static final int LINE_LENGTH_LIMIT = 72; 41 42 private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' }; 43 44 private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' }; 45 46 private static final Field BAIS_BUF = getByteArrayInputStreamField("buf"); 47 private static final Field BAIS_POS = getByteArrayInputStreamField("pos"); 48 49 private static Field getByteArrayInputStreamField(String name) { 50 try { 51 Field f = ByteArrayInputStream.class.getDeclaredField(name); 52 f.setAccessible(true); 53 return f; 54 } catch (Exception ex) { 55 throw new AssertionError(ex); 56 } 57 } 58 59 private Attributes mainAttributes = new Attributes(); 60 61 private HashMap<String, Attributes> entries = new HashMap<String, Attributes>(); 62 63 static class Chunk { 64 int start; 65 int end; 66 67 Chunk(int start, int end) { 68 this.start = start; 69 this.end = end; 70 } 71 } 72 73 private HashMap<String, Chunk> chunks; 74 75 /** 76 * The end of the main attributes section in the manifest is needed in 77 * verification. 78 */ 79 private int mainEnd; 80 81 /** 82 * Creates a new {@code Manifest} instance. 83 */ 84 public Manifest() { 85 } 86 87 /** 88 * Creates a new {@code Manifest} instance using the attributes obtained 89 * from the input stream. 90 * 91 * @param is 92 * {@code InputStream} to parse for attributes. 93 * @throws IOException 94 * if an IO error occurs while creating this {@code Manifest} 95 */ 96 public Manifest(InputStream is) throws IOException { 97 read(is); 98 } 99 100 /** 101 * Creates a new {@code Manifest} instance. The new instance will have the 102 * same attributes as those found in the parameter {@code Manifest}. 103 * 104 * @param man 105 * {@code Manifest} instance to obtain attributes from. 106 */ 107 @SuppressWarnings("unchecked") 108 public Manifest(Manifest man) { 109 mainAttributes = (Attributes) man.mainAttributes.clone(); 110 entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man 111 .getEntries()).clone(); 112 } 113 114 Manifest(InputStream is, boolean readChunks) throws IOException { 115 if (readChunks) { 116 chunks = new HashMap<String, Chunk>(); 117 } 118 read(is); 119 } 120 121 /** 122 * Resets the both the main attributes as well as the entry attributes 123 * associated with this {@code Manifest}. 124 */ 125 public void clear() { 126 entries.clear(); 127 mainAttributes.clear(); 128 } 129 130 /** 131 * Returns the {@code Attributes} associated with the parameter entry 132 * {@code name}. 133 * 134 * @param name 135 * the name of the entry to obtain {@code Attributes} from. 136 * @return the Attributes for the entry or {@code null} if the entry does 137 * not exist. 138 */ 139 public Attributes getAttributes(String name) { 140 return getEntries().get(name); 141 } 142 143 /** 144 * Returns a map containing the {@code Attributes} for each entry in the 145 * {@code Manifest}. 146 * 147 * @return the map of entry attributes. 148 */ 149 public Map<String, Attributes> getEntries() { 150 return entries; 151 } 152 153 /** 154 * Returns the main {@code Attributes} of the {@code JarFile}. 155 * 156 * @return main {@code Attributes} associated with the source {@code 157 * JarFile}. 158 */ 159 public Attributes getMainAttributes() { 160 return mainAttributes; 161 } 162 163 /** 164 * Creates a copy of this {@code Manifest}. The returned {@code Manifest} 165 * will equal the {@code Manifest} from which it was cloned. 166 * 167 * @return a copy of this instance. 168 */ 169 @Override 170 public Object clone() { 171 return new Manifest(this); 172 } 173 174 /** 175 * Writes this {@code Manifest}'s name/attributes pairs to the given {@code OutputStream}. 176 * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before 177 * calling this method, or no attributes will be written. 178 * 179 * @throws IOException 180 * If an error occurs writing the {@code Manifest}. 181 */ 182 public void write(OutputStream os) throws IOException { 183 write(this, os); 184 } 185 186 /** 187 * Merges name/attribute pairs read from the input stream {@code is} into this manifest. 188 * 189 * @param is 190 * The {@code InputStream} to read from. 191 * @throws IOException 192 * If an error occurs reading the manifest. 193 */ 194 public void read(InputStream is) throws IOException { 195 byte[] buf; 196 if (is instanceof ByteArrayInputStream) { 197 buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is); 198 } else { 199 buf = Streams.readFullyNoClose(is); 200 } 201 202 if (buf.length == 0) { 203 return; 204 } 205 206 // a workaround for HARMONY-5662 207 // replace EOF and NUL with another new line 208 // which does not trigger an error 209 byte b = buf[buf.length - 1]; 210 if (b == 0 || b == 26) { 211 buf[buf.length - 1] = '\n'; 212 } 213 214 ManifestReader im = new ManifestReader(buf, mainAttributes); 215 mainEnd = im.getEndOfMainSection(); 216 im.readEntries(entries, chunks); 217 } 218 219 /** 220 * Returns a byte[] containing all the bytes from a ByteArrayInputStream. 221 * Where possible, this returns the actual array rather than a copy. 222 */ 223 private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) { 224 byte[] buffer; 225 synchronized (bais) { 226 byte[] buf; 227 int pos; 228 try { 229 buf = (byte[]) BAIS_BUF.get(bais); 230 pos = BAIS_POS.getInt(bais); 231 } catch (IllegalAccessException iae) { 232 throw new AssertionError(iae); 233 } 234 int available = bais.available(); 235 if (pos == 0 && buf.length == available) { 236 buffer = buf; 237 } else { 238 buffer = new byte[available]; 239 System.arraycopy(buf, pos, buffer, 0, available); 240 } 241 bais.skip(available); 242 } 243 return buffer; 244 } 245 246 /** 247 * Returns the hash code for this instance. 248 * 249 * @return this {@code Manifest}'s hashCode. 250 */ 251 @Override 252 public int hashCode() { 253 return mainAttributes.hashCode() ^ getEntries().hashCode(); 254 } 255 256 /** 257 * Determines if the receiver is equal to the parameter object. Two {@code 258 * Manifest}s are equal if they have identical main attributes as well as 259 * identical entry attributes. 260 * 261 * @param o 262 * the object to compare against. 263 * @return {@code true} if the manifests are equal, {@code false} otherwise 264 */ 265 @Override 266 public boolean equals(Object o) { 267 if (o == null) { 268 return false; 269 } 270 if (o.getClass() != this.getClass()) { 271 return false; 272 } 273 if (!mainAttributes.equals(((Manifest) o).mainAttributes)) { 274 return false; 275 } 276 return getEntries().equals(((Manifest) o).getEntries()); 277 } 278 279 Chunk getChunk(String name) { 280 return chunks.get(name); 281 } 282 283 void removeChunks() { 284 chunks = null; 285 } 286 287 int getMainAttributesEnd() { 288 return mainEnd; 289 } 290 291 /** 292 * Writes out the attribute information of the specified manifest to the 293 * specified {@code OutputStream} 294 * 295 * @param manifest 296 * the manifest to write out. 297 * @param out 298 * The {@code OutputStream} to write to. 299 * @throws IOException 300 * If an error occurs writing the {@code Manifest}. 301 */ 302 static void write(Manifest manifest, OutputStream out) throws IOException { 303 CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); 304 ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT); 305 306 Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION; 307 String version = manifest.mainAttributes.getValue(versionName); 308 if (version == null) { 309 versionName = Attributes.Name.SIGNATURE_VERSION; 310 version = manifest.mainAttributes.getValue(versionName); 311 } 312 if (version != null) { 313 writeEntry(out, versionName, version, encoder, buffer); 314 Iterator<?> entries = manifest.mainAttributes.keySet().iterator(); 315 while (entries.hasNext()) { 316 Attributes.Name name = (Attributes.Name) entries.next(); 317 if (!name.equals(versionName)) { 318 writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer); 319 } 320 } 321 } 322 out.write(LINE_SEPARATOR); 323 Iterator<String> i = manifest.getEntries().keySet().iterator(); 324 while (i.hasNext()) { 325 String key = i.next(); 326 writeEntry(out, Attributes.Name.NAME, key, encoder, buffer); 327 Attributes attributes = manifest.entries.get(key); 328 Iterator<?> entries = attributes.keySet().iterator(); 329 while (entries.hasNext()) { 330 Attributes.Name name = (Attributes.Name) entries.next(); 331 writeEntry(out, name, attributes.getValue(name), encoder, buffer); 332 } 333 out.write(LINE_SEPARATOR); 334 } 335 } 336 337 private static void writeEntry(OutputStream os, Attributes.Name name, 338 String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException { 339 String nameString = name.getName(); 340 os.write(nameString.getBytes(StandardCharsets.US_ASCII)); 341 os.write(VALUE_SEPARATOR); 342 343 encoder.reset(); 344 bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2); 345 346 CharBuffer cBuf = CharBuffer.wrap(value); 347 348 while (true) { 349 CoderResult r = encoder.encode(cBuf, bBuf, true); 350 if (CoderResult.UNDERFLOW == r) { 351 r = encoder.flush(bBuf); 352 } 353 os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position()); 354 os.write(LINE_SEPARATOR); 355 if (CoderResult.UNDERFLOW == r) { 356 break; 357 } 358 os.write(' '); 359 bBuf.clear().limit(LINE_LENGTH_LIMIT - 1); 360 } 361 } 362 } 363