< prev index next >

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

Print this page
rev 51517 : 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.


 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.security.util.SecurityProperties;
  42 
  43 import sun.util.logging.PlatformLogger;
  44 
  45 /**
  46  * The Attributes class maps Manifest attribute names to associated string
  47  * values. Valid attribute names are case-insensitive, are restricted to
  48  * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
  49  * characters in length. There must be a colon and a SPACE after the name;
  50  * the combined length will not exceed 72 characters.
  51  * Attribute values can contain any characters and
  52  * will be UTF8-encoded when written to the output stream.  See the
  53  * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
  54  * for more information about valid attribute names and values.
  55  *
  56  * <p>This map and its views have a predictable iteration order, namely the
  57  * order that keys were inserted into the map, as with {@link LinkedHashMap}.
  58  *
  59  * @author  David Connelly
  60  * @see     Manifest
  61  * @since   1.2
  62  */
  63 public class Attributes implements Map<Object,Object>, Cloneable {
  64     /**
  65      * The attribute name-value mappings.
  66      */
  67     protected Map<Object,Object> map;
  68 
  69     private static final boolean jarPathInExceptionText = 
  70                  SecurityProperties.includedInExceptions("jarPath"); 
  71 
  72     /**
  73      * Constructs a new, empty Attributes object with default size.
  74      */
  75     public Attributes() {
  76         this(11);
  77     }
  78 
  79     /**
  80      * Constructs a new, empty Attributes object with the specified
  81      * initial size.
  82      *
  83      * @param size the initial number of attributes
  84      */
  85     public Attributes(int size) {
  86         map = new LinkedHashMap<>(size);
  87     }
  88 
  89     /**
  90      * Constructs a new Attributes object with the same attribute name-value
  91      * mappings as in the specified Attributes.


 361 
 362                 String value = (String) e.getValue();
 363                 if (value != null) {
 364                     byte[] vb = value.getBytes("UTF8");
 365                     value = new String(vb, 0, 0, vb.length);
 366                 }
 367                 buffer.append(value);
 368 
 369                 Manifest.make72Safe(buffer);
 370                 buffer.append("\r\n");
 371                 out.writeBytes(buffer.toString());
 372             }
 373         }
 374         out.writeBytes("\r\n");
 375     }
 376 
 377     /*
 378      * Reads attributes from the specified input stream.
 379      * XXX Need to handle UTF8 values.
 380      */

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


< prev index next >