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