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 testWriteValidManifestOrException()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 writeValidManifest(String name, String value)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 writeInvalidManifestThrowsException(String name, String value)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 testReadDifferentLineWidths()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 readSpecificLineWidthManifest(String name, String value, int lineWidth)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 breakLines(int lineWidth, String nameAndValue)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 writeManifest(String name, String value)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 printManifest(byte[] mfBytes)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 assertMainAndSectionValues(Manifest mf, String name, String value)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 } 285