COMPRESS-385, add ArchiveStreamFactory; undo boneheaded RELEASE-NOTES.txt fiasco.
Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/2cf57511 Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/2cf57511 Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/2cf57511 Branch: refs/heads/master Commit: 2cf575115748e9865daaa369507019141573b86c Parents: 30fce9d Author: tballison <talli...@mitre.org> Authored: Fri Apr 14 12:35:01 2017 -0400 Committer: Stefan Bodewig <bode...@apache.org> Committed: Tue Apr 18 14:40:25 2017 +0200 ---------------------------------------------------------------------- RELEASE-NOTES.txt | 12 -- src/changes/changes.xml | 4 + .../archivers/ArchiveStreamFactory.java | 110 +++++++++++-------- .../compressors/CompressorStreamFactory.java | 2 +- .../commons/compress/MockEvilInputStream.java | 39 +++++++ .../archivers/ArchiveStreamFactoryTest.java | 44 ++++++++ .../compressors/DetectCompressorTestCase.java | 21 +--- 7 files changed, 158 insertions(+), 74 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/RELEASE-NOTES.txt ---------------------------------------------------------------------- diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index b16a4ea..21c9c4c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,18 +5,6 @@ compression and archive formats. These include: bzip2, gzip, pack200, lzma, xz, Snappy, traditional Unix Compress, DEFLATE and ar, cpio, jar, tar, zip, dump, 7z, arj. -Release 1.14 ------------- - -Commons Compress 1.13 is the first version to require Java 7 at -runtime. - -Changes in this version include: - -New features: -o detect(InputStream) now available in CompressorStreamFactory - Issue: Compress-385. - Release 1.13 ------------ http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 44d57f0..f1ac318 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -44,6 +44,10 @@ The <action> type attribute can be add,update,fix,remove. <body> <release version="1.14" date="not released, yet" description="Release 1.14"> + <action issue="COMPRESS-385" type="add" date="2017-04-14"> + Add static detect(InputStream in) to CompressorStreamFactory + and ArchiveStreamFactory + </action> <action issue="COMPRESS-378" type="fix" date="2017-01-09"> SnappyCompressorInputStream slides the window too early leading to ArrayIndexOutOfBoundsExceptions for some streams. http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java index 6ca0437..cc51458 100644 --- a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java +++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java @@ -473,6 +473,16 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider { */ public ArchiveInputStream createArchiveInputStream(final InputStream in) throws ArchiveException { + return createArchiveInputStream(detect(in), in); + } + + /** + * Try to determine the type of Archiver + * @param in input stream + * @return type of archiver if found + * @throws ArchiveException if an archiver cannot be detected in the stream + */ + public static String detect(InputStream in) throws ArchiveException { if (in == null) { throw new IllegalArgumentException("Stream must not be null."); } @@ -483,62 +493,72 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider { final byte[] signature = new byte[SIGNATURE_SIZE]; in.mark(signature.length); + int signatureLength = -1; try { - int signatureLength = IOUtils.readFully(in, signature); + signatureLength = IOUtils.readFully(in, signature); in.reset(); - if (ZipArchiveInputStream.matches(signature, signatureLength)) { - return createArchiveInputStream(ZIP, in); - } else if (JarArchiveInputStream.matches(signature, signatureLength)) { - return createArchiveInputStream(JAR, in); - } else if (ArArchiveInputStream.matches(signature, signatureLength)) { - return createArchiveInputStream(AR, in); - } else if (CpioArchiveInputStream.matches(signature, signatureLength)) { - return createArchiveInputStream(CPIO, in); - } else if (ArjArchiveInputStream.matches(signature, signatureLength)) { - return createArchiveInputStream(ARJ, in); - } else if (SevenZFile.matches(signature, signatureLength)) { - throw new StreamingNotSupportedException(SEVEN_Z); - } + } catch (IOException e) { + throw new ArchiveException("IOException while reading signature."); + } - // Dump needs a bigger buffer to check the signature; - final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE]; - in.mark(dumpsig.length); + if (JarArchiveInputStream.matches(signature, signatureLength)) { + return JAR; + } else if (ZipArchiveInputStream.matches(signature, signatureLength)) { + return ZIP; + } else if (ArArchiveInputStream.matches(signature, signatureLength)) { + return AR; + } else if (CpioArchiveInputStream.matches(signature, signatureLength)) { + return CPIO; + } else if (ArjArchiveInputStream.matches(signature, signatureLength)) { + return ARJ; + } else if (SevenZFile.matches(signature, signatureLength)) { + throw new StreamingNotSupportedException(SEVEN_Z); + } + + // Dump needs a bigger buffer to check the signature; + final byte[] dumpsig = new byte[DUMP_SIGNATURE_SIZE]; + in.mark(dumpsig.length); + try { signatureLength = IOUtils.readFully(in, dumpsig); in.reset(); - if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) { - return createArchiveInputStream(DUMP, in); - } + } catch (IOException e) { + throw new ArchiveException("IOException while reading dump signature"); + } + if (DumpArchiveInputStream.matches(dumpsig, signatureLength)) { + return DUMP; + } - // Tar needs an even bigger buffer to check the signature; read the first block - final byte[] tarHeader = new byte[TAR_HEADER_SIZE]; - in.mark(tarHeader.length); + // Tar needs an even bigger buffer to check the signature; read the first block + final byte[] tarHeader = new byte[TAR_HEADER_SIZE]; + in.mark(tarHeader.length); + try { signatureLength = IOUtils.readFully(in, tarHeader); in.reset(); - if (TarArchiveInputStream.matches(tarHeader, signatureLength)) { - return createArchiveInputStream(TAR, in); - } - // COMPRESS-117 - improve auto-recognition - if (signatureLength >= TAR_HEADER_SIZE) { - TarArchiveInputStream tais = null; - try { - tais = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader)); - // COMPRESS-191 - verify the header checksum - if (tais.getNextTarEntry().isCheckSumOK()) { - return createArchiveInputStream(TAR, in); - } - } catch (final Exception e) { // NOPMD - // can generate IllegalArgumentException as well - // as IOException - // autodetection, simply not a TAR - // ignored - } finally { - IOUtils.closeQuietly(tais); + } catch (IOException e) { + throw new ArchiveException("IOException while reading tar signature"); + } + if (TarArchiveInputStream.matches(tarHeader, signatureLength)) { + return TAR; + } + + // COMPRESS-117 - improve auto-recognition + if (signatureLength >= TAR_HEADER_SIZE) { + TarArchiveInputStream tais = null; + try { + tais = new TarArchiveInputStream(new ByteArrayInputStream(tarHeader)); + // COMPRESS-191 - verify the header checksum + if (tais.getNextTarEntry().isCheckSumOK()) { + return TAR; } + } catch (final Exception e) { // NOPMD + // can generate IllegalArgumentException as well + // as IOException + // autodetection, simply not a TAR + // ignored + } finally { + IOUtils.closeQuietly(tais); } - } catch (final IOException e) { - throw new ArchiveException("Could not use reset and mark operations.", e); } - throw new ArchiveException("No Archiver found for the stream signature"); } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java index c347cca..87c1209 100644 --- a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java +++ b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java @@ -400,7 +400,7 @@ public class CompressorStreamFactory implements CompressorStreamProvider { signatureLength = IOUtils.readFully(in, signature); in.reset(); } catch (IOException e) { - throw new CompressorException("Failed while reading signature from InputStream.", e); + throw new CompressorException("IOException while reading signature.", e); } if (BZip2CompressorInputStream.matches(signature, signatureLength)) { http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/test/java/org/apache/commons/compress/MockEvilInputStream.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/MockEvilInputStream.java b/src/test/java/org/apache/commons/compress/MockEvilInputStream.java new file mode 100644 index 0000000..884cf50 --- /dev/null +++ b/src/test/java/org/apache/commons/compress/MockEvilInputStream.java @@ -0,0 +1,39 @@ +package org.apache.commons.compress;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import java.io.IOException; +import java.io.InputStream; + +/** + * Simple mock InputStream that always throws an IOException + * when {@link #read()} or {@link #read(byte[], int, int)} + * is called. + */ +public class MockEvilInputStream extends InputStream { + + @Override + public int read() throws IOException { + throw new IOException("Evil"); + } + + @Override + public int read(byte[] bytes, int offset, int length) throws IOException { + throw new IOException("Evil"); + } +} + http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java b/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java index 4236b28..27e2790 100644 --- a/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/ArchiveStreamFactoryTest.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import org.apache.commons.compress.MockEvilInputStream; import org.apache.commons.compress.archivers.arj.ArjArchiveInputStream; import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream; import org.apache.commons.compress.archivers.dump.DumpArchiveInputStream; @@ -226,6 +227,49 @@ public class ArchiveStreamFactoryTest { DUMP_DEFAULT = dflt; } + @Test + public void testDetect() throws Exception { + for (String extension : new String[]{ + ArchiveStreamFactory.ARJ, + ArchiveStreamFactory.CPIO, + ArchiveStreamFactory.DUMP, + ArchiveStreamFactory.JAR, + ArchiveStreamFactory.TAR, + //TODO-- figure out how to differentiate btwn JAR and ZIP + // ArchiveStreamFactory.ZIP + }) { + assertEquals(extension, detect("bla."+extension)); + } + + try { + ArchiveStreamFactory.detect(new BufferedInputStream(new ByteArrayInputStream(new byte[0]))); + fail("shouldn't be able to detect empty stream"); + } catch (ArchiveException e) { + assertEquals("No Archiver found for the stream signature", e.getMessage()); + } + + try { + ArchiveStreamFactory.detect(null); + fail("shouldn't be able to detect null stream"); + } catch (IllegalArgumentException e) { + assertEquals("Stream must not be null.", e.getMessage()); + } + + try { + ArchiveStreamFactory.detect(new BufferedInputStream(new MockEvilInputStream())); + fail("Expected ArchiveException"); + } catch (ArchiveException e) { + assertEquals("IOException while reading signature.", e.getMessage()); + } + } + + private String detect(String resource) throws IOException, ArchiveException { + try(InputStream in = new BufferedInputStream(new FileInputStream( + getFile(resource)))) { + return ArchiveStreamFactory.detect(in); + } + } + static final TestData[] TESTS = { new TestData("bla.arj", ArchiveStreamFactory.ARJ, false, ARJ_DEFAULT, FACTORY, "charsetName"), new TestData("bla.arj", ArchiveStreamFactory.ARJ, false, "UTF-8", FACTORY_UTF8, "charsetName"), http://git-wip-us.apache.org/repos/asf/commons-compress/blob/2cf57511/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java b/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java index 187d931..8ccf579 100644 --- a/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java +++ b/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java @@ -31,6 +31,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import org.apache.commons.compress.MockEvilInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; @@ -121,6 +122,7 @@ public final class DetectCompressorTestCase { @Test public void testDetect() throws Exception { + assertEquals(CompressorStreamFactory.BZIP2, detect("bla.txt.bz2")); assertEquals(CompressorStreamFactory.GZIP, detect("bla.tgz")); assertEquals(CompressorStreamFactory.PACK200, detect("bla.pack")); @@ -131,7 +133,7 @@ public final class DetectCompressorTestCase { CompressorStreamFactory.detect(new BufferedInputStream(new ByteArrayInputStream(new byte[0]))); fail("shouldn't be able to detect empty stream"); } catch (CompressorException e) { - assertTrue(e.getMessage().contains("No Compressor found")); + assertEquals("No Compressor found for the stream signature.", e.getMessage()); } try { @@ -142,10 +144,10 @@ public final class DetectCompressorTestCase { } try { - CompressorStreamFactory.detect(new BufferedInputStream(new BadInputStream())); + CompressorStreamFactory.detect(new BufferedInputStream(new MockEvilInputStream())); fail("Expected IOException"); } catch (CompressorException e) { - assertEquals("Failed while reading signature from InputStream.", e.getMessage()); + assertEquals("IOException while reading signature.", e.getMessage()); } @@ -216,17 +218,4 @@ public final class DetectCompressorTestCase { new BufferedInputStream(new FileInputStream( getFile(resource)))); } - - private static class BadInputStream extends InputStream { - @Override - public int read() throws IOException { - throw new IOException("Bad"); - } - - @Override - public int read(byte[] bytes, int offset, int length) throws IOException { - throw new IOException("Bad"); - } - } - }