From 34af02380c0a1b94dfa2987cce6375554b04eb79 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 10 May 2018 16:58:00 +0100 Subject: [PATCH] Ensure that ZipHeaderPeekIS doesn't miss bytes on partial header read Closes gh-13032 --- .../boot/loader/tools/JarWriter.java | 33 +++- .../tools/ZipHeaderPeekInputStreamTests.java | 145 ++++++++++++++++++ 2 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index 2033a664036..c0f0334bb8d 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -315,26 +315,34 @@ public class JarWriter implements LoaderClassesWriter { /** * {@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(); @@ -349,11 +357,20 @@ public class JarWriter implements LoaderClassesWriter { 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-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java new file mode 100644 index 00000000000..32dace058c4 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/ZipHeaderPeekInputStreamTests.java @@ -0,0 +1,145 @@ +/* + * 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 { + ZipHeaderPeekInputStream in = null; + try { + in = new ZipHeaderPeekInputStream(new ByteArrayInputStream( + new byte[] { 0x50, 0x4b, 0x03, 0x04, 5, 6 })); + assertThat(in.hasZipHeader()).isTrue(); + } + finally { + if (in != null) { + in.close(); + } + } + } + + @Test + public void hasZipHeaderReturnsFalseWheStreamDoesNotStartWithZipHeader() + throws IOException { + ZipHeaderPeekInputStream in = null; + try { + in = new ZipHeaderPeekInputStream( + new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 })); + assertThat(in.hasZipHeader()).isFalse(); + } + finally { + if (in != null) { + in.close(); + } + } + } + + @Test + public void readIndividualBytes() throws IOException { + ZipHeaderPeekInputStream in = null; + try { + 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); + } + finally { + if (in != null) { + in.close(); + } + } + } + + @Test + public void readMultipleBytes() throws IOException { + ZipHeaderPeekInputStream in = null; + try { + 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); + } + finally { + if (in != null) { + in.close(); + } + } + } + + @Test + public void readingMoreThanEntireStreamReadsToEndOfStream() throws IOException { + ZipHeaderPeekInputStream in = null; + try { + 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); + } + finally { + if (in != null) { + in.close(); + } + } + } + + @Test + public void readOfSomeOfTheHeaderThenMoreThanEntireStreamReadsToEndOfStream() + throws IOException { + ZipHeaderPeekInputStream in = null; + try { + 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); + } + finally { + if (in != null) { + in.close(); + } + } + } + +}