jdk-24/test/jdk/sun/security/util/ManifestDigester/FindSection.java
2019-07-18 08:53:06 +08:00

751 lines
26 KiB
Java

/*
* Copyright (c) 2019, 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import sun.security.util.ManifestDigester;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.*;
/**
* @test
* @bug 8217375
* @modules java.base/sun.security.util:+open
* @compile ../../tools/jarsigner/Utils.java
* @run testng/othervm FindSection
* @summary Check {@link ManifestDigester#findSection}.
*/
public class FindSection {
/*
* TODO:
* FIXED_8217375 is not intended to keep. it is intended to show what
* exactly has changed with respect to the previous version for which no
* such test existed.
*/
static final boolean FIXED_8217375 = true;
/**
* {@link ManifestDigester.Entry#digestWorkaround} should not feed the
* trailing blank line into the digester. Before resolution of 8217375 it
* fed the trailing blank line into the digest if the second line break
* was at the end of the file due to <pre>
* if (allBlank || (i == len-1)) {
* if (i == len-1)
* pos.endOfSection = i;
* else
* pos.endOfSection = last;
* </pre> in {@link ManifestDigester#findSection}. In that case at the end
* of the manifest file, {@link ManifestDigester.Entry#digestWorkaround}
* would have produced the same digest as
* {@link ManifestDigester.Entry#digest} which was wrong and without effect
* at best.
* <p>
* Once this fix is accepted, this flag can be removed along with
* {@link #actualEndOfSection8217375}.
*/
static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375;
/**
* {@link ManifestDigester.Position.endOfSection} usually points to the
* start position of the blank line trailing a section minus one.
* If a {@link ManifestDigester.Position} returned by
* {@link ManifestDigester#findSection} is based on a portion that starts
* with a blank line, above statement is (or was) not true, because of the
* initialization of {@code last} in {@link ManifestDigester#findSection}
* <pre>
* int last = offset;
* </pre>
* which would point after incrementing it in {@code pos.endOfSection + 1}
* on line 128 (line number before this change) or {@code int sectionLen =
* pos.endOfSection-start+1;} on line 133 (line number before this change)
* at one byte after the first line break character of usually two and
* possibly (assuming "{@code \r\n}" default line break normally) in between
* the two characters of a line break. After subtracting again the index of
* the section start position on former line 133, the last byte would be
* missed to be digested by {@link ManifestDigester.Entry#digestWorkaround}.
* <p>
* All this, however could possibly matter (or have mattered) only when
* {@link ManifestDigester#findSection} was invoked with an offset position
* pointing straight to a line break which happens if a manifest starts
* with an empty line or if there are superfluous blank lines between
* sections in both cases no useful manifest portion is identified.
* Superfluous blank lines are not identified as sections (because they
* don't have a name and specifically don't meet {@code if (len > 6) {} on
* former line 136. Manifests starting with a line break are not any more
* useful either.
* <p>
* Once this fix is accepted, this flag can be removed along with
* {@link #actualEndOfSection8217375}.
*/
static final boolean FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION =
FIXED_8217375;
static Constructor<?> PositionConstructor;
static Method findSection;
static Field rawBytes;
static Field endOfFirstLine;
static Field endOfSection;
static Field startOfNext;
@BeforeClass
public static void setFindSectionAccessible() throws Exception {
Class<?> Position = Arrays.stream(ManifestDigester.class.
getDeclaredClasses()).filter(c -> c.getSimpleName().
equals("Position")).findFirst().get();
PositionConstructor = Position.getDeclaredConstructor();
PositionConstructor.setAccessible(true);
findSection = ManifestDigester.class.getDeclaredMethod("findSection",
int.class, Position);
findSection.setAccessible(true);
rawBytes = ManifestDigester.class.getDeclaredField("rawBytes");
rawBytes.setAccessible(true);
endOfFirstLine = Position.getDeclaredField("endOfFirstLine");
endOfFirstLine.setAccessible(true);
endOfSection = Position.getDeclaredField("endOfSection");
endOfSection.setAccessible(true);
startOfNext = Position.getDeclaredField("startOfNext");
startOfNext.setAccessible(true);
}
static class Position {
final int endOfFirstLine; // not including newline character
final int endOfSection; // end of section, not including the blank line
// between sections
final int startOfNext; // the start of the next section
Position(Object pos) throws ReflectiveOperationException {
endOfFirstLine = FindSection.endOfFirstLine.getInt(pos);
endOfSection = FindSection.endOfSection.getInt(pos);
startOfNext = FindSection.startOfNext.getInt(pos);
}
}
Position findSection(byte[] manifestBytes)
throws ReflectiveOperationException {
ManifestDigester manDig = new ManifestDigester("\n\n".getBytes(UTF_8));
FindSection.rawBytes.set(manDig, manifestBytes);
Object pos = PositionConstructor.newInstance();
Object result = findSection.invoke(manDig, offset, pos);
if (Boolean.FALSE.equals(result)) {
return null; // indicates findSection having returned false
} else {
return new Position(pos);
}
}
@DataProvider(name = "parameters")
public static Object[][] parameters() {
return new Object[][] { { 0 }, { 42 } };
}
@Factory(dataProvider = "parameters")
public static Object[] createTests(int offset) {
return new Object[]{ new FindSection(offset) };
}
final int offset;
FindSection(int offset) {
this.offset = offset;
}
@BeforeMethod
public void verbose() {
System.out.println("offset = " + offset);
}
Position findSection(String manifestString)
throws ReflectiveOperationException {
byte[] manifestBytes = manifestString.getBytes(UTF_8);
byte[] manifestWithOffset = new byte[manifestBytes.length + offset];
System.arraycopy(manifestBytes, 0, manifestWithOffset, offset,
manifestBytes.length);
return findSection(manifestWithOffset);
}
/**
* Surprising, but the offset actually makes a difference in
* {@link ManifestDigester#findSection} return value.
*/
@SuppressWarnings("unused")
int actualEndOfFirstLine8217375(int correctPosition) {
// if the parsed portion of the manifest starts with a blank line,
// and offset is 0, "pos.endOfFirstLine = -1;" probably denoting a
// yet uninitialized value coincides with the assignment by
// "pos.endOfFirstLine = i-1;" if i == 0 and
// "if (pos.endOfFirstLine == -1)" after "case '\n':" happens to
// become true even though already assigned.
if (offset == 0 && correctPosition == -1 && !FIXED_8217375) return 0;
return correctPosition;
}
@SuppressWarnings("unused")
int actualEndOfSection8217375(int correctPosition, boolean eof, int lbl) {
// if the parsed portion of the manifest ends with a blank line and
// just before eof, the blank line is included in Position.endOfSection/
// Section.length (the one usually without blank line as well as in
// Position.startOfNext/Section.lengthWithBlankLine) which is used
// in digestWorkaround (independent of the digest without workaround)
if (eof && !FIXED_8217375_EOF_ENDOFSECTION) {
return correctPosition + lbl;
} else if (correctPosition == -1
&& !FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION) {
return 0;
} else {
return correctPosition;
}
}
AssertionError collectErrors(AssertionError a, Runnable run) {
try {
run.run();
} catch (AssertionError e) {
if (a == null) a = new AssertionError();
a.addSuppressed(e);
}
return a;
}
void assertPosition(Position pos,
int endOfFirstLine, int endOfSection, int startOfNext) {
AssertionError a = null;
a = collectErrors(a, () -> assertEquals(
pos.endOfFirstLine, endOfFirstLine + offset, "endOfFirstLine"));
a = collectErrors(a, () -> assertEquals(
pos.endOfSection, endOfSection + offset, "endOfSection"));
a = collectErrors(a, () -> assertEquals(
pos.startOfNext, startOfNext + offset, "startOfNext"));
if (a != null) throw a;
}
void catchCrCausesIndexOutOfBoundsException(
Callable<Position> test, Consumer<Position> asserts) {
try {
Position x = test.call();
if (!FIXED_8217375) fail();
asserts.accept(x);
} catch (Exception e) {
if (e instanceof IndexOutOfBoundsException ||
e.getCause() instanceof IndexOutOfBoundsException) {
if (FIXED_8217375) throw new AssertionError(e);
} else {
throw new AssertionError(e);
}
}
}
@Test
public void testEmpty() throws Exception {
assertNull(findSection(""));
}
@Test
public void testOneLineBreakCr() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("\r"),
p -> assertPosition(p,
-1, actualEndOfSection8217375(-1, false, 1), 1)
);
}
@Test
public void testOneLineBreakLf() throws Exception {
assertPosition(findSection("\n"),
-1, actualEndOfSection8217375(-1, false, 1), 1);
}
@Test
public void testOneLineBreakCrLf() throws Exception {
assertPosition(findSection("\r\n"),
actualEndOfFirstLine8217375(-1),
actualEndOfSection8217375(-1, true, 2),
2);
}
@Test
public void testSpaceAndLineBreakCr() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection(" \r"),
p -> assertPosition(p, 2, 3, 4)
);
}
@Test
public void testSpaceAndOneLineBreakLf() throws Exception {
assertPosition(findSection(" \n"), 2, 3, 4);
}
@Test
public void testSpaceAndOneLineBreakCrLf() throws Exception {
assertPosition(findSection(" \r\n"), 2, 4, 5);
}
@Test
public void testOneLineBreakCrAndSpace() throws Exception {
assertPosition(findSection("\r "),
-1, actualEndOfSection8217375(-1, false, 1), 1);
}
@Test
public void testOneLineBreakLfAndSpace() throws Exception {
assertPosition(findSection("\n "),
-1, actualEndOfSection8217375(-1, false, 1), 1);
}
@Test
public void testOneLineBreakCrLfAndSpace() throws Exception {
assertPosition(findSection("\r\n "),
actualEndOfFirstLine8217375(-1),
actualEndOfSection8217375(-1, false, 1),
2);
}
@Test
public void testCrEof() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("abc\r"),
p -> assertPosition(p, 2, 3, 4)
);
}
@Test
public void testLfEof() throws Exception {
assertPosition(findSection("abc\n"), 2, 3, 4);
}
@Test
public void testCrLfEof() throws Exception {
assertPosition(findSection("abc\r\n"), 2, 4, 5);
}
@Test
public void testCrContinued() throws Exception {
assertPosition(findSection("abc\rxyz\r\n\r\n "), 2, 8, 11);
}
@Test
public void testLfContinued() throws Exception {
assertPosition(findSection("abc\nxyz\r\n\r\n "), 2, 8, 11);
}
@Test
public void testCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
}
@Test
public void testCrCrEof() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("abc\r\nxyz\r\r"),
p -> assertPosition(p,
2, actualEndOfSection8217375(8, true, 1), 10)
);
}
@Test
public void testCrCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\r "), 2, 8, 10);
}
@Test
public void testLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\n\n"),
2, actualEndOfSection8217375(8, true, 1), 10);
}
@Test
public void testLfLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\n\n "), 2, 8, 10);
}
@Test
public void testCrLfEof2() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n"), 2, 9, 10);
}
@Test
public void testMainSectionNotTerminatedWithLineBreak() throws Exception {
assertNull(findSection("abc\r\nxyz\r\n "));
}
@Test
public void testLfCrEof() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("abc\r\nxyz\n\r"),
p -> assertPosition(p,
2, actualEndOfSection8217375(8, true, 1), 10)
);
}
@Test
public void testLfCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\n\r "), 2, 8, 10);
}
@Test
public void testCrLfCrEof() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("abc\r\nxyz\r\n\r"),
p -> assertPosition(p,
2, actualEndOfSection8217375(9, true, 2), 11)
);
}
@Test
public void testCrLfCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r "), 2, 9, 11);
}
@Test
public void testCrLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n"),
2, actualEndOfSection8217375(9, true, 1), 11);
}
@Test
public void testCrLfLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n "), 2, 9, 11);
}
@Test
public void testCrLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n"),
2, actualEndOfSection8217375(9, true, 2), 12);
}
@Test
public void testCrLfCfLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
}
@Test
public void testCrLfCrCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r"), 2, 9, 11);
}
@Test
public void testCrLfCrCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r "), 2, 9, 11);
}
@Test
public void testCrLfLfCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r"), 2, 9, 11);
}
@Test
public void testCrLfLfCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r "), 2, 9, 11);
}
@Test
public void testCrLfCrLfCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r"), 2, 9, 12);
}
@Test
public void testCrLfCfLfCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12);
}
@Test
public void testCrLfCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
}
@Test
public void testCrLfLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\n"), 2, 9, 11);
}
@Test
public void testCrLfLfLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\n "), 2, 9, 11);
}
@Test
public void testCrLfCrLfLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\n "), 2, 9, 12);
}
@Test
public void testCrLfCrCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n"), 2, 9, 11);
}
@Test
public void testCrLfCrCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n "), 2, 9, 11);
}
@Test
public void testCrLfLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n"), 2, 9, 11);
}
@Test
public void testCrLfLfCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n "), 2, 9, 11);
}
@Test
public void testCrLfCrLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n"), 2, 9, 12);
}
@Test
public void testCrLfCfLfCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12);
}
@Test
public void testCrLfLfCrCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\r"), 2, 9, 11);
}
@Test
public void testCrLfCrLfCrCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r"), 2, 9, 12);
}
@Test
public void testCrLfCrLfCrContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12);
}
@Test
public void testCrLfLfLfCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\n\r"), 2, 9, 11);
}
@Test
public void testCrLfLfCrLfCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r"), 2, 9, 11);
}
@Test
public void testCrLfLfLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\n\n"), 2, 9, 11);
}
@Test
public void testCrLfLfCrLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\n"), 2, 9, 11);
}
@Test
public void testCrLfLfCrCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\r\n"), 2, 9, 11);
}
@Test
public void testCrLfCrLfCrCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r\n"), 2, 9, 12);
}
@Test
public void testCrLfCrLfCrLfContinued() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12);
}
@Test
public void testCrLfLfLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\n\r\n"), 2, 9, 11);
}
@Test
public void testCrLfLfCrLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r\n"), 2, 9, 11);
}
@Test
public void testCrLfCrCrLfCrCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r"), 2, 9, 11);
}
@Test
public void testCrLfCrCrCrCrEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\r"), 2, 9, 11);
}
@Test
public void testCrLfCrCrLfLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\n"), 2, 9, 11);
}
@Test
public void testCrLfCrCrLfCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r\n"), 2, 9, 11);
}
@Test
public void testCrLfCrCrCrLfEof() throws Exception {
assertPosition(findSection("abc\r\nxyz\r\n\r\r\r\n"), 2, 9, 11);
}
/*
* endOfFirstLine is the same regardless of the line break delimiter
*/
@Test
public void testEndOfFirstLineVsLineBreak() throws Exception {
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
Position p = findSection("abc" + lb + "xyz" + lb + lb + " ");
// main assertion showing endOfFirstLine independent of line break
assertEquals(p.endOfFirstLine, 2 + offset);
// assert remaining positions as well just for completeness
assertPosition(p, 2, 5 + 2 * lb.length(), 6 + 3 * lb.length());
}
}
/*
* '\r' at the end of the bytes causes index out of bounds exception
*/
@Test
public void testCrLastCausesIndexOutOfBounds() throws Exception {
catchCrCausesIndexOutOfBoundsException(
() -> findSection("\r"),
p -> assertPosition(p,
-1, actualEndOfSection8217375(-1, true, 1), 1)
);
}
/*
* endOfSection includes second line break if at end of bytes only
*/
@Test
public void testEndOfSectionWithLineBreakVsEof() throws Exception {
AssertionError errors = new AssertionError("offset = " + offset);
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
for (boolean eof : new boolean[] { false, true }) {
Position p;
try {
p = findSection("abc" + lb + lb + (eof ? "" : "xyz"));
} catch (RuntimeException | ReflectiveOperationException e) {
if ((e instanceof IndexOutOfBoundsException ||
e.getCause() instanceof IndexOutOfBoundsException)
&& eof && "\r".equals(lb) && !FIXED_8217375) continue;
throw e;
}
AssertionError a = new AssertionError("offset = " + offset
+ ", lb = " + Utils.escapeStringWithNumbers(lb) + ", "
+ "eof = " + eof);
// main assertion showing endOfSection including second line
// break when at end of file
a = collectErrors(a, () -> assertEquals(
p.endOfSection,
actualEndOfSection8217375(
2 + lb.length() + offset, eof, lb.length()) ));
// assert remaining positions as well just for completeness
a = collectErrors(a, () -> assertPosition(p,
2,
actualEndOfSection8217375(
2 + lb.length(), eof, lb.length()),
3 + lb.length() * 2));
if (a.getSuppressed().length > 0) errors.addSuppressed(a);
}
}
if (errors.getSuppressed().length > 0) throw errors;
}
/*
* returns position even if only one line break before end of bytes.
* because no name will be found the result will be skipped and no entry
* will be created.
*/
@Test
public void testReturnPosVsEof() throws Exception {
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
for (boolean eof : new boolean[] { false, true }) {
try {
Position p = findSection("abc" + lb + (eof ? "" : "xyz"));
assertTrue(p != null == eof);
} catch (RuntimeException | ReflectiveOperationException e) {
if ((e instanceof IndexOutOfBoundsException ||
e.getCause() instanceof IndexOutOfBoundsException)
&& eof && "\r".equals(lb) && !FIXED_8217375) continue;
throw e;
}
}
}
}
/*
* it could be normally be expected that startOfNext would point to the
* start of the next section after a blank line but that is not the case
* if a section ends with only one line break and no blank line immediately
* before eof of the manifest.
* such an entry will be digested without the trailing blank line which is
* only fine until another section should be added afterwards.
*/
@Test
public void testStartOfNextPointsToEofWithNoBlankLine() throws Exception {
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
for (boolean blank : new boolean[] { false, true }) {
String manifest = "abc" + lb + "xyz" + lb + (blank ? lb : "");
try {
Position p = findSection(manifest);
// assert that startOfNext points to eof in all cases
// whether with or without a blank line before eof
assertEquals(p.startOfNext, manifest.length() + offset);
// assert remaining positions as well just for completeness
assertPosition(p,
2,
actualEndOfSection8217375(
5 + lb.length() * 2,
true,
blank ? lb.length() : 0),
manifest.length());
} catch (RuntimeException | ReflectiveOperationException e) {
if ((e instanceof IndexOutOfBoundsException ||
e.getCause() instanceof IndexOutOfBoundsException)
&& "\r".equals(lb) && !FIXED_8217375) continue;
throw e;
}
}
}
}
}