From 77d92466bc64a380caadaf9994055ff0d87f346d Mon Sep 17 00:00:00 2001 From: Thomas Nardone Date: Mon, 15 Apr 2024 14:07:15 -0700 Subject: [PATCH] ProgressiveStringDecoder (#44027) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44027 Changelog: [Internal] Reviewed By: cortinico Differential Revision: D55981019 --- .../network/ProgressiveStringDecoder.java | 88 ------------------- .../network/ProgressiveStringDecoder.kt | 82 +++++++++++++++++ 2 files changed, 82 insertions(+), 88 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.java deleted file mode 100644 index 35646952b2c2a2..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -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. - */ -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 remainderLength = 0; - while (!decoded && (remainderLength < 4)) { - try { - result = mDecoder.decode(decodeBuffer); - decoded = true; - } catch (CharacterCodingException e) { - remainderLength++; - decodeBuffer = ByteBuffer.wrap(decodeData, 0, length - remainderLength); - } - } - boolean hasRemainder = decoded && remainderLength > 0; - if (hasRemainder) { - remainder = new byte[remainderLength]; - System.arraycopy(decodeData, length - remainderLength, remainder, 0, remainderLength); - } 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()); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.kt new file mode 100644 index 00000000000000..d239b5b38081f8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +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. + */ +internal class ProgressiveStringDecoder(charset: Charset) { + + private val decoder: CharsetDecoder = charset.newDecoder() + private var remainder: ByteArray? = null + + /** + * 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 + */ + fun decodeNext(data: ByteArray, length: Int): String { + var bufferLength = length + val currentRemainder = remainder + val decodeData: ByteArray = + if (currentRemainder != null) { + ByteArray(currentRemainder.size + length).also { newData -> + System.arraycopy(currentRemainder, 0, newData, 0, currentRemainder.size) + System.arraycopy(data, 0, newData, currentRemainder.size, length) + bufferLength += currentRemainder.size + } + } else { + data + } + var decodeBuffer = ByteBuffer.wrap(decodeData, 0, bufferLength) + var result: CharBuffer? = null + var remainderLength = 0 + while (result == null && (remainderLength < MAX_BYTES)) { + try { + result = decoder.decode(decodeBuffer) + } catch (e: CharacterCodingException) { + remainderLength++ + decodeBuffer = ByteBuffer.wrap(decodeData, 0, bufferLength - remainderLength) + } + } + val hasRemainder = result != null && (remainderLength > 0) + remainder = + if (hasRemainder) { + ByteArray(remainderLength).also { newRemainder -> + System.arraycopy( + decodeData, bufferLength - remainderLength, newRemainder, 0, remainderLength) + } + } else { + null + } + if (result == null) { + FLog.w(ReactConstants.TAG, "failed to decode string from byte array") + return EMPTY_STRING + } else { + return String(result.array(), 0, result.length) + } + } +} + +private const val EMPTY_STRING = "" +private const val MAX_BYTES = 4