undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
268
test/jdk/java/util/zip/CopyZipFile.java
Normal file
268
test/jdk/java/util/zip/CopyZipFile.java
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* Copyright Amazon.com Inc. 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8253952
|
||||
* @summary Test behaviour when copying ZipEntries between zip files.
|
||||
* @run junit CopyZipFile
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class CopyZipFile {
|
||||
// ZIP file created in this test
|
||||
private Path zip = Path.of("first.zip");
|
||||
// The content to put in each entry
|
||||
private static final byte[] TEST_STRING = "TestTestTest".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
/**
|
||||
* Create the sample ZIP file used in this test, including a STORED entry
|
||||
* and DEFLATE entries with various compression levels.
|
||||
* @throws IOException if an unexpected IOException occurs
|
||||
*/
|
||||
@BeforeEach
|
||||
public void createZip() throws IOException {
|
||||
// By default, ZipOutputStream creates zip files with Local File Headers
|
||||
// without size, compressed size and crc values and an extra Data
|
||||
// Descriptor (see https://en.wikipedia.org/wiki/Zip_(file_format)
|
||||
// after the data belonging to that entry with these values if in the
|
||||
// corresponding ZipEntry one of the size, compressedSize or crc fields is
|
||||
// equal to '-1' (which is the default for newly created ZipEntries).
|
||||
try (OutputStream os = Files.newOutputStream(zip) ;
|
||||
ZipOutputStream zos = new ZipOutputStream(os)) {
|
||||
// First file will be compressed with DEFAULT_COMPRESSION (i.e. -1 or 6)
|
||||
zos.setLevel(Deflater.DEFAULT_COMPRESSION);
|
||||
zos.putNextEntry(new ZipEntry("DEFAULT_COMPRESSION.txt"));
|
||||
zos.write(TEST_STRING);
|
||||
|
||||
// Second file won't be compressed at all (i.e. STORED)
|
||||
zos.setMethod(ZipOutputStream.STORED);
|
||||
ZipEntry ze = new ZipEntry("STORED.txt");
|
||||
ze.setSize(TEST_STRING.length);
|
||||
ze.setCompressedSize(TEST_STRING.length);
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(TEST_STRING);
|
||||
ze.setCrc(crc.getValue());
|
||||
zos.putNextEntry(ze);
|
||||
zos.write(TEST_STRING);
|
||||
|
||||
// Third file will be compressed with NO_COMPRESSION (i.e. 0)
|
||||
zos.setMethod(ZipOutputStream.DEFLATED);
|
||||
zos.setLevel(Deflater.NO_COMPRESSION);
|
||||
zos.putNextEntry(new ZipEntry("NO_COMPRESSION.txt"));
|
||||
zos.write(TEST_STRING);
|
||||
|
||||
// Fourth file will be compressed with BEST_SPEED (i.e. 1)
|
||||
zos.setLevel(Deflater.BEST_SPEED);
|
||||
zos.putNextEntry(new ZipEntry("BEST_SPEED.txt"));
|
||||
zos.write(TEST_STRING);
|
||||
|
||||
// Fifth file will be compressed with BEST_COMPRESSION (i.e. 9)
|
||||
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||
zos.putNextEntry(new ZipEntry("BEST_COMPRESSION.txt"));
|
||||
zos.write(TEST_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the ZIP file produced by this test
|
||||
* @throws IOException if an unexpected IOException occurs
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanup() throws IOException {
|
||||
Files.deleteIfExists(zip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all entries using ZipInputStream.getNextEntry and copy them
|
||||
* to a new zip file using ZipOutputStream.putNextEntry. This only works
|
||||
* reliably because the input zip file has no values for the size, compressedSize
|
||||
* and crc values of streamed zip entries in the local file header and
|
||||
* therefore the ZipEntry objects created by ZipOutputStream.getNextEntry
|
||||
* will have all these fields set to '-1'.
|
||||
*
|
||||
* @throws IOException if an unexpected IOException occurs
|
||||
*/
|
||||
@Test
|
||||
public void copyFromZipInputStreamToZipOutputStream() throws IOException {
|
||||
|
||||
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip));
|
||||
ZipOutputStream zos = new ZipOutputStream(OutputStream.nullOutputStream())) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
// ZipInputStream.getNextEntry() only reads the Local File Header of a zip entry,
|
||||
// so for the zip file we've just generated the ZipEntry fields 'size', 'compressedSize`
|
||||
// and 'crc' for deflated entries should be uninitialized (i.e. '-1').
|
||||
System.out.println(
|
||||
String.format("name=%s, clen=%d, len=%d, crc=%d",
|
||||
entry.getName(), entry.getCompressedSize(), entry.getSize(), entry.getCrc()));
|
||||
if (entry.getMethod() == ZipEntry.DEFLATED) {
|
||||
// Expect size, compressed size and crc to not be initialized at this point
|
||||
assertEquals(-1, entry.getCompressedSize());
|
||||
assertEquals(-1, entry.getSize());
|
||||
assertEquals(-1, entry.getCrc());
|
||||
}
|
||||
zos.putNextEntry(entry);
|
||||
zis.transferTo(zos);
|
||||
// After all the data belonging to a zip entry has been inflated (i.e. after ZipInputStream.read()
|
||||
// returned '-1'), it is guaranteed that the ZipInputStream will also have consumed the Data
|
||||
// Descriptor (if any) after the data and will have updated the 'size', 'compressedSize' and 'crc'
|
||||
// fields of the ZipEntry object.
|
||||
System.out.println(
|
||||
String.format("name=%s, clen=%d, len=%d, crc=%d\n",
|
||||
entry.getName(), entry.getCompressedSize(), entry.getSize(), entry.getCrc()));
|
||||
// Expect size, compressed size and crc to be initialized at this point
|
||||
assertNotEquals(-1, entry.getCompressedSize());
|
||||
assertNotEquals(-1, entry.getSize());
|
||||
assertNotEquals(-1, entry.getCrc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all entries using the ZipFile class and copy them to a new zip file
|
||||
* using ZipOutputStream.putNextEntry.
|
||||
* The ZipFile class reads all the zip entries from the Central
|
||||
* Directory, which has accurate information for size, compressedSize and crc.
|
||||
* This means that all ZipEntry objects returned from ZipFile will have correct
|
||||
* settings for these fields.
|
||||
* If the compression level was different in the input zip file (which we can't know
|
||||
* because the zip file format doesn't record this information), the
|
||||
* size of the re-compressed entry we are writing to the ZipOutputStream might differ
|
||||
* from the original compressed size recorded in the ZipEntry. This would result in an
|
||||
* "invalid entry compressed size" ZipException if ZipOutputStream wouldn't ignore
|
||||
* the implicitely set compressed size attribute of ZipEntries read from a ZipFile
|
||||
* or ZipInputStream.
|
||||
* @throws IOException if an unexpected IOException occurs
|
||||
*/
|
||||
@Test
|
||||
public void copyFromZipFileToZipOutputStream() throws IOException {
|
||||
try (ZipOutputStream zos = new ZipOutputStream(OutputStream.nullOutputStream());
|
||||
ZipFile zf = new ZipFile(zip.toFile())) {
|
||||
ZipEntry entry;
|
||||
Enumeration<? extends ZipEntry> entries = zf.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
entry = entries.nextElement();
|
||||
System.out.println(
|
||||
String.format("name=%s, clen=%d, len=%d, crc=%d\n",
|
||||
entry.getName(), entry.getCompressedSize(),
|
||||
entry.getSize(), entry.getCrc()));
|
||||
// Expect size, compressed size and crc to be initialized at this point
|
||||
assertNotEquals(-1, entry.getCompressedSize());
|
||||
assertNotEquals(-1, entry.getSize());
|
||||
assertNotEquals(-1, entry.getCrc());
|
||||
|
||||
zos.putNextEntry(entry);
|
||||
try (InputStream is = zf.getInputStream(entry)) {
|
||||
is.transferTo(zos);
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the compressed size is set explicitly using ZipEntry.setCompressedSize(),
|
||||
* then the entry will be restreamed with a data descriptor and the compressed size
|
||||
* recomputed. If the source compression level was different from the target compression
|
||||
* level, the compressed sizes may differ and a ZipException will be thrown
|
||||
* when the entry is closed in ZipOutputStream.closeEntry
|
||||
*
|
||||
* @throws IOException if an unexpected IOException is thrown
|
||||
*/
|
||||
@Test
|
||||
public void explicitCompressedSizeWithDifferentCompressionLevels() throws IOException {
|
||||
try (ZipOutputStream zos = new ZipOutputStream(OutputStream.nullOutputStream());
|
||||
ZipFile zf = new ZipFile(zip.toFile())) {
|
||||
// Be explicit about the default compression level
|
||||
zos.setLevel(Deflater.DEFAULT_COMPRESSION);
|
||||
|
||||
Enumeration<? extends ZipEntry> entries = zf.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
|
||||
// Explicitly setting the compressed size will disable data descriptors
|
||||
// and enable validation that the compressed size in the ZipEntry matches the
|
||||
// actual compressed size written by ZipOutputStream
|
||||
entry.setCompressedSize(entry.getCompressedSize());
|
||||
|
||||
try (InputStream is = zf.getInputStream(entry)) {
|
||||
zos.putNextEntry(entry);
|
||||
is.transferTo(zos);
|
||||
// Some compression levels lead to unexpected recompressed sizes when closing the entry
|
||||
switch (entry.getName()) {
|
||||
case "DEFAULT_COMPRESSION.txt" -> {
|
||||
// DEFAULT_COMPRESSION matches expected size
|
||||
zos.closeEntry();
|
||||
}
|
||||
case "STORED.txt" -> {
|
||||
// STORED should not throw
|
||||
zos.closeEntry();
|
||||
}
|
||||
case "NO_COMPRESSION.txt", "BEST_SPEED.txt" -> {
|
||||
// NO_COMPRESSION and BEST_SPEED should lead to an unexpected recompressed size
|
||||
ZipException ze = assertThrows(ZipException.class, () -> {
|
||||
zos.closeEntry();
|
||||
});
|
||||
|
||||
// Hack to fix and close the offending zip entry with the correct recompressed size.
|
||||
// The exception message is something like:
|
||||
// "invalid entry compressed size (expected 12 but got 7 bytes)"
|
||||
// and we need to extract the second integer.
|
||||
Pattern cSize = Pattern.compile("\\d+");
|
||||
Matcher m = cSize.matcher(ze.getMessage());
|
||||
m.find();
|
||||
m.find();
|
||||
entry.setCompressedSize(Integer.parseInt(m.group()));
|
||||
zos.closeEntry();
|
||||
}
|
||||
case "BEST_COMPRESSION.txt" -> {
|
||||
// BEST_COMPRESSION produces the same compressed
|
||||
// size as DEFAULT_COMPRESSION for sample content
|
||||
zos.closeEntry();
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException("Unexpected entry " + entry.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue