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