Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Multibyte character printing support for frontend code
4 : : *
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * src/fe_utils/mbprint.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres_fe.h"
14 : :
15 : : #include "fe_utils/mbprint.h"
16 : :
17 : : #include "libpq-fe.h"
18 : :
19 : :
20 : : /*
21 : : * To avoid version-skew problems, this file must not use declarations
22 : : * from pg_wchar.h: the encoding IDs we are dealing with are determined
23 : : * by the libpq.so we are linked with, and that might not match the
24 : : * numbers we see at compile time. (If this file were inside libpq,
25 : : * the problem would go away...)
26 : : *
27 : : * Hence, we have our own definition of pg_wchar, and we get the values
28 : : * of any needed encoding IDs on-the-fly.
29 : : */
30 : :
31 : : typedef unsigned int pg_wchar;
32 : :
33 : : static int
34 : 2768160 : pg_get_utf8_id(void)
35 : : {
36 : : static int utf8_id = -1;
37 : :
38 [ + + ]: 2768160 : if (utf8_id < 0)
39 : 7185 : utf8_id = pg_char_to_encoding("utf8");
40 : 2768160 : return utf8_id;
41 : : }
42 : :
43 : : #define PG_UTF8 pg_get_utf8_id()
44 : :
45 : :
46 : : /*
47 : : * Convert a UTF-8 character to a Unicode code point.
48 : : * This is a one-character version of pg_utf2wchar_with_len.
49 : : *
50 : : * No error checks here, c must point to a long-enough string.
51 : : */
52 : : static char32_t
53 : 0 : utf8_to_unicode(const unsigned char *c)
54 : : {
55 [ # # ]: 0 : if ((*c & 0x80) == 0)
56 : 0 : return (char32_t) c[0];
57 [ # # ]: 0 : else if ((*c & 0xe0) == 0xc0)
58 : 0 : return (char32_t) (((c[0] & 0x1f) << 6) |
59 : 0 : (c[1] & 0x3f));
60 [ # # ]: 0 : else if ((*c & 0xf0) == 0xe0)
61 : 0 : return (char32_t) (((c[0] & 0x0f) << 12) |
62 : 0 : ((c[1] & 0x3f) << 6) |
63 : 0 : (c[2] & 0x3f));
64 [ # # ]: 0 : else if ((*c & 0xf8) == 0xf0)
65 : 0 : return (char32_t) (((c[0] & 0x07) << 18) |
66 : 0 : ((c[1] & 0x3f) << 12) |
67 : 0 : ((c[2] & 0x3f) << 6) |
68 : 0 : (c[3] & 0x3f));
69 : : else
70 : : /* that is an invalid code on purpose */
71 : 0 : return 0xffffffff;
72 : : }
73 : :
74 : :
75 : : /*
76 : : * Unicode 3.1 compliant validation : for each category, it checks the
77 : : * combination of each byte to make sure it maps to a valid range. It also
78 : : * returns -1 for the following UCS values: ucs > 0x10ffff ucs & 0xfffe =
79 : : * 0xfffe 0xfdd0 < ucs < 0xfdef ucs & 0xdb00 = 0xd800 (surrogates)
80 : : */
81 : : static int
82 : 14110403 : utf_charcheck(const unsigned char *c)
83 : : {
84 [ + + ]: 14110403 : if ((*c & 0x80) == 0)
85 : 14108681 : return 1;
86 [ + + ]: 1722 : else if ((*c & 0xe0) == 0xc0)
87 : : {
88 : : /* two-byte char */
89 [ + + + - ]: 1495 : if (((c[1] & 0xc0) == 0x80) && ((c[0] & 0x1f) > 0x01))
90 : 1463 : return 2;
91 : 32 : return -1;
92 : : }
93 [ + + ]: 227 : else if ((*c & 0xf0) == 0xe0)
94 : : {
95 : : /* three-byte char */
96 [ + - ]: 207 : if (((c[1] & 0xc0) == 0x80) &&
97 [ - + - - ]: 207 : (((c[0] & 0x0f) != 0x00) || ((c[1] & 0x20) == 0x20)) &&
98 [ + - ]: 207 : ((c[2] & 0xc0) == 0x80))
99 : : {
100 : 207 : int z = c[0] & 0x0f;
101 : 207 : int yx = ((c[1] & 0x3f) << 6) | (c[0] & 0x3f);
102 : 207 : int lx = yx & 0x7f;
103 : :
104 : : /* check 0xfffe/0xffff, 0xfdd0..0xfedf range, surrogates */
105 [ + + ]: 207 : if (((z == 0x0f) &&
106 [ + - ]: 32 : (((yx & 0xffe) == 0xffe) ||
107 [ - + - - : 207 : (((yx & 0xf80) == 0xd80) && (lx >= 0x30) && (lx <= 0x4f)))) ||
- - - + ]
108 [ # # ]: 0 : ((z == 0x0d) && ((yx & 0xb00) == 0x800)))
109 : 0 : return -1;
110 : 207 : return 3;
111 : : }
112 : 0 : return -1;
113 : : }
114 [ + - ]: 20 : else if ((*c & 0xf8) == 0xf0)
115 : : {
116 : 20 : int u = ((c[0] & 0x07) << 2) | ((c[1] & 0x30) >> 4);
117 : :
118 : : /* four-byte char */
119 [ + - + - ]: 20 : if (((c[1] & 0xc0) == 0x80) &&
120 [ + - ]: 20 : (u > 0x00) && (u <= 0x10) &&
121 [ + - + - ]: 20 : ((c[2] & 0xc0) == 0x80) && ((c[3] & 0xc0) == 0x80))
122 : : {
123 : : /* test for 0xzzzzfffe/0xzzzzfffff */
124 [ + - - + ]: 20 : if (((c[1] & 0x0f) == 0x0f) && ((c[2] & 0x3f) == 0x3f) &&
125 [ # # ]: 0 : ((c[3] & 0x3e) == 0x3e))
126 : 0 : return -1;
127 : 20 : return 4;
128 : : }
129 : 0 : return -1;
130 : : }
131 : 0 : return -1;
132 : : }
133 : :
134 : :
135 : : static void
136 : 16 : mb_utf_validate(unsigned char *pwcs)
137 : : {
138 : 16 : unsigned char *p = pwcs;
139 : :
140 [ + + ]: 72 : while (*pwcs)
141 : : {
142 : : int len;
143 : :
144 [ + + ]: 56 : if ((len = utf_charcheck(pwcs)) > 0)
145 : : {
146 [ - + ]: 40 : if (p != pwcs)
147 : : {
148 : : int i;
149 : :
150 [ # # ]: 0 : for (i = 0; i < len; i++)
151 : 0 : *p++ = *pwcs++;
152 : : }
153 : : else
154 : : {
155 : 40 : pwcs += len;
156 : 40 : p += len;
157 : : }
158 : : }
159 : : else
160 : : /* we skip the char */
161 : 16 : pwcs++;
162 : : }
163 [ + - ]: 16 : if (p != pwcs)
164 : 16 : *p = '\0';
165 : 16 : }
166 : :
167 : :
168 : : static bool
169 : 2766438 : mb_utf_is_valid(const unsigned char *pwcs)
170 : : {
171 [ + + ]: 16876769 : while (*pwcs)
172 : : {
173 : : int len;
174 : :
175 [ + + ]: 14110347 : if ((len = utf_charcheck(pwcs)) > 0)
176 : 14110331 : pwcs += len;
177 : : else
178 : 16 : return false;
179 : :
180 : : }
181 : 2766422 : return true;
182 : : }
183 : :
184 : :
185 : : /*
186 : : * public functions : wcswidth and mbvalidate
187 : : */
188 : :
189 : : /*
190 : : * pg_wcswidth is the dumb display-width function.
191 : : * It assumes that everything will appear on one line.
192 : : * OTOH it is easier to use than pg_wcssize if this applies to you.
193 : : */
194 : : int
195 : 0 : pg_wcswidth(const char *pwcs, size_t len, int encoding)
196 : : {
197 : 0 : int width = 0;
198 : :
199 [ # # ]: 0 : while (len > 0)
200 : : {
201 : : int chlen,
202 : : chwidth;
203 : :
204 : 0 : chlen = PQmblen(pwcs, encoding);
205 [ # # ]: 0 : if (len < (size_t) chlen)
206 : 0 : break; /* Invalid string */
207 : :
208 : 0 : chwidth = PQdsplen(pwcs, encoding);
209 [ # # ]: 0 : if (chwidth > 0)
210 : 0 : width += chwidth;
211 : :
212 : 0 : pwcs += chlen;
213 : 0 : len -= chlen;
214 : : }
215 : 0 : return width;
216 : : }
217 : :
218 : : /*
219 : : * pg_wcssize takes the given string in the given encoding and returns three
220 : : * values:
221 : : * result_width: Width in display characters of the longest line in string
222 : : * result_height: Number of lines in display output
223 : : * result_format_size: Number of bytes required to store formatted
224 : : * representation of string
225 : : *
226 : : * This MUST be kept in sync with pg_wcsformat!
227 : : */
228 : : void
229 : 954530 : pg_wcssize(const unsigned char *pwcs, size_t len, int encoding,
230 : : int *result_width, int *result_height, int *result_format_size)
231 : : {
232 : : int w,
233 : 954530 : chlen = 0,
234 : 954530 : linewidth = 0;
235 : 954530 : int width = 0;
236 : 954530 : int height = 1;
237 : 954530 : int format_size = 0;
238 : :
239 [ + + + - ]: 11374193 : for (; *pwcs && len > 0; pwcs += chlen)
240 : : {
241 : 10419663 : chlen = PQmblen((const char *) pwcs, encoding);
242 [ - + ]: 10419663 : if (len < (size_t) chlen)
243 : 0 : break;
244 : 10419663 : w = PQdsplen((const char *) pwcs, encoding);
245 : :
246 [ + + ]: 10419663 : if (chlen == 1) /* single-byte char */
247 : : {
248 [ + + ]: 10417974 : if (*pwcs == '\n') /* Newline */
249 : : {
250 [ + + ]: 13193 : if (linewidth > width)
251 : 3483 : width = linewidth;
252 : 13193 : linewidth = 0;
253 : 13193 : height += 1;
254 : 13193 : format_size += 1; /* For NUL char */
255 : : }
256 [ + + ]: 10404781 : else if (*pwcs == '\r') /* Linefeed */
257 : : {
258 : 4 : linewidth += 2;
259 : 4 : format_size += 2;
260 : : }
261 [ + + ]: 10404777 : else if (*pwcs == '\t') /* Tab */
262 : : {
263 : : do
264 : : {
265 : 975 : linewidth++;
266 : 975 : format_size++;
267 [ + + ]: 975 : } while (linewidth % 8 != 0);
268 : : }
269 [ + + ]: 10404653 : else if (w < 0) /* Other control char */
270 : : {
271 : 45 : linewidth += 4;
272 : 45 : format_size += 4;
273 : : }
274 : : else /* Output it as-is */
275 : : {
276 : 10404608 : linewidth += w;
277 : 10404608 : format_size += 1;
278 : : }
279 : : }
280 [ - + ]: 1689 : else if (w < 0) /* Non-ascii control char */
281 : : {
282 : 0 : linewidth += 6; /* \u0000 */
283 : 0 : format_size += 6;
284 : : }
285 : : else /* All other chars */
286 : : {
287 : 1689 : linewidth += w;
288 : 1689 : format_size += chlen;
289 : : }
290 : 10419663 : len -= chlen;
291 : : }
292 [ + + ]: 954530 : if (linewidth > width)
293 : 886344 : width = linewidth;
294 : 954530 : format_size += 1; /* For NUL char */
295 : :
296 : : /* Set results */
297 [ + + ]: 954530 : if (result_width)
298 : 954519 : *result_width = width;
299 [ + - ]: 954530 : if (result_height)
300 : 954530 : *result_height = height;
301 [ + + ]: 954530 : if (result_format_size)
302 : 950159 : *result_format_size = format_size;
303 : 954530 : }
304 : :
305 : : /*
306 : : * Format a string into one or more "struct lineptr" lines.
307 : : * lines[i].ptr == NULL indicates the end of the array.
308 : : *
309 : : * This MUST be kept in sync with pg_wcssize!
310 : : */
311 : : void
312 : 951464 : pg_wcsformat(const unsigned char *pwcs, size_t len, int encoding,
313 : : struct lineptr *lines, int count)
314 : : {
315 : : int w,
316 : 951464 : chlen = 0;
317 : 951464 : int linewidth = 0;
318 : 951464 : unsigned char *ptr = lines->ptr; /* Pointer to data area */
319 : :
320 [ + + + - ]: 11271287 : for (; *pwcs && len > 0; pwcs += chlen)
321 : : {
322 : 10319823 : chlen = PQmblen((const char *) pwcs, encoding);
323 [ - + ]: 10319823 : if (len < (size_t) chlen)
324 : 0 : break;
325 : 10319823 : w = PQdsplen((const char *) pwcs, encoding);
326 : :
327 [ + + ]: 10319823 : if (chlen == 1) /* single-byte char */
328 : : {
329 [ + + ]: 10318134 : if (*pwcs == '\n') /* Newline */
330 : : {
331 : 13337 : *ptr++ = '\0';
332 : 13337 : lines->width = linewidth;
333 : 13337 : linewidth = 0;
334 : 13337 : lines++;
335 : 13337 : count--;
336 [ - + ]: 13337 : if (count <= 0)
337 : 0 : exit(1); /* Screwup */
338 : :
339 : : /* make next line point to remaining memory */
340 : 13337 : lines->ptr = ptr;
341 : : }
342 [ + + ]: 10304797 : else if (*pwcs == '\r') /* Linefeed */
343 : : {
344 : 4 : strcpy((char *) ptr, "\\r");
345 : 4 : linewidth += 2;
346 : 4 : ptr += 2;
347 : : }
348 [ + + ]: 10304793 : else if (*pwcs == '\t') /* Tab */
349 : : {
350 : : do
351 : : {
352 : 975 : *ptr++ = ' ';
353 : 975 : linewidth++;
354 [ + + ]: 975 : } while (linewidth % 8 != 0);
355 : : }
356 [ + + ]: 10304669 : else if (w < 0) /* Other control char */
357 : : {
358 : 45 : sprintf((char *) ptr, "\\x%02X", *pwcs);
359 : 45 : linewidth += 4;
360 : 45 : ptr += 4;
361 : : }
362 : : else /* Output it as-is */
363 : : {
364 : 10304624 : linewidth += w;
365 : 10304624 : *ptr++ = *pwcs;
366 : : }
367 : : }
368 [ - + ]: 1689 : else if (w < 0) /* Non-ascii control char */
369 : : {
370 [ # # ]: 0 : if (encoding == PG_UTF8)
371 : 0 : sprintf((char *) ptr, "\\u%04X", utf8_to_unicode(pwcs));
372 : : else
373 : : {
374 : : /*
375 : : * This case cannot happen in the current code because only
376 : : * UTF-8 signals multibyte control characters. But we may need
377 : : * to support it at some stage
378 : : */
379 : 0 : sprintf((char *) ptr, "\\u????");
380 : : }
381 : 0 : ptr += 6;
382 : 0 : linewidth += 6;
383 : : }
384 : : else /* All other chars */
385 : : {
386 : : int i;
387 : :
388 [ + + ]: 5314 : for (i = 0; i < chlen; i++)
389 : 3625 : *ptr++ = pwcs[i];
390 : 1689 : linewidth += w;
391 : : }
392 : 10319823 : len -= chlen;
393 : : }
394 : 951464 : lines->width = linewidth;
395 : 951464 : *ptr++ = '\0'; /* Terminate formatted string */
396 : :
397 [ - + ]: 951464 : if (count <= 0)
398 : 0 : exit(1); /* Screwup */
399 : :
400 : 951464 : (lines + 1)->ptr = NULL; /* terminate line array */
401 : 951464 : }
402 : :
403 : :
404 : : /*
405 : : * Encoding validation: delete any unvalidatable characters from the string
406 : : *
407 : : * This seems redundant with existing functionality elsewhere?
408 : : */
409 : : unsigned char *
410 : 16 : mbvalidate(unsigned char *pwcs, int encoding)
411 : : {
412 [ + - ]: 16 : if (encoding == PG_UTF8)
413 : 16 : mb_utf_validate(pwcs);
414 : : else
415 : : {
416 : : /*
417 : : * other encodings needing validation should add their own routines
418 : : * here
419 : : */
420 : : }
421 : :
422 : 16 : return pwcs;
423 : : }
424 : :
425 : : bool
426 : 2768144 : mb_is_valid(const unsigned char *pwcs, int encoding)
427 : : {
428 [ + + ]: 2768144 : if (encoding == PG_UTF8)
429 : 2766438 : return mb_utf_is_valid(pwcs);
430 : : else
431 : : {
432 : : /*
433 : : * other encodings needing validation should add their own routines
434 : : * here
435 : : */
436 : 1706 : return true;
437 : : }
438 : : }
|