1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.jar; 27 28 import java.io.FilterInputStream; 29 import java.io.DataOutputStream; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.IOException; 33 import java.util.Map; 34 import java.util.HashMap; 35 import java.util.Iterator; 36 37 /** 38 * The Manifest class is used to maintain Manifest entry names and their 39 * associated Attributes. There are main Manifest Attributes as well as 40 * per-entry Attributes. For information on the Manifest format, please 41 * see the 42 * <a href="../../../../technotes/guides/jar/jar.html"> 43 * Manifest format specification</a>. 44 * 45 * @author David Connelly 46 * @see Attributes 47 * @since 1.2 48 */ 49 public class Manifest implements Cloneable { 50 // manifest main attributes 51 private Attributes attr = new Attributes(); 52 53 // manifest entries 54 private Map<String, Attributes> entries = new HashMap<>(); 55 56 /** 57 * Constructs a new, empty Manifest. 58 */ 59 public Manifest() { 60 } 61 62 /** 63 * Constructs a new Manifest from the specified input stream. 64 * 65 * @param is the input stream containing manifest data 66 * @throws IOException if an I/O error has occurred 67 */ 68 public Manifest(InputStream is) throws IOException { 69 read(is); 70 } 71 72 /** 73 * Constructs a new Manifest that is a copy of the specified Manifest. 74 * 75 * @param man the Manifest to copy 76 */ 77 public Manifest(Manifest man) { 78 attr.putAll(man.getMainAttributes()); 79 entries.putAll(man.getEntries()); 80 } 81 82 /** 83 * Returns the main Attributes for the Manifest. 84 * @return the main Attributes for the Manifest 85 */ 86 public Attributes getMainAttributes() { 87 return attr; 88 } 89 90 /** 91 * Returns a Map of the entries contained in this Manifest. Each entry 92 * is represented by a String name (key) and associated Attributes (value). 93 * The Map permits the {@code null} key, but no entry with a null key is 94 * created by {@link #read}, nor is such an entry written by using {@link 95 * #write}. 96 * 97 * @return a Map of the entries contained in this Manifest 98 */ 99 public Map<String,Attributes> getEntries() { 100 return entries; 101 } 102 103 /** 104 * Returns the Attributes for the specified entry name. 105 * This method is defined as: 106 * <pre> 107 * return (Attributes)getEntries().get(name) 108 * </pre> 109 * Though {@code null} is a valid {@code name}, when 110 * {@code getAttributes(null)} is invoked on a {@code Manifest} 111 * obtained from a jar file, {@code null} will be returned. While jar 112 * files themselves do not allow {@code null}-named attributes, it is 113 * possible to invoke {@link #getEntries} on a {@code Manifest}, and 114 * on that result, invoke {@code put} with a null key and an 115 * arbitrary value. Subsequent invocations of 116 * {@code getAttributes(null)} will return the just-{@code put} 117 * value. 118 * <p> 119 * Note that this method does not return the manifest's main attributes; 120 * see {@link #getMainAttributes}. 121 * 122 * @param name entry name 123 * @return the Attributes for the specified entry name 124 */ 125 public Attributes getAttributes(String name) { 126 return getEntries().get(name); 127 } 128 129 /** 130 * Clears the main Attributes as well as the entries in this Manifest. 131 */ 132 public void clear() { 133 attr.clear(); 134 entries.clear(); 135 } 136 137 /** 138 * Writes the Manifest to the specified OutputStream. 139 * Attributes.Name.MANIFEST_VERSION must be set in 140 * MainAttributes prior to invoking this method. 141 * 142 * @param out the output stream 143 * @exception IOException if an I/O error has occurred 144 * @see #getMainAttributes 145 */ 146 @SuppressWarnings("deprecation") 147 public void write(OutputStream out) throws IOException { 148 DataOutputStream dos = new DataOutputStream(out); 149 // Write out the main attributes for the manifest 150 attr.writeMain(dos); 151 // Now write out the pre-entry attributes 152 for (Map.Entry<String, Attributes> e : entries.entrySet()) { 153 StringBuffer buffer = new StringBuffer("Name: "); 154 String value = e.getKey(); 155 if (value != null) { 156 byte[] vb = value.getBytes("UTF8"); 157 value = new String(vb, 0, 0, vb.length); 158 } 159 buffer.append(value); 160 buffer.append("\r\n"); 161 make72Safe(buffer); 162 dos.writeBytes(buffer.toString()); 163 e.getValue().write(dos); 164 } 165 dos.flush(); 166 } 167 168 /** 169 * Adds line breaks to enforce a maximum 72 bytes per line. 170 */ 171 static void make72Safe(StringBuffer line) { 172 int length = line.length(); 173 if (length > 72) { 174 int index = 70; 175 while (index < length - 2) { 176 line.insert(index, "\r\n "); 177 index += 72; 178 length += 3; 179 } 180 } 181 return; 182 } 183 184 /** 185 * Reads the Manifest from the specified InputStream. The entry 186 * names and attributes read will be merged in with the current 187 * manifest entries. 188 * 189 * @param is the input stream 190 * @exception IOException if an I/O error has occurred 191 */ 192 public void read(InputStream is) throws IOException { 193 // Buffered input stream for reading manifest data 194 FastInputStream fis = new FastInputStream(is); 195 // Line buffer 196 byte[] lbuf = new byte[512]; 197 // Read the main attributes for the manifest 198 attr.read(fis, lbuf); 199 // Total number of entries, attributes read 200 int ecount = 0, acount = 0; 201 // Average size of entry attributes 202 int asize = 2; 203 // Now parse the manifest entries 204 int len; 205 String name = null; 206 boolean skipEmptyLines = true; 207 byte[] lastline = null; 208 209 while ((len = fis.readLine(lbuf)) != -1) { 210 if (lbuf[--len] != '\n') { 211 throw new IOException("manifest line too long"); 212 } 213 if (len > 0 && lbuf[len-1] == '\r') { 214 --len; 215 } 216 if (len == 0 && skipEmptyLines) { 217 continue; 218 } 219 skipEmptyLines = false; 220 221 if (name == null) { 222 name = parseName(lbuf, len); 223 if (name == null) { 224 throw new IOException("invalid manifest format"); 225 } 226 if (fis.peek() == ' ') { 227 // name is wrapped 228 lastline = new byte[len - 6]; 229 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 230 continue; 231 } 232 } else { 233 // continuation line 234 byte[] buf = new byte[lastline.length + len - 1]; 235 System.arraycopy(lastline, 0, buf, 0, lastline.length); 236 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 237 if (fis.peek() == ' ') { 238 // name is wrapped 239 lastline = buf; 240 continue; 241 } 242 name = new String(buf, 0, buf.length, "UTF8"); 243 lastline = null; 244 } 245 Attributes attr = getAttributes(name); 246 if (attr == null) { 247 attr = new Attributes(asize); 248 entries.put(name, attr); 249 } 250 attr.read(fis, lbuf); 251 ecount++; 252 acount += attr.size(); 253 //XXX: Fix for when the average is 0. When it is 0, 254 // you get an Attributes object with an initial 255 // capacity of 0, which tickles a bug in HashMap. 256 asize = Math.max(2, acount / ecount); 257 258 name = null; 259 skipEmptyLines = true; 260 } 261 } 262 263 private String parseName(byte[] lbuf, int len) { 264 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 265 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 266 lbuf[4] == ':' && lbuf[5] == ' ') { 267 try { 268 return new String(lbuf, 6, len - 6, "UTF8"); 269 } 270 catch (Exception e) { 271 } 272 } 273 return null; 274 } 275 276 private int toLower(int c) { 277 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 278 } 279 280 /** 281 * Returns true if the specified Object is also a Manifest and has 282 * the same main Attributes and entries. 283 * 284 * @param o the object to be compared 285 * @return true if the specified Object is also a Manifest and has 286 * the same main Attributes and entries 287 */ 288 public boolean equals(Object o) { 289 if (o instanceof Manifest) { 290 Manifest m = (Manifest)o; 291 return attr.equals(m.getMainAttributes()) && 292 entries.equals(m.getEntries()); 293 } else { 294 return false; 295 } 296 } 297 298 /** 299 * Returns the hash code for this Manifest. 300 */ 301 public int hashCode() { 302 return attr.hashCode() + entries.hashCode(); 303 } 304 305 /** 306 * Returns a shallow copy of this Manifest. The shallow copy is 307 * implemented as follows: 308 * <pre> 309 * public Object clone() { return new Manifest(this); } 310 * </pre> 311 * @return a shallow copy of this Manifest 312 */ 313 public Object clone() { 314 return new Manifest(this); 315 } 316 317 /* 318 * A fast buffered input stream for parsing manifest files. 319 */ 320 static class FastInputStream extends FilterInputStream { 321 private byte buf[]; 322 private int count = 0; 323 private int pos = 0; 324 325 FastInputStream(InputStream in) { 326 this(in, 8192); 327 } 328 329 FastInputStream(InputStream in, int size) { 330 super(in); 331 buf = new byte[size]; 332 } 333 334 public int read() throws IOException { 335 if (pos >= count) { 336 fill(); 337 if (pos >= count) { 338 return -1; 339 } 340 } 341 return Byte.toUnsignedInt(buf[pos++]); 342 } 343 344 public int read(byte[] b, int off, int len) throws IOException { 345 int avail = count - pos; 346 if (avail <= 0) { 347 if (len >= buf.length) { 348 return in.read(b, off, len); 349 } 350 fill(); 351 avail = count - pos; 352 if (avail <= 0) { 353 return -1; 354 } 355 } 356 if (len > avail) { 357 len = avail; 358 } 359 System.arraycopy(buf, pos, b, off, len); 360 pos += len; 361 return len; 362 } 363 364 /* 365 * Reads 'len' bytes from the input stream, or until an end-of-line 366 * is reached. Returns the number of bytes read. 367 */ 368 public int readLine(byte[] b, int off, int len) throws IOException { 369 byte[] tbuf = this.buf; 370 int total = 0; 371 while (total < len) { 372 int avail = count - pos; 373 if (avail <= 0) { 374 fill(); 375 avail = count - pos; 376 if (avail <= 0) { 377 return -1; 378 } 379 } 380 int n = len - total; 381 if (n > avail) { 382 n = avail; 383 } 384 int tpos = pos; 385 int maxpos = tpos + n; 386 while (tpos < maxpos && tbuf[tpos++] != '\n') ; 387 n = tpos - pos; 388 System.arraycopy(tbuf, pos, b, off, n); 389 off += n; 390 total += n; 391 pos = tpos; 392 if (tbuf[tpos-1] == '\n') { 393 break; 394 } 395 } 396 return total; 397 } 398 399 public byte peek() throws IOException { 400 if (pos == count) 401 fill(); 402 if (pos == count) 403 return -1; // nothing left in buffer 404 return buf[pos]; 405 } 406 407 public int readLine(byte[] b) throws IOException { 408 return readLine(b, 0, b.length); 409 } 410 411 public long skip(long n) throws IOException { 412 if (n <= 0) { 413 return 0; 414 } 415 long avail = count - pos; 416 if (avail <= 0) { 417 return in.skip(n); 418 } 419 if (n > avail) { 420 n = avail; 421 } 422 pos += n; 423 return n; 424 } 425 426 public int available() throws IOException { 427 return (count - pos) + in.available(); 428 } 429 430 public void close() throws IOException { 431 if (in != null) { 432 in.close(); 433 in = null; 434 buf = null; 435 } 436 } 437 438 private void fill() throws IOException { 439 count = pos = 0; 440 int n = in.read(buf, 0, buf.length); 441 if (n > 0) { 442 count = n; 443 } 444 } 445 } 446 }