Skip to content

Commit

Permalink
Update NestedByteChannel.read to read all possible data when
Browse files Browse the repository at this point in the history
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
  • Loading branch information
philwebb committed Nov 29, 2023
1 parent 75a8955 commit 9a0f954
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,16 @@ public void close() throws IOException {
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +37,7 @@
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;
Expand Down Expand Up @@ -117,6 +123,36 @@ void readReadsBytesAndIncrementsPosition() throws IOException {
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

}

0 comments on commit 9a0f954

Please sign in to comment.