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 35646952b2c..00000000000 --- 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 00000000000..32e2d7f2d03 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressiveStringDecoder.kt @@ -0,0 +1,85 @@ +/* + * 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.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 The byte array containing the encoded string data. + * @param initialLength The number of valid bytes in the provided data array that should be + * decoded. + * @return The decoded string. If decoding fails, an empty string is returned. + */ + fun decodeNext(data: ByteArray, initialLength: Int): String { + var length = initialLength + var decodeData: ByteArray = data + + remainder?.let { r -> + decodeData = ByteArray(r.size + length) + System.arraycopy(r, 0, decodeData, 0, r.size) + System.arraycopy(data, 0, decodeData, r.size, length) + length += r.size + } + + var decodeBuffer = ByteBuffer.wrap(decodeData, 0, length) + var result: CharBuffer? = null + var decoded = false + var remainderLength = 0 + + while (!decoded && (remainderLength < 4)) { + try { + result = decoder.decode(decodeBuffer) + decoded = true + } catch (e: CharacterCodingException) { + remainderLength++ + decodeBuffer = ByteBuffer.wrap(decodeData, 0, length - remainderLength) + } + } + + val hasRemainder = decoded && remainderLength > 0 + if (hasRemainder) { + remainder = + ByteArray(remainderLength).apply { + System.arraycopy(decodeData, length - remainderLength, this, 0, remainderLength) + } + } else { + remainder = null + } + + if (!decoded) { + FLog.w(ReactConstants.TAG, "failed to decode string from byte array") + return EMPTY_STRING + } else { + return result?.let { String(it.array(), 0, it.length) } ?: EMPTY_STRING + } + } + + companion object { + private const val EMPTY_STRING = "" + } +}