Skip to content

Commit

Permalink
fix OTA issue #1378
Browse files Browse the repository at this point in the history
  • Loading branch information
doom369 committed Jan 23, 2021
1 parent cc00de7 commit c1971b6
Show file tree
Hide file tree
Showing 5 changed files with 375 additions and 6 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@
<maven-checkstyle-plugin.version>3.0.0</maven-checkstyle-plugin.version>

<!-- dependencies -->
<netty.version>4.1.56.Final</netty.version>
<netty.boring.ssl.version>2.0.35.Final</netty.boring.ssl.version>
<netty.version>4.1.58.Final</netty.version>
<netty.boring.ssl.version>2.0.36.Final</netty.boring.ssl.version>
<log4j2.version>2.14.0</log4j2.version>
<jackson-databind.version>2.12.0</jackson-databind.version>
<jackson-databind.version>2.12.1</jackson-databind.version>
<disruptor.version>3.4.2</disruptor.version>
<async-http-client.version>2.12.1</async-http-client.version>
<postgresql.version>42.2.16</postgresql.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<InterfaceHttpData> getBodyHttpDatas() {
return decoder.getBodyHttpDatas();
}

@Override
public List<InterfaceHttpData> 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)};
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Loading

0 comments on commit c1971b6

Please sign in to comment.