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