jdk-24/test/jdk/java/util/jar/Manifest/LineBreakLineWidth.java
2018-03-01 15:50:26 -05:00

285 lines
11 KiB
Java

/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.jar.Manifest;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
/**
* @test
* @bug 6372077
* @run testng LineBreakLineWidth
* @summary write valid manifests with respect to line breaks
* and read any line width
*/
public class LineBreakLineWidth {
/**
* maximum header name length from {@link Name#isValid(String)}
* not including the name-value delimiter <code>": "</code>
*/
final static int MAX_HEADER_NAME_LENGTH = 70;
/**
* range of one..{@link #TEST_WIDTH_RANGE} covered in this test that
* exceeds the range of allowed header name lengths or line widths
* in order to also cover invalid cases beyond the valid boundaries
* and to keep it somewhat independent from the actual manifest width.
* <p>
* bigger than 72 (maximum manifest header line with in bytes (not utf-8
* encoded characters) but otherwise arbitrarily chosen
*/
final static int TEST_WIDTH_RANGE = 123;
/**
* tests if only valid manifest files can written depending on the header
* name length or that an exception occurs already on the attempt to write
* an invalid one otherwise and that no invalid manifest can be written.
* <p>
* due to bug JDK-6372077 it was possible to write a manifest that could
* not be read again. independent of the actual manifest line width, such
* a situation should never happen, which is the subject of this test.
*/
@Test
public void testWriteValidManifestOrException() throws IOException {
/*
* multi-byte utf-8 characters cannot occur in header names,
* only in values which are not subject of this test here.
* hence, each character in a header name uses exactly one byte and
* variable length utf-8 character encoding doesn't affect this test.
*/
String name = "";
for (int l = 1; l <= TEST_WIDTH_RANGE; l++) {
name += "x";
System.out.println("name = " + name + ", "
+ "name.length = " + name.length());
if (l <= MAX_HEADER_NAME_LENGTH) {
writeValidManifest(name, "somevalue");
} else {
writeInvalidManifestThrowsException(name, "somevalue");
}
}
}
static void writeValidManifest(String name, String value)
throws IOException {
byte[] mfBytes = writeManifest(name, value);
Manifest mf = new Manifest(new ByteArrayInputStream(mfBytes));
assertMainAndSectionValues(mf, name, value);
}
static void writeInvalidManifestThrowsException(String name, String value)
throws IOException {
try {
writeManifest(name, value);
} catch (IllegalArgumentException e) {
// no invalid manifest was produced which is considered acceptable
return;
}
fail("no error writing manifest considered invalid");
}
/**
* tests that manifest files can be read even if the line breaks are
* placed in different positions than where the current JDK's
* {@link Manifest#write(java.io.OutputStream)} would have put it provided
* the manifest is valid otherwise.
* <p>
* the <a href="{@docRoot}/../specs/jar/jar.html#Notes_on_Manifest_and_Signature_Files">
* "Notes on Manifest and Signature Files" in the "JAR File
* Specification"</a> state that "no line may be longer than 72 bytes
* (not characters), in its utf8-encoded form." but allows for earlier or
* additional line breaks.
* <p>
* the most important purpose of this test case is probably to make sure
* that manifest files broken at 70 bytes line width written with the
* previous version of {@link Manifest} before this fix still work well.
*/
@Test
public void testReadDifferentLineWidths() throws IOException {
/*
* uses only one-byte utf-8 encoded characters as values.
* correctly breaking multi-byte utf-8 encoded characters
* would be subject of another test if there was one such.
*/
// w: line width
// 6 minimum required for section names starting with "Name: "
for (int w = 6; w <= TEST_WIDTH_RANGE; w++) {
// ln: header name length
String name = "";
// - 2 due to the delimiter ": " that has to fit on the same
// line as the name
for (int ln = 1; ln <= w - 2; ln++) {
name += "x";
// lv: value length
String value = "";
for (int lv = 1; lv <= TEST_WIDTH_RANGE; lv++) {
value += "y";
}
System.out.println("lineWidth = " + w);
System.out.println("name = " + name + ""
+ ", name.length = " + name.length());
System.out.println("value = " + value + ""
+ ", value.length = " + value.length());
readSpecificLineWidthManifest(name, value, w);
}
}
}
static void readSpecificLineWidthManifest(String name, String value,
int lineWidth) throws IOException {
/*
* breaking header names is not allowed and hence cannot be reasonably
* tested. it cannot easily be helped, that invalid manifest files
* written by the previous Manifest version implementation are illegal
* if the header name is 69 or 70 bytes and in that case the name/value
* delimiter ": " was broken on a new line.
*
* changing the line width in Manifest#make72Safe(StringBuffer),
* however, also affects at which positions values are broken across
* lines (should always have affected values only and never header
* names or the delimiter) which is tested here.
*
* ideally, any previous Manifest implementation would have been used
* here to provide manifest files to test reading but these are no
* longer available in this version's sources and there might as well
* be other libraries writing manifests. Therefore, in order to be able
* to test any manifest file considered valid with respect to line
* breaks that could not possibly be produced with the current Manifest
* implementation, this test provides its own manifests in serialized
* form.
*/
String lineBrokenSectionName = breakLines(lineWidth, "Name: " + name);
String lineBrokenNameAndValue = breakLines(lineWidth, name + ": " + value);
ByteArrayOutputStream mfBuf = new ByteArrayOutputStream();
mfBuf.write("Manifest-Version: 1.0".getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
mfBuf.write(lineBrokenNameAndValue.getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
mfBuf.write(lineBrokenSectionName.getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
mfBuf.write(lineBrokenNameAndValue.getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
mfBuf.write("\r\n".getBytes(UTF_8));
byte[] mfBytes = mfBuf.toByteArray();
printManifest(mfBytes);
boolean nameValid = name.length() <= MAX_HEADER_NAME_LENGTH;
Manifest mf;
try {
mf = new Manifest(new ByteArrayInputStream(mfBytes));
} catch (IOException e) {
if (!nameValid &&
e.getMessage().startsWith("invalid header field")) {
// expected because the name is not valid
return;
}
throw new AssertionError(e.getMessage(), e);
}
assertTrue(nameValid, "failed to detect invalid manifest");
assertMainAndSectionValues(mf, name, value);
}
static String breakLines(int lineWidth, String nameAndValue) {
String lineBrokenNameAndValue = "";
int charsOnLastLine = 0;
for (int i = 0; i < nameAndValue.length(); i++) {
lineBrokenNameAndValue += nameAndValue.substring(i, i + 1);
charsOnLastLine++;
if (0 < i && i < nameAndValue.length() - 1
&& charsOnLastLine == lineWidth) {
lineBrokenNameAndValue += "\r\n ";
charsOnLastLine = 1;
}
}
return lineBrokenNameAndValue;
}
static byte[] writeManifest(String name, String value) throws IOException {
/*
* writing manifest main headers is implemented separately from
* writing named sections manifest headers:
* - java.util.jar.Attributes.writeMain(DataOutputStream)
* - java.util.jar.Attributes.write(DataOutputStream)
* which is why this is also covered separately in this test by
* always adding the same value twice, in the main attributes as
* well as in a named section (using the header name also as the
* section name).
*/
Manifest mf = new Manifest();
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
mf.getMainAttributes().putValue(name, value);
Attributes section = new Attributes();
section.putValue(name, value);
mf.getEntries().put(name, section);
ByteArrayOutputStream out = new ByteArrayOutputStream();
mf.write(out);
byte[] mfBytes = out.toByteArray();
printManifest(mfBytes);
return mfBytes;
}
private static void printManifest(byte[] mfBytes) {
final String sepLine = "----------------------------------------------"
+ "---------------------|-|-|"; // |-positions: ---68-70-72
System.out.println(sepLine);
System.out.print(new String(mfBytes, UTF_8));
System.out.println(sepLine);
}
private static void assertMainAndSectionValues(Manifest mf, String name,
String value) {
String mainValue = mf.getMainAttributes().getValue(name);
String sectionValue = mf.getAttributes(name).getValue(name);
assertEquals(value, mainValue, "value different in main section");
assertEquals(value, sectionValue, "value different in named section");
}
}