|
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 } |