Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bc78378
Fix copying of zero length resources
gregw Oct 10, 2025
1abce9d
Fix copying of zero length resources
gregw Oct 10, 2025
c30d2fa
TODOs from reviews
gregw Oct 11, 2025
01d9696
updates from reviews
gregw Oct 11, 2025
a300209
Propose the checkOffsetLengthSize contract
gregw Oct 12, 2025
ce185bf
Propose the checkOffsetLengthSize contract
gregw Oct 12, 2025
e8cd8dd
Propose the checkOffsetLengthSize contract
gregw Oct 13, 2025
4cca316
Propose the checkOffsetLengthSize contract
gregw Oct 13, 2025
7ed9ed7
Propose the checkOffsetLengthSize contract
gregw Oct 13, 2025
ccb80c0
Propose the checkOffsetLengthSize contract
gregw Oct 13, 2025
a61e7f3
More forgiving offsetLengthSize contract
gregw Oct 13, 2025
bab1fdd
Updates from review
gregw Oct 14, 2025
c1d181d
Implementing TODOs; added ContentSourceRange class and improved testing
lachlan-roberts Oct 15, 2025
ae86776
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/jetty-12.…
gregw Oct 15, 2025
4147ef3
PR #13690 - fixes for test failures
lachlan-roberts Oct 15, 2025
1d255e7
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/jetty-12.…
gregw Oct 16, 2025
25a685c
Updates from review
gregw Oct 19, 2025
0706aba
PR #13690 - changes from review
lachlan-roberts Oct 20, 2025
a00d126
PR #13690 - extra test for failure case
lachlan-roberts Oct 20, 2025
c4c6d6a
PR #13690 - changes from review
lachlan-roberts Oct 20, 2025
00856b4
update from review
gregw Oct 24, 2025
8c29420
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/jetty-12.…
gregw Oct 28, 2025
6c725c4
Updates from review
gregw Oct 28, 2025
2fe1d8f
Updates from review
gregw Oct 28, 2025
cc50b37
add missing checks in HttpContent.writeTo() implementations
lorban Oct 28, 2025
e36049c
Updates from review
gregw Oct 28, 2025
8f95ae2
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/jetty-12.…
gregw Oct 28, 2025
a6ca6a7
Updates from review
gregw Oct 28, 2025
ccfc4b9
Updates from review
gregw Oct 28, 2025
9f586b1
Merge branch 'jetty-12.1.x' into fix/jetty-12.1.x/13685-zeroLengthFiles
gregw Oct 29, 2025
b2ebc77
Updates from review
gregw Oct 29, 2025
fc77020
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/jetty-12.…
gregw Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,9 @@ public final Content.Source createContentSource()
@Override
public Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length)
{
return newContentSource();
// We call the deprecated newContentSource() to support existing subclasses.
// All current implementations of Part do override newContentSource(ByteBufferPool.Sized, long, long).
return Content.Source.from(newContentSource(), offset, length);
}

public long getLength()
Expand Down Expand Up @@ -499,10 +501,19 @@ public ByteBufferPart(String name, String fileName, HttpFields fields, List<Byte
this.content = content;
}

@Override
public long getLength()
{
long length = 0;
for (ByteBuffer b : content)
length += BufferUtil.length(b);
return length;
}

@Override
public Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length)
{
return new ByteBufferContentSource(content);
return new ByteBufferContentSource(content, offset, length);
}

@Override
Expand Down Expand Up @@ -535,9 +546,21 @@ public ChunksPart(String name, String fileName, HttpFields fields, List<Content.
content.forEach(Content.Chunk::retain);
}

@Override
public long getLength()
{
long length = 0;
for (Content.Chunk c : content)
length += c.size();
return length;
}

@Override
public Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length)
{
long size = getLength();
length = TypeUtil.checkOffsetLengthSize(offset, length, size);

try (AutoLock ignored = lock.lock())
{
if (closed)
Expand All @@ -556,7 +579,7 @@ public Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long off
ChunksContentSource newContentSource = new ChunksContentSource(chunks);
chunks.forEach(Content.Chunk::release);
contentSources.add(newContentSource);
return newContentSource;
return Content.Source.from(newContentSource, offset, length);
}
}

Expand Down Expand Up @@ -650,12 +673,19 @@ public ContentSourcePart(String name, String fileName, HttpFields fields, Conten
this.content = Objects.requireNonNull(content);
}

@Override
public long getLength()
{
return content.getLength();
}

@Override
public Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length)
{
length = TypeUtil.checkOffsetLengthSize(offset, length, content.getLength());
Content.Source c = content;
content = null;
return c;
return Content.Source.from(c, offset, length);
}

@Override
Expand Down
69 changes: 56 additions & 13 deletions jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Content.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.eclipse.jetty.io.internal.ContentCopier;
import org.eclipse.jetty.io.internal.ContentSourceByteBuffer;
import org.eclipse.jetty.io.internal.ContentSourceConsumer;
import org.eclipse.jetty.io.internal.ContentSourceRange;
import org.eclipse.jetty.io.internal.ContentSourceRetainableByteBuffer;
import org.eclipse.jetty.io.internal.ContentSourceString;
import org.eclipse.jetty.util.Blocker;
Expand Down Expand Up @@ -169,9 +170,13 @@ interface Factory
* Creates a new {@link Content.Source}.
*
* @param bufferPool the {@link ByteBufferPool.Sized} to get buffers from. {@code null} means allocate new buffers as needed.
* @param offset the offset byte of the resource to start from.
* @param offset the offset byte of the content to start from.
* Must be greater than or equal to 0 and less than the content length (if known).
* @param length the length of the content to make available, -1 for the full length.
* If the size of the content is known, the length may be truncated to the content size minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
Content.Source newContentSource(ByteBufferPool.Sized bufferPool, long offset, long length);
}
Expand All @@ -198,16 +203,42 @@ static Content.Source from(Path path)

/**
* Create a {@code Content.Source} from a {@link Path}.
*
* @param path The {@link Path}s to use as the source.
* @param offset The offset in bytes from which to start the source
* @param length The length in bytes of the source.
* @return A {@code Content.Source}
* @param offset the offset byte of the content to start from.
* Must be greater than or equal to 0 and less than the content length (if known).
* @param length the length of the content to make available, -1 for the full length.
* If the size of the content is known, the length may be truncated to the content size minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
static Content.Source from(Path path, long offset, long length)
{
return from(null, path, offset, length);
}

/**
* Wrap a {@link Content.Source} to make it appear as a sub-range of the original.
*
* @param source The {@link Content.Source} to wrap.
* @param offset the offset byte of the content to start from.
* Must be greater than or equal to 0 and less than the content length (if known).
* @param length the length of the content to make available, -1 for the full length.
* If the size of the content is known, the length may be truncated to the content size minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
static Content.Source from(Content.Source source, long offset, long length)
{
// If the offset and length include the full content, then do not wrap.
if (offset == 0 && (length == -1 || length == source.getLength()))
return source;

return new ContentSourceRange(source, offset, length);
}

/**
* Create a {@code Content.Source} from a {@link Path}.
* @param byteBufferPool The {@link org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
Expand All @@ -223,9 +254,13 @@ static Content.Source from(ByteBufferPool.Sized byteBufferPool, Path path)
* Create a {@code Content.Source} from a {@link Path}.
* @param byteBufferPool The {@link org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
* @param path The {@link Path}s to use as the source.
* @param offset The offset in bytes from which to start the source
* @param length The length in bytes of the source, -1 for the full length.
* @return A {@code Content.Source}
* @param offset the offset byte of the content to start from.
* Must be greater than or equal to 0 and less than the content length (if known).
* @param length the length of the content to make available, -1 for the full length.
* If the size of the content is known, the length may be truncated to the content size minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
static Content.Source from(ByteBufferPool.Sized byteBufferPool, Path path, long offset, long length)
{
Expand All @@ -247,9 +282,13 @@ static Content.Source from(ByteBufferPool.Sized byteBufferPool, ByteChannel byte
* Create a {@code Content.Source} from a {@link ByteChannel}.
* @param byteBufferPool The {@link org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
* @param seekableByteChannel The {@link ByteChannel}s to use as the source.
* @param offset The offset in bytes from which to start the source
* @param length The length in bytes of the source.
* @return A {@code Content.Source}
* @param offset the offset byte of the content to start from.
* Must be greater than or equal to 0 and less than the content length (if known).
* @param length the length of the content to make available, -1 for the full length.
* If the size of the content is known, the length may be truncated to the content size minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
static Content.Source from(ByteBufferPool.Sized byteBufferPool, SeekableByteChannel seekableByteChannel, long offset, long length)
{
Expand All @@ -276,9 +315,13 @@ static Content.Source from(ByteBufferPool.Sized byteBufferPool, InputStream inpu
* Create a {@code Content.Source} from an {@link InputStream}.
* @param byteBufferPool The {@link org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
* @param inputStream The {@link InputStream}s to use as the source.
* @param offset The offset in bytes from which to start the source
* @param length The number of bytes to read from the source, or -1 to read to the end of the stream
* @return A {@code Content.Source}
* @param offset the offset byte of the resource to start from.
* Must be greater than or equal to 0 and less than the resource size (if known).
* @param length the length of the content to make available, or -1 for the full length available.
* The length may be truncated if the stream ends sooner.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @see TypeUtil#checkOffsetLengthSize(long, long, long)
*/
static Content.Source from(ByteBufferPool.Sized byteBufferPool, InputStream inputStream, long offset, long length)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.resource.MemoryResource;
import org.eclipse.jetty.util.resource.Resource;

