Permalink
Browse files

Fix handling of bad utf16 passed out of JS

Summary:
JSC's utf16 -> utf8 conversion crashes on encountering bad utf16. Instead, use our own conversion (conveniently copied from fbjni).

Original fix thanks to rigdern (#9302)

Reviewed By: mhorowitz

Differential Revision: D3746947

fbshipit-source-id: 29887ca720f6a2b074f01f853bad28a083b273bc
  • Loading branch information...
1 parent 9a5d3ba commit ccdc57dd966d80b09fafb93b05abce925b71e949 @cjhopman cjhopman committed with Facebook Github Bot 4 Aug 24, 2016
@@ -21,6 +21,7 @@ LOCAL_SRC_FILES := \
NativeToJsBridge.cpp \
Platform.cpp \
Value.cpp \
+ Unicode.cpp \
LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
@@ -126,6 +126,7 @@ react_library(
'NativeToJsBridge.cpp',
'Platform.cpp',
'Value.cpp',
+ 'Unicode.cpp',
],
headers = [
'JSCLegacyProfiler.h',
@@ -155,6 +156,7 @@ react_library(
'Platform.h',
'SystraceSection.h',
'Value.h',
+ 'Unicode.h',
],
preprocessor_flags = [
'-DLOG_TAG="ReactNative"',
@@ -0,0 +1,88 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "Unicode.h"
+
+namespace facebook {
+namespace react {
+namespace unicode {
+namespace {
+
+// TODO(12827176): Don't duplicate this code here and fbjni.
+
+const uint16_t kUtf8OneByteBoundary = 0x80;
+const uint16_t kUtf8TwoBytesBoundary = 0x800;
+const uint16_t kUtf16HighSubLowBoundary = 0xD800;
+const uint16_t kUtf16HighSubHighBoundary = 0xDC00;
+const uint16_t kUtf16LowSubHighBoundary = 0xE000;
+
+// Calculate how many bytes are needed to convert an UTF16 string into UTF8
+// UTF16 string
+size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
+ if (!utf16String || utf16StringLen == 0) {
+ return 0;
+ }
+
+ uint32_t utf8StringLen = 0;
+ auto utf16StringEnd = utf16String + utf16StringLen;
+ auto idx16 = utf16String;
+ while (idx16 < utf16StringEnd) {
+ auto ch = *idx16++;
+ if (ch < kUtf8OneByteBoundary) {
+ utf8StringLen++;
+ } else if (ch < kUtf8TwoBytesBoundary) {
+ utf8StringLen += 2;
+ } else if (
+ (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+ (idx16 < utf16StringEnd) &&
+ (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+ utf8StringLen += 4;
+ idx16++;
+ } else {
+ utf8StringLen += 3;
+ }
+ }
+
+ return utf8StringLen;
+}
+
+} // namespace
+
+std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
+ if (!utf16String || utf16StringLen <= 0) {
+ return "";
+ }
+
+ std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
+ auto idx8 = utf8String.begin();
+ auto idx16 = utf16String;
+ auto utf16StringEnd = utf16String + utf16StringLen;
+ while (idx16 < utf16StringEnd) {
+ auto ch = *idx16++;
+ if (ch < kUtf8OneByteBoundary) {
+ *idx8++ = (ch & 0x7F);
+ } else if (ch < kUtf8TwoBytesBoundary) {
+ *idx8++ = 0b11000000 | (ch >> 6);
+ *idx8++ = 0b10000000 | (ch & 0x3F);
+ } else if (
+ (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+ (idx16 < utf16StringEnd) &&
+ (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+ auto ch2 = *idx16++;
+ uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
+ *idx8++ = 0b11110000 | (trunc_byte >> 2);
+ *idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
+ *idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
+ *idx8++ = 0b10000000 | (ch2 & 0x3F);
+ } else {
+ *idx8++ = 0b11100000 | (ch >> 12);
+ *idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
+ *idx8++ = 0b10000000 | (ch & 0x3F);
+ }
+ }
+
+ return utf8String;
+}
+
+} // namespace unicode
+} // namespace react
+} // namespace facebook
@@ -0,0 +1,14 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <string>
+#include <cstdint>
+
+namespace facebook {
+namespace react {
+namespace unicode {
+std::string utf16toUTF8(const uint16_t* utf16, size_t length) noexcept;
+}
+}
+}
@@ -15,6 +15,7 @@
#include <folly/dynamic.h>
#include "noncopyable.h"
+#include "Unicode.h"
#if WITH_FBJSCEXTENSIONS
#include <jsc_stringref.h>
@@ -85,12 +86,24 @@ class String : public noncopyable {
return JSStringGetMaximumUTF8CStringSize(m_string);
}
+ /*
+ * JavaScriptCore is built with strict utf16 -> utf8 conversion.
+ * This means if JSC's built-in conversion function encounters a JavaScript
+ * string which contains half of a 32-bit UTF-16 symbol, it produces an error
+ * rather than returning a string.
+ *
+ * Instead of relying on this, we use our own utf16 -> utf8 conversion function
+ * which is more lenient and always returns a string. When an invalid UTF-16
+ * string is provided, it'll likely manifest as a rendering glitch in the app for
+ * the invalid symbol.
+ *
+ * For details on JavaScript's unicode support see:
+ * https://mathiasbynens.be/notes/javascript-unicode
+ */
std::string str() const {
- size_t reserved = utf8Size();
- char* bytes = new char[reserved];
- size_t length = JSStringGetUTF8CString(m_string, bytes, reserved) - 1;
- std::unique_ptr<char[]> retainedBytes(bytes);
- return std::string(bytes, length);
+ const JSChar* utf16 = JSStringGetCharactersPtr(m_string);
+ int stringLength = JSStringGetLength(m_string);
+ return unicode::utf16toUTF8(utf16, stringLength);
}
// Assumes that utf8 is null terminated

0 comments on commit ccdc57d

Please sign in to comment.