1 /*
   2  * Copyright (c) 1997, 2012, 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.DataInputStream;
  29 import java.io.DataOutputStream;
  30 import java.io.IOException;
  31 import java.util.HashMap;
  32 import java.util.Map;
  33 import java.util.Set;
  34 import java.util.Collection;
  35 import java.util.AbstractSet;
  36 import java.util.Iterator;
  37 import sun.util.logging.PlatformLogger;
  38 import java.util.Comparator;
  39 import sun.misc.ASCIICaseInsensitiveComparator;
  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. Attribute values can contain any characters and
  46  * will be UTF8-encoded when written to the output stream.  See the
  47  * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
  48  * for more information about valid attribute names and values.
  49  *
  50  * @author  David Connelly
  51  * @see     Manifest
  52  * @since   1.2
  53  */
  54 public class Attributes implements Map<Object,Object>, Cloneable {
  55     /**
  56      * The attribute name-value mappings.
  57      */
  58     protected Map<Object,Object> map;
  59 
  60     /**
  61      * Constructs a new, empty Attributes object with default size.
  62      */
  63     public Attributes() {
  64         this(11);
  65     }
  66 
  67     /**
  68      * Constructs a new, empty Attributes object with the specified
  69      * initial size.
  70      *
  71      * @param size the initial number of attributes
  72      */
  73     public Attributes(int size) {
  74         map = new HashMap<>(size);
  75     }
  76 
  77     /**
  78      * Constructs a new Attributes object with the same attribute name-value
  79      * mappings as in the specified Attributes.
  80      *
  81      * @param attr the specified Attributes
  82      */
  83     public Attributes(Attributes attr) {
  84         map = new HashMap<>(attr);
  85     }
  86 
  87 
  88     /**
  89      * Returns the value of the specified attribute name, or null if the
  90      * attribute name was not found.
  91      *
  92      * @param name the attribute name
  93      * @return the value of the specified attribute name, or null if
  94      *         not found.
  95      */
  96     public Object get(Object name) {
  97         return map.get(name);
  98     }
  99 
 100     /**
 101      * Returns the value of the specified attribute name, specified as
 102      * a string, or null if the attribute was not found. The attribute
 103      * name is case-insensitive.
 104      * <p>
 105      * This method is defined as:
 106      * <pre>
 107      *      return (String)get(new Attributes.Name((String)name));
 108      * </pre>
 109      *
 110      * @param name the attribute name as a string
 111      * @return the String value of the specified attribute name, or null if
 112      *         not found.
 113      * @throws IllegalArgumentException if the attribute name is invalid
 114      */
 115     public String getValue(String name) {
 116         return (String)get(new Attributes.Name(name));
 117     }
 118 
 119     /**
 120      * Returns the value of the specified Attributes.Name, or null if the
 121      * attribute was not found.
 122      * <p>
 123      * This method is defined as:
 124      * <pre>
 125      *     return (String)get(name);
 126      * </pre>
 127      *
 128      * @param name the Attributes.Name object
 129      * @return the String value of the specified Attribute.Name, or null if
 130      *         not found.
 131      */
 132     public String getValue(Name name) {
 133         return (String)get(name);
 134     }
 135 
 136     /**
 137      * Associates the specified value with the specified attribute name
 138      * (key) in this Map. If the Map previously contained a mapping for
 139      * the attribute name, the old value is replaced.
 140      *
 141      * @param name the attribute name
 142      * @param value the attribute value
 143      * @return the previous value of the attribute, or null if none
 144      * @exception ClassCastException if the name is not a Attributes.Name
 145      *            or the value is not a String
 146      */
 147     public Object put(Object name, Object value) {
 148         return map.put((Attributes.Name)name, (String)value);
 149     }
 150 
 151     /**
 152      * Associates the specified value with the specified attribute name,
 153      * specified as a String. The attributes name is case-insensitive.
 154      * If the Map previously contained a mapping for the attribute name,
 155      * the old value is replaced.
 156      * <p>
 157      * This method is defined as:
 158      * <pre>
 159      *      return (String)put(new Attributes.Name(name), value);
 160      * </pre>
 161      *
 162      * @param name the attribute name as a string
 163      * @param value the attribute value
 164      * @return the previous value of the attribute, or null if none
 165      * @exception IllegalArgumentException if the attribute name is invalid
 166      */
 167     public String putValue(String name, String value) {
 168         return (String)put(new Name(name), value);
 169     }
 170 
 171     /**
 172      * Removes the attribute with the specified name (key) from this Map.
 173      * Returns the previous attribute value, or null if none.
 174      *
 175      * @param name attribute name
 176      * @return the previous value of the attribute, or null if none
 177      */
 178     public Object remove(Object name) {
 179         return map.remove(name);
 180     }
 181 
 182     /**
 183      * Returns true if this Map maps one or more attribute names (keys)
 184      * to the specified value.
 185      *
 186      * @param value the attribute value
 187      * @return true if this Map maps one or more attribute names to
 188      *         the specified value
 189      */
 190     public boolean containsValue(Object value) {
 191         return map.containsValue(value);
 192     }
 193 
 194     /**
 195      * Returns true if this Map contains the specified attribute name (key).
 196      *
 197      * @param name the attribute name
 198      * @return true if this Map contains the specified attribute name
 199      */
 200     public boolean containsKey(Object name) {
 201         return map.containsKey(name);
 202     }
 203 
 204     /**
 205      * Copies all of the attribute name-value mappings from the specified
 206      * Attributes to this Map. Duplicate mappings will be replaced.
 207      *
 208      * @param attr the Attributes to be stored in this map
 209      * @exception ClassCastException if attr is not an Attributes
 210      */
 211     public void putAll(Map<?,?> attr) {
 212         // ## javac bug?
 213         if (!Attributes.class.isInstance(attr))
 214             throw new ClassCastException();
 215         for (Map.Entry<?,?> me : (attr).entrySet())
 216             put(me.getKey(), me.getValue());
 217     }
 218 
 219     /**
 220      * Removes all attributes from this Map.
 221      */
 222     public void clear() {
 223         map.clear();
 224     }
 225 
 226     /**
 227      * Returns the number of attributes in this Map.
 228      */
 229     public int size() {
 230         return map.size();
 231     }
 232 
 233     /**
 234      * Returns true if this Map contains no attributes.
 235      */
 236     public boolean isEmpty() {
 237         return map.isEmpty();
 238     }
 239 
 240     /**
 241      * Returns a Set view of the attribute names (keys) contained in this Map.
 242      */
 243     public Set<Object> keySet() {
 244         return map.keySet();
 245     }
 246 
 247     /**
 248      * Returns a Collection view of the attribute values contained in this Map.
 249      */
 250     public Collection<Object> values() {
 251         return map.values();
 252     }
 253 
 254     /**
 255      * Returns a Collection view of the attribute name-value mappings
 256      * contained in this Map.
 257      */
 258     public Set<Map.Entry<Object,Object>> entrySet() {
 259         return map.entrySet();
 260     }
 261 
 262     /**
 263      * Compares the specified Attributes object with this Map for equality.
 264      * Returns true if the given object is also an instance of Attributes
 265      * and the two Attributes objects represent the same mappings.
 266      *
 267      * @param o the Object to be compared
 268      * @return true if the specified Object is equal to this Map
 269      */
 270     public boolean equals(Object o) {
 271         return map.equals(o);
 272     }
 273 
 274     /**
 275      * Returns the hash code value for this Map.
 276      */
 277     public int hashCode() {
 278         return map.hashCode();
 279     }
 280 
 281     /**
 282      * Returns a copy of the Attributes, implemented as follows:
 283      * <pre>
 284      *     public Object clone() { return new Attributes(this); }
 285      * </pre>
 286      * Since the attribute names and values are themselves immutable,
 287      * the Attributes returned can be safely modified without affecting
 288      * the original.
 289      */
 290     public Object clone() {
 291         return new Attributes(this);
 292     }
 293 
 294     /*
 295      * Writes the current attributes to the specified data output stream.
 296      * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
 297      */
 298      void write(DataOutputStream os) throws IOException {
 299         Iterator<Map.Entry<Object, Object>> it = entrySet().iterator();
 300         while (it.hasNext()) {
 301             Map.Entry<Object, Object> e = it.next();
 302             StringBuffer buffer = new StringBuffer(
 303                                         ((Name)e.getKey()).toString());
 304             buffer.append(": ");
 305 
 306             String value = (String)e.getValue();
 307             if (value != null) {
 308                 byte[] vb = value.getBytes("UTF8");
 309                 value = new String(vb, 0, 0, vb.length);
 310             }
 311             buffer.append(value);
 312 
 313             buffer.append("\r\n");
 314             Manifest.make72Safe(buffer);
 315             os.writeBytes(buffer.toString());
 316         }
 317         os.writeBytes("\r\n");
 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     {
 329         // write out the *-Version header first, if it exists
 330         String vername = Name.MANIFEST_VERSION.toString();
 331         String version = getValue(vername);
 332         if (version == null) {
 333             vername = Name.SIGNATURE_VERSION.toString();
 334             version = getValue(vername);
 335         }
 336 
 337         if (version != null) {
 338             out.writeBytes(vername+": "+version+"\r\n");
 339         }
 340 
 341         // write out all attributes except for the version
 342         // we wrote out earlier
 343         Iterator<Map.Entry<Object, Object>> it = entrySet().iterator();
 344         while (it.hasNext()) {
 345             Map.Entry<Object, Object> e = it.next();
 346             String name = ((Name)e.getKey()).toString();
 347             if ((version != null) && ! (name.equalsIgnoreCase(vername))) {
 348 
 349                 StringBuffer buffer = new StringBuffer(name);
 350                 buffer.append(": ");
 351 
 352                 String value = (String)e.getValue();
 353                 if (value != null) {
 354                     byte[] vb = value.getBytes("UTF8");
 355                     value = new String(vb, 0, 0, vb.length);
 356                 }
 357                 buffer.append(value);
 358 
 359                 buffer.append("\r\n");
 360                 Manifest.make72Safe(buffer);
 361                 out.writeBytes(buffer.toString());
 362             }
 363         }
 364         out.writeBytes("\r\n");
 365     }
 366 
 367     /*
 368      * Reads attributes from the specified input stream.
 369      * XXX Need to handle UTF8 values.
 370      */
 371     void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
 372         String name = null, value = null;
 373         byte[] lastline = null;
 374 
 375         int len;
 376         while ((len = is.readLine(lbuf)) != -1) {
 377             boolean lineContinued = false;
 378             if (lbuf[--len] != '\n') {
 379                 throw new IOException("line too long");
 380             }
 381             if (len > 0 && lbuf[len-1] == '\r') {
 382                 --len;
 383             }
 384             if (len == 0) {
 385                 break;
 386             }
 387             int i = 0;
 388             if (lbuf[0] == ' ') {
 389                 // continuation of previous line
 390                 if (name == null) {
 391                     throw new IOException("misplaced continuation line");
 392                 }
 393                 lineContinued = true;
 394                 byte[] buf = new byte[lastline.length + len - 1];
 395                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
 396                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
 397                 if (is.peek() == ' ') {
 398                     lastline = buf;
 399                     continue;
 400                 }
 401                 value = new String(buf, 0, buf.length, "UTF8");
 402                 lastline = null;
 403             } else {
 404                 while (lbuf[i++] != ':') {
 405                     if (i >= len) {
 406                         throw new IOException("invalid header field");
 407                     }
 408                 }
 409                 if (lbuf[i++] != ' ') {
 410                     throw new IOException("invalid header field");
 411                 }
 412                 name = new String(lbuf, 0, 0, i - 2);
 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, "UTF8");
 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             }
 435         }
 436     }
 437 
 438     /**
 439      * The Attributes.Name class represents an attribute name stored in
 440      * this Map. Valid attribute names are case-insensitive, are restricted
 441      * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
 442      * 70 characters in length. Attribute values can contain any characters
 443      * and will be UTF8-encoded when written to the output stream.  See the
 444      * <a href="../../../../technotes/guides/jar/jar.html">JAR File Specification</a>
 445      * for more information about valid attribute names and values.
 446      */
 447     public static class Name {
 448         private String name;
 449         private int hashCode = -1;
 450 
 451         /**
 452          * Constructs a new attribute name using the given string name.
 453          *
 454          * @param name the attribute string name
 455          * @exception IllegalArgumentException if the attribute name was
 456          *            invalid
 457          * @exception NullPointerException if the attribute name was null
 458          */
 459         public Name(String name) {
 460             if (name == null) {
 461                 throw new NullPointerException("name");
 462             }
 463             if (!isValid(name)) {
 464                 throw new IllegalArgumentException(name);
 465             }
 466             this.name = name.intern();
 467         }
 468 
 469         private static boolean isValid(String name) {
 470             int len = name.length();
 471             if (len > 70 || len == 0) {
 472                 return false;
 473             }
 474             for (int i = 0; i < len; i++) {
 475                 if (!isValid(name.charAt(i))) {
 476                     return false;
 477                 }
 478             }
 479             return true;
 480         }
 481 
 482         private static boolean isValid(char c) {
 483             return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
 484         }
 485 
 486         private static boolean isAlpha(char c) {
 487             return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
 488         }
 489 
 490         private static boolean isDigit(char c) {
 491             return c >= '0' && c <= '9';
 492         }
 493 
 494         /**
 495          * Compares this attribute name to another for equality.
 496          * @param o the object to compare
 497          * @return true if this attribute name is equal to the
 498          *         specified attribute object
 499          */
 500         public boolean equals(Object o) {
 501             if (o instanceof Name) {
 502                 Comparator<String> c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER;
 503                 return c.compare(name, ((Name)o).name) == 0;
 504             } else {
 505                 return false;
 506             }
 507         }
 508 
 509         /**
 510          * Computes the hash value for this attribute name.
 511          */
 512         public int hashCode() {
 513             if (hashCode == -1) {
 514                 hashCode = ASCIICaseInsensitiveComparator.lowerCaseHashCode(name);
 515             }
 516             return hashCode;
 517         }
 518 
 519         /**
 520          * Returns the attribute name as a String.
 521          */
 522         public String toString() {
 523             return name;
 524         }
 525 
 526         /**
 527          * <code>Name</code> object for <code>Manifest-Version</code>
 528          * manifest attribute. This attribute indicates the version number
 529          * of the manifest standard to which a JAR file's manifest conforms.
 530          * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
 531          *      Manifest and Signature Specification</a>
 532          */
 533         public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
 534 
 535         /**
 536          * <code>Name</code> object for <code>Signature-Version</code>
 537          * manifest attribute used when signing JAR files.
 538          * @see <a href="../../../../technotes/guides/jar/jar.html#JAR Manifest">
 539          *      Manifest and Signature Specification</a>
 540          */
 541         public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
 542 
 543         /**
 544          * <code>Name</code> object for <code>Content-Type</code>
 545          * manifest attribute.
 546          */
 547         public static final Name CONTENT_TYPE = new Name("Content-Type");
 548 
 549         /**
 550          * <code>Name</code> object for <code>Class-Path</code>
 551          * manifest attribute. Bundled extensions can use this attribute
 552          * to find other JAR files containing needed classes.
 553          * @see <a href="../../../../technotes/guides/extensions/spec.html#bundled">
 554          *      Extensions Specification</a>
 555          */
 556         public static final Name CLASS_PATH = new Name("Class-Path");
 557 
 558         /**
 559          * <code>Name</code> object for <code>Main-Class</code> manifest
 560          * attribute used for launching applications packaged in JAR files.
 561          * The <code>Main-Class</code> attribute is used in conjunction
 562          * with the <code>-jar</code> command-line option of the
 563          * <tt>java</tt> application launcher.
 564          */
 565         public static final Name MAIN_CLASS = new Name("Main-Class");
 566 
 567         /**
 568          * {@code Name} object for {@code Profile} manifest attribute used by
 569          * applications or libraries packaged as JAR files to indicate the 
 570          * minimum profile required to execute the application.
 571          * @since 1.8
 572          * @see UnsupportedProfileException
 573          */
 574         public static final Name PROFILE = new Name("Profile");
 575 
 576         /**
 577          * <code>Name</code> object for <code>Sealed</code> manifest attribute
 578          * used for sealing.
 579          * @see <a href="../../../../technotes/guides/extensions/spec.html#sealing">
 580          *      Extension Sealing</a>
 581          */
 582         public static final Name SEALED = new Name("Sealed");
 583 
 584        /**
 585          * <code>Name</code> object for <code>Extension-List</code> manifest attribute
 586          * used for declaring dependencies on installed extensions.
 587          * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
 588          *      Installed extension dependency</a>
 589          */
 590         public static final Name EXTENSION_LIST = new Name("Extension-List");
 591 
 592         /**
 593          * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
 594          * used for declaring dependencies on installed extensions.
 595          * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
 596          *      Installed extension dependency</a>
 597          */
 598         public static final Name EXTENSION_NAME = new Name("Extension-Name");
 599 
 600         /**
 601          * <code>Name</code> object for <code>Extension-Name</code> manifest attribute
 602          * used for declaring dependencies on installed extensions.
 603          * @see <a href="../../../../technotes/guides/extensions/spec.html#dependency">
 604          *      Installed extension dependency</a>
 605          */
 606         public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
 607 
 608         /**
 609          * <code>Name</code> object for <code>Implementation-Title</code>
 610          * manifest attribute used for package versioning.
 611          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 612          *      Java Product Versioning Specification</a>
 613          */
 614         public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
 615 
 616         /**
 617          * <code>Name</code> object for <code>Implementation-Version</code>
 618          * manifest attribute used for package versioning.
 619          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 620          *      Java Product Versioning Specification</a>
 621          */
 622         public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
 623 
 624         /**
 625          * <code>Name</code> object for <code>Implementation-Vendor</code>
 626          * manifest attribute used for package versioning.
 627          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 628          *      Java Product Versioning Specification</a>
 629          */
 630         public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
 631 
 632         /**
 633          * <code>Name</code> object for <code>Implementation-Vendor-Id</code>
 634          * manifest attribute used for package versioning.
 635          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 636          *      Java Product Versioning Specification</a>
 637          */
 638         public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
 639 
 640        /**
 641          * <code>Name</code> object for <code>Implementation-URL</code>
 642          * manifest attribute used for package versioning.
 643          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 644          *      Java Product Versioning Specification</a>
 645          */
 646         public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
 647 
 648         /**
 649          * <code>Name</code> object for <code>Specification-Title</code>
 650          * manifest attribute used for package versioning.
 651          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 652          *      Java Product Versioning Specification</a>
 653          */
 654         public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
 655 
 656         /**
 657          * <code>Name</code> object for <code>Specification-Version</code>
 658          * manifest attribute used for package versioning.
 659          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 660          *      Java Product Versioning Specification</a>
 661          */
 662         public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
 663 
 664         /**
 665          * <code>Name</code> object for <code>Specification-Vendor</code>
 666          * manifest attribute used for package versioning.
 667          * @see <a href="../../../../technotes/guides/versioning/spec/versioning2.html#wp90779">
 668          *      Java Product Versioning Specification</a>
 669          */
 670         public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
 671     }
 672 }