Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

android progressive decoding kitkat compatibility #24038

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.facebook.react.modules.network;

interface IProgressiveStringDecoder {
String decodeNext(byte[] data, int length);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.facebook.react.modules.network;

import android.net.Uri;
import android.os.Build;
import android.util.Base64;

import com.facebook.react.bridge.Arguments;
Expand Down Expand Up @@ -568,7 +569,13 @@ private void readWithProgress(
Charset charset = responseBody.contentType() == null ? StandardCharsets.UTF_8 :
responseBody.contentType().charset(StandardCharsets.UTF_8);

ProgressiveStringDecoder streamDecoder = new ProgressiveStringDecoder(charset);
IProgressiveStringDecoder streamDecoder;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && charset == StandardCharsets.UTF_8) {
streamDecoder = new ProgressiveUTF8StringDecoder();
} else {
streamDecoder = new ProgressiveStringDecoder(charset);
}

InputStream inputStream = responseBody.byteStream();
try {
byte[] buffer = new byte[MAX_CHUNK_SIZE_BETWEEN_FLUSHES];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* given encoding. Otherwise some parts of the data won't be decoded.
*
*/
public class ProgressiveStringDecoder {
public class ProgressiveStringDecoder implements IProgressiveStringDecoder {

private static final String EMPTY_STRING = "";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright (c) Facebook, Inc. and its 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.react.common.StandardCharsets;

/**
* Class to decode UTF-8 strings from byte array chunks.
* UTF-8 could have symbol size from 1 to 4 bytes.
* In case of progressive decoding we could accidentally break the original string.
*
* Use this class to make sure that we extract Strings from byte stream correctly.
*
* This class should be used for KitKat only. It is workaround for the issue:
* https://github.com/facebook/react-native/issues/21006
*
*/
public class ProgressiveUTF8StringDecoder implements IProgressiveStringDecoder {

private byte[] mRemainder = null;

/**
* Bit mask implementation performed 1.5x worse than this one
*
* @param firstByte - first byte of the symbol
* @return count of bytes in the symbol
*/
private int symbolSize(byte firstByte) {
int code = firstByte & 0XFF;
if (code >= 240) {
return 4;
} else if (code >= 224 ) {
return 3;
} else if (code >= 192 ) {
return 2;
}

return 1;
}

/**
* Parses data to UTF-8 String
* If last symbol is partial we save it to mRemainder and concatenate it to the next chunk
* @param data
* @param length length of data to decode
* @return
*/
@Override
public String decodeNext(byte[] data, int length) {
int i = 0;
int lastSymbolSize = 0;
if (mRemainder != null) {
i = symbolSize(mRemainder[0]) - mRemainder.length;
}
while (i < length) {
lastSymbolSize = symbolSize(data[i]);
i += lastSymbolSize;

}

byte[] result;
int symbolsToCopy = length;
boolean hasNewReminder = false;
if (i > length) {
hasNewReminder = true;
symbolsToCopy = i - lastSymbolSize;
}

if (mRemainder == null) {
result = data;
} else {
result = new byte[symbolsToCopy + mRemainder.length];
System.arraycopy(mRemainder, 0, result, 0, mRemainder.length);
System.arraycopy(data, 0, result, mRemainder.length, symbolsToCopy);
mRemainder = null;
symbolsToCopy = result.length;
}

if (hasNewReminder) {
int reminderSize = lastSymbolSize - i + length;
mRemainder = new byte[reminderSize];
System.arraycopy(data, length - reminderSize, mRemainder, 0, reminderSize );
}

return new String(result, 0, symbolsToCopy, StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,29 @@ private int findMethod(String mname, List<JavaModuleWrapper.MethodDescriptor> me
@Test(expected = NativeArgumentsParseException.class)
public void testCallMethodWithoutEnoughArgs() throws Exception {
int methodId = findMethod("regularMethod",mMethods);
Mockito.stub(mArguments.size()).toReturn(1);
Mockito.when(mArguments.size()).thenReturn(1);
mWrapper.invoke(methodId, mArguments);
}

@Test
public void testCallMethodWithEnoughArgs() {
int methodId = findMethod("regularMethod", mMethods);
Mockito.stub(mArguments.size()).toReturn(2);
Mockito.when(mArguments.size()).thenReturn(2);
mWrapper.invoke(methodId, mArguments);
}

@Test
public void testCallAsyncMethodWithEnoughArgs() {
// Promise block evaluates to 2 args needing to be passed from JS
int methodId = findMethod("asyncMethod", mMethods);
Mockito.stub(mArguments.size()).toReturn(3);
Mockito.when(mArguments.size()).thenReturn(3);
mWrapper.invoke(methodId, mArguments);
}

@Test
public void testCallSyncMethod() {
int methodId = findMethod("syncMethod", mMethods);
Mockito.stub(mArguments.size()).toReturn(2);
Mockito.when(mArguments.size()).thenReturn(2);
mWrapper.invoke(methodId, mArguments);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Copyright (c) Facebook, Inc. and its 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 org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import java.nio.charset.Charset;


@RunWith(RobolectricTestRunner.class)
public class ProgressiveUTF8StringDecoderTest {

private static String TEST_DATA_1_BYTE = "Lorem ipsum dolor sit amet, ea ius viris laoreet gloriatur, ea enim illud mel. Ea eligendi erroribus inciderint sea, id nemore sensibus contentiones qui. Eos et nulla abhorreant, noluisse adipiscing reprehendunt an sit. Harum iriure meliore ne nec, clita semper voluptaria at sea. Ius civibus vituperata reprehendunt ut.\n" +
"\n" +
"Sed nisl postea maiorum ex, mea eros verterem ea. Ne usu brute debitis appareat. Ad quem reprimique dissentias duo. Sit an labitur eleifend, illud zril audiam nam ex, epicuri luptatum ne usu. Lorem mundi utinam vix ea.\n" +
"\n" +
"Te eam nominati qualisque. Ut praesent consetetur pro. Soleat vivendum vim ea. Altera dolores eam in. Eum at praesent complectitur. Nec ea inani definitiones, tantas vivendum mei an, mea an ubique omnium latine. Has mundi ocurreret ei, nam ea iuvaret gloriatur.\n" +
"\n" +
"Ad omnes malorum vim, no latine facilisi mel, dicant salutandi conclusionemque ei est. Nam cu partem alterum minimum. Et quo iriure deleniti accommodare, ad impetus perfecto liberavisse pri. Instructior necessitatibus ut mel, ex cum sumo atqui comprehensam, ei nullam oporteat sed. Ius meliore placerat cu.\n" +
"\n" +
"Eum in ferri nobis, eam eu verear facilisis referrentur. Veniam epicuri referrentur at nam. Vel congue diceret fabulas te, ei fabellas temporibus mei. Nemore corrumpit quo ex, et vis soluta reprehendunt. Et eos eripuit atomorum.\n" +
"\n" +
"Eum no novum tantas decore. Indoctum definiebas intellegam ut vel. Cu per ipsum graeco, in nam dico dolore, usu id ludus consulatu. Vis an clita commune, cu quot quaeque cum. In eos semper aperiri. Ne mea probo inermis, no vis audiam volutpat.\n" +
"\n" +
"Cu quaeque scaevola vis. Civibus commune scriptorem vim an, vim ea vocent petentium consequuntur, meis propriae invidunt eam ex. Pro et ponderum recusabo sapientem. Vel legere possim ornatus ne, saepe commodo scaevola an quo. An scaevola repudiandae sed. Eam ei veri nemore.\n" +
"\n" +
"Ullum deleniti cum at. An has soleat docendi, epicuri erroribus inciderint pro ea. Noluisse invidunt splendide quo in, eam odio invenire ea. Eu hinc definiebas scripserit duo, has cu equidem ponderum expetenda, eum vulputate intellegat id. Pri eu natum semper pertinax, ei vel inani aliquip habemus, sit an facer dicam. Et graeci abhorreant contentiones duo, et summo partiendo conclusionemque per.\n" +
"\n" +
"Sed ei etiam iudico abhorreant. Pri an regione fastidii, clita discere eu nec. Torquatos percipitur inciderint eos in, id per prompta blandit. Sit et epicuri deleniti. Per labores corpora no.\n" +
"\n" +
"Quodsi melius facilis pri ei, has adhuc recusabo reprimique ut. Laoreet definitionem cum cu, amet nonumes ut vis, qui ut sonet ancillae. Vim no doctus efficiantur, ancillae indoctum ex sea, vel eu fabulas volumus argumentum. Ex eum aeque commune placerat, nam choro tamquam luptatum et. Ne sea vero idque liberavisse";

private static String TEST_DATA_2_BYTES = "Лорем ипсум долор сит амет, доминг дисцере ад вих, велит игнота ратионибус мел цу. Не вирис малорум яуаеяуе хас, еу либрис доцтус хис. Моллис садипсцинг ан цум, семпер молестие репрехендунт усу те. Цасе аетерно оффендит ан еос. При ан толлит опортере оцурререт, ан яуот мутат трацтатос вих.\n" +
"\n" +
"Нец фалли харум ратионибус еа. Магна адмодум ат нам, яуи еа рецусабо мандамус, аццусам цонсеяуунтур цу хис. Импедит цотидиеяуе улламцорпер еа мел, усу ет долорес аргументум. Веро торяуатос ех нам, цибо либерависсе ест еи. Вис долор омниум сплендиде ад, велит рецусабо цонсететур иус цу.\n" +
"\n" +
"Еи дуо меис атоморум сигниферумяуе, аугуе аццусам мел ет. Ут ностро легендос хонестатис пер, ут яуас мовет сеа. Меа цу продессет аппеллантур. Вис еа яуод оффендит, дебет видерер ет нам.\n" +
"\n" +
"Еам еа дебитис иудицабит, не хас иллуд цивибус. Усу ет алии уллум утамур. Поссит цонституто те яуи, хас ет лаудем аудире, нам еи епицури салутанди. Лудус делицатиссими цум еу, либер адиписцинг еи нец. Ид ерипуит лобортис антиопам хис, санцтус елигенди неглегентур сед ут, вел сентентиае инструцтиор еи. Ан про унум яуалисяуе.\n" +
"\n" +
"Ат еррор алтера сит, пер еу яуот номинави. Пертинах репудиаре цум еу. Еа фуиссет антиопам вим, пробатус реферрентур ут иус. Еум ад модус утрояуе диспутандо.\n" +
"\n" +
"Ехерци бландит ут меа. Солет импедит сед ад. Дуо порро тимеам аудире не, алии ерант номинави цу нец, сит ферри веритус адиписци те. Те меи синт адверсариум, ад феугаит инвидунт луцилиус сед, дицунт нумяуам нам те. Еум дицант елеифенд цонсецтетуер ет, суммо вереар епицуреи не про. Не лудус сцрипта опортере вим, еи дуо идяуе алияуам сигниферумяуе. Цум еу лабитур инвенире, про ессе губергрен темпорибус еи, ад хис минимум пертинах.\n" +
"\n" +
"Дуо ад вери евертитур интеллегат, демоцритум еффициенди дуо ет. Нец но доценди демоцритум сцрипторем, витуперата цонституам нецесситатибус ут вим. Яуи виде санцтус мандамус ан, нонумес принципес вел ат, ех дуо инани нулла. Петентиум маиестатис еам ин, те ерант дебитис еурипидис вис. Но вел антиопам цотидиеяуе еффициантур, сеа еи нибх нонумы инцидеринт.\n" +
"\n" +
"Одио омнес но яуо, популо ноструд иус ад. Инани хонестатис но вис. Хис еу лудус партем персиус, пурто малис витуперата при ан, еи елаборарет ассуеверит вим. Цу бруте утинам тинцидунт вих, цум ад дицтас лобортис лаборамус. Нец хабемус рецусабо ат, ех фацилис денияуе ест. При те велит алияуам аццусамус, юсто утамур антиопам но нам.\n" +
"\n" +
"Про не еррем иудицо мелиоре, еи цибо ерудити санцтус хас. Яуод еяуидем еу вис, вих яуидам легимус ад, ид сеа солум легере мандамус. Аеяуе детрахит ех иус, суас вертерем еум цу. Еи вим алиа ехерци пхаедрум, хас не лаборес цоррумпит. Ат граеци сцрипта вим.\n" +
"\n" +
"Иус ат менандри персеяуерис. Про модус дицта еу, ин граеци доценди фиерент при, еи хас аугуе мандамус дефинитионем. Ет путент интерпретарис сит, перицула сентентиае ат ест. При ут сумо видит волуптатибус, нобис деленити еа.";
private static String TEST_DATA_3_BYTES = "案のづよド捕毎エオ文疑ろめた今宮レ秋像とが供持属ょー真場中ホサヒ不箱らご著質ーぼンろ保6年読さ系蔵べるル緩参フシセタ鮮県フずッ歳民ナセ楽飲匹恒桜ぱ。要電ネソメ嘉負向ス援中ぜく界党フネ属平ぎ象越容レ書95争効99争効7翌テ売約わこよッ紙点発事9入そさ補綱のラず他亭匠ぞ。\n" +
"\n" +
"天レ供内ソ愛7読でぽせ回書ほごしな浅月企設潟せぐり裂個ホヌヤ局題制エ柏央ざぽ。外くにさ下格か終所あ硬当ワ着少選とけリへ康件終にぎ季規らおず給測トユテ考毎サトス事版にーご文8忙チ深暮タヲムラ度6応しぞぎぐ装速て続際ぞ発准揮包孤てい。制はたちき合南む乙甲ゅさと捕4球任条こでン頭広セスモウ月夜エス面陽ヨネ力京ウリ紙聞ト印2火映ラ基頭スフ点愛伎協ねド。\n" +
"\n" +
"属と共代みむもず以監すい者新ス田政家ヱス使校音刑トホ則上ゅぐ一未ヌ意40芸標んは学必強ゅ帝歯没牧具もか。58新イシレ正米ニユ負皇っぐせの必容キソタコ公3容ーつぶべ年然検ざ整賞ニチ注興ぐ放約えあ野夜磨やゃフよ。柳ソシアテ申1科ル舗紀深むぜ競供とび室全ハネ測高エラク権暮ヲクオト館暮ヌ黒杯クリぴぽ火竹ねる種4帰替やあい北問クルゃン登壌粉つどべ。";

private static final String TEST_DATA_4_BYTES ="\uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55" +
"\uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" +
"\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" +
"\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" +
"\uD800\uDE80\uD800\uDE80\uD800\uDE80";

@Test
public void testUnicode1Byte() {
chunkString(TEST_DATA_1_BYTE, 64);
}

@Test
public void testUnicode2Bytes() {
chunkString(TEST_DATA_2_BYTES, 63);
}

@Test
public void testUnicode3Bytes() throws Exception {
chunkString(TEST_DATA_3_BYTES, 64);
}

@Test
public void testUnicode4Bytes() throws Exception {
chunkString(TEST_DATA_4_BYTES, 111);
}

private void chunkString(String originalString, int chunkSize) {
byte data [] = originalString.getBytes(Charset.forName("UTF-8"));

StringBuilder builder = new StringBuilder();
ProgressiveUTF8StringDecoder collector = new ProgressiveUTF8StringDecoder();
byte[] buffer = new byte[chunkSize];
for (int i = 0; i < data.length; i+= chunkSize) {
int bytesRead = Math.min(chunkSize, data.length - i);
System.arraycopy(data, i, buffer, 0, bytesRead );
builder.append(collector.decodeNext(buffer, bytesRead ));
}

String actualString = builder.toString();
Assert.assertEquals(originalString, actualString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.views.text.ReactRawTextShadowNode;
import com.facebook.react.views.view.ReactViewBackgroundDrawable;
import com.facebook.react.views.text.CustomTextTransformSpan;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down