This is an automated email from the ASF dual-hosted git repository. bodewig pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
The following commit(s) were added to refs/heads/master by this push: new 488425c only try to recover from broken 7z archive if explicitly asked to 488425c is described below commit 488425c1b9fb8c8d0f1ef1ce7d665058880870e2 Author: Stefan Bodewig <bode...@apache.org> AuthorDate: Sun Jun 27 22:06:22 2021 +0200 only try to recover from broken 7z archive if explicitly asked to --- src/changes/changes.xml | 4 +++ .../compress/archivers/sevenz/SevenZFile.java | 7 +++- .../archivers/sevenz/SevenZFileOptions.java | 42 ++++++++++++++++++++-- .../compress/archivers/sevenz/SevenZFileTest.java | 26 +++++++++++--- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6225284..9b6cdfb 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -191,6 +191,10 @@ The <action> type attribute can be add,update,fix,remove. Also added sanity checks before even trying to parse an archive and made SevenZFileOptions' maxMemorySizeInKb apply to the stored metadata for an archive. + + And further added an option that needs to be enabled in order + to make SevenZFile try to recover a broken archive. This is a + backwards incompatible change. </action> <action issue="COMPRESS-546" type="fix" date="2020-08-15" due-to="Maksim Zuev" dev="PeterLee"> diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java index 967d6e9..fde329e 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java @@ -474,7 +474,12 @@ public class SevenZFile implements Closeable { return initializeArchive(startHeader, password, true); } // No valid header found - probably first file of multipart archive was removed too early. Scan for end header. - return tryToLocateEndHeader(password); + if (options.getTryToRecoverBrokenArchives()) { + return tryToLocateEndHeader(password); + } + throw new IOException("archive seems to be invalid.\nYou may want to retry and enable the" + + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed" + + " prematurely."); } private Archive tryToLocateEndHeader(final byte[] password) throws IOException { diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFileOptions.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFileOptions.java index ad920b5..d886091 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFileOptions.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFileOptions.java @@ -26,13 +26,17 @@ package org.apache.commons.compress.archivers.sevenz; public class SevenZFileOptions { private static final int DEFAUL_MEMORY_LIMIT_IN_KB = Integer.MAX_VALUE; private static final boolean DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES= false; + private static final boolean DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES = false; private final int maxMemoryLimitInKb; private final boolean useDefaultNameForUnnamedEntries; + private final boolean tryToRecoverBrokenArchives; - private SevenZFileOptions(final int maxMemoryLimitInKb, final boolean useDefaultNameForUnnamedEntries) { + private SevenZFileOptions(final int maxMemoryLimitInKb, final boolean useDefaultNameForUnnamedEntries, + final boolean tryToRecoverBrokenArchives) { this.maxMemoryLimitInKb = maxMemoryLimitInKb; this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries; + this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives; } /** @@ -44,7 +48,8 @@ public class SevenZFileOptions { * </ul> */ public static final SevenZFileOptions DEFAULT = new SevenZFileOptions(DEFAUL_MEMORY_LIMIT_IN_KB, - DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES); + DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES, + DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES); /** * Obtains a builder for SevenZFileOptions. @@ -78,6 +83,15 @@ public class SevenZFileOptions { } /** + * Whether {@link SevenZFile} shall try to recover from a certain type of broken archive. + * @return whether SevenZFile shall try to recover from a certain type of broken archive. + * @since 1.21 + */ + public boolean getTryToRecoverBrokenArchives() { + return tryToRecoverBrokenArchives; + } + + /** * Mutable builder for the immutable {@link SevenZFileOptions}. * * @since 1.19 @@ -85,6 +99,8 @@ public class SevenZFileOptions { public static class Builder { private int maxMemoryLimitInKb = DEFAUL_MEMORY_LIMIT_IN_KB; private boolean useDefaultNameForUnnamedEntries = DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES; + private boolean tryToRecoverBrokenArchives = DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES; + /** * Sets the maximum amount of memory to use for parsing the * archive and during extraction. @@ -114,12 +130,32 @@ public class SevenZFileOptions { } /** + * Sets whether {@link SevenZFile} will try to revover broken archives where the CRC of the file's metadata is + * 0. + * + * <p>This special kind of broken archive is encountered when mutli volume archives are closed prematurely. If + * you enable this option SevenZFile will trust data that looks as if it could contain metadata of an archive + * and allocate big amounts of memory. It is strongly recommended to not enable this option without setting + * {@link #withMaxMemoryLimitInKb} at the same time. + * + * @param tryToRecoverBrokenArchives if true SevenZFile will try to recover archives that are broken in the + * specific way + * @return the reconfigured builder + * @since 1.21 + */ + public Builder withTryToRecoverBrokenArchives(final boolean tryToRecoverBrokenArchives) { + this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives; + return this; + } + + /** * Create the {@link SevenZFileOptions}. * * @return configured {@link SevenZFileOptions}. */ public SevenZFileOptions build() { - return new SevenZFileOptions(maxMemoryLimitInKb, useDefaultNameForUnnamedEntries); + return new SevenZFileOptions(maxMemoryLimitInKb, useDefaultNameForUnnamedEntries, + tryToRecoverBrokenArchives); } } } diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java index 7dde40a..16ddd78 100644 --- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java @@ -173,7 +173,12 @@ public class SevenZFileTest extends AbstractTestCase { } private void test7zUnarchive(final File f, final SevenZMethod m) throws Exception { - test7zUnarchive(f, m, (char[]) null); + test7zUnarchive(f, m, false); + } + + private void test7zUnarchive(final File f, final SevenZMethod m, boolean tryToRecoverBrokenArchives) + throws Exception { + test7zUnarchive(f, m, (char[]) null, tryToRecoverBrokenArchives); } @Test @@ -413,11 +418,17 @@ public class SevenZFileTest extends AbstractTestCase { } @Test - public void test7zUnarchiveWithDefectHeader() throws Exception { + public void test7zUnarchiveWithDefectHeaderFailsByDefault() throws Exception { + thrown.expect(IOException.class); test7zUnarchive(getFile("bla.noendheaderoffset.7z"), SevenZMethod.LZMA); } @Test + public void test7zUnarchiveWithDefectHeader() throws Exception { + test7zUnarchive(getFile("bla.noendheaderoffset.7z"), SevenZMethod.LZMA, true); + } + + @Test public void extractSpecifiedFile() throws Exception { try (SevenZFile sevenZFile = new SevenZFile(getFile("COMPRESS-256.7z"))) { final String testTxtContents = "111111111111111111111111111000101011\n" + @@ -725,7 +736,8 @@ public class SevenZFileTest extends AbstractTestCase { testFiles.add(getPath("COMPRESS-542-endheadercorrupted2.7z")); for (final Path file : testFiles) { - try (SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(file))) { + try (SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(file), + SevenZFileOptions.builder().withTryToRecoverBrokenArchives(true).build())) { fail("Expected IOException: start header corrupt and unable to guess end header"); } catch (final IOException e) { assertEquals("Start header corrupt and unable to guess end header", e.getMessage()); @@ -751,7 +763,13 @@ public class SevenZFileTest extends AbstractTestCase { } private void test7zUnarchive(final File f, final SevenZMethod m, final char[] password) throws Exception { - try (SevenZFile sevenZFile = new SevenZFile(f, password)) { + test7zUnarchive(f, m, password, false); + } + + private void test7zUnarchive(final File f, final SevenZMethod m, final char[] password, + final boolean tryToRecoverBrokenArchives) throws Exception { + try (SevenZFile sevenZFile = new SevenZFile(f, password, + SevenZFileOptions.builder().withTryToRecoverBrokenArchives(tryToRecoverBrokenArchives).build())) { test7zUnarchive(sevenZFile, m); } }