blob: 30355b375fbd7028b0a04b8e6abea37d0a8a87ca [file]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
/*
* Copyright © 1991-2015 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in
* http://www.unicode.org/copyright.html.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of the Unicode data files and any associated documentation
* (the "Data Files") or Unicode software and any associated documentation
* (the "Software") to deal in the Data Files or Software
* without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, and/or sell copies of
* the Data Files or Software, and to permit persons to whom the Data Files
* or Software are furnished to do so, provided that
* (a) this copyright and permission notice appear with all copies
* of the Data Files or Software,
* (b) this copyright and permission notice appear in associated
* documentation, and
* (c) there is clear notice in each modified Data File or in the Software
* as well as in the documentation associated with the Data File(s) or
* Software that the data or software has been modified.
*
* THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT OF THIRD PARTY RIGHTS.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
* NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
* DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THE DATA FILES OR SOFTWARE.
*
* Except as contained in this notice, the name of a copyright holder
* shall not be used in advertising or otherwise to promote the sale,
* use or other dealings in these Data Files or Software without prior
* written authorization of the copyright holder.
*/
#ifndef THIRD_PARTY_CRUBIT_SUPPORT_RS_STD_INTERNAL_IS_UTF8_H_
#define THIRD_PARTY_CRUBIT_SUPPORT_RS_STD_INTERNAL_IS_UTF8_H_
#include <cstdint>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
namespace rs_std::internal {
// Returns whether the given character is ASCII.
constexpr bool IsAscii(char c) {
// We explicitly use `unsigned char` because the comparison would always be
// true if `char` is signed.
constexpr unsigned char kMaxAscii = '\u007f';
return static_cast<unsigned char>(c) <= kMaxAscii;
}
// Returns whether the given string is all ASCII.
constexpr bool IsAscii(absl::string_view str) {
for (const char c : str) {
if (!IsAscii(c)) {
return false;
}
}
return true;
}
// A mapping from the initial byte of a UTF-8 encoded code point to the number
// of bytes in the code point.
//
// See https://tools.ietf.org/html/rfc3629
// clang-format off
// NOLINTNEXTLINE(clang-diagnostic-unsafe-buffer-usage): Indexed with uint8_t.
static constexpr const uint8_t kFirstByteToUtf8CharSize[256] = {
// 1 2 3 4 5 6 7 8 9 A B C D E F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
};
// clang-format on
// Returns whether the given string is a valid single UTF-8 code point.
//
// The caller must first ensure that `str`'s length matches the length
// specified by the first byte.
constexpr bool IsUtf8Char(absl::string_view str) {
// 2-byte encoding is for codepoints \u{0080} to \u{07ff}
// first C2 80 last DF BF
// 3-byte encoding is for codepoints \u{0800} to \u{ffff}
// first E0 A0 80 last EF BF BF
// excluding surrogates codepoints \u{d800} to \u{dfff}
// ED A0 80 to ED BF BF
// 4-byte encoding is for codepoints \u{10000} to \u{10ffff}
// first F0 90 80 80 last F4 8F BF BF
//
// Use the UTF-8 syntax from the RFC
//
// https://tools.ietf.org/html/rfc3629
// UTF8-1 = %x00-7F
// UTF8-2 = %xC2-DF UTF8-tail
// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
// %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
// %xF4 %x80-8F 2( UTF8-tail )
//
// References:
// - Rust stdlib:
// https://github.com/rust-lang/rust/blob/48994b1674b3212d27b5e83841c0966bc2b4be43/library/core/src/str/validations.rs#L185-L213
// - LLVM:
// https://github.com/llvm/llvm-project/blob/357306572d4734a75e649284b4808299d0aba9c8/llvm/lib/Support/ConvertUTF.cpp#L397-L420
switch (str.size()) {
// 0 or >4 bytes lengths are invalid.
default:
return false;
// We explicitly use `unsigned char` in the following because some of the
// comparisons would otherwise be tautological if `char` is signed.
// (For example, `fourth` > 0xBF is always false if `fourth` is a
// `signed char`.)
case 4: {
unsigned char fourth = str[3];
if (fourth < 0x80 || fourth > 0xBF) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
}
case 3: {
unsigned char third = str[2];
if (third < 0x80 || third > 0xBF) {
return false;
}
ABSL_FALLTHROUGH_INTENDED;
}
case 2: {
unsigned char second = str[1];
if (second < 0x80 || second > 0xBF) {
return false;
}
switch (str[0]) {
case 0xE0: {
if (second < 0xA0) {
return false;
}
break;
}
case 0xED: {
if (second > 0x9F) {
return false;
}
break;
}
case 0xF0: {
if (second < 0x90) {
return false;
}
break;
}
case 0xF4: {
if (second > 0x8F) {
return false;
}
break;
}
default: {
if (second < 0x80) {
return false;
}
}
}
ABSL_FALLTHROUGH_INTENDED;
}
case 1: {
unsigned char first = str[0];
if (first >= 0x80 && first < 0xC2) {
return false;
}
if (first > 0xF4) {
return false;
}
}
}
return true;
}
// Returns whether the given string is a valid UTF-8.
constexpr bool IsUtf8(absl::string_view str) {
// ASCII fast-path.
if (IsAscii(str)) {
return true;
}
while (!str.empty()) {
// Cast to `unsigned char` to ensure correctness if char is signed.
const uint8_t char_size =
kFirstByteToUtf8CharSize[static_cast<unsigned char>(str[0])];
if (char_size > str.size()) {
return false;
}
const absl::string_view char_view = str.substr(0, char_size);
if (!IsUtf8Char(char_view)) {
return false;
}
str = str.substr(char_size);
}
return true;
}
} // namespace rs_std::internal
#endif // THIRD_PARTY_CRUBIT_SUPPORT_RS_STD_INTERNAL_IS_UTF8_H_