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.File; 30 import java.io.IOException; 31 import java.security.AccessController; 32 import java.security.PrivilegedAction; 33 import java.security.Security; 34 import java.util.Collection; 35 import java.util.HashMap; 36 import java.util.LinkedHashMap; 37 import java.util.Map; 38 import java.util.Objects; 39 import java.util.Set; 40 41 import sun.security.util.SecurityProperties; 42 43 import sun.util.logging.PlatformLogger; 44 45 /** 46 * The Attributes class maps Manifest attribute names to associated string 47 * values. Valid attribute names are case-insensitive, are restricted to 48 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70 49 * characters in length. There must be a colon and a SPACE after the name; 50 * the combined length will not exceed 72 characters. 51 * Attribute values can contain any characters and 52 * will be UTF8-encoded when written to the output stream. See the 53 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 54 * for more information about valid attribute names and values. 55 * 56 * <p>This map and its views have a predictable iteration order, namely the 57 * order that keys were inserted into the map, as with {@link LinkedHashMap}. 58 * 59 * @author David Connelly 60 * @see Manifest 61 * @since 1.2 62 */ 63 public class Attributes implements Map<Object,Object>, Cloneable { 64 /** 65 * The attribute name-value mappings. 66 */ 67 protected Map<Object,Object> map; 68 69 private static final boolean jarPathInExceptionText = 70 SecurityProperties.includedInExceptions("jarPath"); 71 72 /** 73 * Constructs a new, empty Attributes object with default size. 74 */ 75 public Attributes() { 76 this(11); 77 } 78 79 /** 80 * Constructs a new, empty Attributes object with the specified 81 * initial size. 82 * 83 * @param size the initial number of attributes 84 */ 85 public Attributes(int size) { 86 map = new LinkedHashMap<>(size); 87 } 88 89 /** 90 * Constructs a new Attributes object with the same attribute name-value 91 * mappings as in the specified Attributes. 92 * 93 * @param attr the specified Attributes 94 */ 95 public Attributes(Attributes attr) { 96 map = new LinkedHashMap<>(attr); 97 } 98 99 100 /** 101 * Returns the value of the specified attribute name, or null if the 102 * attribute name was not found. 103 * 104 * @param name the attribute name 105 * @return the value of the specified attribute name, or null if 106 * not found. 107 */ 108 public Object get(Object name) { 109 return map.get(name); 110 } 111 112 /** 113 * Returns the value of the specified attribute name, specified as 114 * a string, or null if the attribute was not found. The attribute 115 * name is case-insensitive. 116 * <p> 117 * This method is defined as: 118 * <pre> 119 * return (String)get(new Attributes.Name((String)name)); 120 * </pre> 121 * 122 * @param name the attribute name as a string 123 * @return the String value of the specified attribute name, or null if 124 * not found. 125 * @throws IllegalArgumentException if the attribute name is invalid 126 */ 127 public String getValue(String name) { 128 return (String)get(Name.of(name)); 129 } 130 131 /** 132 * Returns the value of the specified Attributes.Name, or null if the 133 * attribute was not found. 134 * <p> 135 * This method is defined as: 136 * <pre> 137 * return (String)get(name); 138 * </pre> 139 * 140 * @param name the Attributes.Name object 141 * @return the String value of the specified Attribute.Name, or null if 142 * not found. 143 */ 144 public String getValue(Name name) { 145 return (String)get(name); 146 } 147 148 /** 149 * Associates the specified value with the specified attribute name 150 * (key) in this Map. If the Map previously contained a mapping for 151 * the attribute name, the old value is replaced. 152 * 153 * @param name the attribute name 154 * @param value the attribute value 155 * @return the previous value of the attribute, or null if none 156 * @exception ClassCastException if the name is not a Attributes.Name 157 * or the value is not a String 158 */ 159 public Object put(Object name, Object value) { 160 return map.put((Attributes.Name)name, (String)value); 161 } 162 163 /** 164 * Associates the specified value with the specified attribute name, 165 * specified as a String. The attributes name is case-insensitive. 166 * If the Map previously contained a mapping for the attribute name, 167 * the old value is replaced. 168 * <p> 169 * This method is defined as: 170 * <pre> 171 * return (String)put(new Attributes.Name(name), value); 172 * </pre> 173 * 174 * @param name the attribute name as a string 175 * @param value the attribute value 176 * @return the previous value of the attribute, or null if none 177 * @exception IllegalArgumentException if the attribute name is invalid 178 */ 179 public String putValue(String name, String value) { 180 return (String)put(Name.of(name), value); 181 } 182 183 /** 184 * Removes the attribute with the specified name (key) from this Map. 185 * Returns the previous attribute value, or null if none. 186 * 187 * @param name attribute name 188 * @return the previous value of the attribute, or null if none 189 */ 190 public Object remove(Object name) { 191 return map.remove(name); 192 } 193 194 /** 195 * Returns true if this Map maps one or more attribute names (keys) 196 * to the specified value. 197 * 198 * @param value the attribute value 199 * @return true if this Map maps one or more attribute names to 200 * the specified value 201 */ 202 public boolean containsValue(Object value) { 203 return map.containsValue(value); 204 } 205 206 /** 207 * Returns true if this Map contains the specified attribute name (key). 208 * 209 * @param name the attribute name 210 * @return true if this Map contains the specified attribute name 211 */ 212 public boolean containsKey(Object name) { 213 return map.containsKey(name); 214 } 215 216 /** 217 * Copies all of the attribute name-value mappings from the specified 218 * Attributes to this Map. Duplicate mappings will be replaced. 219 * 220 * @param attr the Attributes to be stored in this map 221 * @exception ClassCastException if attr is not an Attributes 222 */ 223 public void putAll(Map<?,?> attr) { 224 // ## javac bug? 225 if (!Attributes.class.isInstance(attr)) 226 throw new ClassCastException(); 227 for (Map.Entry<?,?> me : (attr).entrySet()) 228 put(me.getKey(), me.getValue()); 229 } 230 231 /** 232 * Removes all attributes from this Map. 233 */ 234 public void clear() { 235 map.clear(); 236 } 237 238 /** 239 * Returns the number of attributes in this Map. 240 */ 241 public int size() { 242 return map.size(); 243 } 244 245 /** 246 * Returns true if this Map contains no attributes. 247 */ 248 public boolean isEmpty() { 249 return map.isEmpty(); 250 } 251 252 /** 253 * Returns a Set view of the attribute names (keys) contained in this Map. 254 */ 255 public Set<Object> keySet() { 256 return map.keySet(); 257 } 258 259 /** 260 * Returns a Collection view of the attribute values contained in this Map. 261 */ 262 public Collection<Object> values() { 263 return map.values(); 264 } 265 266 /** 267 * Returns a Collection view of the attribute name-value mappings 268 * contained in this Map. 269 */ 270 public Set<Map.Entry<Object,Object>> entrySet() { 271 return map.entrySet(); 272 } 273 274 /** 275 * Compares the specified Attributes object with this Map for equality. 276 * Returns true if the given object is also an instance of Attributes 277 * and the two Attributes objects represent the same mappings. 278 * 279 * @param o the Object to be compared 280 * @return true if the specified Object is equal to this Map 281 */ 282 public boolean equals(Object o) { 283 return map.equals(o); 284 } 285 286 /** 287 * Returns the hash code value for this Map. 288 */ 289 public int hashCode() { 290 return map.hashCode(); 291 } 292 293 /** 294 * Returns a copy of the Attributes, implemented as follows: 295 * <pre> 296 * public Object clone() { return new Attributes(this); } 297 * </pre> 298 * Since the attribute names and values are themselves immutable, 299 * the Attributes returned can be safely modified without affecting 300 * the original. 301 */ 302 public Object clone() { 303 return new Attributes(this); 304 } 305 306 /* 307 * Writes the current attributes to the specified data output stream. 308 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 309 */ 310 @SuppressWarnings("deprecation") 311 void write(DataOutputStream os) throws IOException { 312 for (Entry<Object, Object> e : entrySet()) { 313 StringBuffer buffer = new StringBuffer( 314 ((Name) e.getKey()).toString()); 315 buffer.append(": "); 316 317 String value = (String) e.getValue(); 318 if (value != null) { 319 byte[] vb = value.getBytes("UTF8"); 320 value = new String(vb, 0, 0, vb.length); 321 } 322 buffer.append(value); 323 324 Manifest.make72Safe(buffer); 325 buffer.append("\r\n"); 326 os.writeBytes(buffer.toString()); 327 } 328 os.writeBytes("\r\n"); 329 } 330 331 /* 332 * Writes the current attributes to the specified data output stream, 333 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION 334 * attributes first. 335 * 336 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 337 */ 338 @SuppressWarnings("deprecation") 339 void writeMain(DataOutputStream out) throws IOException 340 { 341 // write out the *-Version header first, if it exists 342 String vername = Name.MANIFEST_VERSION.toString(); 343 String version = getValue(vername); 344 if (version == null) { 345 vername = Name.SIGNATURE_VERSION.toString(); 346 version = getValue(vername); 347 } 348 349 if (version != null) { 350 out.writeBytes(vername+": "+version+"\r\n"); 351 } 352 353 // write out all attributes except for the version 354 // we wrote out earlier 355 for (Entry<Object, Object> e : entrySet()) { 356 String name = ((Name) e.getKey()).toString(); 357 if ((version != null) && !(name.equalsIgnoreCase(vername))) { 358 359 StringBuffer buffer = new StringBuffer(name); 360 buffer.append(": "); 361 362 String value = (String) e.getValue(); 363 if (value != null) { 364 byte[] vb = value.getBytes("UTF8"); 365 value = new String(vb, 0, 0, vb.length); 366 } 367 buffer.append(value); 368 369 Manifest.make72Safe(buffer); 370 buffer.append("\r\n"); 371 out.writeBytes(buffer.toString()); 372 } 373 } 374 out.writeBytes("\r\n"); 375 } 376 377 /* 378 * Reads attributes from the specified input stream. 379 * XXX Need to handle UTF8 values. 380 */ 381 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { 382 read(is, lbuf, null, 0); 383 } 384 385 @SuppressWarnings("deprecation") 386 int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int offset) throws IOException { 387 String name = null, value; 388 byte[] lastline = null; 389 int lineNumber = offset; 390 391 int len; 392 while ((len = is.readLine(lbuf)) != -1) { 393 boolean lineContinued = false; 394 byte c = lbuf[--len]; 395 lineNumber++; 396 397 if (c != '\n' && c != '\r') { 398 throw new IOException("line too long (" 399 + getErrorPosition(filename, lineNumber) + ")"); 400 } 401 if (len > 0 && lbuf[len-1] == '\r') { 402 --len; 403 } 404 if (len == 0) { 405 break; 406 } 407 int i = 0; 408 if (lbuf[0] == ' ') { 409 // continuation of previous line 410 if (name == null) { 411 throw new IOException("misplaced continuation line (" 412 + getErrorPosition(filename, lineNumber) + ")"); 413 } 414 lineContinued = true; 415 byte[] buf = new byte[lastline.length + len - 1]; 416 System.arraycopy(lastline, 0, buf, 0, lastline.length); 417 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 418 if (is.peek() == ' ') { 419 lastline = buf; 420 continue; 421 } 422 value = new String(buf, 0, buf.length, "UTF8"); 423 lastline = null; 424 } else { 425 while (lbuf[i++] != ':') { 426 if (i >= len) { 427 throw new IOException("invalid header field (" 428 + getErrorPosition(filename, lineNumber) + ")"); 429 } 430 } 431 if (lbuf[i++] != ' ') { 432 throw new IOException("invalid header field (" 433 + getErrorPosition(filename, lineNumber) + ")"); 434 } 435 name = new String(lbuf, 0, 0, i - 2); 436 if (is.peek() == ' ') { 437 lastline = new byte[len - i]; 438 System.arraycopy(lbuf, i, lastline, 0, len - i); 439 continue; 440 } 441 value = new String(lbuf, i, len - i, "UTF8"); 442 } 443 try { 444 if ((putValue(name, value) != null) && (!lineContinued)) { 445 PlatformLogger.getLogger("java.util.jar").warning( 446 "Duplicate name in Manifest: " + name 447 + ".\n" 448 + "Ensure that the manifest does not " 449 + "have duplicate entries, and\n" 450 + "that blank lines separate " 451 + "individual sections in both your\n" 452 + "manifest and in the META-INF/MANIFEST.MF " 453 + "entry in the jar file."); 454 } 455 } catch (IllegalArgumentException e) { 456 throw new IOException("invalid header field name: " + name 457 + " (" + getErrorPosition(filename, lineNumber) + ")"); 458 } 459 } 460 return lineNumber; 461 } 462 463 static String getErrorPosition(String filename, final int lineNumber) { 464 if (filename == null || !jarPathInExceptionText) { 465 return "line " + lineNumber; 466 } 467 468 final File file = new File(filename); 469 return AccessController.doPrivileged(new PrivilegedAction<String>() { 470 public String run() { 471 return file.getAbsolutePath() + ":" + lineNumber; 472 } 473 }); 474 } 475 476 /** 477 * The Attributes.Name class represents an attribute name stored in 478 * this Map. Valid attribute names are case-insensitive, are restricted 479 * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 480 * 70 characters in length. Attribute values can contain any characters 481 * and will be UTF8-encoded when written to the output stream. See the 482 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 483 * for more information about valid attribute names and values. 484 */ 485 public static class Name { 486 private final String name; 487 private final int hashCode; 488 489 /** 490 * Avoid allocation for common Names 491 */ 492 private static final Map<String, Name> KNOWN_NAMES; 493 494 static final Name of(String name) { 495 Name n = KNOWN_NAMES.get(name); 496 if (n != null) { 497 return n; 498 } 499 return new Name(name); 500 } 501 502 /** 503 * Constructs a new attribute name using the given string name. 504 * 505 * @param name the attribute string name 506 * @exception IllegalArgumentException if the attribute name was 507 * invalid 508 * @exception NullPointerException if the attribute name was null 509 */ 510 public Name(String name) { 511 this.hashCode = hash(name); 512 this.name = name.intern(); 513 } 514 515 // Checks the string is valid 516 private final int hash(String name) { 517 Objects.requireNonNull(name, "name"); 518 int len = name.length(); 519 if (len > 70 || len == 0) { 520 throw new IllegalArgumentException(name); 521 } 522 // Calculate hash code case insensitively 523 int h = 0; 524 for (int i = 0; i < len; i++) { 525 char c = name.charAt(i); 526 if (c >= 'a' && c <= 'z') { 527 // hashcode must be identical for upper and lower case 528 h = h * 31 + (c - 0x20); 529 } else if ((c >= 'A' && c <= 'Z' || 530 c >= '0' && c <= '9' || 531 c == '_' || c == '-')) { 532 h = h * 31 + c; 533 } else { 534 throw new IllegalArgumentException(name); 535 } 536 } 537 return h; 538 } 539 540 /** 541 * Compares this attribute name to another for equality. 542 * @param o the object to compare 543 * @return true if this attribute name is equal to the 544 * specified attribute object 545 */ 546 public boolean equals(Object o) { 547 if (this == o) { 548 return true; 549 } 550 if (o instanceof Name) { 551 Name other = (Name)o; 552 return other.name.equalsIgnoreCase(name); 553 } else { 554 return false; 555 } 556 } 557 558 /** 559 * Computes the hash value for this attribute name. 560 */ 561 public int hashCode() { 562 return hashCode; 563 } 564 565 /** 566 * Returns the attribute name as a String. 567 */ 568 public String toString() { 569 return name; 570 } 571 572 /** 573 * {@code Name} object for {@code Manifest-Version} 574 * manifest attribute. This attribute indicates the version number 575 * of the manifest standard to which a JAR file's manifest conforms. 576 * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest"> 577 * Manifest and Signature Specification</a> 578 */ 579 public static final Name MANIFEST_VERSION = new Name("Manifest-Version"); 580 581 /** 582 * {@code Name} object for {@code Signature-Version} 583 * manifest attribute used when signing JAR files. 584 * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest"> 585 * Manifest and Signature Specification</a> 586 */ 587 public static final Name SIGNATURE_VERSION = new Name("Signature-Version"); 588 589 /** 590 * {@code Name} object for {@code Content-Type} 591 * manifest attribute. 592 */ 593 public static final Name CONTENT_TYPE = new Name("Content-Type"); 594 595 /** 596 * {@code Name} object for {@code Class-Path} 597 * manifest attribute. 598 * @see <a href="{@docRoot}/../specs/jar/jar.html#classpath"> 599 * JAR file specification</a> 600 */ 601 public static final Name CLASS_PATH = new Name("Class-Path"); 602 603 /** 604 * {@code Name} object for {@code Main-Class} manifest 605 * attribute used for launching applications packaged in JAR files. 606 * The {@code Main-Class} attribute is used in conjunction 607 * with the {@code -jar} command-line option of the 608 * {@code java} application launcher. 609 */ 610 public static final Name MAIN_CLASS = new Name("Main-Class"); 611 612 /** 613 * {@code Name} object for {@code Sealed} manifest attribute 614 * used for sealing. 615 * @see <a href="{@docRoot}/../specs/jar/jar.html#package-sealing"> 616 * Package Sealing</a> 617 */ 618 public static final Name SEALED = new Name("Sealed"); 619 620 /** 621 * {@code Name} object for {@code Extension-List} manifest attribute 622 * used for the extension mechanism that is no longer supported. 623 */ 624 public static final Name EXTENSION_LIST = new Name("Extension-List"); 625 626 /** 627 * {@code Name} object for {@code Extension-Name} manifest attribute. 628 * used for the extension mechanism that is no longer supported. 629 */ 630 public static final Name EXTENSION_NAME = new Name("Extension-Name"); 631 632 /** 633 * {@code Name} object for {@code Extension-Installation} manifest attribute. 634 * 635 * @deprecated Extension mechanism is no longer supported. 636 */ 637 @Deprecated 638 public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); 639 640 /** 641 * {@code Name} object for {@code Implementation-Title} 642 * manifest attribute used for package versioning. 643 */ 644 public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 645 646 /** 647 * {@code Name} object for {@code Implementation-Version} 648 * manifest attribute used for package versioning. 649 */ 650 public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 651 652 /** 653 * {@code Name} object for {@code Implementation-Vendor} 654 * manifest attribute used for package versioning. 655 */ 656 public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 657 658 /** 659 * {@code Name} object for {@code Implementation-Vendor-Id} 660 * manifest attribute. 661 * 662 * @deprecated Extension mechanism is no longer supported. 663 */ 664 @Deprecated 665 public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 666 667 /** 668 * {@code Name} object for {@code Implementation-URL} 669 * manifest attribute. 670 * 671 * @deprecated Extension mechanism is no longer supported. 672 */ 673 @Deprecated 674 public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); 675 676 /** 677 * {@code Name} object for {@code Specification-Title} 678 * manifest attribute used for package versioning. 679 */ 680 public static final Name SPECIFICATION_TITLE = new Name("Specification-Title"); 681 682 /** 683 * {@code Name} object for {@code Specification-Version} 684 * manifest attribute used for package versioning. 685 */ 686 public static final Name SPECIFICATION_VERSION = new Name("Specification-Version"); 687 688 /** 689 * {@code Name} object for {@code Specification-Vendor} 690 * manifest attribute used for package versioning. 691 */ 692 public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 693 694 /** 695 * {@code Name} object for {@code Multi-Release} 696 * manifest attribute that indicates this is a multi-release JAR file. 697 * 698 * @since 9 699 */ 700 public static final Name MULTI_RELEASE = new Name("Multi-Release"); 701 702 private static void addName(Map<String, Name> names, Name name) { 703 names.put(name.name, name); 704 } 705 706 static { 707 var names = new HashMap<String, Name>(64); 708 addName(names, MANIFEST_VERSION); 709 addName(names, SIGNATURE_VERSION); 710 addName(names, CONTENT_TYPE); 711 addName(names, CLASS_PATH); 712 addName(names, MAIN_CLASS); 713 addName(names, SEALED); 714 addName(names, EXTENSION_LIST); 715 addName(names, EXTENSION_NAME); 716 addName(names, IMPLEMENTATION_TITLE); 717 addName(names, IMPLEMENTATION_VERSION); 718 addName(names, IMPLEMENTATION_VENDOR); 719 addName(names, SPECIFICATION_TITLE); 720 addName(names, SPECIFICATION_VERSION); 721 addName(names, SPECIFICATION_VENDOR); 722 addName(names, MULTI_RELEASE); 723 724 // Common attributes used in MANIFEST.MF et.al; adding these has a 725 // small footprint cost, but is likely to be quickly paid for by 726 // reducing allocation when reading and parsing typical manifests 727 addName(names, new Name("Add-Exports")); 728 addName(names, new Name("Add-Opens")); 729 addName(names, new Name("Ant-Version")); 730 addName(names, new Name("Archiver-Version")); 731 addName(names, new Name("Build-Jdk")); 732 addName(names, new Name("Built-By")); 733 addName(names, new Name("Bnd-LastModified")); 734 addName(names, new Name("Bundle-Description")); 735 addName(names, new Name("Bundle-DocURL")); 736 addName(names, new Name("Bundle-License")); 737 addName(names, new Name("Bundle-ManifestVersion")); 738 addName(names, new Name("Bundle-Name")); 739 addName(names, new Name("Bundle-Vendor")); 740 addName(names, new Name("Bundle-Version")); 741 addName(names, new Name("Bundle-SymbolicName")); 742 addName(names, new Name("Created-By")); 743 addName(names, new Name("Export-Package")); 744 addName(names, new Name("Import-Package")); 745 addName(names, new Name("Name")); 746 addName(names, new Name("SHA1-Digest")); 747 addName(names, new Name("X-Compile-Source-JDK")); 748 addName(names, new Name("X-Compile-Target-JDK")); 749 KNOWN_NAMES = names; 750 } 751 } 752 }