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 @SuppressWarnings("deprecation") 373 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { 374 String name = null, value; 375 byte[] lastline = null; 376 377 int len; 378 while ((len = is.readLine(lbuf)) != -1) { 379 boolean lineContinued = false; 380 byte c = lbuf[--len]; 381 if (c != '\n' && c != '\r') { 382 throw new IOException("line too long"); 383 } 384 if (len > 0 && lbuf[len-1] == '\r') { 385 --len; 386 } 387 if (len == 0) { 388 break; 389 } 390 int i = 0; 391 if (lbuf[0] == ' ') { 392 // continuation of previous line 393 if (name == null) { 394 throw new IOException("misplaced continuation line"); 395 } 396 lineContinued = true; 397 byte[] buf = new byte[lastline.length + len - 1]; 398 System.arraycopy(lastline, 0, buf, 0, lastline.length); 399 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 400 if (is.peek() == ' ') { 401 lastline = buf; 402 continue; 403 } 404 value = new String(buf, 0, buf.length, "UTF8"); 405 lastline = null; 406 } else { 407 while (lbuf[i++] != ':') { 408 if (i >= len) { 409 throw new IOException("invalid header field"); 410 } 411 } 412 if (lbuf[i++] != ' ') { 413 throw new IOException("invalid header field"); 414 } 415 name = new String(lbuf, 0, 0, i - 2); 416 if (is.peek() == ' ') { 417 lastline = new byte[len - i]; 418 System.arraycopy(lbuf, i, lastline, 0, len - i); 419 continue; 420 } 421 value = new String(lbuf, i, len - i, "UTF8"); 422 } 423 try { 424 if ((putValue(name, value) != null) && (!lineContinued)) { 425 PlatformLogger.getLogger("java.util.jar").warning( 426 "Duplicate name in Manifest: " + name 427 + ".\n" 428 + "Ensure that the manifest does not " 429 + "have duplicate entries, and\n" 430 + "that blank lines separate " 431 + "individual sections in both your\n" 432 + "manifest and in the META-INF/MANIFEST.MF " 433 + "entry in the jar file."); 434 } 435 } catch (IllegalArgumentException e) { 436 throw new IOException("invalid header field name: " + name); 437 } 438 } 439 } 440 441 /** 442 * The Attributes.Name class represents an attribute name stored in 443 * this Map. Valid attribute names are case-insensitive, are restricted 444 * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 445 * 70 characters in length. Attribute values can contain any characters 446 * and will be UTF8-encoded when written to the output stream. See the 447 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 448 * for more information about valid attribute names and values. 449 */ 450 public static class Name { 451 private final String name; 452 private final int hashCode; 453 454 /** 455 * Avoid allocation for common Names 456 */ 457 private static final Map<String, Name> KNOWN_NAMES; 458 459 static final Name of(String name) { 460 Name n = KNOWN_NAMES.get(name); 461 if (n != null) { 462 return n; 463 } 464 return new Name(name); 465 } 466 467 /** 468 * Constructs a new attribute name using the given string name. 469 * 470 * @param name the attribute string name 471 * @exception IllegalArgumentException if the attribute name was 472 * invalid 473 * @exception NullPointerException if the attribute name was null 474 */ 475 public Name(String name) { 476 this.hashCode = hash(name); 477 this.name = name.intern(); 478 } 479 480 // Checks the string is valid 481 private final int hash(String name) { 482 Objects.requireNonNull(name, "name"); 483 int len = name.length(); 484 if (len > 70 || len == 0) { 485 throw new IllegalArgumentException(name); 486 } 487 // Calculate hash code case insensitively 488 int h = 0; 489 for (int i = 0; i < len; i++) { 490 char c = name.charAt(i); 491 if (c >= 'a' && c <= 'z') { 492 // hashcode must be identical for upper and lower case 493 h = h * 31 + (c - 0x20); 494 } else if ((c >= 'A' && c <= 'Z' || 495 c >= '0' && c <= '9' || 496 c == '_' || c == '-')) { 497 h = h * 31 + c; 498 } else { 499 throw new IllegalArgumentException(name); 500 } 501 } 502 return h; 503 } 504 505 /** 506 * Compares this attribute name to another for equality. 507 * @param o the object to compare 508 * @return true if this attribute name is equal to the 509 * specified attribute object 510 */ 511 public boolean equals(Object o) { 512 if (this == o) { 513 return true; 514 } 515 if (o instanceof Name) { 516 Name other = (Name)o; 517 return other.name.equalsIgnoreCase(name); 518 } else { 519 return false; 520 } 521 } 522 523 /** 524 * Computes the hash value for this attribute name. 525 */ 526 public int hashCode() { 527 return hashCode; 528 } 529 530 /** 531 * Returns the attribute name as a String. 532 */ 533 public String toString() { 534 return name; 535 } 536 537 /** 538 * {@code Name} object for {@code Manifest-Version} 539 * manifest attribute. This attribute indicates the version number 540 * of the manifest standard to which a JAR file's manifest conforms. 541 * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest"> 542 * Manifest and Signature Specification</a> 543 */ 544 public static final Name MANIFEST_VERSION = new Name("Manifest-Version"); 545 546 /** 547 * {@code Name} object for {@code Signature-Version} 548 * manifest attribute used when signing JAR files. 549 * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest"> 550 * Manifest and Signature Specification</a> 551 */ 552 public static final Name SIGNATURE_VERSION = new Name("Signature-Version"); 553 554 /** 555 * {@code Name} object for {@code Content-Type} 556 * manifest attribute. 557 */ 558 public static final Name CONTENT_TYPE = new Name("Content-Type"); 559 560 /** 561 * {@code Name} object for {@code Class-Path} 562 * manifest attribute. 563 * @see <a href="{@docRoot}/../specs/jar/jar.html#classpath"> 564 * JAR file specification</a> 565 */ 566 public static final Name CLASS_PATH = new Name("Class-Path"); 567 568 /** 569 * {@code Name} object for {@code Main-Class} manifest 570 * attribute used for launching applications packaged in JAR files. 571 * The {@code Main-Class} attribute is used in conjunction 572 * with the {@code -jar} command-line option of the 573 * {@code java} application launcher. 574 */ 575 public static final Name MAIN_CLASS = new Name("Main-Class"); 576 577 /** 578 * {@code Name} object for {@code Sealed} manifest attribute 579 * used for sealing. 580 * @see <a href="{@docRoot}/../specs/jar/jar.html#package-sealing"> 581 * Package Sealing</a> 582 */ 583 public static final Name SEALED = new Name("Sealed"); 584 585 /** 586 * {@code Name} object for {@code Extension-List} manifest attribute 587 * used for the extension mechanism that is no longer supported. 588 */ 589 public static final Name EXTENSION_LIST = new Name("Extension-List"); 590 591 /** 592 * {@code Name} object for {@code Extension-Name} manifest attribute. 593 * used for the extension mechanism that is no longer supported. 594 */ 595 public static final Name EXTENSION_NAME = new Name("Extension-Name"); 596 597 /** 598 * {@code Name} object for {@code Extension-Installation} manifest attribute. 599 * 600 * @deprecated Extension mechanism is no longer supported. 601 */ 602 @Deprecated 603 public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); 604 605 /** 606 * {@code Name} object for {@code Implementation-Title} 607 * manifest attribute used for package versioning. 608 */ 609 public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 610 611 /** 612 * {@code Name} object for {@code Implementation-Version} 613 * manifest attribute used for package versioning. 614 */ 615 public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 616 617 /** 618 * {@code Name} object for {@code Implementation-Vendor} 619 * manifest attribute used for package versioning. 620 */ 621 public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 622 623 /** 624 * {@code Name} object for {@code Implementation-Vendor-Id} 625 * manifest attribute. 626 * 627 * @deprecated Extension mechanism is no longer supported. 628 */ 629 @Deprecated 630 public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 631 632 /** 633 * {@code Name} object for {@code Implementation-URL} 634 * manifest attribute. 635 * 636 * @deprecated Extension mechanism is no longer supported. 637 */ 638 @Deprecated 639 public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); 640 641 /** 642 * {@code Name} object for {@code Specification-Title} 643 * manifest attribute used for package versioning. 644 */ 645 public static final Name SPECIFICATION_TITLE = new Name("Specification-Title"); 646 647 /** 648 * {@code Name} object for {@code Specification-Version} 649 * manifest attribute used for package versioning. 650 */ 651 public static final Name SPECIFICATION_VERSION = new Name("Specification-Version"); 652 653 /** 654 * {@code Name} object for {@code Specification-Vendor} 655 * manifest attribute used for package versioning. 656 */ 657 public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 658 659 /** 660 * {@code Name} object for {@code Multi-Release} 661 * manifest attribute that indicates this is a multi-release JAR file. 662 * 663 * @since 9 664 */ 665 public static final Name MULTI_RELEASE = new Name("Multi-Release"); 666 667 private static void addName(Map<String, Name> names, Name name) { 668 names.put(name.name, name); 669 } 670 671 static { 672 var names = new HashMap<String, Name>(64); 673 addName(names, MANIFEST_VERSION); 674 addName(names, SIGNATURE_VERSION); 675 addName(names, CONTENT_TYPE); 676 addName(names, CLASS_PATH); 677 addName(names, MAIN_CLASS); 678 addName(names, SEALED); 679 addName(names, EXTENSION_LIST); 680 addName(names, EXTENSION_NAME); 681 addName(names, IMPLEMENTATION_TITLE); 682 addName(names, IMPLEMENTATION_VERSION); 683 addName(names, IMPLEMENTATION_VENDOR); 684 addName(names, SPECIFICATION_TITLE); 685 addName(names, SPECIFICATION_VERSION); 686 addName(names, SPECIFICATION_VENDOR); 687 addName(names, MULTI_RELEASE); 688 689 // Common attributes used in MANIFEST.MF et.al; adding these has a 690 // small footprint cost, but is likely to be quickly paid for by 691 // reducing allocation when reading and parsing typical manifests 692 addName(names, new Name("Add-Exports")); 693 addName(names, new Name("Add-Opens")); 694 addName(names, new Name("Ant-Version")); 695 addName(names, new Name("Archiver-Version")); 696 addName(names, new Name("Build-Jdk")); 697 addName(names, new Name("Built-By")); 698 addName(names, new Name("Bnd-LastModified")); 699 addName(names, new Name("Bundle-Description")); 700 addName(names, new Name("Bundle-DocURL")); 701 addName(names, new Name("Bundle-License")); 702 addName(names, new Name("Bundle-ManifestVersion")); 703 addName(names, new Name("Bundle-Name")); 704 addName(names, new Name("Bundle-Vendor")); 705 addName(names, new Name("Bundle-Version")); 706 addName(names, new Name("Bundle-SymbolicName")); 707 addName(names, new Name("Created-By")); 708 addName(names, new Name("Export-Package")); 709 addName(names, new Name("Import-Package")); 710 addName(names, new Name("Name")); 711 addName(names, new Name("SHA1-Digest")); 712 addName(names, new Name("X-Compile-Source-JDK")); 713 addName(names, new Name("X-Compile-Target-JDK")); 714 KNOWN_NAMES = names; 715 } 716 } 717 }