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 import static java.nio.charset.StandardCharsets.UTF_8; 40 41 /** 42 * The Attributes class maps Manifest attribute names to associated string 43 * values. Valid attribute names are case-insensitive, are restricted to 44 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70 45 * characters in length. There must be a colon and a SPACE after the name; 46 * the combined length will not exceed 72 characters. 47 * Attribute values can contain any characters and 48 * will be UTF8-encoded when written to the output stream. See the 49 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 50 * for more information about valid attribute names and values. 51 * 52 * <p>This map and its views have a predictable iteration order, namely the 53 * order that keys were inserted into the map, as with {@link LinkedHashMap}. 54 * 55 * @author David Connelly 56 * @see Manifest 57 * @since 1.2 58 */ 59 public class Attributes implements Map<Object,Object>, Cloneable { 60 /** 61 * The attribute name-value mappings. 62 */ 63 protected Map<Object,Object> map; 64 65 /** 66 * Constructs a new, empty Attributes object with default size. 67 */ 68 public Attributes() { 69 this(11); 70 } 71 72 /** 73 * Constructs a new, empty Attributes object with the specified 74 * initial size. 75 * 76 * @param size the initial number of attributes 77 */ 78 public Attributes(int size) { 79 map = new LinkedHashMap<>(size); 80 } 81 82 /** 83 * Constructs a new Attributes object with the same attribute name-value 84 * mappings as in the specified Attributes. 85 * 86 * @param attr the specified Attributes 87 */ 88 public Attributes(Attributes attr) { 89 map = new LinkedHashMap<>(attr); 90 } 91 92 93 /** 94 * Returns the value of the specified attribute name, or null if the 95 * attribute name was not found. 96 * 97 * @param name the attribute name 98 * @return the value of the specified attribute name, or null if 99 * not found. 100 */ 101 public Object get(Object name) { 102 return map.get(name); 103 } 104 105 /** 106 * Returns the value of the specified attribute name, specified as 107 * a string, or null if the attribute was not found. The attribute 108 * name is case-insensitive. 109 * <p> 110 * This method is defined as: 111 * <pre> 112 * return (String)get(new Attributes.Name((String)name)); 113 * </pre> 114 * 115 * @param name the attribute name as a string 116 * @return the String value of the specified attribute name, or null if 117 * not found. 118 * @throws IllegalArgumentException if the attribute name is invalid 119 */ 120 public String getValue(String name) { 121 return (String)get(Name.of(name)); 122 } 123 124 /** 125 * Returns the value of the specified Attributes.Name, or null if the 126 * attribute was not found. 127 * <p> 128 * This method is defined as: 129 * <pre> 130 * return (String)get(name); 131 * </pre> 132 * 133 * @param name the Attributes.Name object 134 * @return the String value of the specified Attribute.Name, or null if 135 * not found. 136 */ 137 public String getValue(Name name) { 138 return (String)get(name); 139 } 140 141 /** 142 * Associates the specified value with the specified attribute name 143 * (key) in this Map. If the Map previously contained a mapping for 144 * the attribute name, the old value is replaced. 145 * 146 * @param name the attribute name 147 * @param value the attribute value 148 * @return the previous value of the attribute, or null if none 149 * @exception ClassCastException if the name is not a Attributes.Name 150 * or the value is not a String 151 */ 152 public Object put(Object name, Object value) { 153 return map.put((Attributes.Name)name, (String)value); 154 } 155 156 /** 157 * Associates the specified value with the specified attribute name, 158 * specified as a String. The attributes name is case-insensitive. 159 * If the Map previously contained a mapping for the attribute name, 160 * the old value is replaced. 161 * <p> 162 * This method is defined as: 163 * <pre> 164 * return (String)put(new Attributes.Name(name), value); 165 * </pre> 166 * 167 * @param name the attribute name as a string 168 * @param value the attribute value 169 * @return the previous value of the attribute, or null if none 170 * @exception IllegalArgumentException if the attribute name is invalid 171 */ 172 public String putValue(String name, String value) { 173 return (String)put(Name.of(name), value); 174 } 175 176 /** 177 * Removes the attribute with the specified name (key) from this Map. 178 * Returns the previous attribute value, or null if none. 179 * 180 * @param name attribute name 181 * @return the previous value of the attribute, or null if none 182 */ 183 public Object remove(Object name) { 184 return map.remove(name); 185 } 186 187 /** 188 * Returns true if this Map maps one or more attribute names (keys) 189 * to the specified value. 190 * 191 * @param value the attribute value 192 * @return true if this Map maps one or more attribute names to 193 * the specified value 194 */ 195 public boolean containsValue(Object value) { 196 return map.containsValue(value); 197 } 198 199 /** 200 * Returns true if this Map contains the specified attribute name (key). 201 * 202 * @param name the attribute name 203 * @return true if this Map contains the specified attribute name 204 */ 205 public boolean containsKey(Object name) { 206 return map.containsKey(name); 207 } 208 209 /** 210 * Copies all of the attribute name-value mappings from the specified 211 * Attributes to this Map. Duplicate mappings will be replaced. 212 * 213 * @param attr the Attributes to be stored in this map 214 * @exception ClassCastException if attr is not an Attributes 215 */ 216 public void putAll(Map<?,?> attr) { 217 // ## javac bug? 218 if (!Attributes.class.isInstance(attr)) 219 throw new ClassCastException(); 220 for (Map.Entry<?,?> me : (attr).entrySet()) 221 put(me.getKey(), me.getValue()); 222 } 223 224 /** 225 * Removes all attributes from this Map. 226 */ 227 public void clear() { 228 map.clear(); 229 } 230 231 /** 232 * Returns the number of attributes in this Map. 233 */ 234 public int size() { 235 return map.size(); 236 } 237 238 /** 239 * Returns true if this Map contains no attributes. 240 */ 241 public boolean isEmpty() { 242 return map.isEmpty(); 243 } 244 245 /** 246 * Returns a Set view of the attribute names (keys) contained in this Map. 247 */ 248 public Set<Object> keySet() { 249 return map.keySet(); 250 } 251 252 /** 253 * Returns a Collection view of the attribute values contained in this Map. 254 */ 255 public Collection<Object> values() { 256 return map.values(); 257 } 258 259 /** 260 * Returns a Collection view of the attribute name-value mappings 261 * contained in this Map. 262 */ 263 public Set<Map.Entry<Object,Object>> entrySet() { 264 return map.entrySet(); 265 } 266 267 /** 268 * Compares the specified Attributes object with this Map for equality. 269 * Returns true if the given object is also an instance of Attributes 270 * and the two Attributes objects represent the same mappings. 271 * 272 * @param o the Object to be compared 273 * @return true if the specified Object is equal to this Map 274 */ 275 public boolean equals(Object o) { 276 return map.equals(o); 277 } 278 279 /** 280 * Returns the hash code value for this Map. 281 */ 282 public int hashCode() { 283 return map.hashCode(); 284 } 285 286 /** 287 * Returns a copy of the Attributes, implemented as follows: 288 * <pre> 289 * public Object clone() { return new Attributes(this); } 290 * </pre> 291 * Since the attribute names and values are themselves immutable, 292 * the Attributes returned can be safely modified without affecting 293 * the original. 294 */ 295 public Object clone() { 296 return new Attributes(this); 297 } 298 299 /* 300 * Writes the current attributes to the specified data output stream. 301 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 302 */ 303 void write(DataOutputStream out) throws IOException { 304 StringBuilder buffer = new StringBuilder(72); 305 for (Entry<Object, Object> e : entrySet()) { 306 buffer.setLength(0); 307 buffer.append(e.getKey().toString()); 308 buffer.append(": "); 309 buffer.append(e.getValue()); 310 Manifest.println72(out, buffer.toString()); 311 } 312 Manifest.println(out); // empty line after individual section 313 } 314 315 /* 316 * Writes the current attributes to the specified data output stream, 317 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION 318 * attributes first. 319 * 320 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 321 */ 322 void writeMain(DataOutputStream out) throws IOException { 323 StringBuilder buffer = new StringBuilder(72); 324 325 // write out the *-Version header first, if it exists 326 String vername = Name.MANIFEST_VERSION.toString(); 327 String version = getValue(vername); 328 if (version == null) { 329 vername = Name.SIGNATURE_VERSION.toString(); 330 version = getValue(vername); 331 } 332 333 if (version != null) { 334 buffer.append(vername); 335 buffer.append(": "); 336 buffer.append(version); 337 out.write(buffer.toString().getBytes(UTF_8)); 338 Manifest.println(out); 339 } 340 341 // write out all attributes except for the version 342 // we wrote out earlier 343 for (Entry<Object, Object> e : entrySet()) { 344 String name = ((Name) e.getKey()).toString(); 345 if ((version != null) && !(name.equalsIgnoreCase(vername))) { 346 buffer.setLength(0); 347 buffer.append(name); 348 buffer.append(": "); 349 buffer.append(e.getValue()); 350 Manifest.println72(out, buffer.toString()); 351 } 352 } 353 354 Manifest.println(out); // empty line after main attributes section 355 } 356 357 /* 358 * Reads attributes from the specified input stream. 359 */ 360 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { 361 read(is, lbuf, null, 0); 362 } 363 364 @SuppressWarnings("deprecation") 365 int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException { 366 String name = null, value; 367 byte[] lastline = null; 368 369 int len; 370 while ((len = is.readLine(lbuf)) != -1) { 371 boolean lineContinued = false; 372 byte c = lbuf[--len]; 373 lineNumber++; 374 375 if (c != '\n' && c != '\r') { 376 throw new IOException("line too long (" 377 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 378 } 379 if (len > 0 && lbuf[len-1] == '\r') { 380 --len; 381 } 382 if (len == 0) { 383 break; 384 } 385 int i = 0; 386 if (lbuf[0] == ' ') { 387 // continuation of previous line 388 if (name == null) { 389 throw new IOException("misplaced continuation line (" 390 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 391 } 392 lineContinued = true; 393 byte[] buf = new byte[lastline.length + len - 1]; 394 System.arraycopy(lastline, 0, buf, 0, lastline.length); 395 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 396 if (is.peek() == ' ') { 397 lastline = buf; 398 continue; 399 } 400 value = new String(buf, 0, buf.length, UTF_8); 401 lastline = null; 402 } else { 403 while (lbuf[i++] != ':') { 404 if (i >= len) { 405 throw new IOException("invalid header field (" 406 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 407 } 408 } 409 if (lbuf[i++] != ' ') { 410 throw new IOException("invalid header field (" 411 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 412 } 413 name = new String(lbuf, 0, i - 2, UTF_8); 414 if (is.peek() == ' ') { 415 lastline = new byte[len - i]; 416 System.arraycopy(lbuf, i, lastline, 0, len - i); 417 continue; 418 } 419 value = new String(lbuf, i, len - i, UTF_8); 420 } 421 try { 422 if ((putValue(name, value) != null) && (!lineContinued)) { 423 PlatformLogger.getLogger("java.util.jar").warning( 424 "Duplicate name in Manifest: " + name 425 + ".\n" 426 + "Ensure that the manifest does not " 427 + "have duplicate entries, and\n" 428 + "that blank lines separate " 429 + "individual sections in both your\n" 430 + "manifest and in the META-INF/MANIFEST.MF " 431 + "entry in the jar file."); 432 } 433 } catch (IllegalArgumentException e) { 434 throw new IOException("invalid header field name: " + name 435 + " (" + Manifest.getErrorPosition(filename, lineNumber) + ")"); 436 } 437 } 438 return lineNumber; 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#class-path-attribute"> 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 }