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