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