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