1 /*
2  * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 package java.util.jar;
27
28 import java.io.DataOutputStream;
29 import java.io.IOException;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36
37 import sun.util.logging.PlatformLogger;
38
39 /**
40  * The Attributes class maps Manifest attribute names to associated string
41  * values. Valid attribute names are case-insensitive, are restricted to
42  * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
43  * characters in length. There must be a colon and a SPACE after the name;
44  * the combined length will not exceed 72 characters.
45  * Attribute values can contain any characters and
46  * will be UTF8-encoded when written to the output stream.  See the
47  * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
48  * for more information about valid attribute names and values.
49  *
50  * <p>This map and its views have a predictable iteration order, namely the
51  * order that keys were inserted into the map, as with {@link LinkedHashMap}.
52  *
53  * @author  David Connelly
54  * @see     Manifest
55  * @since   1.2
56  */

57 public class Attributes implements Map<Object,Object>, Cloneable {
58     /**
59      * The attribute name-value mappings.
60      */

61     protected Map<Object,Object> map;
62
63     /**
64      * Constructs a new, empty Attributes object with default size.
65      */

66     public Attributes() {
67         this(11);
68     }
69
70     /**
71      * Constructs a new, empty Attributes object with the specified
72      * initial size.
73      *
74      * @param size the initial number of attributes
75      */

76     public Attributes(int size) {
77         map = new LinkedHashMap<>(size);
78     }
79
80     /**
81      * Constructs a new Attributes object with the same attribute name-value
82      * mappings as in the specified Attributes.
83      *
84      * @param attr the specified Attributes
85      */

86     public Attributes(Attributes attr) {
87         map = new LinkedHashMap<>(attr);
88     }
89
90
91     /**
92      * Returns the value of the specified attribute name, or null if the
93      * attribute name was not found.
94      *
95      * @param name the attribute name
96      * @return the value of the specified attribute name, or null if
97      *         not found.
98      */

99     public Object get(Object name) {
100         return map.get(name);
101     }
102
103     /**
104      * Returns the value of the specified attribute name, specified as
105      * a string, or null if the attribute was not found. The attribute
106      * name is case-insensitive.
107      * <p>
108      * This method is defined as:
109      * <pre>
110      *      return (String)get(new Attributes.Name((String)name));
111      * </pre>
112      *
113      * @param name the attribute name as a string
114      * @return the String value of the specified attribute name, or null if
115      *         not found.
116      * @throws IllegalArgumentException if the attribute name is invalid
117      */

118     public String getValue(String name) {
119         return (String)get(Name.of(name));
120     }
121
122     /**
123      * Returns the value of the specified Attributes.Name, or null if the
124      * attribute was not found.
125      * <p>
126      * This method is defined as:
127      * <pre>
128      *     return (String)get(name);
129      * </pre>
130      *
131      * @param name the Attributes.Name object
132      * @return the String value of the specified Attribute.Name, or null if
133      *         not found.
134      */

135     public String getValue(Name name) {
136         return (String)get(name);
137     }
138
139     /**
140      * Associates the specified value with the specified attribute name
141      * (key) in this Map. If the Map previously contained a mapping for
142      * the attribute name, the old value is replaced.
143      *
144      * @param name the attribute name
145      * @param value the attribute value
146      * @return the previous value of the attribute, or null if none
147      * @exception ClassCastException if the name is not a Attributes.Name
148      *            or the value is not a String
149      */

150     public Object put(Object name, Object value) {
151         return map.put((Attributes.Name)name, (String)value);
152     }
153
154     /**
155      * Associates the specified value with the specified attribute name,
156      * specified as a String. The attributes name is case-insensitive.
157      * If the Map previously contained a mapping for the attribute name,
158      * the old value is replaced.
159      * <p>
160      * This method is defined as:
161      * <pre>
162      *      return (String)put(new Attributes.Name(name), value);
163      * </pre>
164      *
165      * @param name the attribute name as a string
166      * @param value the attribute value
167      * @return the previous value of the attribute, or null if none
168      * @exception IllegalArgumentException if the attribute name is invalid
169      */

170     public String putValue(String name, String value) {
171         return (String)put(Name.of(name), value);
172     }
173
174     /**
175      * Removes the attribute with the specified name (key) from this Map.
176      * Returns the previous attribute value, or null if none.
177      *
178      * @param name attribute name
179      * @return the previous value of the attribute, or null if none
180      */

181     public Object remove(Object name) {
182         return map.remove(name);
183     }
184
185     /**
186      * Returns true if this Map maps one or more attribute names (keys)
187      * to the specified value.
188      *
189      * @param value the attribute value
190      * @return true if this Map maps one or more attribute names to
191      *         the specified value
192      */

193     public boolean containsValue(Object value) {
194         return map.containsValue(value);
195     }
196
197     /**
198      * Returns true if this Map contains the specified attribute name (key).
199      *
200      * @param name the attribute name
201      * @return true if this Map contains the specified attribute name
202      */

203     public boolean containsKey(Object name) {
204         return map.containsKey(name);
205     }
206
207     /**
208      * Copies all of the attribute name-value mappings from the specified
209      * Attributes to this Map. Duplicate mappings will be replaced.
210      *
211      * @param attr the Attributes to be stored in this map
212      * @exception ClassCastException if attr is not an Attributes
213      */

214     public void putAll(Map<?,?> attr) {
215         // ## javac bug?
216         if (!Attributes.class.isInstance(attr))
217             throw new ClassCastException();
218         for (Map.Entry<?,?> me : (attr).entrySet())
219             put(me.getKey(), me.getValue());
220     }
221
222     /**
223      * Removes all attributes from this Map.
224      */

225     public void clear() {
226         map.clear();
227     }
228
229     /**
230      * Returns the number of attributes in this Map.
231      */

232     public int size() {
233         return map.size();
234     }
235
236     /**
237      * Returns true if this Map contains no attributes.
238      */

239     public boolean isEmpty() {
240         return map.isEmpty();
241     }
242
243     /**
244      * Returns a Set view of the attribute names (keys) contained in this Map.
245      */

246     public Set<Object> keySet() {
247         return map.keySet();
248     }
249
250     /**
251      * Returns a Collection view of the attribute values contained in this Map.
252      */

253     public Collection<Object> values() {
254         return map.values();
255     }
256
257     /**
258      * Returns a Collection view of the attribute name-value mappings
259      * contained in this Map.
260      */

261     public Set<Map.Entry<Object,Object>> entrySet() {
262         return map.entrySet();
263     }
264
265     /**
266      * Compares the specified Attributes object with this Map for equality.
267      * Returns true if the given object is also an instance of Attributes
268      * and the two Attributes objects represent the same mappings.
269      *
270      * @param o the Object to be compared
271      * @return true if the specified Object is equal to this Map
272      */

273     public boolean equals(Object o) {
274         return map.equals(o);
275     }
276
277     /**
278      * Returns the hash code value for this Map.
279      */

280     public int hashCode() {
281         return map.hashCode();
282     }
283
284     /**
285      * Returns a copy of the Attributes, implemented as follows:
286      * <pre>
287      *     public Object clone() { return new Attributes(this); }
288      * </pre>
289      * Since the attribute names and values are themselves immutable,
290      * the Attributes returned can be safely modified without affecting
291      * the original.
292      */

293     public Object clone() {
294         return new Attributes(this);
295     }
296
297     /*
298      * Writes the current attributes to the specified data output stream.
299      * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
300      */

301      @SuppressWarnings("deprecation")
302      void write(DataOutputStream os) throws IOException {
303          for (Entry<Object, Object> e : entrySet()) {
304              StringBuffer buffer = new StringBuffer(
305                                          ((Name) e.getKey()).toString());
306              buffer.append(": ");
307
308              String value = (String) e.getValue();
309              if (value != null) {
310                  byte[] vb = value.getBytes("UTF8");
311                  value = new String(vb, 0, 0, vb.length);
312              }
313              buffer.append(value);
314
315              Manifest.make72Safe(buffer);
316              buffer.append("\r\n");
317              os.writeBytes(buffer.toString());
318          }
319         os.writeBytes("\r\n");
320     }
321
322     /*
323      * Writes the current attributes to the specified data output stream,
324      * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
325      * attributes first.
326      *
327      * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
328      */

329     @SuppressWarnings("deprecation")
330     void writeMain(DataOutputStream out) throws IOException
331     {
332         // write out the *-Version header first, if it exists
333         String vername = Name.MANIFEST_VERSION.toString();
334         String version = getValue(vername);
335         if (version == null) {
336             vername = Name.SIGNATURE_VERSION.toString();
337             version = getValue(vername);
338         }
339
340         if (version != null) {
341             out.writeBytes(vername+": "+version+"\r\n");
342         }
343
344         // write out all attributes except for the version
345         // we wrote out earlier
346         for (Entry<Object, Object> e : entrySet()) {
347             String name = ((Name) e.getKey()).toString();
348             if ((version != null) && !(name.equalsIgnoreCase(vername))) {
349
350                 StringBuffer buffer = new StringBuffer(name);
351                 buffer.append(": ");
352
353                 String value = (String) e.getValue();
354                 if (value != null) {
355                     byte[] vb = value.getBytes("UTF8");
356                     value = new String(vb, 0, 0, vb.length);
357                 }
358                 buffer.append(value);
359
360                 Manifest.make72Safe(buffer);
361                 buffer.append("\r\n");
362                 out.writeBytes(buffer.toString());
363             }
364         }
365         out.writeBytes("\r\n");
366     }
367
368     /*
369      * Reads attributes from the specified input stream.
370      * XXX Need to handle UTF8 values.
371      */

372     @SuppressWarnings("deprecation")
373     void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
374         String name = null, value;
375         byte[] lastline = null;
376
377         int len;
378         while ((len = is.readLine(lbuf)) != -1) {
379             boolean lineContinued = false;
380             byte c = lbuf[--len];
381             if (c != '\n' && c != '\r') {
382                 throw new IOException("line too long");
383             }
384             if (len > 0 && lbuf[len-1] == '\r') {
385                 --len;
386             }
387             if (len == 0) {
388                 break;
389             }
390             int i = 0;
391             if (lbuf[0] == ' ') {
392                 // continuation of previous line
393                 if (name == null) {
394                     throw new IOException("misplaced continuation line");
395                 }
396                 lineContinued = true;
397                 byte[] buf = new byte[lastline.length + len - 1];
398                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
399                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
400                 if (is.peek() == ' ') {
401                     lastline = buf;
402                     continue;
403                 }
404                 value = new String(buf, 0, buf.length, "UTF8");
405                 lastline = null;
406             } else {
407                 while (lbuf[i++] != ':') {
408                     if (i >= len) {
409                         throw new IOException("invalid header field");
410                     }
411                 }
412                 if (lbuf[i++] != ' ') {
413                     throw new IOException("invalid header field");
414                 }
415                 name = new String(lbuf, 0, 0, i - 2);
416                 if (is.peek() == ' ') {
417                     lastline = new byte[len - i];
418                     System.arraycopy(lbuf, i, lastline, 0, len - i);
419                     continue;
420                 }
421                 value = new String(lbuf, i, len - i, "UTF8");
422             }
423             try {
424                 if ((putValue(name, value) != null) && (!lineContinued)) {
425                     PlatformLogger.getLogger("java.util.jar").warning(
426                                      "Duplicate name in Manifest: " + name
427                                      + ".\n"
428                                      + "Ensure that the manifest does not "
429                                      + "have duplicate entries, and\n"
430                                      + "that blank lines separate "
431                                      + "individual sections in both your\n"
432                                      + "manifest and in the META-INF/MANIFEST.MF "
433                                      + "entry in the jar file.");
434                 }
435             } catch (IllegalArgumentException e) {
436                 throw new IOException("invalid header field name: " + name);
437             }
438         }
439     }
440
441     /**
442      * The Attributes.Name class represents an attribute name stored in
443      * this Map. Valid attribute names are case-insensitive, are restricted
444      * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
445      * 70 characters in length. Attribute values can contain any characters
446      * and will be UTF8-encoded when written to the output stream.  See the
447      * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
448      * for more information about valid attribute names and values.
449      */

450     public static class Name {
451         private final String name;
452         private final int hashCode;
453
454         /**
455          * Avoid allocation for common Names
456          */

457         private static final Map<String, Name> KNOWN_NAMES;
458
459         static final Name of(String name) {
460             Name n = KNOWN_NAMES.get(name);
461             if (n != null) {
462                 return n;
463             }
464             return new Name(name);
465         }
466
467         /**
468          * Constructs a new attribute name using the given string name.
469          *
470          * @param name the attribute string name
471          * @exception IllegalArgumentException if the attribute name was
472          *            invalid
473          * @exception NullPointerException if the attribute name was null
474          */

475         public Name(String name) {
476             this.hashCode = hash(name);
477             this.name = name.intern();
478         }
479
480         // Checks the string is valid
481         private final int hash(String name) {
482             Objects.requireNonNull(name, "name");
483             int len = name.length();
484             if (len > 70 || len == 0) {
485                 throw new IllegalArgumentException(name);
486             }
487             // Calculate hash code case insensitively
488             int h = 0;
489             for (int i = 0; i < len; i++) {
490                 char c = name.charAt(i);
491                 if (c >= 'a' && c <= 'z') {
492                     // hashcode must be identical for upper and lower case
493                     h = h * 31 + (c - 0x20);
494                 } else if ((c >= 'A' && c <= 'Z' ||
495                         c >= '0' && c <= '9' ||
496                         c == '_' || c == '-')) {
497                     h = h * 31 + c;
498                 } else {
499                     throw new IllegalArgumentException(name);
500                 }
501             }
502             return h;
503         }
504
505         /**
506          * Compares this attribute name to another for equality.
507          * @param o the object to compare
508          * @return true if this attribute name is equal to the
509          *         specified attribute object
510          */

511         public boolean equals(Object o) {
512             if (this == o) {
513                 return true;
514             }
515             if (o instanceof Name) {
516                 Name other = (Name)o;
517                 return other.name.equalsIgnoreCase(name);
518             } else {
519                 return false;
520             }
521         }
522
523         /**
524          * Computes the hash value for this attribute name.
525          */

526         public int hashCode() {
527             return hashCode;
528         }
529
530         /**
531          * Returns the attribute name as a String.
532          */

533         public String toString() {
534             return name;
535         }
536
537         /**
538          * {@code Name} object for {@code Manifest-Version}
539          * manifest attribute. This attribute indicates the version number
540          * of the manifest standard to which a JAR file's manifest conforms.
541          * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
542          *      Manifest and Signature Specification</a>
543          */

544         public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
545
546         /**
547          * {@code Name} object for {@code Signature-Version}
548          * manifest attribute used when signing JAR files.
549          * @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
550          *      Manifest and Signature Specification</a>
551          */

552         public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
553
554         /**
555          * {@code Name} object for {@code Content-Type}
556          * manifest attribute.
557          */

558         public static final Name CONTENT_TYPE = new Name("Content-Type");
559
560         /**
561          * {@code Name} object for {@code Class-Path}
562          * manifest attribute.
563          * @see <a href="{@docRoot}/../specs/jar/jar.html#classpath">
564          *      JAR file specification</a>
565          */

566         public static final Name CLASS_PATH = new Name("Class-Path");
567
568         /**
569          * {@code Name} object for {@code Main-Class} manifest
570          * attribute used for launching applications packaged in JAR files.
571          * The {@code Main-Class} attribute is used in conjunction
572          * with the {@code -jar} command-line option of the
573          * {@code java} application launcher.
574          */

575         public static final Name MAIN_CLASS = new Name("Main-Class");
576
577         /**
578          * {@code Name} object for {@code Sealed} manifest attribute
579          * used for sealing.
580          * @see <a href="{@docRoot}/../specs/jar/jar.html#package-sealing">
581          *      Package Sealing</a>
582          */

583         public static final Name SEALED = new Name("Sealed");
584
585         /**
586          * {@code Name} object for {@code Extension-List} manifest attribute
587          * used for the extension mechanism that is no longer supported.
588          */

589         public static final Name EXTENSION_LIST = new Name("Extension-List");
590
591         /**
592          * {@code Name} object for {@code Extension-Name} manifest attribute.
593          * used for the extension mechanism that is no longer supported.
594          */

595         public static final Name EXTENSION_NAME = new Name("Extension-Name");
596
597         /**
598          * {@code Name} object for {@code Extension-Installation} manifest attribute.
599          *
600          * @deprecated Extension mechanism is no longer supported.
601          */

602         @Deprecated
603         public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
604
605         /**
606          * {@code Name} object for {@code Implementation-Title}
607          * manifest attribute used for package versioning.
608          */

609         public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
610
611         /**
612          * {@code Name} object for {@code Implementation-Version}
613          * manifest attribute used for package versioning.
614          */

615         public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
616
617         /**
618          * {@code Name} object for {@code Implementation-Vendor}
619          * manifest attribute used for package versioning.
620          */

621         public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
622
623         /**
624          * {@code Name} object for {@code Implementation-Vendor-Id}
625          * manifest attribute.
626          *
627          * @deprecated Extension mechanism is no longer supported.
628          */

629         @Deprecated
630         public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
631
632         /**
633          * {@code Name} object for {@code Implementation-URL}
634          * manifest attribute.
635          *
636          * @deprecated Extension mechanism is no longer supported.
637          */

638         @Deprecated
639         public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
640
641         /**
642          * {@code Name} object for {@code Specification-Title}
643          * manifest attribute used for package versioning.
644          */

645         public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
646
647         /**
648          * {@code Name} object for {@code Specification-Version}
649          * manifest attribute used for package versioning.
650          */

651         public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
652
653         /**
654          * {@code Name} object for {@code Specification-Vendor}
655          * manifest attribute used for package versioning.
656          */

657         public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
658
659         /**
660          * {@code Name} object for {@code Multi-Release}
661          * manifest attribute that indicates this is a multi-release JAR file.
662          *
663          * @since   9
664          */

665         public static final Name MULTI_RELEASE = new Name("Multi-Release");
666
667         private static void addName(Map<String, Name> names, Name name) {
668             names.put(name.name, name);
669         }
670
671         static {
672             var names = new HashMap<String, Name>(64);
673             addName(names, MANIFEST_VERSION);
674             addName(names, SIGNATURE_VERSION);
675             addName(names, CONTENT_TYPE);
676             addName(names, CLASS_PATH);
677             addName(names, MAIN_CLASS);
678             addName(names, SEALED);
679             addName(names, EXTENSION_LIST);
680             addName(names, EXTENSION_NAME);
681             addName(names, IMPLEMENTATION_TITLE);
682             addName(names, IMPLEMENTATION_VERSION);
683             addName(names, IMPLEMENTATION_VENDOR);
684             addName(names, SPECIFICATION_TITLE);
685             addName(names, SPECIFICATION_VERSION);
686             addName(names, SPECIFICATION_VENDOR);
687             addName(names, MULTI_RELEASE);
688
689             // Common attributes used in MANIFEST.MF et.al; adding these has a
690             // small footprint cost, but is likely to be quickly paid for by
691             // reducing allocation when reading and parsing typical manifests
692             addName(names, new Name("Add-Exports"));
693             addName(names, new Name("Add-Opens"));
694             addName(names, new Name("Ant-Version"));
695             addName(names, new Name("Archiver-Version"));
696             addName(names, new Name("Build-Jdk"));
697             addName(names, new Name("Built-By"));
698             addName(names, new Name("Bnd-LastModified"));
699             addName(names, new Name("Bundle-Description"));
700             addName(names, new Name("Bundle-DocURL"));
701             addName(names, new Name("Bundle-License"));
702             addName(names, new Name("Bundle-ManifestVersion"));
703             addName(names, new Name("Bundle-Name"));
704             addName(names, new Name("Bundle-Vendor"));
705             addName(names, new Name("Bundle-Version"));
706             addName(names, new Name("Bundle-SymbolicName"));
707             addName(names, new Name("Created-By"));
708             addName(names, new Name("Export-Package"));
709             addName(names, new Name("Import-Package"));
710             addName(names, new Name("Name"));
711             addName(names, new Name("SHA1-Digest"));
712             addName(names, new Name("X-Compile-Source-JDK"));
713             addName(names, new Name("X-Compile-Target-JDK"));
714             KNOWN_NAMES = names;
715         }
716     }
717 }
718