Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * unicode_case.c
3 : : * Unicode case mapping and case conversion.
4 : : *
5 : : * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
6 : : *
7 : : * IDENTIFICATION
8 : : * src/common/unicode_case.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : : #ifndef FRONTEND
13 : : #include "postgres.h"
14 : : #else
15 : : #include "postgres_fe.h"
16 : : #endif
17 : :
18 : : #include "common/unicode_case.h"
19 : : #include "common/unicode_case_table.h"
20 : : #include "common/unicode_category.h"
21 : : #include "mb/pg_wchar.h"
22 : :
23 : : enum CaseMapResult
24 : : {
25 : : CASEMAP_SELF,
26 : : CASEMAP_SIMPLE,
27 : : CASEMAP_SPECIAL,
28 : : };
29 : :
30 : : /*
31 : : * Map for each case kind.
32 : : */
33 : : static const char32_t *const casekind_map[NCaseKind] =
34 : : {
35 : : [CaseLower] = case_map_lower,
36 : : [CaseTitle] = case_map_title,
37 : : [CaseUpper] = case_map_upper,
38 : : [CaseFold] = case_map_fold,
39 : : };
40 : :
41 : : static char32_t find_case_map(char32_t ucs, const char32_t *map);
42 : : static size_t convert_case(char *dst, size_t dstsize, const char *src, size_t srclen,
43 : : size_t *pconsumed, CaseKind str_casekind, bool full,
44 : : WordBoundaryNext wbnext, void *wbstate);
45 : : static enum CaseMapResult casemap(char32_t u1, CaseKind casekind, bool full,
46 : : const char *src, size_t srclen, size_t srcoff,
47 : : char32_t *simple, const char32_t **special);
48 : :
49 : : char32_t
50 : 325 : unicode_lowercase_simple(char32_t code)
51 : : {
52 : 325 : char32_t cp = find_case_map(code, case_map_lower);
53 : :
54 [ + - ]: 325 : return cp != 0 ? cp : code;
55 : : }
56 : :
57 : : char32_t
58 : 0 : unicode_titlecase_simple(char32_t code)
59 : : {
60 : 0 : char32_t cp = find_case_map(code, case_map_title);
61 : :
62 [ # # ]: 0 : return cp != 0 ? cp : code;
63 : : }
64 : :
65 : : char32_t
66 : 325 : unicode_uppercase_simple(char32_t code)
67 : : {
68 : 325 : char32_t cp = find_case_map(code, case_map_upper);
69 : :
70 [ + - ]: 325 : return cp != 0 ? cp : code;
71 : : }
72 : :
73 : : char32_t
74 : 0 : unicode_casefold_simple(char32_t code)
75 : : {
76 : 0 : char32_t cp = find_case_map(code, case_map_fold);
77 : :
78 [ # # ]: 0 : return cp != 0 ? cp : code;
79 : : }
80 : :
81 : : /*
82 : : * unicode_strlower()
83 : : *
84 : : * Convert src to lowercase, and return the result length (not including
85 : : * terminating NUL). Sets *pconsumed to the amount of src successfully
86 : : * consumed; if less than srclen, indicates a decoding error.
87 : : *
88 : : * String src must be encoded in UTF-8.
89 : : *
90 : : * Result string is stored in dst, truncating if larger than dstsize. If
91 : : * dstsize is greater than the result length, dst will be NUL-terminated;
92 : : * otherwise not.
93 : : *
94 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
95 : : * required buffer size before allocating.
96 : : *
97 : : * If full is true, use special case mappings if available and if the
98 : : * conditions are satisfied.
99 : : */
100 : : size_t
101 : 6321 : unicode_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
102 : : size_t *pconsumed, bool full)
103 : : {
104 : 6321 : return convert_case(dst, dstsize, src, srclen, pconsumed, CaseLower, full,
105 : : NULL, NULL);
106 : : }
107 : :
108 : : /*
109 : : * unicode_strtitle()
110 : : *
111 : : * Convert src to titlecase, and return the result length (not including
112 : : * terminating NUL). Sets *pconsumed to the amount of src successfully
113 : : * consumed; if less than srclen, indicates a decoding error.
114 : : *
115 : : * String src must be encoded in UTF-8.
116 : : *
117 : : * Result string is stored in dst, truncating if larger than dstsize. If
118 : : * dstsize is greater than the result length, dst will be NUL-terminated;
119 : : * otherwise not.
120 : : *
121 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
122 : : * required buffer size before allocating.
123 : : *
124 : : * If full is true, use special case mappings if available and if the
125 : : * conditions are satisfied. Otherwise, use only simple mappings and use
126 : : * uppercase instead of titlecase.
127 : : *
128 : : * Titlecasing requires knowledge about word boundaries, which is provided by
129 : : * the callback wbnext. A word boundary is the offset of the start of a word
130 : : * or the offset of the character immediately following a word.
131 : : *
132 : : * The caller is expected to initialize and free the callback state
133 : : * wbstate. The callback should first return offset 0 for the first boundary;
134 : : * then the offset of each subsequent word boundary; then the total length of
135 : : * the string to indicate the final boundary.
136 : : */
137 : : size_t
138 : 133 : unicode_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
139 : : size_t *pconsumed, bool full, WordBoundaryNext wbnext,
140 : : void *wbstate)
141 : : {
142 : 133 : return convert_case(dst, dstsize, src, srclen, pconsumed, CaseTitle, full,
143 : : wbnext, wbstate);
144 : : }
145 : :
146 : : /*
147 : : * unicode_strupper()
148 : : *
149 : : * Convert src to uppercase, and return the result length (not including
150 : : * terminating NUL). Sets *pconsumed to the amount of src successfully
151 : : * consumed; if less than srclen, indicates a decoding error.
152 : : *
153 : : * String src must be encoded in UTF-8.
154 : : *
155 : : * Result string is stored in dst, truncating if larger than dstsize. If
156 : : * dstsize is greater than the result length, dst will be NUL-terminated;
157 : : * otherwise not.
158 : : *
159 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
160 : : * required buffer size before allocating.
161 : : *
162 : : * If full is true, use special case mappings if available and if the
163 : : * conditions are satisfied.
164 : : */
165 : : size_t
166 : 158561 : unicode_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
167 : : size_t *pconsumed, bool full)
168 : : {
169 : 158561 : return convert_case(dst, dstsize, src, srclen, pconsumed, CaseUpper, full,
170 : : NULL, NULL);
171 : : }
172 : :
173 : : /*
174 : : * unicode_strfold()
175 : : *
176 : : * Case fold src, and return the result length (not including terminating
177 : : * NUL). Sets *pconsumed to the amount of src successfully consumed; if less
178 : : * than srclen, indicates a decoding error.
179 : : *
180 : : * String src must be encoded in UTF-8.
181 : : *
182 : : * Result string is stored in dst, truncating if larger than dstsize. If
183 : : * dstsize is greater than the result length, dst will be NUL-terminated;
184 : : * otherwise not.
185 : : *
186 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
187 : : * required buffer size before allocating.
188 : : */
189 : : size_t
190 : 10 : unicode_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
191 : : size_t *pconsumed, bool full)
192 : : {
193 : 10 : return convert_case(dst, dstsize, src, srclen, pconsumed, CaseFold, full,
194 : : NULL, NULL);
195 : : }
196 : :
197 : : /* local version of pg_utf_mblen() to be inlinable */
198 : : static int
199 : 329329 : utf8_mblen(const unsigned char *s)
200 : : {
201 [ + + ]: 329329 : if ((*s & 0x80) == 0)
202 : 327743 : return 1;
203 [ + + ]: 1586 : else if ((*s & 0xe0) == 0xc0)
204 : 1320 : return 2;
205 [ + - ]: 266 : else if ((*s & 0xf0) == 0xe0)
206 : 266 : return 3;
207 [ # # ]: 0 : else if ((*s & 0xf8) == 0xf0)
208 : 0 : return 4;
209 : : else
210 : 0 : return -1;
211 : : }
212 : :
213 : : /*
214 : : * Implement Unicode Default Case Conversion algorithm.
215 : : *
216 : : * If str_casekind is CaseLower or CaseUpper, map each character in the string
217 : : * for which a mapping is available.
218 : : *
219 : : * If str_casekind is CaseTitle, maps characters found on a word boundary to
220 : : * titlecase (or uppercase if full is false) and other characters to
221 : : * lowercase. NB: does not currently implement the Unicode behavior in which
222 : : * the word boundary is adjusted to the next Cased character. That behavior
223 : : * could be implemented as an option, but it doesn't match the default
224 : : * behavior of ICU, nor does it match the documented behavior of INITCAP().
225 : : *
226 : : * If full is true, use special mappings for relevant characters, which can
227 : : * map a single codepoint to multiple codepoints, or depend on conditions.
228 : : */
229 : : static size_t
230 : 165025 : convert_case(char *dst, size_t dstsize, const char *src, size_t srclen,
231 : : size_t *pconsumed, CaseKind str_casekind, bool full,
232 : : WordBoundaryNext wbnext, void *wbstate)
233 : : {
234 : : /* character CaseKind varies while titlecasing */
235 : 165025 : CaseKind chr_casekind = str_casekind;
236 : 165025 : size_t srcoff = 0;
237 : 165025 : size_t result_len = 0;
238 : 165025 : size_t boundary = 0;
239 : :
240 : : /*
241 : : * Must be guaranteed by caller to avoid overflow (text values limited to
242 : : * MaxAllocSize anyway).
243 : : */
244 : : Assert(srclen < SIZE_MAX / UTF8_MAX_CASEMAP_EXPANSION);
245 : :
246 : : Assert((str_casekind == CaseTitle && wbnext && wbstate) ||
247 : : (str_casekind != CaseTitle && !wbnext && !wbstate));
248 : :
249 [ + + ]: 165025 : if (str_casekind == CaseTitle)
250 : : {
251 : 133 : boundary = wbnext(wbstate);
252 : : Assert(boundary == 0); /* start of text is always a boundary */
253 : : }
254 : :
255 [ + + ]: 494184 : while (srcoff < srclen)
256 : : {
257 : 329159 : int u1len = utf8_mblen((const unsigned char *) src + srcoff);
258 : : char32_t u1;
259 : 329159 : char32_t simple = 0;
260 : 329159 : const char32_t *special = NULL;
261 : : enum CaseMapResult casemap_result;
262 : :
263 : : /* invalid UTF8 */
264 [ + - + - ]: 329159 : if (u1len < 0 || srcoff + u1len > srclen)
265 : : break;
266 : :
267 : 329159 : u1 = utf8_to_unicode((const unsigned char *) src + srcoff);
268 : :
269 [ + + ]: 329159 : if (str_casekind == CaseTitle)
270 : : {
271 [ + + ]: 1032 : if (srcoff == boundary)
272 : : {
273 [ + + ]: 431 : chr_casekind = full ? CaseTitle : CaseUpper;
274 : 431 : boundary = wbnext(wbstate);
275 : : }
276 : : else
277 : 601 : chr_casekind = CaseLower;
278 : : }
279 : :
280 : 329159 : casemap_result = casemap(u1, chr_casekind, full, src, srclen, srcoff,
281 : : &simple, &special);
282 : :
283 [ + + + - ]: 329159 : switch (casemap_result)
284 : : {
285 : 93 : case CASEMAP_SELF:
286 : : /* no mapping; copy bytes from src */
287 : : Assert(simple == 0);
288 : : Assert(special == NULL);
289 [ + - ]: 93 : if (result_len + u1len <= dstsize)
290 : 93 : memcpy(dst + result_len, src + srcoff, u1len);
291 : :
292 : 93 : result_len += u1len;
293 : 93 : break;
294 : 328983 : case CASEMAP_SIMPLE:
295 : : {
296 : : /* replace with single character */
297 : 328983 : char32_t u2 = simple;
298 : 328983 : char32_t u2len = unicode_utf8len(u2);
299 : :
300 : : Assert(special == NULL);
301 [ + + ]: 328983 : if (result_len + u2len <= dstsize)
302 : 328951 : unicode_to_utf8(u2, (unsigned char *) dst + result_len);
303 : :
304 : 328983 : result_len += u2len;
305 : : }
306 : 328983 : break;
307 : 83 : case CASEMAP_SPECIAL:
308 : : /* replace with up to UNICODE_MAX_CASEMAP_CODEPOINTS */
309 : : Assert(simple == 0);
310 [ + - + + ]: 205 : for (int i = 0; i < UNICODE_MAX_CASEMAP_CODEPOINTS && special[i]; i++)
311 : : {
312 : 122 : char32_t u2 = special[i];
313 : 122 : size_t u2len = unicode_utf8len(u2);
314 : :
315 [ + - ]: 122 : if (result_len + u2len <= dstsize)
316 : 122 : unicode_to_utf8(u2, (unsigned char *) dst + result_len);
317 : :
318 : 122 : result_len += u2len;
319 : : }
320 : 83 : break;
321 : : }
322 : :
323 : 329159 : srcoff += u1len;
324 : : }
325 : :
326 [ + + ]: 165025 : if (result_len < dstsize)
327 : 164977 : dst[result_len] = '\0';
328 : :
329 : 165025 : *pconsumed = srcoff;
330 : 165025 : return result_len;
331 : : }
332 : :
333 : : /*
334 : : * Check that the condition matches Final_Sigma, described in Unicode Table
335 : : * 3-17. The character at the given offset must be directly preceded by a
336 : : * Cased character, and must not be directly followed by a Cased character.
337 : : *
338 : : * Case_Ignorable characters are ignored. Neither beginning of string nor end
339 : : * of string are considered Cased characters. NB: some characters may be both
340 : : * Cased and Case_Ignorable, in which case they are ignored.
341 : : */
342 : : static bool
343 : 55 : check_final_sigma(const unsigned char *str, size_t len, size_t offset)
344 : : {
345 : 55 : bool preceded_by_cased = false;
346 : 55 : bool followed_by_cased = false;
347 : : char32_t curr;
348 : : int ulen;
349 : :
350 : : /* iterate backwards looking for preceding character */
351 [ + + ]: 140 : for (size_t i = offset; i > 0;)
352 : : {
353 : : /* skip backwards through continuation bytes */
354 : 130 : i--;
355 [ + + ]: 130 : if ((str[i] & 0xC0) == 0x80)
356 : 60 : continue;
357 : :
358 : : /* now at leading byte of previous sequence */
359 : : Assert((str[i] & 0x80) == 0 || (str[i] & 0xC0) == 0xC0);
360 : :
361 : 70 : ulen = utf8_mblen((const unsigned char *) str + i);
362 : :
363 : : /* invalid UTF8 */
364 [ + - - + ]: 70 : if (ulen < 0 || i + ulen > len)
365 : 0 : return false;
366 : :
367 : 70 : curr = utf8_to_unicode((const unsigned char *) str + i);
368 : :
369 [ + + ]: 70 : if (!pg_u_prop_case_ignorable(curr))
370 : : {
371 : 45 : preceded_by_cased = pg_u_prop_cased(curr);
372 : 45 : break;
373 : : }
374 : : }
375 : :
376 : 55 : ulen = utf8_mblen((const unsigned char *) str + offset);
377 : :
378 : : /* iterate forward looking for following character */
379 [ + + ]: 75 : for (size_t i = offset + ulen; i < len;)
380 : : {
381 : 45 : ulen = utf8_mblen((const unsigned char *) str + i);
382 : :
383 : : /* invalid UTF8 */
384 [ + - - + ]: 45 : if (ulen < 0 || i + ulen > len)
385 : 0 : return false;
386 : :
387 : 45 : curr = utf8_to_unicode((const unsigned char *) str + i);
388 : :
389 [ + + ]: 45 : if (!pg_u_prop_case_ignorable(curr))
390 : : {
391 : 25 : followed_by_cased = pg_u_prop_cased(curr);
392 : 25 : break;
393 : : }
394 : :
395 : 20 : i += ulen;
396 : : }
397 : :
398 [ + + + + ]: 55 : return (preceded_by_cased && !followed_by_cased);
399 : : }
400 : :
401 : : /*
402 : : * Unicode allows for special casing to be applied only under certain
403 : : * circumstances. The only currently-supported condition is Final_Sigma.
404 : : */
405 : : static bool
406 : 118 : check_special_conditions(int conditions, const char *str, size_t len,
407 : : size_t offset)
408 : : {
409 [ + + ]: 118 : if (conditions == 0)
410 : 63 : return true;
411 [ + - ]: 55 : else if (conditions == PG_U_FINAL_SIGMA)
412 : 55 : return check_final_sigma((const unsigned char *) str, len, offset);
413 : :
414 : : /* no other conditions supported */
415 : : Assert(false);
416 : 0 : return false;
417 : : }
418 : :
419 : : /*
420 : : * Map the given character to the requested case.
421 : : *
422 : : * If full is true, and a special case mapping is found and the conditions are
423 : : * met, 'special' is set to the mapping result (which is an array of up to
424 : : * UNICODE_MAX_CASEMAP_CODEPOINTS) and CASEMAP_SPECIAL is returned.
425 : : *
426 : : * Otherwise, search for a simple mapping, and if found, set 'simple' to the
427 : : * result and return CASEMAP_SIMPLE.
428 : : *
429 : : * If no mapping is found, return CASEMAP_SELF, and the caller should copy the
430 : : * character without modification.
431 : : */
432 : : static enum CaseMapResult
433 : 329159 : casemap(char32_t u1, CaseKind casekind, bool full,
434 : : const char *src, size_t srclen, size_t srcoff,
435 : : char32_t *simple, const char32_t **special)
436 : : {
437 : : uint16 idx;
438 : :
439 : : /* Fast path for codepoints < 0x80 */
440 [ + + ]: 329159 : if (u1 < 0x80)
441 : : {
442 : : /*
443 : : * The first elements in all tables are reserved as 0 (as NULL). The
444 : : * data starts at index 1, not 0.
445 : : */
446 : 327728 : *simple = casekind_map[casekind][u1 + 1];
447 : :
448 : 327728 : return CASEMAP_SIMPLE;
449 : : }
450 : :
451 : 1431 : idx = case_index(u1);
452 : :
453 [ + + ]: 1431 : if (idx == 0)
454 : 93 : return CASEMAP_SELF;
455 : :
456 [ + + + + : 1456 : if (full && case_map_special[idx] &&
+ + ]
457 : 118 : check_special_conditions(special_case[case_map_special[idx]].conditions,
458 : : src, srclen, srcoff))
459 : : {
460 : 83 : *special = special_case[case_map_special[idx]].map[casekind];
461 : 83 : return CASEMAP_SPECIAL;
462 : : }
463 : :
464 : 1255 : *simple = casekind_map[casekind][idx];
465 : :
466 : 1255 : return CASEMAP_SIMPLE;
467 : : }
468 : :
469 : : /*
470 : : * Find entry in simple case map.
471 : : * If the entry does not exist, 0 will be returned.
472 : : */
473 : : static char32_t
474 : 650 : find_case_map(char32_t ucs, const char32_t *map)
475 : : {
476 : : /* Fast path for codepoints < 0x80 */
477 [ + + ]: 650 : if (ucs < 0x80)
478 : : /* The first elements in all tables are reserved as 0 (as NULL). */
479 : 362 : return map[ucs + 1];
480 : 288 : return map[case_index(ucs)];
481 : : }
|