1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package java.util.jar; 28 29 import java.io.DataInputStream; 30 import java.io.DataOutputStream; 31 import java.io.IOException; 32 import java.util.HashMap; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.Collection; 36 import java.util.AbstractSet; 37 import java.util.Iterator; 38 import sun.util.logging.PlatformLogger; 39 import java.util.Comparator; 40 import sun.misc.ASCIICaseInsensitiveComparator; 41 42 /** 43 * The Attributes class maps Manifest attribute names to associated string 44 * values. Valid attribute names are case-insensitive, are restricted to 45 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70 46 * characters in length. Attribute values can contain any characters and 47 * will be UTF8-encoded when written to the output stream. See the 48 * <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/jar/jar.html">JAR File Specification</a> 49 * for more information about valid attribute names and values. 50 * 51 * @author David Connelly 52 * @see Manifest 53 * @since 1.2 54 */ 55 public class Attributes implements Map<Object,Object>, Cloneable { 56 /** 57 * The attribute name-value mappings. 58 */ 59 protected Map<Object,Object> map; 60 61 /** 62 * Constructs a new, empty Attributes object with default size. 63 */ Attributes()64 public Attributes() { 65 this(11); 66 } 67 68 /** 69 * Constructs a new, empty Attributes object with the specified 70 * initial size. 71 * 72 * @param size the initial number of attributes 73 */ Attributes(int size)74 public Attributes(int size) { 75 map = new HashMap<>(size); 76 } 77 78 /** 79 * Constructs a new Attributes object with the same attribute name-value 80 * mappings as in the specified Attributes. 81 * 82 * @param attr the specified Attributes 83 */ Attributes(Attributes attr)84 public Attributes(Attributes attr) { 85 map = new HashMap<>(attr); 86 } 87 88 89 /** 90 * Returns the value of the specified attribute name, or null if the 91 * attribute name was not found. 92 * 93 * @param name the attribute name 94 * @return the value of the specified attribute name, or null if 95 * not found. 96 */ get(Object name)97 public Object get(Object name) { 98 return map.get(name); 99 } 100 101 /** 102 * Returns the value of the specified attribute name, specified as 103 * a string, or null if the attribute was not found. The attribute 104 * name is case-insensitive. 105 * <p> 106 * This method is defined as: 107 * <pre> 108 * return (String)get(new Attributes.Name((String)name)); 109 * </pre> 110 * 111 * @param name the attribute name as a string 112 * @return the String value of the specified attribute name, or null if 113 * not found. 114 * @throws IllegalArgumentException if the attribute name is invalid 115 */ getValue(String name)116 public String getValue(String name) { 117 return (String)get(new Attributes.Name(name)); 118 } 119 120 /** 121 * Returns the value of the specified Attributes.Name, or null if the 122 * attribute was not found. 123 * <p> 124 * This method is defined as: 125 * <pre> 126 * return (String)get(name); 127 * </pre> 128 * 129 * @param name the Attributes.Name object 130 * @return the String value of the specified Attribute.Name, or null if 131 * not found. 132 */ getValue(Name name)133 public String getValue(Name name) { 134 return (String)get(name); 135 } 136 137 /** 138 * Associates the specified value with the specified attribute name 139 * (key) in this Map. If the Map previously contained a mapping for 140 * the attribute name, the old value is replaced. 141 * 142 * @param name the attribute name 143 * @param value the attribute value 144 * @return the previous value of the attribute, or null if none 145 * @exception ClassCastException if the name is not a Attributes.Name 146 * or the value is not a String 147 */ put(Object name, Object value)148 public Object put(Object name, Object value) { 149 return map.put((Attributes.Name)name, (String)value); 150 } 151 152 /** 153 * Associates the specified value with the specified attribute name, 154 * specified as a String. The attributes name is case-insensitive. 155 * If the Map previously contained a mapping for the attribute name, 156 * the old value is replaced. 157 * <p> 158 * This method is defined as: 159 * <pre> 160 * return (String)put(new Attributes.Name(name), value); 161 * </pre> 162 * 163 * @param name the attribute name as a string 164 * @param value the attribute value 165 * @return the previous value of the attribute, or null if none 166 * @exception IllegalArgumentException if the attribute name is invalid 167 */ putValue(String name, String value)168 public String putValue(String name, String value) { 169 return (String)put(new Name(name), value); 170 } 171 172 /** 173 * Removes the attribute with the specified name (key) from this Map. 174 * Returns the previous attribute value, or null if none. 175 * 176 * @param name attribute name 177 * @return the previous value of the attribute, or null if none 178 */ remove(Object name)179 public Object remove(Object name) { 180 return map.remove(name); 181 } 182 183 /** 184 * Returns true if this Map maps one or more attribute names (keys) 185 * to the specified value. 186 * 187 * @param value the attribute value 188 * @return true if this Map maps one or more attribute names to 189 * the specified value 190 */ containsValue(Object value)191 public boolean containsValue(Object value) { 192 return map.containsValue(value); 193 } 194 195 /** 196 * Returns true if this Map contains the specified attribute name (key). 197 * 198 * @param name the attribute name 199 * @return true if this Map contains the specified attribute name 200 */ containsKey(Object name)201 public boolean containsKey(Object name) { 202 return map.containsKey(name); 203 } 204 205 /** 206 * Copies all of the attribute name-value mappings from the specified 207 * Attributes to this Map. Duplicate mappings will be replaced. 208 * 209 * @param attr the Attributes to be stored in this map 210 * @exception ClassCastException if attr is not an Attributes 211 */ putAll(Map<?,?> attr)212 public void putAll(Map<?,?> attr) { 213 // ## javac bug? 214 if (!Attributes.class.isInstance(attr)) 215 throw new ClassCastException(); 216 for (Map.Entry<?,?> me : (attr).entrySet()) 217 put(me.getKey(), me.getValue()); 218 } 219 220 /** 221 * Removes all attributes from this Map. 222 */ clear()223 public void clear() { 224 map.clear(); 225 } 226 227 /** 228 * Returns the number of attributes in this Map. 229 */ size()230 public int size() { 231 return map.size(); 232 } 233 234 /** 235 * Returns true if this Map contains no attributes. 236 */ isEmpty()237 public boolean isEmpty() { 238 return map.isEmpty(); 239 } 240 241 /** 242 * Returns a Set view of the attribute names (keys) contained in this Map. 243 */ keySet()244 public Set<Object> keySet() { 245 return map.keySet(); 246 } 247 248 /** 249 * Returns a Collection view of the attribute values contained in this Map. 250 */ values()251 public Collection<Object> values() { 252 return map.values(); 253 } 254 255 /** 256 * Returns a Collection view of the attribute name-value mappings 257 * contained in this Map. 258 */ entrySet()259 public Set<Map.Entry<Object,Object>> entrySet() { 260 return map.entrySet(); 261 } 262 263 /** 264 * Compares the specified Attributes object with this Map for equality. 265 * Returns true if the given object is also an instance of Attributes 266 * and the two Attributes objects represent the same mappings. 267 * 268 * @param o the Object to be compared 269 * @return true if the specified Object is equal to this Map 270 */ equals(Object o)271 public boolean equals(Object o) { 272 return map.equals(o); 273 } 274 275 /** 276 * Returns the hash code value for this Map. 277 */ hashCode()278 public int hashCode() { 279 return map.hashCode(); 280 } 281 282 /** 283 * Returns a copy of the Attributes, implemented as follows: 284 * <pre> 285 * public Object clone() { return new Attributes(this); } 286 * </pre> 287 * Since the attribute names and values are themselves immutable, 288 * the Attributes returned can be safely modified without affecting 289 * the original. 290 */ clone()291 public Object clone() { 292 return new Attributes(this); 293 } 294 295 /* 296 * Writes the current attributes to the specified data output stream. 297 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes 298 */ write(DataOutputStream os)299 void write(DataOutputStream os) throws IOException { 300 Iterator<Map.Entry<Object, Object>> it = entrySet().iterator(); 301 while (it.hasNext()) { 302 Map.Entry<Object, Object> e = it.next(); 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 */ writeMain(DataOutputStream out)328 void writeMain(DataOutputStream out) throws IOException 329 { 330 // write out the *-Version header first, if it exists 331 String vername = Name.MANIFEST_VERSION.toString(); 332 String version = getValue(vername); 333 if (version == null) { 334 vername = Name.SIGNATURE_VERSION.toString(); 335 version = getValue(vername); 336 } 337 338 if (version != null) { 339 out.writeBytes(vername+": "+version+"\r\n"); 340 } 341 342 // write out all attributes except for the version 343 // we wrote out earlier 344 Iterator<Map.Entry<Object, Object>> it = entrySet().iterator(); 345 while (it.hasNext()) { 346 Map.Entry<Object, Object> e = it.next(); 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 buffer.append("\r\n"); 361 Manifest.make72Safe(buffer); 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 */ read(Manifest.FastInputStream is, byte[] lbuf)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="{@docRoot}openjdk-redirect.html?v=8&path=/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 */ Name(String name)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 isValid(String name)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 isValid(char c)483 private static boolean isValid(char c) { 484 return isAlpha(c) || isDigit(c) || c == '_' || c == '-'; 485 } 486 isAlpha(char c)487 private static boolean isAlpha(char c) { 488 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 489 } 490 isDigit(char c)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 */ equals(Object o)501 public boolean equals(Object o) { 502 if (o instanceof Name) { 503 Comparator<String> c = ASCIICaseInsensitiveComparator.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 */ hashCode()513 public int hashCode() { 514 if (hashCode == -1) { 515 hashCode = ASCIICaseInsensitiveComparator.lowerCaseHashCode(name); 516 } 517 return hashCode; 518 } 519 520 /** 521 * Returns the attribute name as a String. 522 */ toString()523 public String toString() { 524 return name; 525 } 526 527 /** 528 * <code>Name</code> object for <code>Manifest-Version</code> 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="{@docRoot}openjdk-redirect.html?v=8&path=/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</code> object for <code>Signature-Version</code> 538 * manifest attribute used when signing JAR files. 539 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/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</code> object for <code>Content-Type</code> 546 * manifest attribute. 547 */ 548 public static final Name CONTENT_TYPE = new Name("Content-Type"); 549 550 /** 551 * <code>Name</code> object for <code>Class-Path</code> 552 * manifest attribute. Bundled extensions can use this attribute 553 * to find other JAR files containing needed classes. 554 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/jar/jar.html#classpath"> 555 * JAR file specification</a> 556 */ 557 public static final Name CLASS_PATH = new Name("Class-Path"); 558 559 /** 560 * <code>Name</code> object for <code>Main-Class</code> manifest 561 * attribute used for launching applications packaged in JAR files. 562 * The <code>Main-Class</code> attribute is used in conjunction 563 * with the <code>-jar</code> command-line option of the 564 * <tt>java</tt> application launcher. 565 */ 566 public static final Name MAIN_CLASS = new Name("Main-Class"); 567 568 /** 569 * <code>Name</code> object for <code>Sealed</code> manifest attribute 570 * used for sealing. 571 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/jar/jar.html#sealing"> 572 * Package Sealing</a> 573 */ 574 public static final Name SEALED = new Name("Sealed"); 575 576 /** 577 * <code>Name</code> object for <code>Extension-List</code> manifest attribute 578 * used for declaring dependencies on installed extensions. 579 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/spec.html#dependency"> 580 * Installed extension dependency</a> 581 */ 582 public static final Name EXTENSION_LIST = new Name("Extension-List"); 583 584 /** 585 * <code>Name</code> object for <code>Extension-Name</code> manifest attribute 586 * used for declaring dependencies on installed extensions. 587 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/spec.html#dependency"> 588 * Installed extension dependency</a> 589 */ 590 public static final Name EXTENSION_NAME = new Name("Extension-Name"); 591 592 /** 593 * <code>Name</code> object for <code>Extension-Name</code> manifest attribute 594 * used for declaring dependencies on installed extensions. 595 * @deprecated Extension mechanism will be removed in a future release. 596 * Use class path instead. 597 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/spec.html#dependency"> 598 * Installed extension dependency</a> 599 */ 600 @Deprecated 601 public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); 602 603 /** 604 * <code>Name</code> object for <code>Implementation-Title</code> 605 * manifest attribute used for package versioning. 606 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 607 * Java Product Versioning Specification</a> 608 */ 609 public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 610 611 /** 612 * <code>Name</code> object for <code>Implementation-Version</code> 613 * manifest attribute used for package versioning. 614 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 615 * Java Product Versioning Specification</a> 616 */ 617 public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 618 619 /** 620 * <code>Name</code> object for <code>Implementation-Vendor</code> 621 * manifest attribute used for package versioning. 622 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 623 * Java Product Versioning Specification</a> 624 */ 625 public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 626 627 /** 628 * <code>Name</code> object for <code>Implementation-Vendor-Id</code> 629 * manifest attribute used for package versioning. 630 * @deprecated Extension mechanism will be removed in a future release. 631 * Use class path instead. 632 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/versioning.html#applet"> 633 * Optional Package Versioning</a> 634 */ 635 @Deprecated 636 public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 637 638 /** 639 * <code>Name</code> object for <code>Implementation-URL</code> 640 * manifest attribute used for package versioning. 641 * @deprecated Extension mechanism will be removed in a future release. 642 * Use class path instead. 643 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/versioning.html#applet"> 644 * Optional Package Versioning</a> 645 */ 646 @Deprecated 647 public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); 648 649 /** 650 * <code>Name</code> object for <code>Specification-Title</code> 651 * manifest attribute used for package versioning. 652 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 653 * Java Product Versioning Specification</a> 654 */ 655 public static final Name SPECIFICATION_TITLE = new Name("Specification-Title"); 656 657 /** 658 * <code>Name</code> object for <code>Specification-Version</code> 659 * manifest attribute used for package versioning. 660 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 661 * Java Product Versioning Specification</a> 662 */ 663 public static final Name SPECIFICATION_VERSION = new Name("Specification-Version"); 664 665 /** 666 * <code>Name</code> object for <code>Specification-Vendor</code> 667 * manifest attribute used for package versioning. 668 * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/versioning/spec/versioning2.html#wp90779"> 669 * Java Product Versioning Specification</a> 670 */ 671 public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 672 673 /** 674 * @hide 675 */ 676 public static final Name NAME = new Name("Name"); 677 } 678 } 679