Issue #11080 - copy ByteBuffer for non-retainable chunks
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
e481b45cda
commit
592ccf7da0
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -27,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.content.AsyncContent;
|
||||
import org.eclipse.jetty.io.content.InputStreamContentSource;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
@ -934,6 +936,48 @@ public class MultiPartFormDataTest
|
|||
assertInstanceOf(IllegalArgumentException.class, cause);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonRetainableContent() throws Exception
|
||||
{
|
||||
String body = """
|
||||
--AaB03x\r
|
||||
content-disposition: form-data; name="field1"\r
|
||||
\r
|
||||
Joe Blow\r
|
||||
--AaB03x\r
|
||||
content-disposition: form-data; name="stuff"; filename="foo.txt"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
aaaabbbbb\r
|
||||
--AaB03x--\r
|
||||
""";
|
||||
|
||||
Content.Source source = new InputStreamContentSource(new ByteArrayInputStream(body.getBytes(ISO_8859_1)))
|
||||
{
|
||||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
Content.Chunk chunk = super.read();
|
||||
return new NonRetainableChunk(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
MultiPartFormData.Parser formData = new MultiPartFormData.Parser("AaB03x");
|
||||
formData.setFilesDirectory(_tmpDir);
|
||||
try (MultiPartFormData.Parts parts = formData.parse(source).get(5, TimeUnit.SECONDS))
|
||||
{
|
||||
assertThat(parts.size(), is(2));
|
||||
MultiPart.Part part1 = parts.getFirst("field1");
|
||||
assertThat(part1, notNullValue());
|
||||
Content.Source partContent = part1.getContentSource();
|
||||
assertThat(Content.Source.asString(partContent), is("Joe Blow"));
|
||||
MultiPart.Part part2 = parts.getFirst("stuff");
|
||||
assertThat(part2, notNullValue());
|
||||
partContent = part2.getContentSource();
|
||||
assertThat(Content.Source.asString(partContent), is("aaaabbbbb"));
|
||||
}
|
||||
}
|
||||
|
||||
private class TestContent extends AsyncContent
|
||||
{
|
||||
@Override
|
||||
|
@ -945,4 +989,43 @@ public class MultiPartFormDataTest
|
|||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NonRetainableChunk implements Content.Chunk
|
||||
{
|
||||
private final ByteBuffer _content;
|
||||
private final boolean _isLast;
|
||||
private final Throwable _failure;
|
||||
|
||||
public NonRetainableChunk(Content.Chunk chunk)
|
||||
{
|
||||
_content = BufferUtil.copy(chunk.getByteBuffer());
|
||||
_isLast = chunk.isLast();
|
||||
_failure = chunk.getFailure();
|
||||
chunk.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast()
|
||||
{
|
||||
return _isLast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getFailure()
|
||||
{
|
||||
return _failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retain()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ import org.eclipse.jetty.util.FutureCallback;
|
|||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>Namespace class that contains the definitions of a {@link Source content source},
|
||||
|
@ -48,6 +50,8 @@ import org.eclipse.jetty.util.Promise;
|
|||
*/
|
||||
public class Content
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Content.class);
|
||||
|
||||
private Content()
|
||||
{
|
||||
}
|
||||
|
@ -666,15 +670,22 @@ public class Content
|
|||
* @param last whether the Chunk is the last one
|
||||
* @param retainable the Retainable this Chunk links to
|
||||
* @return a new Chunk
|
||||
* @throws IllegalArgumentException if the {@code Retainable}
|
||||
* {@link Retainable#canRetain() cannot be retained}
|
||||
*/
|
||||
static Chunk asChunk(ByteBuffer byteBuffer, boolean last, Retainable retainable)
|
||||
{
|
||||
if (!retainable.canRetain())
|
||||
throw new IllegalArgumentException("Cannot create chunk from non-retainable " + retainable);
|
||||
if (byteBuffer.hasRemaining())
|
||||
return new ByteBufferChunk.WithRetainable(byteBuffer, last, Objects.requireNonNull(retainable));
|
||||
{
|
||||
if (retainable.canRetain())
|
||||
{
|
||||
return new ByteBufferChunk.WithRetainable(byteBuffer, last, Objects.requireNonNull(retainable));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Copying buffer because could not retain");
|
||||
return new ByteBufferChunk.WithReferenceCount(BufferUtil.copy(byteBuffer), last);
|
||||
}
|
||||
}
|
||||
retainable.release();
|
||||
return last ? EOF : EMPTY;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue