Skip to content
Snippets Groups Projects
Commit 64ab2faa authored by Daniel Hu's avatar Daniel Hu Committed by Victor Rudometov
Browse files

8303920: Avoid calling out to python in DataDescriptorSignatureMissing test

Reviewed-by: phh
Backport-of: 07eaea8c25bae6ed852685f082f8b50c5b20c1a9
parent 97e4a937
Branches
Tags
No related merge requests found
/* /*
* Copyright 2012 Google, Inc. All Rights Reserved. * Copyright 2012 Google, Inc. All Rights Reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -24,124 +25,137 @@ ...@@ -24,124 +25,137 @@
/** /**
* @test * @test
* @bug 8056934 * @bug 8056934
* @summary Check ability to read zip files created by python zipfile * @summary Verify the ability to read zip files whose local header
* implementation, which fails to write optional (but recommended) data * data descriptor is missing the optional signature
* descriptor signatures. Repro scenario is a Java -> Python -> Java round trip: * <p>
* - ZipOutputStream creates zip file with DEFLATED entries and data
* descriptors with optional signature "PK0x0708".
* - Python reads those entries, preserving the 0x08 flag byte
* - Python outputs those entries with data descriptors lacking the
* optional signature.
* - ZipInputStream cannot handle the missing signature
*
* No way to adapt the technique in this test to get a ZIP64 zip file * No way to adapt the technique in this test to get a ZIP64 zip file
* without data descriptors was found. * without data descriptors was found.
* * @run junit DataDescriptorSignatureMissing
* @ignore This test has brittle dependencies on an external working python.
*/ */
import org.junit.jupiter.api.Test;
import java.io.*; import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.zip.*; import java.util.zip.*;
import static org.junit.jupiter.api.Assertions.*;
public class DataDescriptorSignatureMissing { public class DataDescriptorSignatureMissing {
void printStream(InputStream is) throws IOException {
Reader r = new InputStreamReader(is);
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
int n;
while ((n = r.read(buf)) > 0) {
sb.append(buf, 0, n);
}
System.out.print(sb);
}
int entryCount(File zipFile) throws IOException { /**
try (FileInputStream fis = new FileInputStream(zipFile); * Verify that ZipInputStream correctly parses a ZIP with a Data Descriptor without
ZipInputStream zis = new ZipInputStream(fis)) { * the recommended but optional signature.
for (int count = 0;; count++) */
if (zis.getNextEntry() == null) @Test
return count; public void shouldParseSignaturelessDescriptor() throws IOException {
} // The ZIP with a signature-less descriptor
} byte[] zip = makeZipWithSignaturelessDescriptor();
// ZipInputStream should read the signature-less data descriptor
try (ZipInputStream in = new ZipInputStream(
new ByteArrayInputStream(zip))) {
ZipEntry first = in.getNextEntry();
assertNotNull(first, "Zip file is unexpectedly missing first entry");
assertEquals("first", first.getName());
assertArrayEquals("first".getBytes(StandardCharsets.UTF_8), in.readAllBytes());
void test(String[] args) throws Throwable { ZipEntry second = in.getNextEntry();
if (! new File("/usr/bin/python").canExecute()) assertNotNull(second, "Zip file is unexpectedly missing second entry");
return; assertEquals("second", second.getName());
assertArrayEquals("second".getBytes(StandardCharsets.UTF_8), in.readAllBytes());
// Create a java zip file with DEFLATED entries and data
// descriptors with signatures.
final File in = new File("in.zip");
final File out = new File("out.zip");
final int count = 3;
try (FileOutputStream fos = new FileOutputStream(in);
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (int i = 0; i < count; i++) {
ZipEntry ze = new ZipEntry("hello.python" + i);
ze.setMethod(ZipEntry.DEFLATED);
zos.putNextEntry(ze);
zos.write(new byte[10]);
zos.closeEntry();
} }
} }
// Copy the zip file using python's zipfile module /**
String[] python_program_lines = { * The 'Data descriptor' record is used to facilitate ZIP streaming. If the size of an
"import os", * entry is unknown at the time the LOC header is written, bit 3 of the General Purpose Bit Flag
"import zipfile", * is set, and the File data is immediately followed by the 'Data descriptor' record. This record
"input_zip = zipfile.ZipFile('in.zip', mode='r')", * then contains the compressed and uncompressed sizes of the entry and also the CRC value.
"output_zip = zipfile.ZipFile('out.zip', mode='w')", *
"count08 = 0", * The 'Data descriptor' record is usually preceded by the recommended, but optional
"for input_info in input_zip.infolist():", * signature value 0x08074b50.
" output_info = input_info", *
" if output_info.flag_bits & 0x08 == 0x08:", * A ZIP entry in streaming mode has the following structure:
" count08 += 1", *
" output_zip.writestr(output_info, input_zip.read(input_info))", * ------ Local File Header ------
"output_zip.close()", * 000000 signature 0x04034b50
"if count08 == 0:", * 000004 version 20
" raise ValueError('Expected to see entries with 0x08 flag_bits set')", * 000006 flags 0x0808 # Notice bit 3 is set
}; * [..] Omitted for brevity
StringBuilder python_program_builder = new StringBuilder(); *
for (String line : python_program_lines) * ------ File Data ------
python_program_builder.append(line).append('\n'); * 000035 data 7 bytes
String python_program = python_program_builder.toString(); *
String[] cmdline = { "/usr/bin/python", "-c", python_program }; * ------ Data Descriptor ------
ProcessBuilder pb = new ProcessBuilder(cmdline); * 000042 signature 0x08074b50
pb.redirectErrorStream(true); * 000046 crc 0x3610a686
Process p = pb.start(); * 000050 csize 7
printStream(p.getInputStream()); * 000054 size 5
p.waitFor(); *
equal(p.exitValue(), 0); * A signature-less data descriptor will look like the following:
*
File pythonZipFile = new File("out.zip"); * ------ Data Descriptor ------
check(pythonZipFile.exists()); * 000042 crc 0x3610a686
* 000046 csize 7
equal(entryCount(in), * 000050 size 5
entryCount(out)); *
* This method produces a ZIP with two entries, where the first entry
// We expect out to be identical to in, except for the removal of * is made signature-less.
// the optional data descriptor signatures. */
final int SIG_LENGTH = 4; // length of a zip signature - PKxx private static byte[] makeZipWithSignaturelessDescriptor() throws IOException {
equal(in.length(), // Offset of the signed data descriptor
out.length() + SIG_LENGTH * count); int sigOffset;
in.delete(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.delete(); try (ZipOutputStream zo = new ZipOutputStream(out)) {
// Write a first entry
zo.putNextEntry(new ZipEntry("first"));
zo.write("first".getBytes(StandardCharsets.UTF_8));
// Force the data descriptor to be written out
zo.closeEntry();
// Signed data descriptor starts 16 bytes before current offset
sigOffset = out.size() - 4 * Integer.BYTES;
// Add a second entry
zo.putNextEntry(new ZipEntry("second"));
zo.write("second".getBytes(StandardCharsets.UTF_8));
} }
//--------------------- Infrastructure --------------------------- // The generated ZIP file with a signed data descriptor
volatile int passed = 0, failed = 0; byte[] sigZip = out.toByteArray();
void pass() {passed++;}
void fail() {failed++; Thread.dumpStack();} // The offset of the CRC immediately following the 4-byte signature
void fail(String msg) {System.err.println(msg); fail();} int crcOffset = sigOffset + Integer.BYTES;
void unexpected(Throwable t) {failed++; t.printStackTrace();}
void check(boolean cond) {if (cond) pass(); else fail();} // Create a ZIP file with a signature-less data descriptor for the first entry
void equal(Object x, Object y) { ByteArrayOutputStream sigLess = new ByteArrayOutputStream();
if (x == null ? y == null : x.equals(y)) pass(); sigLess.write(sigZip, 0, sigOffset);
else fail(x + " not equal to " + y);} // Skip the signature
public static void main(String[] args) throws Throwable { sigLess.write(sigZip, crcOffset, sigZip.length - crcOffset);
new DataDescriptorSignatureMissing().instanceMain(args);}
public void instanceMain(String[] args) throws Throwable { byte[] siglessZip = sigLess.toByteArray();
try {test(args);} catch (Throwable t) {unexpected(t);}
System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); // Adjust the CEN offset in the END header
if (failed > 0) throw new AssertionError("Some tests failed");} ByteBuffer buffer = ByteBuffer.wrap(siglessZip).order(ByteOrder.LITTLE_ENDIAN);
// Reduce cenOffset by 4 bytes
int cenOff = siglessZip.length - ZipFile.ENDHDR + ZipFile.ENDOFF;
int realCenOff = buffer.getInt(cenOff) - Integer.BYTES;
buffer.putInt(cenOff, realCenOff);
// Adjust the LOC offset in the second CEN header
int cen = realCenOff;
// Skip past the first CEN header
int nlen = buffer.getShort(cen + ZipFile.CENNAM);
cen += ZipFile.CENHDR + nlen;
// Reduce LOC offset by 4 bytes
int locOff = cen + ZipFile.CENOFF;
buffer.putInt(locOff, buffer.getInt(locOff) - Integer.BYTES);
return siglessZip;
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment