Update NestedByteChannel.read to read all possible data when
Update `NestedByteChannel.read` so that it loops until all remaining data has been read into the buffer. Prior to this commit, it was possible for to read only some bytes into the buffer. Although it looks like this should be OK according to the API documentation, the `ZipFileSystem` relies on all remaining bytes being returned. Fixes gh-38595
This commit is contained in:
@@ -81,11 +81,16 @@ class NestedByteChannel implements SeekableByteChannel {
|
||||
@Override
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
assertNotClosed();
|
||||
int count = this.resources.getData().read(dst, this.position);
|
||||
if (count > 0) {
|
||||
int total = 0;
|
||||
while (dst.remaining() > 0) {
|
||||
int count = this.resources.getData().read(dst, this.position);
|
||||
if (count <= 0) {
|
||||
return (total != 0) ? 0 : count;
|
||||
}
|
||||
total += count;
|
||||
this.position += count;
|
||||
}
|
||||
return count;
|
||||
return total;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,11 +17,16 @@
|
||||
package org.springframework.boot.loader.nio.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -32,6 +37,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.loader.ref.Cleaner;
|
||||
import org.springframework.boot.loader.testsupport.TestJar;
|
||||
import org.springframework.boot.loader.zip.AssertFileChannelDataBlocksClosed;
|
||||
import org.springframework.boot.loader.zip.FileChannelDataBlockManagedFileChannel;
|
||||
import org.springframework.boot.loader.zip.ZipContent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -117,6 +123,36 @@ class NestedByteChannelTests {
|
||||
assertThat(dst.array()).isNotEqualTo(ByteBuffer.allocate(10).array());
|
||||
}
|
||||
|
||||
@Test // gh-38592
|
||||
void readReadsAsManyBytesAsPossible() throws Exception {
|
||||
// ZipFileSystem checks that the number of bytes read matches an expected value
|
||||
// ...if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR)
|
||||
// but the readFullyAt assumes that all remaining bytes are attempted to be read
|
||||
// This doesn't seem to exactly match the contract of ReadableByteChannel.read
|
||||
// which states "A read operation might not fill the buffer, and in fact it might
|
||||
// not read any bytes at all", but we need to match ZipFileSystem's expectations
|
||||
int size = FileChannelDataBlockManagedFileChannel.BUFFER_SIZE * 2;
|
||||
byte[] data = new byte[size];
|
||||
this.file = new File(this.temp, "testread.jar");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(this.file);
|
||||
try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) {
|
||||
JarEntry nestedEntry = new JarEntry("data");
|
||||
nestedEntry.setSize(size);
|
||||
nestedEntry.setCompressedSize(size);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(data);
|
||||
nestedEntry.setCrc(crc32.getValue());
|
||||
nestedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutputStream.putNextEntry(nestedEntry);
|
||||
jarOutputStream.write(data);
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
this.channel = new NestedByteChannel(this.file.toPath(), null);
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int) this.file.length());
|
||||
assertThat(this.channel.read(buffer)).isEqualTo(buffer.capacity());
|
||||
assertThat(this.file).binaryContent().isEqualTo(buffer.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeThrowsException() {
|
||||
assertThatExceptionOfType(NonWritableChannelException.class)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.zip;
|
||||
|
||||
import org.springframework.boot.loader.zip.FileChannelDataBlock.ManagedFileChannel;
|
||||
|
||||
/**
|
||||
* Test access to {@link ManagedFileChannel} details.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class FileChannelDataBlockManagedFileChannel {
|
||||
|
||||
private FileChannelDataBlockManagedFileChannel() {
|
||||
}
|
||||
|
||||
public static int BUFFER_SIZE = FileChannelDataBlock.ManagedFileChannel.BUFFER_SIZE;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user