Ensure that ZipHeaderPeekIS doesn't miss bytes on partial header read
Closes gh-13032
This commit is contained in:
		
							parent
							
								
									b17a00d0c3
								
							
						
					
					
						commit
						34af02380c
					
				|  | @ -315,26 +315,34 @@ public class JarWriter implements LoaderClassesWriter { | ||||||
| 	/** | 	/** | ||||||
| 	 * {@link InputStream} that can peek ahead at zip header bytes. | 	 * {@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 static final byte[] ZIP_HEADER = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; | ||||||
| 
 | 
 | ||||||
| 		private final byte[] header; | 		private final byte[] header; | ||||||
| 
 | 
 | ||||||
|  | 		private final int headerLength; | ||||||
|  | 
 | ||||||
|  | 		private int position; | ||||||
|  | 
 | ||||||
| 		private ByteArrayInputStream headerStream; | 		private ByteArrayInputStream headerStream; | ||||||
| 
 | 
 | ||||||
| 		protected ZipHeaderPeekInputStream(InputStream in) throws IOException { | 		protected ZipHeaderPeekInputStream(InputStream in) throws IOException { | ||||||
| 			super(in); | 			super(in); | ||||||
| 			this.header = new byte[4]; | 			this.header = new byte[4]; | ||||||
| 			int len = in.read(this.header); | 			this.headerLength = in.read(this.header); | ||||||
| 			this.headerStream = new ByteArrayInputStream(this.header, 0, len); | 			this.headerStream = new ByteArrayInputStream(this.header, 0, | ||||||
|  | 					this.headerLength); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		@Override | 		@Override | ||||||
| 		public int read() throws IOException { | 		public int read() throws IOException { | ||||||
| 			int read = (this.headerStream != null ? this.headerStream.read() : -1); | 			int read = (this.headerStream != null ? this.headerStream.read() : -1); | ||||||
| 			if (read != -1) { | 			if (read != -1) { | ||||||
| 				this.headerStream = null; | 				this.position++; | ||||||
|  | 				if (this.position >= this.headerLength) { | ||||||
|  | 					this.headerStream = null; | ||||||
|  | 				} | ||||||
| 				return read; | 				return read; | ||||||
| 			} | 			} | ||||||
| 			return super.read(); | 			return super.read(); | ||||||
|  | @ -349,11 +357,20 @@ public class JarWriter implements LoaderClassesWriter { | ||||||
| 		public int read(byte[] b, int off, int len) throws IOException { | 		public int read(byte[] b, int off, int len) throws IOException { | ||||||
| 			int read = (this.headerStream != null ? this.headerStream.read(b, off, len) | 			int read = (this.headerStream != null ? this.headerStream.read(b, off, len) | ||||||
| 					: -1); | 					: -1); | ||||||
| 			if (read != -1) { | 			if (read > 0) { | ||||||
| 				this.headerStream = null; | 				this.position += read; | ||||||
| 				return 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() { | 		public boolean hasZipHeader() { | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue