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