test/jdk/java/util/jar/Manifest/LineBreakLineWidth.java
changeset 49111 1b33025ae610
equal deleted inserted replaced
49110:a7af40c779d8 49111:1b33025ae610
       
     1 /*
       
     2  * Copyright (c) 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.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import static java.nio.charset.StandardCharsets.UTF_8;
       
    25 
       
    26 import java.io.ByteArrayInputStream;
       
    27 import java.io.ByteArrayOutputStream;
       
    28 import java.io.IOException;
       
    29 import java.util.jar.Manifest;
       
    30 import java.util.jar.Attributes;
       
    31 import java.util.jar.Attributes.Name;
       
    32 
       
    33 import org.testng.annotations.Test;
       
    34 import static org.testng.Assert.*;
       
    35 
       
    36 /**
       
    37  * @test
       
    38  * @bug 6372077
       
    39  * @run testng LineBreakLineWidth
       
    40  * @summary write valid manifests with respect to line breaks
       
    41  *          and read any line width
       
    42  */
       
    43 public class LineBreakLineWidth {
       
    44 
       
    45     /**
       
    46      * maximum header name length from {@link Name#isValid(String)}
       
    47      * not including the name-value delimiter <code>": "</code>
       
    48      */
       
    49     final static int MAX_HEADER_NAME_LENGTH = 70;
       
    50 
       
    51     /**
       
    52      * range of one..{@link #TEST_WIDTH_RANGE} covered in this test that
       
    53      * exceeds the range of allowed header name lengths or line widths
       
    54      * in order to also cover invalid cases beyond the valid boundaries
       
    55      * and to keep it somewhat independent from the actual manifest width.
       
    56      * <p>
       
    57      * bigger than 72 (maximum manifest header line with in bytes (not utf-8
       
    58      * encoded characters) but otherwise arbitrarily chosen
       
    59      */
       
    60     final static int TEST_WIDTH_RANGE = 123;
       
    61 
       
    62     /**
       
    63      * tests if only valid manifest files can written depending on the header
       
    64      * name length or that an exception occurs already on the attempt to write
       
    65      * an invalid one otherwise and that no invalid manifest can be written.
       
    66      * <p>
       
    67      * due to bug JDK-6372077 it was possible to write a manifest that could
       
    68      * not be read again. independent of the actual manifest line width, such
       
    69      * a situation should never happen, which is the subject of this test.
       
    70      */
       
    71     @Test
       
    72     public void testWriteValidManifestOrException() throws IOException {
       
    73         /*
       
    74          * multi-byte utf-8 characters cannot occur in header names,
       
    75          * only in values which are not subject of this test here.
       
    76          * hence, each character in a header name uses exactly one byte and
       
    77          * variable length utf-8 character encoding doesn't affect this test.
       
    78          */
       
    79 
       
    80         String name = "";
       
    81         for (int l = 1; l <= TEST_WIDTH_RANGE; l++) {
       
    82             name += "x";
       
    83             System.out.println("name = " + name + ", "
       
    84                     + "name.length = " + name.length());
       
    85 
       
    86             if (l <= MAX_HEADER_NAME_LENGTH) {
       
    87                 writeValidManifest(name, "somevalue");
       
    88             } else {
       
    89                 writeInvalidManifestThrowsException(name, "somevalue");
       
    90             }
       
    91         }
       
    92     }
       
    93 
       
    94     static void writeValidManifest(String name, String value)
       
    95             throws IOException {
       
    96         byte[] mfBytes = writeManifest(name, value);
       
    97         Manifest mf = new Manifest(new ByteArrayInputStream(mfBytes));
       
    98         assertMainAndSectionValues(mf, name, value);
       
    99     }
       
   100 
       
   101     static void writeInvalidManifestThrowsException(String name, String value)
       
   102             throws IOException {
       
   103         try {
       
   104             writeManifest(name, value);
       
   105         } catch (IllegalArgumentException e) {
       
   106             // no invalid manifest was produced which is considered acceptable
       
   107             return;
       
   108         }
       
   109 
       
   110         fail("no error writing manifest considered invalid");
       
   111     }
       
   112 
       
   113     /**
       
   114      * tests that manifest files can be read even if the line breaks are
       
   115      * placed in different positions than where the current JDK's
       
   116      * {@link Manifest#write(java.io.OutputStream)} would have put it provided
       
   117      * the manifest is valid otherwise.
       
   118      * <p>
       
   119      * the <a href="{@docRoot}/../specs/jar/jar.html#Notes_on_Manifest_and_Signature_Files">
       
   120      * "Notes on Manifest and Signature Files" in the "JAR File
       
   121      * Specification"</a> state that "no line may be longer than 72 bytes
       
   122      * (not characters), in its utf8-encoded form." but allows for earlier or
       
   123      * additional line breaks.
       
   124      * <p>
       
   125      * the most important purpose of this test case is probably to make sure
       
   126      * that manifest files broken at 70 bytes line width written with the
       
   127      * previous version of {@link Manifest} before this fix still work well.
       
   128      */
       
   129     @Test
       
   130     public void testReadDifferentLineWidths() throws IOException {
       
   131         /*
       
   132          * uses only one-byte utf-8 encoded characters as values.
       
   133          * correctly breaking multi-byte utf-8 encoded characters
       
   134          * would be subject of another test if there was one such.
       
   135          */
       
   136 
       
   137         // w: line width
       
   138         // 6 minimum required for section names starting with "Name: "
       
   139         for (int w = 6; w <= TEST_WIDTH_RANGE; w++) {
       
   140 
       
   141             // ln: header name length
       
   142             String name = "";
       
   143             // - 2 due to the delimiter ": " that has to fit on the same
       
   144             // line as the name
       
   145             for (int ln = 1; ln <= w - 2; ln++) {
       
   146                 name += "x";
       
   147 
       
   148                 // lv: value length
       
   149                 String value = "";
       
   150                 for (int lv = 1; lv <= TEST_WIDTH_RANGE; lv++) {
       
   151                     value += "y";
       
   152                 }
       
   153 
       
   154                 System.out.println("lineWidth = " + w);
       
   155                 System.out.println("name = " + name + ""
       
   156                         + ", name.length = " + name.length());
       
   157                 System.out.println("value = " + value + ""
       
   158                         + ", value.length = " + value.length());
       
   159 
       
   160                 readSpecificLineWidthManifest(name, value, w);
       
   161             }
       
   162         }
       
   163     }
       
   164 
       
   165     static void readSpecificLineWidthManifest(String name, String value,
       
   166             int lineWidth) throws IOException {
       
   167         /*
       
   168          * breaking header names is not allowed and hence cannot be reasonably
       
   169          * tested. it cannot easily be helped, that invalid manifest files
       
   170          * written by the previous Manifest version implementation are illegal
       
   171          * if the header name is 69 or 70 bytes and in that case the name/value
       
   172          * delimiter ": " was broken on a new line.
       
   173          *
       
   174          * changing the line width in Manifest#make72Safe(StringBuffer),
       
   175          * however, also affects at which positions values are broken across
       
   176          * lines (should always have affected values only and never header
       
   177          * names or the delimiter) which is tested here.
       
   178          *
       
   179          * ideally, any previous Manifest implementation would have been used
       
   180          * here to provide manifest files to test reading but these are no
       
   181          * longer available in this version's sources and there might as well
       
   182          * be other libraries writing manifests. Therefore, in order to be able
       
   183          * to test any manifest file considered valid with respect to line
       
   184          * breaks that could not possibly be produced with the current Manifest
       
   185          * implementation, this test provides its own manifests in serialized
       
   186          * form.
       
   187          */
       
   188         String lineBrokenSectionName = breakLines(lineWidth, "Name: " + name);
       
   189         String lineBrokenNameAndValue = breakLines(lineWidth, name + ": " + value);
       
   190 
       
   191         ByteArrayOutputStream mfBuf = new ByteArrayOutputStream();
       
   192         mfBuf.write("Manifest-Version: 1.0".getBytes(UTF_8));
       
   193         mfBuf.write("\r\n".getBytes(UTF_8));
       
   194         mfBuf.write(lineBrokenNameAndValue.getBytes(UTF_8));
       
   195         mfBuf.write("\r\n".getBytes(UTF_8));
       
   196         mfBuf.write("\r\n".getBytes(UTF_8));
       
   197         mfBuf.write(lineBrokenSectionName.getBytes(UTF_8));
       
   198         mfBuf.write("\r\n".getBytes(UTF_8));
       
   199         mfBuf.write(lineBrokenNameAndValue.getBytes(UTF_8));
       
   200         mfBuf.write("\r\n".getBytes(UTF_8));
       
   201         mfBuf.write("\r\n".getBytes(UTF_8));
       
   202         byte[] mfBytes = mfBuf.toByteArray();
       
   203         printManifest(mfBytes);
       
   204 
       
   205         boolean nameValid = name.length() <= MAX_HEADER_NAME_LENGTH;
       
   206 
       
   207         Manifest mf;
       
   208         try {
       
   209             mf = new Manifest(new ByteArrayInputStream(mfBytes));
       
   210         } catch (IOException e) {
       
   211             if (!nameValid &&
       
   212                     e.getMessage().startsWith("invalid header field")) {
       
   213                 // expected because the name is not valid
       
   214                 return;
       
   215             }
       
   216 
       
   217             throw new AssertionError(e.getMessage(), e);
       
   218         }
       
   219 
       
   220         assertTrue(nameValid, "failed to detect invalid manifest");
       
   221 
       
   222         assertMainAndSectionValues(mf, name, value);
       
   223     }
       
   224 
       
   225     static String breakLines(int lineWidth, String nameAndValue) {
       
   226         String lineBrokenNameAndValue = "";
       
   227         int charsOnLastLine = 0;
       
   228         for (int i = 0; i < nameAndValue.length(); i++) {
       
   229             lineBrokenNameAndValue += nameAndValue.substring(i, i + 1);
       
   230             charsOnLastLine++;
       
   231             if (0 < i && i < nameAndValue.length() - 1
       
   232                     && charsOnLastLine == lineWidth) {
       
   233                 lineBrokenNameAndValue += "\r\n ";
       
   234                 charsOnLastLine = 1;
       
   235             }
       
   236         }
       
   237         return lineBrokenNameAndValue;
       
   238     }
       
   239 
       
   240     static byte[] writeManifest(String name, String value) throws IOException {
       
   241         /*
       
   242          * writing manifest main headers is implemented separately from
       
   243          * writing named sections manifest headers:
       
   244          * - java.util.jar.Attributes.writeMain(DataOutputStream)
       
   245          * - java.util.jar.Attributes.write(DataOutputStream)
       
   246          * which is why this is also covered separately in this test by
       
   247          * always adding the same value twice, in the main attributes as
       
   248          * well as in a named section (using the header name also as the
       
   249          * section name).
       
   250          */
       
   251 
       
   252         Manifest mf = new Manifest();
       
   253         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
       
   254         mf.getMainAttributes().putValue(name, value);
       
   255 
       
   256         Attributes section = new Attributes();
       
   257         section.putValue(name, value);
       
   258         mf.getEntries().put(name, section);
       
   259 
       
   260         ByteArrayOutputStream out = new ByteArrayOutputStream();
       
   261         mf.write(out);
       
   262         byte[] mfBytes = out.toByteArray();
       
   263         printManifest(mfBytes);
       
   264         return mfBytes;
       
   265     }
       
   266 
       
   267     private static void printManifest(byte[] mfBytes) {
       
   268         final String sepLine = "----------------------------------------------"
       
   269                 + "---------------------|-|-|"; // |-positions: ---68-70-72
       
   270         System.out.println(sepLine);
       
   271         System.out.print(new String(mfBytes, UTF_8));
       
   272         System.out.println(sepLine);
       
   273     }
       
   274 
       
   275     private static void assertMainAndSectionValues(Manifest mf, String name,
       
   276             String value) {
       
   277         String mainValue = mf.getMainAttributes().getValue(name);
       
   278         String sectionValue = mf.getAttributes(name).getValue(name);
       
   279 
       
   280         assertEquals(value, mainValue, "value different in main section");
       
   281         assertEquals(value, sectionValue, "value different in named section");
       
   282     }
       
   283 
       
   284 }