Skip to content

Commit aad4dbb

Browse files
dryganetsfacebook-github-bot
authored andcommitted
OkHttp is more strict than other http libraries. (#21231)
Summary: It crashes with IllegalStateException in case you pass a wrong URL. It crashes if it meets unexpected symbols in the header name and value, while standard says it is not recommended to use those symbols not that they are prohibited. The headers handing is a special use case as a client might have an auth token in the header. In this case, we want to get 401 error response from the server to find out that token is wrong. In case of the onerror client will continue to retry with an existing token. [ANDROID][Fixed] - Networking: Passing invalid URL not crashes the app instead onerror callback of HttpClient is called. Invalid symbols are stripped from the headers to allow HTTP query to fail with 401 error code in case of the broken token. Pull Request resolved: #21231 Reviewed By: axe-fb Differential Revision: D10222129 Pulled By: hramos fbshipit-source-id: b23340692d0fb059a90e338fa85ad4d9612275f2
1 parent fe3aebf commit aad4dbb

File tree

4 files changed

+148
-4
lines changed

4 files changed

+148
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.modules.network;
9+
10+
/**
11+
*
12+
* The class purpose is to weaken too strict OkHttp restriction on http headers.
13+
* See: https://github.com/square/okhttp/issues/2016
14+
* Auth headers might have an Authentication information. It is better to get 401 from the
15+
* server in this case, rather than non descriptive error as 401 could be handled to invalidate
16+
* the wrong token in the client code.
17+
*/
18+
public class HeaderUtil {
19+
20+
public static String stripHeaderName(String name) {
21+
StringBuilder builder = new StringBuilder(name.length());
22+
boolean modified = false;
23+
for (int i = 0, length = name.length(); i < length; i++) {
24+
char c = name.charAt(i);
25+
if (c > '\u0020' && c < '\u007f') {
26+
builder.append(c);
27+
} else {
28+
modified = true;
29+
}
30+
}
31+
return modified ? builder.toString() : name;
32+
}
33+
34+
public static String stripHeaderValue(String value) {
35+
StringBuilder builder = new StringBuilder(value.length());
36+
boolean modified = false;
37+
for (int i = 0, length = value.length(); i < length; i++) {
38+
char c = value.charAt(i);
39+
if ((c > '\u001f' && c < '\u007f') || c == '\t' ) {
40+
builder.append(c);
41+
} else {
42+
modified = true;
43+
}
44+
}
45+
return modified ? builder.toString() : value;
46+
}
47+
}

ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java

+24-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.net.Uri;
1010
import android.util.Base64;
1111

12+
import com.facebook.common.logging.FLog;
1213
import com.facebook.react.bridge.Arguments;
1314
import com.facebook.react.bridge.GuardedAsyncTask;
1415
import com.facebook.react.bridge.ReactApplicationContext;
@@ -104,6 +105,7 @@ public interface ResponseHandler {
104105

105106
protected static final String NAME = "Networking";
106107

108+
private static final String TAG = "NetworkingModule";
107109
private static final String CONTENT_ENCODING_HEADER_NAME = "content-encoding";
108110
private static final String CONTENT_TYPE_HEADER_NAME = "content-type";
109111
private static final String REQUEST_BODY_KEY_STRING = "string";
@@ -234,10 +236,29 @@ public void removeResponseHandler(ResponseHandler handler) {
234236
}
235237

236238
@ReactMethod
239+
public void sendRequest(
240+
String method,
241+
String url,
242+
final int requestId,
243+
ReadableArray headers,
244+
ReadableMap data,
245+
final String responseType,
246+
final boolean useIncrementalUpdates,
247+
int timeout,
248+
boolean withCredentials) {
249+
try {
250+
sendRequestInternal(method, url, requestId, headers, data, responseType,
251+
useIncrementalUpdates, timeout, withCredentials);
252+
} catch (Throwable th) {
253+
FLog.e(TAG, "Failed to send url request: " + url, th);
254+
ResponseUtil.onRequestError(getEventEmitter(), requestId, th.getMessage(), th);
255+
}
256+
}
257+
237258
/**
238259
* @param timeout value of 0 results in no timeout
239260
*/
240-
public void sendRequest(
261+
public void sendRequestInternal(
241262
String method,
242263
String url,
243264
final int requestId,
@@ -720,8 +741,8 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
720741
if (header == null || header.size() != 2) {
721742
return null;
722743
}
723-
String headerName = header.getString(0);
724-
String headerValue = header.getString(1);
744+
String headerName = HeaderUtil.stripHeaderName(header.getString(0));
745+
String headerValue = HeaderUtil.stripHeaderValue(header.getString(1));
725746
if (headerName == null || headerValue == null) {
726747
return null;
727748
}

ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public static void onRequestError(
8585
RCTDeviceEventEmitter eventEmitter,
8686
int requestId,
8787
String error,
88-
IOException e) {
88+
Throwable e) {
8989
WritableArray args = Arguments.createArray();
9090
args.pushInt(requestId);
9191
args.pushString(error);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.modules.network;
9+
10+
import org.junit.Test;
11+
12+
import static org.junit.Assert.assertEquals;
13+
14+
public class HeaderUtilTest {
15+
public static final String TABULATION_TEST = "\teyJhbGciOiJS\t";
16+
public static final String TABULATION_STRIP_EXPECTED = "eyJhbGciOiJS";
17+
public static final String NUMBERS_TEST = "0123456789";
18+
public static final String SPECIALS_TEST = "!@#$%^&*()-=_+{}[]\\|;:'\",.<>/?";
19+
public static final String ALPHABET_TEST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWHYZ";
20+
public static final String VALUE_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u001f";
21+
public static final String NAME_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u0020\u001f";
22+
public static final String BANNED_TEST_EXPECTED = "name";
23+
24+
@Test
25+
public void nameStripKeepsLetters() {
26+
assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderName(ALPHABET_TEST));
27+
28+
}
29+
30+
@Test
31+
public void valueStripKeepsLetters() {
32+
assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderValue(ALPHABET_TEST));
33+
}
34+
35+
@Test
36+
public void nameStripKeepsNumbers() {
37+
assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderName(NUMBERS_TEST));
38+
39+
}
40+
41+
@Test
42+
public void valueStripKeepsNumbers() {
43+
assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderValue(NUMBERS_TEST));
44+
}
45+
46+
@Test
47+
public void valueStripKeepsSpecials() {
48+
assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderValue(SPECIALS_TEST));
49+
}
50+
51+
@Test
52+
public void nameStripKeepsSpecials() {
53+
assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderName(SPECIALS_TEST));
54+
}
55+
56+
@Test
57+
public void valueStripKeepsTabs() {
58+
assertEquals(TABULATION_TEST, HeaderUtil.stripHeaderValue(TABULATION_TEST));
59+
}
60+
61+
@Test
62+
public void nameStripDeletesTabs() {
63+
assertEquals(TABULATION_STRIP_EXPECTED, HeaderUtil.stripHeaderName(TABULATION_TEST));
64+
}
65+
66+
@Test
67+
public void valueStripRemovesExtraSymbols() {
68+
assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderValue(VALUE_BANNED_SYMBOLS_TEST));
69+
}
70+
71+
@Test
72+
public void nameStripRemovesExtraSymbols() {
73+
assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderName(NAME_BANNED_SYMBOLS_TEST));
74+
}
75+
76+
}

0 commit comments

Comments
 (0)