Summary: @public Adjust license headers throughout the project Reviewed By: SidharthGuglani Differential Revision: D13255691 fbshipit-source-id: 98be2aa372a94e7a54a65e3d64e5c6a436b18083
310 lines
9.2 KiB
C++
310 lines
9.2 KiB
C++
/**
|
|
* 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.
|
|
*/
|
|
#include <jni/LocalString.h>
|
|
#include <fb/Environment.h>
|
|
#include <fb/assert.h>
|
|
|
|
#include <vector>
|
|
|
|
namespace facebook {
|
|
namespace jni {
|
|
|
|
namespace {
|
|
|
|
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;
|
|
|
|
inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
|
|
FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
|
|
|
|
out[0] = 0xE0 | (code >> 12);
|
|
out[1] = 0x80 | ((code >> 6) & 0x3F);
|
|
out[2] = 0x80 | (code & 0x3F);
|
|
}
|
|
|
|
inline char32_t decode3ByteUTF8(const uint8_t* in) {
|
|
return (((in[0] & 0x0f) << 12) |
|
|
((in[1] & 0x3f) << 6) |
|
|
( in[2] & 0x3f));
|
|
}
|
|
|
|
inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) {
|
|
FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits");
|
|
|
|
out[offset] = (char) (0xF0 | (code >> 18));
|
|
out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F));
|
|
out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F));
|
|
out[offset + 3] = (char) (0x80 | (code & 0x3F));
|
|
}
|
|
|
|
template <typename T>
|
|
inline bool isFourByteUTF8Encoding(const T* utf8) {
|
|
return ((*utf8 & 0xF8) == 0xF0);
|
|
}
|
|
|
|
}
|
|
|
|
namespace detail {
|
|
|
|
size_t modifiedLength(const std::string& str) {
|
|
// Scan for supplementary characters
|
|
size_t j = 0;
|
|
for (size_t i = 0; i < str.size(); ) {
|
|
if (str[i] == 0) {
|
|
i += 1;
|
|
j += 2;
|
|
} else if (i + 4 > str.size() ||
|
|
!isFourByteUTF8Encoding(&(str[i]))) {
|
|
// See the code in utf8ToModifiedUTF8 for what's happening here.
|
|
i += 1;
|
|
j += 1;
|
|
} else {
|
|
i += 4;
|
|
j += 6;
|
|
}
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
// returns modified utf8 length; *length is set to strlen(str)
|
|
size_t modifiedLength(const uint8_t* str, size_t* length) {
|
|
// NUL-terminated: Scan for length and supplementary characters
|
|
size_t i = 0;
|
|
size_t j = 0;
|
|
while (str[i] != 0) {
|
|
if (str[i + 1] == 0 ||
|
|
str[i + 2] == 0 ||
|
|
str[i + 3] == 0 ||
|
|
!isFourByteUTF8Encoding(&(str[i]))) {
|
|
i += 1;
|
|
j += 1;
|
|
} else {
|
|
i += 4;
|
|
j += 6;
|
|
}
|
|
}
|
|
|
|
*length = i;
|
|
return j;
|
|
}
|
|
|
|
void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen)
|
|
{
|
|
size_t j = 0;
|
|
for (size_t i = 0; i < len; ) {
|
|
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
|
|
if (utf8[i] == 0) {
|
|
FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short");
|
|
modified[j] = 0xc0;
|
|
modified[j + 1] = 0x80;
|
|
i += 1;
|
|
j += 2;
|
|
continue;
|
|
}
|
|
|
|
if (i + 4 > len ||
|
|
!isFourByteUTF8Encoding(utf8 + i)) {
|
|
// If the input is too short for this to be a four-byte
|
|
// encoding, or it isn't one for real, just copy it on through.
|
|
modified[j] = utf8[i];
|
|
i++;
|
|
j++;
|
|
continue;
|
|
}
|
|
|
|
// Convert 4 bytes of input to 2 * 3 bytes of output
|
|
char32_t code = (((utf8[i] & 0x07) << 18) |
|
|
((utf8[i + 1] & 0x3f) << 12) |
|
|
((utf8[i + 2] & 0x3f) << 6) |
|
|
( utf8[i + 3] & 0x3f));
|
|
char32_t first;
|
|
char32_t second;
|
|
|
|
if (code > 0x10ffff) {
|
|
// These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit
|
|
// limit on that representation. Encode two replacement characters, so the expected output
|
|
// length lines up.
|
|
const char32_t kUnicodeReplacementChar = 0xfffd;
|
|
first = kUnicodeReplacementChar;
|
|
second = kUnicodeReplacementChar;
|
|
} else {
|
|
// split into surrogate pair
|
|
first = ((code - 0x010000) >> 10) | 0xd800;
|
|
second = ((code - 0x010000) & 0x3ff) | 0xdc00;
|
|
}
|
|
|
|
// encode each as a 3 byte surrogate value
|
|
FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short");
|
|
encode3ByteUTF8(first, modified + j);
|
|
encode3ByteUTF8(second, modified + j + 3);
|
|
i += 4;
|
|
j += 6;
|
|
}
|
|
|
|
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
|
|
modified[j++] = '\0';
|
|
}
|
|
|
|
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept {
|
|
// Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
|
|
std::string utf8(len, 0);
|
|
size_t j = 0;
|
|
for (size_t i = 0; i < len; ) {
|
|
// surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
|
|
// encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx
|
|
|
|
if (len >= i + 6 &&
|
|
modified[i] == 0xed &&
|
|
(modified[i + 1] & 0xf0) == 0xa0 &&
|
|
modified[i + 3] == 0xed &&
|
|
(modified[i + 4] & 0xf0) == 0xb0) {
|
|
// Valid surrogate pair
|
|
char32_t pair1 = decode3ByteUTF8(modified + i);
|
|
char32_t pair2 = decode3ByteUTF8(modified + i + 3);
|
|
char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) |
|
|
( pair2 & 0x3ff));
|
|
encode4ByteUTF8(ch, utf8, j);
|
|
i += 6;
|
|
j += 4;
|
|
continue;
|
|
} else if (len >= i + 2 &&
|
|
modified[i] == 0xc0 &&
|
|
modified[i + 1] == 0x80) {
|
|
utf8[j] = 0;
|
|
i += 2;
|
|
j += 1;
|
|
continue;
|
|
}
|
|
|
|
// copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid
|
|
// encoding of some sort, but garbage in garbage out is ok.
|
|
|
|
utf8[j] = (char) modified[i];
|
|
i++;
|
|
j++;
|
|
}
|
|
|
|
utf8.resize(j);
|
|
|
|
return utf8;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
LocalString::LocalString(const std::string& str)
|
|
{
|
|
size_t modlen = detail::modifiedLength(str);
|
|
if (modlen == str.size()) {
|
|
// no supplementary characters, build jstring from input buffer
|
|
m_string = Environment::current()->NewStringUTF(str.data());
|
|
return;
|
|
}
|
|
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
|
|
detail::utf8ToModifiedUTF8(
|
|
reinterpret_cast<const uint8_t*>(str.data()), str.size(),
|
|
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
|
|
m_string = Environment::current()->NewStringUTF(modified.data());
|
|
}
|
|
|
|
LocalString::LocalString(const char* str)
|
|
{
|
|
size_t len;
|
|
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(str), &len);
|
|
if (modlen == len) {
|
|
// no supplementary characters, build jstring from input buffer
|
|
m_string = Environment::current()->NewStringUTF(str);
|
|
return;
|
|
}
|
|
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
|
|
detail::utf8ToModifiedUTF8(
|
|
reinterpret_cast<const uint8_t*>(str), len,
|
|
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
|
|
m_string = Environment::current()->NewStringUTF(modified.data());
|
|
}
|
|
|
|
LocalString::~LocalString() {
|
|
Environment::current()->DeleteLocalRef(m_string);
|
|
}
|
|
|
|
std::string fromJString(JNIEnv* env, jstring str) {
|
|
auto utf16String = JStringUtf16Extractor(env, str);
|
|
auto length = env->GetStringLength(str);
|
|
return detail::utf16toUTF8(utf16String, length);
|
|
}
|
|
|
|
} }
|