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