< prev index next >

src/java.base/share/classes/java/util/jar/Attributes.java

Print this page
rev 51096 : 8205525: Improve exception messages during manifest parsing of jar archives


   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      *


 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 




   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.File;
  30 import java.io.IOException;
  31 import java.security.AccessController;
  32 import java.security.PrivilegedAction;
  33 import java.security.Security;
  34 import java.util.Collection;
  35 import java.util.HashMap;
  36 import java.util.LinkedHashMap;
  37 import java.util.Map;
  38 import java.util.Objects;
  39 import java.util.Set;
  40 
  41 import sun.util.logging.PlatformLogger;
  42 
  43 /**
  44  * The Attributes class maps Manifest attribute names to associated string
  45  * values. Valid attribute names are case-insensitive, are restricted to
  46  * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
  47  * characters in length. There must be a colon and a SPACE after the name;
  48  * the combined length will not exceed 72 characters.
  49  * Attribute values can contain any characters and
  50  * will be UTF8-encoded when written to the output stream.  See the
  51  * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
  52  * for more information about valid attribute names and values.
  53  *
  54  * <p>This map and its views have a predictable iteration order, namely the
  55  * order that keys were inserted into the map, as with {@link LinkedHashMap}.
  56  *
  57  * @author  David Connelly
  58  * @see     Manifest
  59  * @since   1.2
  60  */
  61 public class Attributes implements Map<Object,Object>, Cloneable {
  62     /**
  63      * The attribute name-value mappings.
  64      */
  65     protected Map<Object,Object> map;
  66 
  67     /**
  68      * Security or system property which specifies categories of
  69      * (potentially sensitive) information that may be included
  70      * in exception text. This class only defines one category:
  71      * "jarpath" which represents the path of a jar file
  72      * relating to an IO exception.
  73      * The property value is a comma separated list of
  74      * case insignificant category names.
  75      */
  76     private static final String enhancedTextPropname = "jdk.includeInExceptions";
  77 
  78     private static final boolean jarPathInExceptionText = initTextProp();
  79 
  80     private static boolean initTextProp() {
  81         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
  82             public Boolean run() {
  83                 String val = System.getProperty(enhancedTextPropname);
  84                 if (val == null) {
  85                     val = Security.getProperty(enhancedTextPropname);
  86                     if (val == null)
  87                         return false;
  88                 }
  89                 String[] tokens = val.split(",");
  90                 for (String token : tokens) {
  91                     if (token.equalsIgnoreCase("jarpath"))
  92                         return true;
  93                 }
  94                 return false;
  95             }
  96         });
  97     }
  98 
  99 
 100     /**
 101      * Constructs a new, empty Attributes object with default size.
 102      */
 103     public Attributes() {
 104         this(11);
 105     }
 106 
 107     /**
 108      * Constructs a new, empty Attributes object with the specified
 109      * initial size.
 110      *
 111      * @param size the initial number of attributes
 112      */
 113     public Attributes(int size) {
 114         map = new LinkedHashMap<>(size);
 115     }
 116 
 117     /**
 118      * Constructs a new Attributes object with the same attribute name-value
 119      * mappings as in the specified Attributes.
 120      *


 389 
 390                 String value = (String) e.getValue();
 391                 if (value != null) {
 392                     byte[] vb = value.getBytes("UTF8");
 393                     value = new String(vb, 0, 0, vb.length);
 394                 }
 395                 buffer.append(value);
 396 
 397                 Manifest.make72Safe(buffer);
 398                 buffer.append("\r\n");
 399                 out.writeBytes(buffer.toString());
 400             }
 401         }
 402         out.writeBytes("\r\n");
 403     }
 404 
 405     /*
 406      * Reads attributes from the specified input stream.
 407      * XXX Need to handle UTF8 values.
 408      */

 409     void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
 410         read(is, lbuf, null, 0);
 411     }
 412 
 413     @SuppressWarnings("deprecation")
 414     int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int offset) throws IOException {
 415         String name = null, value;
 416         byte[] lastline = null;
 417         int lineNumber = offset;
 418 
 419         int len;
 420         while ((len = is.readLine(lbuf)) != -1) {
 421             boolean lineContinued = false;
 422             byte c = lbuf[--len];
 423             lineNumber++;
 424 
 425             if (c != '\n' && c != '\r') {
 426                 throw new IOException("line too long (" + getErrorPosition(filename, lineNumber) + ")");
 427             }
 428             if (len > 0 && lbuf[len-1] == '\r') {
 429                 --len;
 430             }
 431             if (len == 0) {
 432                 break;
 433             }
 434             int i = 0;
 435             if (lbuf[0] == ' ') {
 436                 // continuation of previous line
 437                 if (name == null) {
 438                     throw new IOException("misplaced continuation line (" + getErrorPosition(filename, lineNumber) + ")");
 439                 }
 440                 lineContinued = true;
 441                 byte[] buf = new byte[lastline.length + len - 1];
 442                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
 443                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
 444                 if (is.peek() == ' ') {
 445                     lastline = buf;
 446                     continue;
 447                 }
 448                 value = new String(buf, 0, buf.length, "UTF8");
 449                 lastline = null;
 450             } else {
 451                 while (lbuf[i++] != ':') {
 452                     if (i >= len) {
 453                         throw new IOException("invalid header field (" + getErrorPosition(filename, lineNumber) + ")");
 454                     }
 455                 }
 456                 if (lbuf[i++] != ' ') {
 457                     throw new IOException("invalid header field (" + getErrorPosition(filename, lineNumber) + ")");
 458                 }
 459                 name = new String(lbuf, 0, 0, i - 2);
 460                 if (is.peek() == ' ') {
 461                     lastline = new byte[len - i];
 462                     System.arraycopy(lbuf, i, lastline, 0, len - i);
 463                     continue;
 464                 }
 465                 value = new String(lbuf, i, len - i, "UTF8");
 466             }
 467             try {
 468                 if ((putValue(name, value) != null) && (!lineContinued)) {
 469                     PlatformLogger.getLogger("java.util.jar").warning(
 470                                      "Duplicate name in Manifest: " + name
 471                                      + ".\n"
 472                                      + "Ensure that the manifest does not "
 473                                      + "have duplicate entries, and\n"
 474                                      + "that blank lines separate "
 475                                      + "individual sections in both your\n"
 476                                      + "manifest and in the META-INF/MANIFEST.MF "
 477                                      + "entry in the jar file.");
 478                 }
 479             } catch (IllegalArgumentException e) {
 480                 throw new IOException("invalid header field name: " + name + " (" + getErrorPosition(filename, lineNumber) + ")");
 481             }
 482         }
 483         return lineNumber;
 484     }
 485 
 486     static String getErrorPosition(String filename, final int lineNumber) {
 487         if (filename == null || !jarPathInExceptionText) {
 488             return "line " + lineNumber;
 489         }
 490 
 491         final File file = new File(filename);
 492         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 493             public String run() {
 494                 return file.getAbsolutePath() + ":" + lineNumber;
 495             }
 496         });
 497     }
 498 
 499     /**
 500      * The Attributes.Name class represents an attribute name stored in
 501      * this Map. Valid attribute names are case-insensitive, are restricted
 502      * to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
 503      * 70 characters in length. Attribute values can contain any characters
 504      * and will be UTF8-encoded when written to the output stream.  See the
 505      * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
 506      * for more information about valid attribute names and values.
 507      */
 508     public static class Name {
 509         private final String name;
 510         private final int hashCode;
 511 
 512         /**
 513          * Avoid allocation for common Names
 514          */
 515         private static final Map<String, Name> KNOWN_NAMES;
 516 


< prev index next >