Expand Down Expand Up @@ -146,10 +148,14 @@ public static RetainableByteBuffer toRetainableByteBuffer(Resource resource, Byt
*
* @param resource the resource from which to get a {@link Content.Source}.
* @param bufferPool the {@link ByteBufferPool.Sized} to get buffers from. {@code null} means allocate new buffers as needed.
* @param offset the offset byte from which to read from.
* @param length the length of the content to read, -1 for the full length.
* @return the {@link Content.Source}.
* @param offset the offset byte of the resource to start from.
* Must be greater than or equal to 0 and less than the resource length (if known).
* @param length the length of the content to make available, -1 for the full length,
* otherwise must be greater than 0 and less than or equal to the resource length (if known) minus the offset.
* @return a {@link Content.Source}.
* @throws IndexOutOfBoundsException if the offset or length are out of range.
* @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents.
* @see Objects#checkFromIndexSize(long, long, long)
*/
public static Content.Source asContentSource(Resource resource, ByteBufferPool.Sized bufferPool, long offset, long length) throws IllegalArgumentException
{
Expand All @@ -165,6 +171,8 @@ public static Content.Source asContentSource(Resource resource, ByteBufferPool.S
if (path != null)
return Content.Source.from(bufferPool, path, offset, length);

length = TypeUtil.checkOffsetLengthSize(offset, length, resource.length());

// Try an optimization for MemoryResource.
if (resource instanceof MemoryResource memoryResource)
return Content.Source.from(BufferUtil.slice(ByteBuffer.wrap(memoryResource.getBytes()), Math.toIntExact(offset), Math.toIntExact(length)));
Expand Down Expand Up @@ -237,6 +245,8 @@ public static void copy(Resource resource, Content.Sink sink, ByteBufferPool.Siz
return;
}

length = TypeUtil.checkOffsetLengthSize(offset, length, resource.length());

// Save a Content.Source allocation for resources with a Path.
Path path = resource.getPath();
if (path != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.util.List;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;

Expand All @@ -45,8 +47,18 @@ public ByteBufferContentSource(ByteBuffer... byteBuffers)

public ByteBufferContentSource(Collection<ByteBuffer> byteBuffers)
{
this.byteBuffers = byteBuffers;
this.length = byteBuffers.stream().mapToLong(Buffer::remaining).sum();
this(byteBuffers, 0, -1);
}

public ByteBufferContentSource(Collection<ByteBuffer> byteBuffers, long offset, long length)
{
long size = byteBuffers.stream().mapToLong(Buffer::remaining).sum();
length = TypeUtil.checkOffsetLengthSize(offset, length, size);
if (offset == 0 && size == length)
this.byteBuffers = byteBuffers;
else
this.byteBuffers = BufferUtil.slice(byteBuffers, offset, length);
this.length = length;
}

public Collection<ByteBuffer> getByteBuffers()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;

Expand Down Expand Up @@ -63,6 +64,7 @@ public InputStreamContentSource(InputStream inputStream, ByteBufferPool.Sized bu

public InputStreamContentSource(InputStream inputStream, ByteBufferPool.Sized bufferPool, long offset, long length)
{
length = TypeUtil.checkOffsetLengthSize(offset, length, -1);
this.inputStream = Objects.requireNonNull(inputStream);
this.bufferPool = Objects.requireNonNullElse(bufferPool, ByteBufferPool.SIZED_NON_POOLING);
if (length != 0)
Expand Down
Loading