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