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