Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Multibyte character printing support for frontend code
4 : : *
5 : : *
6 : : * Portions Copyright (c) 1996-2025, 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
5807 tgl@sss.pgh.pa.us 34 :CBC 2580961 : pg_get_utf8_id(void)
35 : : {
36 : : static int utf8_id = -1;
37 : :
6538 38 [ + + ]: 2580961 : if (utf8_id < 0)
39 : 5354 : utf8_id = pg_char_to_encoding("utf8");
40 : 2580961 : 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 pg_wchar
5498 tgl@sss.pgh.pa.us 53 :UBC 0 : utf8_to_unicode(const unsigned char *c)
54 : : {
8717 bruce@momjian.us 55 [ # # ]: 0 : if ((*c & 0x80) == 0)
56 : 0 : return (pg_wchar) c[0];
57 [ # # ]: 0 : else if ((*c & 0xe0) == 0xc0)
58 : 0 : return (pg_wchar) (((c[0] & 0x1f) << 6) |
59 : 0 : (c[1] & 0x3f));
60 [ # # ]: 0 : else if ((*c & 0xf0) == 0xe0)
61 : 0 : return (pg_wchar) (((c[0] & 0x0f) << 12) |
62 : 0 : ((c[1] & 0x3f) << 6) |
63 : 0 : (c[2] & 0x3f));
5500 tgl@sss.pgh.pa.us 64 [ # # ]: 0 : else if ((*c & 0xf8) == 0xf0)
8717 bruce@momjian.us 65 : 0 : return (pg_wchar) (((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 */
8727 ishii@postgresql.org 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
8727 ishii@postgresql.org 82 :CBC 11401683 : utf_charcheck(const unsigned char *c)
83 : : {
8717 bruce@momjian.us 84 [ + + ]: 11401683 : if ((*c & 0x80) == 0)
8727 ishii@postgresql.org 85 : 11400443 : return 1;
8717 bruce@momjian.us 86 [ + + ]: 1240 : else if ((*c & 0xe0) == 0xc0)
87 : : {
88 : : /* two-byte char */
89 [ + - + - ]: 1075 : if (((c[1] & 0xc0) == 0x80) && ((c[0] & 0x1f) > 0x01))
8727 ishii@postgresql.org 90 : 1075 : return 2;
8727 ishii@postgresql.org 91 :UBC 0 : return -1;
92 : : }
8717 bruce@momjian.us 93 [ + + ]:CBC 165 : else if ((*c & 0xf0) == 0xe0)
94 : : {
95 : : /* three-byte char */
8727 ishii@postgresql.org 96 [ + - ]: 153 : if (((c[1] & 0xc0) == 0x80) &&
97 [ - + - - ]: 153 : (((c[0] & 0x0f) != 0x00) || ((c[1] & 0x20) == 0x20)) &&
8717 bruce@momjian.us 98 [ + - ]: 153 : ((c[2] & 0xc0) == 0x80))
99 : : {
100 : 153 : int z = c[0] & 0x0f;
101 : 153 : int yx = ((c[1] & 0x3f) << 6) | (c[0] & 0x3f);
102 : 153 : int lx = yx & 0x7f;
103 : :
104 : : /* check 0xfffe/0xffff, 0xfdd0..0xfedf range, surrogates */
8727 ishii@postgresql.org 105 [ + + ]: 153 : if (((z == 0x0f) &&
106 [ + - ]: 24 : (((yx & 0xffe) == 0xffe) ||
2999 tgl@sss.pgh.pa.us 107 [ - + - - : 153 : (((yx & 0xf80) == 0xd80) && (lx >= 0x30) && (lx <= 0x4f)))) ||
- - - + ]
8717 bruce@momjian.us 108 [ # # ]:UBC 0 : ((z == 0x0d) && ((yx & 0xb00) == 0x800)))
8727 ishii@postgresql.org 109 : 0 : return -1;
8727 ishii@postgresql.org 110 :CBC 153 : return 3;
111 : : }
8727 ishii@postgresql.org 112 :UBC 0 : return -1;
113 : : }
8717 bruce@momjian.us 114 [ + - ]:CBC 12 : else if ((*c & 0xf8) == 0xf0)
115 : : {
116 : 12 : int u = ((c[0] & 0x07) << 2) | ((c[1] & 0x30) >> 4);
117 : :
118 : : /* four-byte char */
8727 ishii@postgresql.org 119 [ + - + - ]: 12 : if (((c[1] & 0xc0) == 0x80) &&
120 [ + - ]: 12 : (u > 0x00) && (u <= 0x10) &&
8717 bruce@momjian.us 121 [ + - + - ]: 12 : ((c[2] & 0xc0) == 0x80) && ((c[3] & 0xc0) == 0x80))
122 : : {
123 : : /* test for 0xzzzzfffe/0xzzzzfffff */
8727 ishii@postgresql.org 124 [ + - - + ]: 12 : if (((c[1] & 0x0f) == 0x0f) && ((c[2] & 0x3f) == 0x3f) &&
8717 bruce@momjian.us 125 [ # # ]:UBC 0 : ((c[3] & 0x3e) == 0x3e))
8727 ishii@postgresql.org 126 : 0 : return -1;
8727 ishii@postgresql.org 127 :CBC 12 : return 4;
128 : : }
8727 ishii@postgresql.org 129 :UBC 0 : return -1;
130 : : }
131 : 0 : return -1;
132 : : }
133 : :
134 : :
135 : : static void
8727 ishii@postgresql.org 136 :CBC 2579326 : mb_utf_validate(unsigned char *pwcs)
137 : : {
138 : 2579326 : unsigned char *p = pwcs;
139 : :
8717 bruce@momjian.us 140 [ + + ]: 13981009 : while (*pwcs)
141 : : {
142 : : int len;
143 : :
7148 144 [ + - ]: 11401683 : if ((len = utf_charcheck(pwcs)) > 0)
145 : : {
8717 146 [ - + ]: 11401683 : if (p != pwcs)
147 : : {
148 : : int i;
149 : :
7148 bruce@momjian.us 150 [ # # ]:UBC 0 : for (i = 0; i < len; i++)
8727 ishii@postgresql.org 151 : 0 : *p++ = *pwcs++;
152 : : }
153 : : else
154 : : {
7148 bruce@momjian.us 155 :CBC 11401683 : pwcs += len;
156 : 11401683 : p += len;
157 : : }
158 : : }
159 : : else
160 : : /* we skip the char */
8727 ishii@postgresql.org 161 :UBC 0 : pwcs++;
162 : : }
8717 bruce@momjian.us 163 [ - + ]:CBC 2579326 : if (p != pwcs)
8727 ishii@postgresql.org 164 :UBC 0 : *p = '\0';
8727 ishii@postgresql.org 165 :CBC 2579326 : }
166 : :
167 : : /*
168 : : * public functions : wcswidth and mbvalidate
169 : : */
170 : :
171 : : /*
172 : : * pg_wcswidth is the dumb display-width function.
173 : : * It assumes that everything will appear on one line.
174 : : * OTOH it is easier to use than pg_wcssize if this applies to you.
175 : : */
176 : : int
4931 tgl@sss.pgh.pa.us 177 : 2231 : pg_wcswidth(const char *pwcs, size_t len, int encoding)
178 : : {
6912 bruce@momjian.us 179 : 2231 : int width = 0;
180 : :
7148 181 [ + + ]: 22591 : while (len > 0)
182 : : {
183 : : int chlen,
184 : : chwidth;
185 : :
4931 tgl@sss.pgh.pa.us 186 : 20360 : chlen = PQmblen(pwcs, encoding);
187 [ - + ]: 20360 : if (len < (size_t) chlen)
6912 bruce@momjian.us 188 :UBC 0 : break; /* Invalid string */
189 : :
4931 tgl@sss.pgh.pa.us 190 :CBC 20360 : chwidth = PQdsplen(pwcs, encoding);
7148 bruce@momjian.us 191 [ + - ]: 20360 : if (chwidth > 0)
192 : 20360 : width += chwidth;
193 : :
194 : 20360 : pwcs += chlen;
4931 tgl@sss.pgh.pa.us 195 : 20360 : len -= chlen;
196 : : }
7148 bruce@momjian.us 197 : 2231 : return width;
198 : : }
199 : :
200 : : /*
201 : : * pg_wcssize takes the given string in the given encoding and returns three
202 : : * values:
203 : : * result_width: Width in display characters of the longest line in string
204 : : * result_height: Number of lines in display output
205 : : * result_format_size: Number of bytes required to store formatted
206 : : * representation of string
207 : : *
208 : : * This MUST be kept in sync with pg_wcsformat!
209 : : */
210 : : void
5109 peter_e@gmx.net 211 : 1209788 : pg_wcssize(const unsigned char *pwcs, size_t len, int encoding,
212 : : int *result_width, int *result_height, int *result_format_size)
213 : : {
214 : : int w,
6912 bruce@momjian.us 215 : 1209788 : chlen = 0,
216 : 1209788 : linewidth = 0;
217 : 1209788 : int width = 0;
218 : 1209788 : int height = 1;
219 : 1209788 : int format_size = 0;
220 : :
7148 221 [ + + + - ]: 14710342 : for (; *pwcs && len > 0; pwcs += chlen)
222 : : {
4939 peter_e@gmx.net 223 : 13500554 : chlen = PQmblen((const char *) pwcs, encoding);
6912 bruce@momjian.us 224 [ - + ]: 13500554 : if (len < (size_t) chlen)
7148 bruce@momjian.us 225 :UBC 0 : break;
4939 peter_e@gmx.net 226 :CBC 13500554 : w = PQdsplen((const char *) pwcs, encoding);
227 : :
6828 tgl@sss.pgh.pa.us 228 [ + + ]: 13500554 : if (chlen == 1) /* single-byte char */
229 : : {
6912 bruce@momjian.us 230 [ + + ]: 13498074 : if (*pwcs == '\n') /* Newline */
231 : : {
7148 232 [ + + ]: 19257 : if (linewidth > width)
233 : 4561 : width = linewidth;
234 : 19257 : linewidth = 0;
235 : 19257 : height += 1;
2999 tgl@sss.pgh.pa.us 236 : 19257 : format_size += 1; /* For NUL char */
237 : : }
238 [ + + ]: 13478817 : else if (*pwcs == '\r') /* Linefeed */
239 : : {
7148 bruce@momjian.us 240 : 8 : linewidth += 2;
241 : 8 : format_size += 2;
242 : : }
2999 tgl@sss.pgh.pa.us 243 [ + + ]: 13478809 : else if (*pwcs == '\t') /* Tab */
244 : : {
245 : : do
246 : : {
6329 247 : 1710 : linewidth++;
248 : 1710 : format_size++;
249 [ + + ]: 1710 : } while (linewidth % 8 != 0);
250 : : }
6828 251 [ + + ]: 13478591 : else if (w < 0) /* Other control char */
252 : : {
7148 bruce@momjian.us 253 : 78 : linewidth += 4;
254 : 78 : format_size += 4;
255 : : }
256 : : else /* Output it as-is */
257 : : {
6828 tgl@sss.pgh.pa.us 258 : 13478513 : linewidth += w;
7148 bruce@momjian.us 259 : 13478513 : format_size += 1;
260 : : }
261 : : }
6828 tgl@sss.pgh.pa.us 262 [ - + ]: 2480 : else if (w < 0) /* Non-ascii control char */
263 : : {
6912 bruce@momjian.us 264 :UBC 0 : linewidth += 6; /* \u0000 */
7148 265 : 0 : format_size += 6;
266 : : }
267 : : else /* All other chars */
268 : : {
7148 bruce@momjian.us 269 :CBC 2480 : linewidth += w;
270 : 2480 : format_size += chlen;
271 : : }
272 : 13500554 : len -= chlen;
273 : : }
274 [ + + ]: 1209788 : if (linewidth > width)
275 : 1118231 : width = linewidth;
5931 276 : 1209788 : format_size += 1; /* For NUL char */
277 : :
278 : : /* Set results */
7148 279 [ + - ]: 1209788 : if (result_width)
280 : 1209788 : *result_width = width;
281 [ + - ]: 1209788 : if (result_height)
282 : 1209788 : *result_height = height;
283 [ + + ]: 1209788 : if (result_format_size)
284 : 1206881 : *result_format_size = format_size;
285 : 1209788 : }
286 : :
287 : : /*
288 : : * Format a string into one or more "struct lineptr" lines.
289 : : * lines[i].ptr == NULL indicates the end of the array.
290 : : *
291 : : * This MUST be kept in sync with pg_wcssize!
292 : : */
293 : : void
5109 peter_e@gmx.net 294 : 668537 : pg_wcsformat(const unsigned char *pwcs, size_t len, int encoding,
295 : : struct lineptr *lines, int count)
296 : : {
297 : : int w,
7148 bruce@momjian.us 298 : 668537 : chlen = 0;
6912 299 : 668537 : int linewidth = 0;
300 : 668537 : unsigned char *ptr = lines->ptr; /* Pointer to data area */
301 : :
7148 302 [ + + + - ]: 7864631 : for (; *pwcs && len > 0; pwcs += chlen)
303 : : {
4939 peter_e@gmx.net 304 : 7196094 : chlen = PQmblen((const char *) pwcs, encoding);
6912 bruce@momjian.us 305 [ - + ]: 7196094 : if (len < (size_t) chlen)
7148 bruce@momjian.us 306 :UBC 0 : break;
4939 peter_e@gmx.net 307 :CBC 7196094 : w = PQdsplen((const char *) pwcs, encoding);
308 : :
6828 tgl@sss.pgh.pa.us 309 [ + + ]: 7196094 : if (chlen == 1) /* single-byte char */
310 : : {
6912 bruce@momjian.us 311 [ + + ]: 7194854 : if (*pwcs == '\n') /* Newline */
312 : : {
6828 tgl@sss.pgh.pa.us 313 : 10245 : *ptr++ = '\0';
7148 bruce@momjian.us 314 : 10245 : lines->width = linewidth;
315 : 10245 : linewidth = 0;
316 : 10245 : lines++;
317 : 10245 : count--;
6329 tgl@sss.pgh.pa.us 318 [ - + ]: 10245 : if (count <= 0)
6912 bruce@momjian.us 319 :UBC 0 : exit(1); /* Screwup */
320 : :
321 : : /* make next line point to remaining memory */
7148 bruce@momjian.us 322 :CBC 10245 : lines->ptr = ptr;
323 : : }
2999 tgl@sss.pgh.pa.us 324 [ + + ]: 7184609 : else if (*pwcs == '\r') /* Linefeed */
325 : : {
7148 326 : 4 : strcpy((char *) ptr, "\\r");
bruce@momjian.us 327 : 4 : linewidth += 2;
328 : 4 : ptr += 2;
329 : : }
2999 tgl@sss.pgh.pa.us 330 [ + + ]: 7184605 : else if (*pwcs == '\t') /* Tab */
331 : : {
332 : : do
333 : : {
6330 bruce@momjian.us 334 : 855 : *ptr++ = ' ';
335 : 855 : linewidth++;
336 [ + + ]: 855 : } while (linewidth % 8 != 0);
337 : : }
6828 tgl@sss.pgh.pa.us 338 [ + + ]: 7184496 : else if (w < 0) /* Other control char */
339 : : {
7148 340 : 39 : sprintf((char *) ptr, "\\x%02X", *pwcs);
bruce@momjian.us 341 : 39 : linewidth += 4;
342 : 39 : ptr += 4;
343 : : }
344 : : else /* Output it as-is */
345 : : {
6828 tgl@sss.pgh.pa.us 346 : 7184457 : linewidth += w;
7148 bruce@momjian.us 347 : 7184457 : *ptr++ = *pwcs;
348 : : }
349 : : }
6828 tgl@sss.pgh.pa.us 350 [ - + ]: 1240 : else if (w < 0) /* Non-ascii control char */
351 : : {
7148 bruce@momjian.us 352 [ # # ]:UBC 0 : if (encoding == PG_UTF8)
5498 tgl@sss.pgh.pa.us 353 : 0 : sprintf((char *) ptr, "\\u%04X", utf8_to_unicode(pwcs));
354 : : else
355 : : {
356 : : /*
357 : : * This case cannot happen in the current code because only
358 : : * UTF-8 signals multibyte control characters. But we may need
359 : : * to support it at some stage
360 : : */
7148 361 : 0 : sprintf((char *) ptr, "\\u????");
362 : : }
bruce@momjian.us 363 : 0 : ptr += 6;
364 : 0 : linewidth += 6;
365 : : }
366 : : else /* All other chars */
367 : : {
368 : : int i;
369 : :
6912 bruce@momjian.us 370 [ + + ]:CBC 3897 : for (i = 0; i < chlen; i++)
7148 371 : 2657 : *ptr++ = pwcs[i];
372 : 1240 : linewidth += w;
373 : : }
374 : 7196094 : len -= chlen;
375 : : }
376 : 668537 : lines->width = linewidth;
5931 377 : 668537 : *ptr++ = '\0'; /* Terminate formatted string */
378 : :
6329 tgl@sss.pgh.pa.us 379 [ - + ]: 668537 : if (count <= 0)
5931 bruce@momjian.us 380 :UBC 0 : exit(1); /* Screwup */
381 : :
5931 bruce@momjian.us 382 :CBC 668537 : (lines + 1)->ptr = NULL; /* terminate line array */
8727 ishii@postgresql.org 383 : 668537 : }
384 : :
385 : :
386 : : /*
387 : : * Encoding validation: delete any unvalidatable characters from the string
388 : : *
389 : : * This seems redundant with existing functionality elsewhere?
390 : : */
391 : : unsigned char *
7148 bruce@momjian.us 392 : 2580961 : mbvalidate(unsigned char *pwcs, int encoding)
393 : : {
8208 peter_e@gmx.net 394 [ + + ]: 2580961 : if (encoding == PG_UTF8)
4922 395 : 2579326 : mb_utf_validate(pwcs);
396 : : else
397 : : {
398 : : /*
399 : : * other encodings needing validation should add their own routines
400 : : * here
401 : : */
402 : : }
403 : :
7287 tgl@sss.pgh.pa.us 404 : 2580961 : return pwcs;
405 : : }
|