| 77 | } |
| 78 | |
| 79 | uint32_t utf8NextCodepoint(const unsigned char** string) { |
| 80 | if (**string == 0) { |
| 81 | return 0; |
| 82 | } |
| 83 | |
| 84 | const unsigned char lead = **string; |
| 85 | const int bytes = utf8CodepointLen(lead); |
| 86 | const uint8_t* chr = *string; |
| 87 | |
| 88 | // Invalid lead byte (stray continuation byte 0x80-0xBF, or 0xFE/0xFF) |
| 89 | if (bytes == 1 && lead >= 0x80) { |
| 90 | (*string)++; |
| 91 | return REPLACEMENT_GLYPH; |
| 92 | } |
| 93 | |
| 94 | if (bytes == 1) { |
| 95 | (*string)++; |
| 96 | return chr[0]; |
| 97 | } |
| 98 | |
| 99 | // Validate continuation bytes before consuming them |
| 100 | for (int i = 1; i < bytes; i++) { |
| 101 | if ((chr[i] & 0xC0) != 0x80) { |
| 102 | // Missing or invalid continuation byte — skip all bytes consumed so far |
| 103 | *string += i; |
| 104 | return REPLACEMENT_GLYPH; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | uint32_t cp = chr[0] & ((1 << (7 - bytes)) - 1); // mask header bits |
| 109 | |
| 110 | for (int i = 1; i < bytes; i++) { |
| 111 | cp = (cp << 6) | (chr[i] & 0x3F); |
| 112 | } |
| 113 | |
| 114 | // Reject overlong encodings, surrogates, and out-of-range values |
| 115 | const bool overlong = (bytes == 2 && cp < 0x80) || (bytes == 3 && cp < 0x800) || (bytes == 4 && cp < 0x10000); |
| 116 | const bool surrogate = (cp >= 0xD800 && cp <= 0xDFFF); |
| 117 | if (overlong || surrogate || cp > 0x10FFFF) { |
| 118 | (*string)++; |
| 119 | return REPLACEMENT_GLYPH; |
| 120 | } |
| 121 | |
| 122 | *string += bytes; |
| 123 | |
| 124 | return cp; |
| 125 | } |
| 126 | |
| 127 | void utf8AppendCodepoint(uint32_t cp, std::string& out) { |
| 128 | if (cp < 0x80) { |