diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java
index 84ba61d740..ed55effa28 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java
@@ -43,6 +43,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
protected AbstractMemoryHttpData(String name, Charset charset, long size) {
super(name, charset, size);
+ byteBuf = EMPTY_BUFFER;
}
@Override
@@ -108,6 +109,10 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
} else if (localsize == 0) {
// Nothing to add and byteBuf already exists
buffer.release();
+ } else if (byteBuf.readableBytes() == 0) {
+ // Previous buffer is empty, so just replace it
+ byteBuf.release();
+ byteBuf = buffer;
} else if (byteBuf instanceof CompositeByteBuf) {
CompositeByteBuf cbb = (CompositeByteBuf) byteBuf;
cbb.addComponent(true, buffer);
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java
index 8a0001e43f..84c056963d 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java
@@ -19,7 +19,6 @@ import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpRequest;
-import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -38,6 +37,15 @@ import java.util.Map.Entry;
*
MemoryAttribute, DiskAttribute or MixedAttribute
* MemoryFileUpload, DiskFileUpload or MixedFileUpload
*
+ * A good example of releasing HttpData once all work is done is as follow:
+ * {@code
+ * for (InterfaceHttpData httpData: decoder.getBodyHttpDatas()) {
+ * httpData.release();
+ * factory.removeHttpDataFromClean(request, httpData);
+ * }
+ * factory.cleanAllHttpData();
+ * decoder.destroy();
+ * }
*/
public class DefaultHttpDataFactory implements HttpDataFactory {
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java
index 2e464225be..266e566523 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java
@@ -121,7 +121,8 @@ public interface HttpData extends InterfaceHttpData, ByteBufHolder {
void delete();
/**
- * Returns the contents of the file item as an array of bytes.
+ * Returns the contents of the file item as an array of bytes.
+ * Note: this method will allocate a lot of memory, if the data is currently stored on the file system.
*
* @return the contents of the file item as an array of bytes.
* @throws IOException
@@ -129,7 +130,8 @@ public interface HttpData extends InterfaceHttpData, ByteBufHolder {
byte[] get() throws IOException;
/**
- * Returns the content of the file item as a ByteBuf
+ * Returns the content of the file item as a ByteBuf.
+ * Note: this method will allocate a lot of memory, if the data is currently stored on the file system.
*
* @return the content of the file item as a ByteBuf
* @throws IOException
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java
index 12f93ae580..34626a8995 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java
@@ -158,7 +158,7 @@ final class HttpPostBodyUtil {
}
/**
- * Try to find LF or CRLF as Line Breaking
+ * Try to find first LF or CRLF as Line Breaking
*
* @param buffer the buffer to search in
* @param index the index to start from in the buffer
@@ -170,7 +170,7 @@ final class HttpPostBodyUtil {
int posFirstChar = buffer.bytesBefore(index, toRead, HttpConstants.LF);
if (posFirstChar == -1) {
// No LF, so neither CRLF
- return -1;
+ return -1;
}
if (posFirstChar > 0 && buffer.getByte(index + posFirstChar - 1) == HttpConstants.CR) {
posFirstChar--;
@@ -178,6 +178,38 @@ final class HttpPostBodyUtil {
return posFirstChar;
}
+ /**
+ * Try to find last LF or CRLF as Line Breaking
+ *
+ * @param buffer the buffer to search in
+ * @param index the index to start from in the buffer
+ * @return a relative position from index > 0 if LF or CRLF is found
+ * or < 0 if not found
+ */
+ static int findLastLineBreak(ByteBuf buffer, int index) {
+ int candidate = findLineBreak(buffer, index);
+ int findCRLF = 0;
+ if (candidate >= 0) {
+ if (buffer.getByte(index + candidate) == HttpConstants.CR) {
+ findCRLF = 2;
+ } else {
+ findCRLF = 1;
+ }
+ candidate += findCRLF;
+ }
+ int next;
+ while (candidate > 0 && (next = findLineBreak(buffer, index + candidate)) >= 0) {
+ candidate += next;
+ if (buffer.getByte(index + candidate) == HttpConstants.CR) {
+ findCRLF = 2;
+ } else {
+ findCRLF = 1;
+ }
+ candidate += findCRLF;
+ }
+ return candidate - findCRLF;
+ }
+
/**
* Try to find the delimiter, with LF or CRLF in front of it (added as delimiters) if needed
*
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
index 1b2d5540f5..43ba3444a5 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
@@ -41,6 +41,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static io.netty.handler.codec.http.multipart.HttpPostBodyUtil.BINARY_STRING;
import static io.netty.handler.codec.http.multipart.HttpPostBodyUtil.BIT_7_STRING;
import static io.netty.handler.codec.http.multipart.HttpPostBodyUtil.BIT_8_STRING;
@@ -1144,8 +1145,16 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
final byte[] bdelimiter = delimiter.getBytes(httpData.getCharset());
int posDelimiter = HttpPostBodyUtil.findDelimiter(undecodedChunk, startReaderIndex, bdelimiter, true);
if (posDelimiter < 0) {
- // Not found but however perhaps because incomplete so search LF or CRLF
- posDelimiter = HttpPostBodyUtil.findLineBreak(undecodedChunk, startReaderIndex);
+ // Not found but however perhaps because incomplete so search LF or CRLF from the end.
+ // Possible last bytes contain partially delimiter
+ // (delimiter is possibly partially there, at least 1 missing byte),
+ // therefore searching last delimiter.length +1 (+1 for CRLF instead of LF)
+ int lastPosition = undecodedChunk.readableBytes() - bdelimiter.length - 1;
+ if (lastPosition < 0) {
+ // Not enough bytes, but at most delimiter.length bytes available so can still try to find CRLF there
+ lastPosition = 0;
+ }
+ posDelimiter = HttpPostBodyUtil.findLastLineBreak(undecodedChunk, startReaderIndex + lastPosition);
if (posDelimiter < 0) {
// not found so this chunk can be fully added
ByteBuf content = undecodedChunk.copy();
@@ -1157,18 +1166,21 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
undecodedChunk.readerIndex(startReaderIndex);
undecodedChunk.writerIndex(startReaderIndex);
return false;
- } else if (posDelimiter > 0) {
- // Not fully but still some bytes to provide: httpData is not yet finished since delimiter not found
- ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
- try {
- httpData.addContent(content, false);
- } catch (IOException e) {
- throw new ErrorDataDecoderException(e);
- }
- rewriteCurrentBuffer(undecodedChunk, posDelimiter);
+ }
+ // posDelimiter is not from startReaderIndex but from startReaderIndex + lastPosition
+ posDelimiter += lastPosition;
+ if (posDelimiter == 0) {
+ // Nothing to add
return false;
}
- // Empty chunk or so
+ // Not fully but still some bytes to provide: httpData is not yet finished since delimiter not found
+ ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
+ try {
+ httpData.addContent(content, false);
+ } catch (IOException e) {
+ throw new ErrorDataDecoderException(e);
+ }
+ rewriteCurrentBuffer(undecodedChunk, posDelimiter);
return false;
}
// Delimiter found at posDelimiter, including LF or CRLF, so httpData has its last chunk
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
index 93991c375a..40f2f400a4 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
@@ -25,6 +25,7 @@ import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
@@ -1003,27 +1004,44 @@ public class HttpPostRequestDecoderTest {
HttpPostMultipartRequestDecoder decoder = new HttpPostMultipartRequestDecoder(factory, request);
decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(prefix.getBytes(CharsetUtil.UTF_8))));
+ assertNotNull(((HttpData) decoder.currentPartialHttpData()).content());
byte[] body = new byte[bytesPerChunk];
Arrays.fill(body, (byte) 1);
+ // Set first bytes as CRLF to ensure it is correctly getting the last CRLF
+ body[0] = HttpConstants.CR;
+ body[1] = HttpConstants.LF;
for (int i = 0; i < nbChunks; i++) {
ByteBuf content = Unpooled.wrappedBuffer(body, 0, bytesPerChunk);
- decoder.offer(new DefaultHttpContent(content)); // **OutOfMemory here**
+ decoder.offer(new DefaultHttpContent(content)); // **OutOfMemory previously here**
+ assertNotNull(((HttpData) decoder.currentPartialHttpData()).content());
content.release();
}
byte[] bsuffix1 = suffix1.getBytes(CharsetUtil.UTF_8);
- byte[] lastbody = new byte[bytesLastChunk + bsuffix1.length];
- Arrays.fill(body, (byte) 1);
+ byte[] previousLastbody = new byte[bytesLastChunk - bsuffix1.length];
+ byte[] lastbody = new byte[2 * bsuffix1.length];
+ Arrays.fill(previousLastbody, (byte) 1);
+ previousLastbody[0] = HttpConstants.CR;
+ previousLastbody[1] = HttpConstants.LF;
+ Arrays.fill(lastbody, (byte) 1);
+ lastbody[0] = HttpConstants.CR;
+ lastbody[1] = HttpConstants.LF;
for (int i = 0; i < bsuffix1.length; i++) {
- lastbody[bytesLastChunk + i] = bsuffix1[i];
+ lastbody[bsuffix1.length + i] = bsuffix1[i];
}
- ByteBuf content2 = Unpooled.wrappedBuffer(lastbody, 0, lastbody.length);
+ ByteBuf content2 = Unpooled.wrappedBuffer(previousLastbody, 0, previousLastbody.length);
decoder.offer(new DefaultHttpContent(content2));
+ assertNotNull(((HttpData) decoder.currentPartialHttpData()).content());
+ content2.release();
+ content2 = Unpooled.wrappedBuffer(lastbody, 0, lastbody.length);
+ decoder.offer(new DefaultHttpContent(content2));
+ assertNotNull(((HttpData) decoder.currentPartialHttpData()).content());
content2.release();
content2 = Unpooled.wrappedBuffer(suffix2.getBytes(CharsetUtil.UTF_8));
decoder.offer(new DefaultHttpContent(content2));
+ assertNull(decoder.currentPartialHttpData());
content2.release();
decoder.offer(new DefaultLastHttpContent());