// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed 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. package com.google.devtools.build.android.ziputils; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENHOW; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENLEN; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIZ; import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDOFF; import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIG; import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIZ; import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSUB; import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDTOT; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import com.google.devtools.build.android.ziputils.ZipIn.ZipEntry; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.zip.ZipInputStream; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests for {@link ZipIn}. */ @RunWith(JUnit4.class) public class ZipInTest { private static final int ENTRY_COUNT = 1000; private FakeFileSystem fileSystem; @Before public void setUp() throws Exception { fileSystem = new FakeFileSystem(); } /** * Test of endOfCentralDirectory method, of class ZipIn. */ @Test public void testEndOfCentralDirectory() throws Exception { String filename = "test.zip"; byte[] bytes; ByteBuffer buffer; String comment; int commentLen; int offset; ZipIn zipIn; EndOfCentralDirectory result; String subcase; // Find it, even if it's the only useful thing in the file. subcase = " EOCD found it, "; bytes = new byte[] { 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); result = zipIn.endOfCentralDirectory(); assertNotNull(subcase + "found", result); subcase = " EOCD not there at all, "; bytes = new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); try { zipIn.endOfCentralDirectory(); fail(subcase + "expected IllegalStateException"); } catch (Exception ex) { assertSame(subcase + "caught exception", IllegalStateException.class, ex.getClass()); } // If we can't read it, it's not there subcase = " EOCD too late to read, "; bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); try { zipIn.endOfCentralDirectory(); fail(subcase + "expected IndexOutOfBoundsException"); } catch (Exception ex) { assertSame(subcase + "caught exception", IndexOutOfBoundsException.class, ex.getClass()); } // Current implementation doesn't know to scan past a bad EOCD record. // I'm not sure if it should. subcase = " EOCD good hidden by bad, "; bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); try { zipIn.endOfCentralDirectory(); fail(subcase + "expected IndexOutOfBoundsException"); } catch (Exception ex) { assertSame(subcase + "caught exception", IndexOutOfBoundsException.class, ex.getClass()); } // Minimal format checking here, assuming the EndOfDirectoryTest class // test for that. subcase = " EOCD truncated comment, "; bytes = new byte[100]; buffer = ByteBuffer.wrap(bytes); comment = "optional file comment"; commentLen = comment.getBytes(UTF_8).length; offset = bytes.length - ZipInputStream.ENDHDR - commentLen; buffer.position(offset); EndOfCentralDirectory.view(buffer, comment); byte[] truncated = Arrays.copyOf(bytes, bytes.length - 5); fileSystem.addFile(filename, truncated); zipIn = newZipIn(filename); try { // not sure this is the exception we want! zipIn.endOfCentralDirectory(); fail(subcase + "expected IllegalArgumentException"); } catch (Exception ex) { assertSame(subcase + "caught exception", IllegalArgumentException.class, ex.getClass()); } subcase = " EOCD no comment, "; bytes = new byte[100]; buffer = ByteBuffer.wrap(bytes); comment = null; commentLen = 0; offset = bytes.length - ZipInputStream.ENDHDR - commentLen; buffer.position(offset); EndOfCentralDirectory.view(buffer, comment); fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); result = zipIn.endOfCentralDirectory(); assertNotNull(subcase + "found", result); assertEquals(subcase + "comment", "", result.getComment()); assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG)); subcase = " EOCD comment, "; bytes = new byte[100]; buffer = ByteBuffer.wrap(bytes); comment = "optional file comment"; commentLen = comment.getBytes(UTF_8).length; offset = bytes.length - ZipInputStream.ENDHDR - commentLen; buffer.position(offset); EndOfCentralDirectory.view(buffer, comment); assertEquals(subcase + "setup", comment, new String(bytes, bytes.length - commentLen, commentLen, UTF_8)); fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); result = zipIn.endOfCentralDirectory(); assertNotNull(subcase + "found", result); assertEquals(subcase + "comment", comment, result.getComment()); assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG)); subcase = " EOCD extra data, "; bytes = new byte[100]; buffer = ByteBuffer.wrap(bytes); comment = null; commentLen = 0; offset = bytes.length - ZipInputStream.ENDHDR - commentLen - 10; buffer.position(offset); EndOfCentralDirectory.view(buffer, comment); fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); result = zipIn.endOfCentralDirectory(); assertNotNull(subcase + "found", result); assertEquals(subcase + "comment", "", result.getComment()); assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG)); } /** * Test of centralDirectory method, of class ZipIn. */ @Test public void testCentralDirectory() throws Exception { String filename = "test.zip"; ByteBuffer buffer; int offset; ZipIn zipIn; String subcase; subcase = " EOCD extra data, "; String commonName = "thisIsNotNormal.txt"; int filenameLen = commonName.getBytes(UTF_8).length; int count = ENTRY_COUNT; int dirEntry = ZipInputStream.CENHDR; int before = count; int between = 0; // implementation doesn't tolerate data between dir entries, does the spec? int after = 20; int eocd = ZipInputStream.ENDHDR; int total = before + (count * (dirEntry + filenameLen)) + ((count - 1) * between) + after + eocd; byte[] bytes = new byte[total]; offset = before; for (int i = 0; i < count; i++) { if (i > 0) { offset += between; } buffer = ByteBuffer.wrap(bytes, offset, bytes.length - offset); DirectoryEntry.view(buffer, commonName, null, null) .set(CENHOW, (short) 8) .set(CENSIZ, before) .set(CENLEN, 2 * before) .set(CENOFF, i); // Not valid of course, but we're only testing central dir parsing. // and there are currently no checks in the parser to see if offset makes sense. offset += dirEntry + filenameLen; } offset += after; buffer = ByteBuffer.wrap(bytes, offset, bytes.length - offset); EndOfCentralDirectory.view(buffer, null) .set(ENDOFF, before) .set(ENDSIZ, offset - before - after) .set(ENDTOT, (short) count) .set(ENDSUB, (short) count); fileSystem.addFile(filename, bytes); zipIn = newZipIn(filename); CentralDirectory result = zipIn.centralDirectory(); assertNotNull(subcase + "found", result); List<DirectoryEntry> list = result.list(); assertEquals(subcase + "size", count, list.size()); for (int i = 0; i < list.size(); i++) { assertEquals(subcase + "offset check[" + i + "]", i, list.get(i).get(CENOFF)); } } /** * Test of scanEntries method, of class ZipIn. */ @Test public void testScanEntries() throws Exception { int count = ENTRY_COUNT * 100; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.scanEntries(new EntryHandler() { int count = 0; @Override public void handle(ZipIn in, LocalFileHeader header, DirectoryEntry dirEntry, ByteBuffer data) throws IOException { assertSame(zipIn, in); String filename = "pkg/f" + count + ".class"; assertEquals(filename, header.getFilename()); assertEquals(filename, dirEntry.getFilename()); count++; } }); } /** * Test of nextHeaderFrom method, of class ZipIn. */ @Test public void testNextHeaderFrom_long() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.endOfCentralDirectory(); count = 0; int offset = 0; LocalFileHeader header; do { header = zipIn.nextHeaderFrom(offset); String name = "pkg/f" + count + ".class"; if (header != null) { assertEquals(name, header.getFilename()); count++; offset = (int) header.fileOffset() + 4; } } while(header != null); assertEquals(ENTRY_COUNT, count); } /** * Test of nextHeaderFrom method, of class ZipIn. */ @Test public void testNextHeaderFrom_DirectoryEntry() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); List<DirectoryEntry> list = zipIn.centralDirectory().list(); count = 0; String name; LocalFileHeader header = zipIn.nextHeaderFrom(null); for (DirectoryEntry dirEntry : list) { name = "pkg/f" + count + ".class"; assertEquals(name, dirEntry.getFilename()); assertEquals(name, header.getFilename()); header = zipIn.nextHeaderFrom(dirEntry); count++; } assertNull(header); } /** * Test of localHeaderFor method, of class ZipIn. */ @Test public void testLocalHeaderFor() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); List<DirectoryEntry> list = zipIn.centralDirectory().list(); count = 0; String name; LocalFileHeader header; for (DirectoryEntry dirEntry : list) { name = "pkg/f" + count + ".class"; header = zipIn.localHeaderFor(dirEntry); assertEquals(name, dirEntry.getFilename()); assertEquals(name, header.getFilename()); count++; } } /** * Test of localHeaderAt method, of class ZipIn. */ @Test public void testLocalHeaderAt() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); List<DirectoryEntry> list = zipIn.centralDirectory().list(); count = 0; String name; LocalFileHeader header; for (DirectoryEntry dirEntry : list) { name = "pkg/f" + count + ".class"; header = zipIn.localHeaderAt(dirEntry.get(CENOFF)); assertEquals(name, dirEntry.getFilename()); assertEquals(name, header.getFilename()); count++; } } /** * Test of nextFrom method, of class ZipIn. */ @Test public void testNextFrom_long() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); count = 0; int offset = 0; ZipEntry zipEntry; do { zipEntry = zipIn.nextFrom(offset); String name = "pkg/f" + count + ".class"; if (zipEntry.getCode() != ZipEntry.Status.ENTRY_NOT_FOUND) { assertNotNull(zipEntry.getHeader()); assertNotNull(zipEntry.getDirEntry()); assertEquals(name, zipEntry.getHeader().getFilename()); assertEquals(name, zipEntry.getDirEntry().getFilename()); count++; offset = (int) zipEntry.getHeader().fileOffset() + 4; } } while(zipEntry.getCode() != ZipEntry.Status.ENTRY_NOT_FOUND); assertEquals(ENTRY_COUNT, count); } /** * Test of nextFrom method, of class ZipIn. */ @Test public void testNextFrom_DirectoryEntry() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); List<DirectoryEntry> list = zipIn.centralDirectory().list(); count = 0; String name; ZipEntry zipEntry = zipIn.nextFrom(null); for (DirectoryEntry dirEntry : list) { if (zipEntry.getCode() == ZipEntry.Status.ENTRY_NOT_FOUND) { break; } name = "pkg/f" + count + ".class"; assertNotNull(zipEntry.getHeader()); assertNotNull(zipEntry.getDirEntry()); assertEquals(name, zipEntry.getHeader().getFilename()); assertEquals(name, zipEntry.getDirEntry().getFilename()); zipEntry = zipIn.nextFrom(dirEntry); count++; } assertEquals(ENTRY_COUNT, count); } /** * Test of entryAt method, of class ZipIn. */ @Test public void testEntryAt() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); List<DirectoryEntry> list = zipIn.centralDirectory().list(); count = 0; String name; ZipEntry zipEntry; for (DirectoryEntry dirEntry : list) { zipEntry = zipIn.entryAt(dirEntry.get(CENOFF)); name = "pkg/f" + count + ".class"; assertNotNull(zipEntry.getHeader()); assertNotNull(zipEntry.getDirEntry()); assertEquals(name, zipEntry.getHeader().getFilename()); assertEquals(name, zipEntry.getDirEntry().getFilename()); count++; } assertEquals(ENTRY_COUNT, count); } /** * Test of entryWith method, of class ZipIn. */ @Test public void testEntryWith() throws Exception { int count = ENTRY_COUNT; String filename = "test.jar"; ZipFileBuilder builder = new ZipFileBuilder(); for (int i = 0; i < count; i++) { builder.add("pkg/f" + i + ".class", "All day long"); } builder.create(filename); final ZipIn zipIn = newZipIn(filename); zipIn.centralDirectory(); count = 0; int offset = 0; LocalFileHeader header; do { header = zipIn.nextHeaderFrom(offset); String name = "pkg/f" + count + ".class"; if (header != null) { ZipEntry zipEntry = zipIn.entryWith(header); assertNotNull(zipEntry.getDirEntry()); assertSame(header, zipEntry.getHeader()); assertEquals(name, zipEntry.getHeader().getFilename()); assertEquals(name, zipEntry.getDirEntry().getFilename()); assertEquals(name, header.getFilename()); count++; offset = (int) header.fileOffset() + 4; } } while(header != null); assertEquals(ENTRY_COUNT, count); } private ZipIn newZipIn(String filename) throws IOException { return new ZipIn(fileSystem.getInputChannel(filename), filename); } }