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