/* * 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
     * if (allBlank || (i == len-1)) {
     *     if (i == len-1)
     *         pos.endOfSection = i;
     *     else
     *         pos.endOfSection = last;
     * 
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. *

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

     * int last = offset;
     * 
* 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}. *

* 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. *

* 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 test, Consumer 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; } } } } }