diff --git a/pom.xml b/pom.xml
index bf618c8db0..e4a892d3fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -157,10 +157,10 @@
3.0.0
- 4.1.56.Final
- 2.0.35.Final
+ 4.1.58.Final
+ 2.0.36.Final
2.14.0
- 2.12.0
+ 2.12.1
3.4.2
2.12.1
42.2.16
diff --git a/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostMultipartRequestDecoder.java b/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostMultipartRequestDecoder.java
new file mode 100644
index 0000000000..dc6915ea28
--- /dev/null
+++ b/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostMultipartRequestDecoder.java
@@ -0,0 +1,34 @@
+package cc.blynk.core.http.handlers;
+
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.multipart.HttpDataFactory;
+import io.netty.handler.codec.http.multipart.HttpPostMultipartRequestDecoder;
+
+import java.nio.charset.Charset;
+
+/**
+ * Full copy of HttpPostRequestDecoder to fix
+ * https://github.com/netty/netty/issues/10281
+ */
+public class BlynkHttpPostMultipartRequestDecoder extends HttpPostMultipartRequestDecoder {
+
+ private final boolean state;
+
+ public BlynkHttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+ super(factory, request, charset);
+ this.state = true;
+ }
+
+ @Override
+ public HttpPostMultipartRequestDecoder offer(HttpContent content) {
+ //this is very dirty hack
+ //we skip the first invocation of the offer
+ //it wont work for every case, but we have Aggregate handler in the pipeline
+ //so we are covered here :)
+ if (state) {
+ return super.offer(content);
+ }
+ return null;
+ }
+}
diff --git a/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostRequestDecoder.java b/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostRequestDecoder.java
new file mode 100644
index 0000000000..9c00365252
--- /dev/null
+++ b/server/http-core/src/main/java/cc/blynk/core/http/handlers/BlynkHttpPostRequestDecoder.java
@@ -0,0 +1,241 @@
+package cc.blynk.core.http.handlers;
+
+import io.netty.handler.codec.http.HttpConstants;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
+import io.netty.handler.codec.http.multipart.HttpDataFactory;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
+import io.netty.handler.codec.http.multipart.HttpPostStandardRequestDecoder;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpPostRequestDecoder;
+import io.netty.util.internal.ObjectUtil;
+import io.netty.util.internal.StringUtil;
+
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * Full copy of HttpPostRequestDecoder to fix
+ * https://github.com/netty/netty/issues/10281
+ */
+public class BlynkHttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
+
+ private final InterfaceHttpPostRequestDecoder decoder;
+
+ /**
+ *
+ * @param request
+ * the request to decode
+ * @throws NullPointerException
+ * for request
+ * @throws HttpPostRequestDecoder.ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public BlynkHttpPostRequestDecoder(HttpRequest request) {
+ this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
+ }
+
+ /**
+ *
+ * @param factory
+ * the factory used to create InterfaceHttpData
+ * @param request
+ * the request to decode
+ * @throws NullPointerException
+ * for request or factory
+ * @throws HttpPostRequestDecoder.ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public BlynkHttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) {
+ this(factory, request, HttpConstants.DEFAULT_CHARSET);
+ }
+
+ /**
+ *
+ * @param factory
+ * the factory used to create InterfaceHttpData
+ * @param request
+ * the request to decode
+ * @param charset
+ * the charset to use as default
+ * @throws NullPointerException
+ * for request or charset or factory
+ * @throws HttpPostRequestDecoder.ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public BlynkHttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+ ObjectUtil.checkNotNull(factory, "factory");
+ ObjectUtil.checkNotNull(request, "request");
+ ObjectUtil.checkNotNull(charset, "charset");
+
+ // Fill default values
+ if (isMultipart(request)) {
+ decoder = new BlynkHttpPostMultipartRequestDecoder(factory, request, charset);
+ } else {
+ decoder = new HttpPostStandardRequestDecoder(factory, request, charset);
+ }
+ }
+
+ /**
+ * Check if the given request is a multipart request
+ * @return True if the request is a Multipart request
+ */
+ public static boolean isMultipart(HttpRequest request) {
+ String mimeType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+ if (mimeType != null && mimeType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString())) {
+ return getMultipartDataBoundary(mimeType) != null;
+ }
+ return false;
+ }
+
+ /**
+ * Check from the request ContentType if this request is a Multipart request.
+ * @return an array of String if multipartDataBoundary exists with the multipartDataBoundary
+ * as first element, charset if any as second (missing if not set), else null
+ */
+ protected static String[] getMultipartDataBoundary(String contentType) {
+ // Check if Post using "multipart/form-data; boundary=--89421926422648 [; charset=xxx]"
+ String[] headerContentType = splitHeaderContentType(contentType);
+ final String multiPartHeader = HttpHeaderValues.MULTIPART_FORM_DATA.toString();
+ if (headerContentType[0].regionMatches(true, 0, multiPartHeader, 0, multiPartHeader.length())) {
+ int mrank;
+ int crank;
+ final String boundaryHeader = HttpHeaderValues.BOUNDARY.toString();
+ if (headerContentType[1].regionMatches(true, 0, boundaryHeader, 0, boundaryHeader.length())) {
+ mrank = 1;
+ crank = 2;
+ } else if (headerContentType[2].regionMatches(true, 0, boundaryHeader, 0, boundaryHeader.length())) {
+ mrank = 2;
+ crank = 1;
+ } else {
+ return null;
+ }
+ String boundary = StringUtil.substringAfter(headerContentType[mrank], '=');
+ if (boundary == null) {
+ throw new HttpPostRequestDecoder.ErrorDataDecoderException("Needs a boundary value");
+ }
+ if (boundary.charAt(0) == '"') {
+ String bound = boundary.trim();
+ int index = bound.length() - 1;
+ if (bound.charAt(index) == '"') {
+ boundary = bound.substring(1, index);
+ }
+ }
+ final String charsetHeader = HttpHeaderValues.CHARSET.toString();
+ if (headerContentType[crank].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
+ String charset = StringUtil.substringAfter(headerContentType[crank], '=');
+ if (charset != null) {
+ return new String[] {"--" + boundary, charset};
+ }
+ }
+ return new String[] {"--" + boundary};
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isMultipart() {
+ return decoder.isMultipart();
+ }
+
+ @Override
+ public void setDiscardThreshold(int discardThreshold) {
+ decoder.setDiscardThreshold(discardThreshold);
+ }
+
+ @Override
+ public int getDiscardThreshold() {
+ return decoder.getDiscardThreshold();
+ }
+
+ @Override
+ public List getBodyHttpDatas() {
+ return decoder.getBodyHttpDatas();
+ }
+
+ @Override
+ public List getBodyHttpDatas(String name) {
+ return decoder.getBodyHttpDatas(name);
+ }
+
+ @Override
+ public InterfaceHttpData getBodyHttpData(String name) {
+ return decoder.getBodyHttpData(name);
+ }
+
+ @Override
+ public InterfaceHttpPostRequestDecoder offer(HttpContent content) {
+ return decoder.offer(content);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return decoder.hasNext();
+ }
+
+ @Override
+ public InterfaceHttpData next() {
+ return decoder.next();
+ }
+
+ @Override
+ public InterfaceHttpData currentPartialHttpData() {
+ return decoder.currentPartialHttpData();
+ }
+
+ @Override
+ public void destroy() {
+ decoder.destroy();
+ }
+
+ @Override
+ public void cleanFiles() {
+ decoder.cleanFiles();
+ }
+
+ @Override
+ public void removeHttpDataFromClean(InterfaceHttpData data) {
+ decoder.removeHttpDataFromClean(data);
+ }
+
+ /**
+ * Split the very first line (Content-Type value) in 3 Strings
+ *
+ * @return the array of 3 Strings
+ */
+ private static String[] splitHeaderContentType(String sb) {
+ int aStart;
+ int aEnd;
+ int bStart;
+ int bEnd;
+ int cStart;
+ int cEnd;
+ aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
+ aEnd = sb.indexOf(';');
+ if (aEnd == -1) {
+ return new String[] {sb, "", ""};
+ }
+ bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd + 1);
+ if (sb.charAt(aEnd - 1) == ' ') {
+ aEnd--;
+ }
+ bEnd = sb.indexOf(';', bStart);
+ if (bEnd == -1) {
+ bEnd = HttpPostBodyUtil.findEndOfString(sb);
+ return new String[] {sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), ""};
+ }
+ cStart = HttpPostBodyUtil.findNonWhitespace(sb, bEnd + 1);
+ if (sb.charAt(bEnd - 1) == ' ') {
+ bEnd--;
+ }
+ cEnd = HttpPostBodyUtil.findEndOfString(sb);
+ return new String[] {sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), sb.substring(cStart, cEnd)};
+ }
+
+}
diff --git a/server/http-core/src/main/java/cc/blynk/core/http/handlers/HttpPostBodyUtil.java b/server/http-core/src/main/java/cc/blynk/core/http/handlers/HttpPostBodyUtil.java
new file mode 100644
index 0000000000..899569b12a
--- /dev/null
+++ b/server/http-core/src/main/java/cc/blynk/core/http/handlers/HttpPostBodyUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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:
+ *
+ * http://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 cc.blynk.core.http.handlers;
+
+/**
+ * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder
+ */
+final class HttpPostBodyUtil {
+
+ public static final int chunkSize = 8096;
+
+ /**
+ * Allowed mechanism for multipart
+ * mechanism := "7bit"
+ / "8bit"
+ / "binary"
+ Not allowed: "quoted-printable"
+ / "base64"
+ */
+ public enum TransferEncodingMechanism {
+ /**
+ * Default encoding
+ */
+ BIT7("7bit"),
+ /**
+ * Short lines but not in ASCII - no encoding
+ */
+ BIT8("8bit"),
+ /**
+ * Could be long text not in ASCII - no encoding
+ */
+ BINARY("binary");
+
+ private final String value;
+
+ TransferEncodingMechanism(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ private HttpPostBodyUtil() {
+ }
+
+ /**
+ * Find the first non whitespace
+ * @return the rank of the first non whitespace
+ */
+ static int findNonWhitespace(String sb, int offset) {
+ int result;
+ for (result = offset; result < sb.length(); result++) {
+ if (!Character.isWhitespace(sb.charAt(result))) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Find the end of String
+ * @return the rank of the end of string
+ */
+ static int findEndOfString(String sb) {
+ int result;
+ for (result = sb.length(); result > 0; result--) {
+ if (!Character.isWhitespace(sb.charAt(result - 1))) {
+ break;
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/server/http-core/src/main/java/cc/blynk/core/http/handlers/UploadHandler.java b/server/http-core/src/main/java/cc/blynk/core/http/handlers/UploadHandler.java
index 23cfc9f682..5d33be86f5 100644
--- a/server/http-core/src/main/java/cc/blynk/core/http/handlers/UploadHandler.java
+++ b/server/http-core/src/main/java/cc/blynk/core/http/handlers/UploadHandler.java
@@ -26,10 +26,10 @@
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpPostRequestDecoder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -51,7 +51,7 @@ public class UploadHandler extends SimpleChannelInboundHandler {
private static final HttpDataFactory factory = new DefaultHttpDataFactory(true);
final String handlerUri;
- private HttpPostRequestDecoder decoder;
+ private InterfaceHttpPostRequestDecoder decoder;
private final String staticFolderPath;
private final String uploadFolder;
@@ -85,7 +85,7 @@ public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
try {
log.debug("Incoming {} {}", req.method(), req.uri());
- decoder = new HttpPostRequestDecoder(factory, req);
+ decoder = new BlynkHttpPostRequestDecoder(factory, req);
} catch (ErrorDataDecoderException e) {
log.error("Error creating http post request decoder.", e);
ctx.writeAndFlush(badRequest(e.getMessage()));