This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
commit 4766e284ac4be66154f13aa75bbfa83c2f779f68 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Mon Jan 15 11:01:43 2024 -0500 Sort members --- .../zip/FileRandomAccessOutputStream.java | 24 +- .../archivers/zip/RandomAccessOutputStream.java | 24 +- .../SeekableChannelRandomAccessOutputStream.java | 14 +- .../compress/archivers/zip/ZipEncodingHelper.java | 8 +- .../commons/compress/archivers/zip/ZipFile.java | 300 ++++++++++----------- .../archivers/zip/ZipSplitOutputStream.java | 54 ++-- .../zip/ZipSplitReadOnlySeekableByteChannel.java | 22 +- .../apache/commons/compress/archivers/ZipTest.java | 118 ++++---- .../archivers/cpio/CpioArchiveInputStreamTest.java | 16 +- .../archivers/zip/ZipArchiveOutputStreamTest.java | 12 +- .../zip/ZipFileIgnoringLocalFileHeaderTest.java | 8 +- .../FramedSnappyCompressorInputStreamTest.java | 74 ++--- 12 files changed, 337 insertions(+), 337 deletions(-) diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStream.java index 8a431c4b5..6ec8ea9ea 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/FileRandomAccessOutputStream.java @@ -33,6 +33,10 @@ class FileRandomAccessOutputStream extends RandomAccessOutputStream { private long position; + FileRandomAccessOutputStream(final FileChannel channel) throws IOException { + this.channel = channel; + } + FileRandomAccessOutputStream(final Path file) throws IOException { this(file, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); } @@ -41,18 +45,13 @@ class FileRandomAccessOutputStream extends RandomAccessOutputStream { this(FileChannel.open(file, options)); } - FileRandomAccessOutputStream(final FileChannel channel) throws IOException { - this.channel = channel; - } - FileChannel channel() { return channel; } @Override - public synchronized void write(final byte[] b, final int off, final int len) throws IOException { - ZipIoUtil.writeFully(this.channel, ByteBuffer.wrap(b, off, len)); - position += len; + public void close() throws IOException { + channel.close(); } @Override @@ -60,6 +59,12 @@ class FileRandomAccessOutputStream extends RandomAccessOutputStream { return position; } + @Override + public synchronized void write(final byte[] b, final int off, final int len) throws IOException { + ZipIoUtil.writeFully(this.channel, ByteBuffer.wrap(b, off, len)); + position += len; + } + @Override public void writeFullyAt(final byte[] b, final int off, final int len, final long atPosition) throws IOException { ByteBuffer buf = ByteBuffer.wrap(b, off, len); @@ -71,9 +76,4 @@ class FileRandomAccessOutputStream extends RandomAccessOutputStream { currentPos += written; } } - - @Override - public void close() throws IOException { - channel.close(); - } } diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/RandomAccessOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/RandomAccessOutputStream.java index 87bfd5913..d0f0aed4c 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/RandomAccessOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/RandomAccessOutputStream.java @@ -33,6 +33,11 @@ abstract class RandomAccessOutputStream extends OutputStream { */ public abstract long position() throws IOException; + @Override + public void write(final int b) throws IOException { + write(new byte[]{ (byte) b }); + } + /** * Writes given data to specific position. * @@ -40,12 +45,14 @@ abstract class RandomAccessOutputStream extends OutputStream { * position in the stream * @param b * data to write + * @param off + * offset of the start of data in param b + * @param len + * the length of data to write * @throws IOException * when write fails. */ - public void writeFullyAt(final byte[] b, final long position) throws IOException { - writeFullyAt(b, 0, b.length, position); - } + abstract void writeFullyAt(byte[] b, int off, int len, long position) throws IOException; /** * Writes given data to specific position. @@ -54,17 +61,10 @@ abstract class RandomAccessOutputStream extends OutputStream { * position in the stream * @param b * data to write - * @param off - * offset of the start of data in param b - * @param len - * the length of data to write * @throws IOException * when write fails. */ - abstract void writeFullyAt(byte[] b, int off, int len, long position) throws IOException; - - @Override - public void write(final int b) throws IOException { - write(new byte[]{ (byte) b }); + public void writeFullyAt(final byte[] b, final long position) throws IOException { + writeFullyAt(b, 0, b.length, position); } } diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStream.java index accad6879..473e9b2f5 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/SeekableChannelRandomAccessOutputStream.java @@ -35,8 +35,8 @@ class SeekableChannelRandomAccessOutputStream extends RandomAccessOutputStream { } @Override - public synchronized void write(final byte[] b, final int off, final int len) throws IOException { - ZipIoUtil.writeFully(this.channel, ByteBuffer.wrap(b, off, len)); + public synchronized void close() throws IOException { + channel.close(); } @Override @@ -44,6 +44,11 @@ class SeekableChannelRandomAccessOutputStream extends RandomAccessOutputStream { return channel.position(); } + @Override + public synchronized void write(final byte[] b, final int off, final int len) throws IOException { + ZipIoUtil.writeFully(this.channel, ByteBuffer.wrap(b, off, len)); + } + @Override public synchronized void writeFullyAt(final byte[] b, final int off, final int len, final long position) throws IOException { long saved = channel.position(); @@ -54,9 +59,4 @@ class SeekableChannelRandomAccessOutputStream extends RandomAccessOutputStream { channel.position(saved); } } - - @Override - public synchronized void close() throws IOException { - channel.close(); - } } diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipEncodingHelper.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipEncodingHelper.java index c73476ed8..e2b595e33 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipEncodingHelper.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipEncodingHelper.java @@ -90,10 +90,6 @@ public abstract class ZipEncodingHelper { return isUTF8Alias(Charsets.toCharset(charset).name()); } - private static boolean isUTF8Alias(final String actual) { - return UTF_8.name().equalsIgnoreCase(actual) || UTF_8.aliases().stream().anyMatch(alias -> alias.equalsIgnoreCase(actual)); - } - /** * Tests whether a given encoding is UTF-8. If the given name is null, then check the platform's default encoding. * @@ -102,4 +98,8 @@ public abstract class ZipEncodingHelper { static boolean isUTF8(final String charsetName) { return isUTF8Alias(charsetName != null ? charsetName : Charset.defaultCharset().name()); } + + private static boolean isUTF8Alias(final String actual) { + return UTF_8.name().equalsIgnoreCase(actual) || UTF_8.aliases().stream().anyMatch(alias -> alias.equalsIgnoreCase(actual)); + } } diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java index 3337308fd..dedcf0bfb 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java @@ -175,6 +175,19 @@ public class ZipFile implements Closeable { return this; } + /** + * Sets max number of multi archive disks, default is 1 (no multi archive). + * + * @param maxNumberOfDisks + * max number of multi archive disks + * + * @return this instance + */ + public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) { + this.maxNumberOfDisks = maxNumberOfDisks; + return this; + } + /** * The actual channel, overrides any other input aspects like a File, Path, and so on. * @@ -197,19 +210,6 @@ public class ZipFile implements Closeable { return this; } - /** - * Sets max number of multi archive disks, default is 1 (no multi archive). - * - * @param maxNumberOfDisks - * max number of multi archive disks - * - * @return this instance - */ - public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) { - this.maxNumberOfDisks = maxNumberOfDisks; - return this; - } - } /** @@ -497,6 +497,142 @@ public class ZipFile implements Closeable { return Files.newByteChannel(path, READ); } + private static SeekableByteChannel openZipChannel(Path path, long maxNumberOfDisks, OpenOption[] openOptions) throws IOException { + FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); + List<FileChannel> channels = new ArrayList<>(); + try { + boolean is64 = positionAtEndOfCentralDirectoryRecord(channel); + long numberOfDisks; + if (is64) { + channel.position(channel.position() + ZipConstants.WORD + ZipConstants.WORD + ZipConstants.DWORD); + ByteBuffer buf = ByteBuffer.allocate(ZipConstants.WORD); + buf.order(ByteOrder.LITTLE_ENDIAN); + IOUtils.readFully(channel, buf); + buf.flip(); + numberOfDisks = buf.getInt() & 0xffffffffL; + } else { + channel.position(channel.position() + ZipConstants.WORD); + ByteBuffer buf = ByteBuffer.allocate(ZipConstants.SHORT); + buf.order(ByteOrder.LITTLE_ENDIAN); + IOUtils.readFully(channel, buf); + buf.flip(); + numberOfDisks = (buf.getShort() & 0xffff) + 1; + } + if (numberOfDisks > Math.min(maxNumberOfDisks, Integer.MAX_VALUE)) { + throw new IOException("Too many disks for zip archive, max=" + + Math.min(maxNumberOfDisks, Integer.MAX_VALUE) + " actual=" + numberOfDisks); + } + + if (numberOfDisks <= 1) { + return channel; + } + channel.close(); + + Path parent = path.getParent(); + String basename = FilenameUtils.removeExtension(path.getFileName().toString()); + + return ZipSplitReadOnlySeekableByteChannel.forPaths( + IntStream.range(0, (int) numberOfDisks) + .mapToObj(i -> { + if (i == numberOfDisks - 1) { + return path; + } + Path lowercase = parent.resolve(String.format("%s.z%02d", basename, i + 1)); + if (Files.exists(lowercase)) { + return lowercase; + } + Path uppercase = parent.resolve(String.format("%s.Z%02d", basename, i + 1)); + if (Files.exists(uppercase)) { + return uppercase; + } + return lowercase; + }) + .collect(Collectors.toList()), + openOptions + ); + } catch (Throwable ex) { + IOUtils.closeQuietly(channel); + channels.forEach(IOUtils::closeQuietly); + throw ex; + } + } + + /** + * Searches for the and positions the stream at the start of the "End of central dir record". + * + * @return + * true if it's Zip64 end of central directory or false if it's Zip32 + */ + private static boolean positionAtEndOfCentralDirectoryRecord(SeekableByteChannel channel) throws IOException { + final boolean found = tryToLocateSignature(channel, MIN_EOCD_SIZE, MAX_EOCD_SIZE, ZipArchiveOutputStream.EOCD_SIG); + if (!found) { + throw new ZipException("Archive is not a ZIP archive"); + } + boolean found64 = false; + long position = channel.position(); + if (position > ZIP64_EOCDL_LENGTH) { + ByteBuffer wordBuf = ByteBuffer.allocate(4); + channel.position(channel.position() - ZIP64_EOCDL_LENGTH); + wordBuf.rewind(); + IOUtils.readFully(channel, wordBuf); + wordBuf.flip(); + found64 = wordBuf.equals(ByteBuffer.wrap(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG)); + if (!found64) { + channel.position(position); + } else { + channel.position(channel.position() - ZipConstants.WORD); + } + } + + return found64; + } + + /** + * Searches the archive backwards from minDistance to maxDistance for the given signature, positions the RandomaccessFile right at the signature if it has + * been found. + */ + private static boolean tryToLocateSignature( + final SeekableByteChannel channel, + final long minDistanceFromEnd, + final long maxDistanceFromEnd, + final byte[] sig + ) throws IOException { + ByteBuffer wordBuf = ByteBuffer.allocate(ZipConstants.WORD); + boolean found = false; + long off = channel.size() - minDistanceFromEnd; + final long stopSearching = Math.max(0L, channel.size() - maxDistanceFromEnd); + if (off >= 0) { + for (; off >= stopSearching; off--) { + channel.position(off); + try { + wordBuf.rewind(); + IOUtils.readFully(channel, wordBuf); + wordBuf.flip(); + } catch (final EOFException ex) { // NOSONAR + break; + } + int curr = wordBuf.get(); + if (curr == sig[POS_0]) { + curr = wordBuf.get(); + if (curr == sig[POS_1]) { + curr = wordBuf.get(); + if (curr == sig[POS_2]) { + curr = wordBuf.get(); + if (curr == sig[POS_3]) { + found = true; + break; + } + } + } + } + } + } + if (found) { + channel.position(off); + } + return found; + } + /** * List of entries in the order they appear inside the central directory. */ @@ -558,13 +694,13 @@ public class ZipFile implements Closeable { private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf); + private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset; private long centralDirectoryStartOffset; private long firstLocalFileHeaderOffset; - /** * Opens the given file for reading, assuming "UTF8" for file names. * @@ -832,66 +968,6 @@ public class ZipFile implements Closeable { this(new File(name).toPath(), encoding, true); } - private static SeekableByteChannel openZipChannel(Path path, long maxNumberOfDisks, OpenOption[] openOptions) throws IOException { - FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); - List<FileChannel> channels = new ArrayList<>(); - try { - boolean is64 = positionAtEndOfCentralDirectoryRecord(channel); - long numberOfDisks; - if (is64) { - channel.position(channel.position() + ZipConstants.WORD + ZipConstants.WORD + ZipConstants.DWORD); - ByteBuffer buf = ByteBuffer.allocate(ZipConstants.WORD); - buf.order(ByteOrder.LITTLE_ENDIAN); - IOUtils.readFully(channel, buf); - buf.flip(); - numberOfDisks = buf.getInt() & 0xffffffffL; - } else { - channel.position(channel.position() + ZipConstants.WORD); - ByteBuffer buf = ByteBuffer.allocate(ZipConstants.SHORT); - buf.order(ByteOrder.LITTLE_ENDIAN); - IOUtils.readFully(channel, buf); - buf.flip(); - numberOfDisks = (buf.getShort() & 0xffff) + 1; - } - if (numberOfDisks > Math.min(maxNumberOfDisks, Integer.MAX_VALUE)) { - throw new IOException("Too many disks for zip archive, max=" + - Math.min(maxNumberOfDisks, Integer.MAX_VALUE) + " actual=" + numberOfDisks); - } - - if (numberOfDisks <= 1) { - return channel; - } - channel.close(); - - Path parent = path.getParent(); - String basename = FilenameUtils.removeExtension(path.getFileName().toString()); - - return ZipSplitReadOnlySeekableByteChannel.forPaths( - IntStream.range(0, (int) numberOfDisks) - .mapToObj(i -> { - if (i == numberOfDisks - 1) { - return path; - } - Path lowercase = parent.resolve(String.format("%s.z%02d", basename, i + 1)); - if (Files.exists(lowercase)) { - return lowercase; - } - Path uppercase = parent.resolve(String.format("%s.Z%02d", basename, i + 1)); - if (Files.exists(uppercase)) { - return uppercase; - } - return lowercase; - }) - .collect(Collectors.toList()), - openOptions - ); - } catch (Throwable ex) { - IOUtils.closeQuietly(channel); - channels.forEach(IOUtils::closeQuietly); - throw ex; - } - } - /** * Whether this class is able to read the given entry. * <p> @@ -1327,36 +1403,6 @@ public class ZipFile implements Closeable { } } - /** - * Searches for the and positions the stream at the start of the "End of central dir record". - * - * @return - * true if it's Zip64 end of central directory or false if it's Zip32 - */ - private static boolean positionAtEndOfCentralDirectoryRecord(SeekableByteChannel channel) throws IOException { - final boolean found = tryToLocateSignature(channel, MIN_EOCD_SIZE, MAX_EOCD_SIZE, ZipArchiveOutputStream.EOCD_SIG); - if (!found) { - throw new ZipException("Archive is not a ZIP archive"); - } - boolean found64 = false; - long position = channel.position(); - if (position > ZIP64_EOCDL_LENGTH) { - ByteBuffer wordBuf = ByteBuffer.allocate(4); - channel.position(channel.position() - ZIP64_EOCDL_LENGTH); - wordBuf.rewind(); - IOUtils.readFully(channel, wordBuf); - wordBuf.flip(); - found64 = wordBuf.equals(ByteBuffer.wrap(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG)); - if (!found64) { - channel.position(position); - } else { - channel.position(channel.position() - ZipConstants.WORD); - } - } - - return found64; - } - /** * Reads an individual entry of the central directory, creates an ZipArchiveEntry from it and adds it to the global maps. * @@ -1637,50 +1683,4 @@ public class ZipFile implements Closeable { IOUtils.readFully(archive, wordBbuf); return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG); } - - /** - * Searches the archive backwards from minDistance to maxDistance for the given signature, positions the RandomaccessFile right at the signature if it has - * been found. - */ - private static boolean tryToLocateSignature( - final SeekableByteChannel channel, - final long minDistanceFromEnd, - final long maxDistanceFromEnd, - final byte[] sig - ) throws IOException { - ByteBuffer wordBuf = ByteBuffer.allocate(ZipConstants.WORD); - boolean found = false; - long off = channel.size() - minDistanceFromEnd; - final long stopSearching = Math.max(0L, channel.size() - maxDistanceFromEnd); - if (off >= 0) { - for (; off >= stopSearching; off--) { - channel.position(off); - try { - wordBuf.rewind(); - IOUtils.readFully(channel, wordBuf); - wordBuf.flip(); - } catch (final EOFException ex) { // NOSONAR - break; - } - int curr = wordBuf.get(); - if (curr == sig[POS_0]) { - curr = wordBuf.get(); - if (curr == sig[POS_1]) { - curr = wordBuf.get(); - if (curr == sig[POS_2]) { - curr = wordBuf.get(); - if (curr == sig[POS_3]) { - found = true; - break; - } - } - } - } - } - } - if (found) { - channel.position(off); - } - return found; - } } diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java index 8d05ecfe5..a8a7a7938 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitOutputStream.java @@ -99,6 +99,13 @@ final class ZipSplitOutputStream extends RandomAccessOutputStream { writeZipSplitSignature(); } + public long calculateDiskPosition(long disk, long localOffset) throws IOException { + if (disk >= Integer.MAX_VALUE) { + throw new IOException("Disk number exceeded internal limits: limit=" + Integer.MAX_VALUE + " requested=" + disk); + } + return diskToPosition.get((int) disk) + localOffset; + } + @Override public void close() throws IOException { if (!finished) { @@ -130,23 +137,6 @@ final class ZipSplitOutputStream extends RandomAccessOutputStream { return newFile; } - private Path getSplitSegmentFilename(final Integer zipSplitSegmentSuffixIndex) throws IOException { - final int newZipSplitSegmentSuffixIndex = zipSplitSegmentSuffixIndex == null ? currentSplitSegmentIndex + 2 : zipSplitSegmentSuffixIndex; - final String baseName = FileNameUtils.getBaseName(zipFile); - String extension = ".z"; - if (newZipSplitSegmentSuffixIndex <= 9) { - extension += "0" + newZipSplitSegmentSuffixIndex; - } else { - extension += newZipSplitSegmentSuffixIndex; - } - - final Path parent = zipFile.getParent(); - final String dir = Objects.nonNull(parent) ? parent.toAbsolutePath().toString() : "."; - final Path newFile = zipFile.getFileSystem().getPath(dir, baseName + extension); - - return newFile; - } - /** * The last ZIP split segment's suffix should be .zip @@ -172,6 +162,23 @@ final class ZipSplitOutputStream extends RandomAccessOutputStream { return currentSplitSegmentIndex; } + private Path getSplitSegmentFilename(final Integer zipSplitSegmentSuffixIndex) throws IOException { + final int newZipSplitSegmentSuffixIndex = zipSplitSegmentSuffixIndex == null ? currentSplitSegmentIndex + 2 : zipSplitSegmentSuffixIndex; + final String baseName = FileNameUtils.getBaseName(zipFile); + String extension = ".z"; + if (newZipSplitSegmentSuffixIndex <= 9) { + extension += "0" + newZipSplitSegmentSuffixIndex; + } else { + extension += newZipSplitSegmentSuffixIndex; + } + + final Path parent = zipFile.getParent(); + final String dir = Objects.nonNull(parent) ? parent.toAbsolutePath().toString() : "."; + final Path newFile = zipFile.getFileSystem().getPath(dir, baseName + extension); + + return newFile; + } + /** * Creates a new ZIP split segment and prepare to write to the new segment * @@ -198,11 +205,9 @@ final class ZipSplitOutputStream extends RandomAccessOutputStream { this.positionToFiles.put(this.totalPosition, newFile); } - public long calculateDiskPosition(long disk, long localOffset) throws IOException { - if (disk >= Integer.MAX_VALUE) { - throw new IOException("Disk number exceeded internal limits: limit=" + Integer.MAX_VALUE + " requested=" + disk); - } - return diskToPosition.get((int) disk) + localOffset; + @Override + public long position() { + return totalPosition; } /** @@ -266,11 +271,6 @@ final class ZipSplitOutputStream extends RandomAccessOutputStream { write(singleByte); } - @Override - public long position() { - return totalPosition; - } - @Override public void writeFullyAt(final byte[] b, final int off, final int len, final long atPosition) throws IOException { long remainingPosition = atPosition; diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java index 20d7b8bed..14913ea7b 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipSplitReadOnlySeekableByteChannel.java @@ -203,8 +203,15 @@ public class ZipSplitReadOnlySeekableByteChannel extends MultiReadOnlySeekableBy * @throws IOException if the first channel doesn't seem to hold the beginning of a split archive * @since 1.22 */ - public static SeekableByteChannel forPaths(final Path... paths) throws IOException { - return forPaths(Arrays.asList(paths), new OpenOption[]{ StandardOpenOption.READ }); + public static SeekableByteChannel forPaths(final List<Path> paths, OpenOption[] openOptions) throws IOException { + final List<SeekableByteChannel> channels = new ArrayList<>(); + for (final Path path : Objects.requireNonNull(paths, "paths must not be null")) { + channels.add(Files.newByteChannel(path, openOptions)); + } + if (channels.size() == 1) { + return channels.get(0); + } + return new ZipSplitReadOnlySeekableByteChannel(channels); } /** @@ -218,15 +225,8 @@ public class ZipSplitReadOnlySeekableByteChannel extends MultiReadOnlySeekableBy * @throws IOException if the first channel doesn't seem to hold the beginning of a split archive * @since 1.22 */ - public static SeekableByteChannel forPaths(final List<Path> paths, OpenOption[] openOptions) throws IOException { - final List<SeekableByteChannel> channels = new ArrayList<>(); - for (final Path path : Objects.requireNonNull(paths, "paths must not be null")) { - channels.add(Files.newByteChannel(path, openOptions)); - } - if (channels.size() == 1) { - return channels.get(0); - } - return new ZipSplitReadOnlySeekableByteChannel(channels); + public static SeekableByteChannel forPaths(final Path... paths) throws IOException { + return forPaths(Arrays.asList(paths), new OpenOption[]{ StandardOpenOption.READ }); } /** diff --git a/src/test/java/org/apache/commons/compress/archivers/ZipTest.java b/src/test/java/org/apache/commons/compress/archivers/ZipTest.java index c48e7dcd5..76c145c47 100644 --- a/src/test/java/org/apache/commons/compress/archivers/ZipTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/ZipTest.java @@ -136,14 +136,6 @@ public final class ZipTest extends AbstractTest { return result; } - private byte[] createArtificialData(int size) { - final ByteArrayOutputStream output = new ByteArrayOutputStream(); - for (int i = 0; i < size; i += 1) { - output.write((byte) ((i & 1) == 0 ? (i / 2 % 256) : (i / 2 / 256))); - } - return output.toByteArray(); - } - private void createArchiveEntry(final String payload, final ZipArchiveOutputStream zos, final String name) throws IOException { final ZipArchiveEntry in = new ZipArchiveEntry(name); zos.putArchiveEntry(in); @@ -152,6 +144,14 @@ public final class ZipTest extends AbstractTest { zos.closeArchiveEntry(); } + private byte[] createArtificialData(int size) { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + for (int i = 0; i < size; i += 1) { + output.write((byte) ((i & 1) == 0 ? (i / 2 % 256) : (i / 2 / 256))); + } + return output.toByteArray(); + } + private ZipArchiveOutputStream createFirstEntry(final ZipArchiveOutputStream zos) throws IOException { createArchiveEntry(first_payload, zos, "file1.txt"); return zos; @@ -233,32 +233,6 @@ public final class ZipTest extends AbstractTest { list.add(Arrays.asList(t, b)); } - @Test - public void testBuildSplitZipTest() throws IOException { - final File directoryToZip = getFilesToZip(); - createTestSplitZipSegments(); - - final File lastFile = newTempFile("splitZip.zip"); - try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile); - InputStream inputStream = Channels.newInputStream(channel); - ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, UTF_8.toString(), true, false, true)) { - - ArchiveEntry entry; - final int filesNum = countNonDirectories(directoryToZip); - int filesCount = 0; - while ((entry = splitInputStream.getNextEntry()) != null) { - if (entry.isDirectory()) { - continue; - } - // compare all files one by one - assertArrayEquals(IOUtils.toByteArray(splitInputStream), Files.readAllBytes(Paths.get(entry.getName()))); - filesCount++; - } - // and the number of files should equal - assertEquals(filesCount, filesNum); - } - } - /** * Tests split archive with 32-bit limit, both STORED and DEFLATED. */ @@ -322,6 +296,37 @@ public final class ZipTest extends AbstractTest { } } + /** + * Tests split archive with 32-bit limit, with end of central directory skipping lack of space in segment. + */ + @Test + public void testBuildSplitZip32_endOfCentralDirectorySkipBoundary() throws IOException { + final File outputZipFile = newTempFile("artificialSplitZip.zip"); + final long splitSize = 64 * 1024L; /* 64 KB */ + // 4 is PK signature, 36 is size of header + local file header, + // 36 is length of central directory entry + // 1 is remaining byte in first archive, this should be skipped + byte[] data1 = createArtificialData(64 * 1024 - 4 - 36 - 52 - 1); + try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) { + zipArchiveOutputStream.setUseZip64(Zip64Mode.Never); + ZipArchiveEntry ze1 = new ZipArchiveEntry("file01"); + ze1.setMethod(ZipEntry.STORED); + zipArchiveOutputStream.putArchiveEntry(ze1); + zipArchiveOutputStream.write(data1); + zipArchiveOutputStream.closeArchiveEntry(); + } + + assertEquals(64 * 1024L - 1, Files.size(outputZipFile.toPath().getParent().resolve("artificialSplitZip.z01"))); + + try (ZipFile zipFile = ZipFile.builder() + .setPath(outputZipFile.toPath()) + .setMaxNumberOfDisks(Integer.MAX_VALUE) + .get() + ) { + assertArrayEquals(data1, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01")))); + } + } + /** * Tests split archive with 32-bit limit, with file local headers crossing segment boundaries. */ @@ -377,34 +382,29 @@ public final class ZipTest extends AbstractTest { } } - /** - * Tests split archive with 32-bit limit, with end of central directory skipping lack of space in segment. - */ @Test - public void testBuildSplitZip32_endOfCentralDirectorySkipBoundary() throws IOException { - final File outputZipFile = newTempFile("artificialSplitZip.zip"); - final long splitSize = 64 * 1024L; /* 64 KB */ - // 4 is PK signature, 36 is size of header + local file header, - // 36 is length of central directory entry - // 1 is remaining byte in first archive, this should be skipped - byte[] data1 = createArtificialData(64 * 1024 - 4 - 36 - 52 - 1); - try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile, splitSize)) { - zipArchiveOutputStream.setUseZip64(Zip64Mode.Never); - ZipArchiveEntry ze1 = new ZipArchiveEntry("file01"); - ze1.setMethod(ZipEntry.STORED); - zipArchiveOutputStream.putArchiveEntry(ze1); - zipArchiveOutputStream.write(data1); - zipArchiveOutputStream.closeArchiveEntry(); - } + public void testBuildSplitZipTest() throws IOException { + final File directoryToZip = getFilesToZip(); + createTestSplitZipSegments(); - assertEquals(64 * 1024L - 1, Files.size(outputZipFile.toPath().getParent().resolve("artificialSplitZip.z01"))); + final File lastFile = newTempFile("splitZip.zip"); + try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile); + InputStream inputStream = Channels.newInputStream(channel); + ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream, UTF_8.toString(), true, false, true)) { - try (ZipFile zipFile = ZipFile.builder() - .setPath(outputZipFile.toPath()) - .setMaxNumberOfDisks(Integer.MAX_VALUE) - .get() - ) { - assertArrayEquals(data1, IOUtils.toByteArray(zipFile.getInputStream(zipFile.getEntry("file01")))); + ArchiveEntry entry; + final int filesNum = countNonDirectories(directoryToZip); + int filesCount = 0; + while ((entry = splitInputStream.getNextEntry()) != null) { + if (entry.isDirectory()) { + continue; + } + // compare all files one by one + assertArrayEquals(IOUtils.toByteArray(splitInputStream), Files.readAllBytes(Paths.get(entry.getName()))); + filesCount++; + } + // and the number of files should equal + assertEquals(filesCount, filesNum); } } diff --git a/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java index 6fb2ed6ef..6da5068fc 100644 --- a/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStreamTest.java @@ -84,6 +84,14 @@ public class CpioArchiveInputStreamTest extends AbstractTest { assertEquals(2, count); } + @Test + public void testInvalidLongValueInMetadata() throws Exception { + try (InputStream in = newInputStream("org/apache/commons/compress/cpio/bad_long_value.cpio"); + CpioArchiveInputStream archive = new CpioArchiveInputStream(in)) { + assertThrows(IOException.class, archive::getNextEntry); + } + } + @Test public void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws Exception { final byte[] buf = new byte[2]; @@ -107,12 +115,4 @@ public class CpioArchiveInputStreamTest extends AbstractTest { } } - @Test - public void testInvalidLongValueInMetadata() throws Exception { - try (InputStream in = newInputStream("org/apache/commons/compress/cpio/bad_long_value.cpio"); - CpioArchiveInputStream archive = new CpioArchiveInputStream(in)) { - assertThrows(IOException.class, archive::getNextEntry); - } - } - } diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStreamTest.java index 0c3fb4899..0a121b51b 100644 --- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStreamTest.java @@ -29,16 +29,16 @@ import org.junit.jupiter.api.Test; public class ZipArchiveOutputStreamTest extends AbstractTempDirTest { @Test - public void testOutputStreamBasics() throws IOException { - try (ZipArchiveOutputStream stream = new ZipArchiveOutputStream(new ByteArrayOutputStream())) { - assertFalse(stream.isSeekable()); + public void testFileBasics() throws IOException { + try (ZipArchiveOutputStream stream = new ZipArchiveOutputStream(createTempFile())) { + assertTrue(stream.isSeekable()); } } @Test - public void testFileBasics() throws IOException { - try (ZipArchiveOutputStream stream = new ZipArchiveOutputStream(createTempFile())) { - assertTrue(stream.isSeekable()); + public void testOutputStreamBasics() throws IOException { + try (ZipArchiveOutputStream stream = new ZipArchiveOutputStream(new ByteArrayOutputStream())) { + assertFalse(stream.isSeekable()); } } } diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java index 8c7b0fd9f..74234c542 100644 --- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileIgnoringLocalFileHeaderTest.java @@ -33,10 +33,6 @@ import org.junit.jupiter.api.io.TempDir; public class ZipFileIgnoringLocalFileHeaderTest { - private static ZipFile openZipWithoutLocalFileHeaderDeprecated(final String fileName) throws IOException { - return new ZipFile(AbstractTest.getFile(fileName), CharsetNames.UTF_8, true, true); - } - private static ZipFile openZipWithoutLocalFileHeader(final String fileName) throws IOException { // @formatter:off return ZipFile.builder() @@ -48,6 +44,10 @@ public class ZipFileIgnoringLocalFileHeaderTest { // @formatter:on } + private static ZipFile openZipWithoutLocalFileHeaderDeprecated(final String fileName) throws IOException { + return new ZipFile(AbstractTest.getFile(fileName), CharsetNames.UTF_8, true, true); + } + @TempDir private File dir; diff --git a/src/test/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStreamTest.java index 27f178fec..5c9a629ad 100644 --- a/src/test/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStreamTest.java @@ -40,6 +40,15 @@ import org.junit.jupiter.api.Test; public final class FramedSnappyCompressorInputStreamTest extends AbstractTest { + private static byte[] generateTestData(final int inputSize) { + final byte[] arr = new byte[inputSize]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (byte) (65 + i % 10); + } + + return arr; + } + private long mask(final long x) { return (x >>> 15 | x << 17) + FramedSnappyCompressorInputStream.MASK_OFFSET & 0xffffFFFFL; } @@ -69,6 +78,15 @@ public final class FramedSnappyCompressorInputStreamTest extends AbstractTest { assertEquals(Long.toHexString(x), Long.toHexString(FramedSnappyCompressorInputStream.unmask(mask(x)))); } + @Test + public void testFinishWithNoWrite() throws IOException { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { + // do nothing here. this will test that flush on close doesn't throw any exceptions if no data is written. + } + assertTrue(buffer.size() == 10, "Only the signature gets written."); + } + /** * Something big enough to make buffers slide. */ @@ -171,6 +189,25 @@ public final class FramedSnappyCompressorInputStreamTest extends AbstractTest { } } + @Test + public void testWriteByteArrayVsWriteByte() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + final byte[] bytes = "abcdefghijklmnop".getBytes(); + try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { + compressor.write(bytes); + compressor.finish(); + } + final byte[] bulkOutput = buffer.toByteArray(); + buffer = new ByteArrayOutputStream(); + try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { + for (final byte element : bytes) { + compressor.write(element); + } + compressor.finish(); + } + assertArrayEquals(bulkOutput, buffer.toByteArray()); + } + @Test public void testWriteDataLargerThanBufferOneCall() throws IOException { final int inputSize = 500_000; @@ -195,41 +232,4 @@ public final class FramedSnappyCompressorInputStreamTest extends AbstractTest { assertArrayEquals(data, decompressed); } - private static byte[] generateTestData(final int inputSize) { - final byte[] arr = new byte[inputSize]; - for (int i = 0; i < arr.length; i++) { - arr[i] = (byte) (65 + i % 10); - } - - return arr; - } - - @Test - public void testFinishWithNoWrite() throws IOException { - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { - // do nothing here. this will test that flush on close doesn't throw any exceptions if no data is written. - } - assertTrue(buffer.size() == 10, "Only the signature gets written."); - } - - @Test - public void testWriteByteArrayVsWriteByte() throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - final byte[] bytes = "abcdefghijklmnop".getBytes(); - try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { - compressor.write(bytes); - compressor.finish(); - } - final byte[] bulkOutput = buffer.toByteArray(); - buffer = new ByteArrayOutputStream(); - try (FramedSnappyCompressorOutputStream compressor = new FramedSnappyCompressorOutputStream(buffer)) { - for (final byte element : bytes) { - compressor.write(element); - } - compressor.finish(); - } - assertArrayEquals(bulkOutput, buffer.toByteArray()); - } - }