mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-10 22:47:58 +08:00
Fix crash due to mishandling of UTF-8 in progressive download.
Summary:
Fixes:
```
Fatal Exception: java.lang.RuntimeException: Failed to create String from JSON
at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:31)
at android.os.Looper.loop(Looper.java:234)
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:193)
at java.lang.Thread.run(Thread.java:818)
```
JavaScriptCore is very strict about invalid UTF symbols.
So if you pass an invalid UTF-8 string to it the string will be decoded as an empty string.
The current implementation of progressive downloading for Android blindly cuts the response in 8KB chunks.
That could cause a problem in case the last symbol in the chunk is multi-byte.
To prevent it I added a class which determines if this is the case and cut the string in the appropriate place.
A remainder is prepended to the next chunk of data.
This should fix the root cause of this issue:
https://github.com/facebook/react-native/issues/10756
Closes https://github.com/facebook/react-native/pull/15295
Differential Revision: D6712570
Pulled By: hramos
fbshipit-source-id: f07fcf0f011c2133c8e860ceb0588a29d36d07fb
This commit is contained in:
committed by
Facebook Github Bot
parent
2fe7483c36
commit
9024f56bda
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
package com.facebook.react.common;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Not all versions of Android SDK have this class in nio package.
|
||||
* This is the reason to have it around.
|
||||
*/
|
||||
public class StandardCharsets {
|
||||
|
||||
/**
|
||||
* Eight-bit UCS Transformation Format
|
||||
*/
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
/**
|
||||
* Sixteen-bit UCS Transformation Format, byte order identified by an
|
||||
* optional byte-order mark
|
||||
*/
|
||||
public static final Charset UTF_16 = Charset.forName("UTF-16");
|
||||
|
||||
/**
|
||||
* Sixteen-bit UCS Transformation Format, big-endian byte order
|
||||
*/
|
||||
public static final Charset UTF_16BE = Charset.forName("UTF-16BE");
|
||||
/**
|
||||
* Sixteen-bit UCS Transformation Format, little-endian byte order
|
||||
*/
|
||||
public static final Charset UTF_16LE = Charset.forName("UTF-16LE");
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -29,6 +30,7 @@ import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.StandardCharsets;
|
||||
import com.facebook.react.common.network.OkHttpCallUtil;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
|
||||
@@ -408,20 +410,24 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
Reader reader = responseBody.charStream();
|
||||
Charset charset = responseBody.contentType() == null ? StandardCharsets.UTF_8 :
|
||||
responseBody.contentType().charset(StandardCharsets.UTF_8);
|
||||
|
||||
ProgressiveStringDecoder streamDecoder = new ProgressiveStringDecoder(charset);
|
||||
InputStream inputStream = responseBody.byteStream();
|
||||
try {
|
||||
char[] buffer = new char[MAX_CHUNK_SIZE_BETWEEN_FLUSHES];
|
||||
byte[] buffer = new byte[MAX_CHUNK_SIZE_BETWEEN_FLUSHES];
|
||||
int read;
|
||||
while ((read = reader.read(buffer)) != -1) {
|
||||
while ((read = inputStream.read(buffer)) != -1) {
|
||||
ResponseUtil.onIncrementalDataReceived(
|
||||
eventEmitter,
|
||||
requestId,
|
||||
new String(buffer, 0, read),
|
||||
streamDecoder.decodeNext(buffer, read),
|
||||
totalBytesRead,
|
||||
contentLength);
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
|
||||
/**
|
||||
* Class to decode encoded strings from byte array chunks.
|
||||
* As in different encodings single character could take up to 4 characters byte array passed to
|
||||
* decode could have parts of the characters which can't be correctly decoded.
|
||||
*
|
||||
* This class is designed in assumption that original byte stream is correctly formatted string in
|
||||
* given encoding. Otherwise some parts of the data won't be decoded.
|
||||
*
|
||||
*/
|
||||
public class ProgressiveStringDecoder {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private final CharsetDecoder mDecoder;
|
||||
|
||||
private byte[] remainder = null;
|
||||
|
||||
/**
|
||||
* @param charset expected charset of the data
|
||||
*/
|
||||
public ProgressiveStringDecoder(Charset charset) {
|
||||
mDecoder = charset.newDecoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses data to String
|
||||
* If there is a partial multi-byte symbol on the edge of the String it get saved to the
|
||||
* reminder and added to the string on the decodeNext call.
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public String decodeNext(byte[] data, int length) {
|
||||
byte[] decodeData;
|
||||
|
||||
if (remainder != null) {
|
||||
decodeData = new byte[remainder.length + length];
|
||||
System.arraycopy(remainder, 0, decodeData, 0, remainder.length);
|
||||
System.arraycopy(data, 0, decodeData, remainder.length, length);
|
||||
length += remainder.length;
|
||||
} else {
|
||||
decodeData = data;
|
||||
}
|
||||
|
||||
ByteBuffer decodeBuffer = ByteBuffer.wrap(decodeData, 0, length);
|
||||
CharBuffer result = null;
|
||||
boolean decoded = false;
|
||||
int remainderLenght = 0;
|
||||
while (!decoded && (remainderLenght < 4)) {
|
||||
try {
|
||||
result = mDecoder.decode(decodeBuffer);
|
||||
decoded = true;
|
||||
} catch (CharacterCodingException e) {
|
||||
remainderLenght++;
|
||||
decodeBuffer = ByteBuffer.wrap(decodeData, 0, length - remainderLenght);
|
||||
}
|
||||
}
|
||||
boolean hasRemainder = decoded && remainderLenght > 0;
|
||||
if (hasRemainder) {
|
||||
remainder = new byte[remainderLenght];
|
||||
System.arraycopy(decodeData, length - remainderLenght, remainder, 0, remainderLenght);
|
||||
} else {
|
||||
remainder = null;
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
FLog.w(ReactConstants.TAG, "failed to decode string from byte array");
|
||||
return EMPTY_STRING;
|
||||
} else {
|
||||
return new String(result.array(), 0, result.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user