1 /* 2 * Copyright (c) 1997, 2022, 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.ByteArrayOutputStream; 29 import java.io.DataOutputStream; 30 import java.io.IOException; 31 import java.util.Collection; 32 import java.util.HashMap; 33 import java.util.LinkedHashMap; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Set; 37 38 import jdk.internal.misc.CDS; 39 import jdk.internal.vm.annotation.Stable; 40 41 import sun.nio.cs.UTF_8; 42 import sun.util.logging.PlatformLogger; 43 44 /** 45 * The Attributes class maps Manifest attribute names to associated string 46 * values. Valid attribute names are case-insensitive, are restricted to 47 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70 48 * characters in length. There must be a colon and a SPACE after the name; 49 * the combined length will not exceed 72 characters. 50 * Attribute values can contain any characters and 51 * will be UTF8-encoded when written to the output stream. See the 52 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 53 * for more information about valid attribute names and values. 54 * 55 * <p>This map and its views have a predictable iteration order, namely the 56 * order that keys were inserted into the map, as with {@link LinkedHashMap}. 57 * 58 * @spec jar/jar.html JAR File Specification 59 * @author David Connelly 60 * @see Manifest 61 * @since 1.2 62 */ 63 public class Attributes implements Map<Object,Object>, Cloneable { 64 /** 65 * The attribute name-value mappings. 66 */ 67 protected Map<Object,Object> map; 68 69 /** 70 * Constructs a new, empty Attributes object with default size. 71 */ 72 public Attributes() { 73 this(16); 74 } 75 76 /** 77 * Constructs a new, empty Attributes object with the specified 78 * initial size. 79 * 80 * @param size the initial number of attributes 81 */ 82 public Attributes(int size) { 83 map = LinkedHashMap.newLinkedHashMap(size); 84 } 85 86 /** 87 * Constructs a new Attributes object with the same attribute name-value 88 * mappings as in the specified Attributes. 89 * 90 * @param attr the specified Attributes 91 */ 92 public Attributes(Attributes attr) { 93 map = new LinkedHashMap<>(attr); 94 } 95 96 97 /** 98 * Returns the value of the specified attribute name, or null if the 99 * attribute name was not found. 100 * 101 * @param name the attribute name 102 * @return the value of the specified attribute name, or null if 103 * not found. 104 */ 105 public Object get(Object name) { 106 return map.get(name); 107 } 108 109 /** 110 * Returns the value of the specified attribute name, specified as 111 * a string, or null if the attribute was not found. The attribute 112 * name is case-insensitive. 113 * <p> 114 * This method is defined as: 115 * <pre> 116 * return (String)get(new Attributes.Name((String)name)); 117 * </pre> 118 * 119 * @param name the attribute name as a string 120 * @return the String value of the specified attribute name, or null if 121 * not found. 122 * @throws IllegalArgumentException if the attribute name is invalid 123 */ 124 public String getValue(String name) { 125 return (String)get(Name.of(name)); 126 } 127 128 /** 129 * Returns the value of the specified Attributes.Name, or null if the 130 * attribute was not found. 131 * <p> 132 * This method is defined as: 133 * <pre> 134 * return (String)get(name); 135 * </pre> 136 * 137 * @param name the Attributes.Name object 138 * @return the String value of the specified Attribute.Name, or null if 139 * not found. 140 */ 141 public String getValue(Name name) { 142 return (String)get(name); 143 } 144 145 /** 146 * Associates the specified value with the specified attribute name 147 * (key) in this Map. If the Map previously contained a mapping for 148 * the attribute name, the old value is replaced. 149 * 150 * @param name the attribute name 151 * @param value the attribute value 152 * @return the previous value of the attribute, or null if none 153 * @throws ClassCastException if the name is not a Attributes.Name 154 * or the value is not a String 155 */ 156 public Object put(Object name, Object value) { 157 return map.put((Attributes.Name)name, (String)value); 158 } 159 160 /** 161 * Associates the specified value with the specified attribute name, 162 * specified as a String. The attributes name is case-insensitive. 163 * If the Map previously contained a mapping for the attribute name, 164 * the old value is replaced. 165 * <p> 166 * This method is defined as: 167 * <pre> 168 * return (String)put(new Attributes.Name(name), value); 169 * </pre> 170 * 171 * @param name the attribute name as a string 172 * @param value the attribute value 173 * @return the previous value of the attribute, or null if none 174 * @throws IllegalArgumentException if the attribute name is invalid 175 */ 176 public String putValue(String name, String value) { 177 return (String)put(Name.of(name), value); 178 } 179 180 /** 181 * Removes the attribute with the specified name (key) from this Map. 182 * Returns the previous attribute value, or null if none. 183 * 184 * @param name attribute name 185 * @return the previous value of the attribute, or null if none 186 */ 187 public Object remove(Object name) { 188 return map.remove(name); 189 } 190 191 /** 192 * Returns true if this Map maps one or more attribute names (keys) 193 * to the specified value. 194 * 195 * @param value the attribute value 196 * @return true if this Map maps one or more attribute names to 197 * the specified value 198 */ 199 public boolean containsValue(Object value) { 200 return map.containsValue(value); 201 } 202 203 /** 204 * Returns true if this Map contains the specified attribute name (key). 205 * 206 * @param name the attribute name 207 * @return true if this Map contains the specified attribute name 208 */ 209 public boolean containsKey(Object name) { 210 return map.containsKey(name); 211 } 212 213 /** 214 * Copies all of the attribute name-value mappings from the specified 215 * Attributes to this Map. Duplicate mappings will be replaced. 216 * 217 * @param attr the Attributes to be stored in this map 218 * @throws ClassCastException if attr is not an Attributes 219 */ 220 public void putAll(Map<?,?> attr) { 221 // ## javac bug? 222 if (!Attributes.class.isInstance(attr)) 223 throw new ClassCastException(); 224 for (Map.Entry<?,?> me : (attr).entrySet()) 225 put(me.getKey(), me.getValue()); 226 } 227 228 /** 229 * Removes all attributes from this Map. 230 */ 231 public void clear() { 232 map.clear(); 233 } 234 235 /** 236 * Returns the number of attributes in this Map. 237 */ 238 public int size() { 239 return map.size(); 240 } 241 242 /** 243 * Returns true if this Map contains no attributes. 244 */ 245 public boolean isEmpty() { 246 return map.isEmpty(); 247 } 248 249 /** 250 * Returns a Set view of the attribute names (keys) contained in this Map. 251 */ 252 public Set<Object> keySet() { 253 return map.keySet(); 254 } 255 256 /** 257 * Returns a Collection view of the attribute values contained in this Map. 258 */ 259 public Collection<Object> values() { 260 return map.values(); 261 } 262 263 /** 264 * Returns a Collection view of the attribute name-value mappings 265 * contained in this Map. 266 */ 267 public Set<Map.Entry<Object,Object>> entrySet() { 268 return map.entrySet(); 269 } 270 271 /** 272 * Compares the specified object to the underlying 273 * {@linkplain Attributes#map map} for equality. 274 * Returns true if the given object is also a Map 275 * and the two maps represent the same mappings. 276 * 277 * @param o the Object to be compared 278 * @return true if the specified Object is equal to this Map 279 */ 280 public boolean equals(Object o) { 281 return map.equals(o); 282 } 283 284 /** 285 * Returns the hash code value for this Map. 286 */ 287 public int hashCode() { 288 return map.hashCode(); 289 } 290 291 /** 292 * Returns a copy of the Attributes, implemented as follows: 293 * <pre> 294 * public Object clone() { return new Attributes(this); } 295 * </pre> 296 * Since the attribute names and values are themselves immutable, 297 * the Attributes returned can be safely modified without affecting 298 * the original. 299 */ 300 public Object clone() { 301 return new Attributes(this); 302 } 303 304 /* 305 * Writes the current attributes to the specified data output stream. 306 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 307 */ 308 void write(DataOutputStream out) throws IOException { 309 StringBuilder buffer = new StringBuilder(72); 310 for (Entry<Object, Object> e : entrySet()) { 311 buffer.setLength(0); 312 buffer.append(e.getKey().toString()); 313 buffer.append(": "); 314 buffer.append(e.getValue()); 315 Manifest.println72(out, buffer.toString()); 316 } 317 Manifest.println(out); // empty line after individual section 318 } 319 320 /* 321 * Writes the current attributes to the specified data output stream, 322 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION 323 * attributes first. 324 * 325 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 326 */ 327 void writeMain(DataOutputStream out) throws IOException { 328 StringBuilder buffer = new StringBuilder(72); 329 330 // write out the *-Version header first, if it exists 331 String vername = Name.MANIFEST_VERSION.toString(); 332 String version = getValue(vername); 333 if (version == null) { 334 vername = Name.SIGNATURE_VERSION.toString(); 335 version = getValue(vername); 336 } 337 338 if (version != null) { 339 buffer.append(vername); 340 buffer.append(": "); 341 buffer.append(version); 342 out.write(buffer.toString().getBytes(UTF_8.INSTANCE)); 343 Manifest.println(out); 344 } 345 346 // write out all attributes except for the version 347 // we wrote out earlier 348 for (Entry<Object, Object> e : entrySet()) { 349 String name = ((Name) e.getKey()).toString(); 350 if ((version != null) && !(name.equalsIgnoreCase(vername))) { 351 buffer.setLength(0); 352 buffer.append(name); 353 buffer.append(": "); 354 buffer.append(e.getValue()); 355 Manifest.println72(out, buffer.toString()); 356 } 357 } 358 359 Manifest.println(out); // empty line after main attributes section 360 } 361 362 /* 363 * Reads attributes from the specified input stream. 364 */ 365 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException { 366 read(is, lbuf, null, 0); 367 } 368 369 int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException { 370 String name = null, value; 371 ByteArrayOutputStream fullLine = new ByteArrayOutputStream(); 372 373 int len; 374 while ((len = is.readLine(lbuf)) != -1) { 375 boolean lineContinued = false; 376 byte c = lbuf[--len]; 377 lineNumber++; 378 379 if (c != '\n' && c != '\r') { 380 throw new IOException("line too long (" 381 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 382 } 383 if (len > 0 && lbuf[len-1] == '\r') { 384 --len; 385 } 386 if (len == 0) { 387 break; 388 } 389 int i = 0; 390 if (lbuf[0] == ' ') { 391 // continuation of previous line 392 if (name == null) { 393 throw new IOException("misplaced continuation line (" 394 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 395 } 396 lineContinued = true; 397 fullLine.write(lbuf, 1, len - 1); 398 if (is.peek() == ' ') { 399 continue; 400 } 401 value = fullLine.toString(UTF_8.INSTANCE); 402 fullLine.reset(); 403 } else { 404 while (lbuf[i++] != ':') { 405 if (i >= len) { 406 throw new IOException("invalid header field (" 407 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 408 } 409 } 410 if (lbuf[i++] != ' ') { 411 throw new IOException("invalid header field (" 412 + Manifest.getErrorPosition(filename, lineNumber) + ")"); 413 } 414 name = new String(lbuf, 0, i - 2, UTF_8.INSTANCE); 415 if (is.peek() == ' ') { 416 fullLine.reset(); 417 fullLine.write(lbuf, i, len - i); 418 continue; 419 } 420 value = new String(lbuf, i, len - i, UTF_8.INSTANCE); 421 } 422 try { 423 if ((putValue(name, value) != null) && (!lineContinued)) { 424 PlatformLogger.getLogger("java.util.jar").warning( 425 "Duplicate name in Manifest: " + name 426 + ".\n" 427 + "Ensure that the manifest does not " 428 + "have duplicate entries, and\n" 429 + "that blank lines separate " 430 + "individual sections in both your\n" 431 + "manifest and in the META-INF/MANIFEST.MF " 432 + "entry in the jar file."); 433 } 434 } catch (IllegalArgumentException e) { 435 throw new IOException("invalid header field name: " + name 436 + " (" + Manifest.getErrorPosition(filename, lineNumber) + ")"); 437 } 438 } 439 return lineNumber; 440 } 441 442 /** 443 * The Attributes.Name class represents an attribute name stored in 444 * this Map. Valid attribute names are case-insensitive, are restricted 445 * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 446 * 70 characters in length. Attribute values can contain any characters 447 * and will be UTF8-encoded when written to the output stream. See the 448 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 449 * for more information about valid attribute names and values. 450 * 451 * @spec jar/jar.html JAR File Specification 452 */ 453 public static class Name { 454 private final String name; 455 private final int hashCode; 456 457 /** 458 * Avoid allocation for common Names 459 */ 460 private static @Stable Map<String, Name> KNOWN_NAMES; 461 462 static final Name of(String name) { 463 Name n = KNOWN_NAMES.get(name); 464 if (n != null) { 465 return n; 466 } 467 return new Name(name); 468 } 469 470 /** 471 * Constructs a new attribute name using the given string name. 472 * 473 * @param name the attribute string name 474 * @throws IllegalArgumentException if the attribute name was 475 * invalid 476 * @throws NullPointerException if the attribute name was null 477 */ 478 public Name(String name) { 479 this.hashCode = hash(name); 480 this.name = name.intern(); 481 } 482 483 // Checks the string is valid 484 private final int hash(String name) { 485 Objects.requireNonNull(name, "name"); 486 int len = name.length(); 487 if (len > 70 || len == 0) { 488 throw new IllegalArgumentException(name); 489 } 490 // Calculate hash code case insensitively 491 int h = 0; 492 for (int i = 0; i < len; i++) { 493 char c = name.charAt(i); 494 if (c >= 'a' && c <= 'z') { 495 // hashcode must be identical for upper and lower case 496 h = h * 31 + (c - 0x20); 497 } else if ((c >= 'A' && c <= 'Z' || 498 c >= '0' && c <= '9' || 499 c == '_' || c == '-')) { 500 h = h * 31 + c; 501 } else { 502 throw new IllegalArgumentException(name); 503 } 504 } 505 return h; 506 } 507 508 /** 509 * Compares this attribute name to another for equality. 510 * @param o the object to compare 511 * @return true if this attribute name is equal to the 512 * specified attribute object 513 */ 514 public boolean equals(Object o) { 515 if (this == o) { 516 return true; 517 } 518 return o instanceof Name other 519 && other.name.equalsIgnoreCase(name); 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; 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; 552 553 /** 554 * {@code Name} object for {@code Content-Type} 555 * manifest attribute. 556 */ 557 public static final 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 665 666 private static void addName(Map<String, Name> names, Name name) { 667 names.put(name.name, name); 668 } 669 670 static { 671 672 CDS.initializeFromArchive(Attributes.Name.class); 673 674 if (KNOWN_NAMES == null) { 675 MANIFEST_VERSION = new Name("Manifest-Version"); 676 SIGNATURE_VERSION = new Name("Signature-Version"); 677 CONTENT_TYPE = new Name("Content-Type"); 678 CLASS_PATH = new Name("Class-Path"); 679 MAIN_CLASS = new Name("Main-Class"); 680 SEALED = new Name("Sealed"); 681 EXTENSION_LIST = new Name("Extension-List"); 682 EXTENSION_NAME = new Name("Extension-Name"); 683 EXTENSION_INSTALLATION = new Name("Extension-Installation"); 684 IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 685 IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 686 IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 687 IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 688 IMPLEMENTATION_URL = new Name("Implementation-URL"); 689 SPECIFICATION_TITLE = new Name("Specification-Title"); 690 SPECIFICATION_VERSION = new Name("Specification-Version"); 691 SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 692 MULTI_RELEASE = new Name("Multi-Release"); 693 694 var names = new HashMap<String, Name>(64); 695 addName(names, MANIFEST_VERSION); 696 addName(names, SIGNATURE_VERSION); 697 addName(names, CONTENT_TYPE); 698 addName(names, CLASS_PATH); 699 addName(names, MAIN_CLASS); 700 addName(names, SEALED); 701 addName(names, EXTENSION_LIST); 702 addName(names, EXTENSION_NAME); 703 addName(names, EXTENSION_INSTALLATION); 704 addName(names, IMPLEMENTATION_TITLE); 705 addName(names, IMPLEMENTATION_VERSION); 706 addName(names, IMPLEMENTATION_VENDOR); 707 addName(names, IMPLEMENTATION_VENDOR_ID); 708 addName(names, IMPLEMENTATION_URL); 709 addName(names, SPECIFICATION_TITLE); 710 addName(names, SPECIFICATION_VERSION); 711 addName(names, SPECIFICATION_VENDOR); 712 addName(names, MULTI_RELEASE); 713 714 // Common attributes used in MANIFEST.MF et.al; adding these has a 715 // small footprint cost, but is likely to be quickly paid for by 716 // reducing allocation when reading and parsing typical manifests 717 718 // JDK specific attributes 719 addName(names, new Name("Add-Exports")); 720 addName(names, new Name("Add-Opens")); 721 addName(names, new Name("Enable-Native-Access")); 722 // LauncherHelper attributes 723 addName(names, new Name("Launcher-Agent-Class")); 724 addName(names, new Name("JavaFX-Application-Class")); 725 // jarsigner attributes 726 addName(names, new Name("Name")); 727 addName(names, new Name("Created-By")); 728 addName(names, new Name("SHA1-Digest")); 729 addName(names, new Name("SHA-256-Digest")); 730 KNOWN_NAMES = Map.copyOf(names); 731 } else { 732 // Even if KNOWN_NAMES was read from archive, we still need 733 // to initialize the public constants 734 MANIFEST_VERSION = KNOWN_NAMES.get("Manifest-Version"); 735 SIGNATURE_VERSION = KNOWN_NAMES.get("Signature-Version"); 736 CONTENT_TYPE = KNOWN_NAMES.get("Content-Type"); 737 CLASS_PATH = KNOWN_NAMES.get("Class-Path"); 738 MAIN_CLASS = KNOWN_NAMES.get("Main-Class"); 739 SEALED = KNOWN_NAMES.get("Sealed"); 740 EXTENSION_LIST = KNOWN_NAMES.get("Extension-List"); 741 EXTENSION_NAME = KNOWN_NAMES.get("Extension-Name"); 742 EXTENSION_INSTALLATION = KNOWN_NAMES.get("Extension-Installation"); 743 IMPLEMENTATION_TITLE = KNOWN_NAMES.get("Implementation-Title"); 744 IMPLEMENTATION_VERSION = KNOWN_NAMES.get("Implementation-Version"); 745 IMPLEMENTATION_VENDOR = KNOWN_NAMES.get("Implementation-Vendor"); 746 IMPLEMENTATION_VENDOR_ID = KNOWN_NAMES.get("Implementation-Vendor-Id"); 747 IMPLEMENTATION_URL = KNOWN_NAMES.get("Implementation-URL"); 748 SPECIFICATION_TITLE = KNOWN_NAMES.get("Specification-Title"); 749 SPECIFICATION_VERSION = KNOWN_NAMES.get("Specification-Version"); 750 SPECIFICATION_VENDOR = KNOWN_NAMES.get("Specification-Vendor"); 751 MULTI_RELEASE = KNOWN_NAMES.get("Multi-Release"); 752 } 753 } 754 } 755 }