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