1 /* 2 * Copyright (c) 1997, 2019, 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.DataOutputStream; 29 import java.io.FilterInputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 import sun.nio.cs.UTF_8; 37 import sun.security.util.SecurityProperties; 38 39 /** 40 * The Manifest class is used to maintain Manifest entry names and their 41 * associated Attributes. There are main Manifest Attributes as well as 42 * per-entry Attributes. For information on the Manifest format, please 43 * see the 44 * <a href="{@docRoot}/../specs/jar/jar.html"> 45 * Manifest format specification</a>. 46 * 47 * @author David Connelly 48 * @see Attributes 49 * @since 1.2 50 */ 51 public class Manifest implements Cloneable { 52 53 // manifest main attributes 54 private final Attributes attr = new Attributes(); 55 56 // manifest entries 57 private final Map<String, Attributes> entries = new HashMap<>(); 58 59 // associated JarVerifier, not null when called by JarFile::getManifest. 60 private final JarVerifier jv; 61 62 /** 63 * Constructs a new, empty Manifest. 64 */ 65 public Manifest() { 66 jv = null; 67 } 68 69 /** 70 * Constructs a new Manifest from the specified input stream. 71 * 72 * @param is the input stream containing manifest data 73 * @throws IOException if an I/O error has occurred 74 */ 75 public Manifest(InputStream is) throws IOException { 76 this(null, is, null); 77 } 78 79 /** 80 * Constructs a new Manifest from the specified input stream. 81 * 82 * @param is the input stream containing manifest data 83 * @param jarFilename the name of the corresponding jar archive if available 84 * @throws IOException if an I/O error has occurred 85 */ 86 Manifest(InputStream is, String jarFilename) throws IOException { 87 this(null, is, jarFilename); 88 } 89 90 /** 91 * Constructs a new Manifest from the specified input stream 92 * and associates it with a JarVerifier. 93 * 94 * @param jv the JarVerifier to use 95 * @param is the input stream containing manifest data 96 * @param jarFilename the name of the corresponding jar archive if available 97 * @throws IOException if an I/O error has occurred 98 */ 99 Manifest(JarVerifier jv, InputStream is, String jarFilename) throws IOException { 100 read(is, jarFilename); 101 this.jv = jv; 102 } 103 104 /** 105 * Constructs a new Manifest that is a copy of the specified Manifest. 106 * 107 * @param man the Manifest to copy 108 */ 109 public Manifest(Manifest man) { 110 attr.putAll(man.getMainAttributes()); 111 entries.putAll(man.getEntries()); 112 jv = man.jv; 113 } 114 115 /** 116 * Returns the main Attributes for the Manifest. 117 * @return the main Attributes for the Manifest 118 */ 119 public Attributes getMainAttributes() { 120 return attr; 121 } 122 123 /** 124 * Returns a Map of the entries contained in this Manifest. Each entry 125 * is represented by a String name (key) and associated Attributes (value). 126 * The Map permits the {@code null} key, but no entry with a null key is 127 * created by {@link #read}, nor is such an entry written by using {@link 128 * #write}. 129 * 130 * @return a Map of the entries contained in this Manifest 131 */ 132 public Map<String,Attributes> getEntries() { 133 return entries; 134 } 135 136 /** 137 * Returns the Attributes for the specified entry name. 138 * This method is defined as: 139 * <pre> 140 * return (Attributes)getEntries().get(name) 141 * </pre> 142 * Though {@code null} is a valid {@code name}, when 143 * {@code getAttributes(null)} is invoked on a {@code Manifest} 144 * obtained from a jar file, {@code null} will be returned. While jar 145 * files themselves do not allow {@code null}-named attributes, it is 146 * possible to invoke {@link #getEntries} on a {@code Manifest}, and 147 * on that result, invoke {@code put} with a null key and an 148 * arbitrary value. Subsequent invocations of 149 * {@code getAttributes(null)} will return the just-{@code put} 150 * value. 151 * <p> 152 * Note that this method does not return the manifest's main attributes; 153 * see {@link #getMainAttributes}. 154 * 155 * @param name entry name 156 * @return the Attributes for the specified entry name 157 */ 158 public Attributes getAttributes(String name) { 159 return getEntries().get(name); 160 } 161 162 /** 163 * Returns the Attributes for the specified entry name, if trusted. 164 * 165 * @param name entry name 166 * @return returns the same result as {@link #getAttributes(String)} 167 * @throws SecurityException if the associated jar is signed but this entry 168 * has been modified after signing (i.e. the section in the manifest 169 * does not exist in SF files of all signers). 170 */ 171 Attributes getTrustedAttributes(String name) { 172 // Note: Before the verification of MANIFEST.MF/.SF/.RSA files is done, 173 // jv.isTrustedManifestEntry() isn't able to detect MANIFEST.MF change. 174 // Users of this method should call SharedSecrets.javaUtilJarAccess() 175 // .ensureInitialization() first. 176 Attributes result = getAttributes(name); 177 if (result != null && jv != null && ! jv.isTrustedManifestEntry(name)) { 178 throw new SecurityException("Untrusted manifest entry: " + name); 179 } 180 return result; 181 } 182 183 /** 184 * Clears the main Attributes as well as the entries in this Manifest. 185 */ 186 public void clear() { 187 attr.clear(); 188 entries.clear(); 189 } 190 191 /** 192 * Writes the Manifest to the specified OutputStream. 193 * Attributes.Name.MANIFEST_VERSION must be set in 194 * MainAttributes prior to invoking this method. 195 * 196 * @param out the output stream 197 * @throws IOException if an I/O error has occurred 198 * @see #getMainAttributes 199 */ 200 public void write(OutputStream out) throws IOException { 201 DataOutputStream dos = new DataOutputStream(out); 202 // Write out the main attributes for the manifest 203 attr.writeMain(dos); 204 // Now write out the per-entry attributes 205 StringBuilder buffer = entries.isEmpty() ? null : new StringBuilder(72); 206 for (Map.Entry<String, Attributes> e : entries.entrySet()) { 207 buffer.setLength(0); 208 buffer.append("Name: "); 209 buffer.append(e.getKey()); 210 println72(dos, buffer.toString()); 211 e.getValue().write(dos); 212 } 213 dos.flush(); 214 } 215 216 /** 217 * Adds line breaks to enforce a maximum of 72 bytes per line. 218 * 219 * @deprecation Replaced with {@link #println72}. 220 */ 221 @Deprecated(since = "13") 222 static void make72Safe(StringBuffer line) { 223 int length = line.length(); 224 int index = 72; 225 while (index < length) { 226 line.insert(index, "\r\n "); 227 index += 74; // + line width + line break ("\r\n") 228 length += 3; // + line break ("\r\n") and space 229 } 230 } 231 232 /** 233 * Writes {@code line} to {@code out} with line breaks and continuation 234 * spaces within the limits of 72 bytes of contents per line followed 235 * by a line break. 236 */ 237 static void println72(OutputStream out, String line) throws IOException { 238 if (!line.isEmpty()) { 239 byte[] lineBytes = line.getBytes(UTF_8.INSTANCE); 240 int length = lineBytes.length; 241 // first line can hold one byte more than subsequent lines which 242 // start with a continuation line break space 243 out.write(lineBytes[0]); 244 int pos = 1; 245 while (length - pos > 71) { 246 out.write(lineBytes, pos, 71); 247 pos += 71; 248 println(out); 249 out.write(' '); 250 } 251 out.write(lineBytes, pos, length - pos); 252 } 253 println(out); 254 } 255 256 /** 257 * Writes a line break to {@code out}. 258 */ 259 static void println(OutputStream out) throws IOException { 260 out.write('\r'); 261 out.write('\n'); 262 } 263 264 static String getErrorPosition(String filename, final int lineNumber) { 265 if (filename == null || 266 !SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS) { 267 return "line " + lineNumber; 268 } 269 return "manifest of " + filename + ":" + lineNumber; 270 } 271 272 /** 273 * Reads the Manifest from the specified InputStream. The entry 274 * names and attributes read will be merged in with the current 275 * manifest entries. 276 * 277 * @param is the input stream 278 * @throws IOException if an I/O error has occurred 279 */ 280 public void read(InputStream is) throws IOException { 281 read(is, null); 282 } 283 284 private void read(InputStream is, String jarFilename) throws IOException { 285 // Buffered input stream for reading manifest data 286 FastInputStream fis = new FastInputStream(is); 287 // Line buffer 288 byte[] lbuf = new byte[512]; 289 // Read the main attributes for the manifest 290 int lineNumber = attr.read(fis, lbuf, jarFilename, 0); 291 // Total number of entries, attributes read 292 int ecount = 0, acount = 0; 293 // Average size of entry attributes 294 int asize = 2; 295 // Now parse the manifest entries 296 int len; 297 String name = null; 298 boolean skipEmptyLines = true; 299 byte[] lastline = null; 300 301 while ((len = fis.readLine(lbuf)) != -1) { 302 byte c = lbuf[--len]; 303 lineNumber++; 304 305 if (c != '\n' && c != '\r') { 306 throw new IOException("manifest line too long (" 307 + getErrorPosition(jarFilename, lineNumber) + ")"); 308 } 309 if (len > 0 && lbuf[len-1] == '\r') { 310 --len; 311 } 312 if (len == 0 && skipEmptyLines) { 313 continue; 314 } 315 skipEmptyLines = false; 316 317 if (name == null) { 318 name = parseName(lbuf, len); 319 if (name == null) { 320 throw new IOException("invalid manifest format (" 321 + getErrorPosition(jarFilename, lineNumber) + ")"); 322 } 323 if (fis.peek() == ' ') { 324 // name is wrapped 325 lastline = new byte[len - 6]; 326 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 327 continue; 328 } 329 } else { 330 // continuation line 331 byte[] buf = new byte[lastline.length + len - 1]; 332 System.arraycopy(lastline, 0, buf, 0, lastline.length); 333 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 334 if (fis.peek() == ' ') { 335 // name is wrapped 336 lastline = buf; 337 continue; 338 } 339 name = new String(buf, 0, buf.length, UTF_8.INSTANCE); 340 lastline = null; 341 } 342 Attributes attr = getAttributes(name); 343 if (attr == null) { 344 attr = new Attributes(asize); 345 entries.put(name, attr); 346 } 347 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber); 348 ecount++; 349 acount += attr.size(); 350 //XXX: Fix for when the average is 0. When it is 0, 351 // you get an Attributes object with an initial 352 // capacity of 0, which tickles a bug in HashMap. 353 asize = Math.max(2, acount / ecount); 354 355 name = null; 356 skipEmptyLines = true; 357 } 358 } 359 360 private String parseName(byte[] lbuf, int len) { 361 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 362 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 363 lbuf[4] == ':' && lbuf[5] == ' ') { 364 return new String(lbuf, 6, len - 6, UTF_8.INSTANCE); 365 } 366 return null; 367 } 368 369 private int toLower(int c) { 370 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 371 } 372 373 /** 374 * Returns true if the specified Object is also a Manifest and has 375 * the same main Attributes and entries. 376 * 377 * @param o the object to be compared 378 * @return true if the specified Object is also a Manifest and has 379 * the same main Attributes and entries 380 */ 381 public boolean equals(Object o) { 382 if (o instanceof Manifest) { 383 Manifest m = (Manifest)o; 384 return attr.equals(m.getMainAttributes()) && 385 entries.equals(m.getEntries()); 386 } else { 387 return false; 388 } 389 } 390 391 /** 392 * Returns the hash code for this Manifest. 393 */ 394 public int hashCode() { 395 return attr.hashCode() + entries.hashCode(); 396 } 397 398 /** 399 * Returns a shallow copy of this Manifest. The shallow copy is 400 * implemented as follows: 401 * <pre> 402 * public Object clone() { return new Manifest(this); } 403 * </pre> 404 * @return a shallow copy of this Manifest 405 */ 406 public Object clone() { 407 return new Manifest(this); 408 } 409 410 /* 411 * A fast buffered input stream for parsing manifest files. 412 */ 413 static class FastInputStream extends FilterInputStream { 414 private byte buf[]; 415 private int count = 0; 416 private int pos = 0; 417 418 FastInputStream(InputStream in) { 419 this(in, 8192); 420 } 421 422 FastInputStream(InputStream in, int size) { 423 super(in); 424 buf = new byte[size]; 425 } 426 427 public int read() throws IOException { 428 if (pos >= count) { 429 fill(); 430 if (pos >= count) { 431 return -1; 432 } 433 } 434 return Byte.toUnsignedInt(buf[pos++]); 435 } 436 437 public int read(byte[] b, int off, int len) throws IOException { 438 int avail = count - pos; 439 if (avail <= 0) { 440 if (len >= buf.length) { 441 return in.read(b, off, len); 442 } 443 fill(); 444 avail = count - pos; 445 if (avail <= 0) { 446 return -1; 447 } 448 } 449 if (len > avail) { 450 len = avail; 451 } 452 System.arraycopy(buf, pos, b, off, len); 453 pos += len; 454 return len; 455 } 456 457 /* 458 * Reads 'len' bytes from the input stream, or until an end-of-line 459 * is reached. Returns the number of bytes read. 460 */ 461 public int readLine(byte[] b, int off, int len) throws IOException { 462 byte[] tbuf = this.buf; 463 int total = 0; 464 while (total < len) { 465 int avail = count - pos; 466 if (avail <= 0) { 467 fill(); 468 avail = count - pos; 469 if (avail <= 0) { 470 return -1; 471 } 472 } 473 int n = len - total; 474 if (n > avail) { 475 n = avail; 476 } 477 int tpos = pos; 478 int maxpos = tpos + n; 479 byte c = 0; 480 // jar.spec.newline: CRLF | LF | CR (not followed by LF) 481 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r'); 482 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') { 483 tpos++; 484 } 485 n = tpos - pos; 486 System.arraycopy(tbuf, pos, b, off, n); 487 off += n; 488 total += n; 489 pos = tpos; 490 c = tbuf[tpos-1]; 491 if (c == '\n') { 492 break; 493 } 494 if (c == '\r') { 495 if (count == pos) { 496 // try to see if there is a trailing LF 497 fill(); 498 if (pos < count && tbuf[pos] == '\n') { 499 if (total < len) { 500 b[off++] = '\n'; 501 total++; 502 } else { 503 // we should always have big enough lbuf but 504 // just in case we don't, replace the last CR 505 // with LF. 506 b[off - 1] = '\n'; 507 } 508 pos++; 509 } 510 } 511 break; 512 } 513 } 514 return total; 515 } 516 517 public byte peek() throws IOException { 518 if (pos == count) 519 fill(); 520 if (pos == count) 521 return -1; // nothing left in buffer 522 return buf[pos]; 523 } 524 525 public int readLine(byte[] b) throws IOException { 526 return readLine(b, 0, b.length); 527 } 528 529 public long skip(long n) throws IOException { 530 if (n <= 0) { 531 return 0; 532 } 533 long avail = count - pos; 534 if (avail <= 0) { 535 return in.skip(n); 536 } 537 if (n > avail) { 538 n = avail; 539 } 540 pos += n; 541 return n; 542 } 543 544 public int available() throws IOException { 545 return (count - pos) + in.available(); 546 } 547 548 public void close() throws IOException { 549 if (in != null) { 550 in.close(); 551 in = null; 552 buf = null; 553 } 554 } 555 556 private void fill() throws IOException { 557 count = pos = 0; 558 int n = in.read(buf, 0, buf.length); 559 if (n > 0) { 560 count = n; 561 } 562 } 563 } 564 }