Line data Source code
1 : #ifndef THINGER_HTTP_UTF8_HPP
2 : #define THINGER_HTTP_UTF8_HPP
3 :
4 : #include <cstddef>
5 : #include <cstdint>
6 : #include <string>
7 : #include <string_view>
8 : #include <fstream>
9 :
10 : namespace thinger::http::utf8 {
11 :
12 : // UTF-8 validation based on Unicode 6.0 Table 3-7 (Well-Formed UTF-8 Byte Sequences)
13 : // http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94
14 : //
15 : // Returns true if the data is valid UTF-8, false otherwise.
16 32 : inline bool is_valid(const uint8_t* data, size_t len) {
17 394 : while (len > 0) {
18 364 : const uint8_t b1 = data[0];
19 :
20 : size_t bytes;
21 :
22 : // U+0000..U+007F: 00..7F
23 364 : if (b1 <= 0x7F) {
24 352 : bytes = 1;
25 :
26 : // U+0080..U+07FF: C2..DF, 80..BF
27 12 : } else if (b1 >= 0xC2 && b1 <= 0xDF) {
28 2 : if (len < 2 || data[1] < 0x80 || data[1] > 0xBF) return false;
29 2 : bytes = 2;
30 :
31 : // U+0800..U+FFFF: 3-byte sequences
32 10 : } else if (b1 >= 0xE0 && b1 <= 0xEF) {
33 6 : if (len < 3) return false;
34 6 : const uint8_t b2 = data[1];
35 6 : const uint8_t b3 = data[2];
36 6 : if (b3 < 0x80 || b3 > 0xBF) return false;
37 :
38 6 : if (b1 == 0xE0) {
39 : // U+0800..U+0FFF: E0, A0..BF, 80..BF
40 0 : if (b2 < 0xA0 || b2 > 0xBF) return false;
41 6 : } else if (b1 == 0xED) {
42 : // U+D000..U+D7FF: ED, 80..9F, 80..BF (surrogates excluded)
43 0 : if (b2 < 0x80 || b2 > 0x9F) return false;
44 : } else {
45 : // U+1000..U+CFFF, U+E000..U+FFFF: E1..EC/EE..EF, 80..BF, 80..BF
46 6 : if (b2 < 0x80 || b2 > 0xBF) return false;
47 : }
48 6 : bytes = 3;
49 :
50 : // U+10000..U+10FFFF: 4-byte sequences
51 10 : } else if (b1 >= 0xF0 && b1 <= 0xF4) {
52 2 : if (len < 4) return false;
53 2 : const uint8_t b2 = data[1];
54 2 : const uint8_t b3 = data[2];
55 2 : const uint8_t b4 = data[3];
56 2 : if (b3 < 0x80 || b3 > 0xBF) return false;
57 2 : if (b4 < 0x80 || b4 > 0xBF) return false;
58 :
59 2 : if (b1 == 0xF0) {
60 : // U+10000..U+3FFFF: F0, 90..BF, 80..BF, 80..BF
61 2 : if (b2 < 0x90 || b2 > 0xBF) return false;
62 0 : } else if (b1 == 0xF4) {
63 : // U+100000..U+10FFFF: F4, 80..8F, 80..BF, 80..BF
64 0 : if (b2 < 0x80 || b2 > 0x8F) return false;
65 : } else {
66 : // U+40000..U+FFFFF: F1..F3, 80..BF, 80..BF, 80..BF
67 0 : if (b2 < 0x80 || b2 > 0xBF) return false;
68 : }
69 2 : bytes = 4;
70 :
71 2 : } else {
72 : // Invalid leading byte (C0, C1, F5..FF, or continuation byte 80..BF)
73 2 : return false;
74 : }
75 :
76 362 : data += bytes;
77 362 : len -= bytes;
78 : }
79 :
80 30 : return true;
81 : }
82 :
83 : // Convenience overload for string_view
84 : inline bool is_valid(std::string_view sv) {
85 : return is_valid(reinterpret_cast<const uint8_t*>(sv.data()), sv.size());
86 : }
87 :
88 : // Check if a file contains valid UTF-8 content
89 : inline bool file_is_valid(const std::string& file_path) {
90 : std::ifstream input(file_path, std::ifstream::binary);
91 : if (!input.is_open()) return false;
92 :
93 : char buffer[4096];
94 : while (input.read(buffer, sizeof(buffer)) || input.gcount() > 0) {
95 : if (!is_valid(reinterpret_cast<const uint8_t*>(buffer), static_cast<size_t>(input.gcount()))) {
96 : return false;
97 : }
98 : if (input.eof()) break;
99 : }
100 :
101 : return true;
102 : }
103 :
104 : } // namespace thinger::http::utf8
105 :
106 : #endif // THINGER_HTTP_UTF8_HPP
|