1 /* 2 * Copyright (c) 1997, 2018, 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="{@docRoot}/../specs/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 // name of the corresponding jar archive if available. 57 private String jarFilename = null; 58 59 /** 60 * Constructs a new, empty Manifest. 61 */ 62 public Manifest() { 63 } 64 65 /** 66 * Constructs a new Manifest from the specified input stream. 67 * 68 * @param is the input stream containing manifest data 69 * @throws IOException if an I/O error has occurred 70 */ 71 public Manifest(InputStream is) throws IOException { 72 read(is); 73 } 74 75 /** 76 * Constructs a new Manifest from the specified input stream. 77 * 78 * @param is the input stream containing manifest data 79 * @param jarFilename the name of the corresponding jar archive if available 80 * @throws IOException if an I/O error has occured 81 */ 82 Manifest(InputStream is, String jarFilename) throws IOException { 83 this.jarFilename = jarFilename; 84 read(is); 85 } 86 87 /** 88 * Constructs a new Manifest that is a copy of the specified Manifest. 89 * 90 * @param man the Manifest to copy 91 */ 92 public Manifest(Manifest man) { 93 attr.putAll(man.getMainAttributes()); 94 entries.putAll(man.getEntries()); 95 } 96 97 /** 98 * Returns the main Attributes for the Manifest. 99 * @return the main Attributes for the Manifest 100 */ 101 public Attributes getMainAttributes() { 102 return attr; 103 } 104 105 /** 106 * Returns a Map of the entries contained in this Manifest. Each entry 107 * is represented by a String name (key) and associated Attributes (value). 108 * The Map permits the {@code null} key, but no entry with a null key is 109 * created by {@link #read}, nor is such an entry written by using {@link 110 * #write}. 111 * 112 * @return a Map of the entries contained in this Manifest 113 */ 114 public Map<String,Attributes> getEntries() { 115 return entries; 116 } 117 118 /** 119 * Returns the Attributes for the specified entry name. 120 * This method is defined as: 121 * <pre> 122 * return (Attributes)getEntries().get(name) 123 * </pre> 124 * Though {@code null} is a valid {@code name}, when 125 * {@code getAttributes(null)} is invoked on a {@code Manifest} 126 * obtained from a jar file, {@code null} will be returned. While jar 127 * files themselves do not allow {@code null}-named attributes, it is 128 * possible to invoke {@link #getEntries} on a {@code Manifest}, and 129 * on that result, invoke {@code put} with a null key and an 130 * arbitrary value. Subsequent invocations of 131 * {@code getAttributes(null)} will return the just-{@code put} 132 * value. 133 * <p> 134 * Note that this method does not return the manifest's main attributes; 135 * see {@link #getMainAttributes}. 136 * 137 * @param name entry name 138 * @return the Attributes for the specified entry name 139 */ 140 public Attributes getAttributes(String name) { 141 return getEntries().get(name); 142 } 143 144 /** 145 * Clears the main Attributes as well as the entries in this Manifest. 146 */ 147 public void clear() { 148 attr.clear(); 149 entries.clear(); 150 } 151 152 /** 153 * Writes the Manifest to the specified OutputStream. 154 * Attributes.Name.MANIFEST_VERSION must be set in 155 * MainAttributes prior to invoking this method. 156 * 157 * @param out the output stream 158 * @exception IOException if an I/O error has occurred 159 * @see #getMainAttributes 160 */ 161 @SuppressWarnings("deprecation") 162 public void write(OutputStream out) throws IOException { 163 DataOutputStream dos = new DataOutputStream(out); 164 // Write out the main attributes for the manifest 165 attr.writeMain(dos); 166 // Now write out the per-entry attributes 167 for (Map.Entry<String, Attributes> e : entries.entrySet()) { 168 StringBuffer buffer = new StringBuffer("Name: "); 169 String value = e.getKey(); 170 if (value != null) { 171 byte[] vb = value.getBytes("UTF8"); 172 value = new String(vb, 0, 0, vb.length); 173 } 174 buffer.append(value); 175 make72Safe(buffer); 176 buffer.append("\r\n"); 177 dos.writeBytes(buffer.toString()); 178 e.getValue().write(dos); 179 } 180 dos.flush(); 181 } 182 183 /** 184 * Adds line breaks to enforce a maximum 72 bytes per line. 185 */ 186 static void make72Safe(StringBuffer line) { 187 int length = line.length(); 188 int index = 72; 189 while (index < length) { 190 line.insert(index, "\r\n "); 191 index += 74; // + line width + line break ("\r\n") 192 length += 3; // + line break ("\r\n") and space 193 } 194 return; 195 } 196 197 /** 198 * Reads the Manifest from the specified InputStream. The entry 199 * names and attributes read will be merged in with the current 200 * manifest entries. 201 * 202 * @param is the input stream 203 * @exception IOException if an I/O error has occurred 204 */ 205 public void read(InputStream is) throws IOException { 206 // Buffered input stream for reading manifest data 207 FastInputStream fis = new FastInputStream(is); 208 // Line buffer 209 byte[] lbuf = new byte[512]; 210 // Read the main attributes for the manifest 211 int lineNumber = attr.read(fis, lbuf, jarFilename, 0); 212 // Total number of entries, attributes read 213 int ecount = 0, acount = 0; 214 // Average size of entry attributes 215 int asize = 2; 216 // Now parse the manifest entries 217 int len; 218 String name = null; 219 boolean skipEmptyLines = true; 220 byte[] lastline = null; 221 222 while ((len = fis.readLine(lbuf)) != -1) { 223 byte c = lbuf[--len]; 224 lineNumber++; 225 226 if (c != '\n' && c != '\r') { 227 throw new IOException("manifest line too long (" 228 + Attributes.getErrorPosition(jarFilename, lineNumber) + ")"); 229 } 230 if (len > 0 && lbuf[len-1] == '\r') { 231 --len; 232 } 233 if (len == 0 && skipEmptyLines) { 234 continue; 235 } 236 skipEmptyLines = false; 237 238 if (name == null) { 239 name = parseName(lbuf, len); 240 if (name == null) { 241 throw new IOException("invalid manifest format" 242 + Attributes.getErrorPosition(jarFilename, lineNumber) + ")"); 243 } 244 if (fis.peek() == ' ') { 245 // name is wrapped 246 lastline = new byte[len - 6]; 247 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 248 continue; 249 } 250 } else { 251 // continuation line 252 byte[] buf = new byte[lastline.length + len - 1]; 253 System.arraycopy(lastline, 0, buf, 0, lastline.length); 254 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 255 if (fis.peek() == ' ') { 256 // name is wrapped 257 lastline = buf; 258 continue; 259 } 260 name = new String(buf, 0, buf.length, "UTF8"); 261 lastline = null; 262 } 263 Attributes attr = getAttributes(name); 264 if (attr == null) { 265 attr = new Attributes(asize); 266 entries.put(name, attr); 267 } 268 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber); 269 ecount++; 270 acount += attr.size(); 271 //XXX: Fix for when the average is 0. When it is 0, 272 // you get an Attributes object with an initial 273 // capacity of 0, which tickles a bug in HashMap. 274 asize = Math.max(2, acount / ecount); 275 276 name = null; 277 skipEmptyLines = true; 278 } 279 } 280 281 private String parseName(byte[] lbuf, int len) { 282 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 283 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 284 lbuf[4] == ':' && lbuf[5] == ' ') { 285 try { 286 return new String(lbuf, 6, len - 6, "UTF8"); 287 } 288 catch (Exception e) { 289 } 290 } 291 return null; 292 } 293 294 private int toLower(int c) { 295 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 296 } 297 298 /** 299 * Returns true if the specified Object is also a Manifest and has 300 * the same main Attributes and entries. 301 * 302 * @param o the object to be compared 303 * @return true if the specified Object is also a Manifest and has 304 * the same main Attributes and entries 305 */ 306 public boolean equals(Object o) { 307 if (o instanceof Manifest) { 308 Manifest m = (Manifest)o; 309 return attr.equals(m.getMainAttributes()) && 310 entries.equals(m.getEntries()); 311 } else { 312 return false; 313 } 314 } 315 316 /** 317 * Returns the hash code for this Manifest. 318 */ 319 public int hashCode() { 320 return attr.hashCode() + entries.hashCode(); 321 } 322 323 /** 324 * Returns a shallow copy of this Manifest. The shallow copy is 325 * implemented as follows: 326 * <pre> 327 * public Object clone() { return new Manifest(this); } 328 * </pre> 329 * @return a shallow copy of this Manifest 330 */ 331 public Object clone() { 332 return new Manifest(this); 333 } 334 335 /* 336 * A fast buffered input stream for parsing manifest files. 337 */ 338 static class FastInputStream extends FilterInputStream { 339 private byte buf[]; 340 private int count = 0; 341 private int pos = 0; 342 343 FastInputStream(InputStream in) { 344 this(in, 8192); 345 } 346 347 FastInputStream(InputStream in, int size) { 348 super(in); 349 buf = new byte[size]; 350 } 351 352 public int read() throws IOException { 353 if (pos >= count) { 354 fill(); 355 if (pos >= count) { 356 return -1; 357 } 358 } 359 return Byte.toUnsignedInt(buf[pos++]); 360 } 361 362 public int read(byte[] b, int off, int len) throws IOException { 363 int avail = count - pos; 364 if (avail <= 0) { 365 if (len >= buf.length) { 366 return in.read(b, off, len); 367 } 368 fill(); 369 avail = count - pos; 370 if (avail <= 0) { 371 return -1; 372 } 373 } 374 if (len > avail) { 375 len = avail; 376 } 377 System.arraycopy(buf, pos, b, off, len); 378 pos += len; 379 return len; 380 } 381 382 /* 383 * Reads 'len' bytes from the input stream, or until an end-of-line 384 * is reached. Returns the number of bytes read. 385 */ 386 public int readLine(byte[] b, int off, int len) throws IOException { 387 byte[] tbuf = this.buf; 388 int total = 0; 389 while (total < len) { 390 int avail = count - pos; 391 if (avail <= 0) { 392 fill(); 393 avail = count - pos; 394 if (avail <= 0) { 395 return -1; 396 } 397 } 398 int n = len - total; 399 if (n > avail) { 400 n = avail; 401 } 402 int tpos = pos; 403 int maxpos = tpos + n; 404 byte c = 0; 405 // jar.spec.newline: CRLF | LF | CR (not followed by LF) 406 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r'); 407 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') { 408 tpos++; 409 } 410 n = tpos - pos; 411 System.arraycopy(tbuf, pos, b, off, n); 412 off += n; 413 total += n; 414 pos = tpos; 415 c = tbuf[tpos-1]; 416 if (c == '\n') { 417 break; 418 } 419 if (c == '\r') { 420 if (count == pos) { 421 // try to see if there is a trailing LF 422 fill(); 423 if (pos < count && tbuf[pos] == '\n') { 424 if (total < len) { 425 b[off++] = '\n'; 426 total++; 427 } else { 428 // we should always have big enough lbuf but 429 // just in case we don't, replace the last CR 430 // with LF. 431 b[off - 1] = '\n'; 432 } 433 pos++; 434 } 435 } 436 break; 437 } 438 } 439 return total; 440 } 441 442 public byte peek() throws IOException { 443 if (pos == count) 444 fill(); 445 if (pos == count) 446 return -1; // nothing left in buffer 447 return buf[pos]; 448 } 449 450 public int readLine(byte[] b) throws IOException { 451 return readLine(b, 0, b.length); 452 } 453 454 public long skip(long n) throws IOException { 455 if (n <= 0) { 456 return 0; 457 } 458 long avail = count - pos; 459 if (avail <= 0) { 460 return in.skip(n); 461 } 462 if (n > avail) { 463 n = avail; 464 } 465 pos += n; 466 return n; 467 } 468 469 public int available() throws IOException { 470 return (count - pos) + in.available(); 471 } 472 473 public void close() throws IOException { 474 if (in != null) { 475 in.close(); 476 in = null; 477 buf = null; 478 } 479 } 480 481 private void fill() throws IOException { 482 count = pos = 0; 483 int n = in.read(buf, 0, buf.length); 484 if (n > 0) { 485 count = n; 486 } 487 } 488 } 489 }