diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index f6ae4c48ea..3f8b2d5802 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -339,26 +339,34 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { /** * {@link InputStream} that can peek ahead at zip header bytes. */ - private static class ZipHeaderPeekInputStream extends FilterInputStream { + static class ZipHeaderPeekInputStream extends FilterInputStream { private static final byte[] ZIP_HEADER = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; private final byte[] header; + private final int headerLength; + + private int position; + private ByteArrayInputStream headerStream; protected ZipHeaderPeekInputStream(InputStream in) throws IOException { super(in); this.header = new byte[4]; - int len = in.read(this.header); - this.headerStream = new ByteArrayInputStream(this.header, 0, len); + this.headerLength = in.read(this.header); + this.headerStream = new ByteArrayInputStream(this.header, 0, + this.headerLength); } @Override public int read() throws IOException { int read = (this.headerStream != null ? this.headerStream.read() : -1); if (read != -1) { - this.headerStream = null; + this.position++; + if (this.position >= this.headerLength) { + this.headerStream = null; + } return read; } return super.read(); @@ -373,11 +381,20 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable { public int read(byte[] b, int off, int len) throws IOException { int read = (this.headerStream != null ? this.headerStream.read(b, off, len) : -1); - if (read != -1) { - this.headerStream = null; - return read; + if (read > 0) { + this.position += read; } - return super.read(b, off, len); + else { + read = 0; + } + if (read < len) { + read += super.read(b, off + read, len - read); + this.position += read; + } + if (this.position >= this.headerLength) { + this.headerStream = null; + } + return read; } public boolean hasZipHeader() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java new file mode 100644 index 0000000000..5ca8d55e14 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * 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 org.springframework.boot.loader.tools; + +/** + * Tests for {@link ZipHeaderPeekInputStream}. + * + * @author Andy Wilkinson + */ +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.junit.Test; + +import org.springframework.boot.loader.tools.JarWriter.ZipHeaderPeekInputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ZipHeaderPeekInputStreamTests { + + @Test + public void hasZipHeaderReturnsTrueWhenStreamStartsWithZipHeader() + throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0x50, 0x4b, 0x03, 0x04, 5, 6 }));) { + assertThat(in.hasZipHeader()).isTrue(); + } + } + + @Test + public void hasZipHeaderReturnsFalseWheStreamDoesNotStartWithZipHeader() + throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }))) { + assertThat(in.hasZipHeader()).isFalse(); + } + } + + @Test + public void readIndividualBytes() throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }))) { + assertThat(in.read()).isEqualTo(0); + assertThat(in.read()).isEqualTo(1); + assertThat(in.read()).isEqualTo(2); + assertThat(in.read()).isEqualTo(3); + assertThat(in.read()).isEqualTo(4); + assertThat(in.read()).isEqualTo(5); + } + } + + @Test + public void readMultipleBytes() throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }))) { + byte[] bytes = new byte[3]; + assertThat(in.read(bytes)).isEqualTo(3); + assertThat(bytes).containsExactly(0, 1, 2); + assertThat(in.read(bytes)).isEqualTo(3); + assertThat(bytes).containsExactly(3, 4, 5); + assertThat(in.read(bytes)).isEqualTo(-1); + } + } + + @Test + public void readingMoreThanEntireStreamReadsToEndOfStream() throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }))) { + byte[] bytes = new byte[8]; + assertThat(in.read(bytes)).isEqualTo(6); + assertThat(bytes).containsExactly(0, 1, 2, 3, 4, 5, 0, 0); + assertThat(in.read(bytes)).isEqualTo(-1); + } + } + + @Test + public void readOfSomeOfTheHeaderThenMoreThanEntireStreamReadsToEndOfStream() + throws IOException { + try (ZipHeaderPeekInputStream in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }))) { + byte[] bytes = new byte[8]; + assertThat(in.read(bytes, 0, 3)).isEqualTo(3); + assertThat(bytes).containsExactly(0, 1, 2, 0, 0, 0, 0, 0); + assertThat(in.read(bytes, 3, 5)).isEqualTo(3); + assertThat(bytes).containsExactly(0, 1, 2, 3, 4, 5, 0, 0); + } + } + +}