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.Comparator; 32 import java.util.LinkedHashMap; 33 import java.util.Locale; 34 import java.util.Map; 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. Attribute values can contain any characters and 44 * will be UTF8-encoded when written to the output stream. See the 45 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> 46 * for more information about valid attribute names and values. 47 * 48 * <p>This map and its views have a predictable iteration order, namely the 49 * order that keys were inserted into the map, as with {@link LinkedHashMap}. 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 */ 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 */ 74 public Attributes(int size) { 75 map = new LinkedHashMap<>(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 */ 84 public Attributes(Attributes attr) { 85 map = new LinkedHashMap<>(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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 223 public void clear() { 224 map.clear(); 225 } 226 227 /** 228 * Returns the number of attributes in this Map. 229 */ 230 public int size() { 231 return map.size(); 232 } 233 234 /** 235 * Returns true if this Map contains no attributes. 236 */ 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 */ 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 */ 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 */ 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 */ 271 public boolean equals(Object o) { 272 return map.equals(o); 273 } 274 275 /** 276 * Returns the hash code value for this Map. 277 */ 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 */ 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 */ 299 @SuppressWarnings("deprecation") 300 void write(DataOutputStream os) throws IOException { 301 for (Entry<Object, Object> e : entrySet()) { 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 */ 327 @SuppressWarnings("deprecation") 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 for (Entry<Object, Object> e : entrySet()) { 345 String name = ((Name) e.getKey()).toString(); 346 if ((version != null) && !(name.equalsIgnoreCase(vername))) { 347 348 StringBuffer buffer = new StringBuffer(name); 349 buffer.append(": "); 350 351 String value = (String) e.getValue(); 352 if (value != null) { 353 byte[] vb = value.getBytes("UTF8"); 354 value = new String(vb, 0, 0, vb.length); 355 } 356 buffer.append(value); 357 358 buffer.append("\r\n"); 359 Manifest.make72Safe(buffer); 360 out.writeBytes(buffer.toString()); 361 } 362 } 363 out.writeBytes("\r\n"); 364 } 365 366 /* 367 * Reads attributes from the specified input stream. 368 * XXX Need to handle UTF8 values. 369 */ 370 @SuppressWarnings("deprecation") 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="{@docRoot}/../specs/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 */ 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 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 482 private static boolean isValid(char c) { 483 return isAlpha(c) || isDigit(c) || c == '_' || c == '-'; 484 } 485 486 private static boolean isAlpha(char c) { 487 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 488 } 489 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 */ 500 public boolean equals(Object o) { 501 if (o instanceof Name) { 502 Comparator<String> c = String.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 */ 512 public int hashCode() { 513 if (hashCode == -1) { 514 hashCode = name.toLowerCase(Locale.ROOT).hashCode(); 515 } 516 return hashCode; 517 } 518 519 /** 520 * Returns the attribute name as a String. 521 */ 522 public String toString() { 523 return name; 524 } 525 526 /** 527 * {@code Name} object for {@code Manifest-Version} 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="{@docRoot}/../specs/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} object for {@code Signature-Version} 537 * manifest attribute used when signing JAR files. 538 * @see <a href="{@docRoot}/../specs/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} object for {@code Content-Type} 545 * manifest attribute. 546 */ 547 public static final Name CONTENT_TYPE = new Name("Content-Type"); 548 549 /** 550 * {@code Name} object for {@code Class-Path} 551 * manifest attribute. 552 * @see <a href="{@docRoot}/../specs/jar/jar.html#classpath"> 553 * JAR file specification</a> 554 */ 555 public static final Name CLASS_PATH = new Name("Class-Path"); 556 557 /** 558 * {@code Name} object for {@code Main-Class} manifest 559 * attribute used for launching applications packaged in JAR files. 560 * The {@code Main-Class} attribute is used in conjunction 561 * with the {@code -jar} command-line option of the 562 * {@code java} application launcher. 563 */ 564 public static final Name MAIN_CLASS = new Name("Main-Class"); 565 566 /** 567 * {@code Name} object for {@code Sealed} manifest attribute 568 * used for sealing. 569 * @see <a href="{@docRoot}/../specs/jar/jar.html#package-sealing"> 570 * Package Sealing</a> 571 */ 572 public static final Name SEALED = new Name("Sealed"); 573 574 /** 575 * {@code Name} object for {@code Extension-List} manifest attribute 576 * used for the extension mechanism that is no longer supported. 577 */ 578 public static final Name EXTENSION_LIST = new Name("Extension-List"); 579 580 /** 581 * {@code Name} object for {@code Extension-Name} manifest attribute. 582 * used for the extension mechanism that is no longer supported. 583 */ 584 public static final Name EXTENSION_NAME = new Name("Extension-Name"); 585 586 /** 587 * {@code Name} object for {@code Extension-Installation} manifest attribute. 588 * 589 * @deprecated Extension mechanism is no longer supported. 590 */ 591 @Deprecated 592 public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); 593 594 /** 595 * {@code Name} object for {@code Implementation-Title} 596 * manifest attribute used for package versioning. 597 */ 598 public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title"); 599 600 /** 601 * {@code Name} object for {@code Implementation-Version} 602 * manifest attribute used for package versioning. 603 */ 604 public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version"); 605 606 /** 607 * {@code Name} object for {@code Implementation-Vendor} 608 * manifest attribute used for package versioning. 609 */ 610 public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor"); 611 612 /** 613 * {@code Name} object for {@code Implementation-Vendor-Id} 614 * manifest attribute. 615 * 616 * @deprecated Extension mechanism is no longer supported. 617 */ 618 @Deprecated 619 public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); 620 621 /** 622 * {@code Name} object for {@code Implementation-URL} 623 * manifest attribute. 624 * 625 * @deprecated Extension mechanism is no longer supported. 626 */ 627 @Deprecated 628 public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); 629 630 /** 631 * {@code Name} object for {@code Specification-Title} 632 * manifest attribute used for package versioning. 633 */ 634 public static final Name SPECIFICATION_TITLE = new Name("Specification-Title"); 635 636 /** 637 * {@code Name} object for {@code Specification-Version} 638 * manifest attribute used for package versioning. 639 */ 640 public static final Name SPECIFICATION_VERSION = new Name("Specification-Version"); 641 642 /** 643 * {@code Name} object for {@code Specification-Vendor} 644 * manifest attribute used for package versioning. 645 */ 646 public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor"); 647 648 /** 649 * {@code Name} object for {@code Multi-Release} 650 * manifest attribute that indicates this is a multi-release JAR file. 651 * 652 * @since 9 653 */ 654 public static final Name MULTI_RELEASE = new Name("Multi-Release"); 655 } 656 }