From 4a807761a4aca9e551ff2cee8ca18a2450fb11ca Mon Sep 17 00:00:00 2001 From: Nate Hunzaker Date: Thu, 21 Feb 2019 22:33:18 -0800 Subject: [PATCH] Prevent okhttp from adding ;charset=utf8 to ContentType Header (#23580) Summary: Before this commit, `fetch()` calls append `"charset=utf8"` to the `Content-Type` header on Android (and not on iOS). This is because of an implementation detail in the okhttp library. This means that you can make a call on the JavaScript side like: ```javascript let body = JSON.stringify({ key: "value" }); let headers = { "Content-Type": "application/json" }; fetch("http://10.0.2.2:3000", { method: "POST", body, headers }); ``` However the resulting request appends the utf8 character: ``` POST - 13:34:32: content-type: application/json; charset=utf-8 content-length: 15 host: 10.0.2.2:3000 connection: Keep-Alive accept-encoding: gzip user-agent: okhttp/3.12.1 ``` Passing byte array into the RequestBody avoids this, as recommended by a maintainer of okhttp: https://github.com/square/okhttp/issues/2099#issuecomment-366757161 Related issues: https://github.com/facebook/react-native/issues/8237 [Android][fixed] - Prevent fetch() POST requests on Android from appending `charset=utf-8` to `Content-Type` header. Pull Request resolved: https://github.com/facebook/react-native/pull/23580 Differential Revision: D14180849 Pulled By: cpojer fbshipit-source-id: b84cadf83361331a9f64d1ff5f2e6399a55527a6 --- .../modules/network/NetworkingModule.java | 2 +- .../modules/network/NetworkingModuleTest.java | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index eb84d6e33d9c24..0bfeb3f1ef4491 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -372,7 +372,7 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) { return; } } else { - requestBody = RequestBody.create(contentMediaType, body); + requestBody = RequestBody.create(contentMediaType, body.getBytes(StandardCharsets.UTF_8)); } } else if (data.hasKey(REQUEST_BODY_KEY_BASE64)) { if (contentType == null) { diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index 39a5cf76533be7..cb6136e7f07404 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -247,7 +247,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { body.putString("string", "This is request body"); mockEvents(); - + networkingModule.sendRequest( "POST", "http://somedomain/bar", @@ -309,6 +309,43 @@ public Object answer(InvocationOnMock invocation) throws Throwable { assertThat(requestHeaders.get("User-Agent")).isEqualTo("React test agent/1.0"); } + @Test + public void testPostJsonContentTypeHeader() throws Exception { + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + OkHttpClient.Builder clientBuilder = mock(OkHttpClient.Builder.class); + when(clientBuilder.build()).thenReturn(httpClient); + when(httpClient.newBuilder()).thenReturn(clientBuilder); + NetworkingModule networkingModule = + new NetworkingModule(mock(ReactApplicationContext.class), "", httpClient); + + JavaOnlyMap body = new JavaOnlyMap(); + body.putString("string", "{ \"key\": \"value\" }"); + + networkingModule.sendRequest( + "POST", + "http://somedomain/bar", + 0, + JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "application/json")), + body, + /* responseType */ "text", + /* useIncrementalUpdates*/ true, + /* timeout */ 0, + /* withCredentials */ false); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + + // Verify okhttp does not append "charset=utf-8" + assertThat(argumentCaptor.getValue().body().contentType().toString()).isEqualTo("application/json"); + } + @Test public void testMultipartPostRequestSimple() throws Exception { PowerMockito.mockStatic(RequestBodyUtil.class);