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