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 /** 39 * The Manifest class is used to maintain Manifest entry names and their 40 * associated Attributes. There are main Manifest Attributes as well as 41 * per-entry Attributes. For information on the Manifest format, please 42 * see the 43 * <a href="{@docRoot}/../specs/jar/jar.html"> 44 * Manifest format specification</a>. 45 * 46 * @author David Connelly 47 * @see Attributes 48 * @since 1.2 49 */ 50 public class Manifest implements Cloneable { 51 52 // manifest main attributes 53 private final Attributes attr = new Attributes(); 54 55 // manifest entries 56 private final Map<String, Attributes> entries = new HashMap<>(); 57 58 // associated JarVerifier, not null when called by JarFile::getManifest. 59 private final JarVerifier jv; 60 61 // name of the corresponding jar archive if available. 62 private final String jarFilename; 63 64 /** 65 * Constructs a new, empty Manifest. 66 */ 67 public Manifest() { 68 jarFilename = null; 69 jv = null; 70 } 71 72 /** 73 * Constructs a new Manifest from the specified input stream. 74 * 75 * @param is the input stream containing manifest data 76 * @throws IOException if an I/O error has occurred 77 */ 78 public Manifest(InputStream is) throws IOException { 79 this(null, is, null); 80 } 81 82 /** 83 * Constructs a new Manifest from the specified input stream. 84 * 85 * @param is the input stream containing manifest data 86 * @param jarFilename the name of the corresponding jar archive if available 87 * @throws IOException if an I/O error has occured 88 */ 89 Manifest(InputStream is, String jarFilename) throws IOException { 90 this(null, is, jarFilename); 91 } 92 93 /** 94 * Constructs a new Manifest from the specified input stream 95 * and associates it with a JarVerifier. 96 */ 97 Manifest(JarVerifier jv, InputStream is, String jarFilename) throws IOException { 98 read(is); 99 this.jarFilename = jarFilename; 100 this.jv = jv; 101 } 102 103 /** 104 * Constructs a new Manifest that is a copy of the specified Manifest. 105 * 106 * @param man the Manifest to copy 107 */ 108 public Manifest(Manifest man) { 109 attr.putAll(man.getMainAttributes()); 110 entries.putAll(man.getEntries()); 111 jarFilename = null; 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 * @exception IOException if an I/O error has occurred 198 * @see #getMainAttributes 199 */ 200 @SuppressWarnings("deprecation") 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 for (Map.Entry<String, Attributes> e : entries.entrySet()) { 207 StringBuffer buffer = new StringBuffer("Name: "); 208 String value = e.getKey(); 209 if (value != null) { 210 byte[] vb = value.getBytes("UTF8"); 211 value = new String(vb, 0, 0, vb.length); 212 } 213 buffer.append(value); 214 make72Safe(buffer); 215 buffer.append("\r\n"); 216 dos.writeBytes(buffer.toString()); 217 e.getValue().write(dos); 218 } 219 dos.flush(); 220 } 221 222 /** 223 * Adds line breaks to enforce a maximum 72 bytes per line. 224 */ 225 static void make72Safe(StringBuffer line) { 226 int length = line.length(); 227 int index = 72; 228 while (index < length) { 229 line.insert(index, "\r\n "); 230 index += 74; // + line width + line break ("\r\n") 231 length += 3; // + line break ("\r\n") and space 232 } 233 return; 234 } 235 236 static String getErrorPosition(String filename, final int lineNumber) { 237 if (filename == null || 238 !SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS) { 239 return "line " + lineNumber; 240 } 241 return "manifest of " + filename + ":" + lineNumber; 242 } 243 244 /** 245 * Reads the Manifest from the specified InputStream. The entry 246 * names and attributes read will be merged in with the current 247 * manifest entries. 248 * 249 * @param is the input stream 250 * @exception IOException if an I/O error has occurred 251 */ 252 public void read(InputStream is) throws IOException { 253 // Buffered input stream for reading manifest data 254 FastInputStream fis = new FastInputStream(is); 255 // Line buffer 256 byte[] lbuf = new byte[512]; 257 // Read the main attributes for the manifest 258 int lineNumber = attr.read(fis, lbuf, jarFilename, 0); 259 // Total number of entries, attributes read 260 int ecount = 0, acount = 0; 261 // Average size of entry attributes 262 int asize = 2; 263 // Now parse the manifest entries 264 int len; 265 String name = null; 266 boolean skipEmptyLines = true; 267 byte[] lastline = null; 268 269 while ((len = fis.readLine(lbuf)) != -1) { 270 byte c = lbuf[--len]; 271 lineNumber++; 272 273 if (c != '\n' && c != '\r') { 274 throw new IOException("manifest line too long (" 275 + getErrorPosition(jarFilename, lineNumber) + ")"); 276 } 277 if (len > 0 && lbuf[len-1] == '\r') { 278 --len; 279 } 280 if (len == 0 && skipEmptyLines) { 281 continue; 282 } 283 skipEmptyLines = false; 284 285 if (name == null) { 286 name = parseName(lbuf, len); 287 if (name == null) { 288 throw new IOException("invalid manifest format" 289 + getErrorPosition(jarFilename, lineNumber) + ")"); 290 } 291 if (fis.peek() == ' ') { 292 // name is wrapped 293 lastline = new byte[len - 6]; 294 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 295 continue; 296 } 297 } else { 298 // continuation line 299 byte[] buf = new byte[lastline.length + len - 1]; 300 System.arraycopy(lastline, 0, buf, 0, lastline.length); 301 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 302 if (fis.peek() == ' ') { 303 // name is wrapped 304 lastline = buf; 305 continue; 306 } 307 name = new String(buf, 0, buf.length, "UTF8"); 308 lastline = null; 309 } 310 Attributes attr = getAttributes(name); 311 if (attr == null) { 312 attr = new Attributes(asize); 313 entries.put(name, attr); 314 } 315 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber); 316 ecount++; 317 acount += attr.size(); 318 //XXX: Fix for when the average is 0. When it is 0, 319 // you get an Attributes object with an initial 320 // capacity of 0, which tickles a bug in HashMap. 321 asize = Math.max(2, acount / ecount); 322 323 name = null; 324 skipEmptyLines = true; 325 } 326 } 327 328 private String parseName(byte[] lbuf, int len) { 329 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 330 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 331 lbuf[4] == ':' && lbuf[5] == ' ') { 332 try { 333 return new String(lbuf, 6, len - 6, "UTF8"); 334 } 335 catch (Exception e) { 336 } 337 } 338 return null; 339 } 340 341 private int toLower(int c) { 342 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 343 } 344 345 /** 346 * Returns true if the specified Object is also a Manifest and has 347 * the same main Attributes and entries. 348 * 349 * @param o the object to be compared 350 * @return true if the specified Object is also a Manifest and has 351 * the same main Attributes and entries 352 */ 353 public boolean equals(Object o) { 354 if (o instanceof Manifest) { 355 Manifest m = (Manifest)o; 356 return attr.equals(m.getMainAttributes()) && 357 entries.equals(m.getEntries()); 358 } else { 359 return false; 360 } 361 } 362 363 /** 364 * Returns the hash code for this Manifest. 365 */ 366 public int hashCode() { 367 return attr.hashCode() + entries.hashCode(); 368 } 369 370 /** 371 * Returns a shallow copy of this Manifest. The shallow copy is 372 * implemented as follows: 373 * <pre> 374 * public Object clone() { return new Manifest(this); } 375 * </pre> 376 * @return a shallow copy of this Manifest 377 */ 378 public Object clone() { 379 return new Manifest(this); 380 } 381 382 /* 383 * A fast buffered input stream for parsing manifest files. 384 */ 385 static class FastInputStream extends FilterInputStream { 386 private byte buf[]; 387 private int count = 0; 388 private int pos = 0; 389 390 FastInputStream(InputStream in) { 391 this(in, 8192); 392 } 393 394 FastInputStream(InputStream in, int size) { 395 super(in); 396 buf = new byte[size]; 397 } 398 399 public int read() throws IOException { 400 if (pos >= count) { 401 fill(); 402 if (pos >= count) { 403 return -1; 404 } 405 } 406 return Byte.toUnsignedInt(buf[pos++]); 407 } 408 409 public int read(byte[] b, int off, int len) throws IOException { 410 int avail = count - pos; 411 if (avail <= 0) { 412 if (len >= buf.length) { 413 return in.read(b, off, len); 414 } 415 fill(); 416 avail = count - pos; 417 if (avail <= 0) { 418 return -1; 419 } 420 } 421 if (len > avail) { 422 len = avail; 423 } 424 System.arraycopy(buf, pos, b, off, len); 425 pos += len; 426 return len; 427 } 428 429 /* 430 * Reads 'len' bytes from the input stream, or until an end-of-line 431 * is reached. Returns the number of bytes read. 432 */ 433 public int readLine(byte[] b, int off, int len) throws IOException { 434 byte[] tbuf = this.buf; 435 int total = 0; 436 while (total < len) { 437 int avail = count - pos; 438 if (avail <= 0) { 439 fill(); 440 avail = count - pos; 441 if (avail <= 0) { 442 return -1; 443 } 444 } 445 int n = len - total; 446 if (n > avail) { 447 n = avail; 448 } 449 int tpos = pos; 450 int maxpos = tpos + n; 451 byte c = 0; 452 // jar.spec.newline: CRLF | LF | CR (not followed by LF) 453 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r'); 454 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') { 455 tpos++; 456 } 457 n = tpos - pos; 458 System.arraycopy(tbuf, pos, b, off, n); 459 off += n; 460 total += n; 461 pos = tpos; 462 c = tbuf[tpos-1]; 463 if (c == '\n') { 464 break; 465 } 466 if (c == '\r') { 467 if (count == pos) { 468 // try to see if there is a trailing LF 469 fill(); 470 if (pos < count && tbuf[pos] == '\n') { 471 if (total < len) { 472 b[off++] = '\n'; 473 total++; 474 } else { 475 // we should always have big enough lbuf but 476 // just in case we don't, replace the last CR 477 // with LF. 478 b[off - 1] = '\n'; 479 } 480 pos++; 481 } 482 } 483 break; 484 } 485 } 486 return total; 487 } 488 489 public byte peek() throws IOException { 490 if (pos == count) 491 fill(); 492 if (pos == count) 493 return -1; // nothing left in buffer 494 return buf[pos]; 495 } 496 497 public int readLine(byte[] b) throws IOException { 498 return readLine(b, 0, b.length); 499 } 500 501 public long skip(long n) throws IOException { 502 if (n <= 0) { 503 return 0; 504 } 505 long avail = count - pos; 506 if (avail <= 0) { 507 return in.skip(n); 508 } 509 if (n > avail) { 510 n = avail; 511 } 512 pos += n; 513 return n; 514 } 515 516 public int available() throws IOException { 517 return (count - pos) + in.available(); 518 } 519 520 public void close() throws IOException { 521 if (in != null) { 522 in.close(); 523 in = null; 524 buf = null; 525 } 526 } 527 528 private void fill() throws IOException { 529 count = pos = 0; 530 int n = in.read(buf, 0, buf.length); 531 if (n > 0) { 532 count = n; 533 } 534 } 535 } 536 }