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-2024, 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
5297 tgl@sss.pgh.pa.us 34 :CBC 2542441 : pg_get_utf8_id(void)
35 : : {
36 : : static int utf8_id = -1;
37 : :
6028 38 [ + + ]: 2542441 : if (utf8_id < 0)
39 : 5442 : utf8_id = pg_char_to_encoding("utf8");
40 : 2542441 : 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
4988 tgl@sss.pgh.pa.us 53 :UBC 0 : utf8_to_unicode(const unsigned char *c)
54 : : {
8207 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));
4990 tgl@sss.pgh.pa.us 64 [ # # ]: 0 : else if ((*c & 0xf8) == 0xf0)
8207 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 */
8217 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
8217 ishii@postgresql.org 82 :CBC 9534298 : utf_charcheck(const unsigned char *c)
83 : : {
8207 bruce@momjian.us 84 [ + + ]: 9534298 : if ((*c & 0x80) == 0)
8217 ishii@postgresql.org 85 : 9533634 : return 1;
8207 bruce@momjian.us 86 [ + + ]: 664 : else if ((*c & 0xe0) == 0xc0)
87 : : {
88 : : /* two-byte char */
89 [ + - + - ]: 574 : if (((c[1] & 0xc0) == 0x80) && ((c[0] & 0x1f) > 0x01))
8217 ishii@postgresql.org 90 : 574 : return 2;
8217 ishii@postgresql.org 91 :UBC 0 : return -1;
92 : : }
8207 bruce@momjian.us 93 [ + + ]:CBC 90 : else if ((*c & 0xf0) == 0xe0)
94 : : {
95 : : /* three-byte char */
8217 ishii@postgresql.org 96 [ + - ]: 78 : if (((c[1] & 0xc0) == 0x80) &&
97 [ - + - - ]: 78 : (((c[0] & 0x0f) != 0x00) || ((c[1] & 0x20) == 0x20)) &&
8207 bruce@momjian.us 98 [ + - ]: 78 : ((c[2] & 0xc0) == 0x80))
99 : : {
100 : 78 : int z = c[0] & 0x0f;
101 : 78 : int yx = ((c[1] & 0x3f) << 6) | (c[0] & 0x3f);
102 : 78 : int lx = yx & 0x7f;
103 : :
104 : : /* check 0xfffe/0xffff, 0xfdd0..0xfedf range, surrogates */
8217 ishii@postgresql.org 105 [ - + ]: 78 : if (((z == 0x0f) &&
8217 ishii@postgresql.org 106 [ # # ]:UBC 0 : (((yx & 0xffe) == 0xffe) ||
2489 tgl@sss.pgh.pa.us 107 [ - - - - :CBC 78 : (((yx & 0xf80) == 0xd80) && (lx >= 0x30) && (lx <= 0x4f)))) ||
- - - + ]
8207 bruce@momjian.us 108 [ # # ]:UBC 0 : ((z == 0x0d) && ((yx & 0xb00) == 0x800)))
8217 ishii@postgresql.org 109 : 0 : return -1;
8217 ishii@postgresql.org 110 :CBC 78 : return 3;
111 : : }
8217 ishii@postgresql.org 112 :UBC 0 : return -1;
113 : : }
8207 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 */
8217 ishii@postgresql.org 119 [ + - + - ]: 12 : if (((c[1] & 0xc0) == 0x80) &&
120 [ + - ]: 12 : (u > 0x00) && (u <= 0x10) &&
8207 bruce@momjian.us 121 [ + - + - ]: 12 : ((c[2] & 0xc0) == 0x80) && ((c[3] & 0xc0) == 0x80))
122 : : {
123 : : /* test for 0xzzzzfffe/0xzzzzfffff */
8217 ishii@postgresql.org 124 [ + - - + ]: 12 : if (((c[1] & 0x0f) == 0x0f) && ((c[2] & 0x3f) == 0x3f) &&
8207 bruce@momjian.us 125 [ # # ]:UBC 0 : ((c[3] & 0x3e) == 0x3e))
8217 ishii@postgresql.org 126 : 0 : return -1;
8217 ishii@postgresql.org 127 :CBC 12 : return 4;
128 : : }
8217 ishii@postgresql.org 129 :UBC 0 : return -1;
130 : : }
131 : 0 : return -1;
132 : : }
133 : :
134 : :
135 : : static void
8217 ishii@postgresql.org 136 :CBC 2540892 : mb_utf_validate(unsigned char *pwcs)
137 : : {
138 : 2540892 : unsigned char *p = pwcs;
139 : :
8207 bruce@momjian.us 140 [ + + ]: 12075190 : while (*pwcs)
141 : : {
142 : : int len;
143 : :
6638 144 [ + - ]: 9534298 : if ((len = utf_charcheck(pwcs)) > 0)
145 : : {
8207 146 [ - + ]: 9534298 : if (p != pwcs)
147 : : {
148 : : int i;
149 : :
6638 bruce@momjian.us 150 [ # # ]:UBC 0 : for (i = 0; i < len; i++)
8217 ishii@postgresql.org 151 : 0 : *p++ = *pwcs++;
152 : : }
153 : : else
154 : : {
6638 bruce@momjian.us 155 :CBC 9534298 : pwcs += len;
156 : 9534298 : p += len;
157 : : }
158 : : }
159 : : else
160 : : /* we skip the char */
8217 ishii@postgresql.org 161 :UBC 0 : pwcs++;
162 : : }
8207 bruce@momjian.us 163 [ - + ]:CBC 2540892 : if (p != pwcs)
8217 ishii@postgresql.org 164 :UBC 0 : *p = '\0';
8217 ishii@postgresql.org 165 :CBC 2540892 : }
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
4421 tgl@sss.pgh.pa.us 177 : 1906 : pg_wcswidth(const char *pwcs, size_t len, int encoding)
178 : : {
6402 bruce@momjian.us 179 : 1906 : int width = 0;
180 : :
6638 181 [ + + ]: 19216 : while (len > 0)
182 : : {
183 : : int chlen,
184 : : chwidth;
185 : :
4421 tgl@sss.pgh.pa.us 186 : 17310 : chlen = PQmblen(pwcs, encoding);
187 [ - + ]: 17310 : if (len < (size_t) chlen)
6402 bruce@momjian.us 188 :UBC 0 : break; /* Invalid string */
189 : :
4421 tgl@sss.pgh.pa.us 190 :CBC 17310 : chwidth = PQdsplen(pwcs, encoding);
6638 bruce@momjian.us 191 [ + - ]: 17310 : if (chwidth > 0)
192 : 17310 : width += chwidth;
193 : :
194 : 17310 : pwcs += chlen;
4421 tgl@sss.pgh.pa.us 195 : 17310 : len -= chlen;
196 : : }
6638 bruce@momjian.us 197 : 1906 : 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
4599 peter_e@gmx.net 211 : 1149371 : 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,
6402 bruce@momjian.us 215 : 1149371 : chlen = 0,
216 : 1149371 : linewidth = 0;
217 : 1149371 : int width = 0;
218 : 1149371 : int height = 1;
219 : 1149371 : int format_size = 0;
220 : :
6638 221 [ + + + - ]: 13681892 : for (; *pwcs && len > 0; pwcs += chlen)
222 : : {
4429 peter_e@gmx.net 223 : 12532521 : chlen = PQmblen((const char *) pwcs, encoding);
6402 bruce@momjian.us 224 [ - + ]: 12532521 : if (len < (size_t) chlen)
6638 bruce@momjian.us 225 :UBC 0 : break;
4429 peter_e@gmx.net 226 :CBC 12532521 : w = PQdsplen((const char *) pwcs, encoding);
227 : :
6318 tgl@sss.pgh.pa.us 228 [ + + ]: 12532521 : if (chlen == 1) /* single-byte char */
229 : : {
6402 bruce@momjian.us 230 [ + + ]: 12531193 : if (*pwcs == '\n') /* Newline */
231 : : {
6638 232 [ + + ]: 18617 : if (linewidth > width)
233 : 4458 : width = linewidth;
234 : 18617 : linewidth = 0;
235 : 18617 : height += 1;
2489 tgl@sss.pgh.pa.us 236 : 18617 : format_size += 1; /* For NUL char */
237 : : }
238 [ + + ]: 12512576 : else if (*pwcs == '\r') /* Linefeed */
239 : : {
6638 bruce@momjian.us 240 : 8 : linewidth += 2;
241 : 8 : format_size += 2;
242 : : }
2489 tgl@sss.pgh.pa.us 243 [ + + ]: 12512568 : else if (*pwcs == '\t') /* Tab */
244 : : {
245 : : do
246 : : {
5819 247 : 1518 : linewidth++;
248 : 1518 : format_size++;
249 [ + + ]: 1518 : } while (linewidth % 8 != 0);
250 : : }
6318 251 [ + + ]: 12512374 : else if (w < 0) /* Other control char */
252 : : {
6638 bruce@momjian.us 253 : 72 : linewidth += 4;
254 : 72 : format_size += 4;
255 : : }
256 : : else /* Output it as-is */
257 : : {
6318 tgl@sss.pgh.pa.us 258 : 12512302 : linewidth += w;
6638 bruce@momjian.us 259 : 12512302 : format_size += 1;
260 : : }
261 : : }
6318 tgl@sss.pgh.pa.us 262 [ - + ]: 1328 : else if (w < 0) /* Non-ascii control char */
263 : : {
6402 bruce@momjian.us 264 :UBC 0 : linewidth += 6; /* \u0000 */
6638 265 : 0 : format_size += 6;
266 : : }
267 : : else /* All other chars */
268 : : {
6638 bruce@momjian.us 269 :CBC 1328 : linewidth += w;
270 : 1328 : format_size += chlen;
271 : : }
272 : 12532521 : len -= chlen;
273 : : }
274 [ + + ]: 1149371 : if (linewidth > width)
275 : 1065093 : width = linewidth;
5421 276 : 1149371 : format_size += 1; /* For NUL char */
277 : :
278 : : /* Set results */
6638 279 [ + - ]: 1149371 : if (result_width)
280 : 1149371 : *result_width = width;
281 [ + - ]: 1149371 : if (result_height)
282 : 1149371 : *result_height = height;
283 [ + + ]: 1149371 : if (result_format_size)
284 : 1146730 : *result_format_size = format_size;
285 : 1149371 : }
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
4599 peter_e@gmx.net 294 : 633094 : pg_wcsformat(const unsigned char *pwcs, size_t len, int encoding,
295 : : struct lineptr *lines, int count)
296 : : {
297 : : int w,
6638 bruce@momjian.us 298 : 633094 : chlen = 0;
6402 299 : 633094 : int linewidth = 0;
300 : 633094 : unsigned char *ptr = lines->ptr; /* Pointer to data area */
301 : :
6638 302 [ + + + - ]: 7308865 : for (; *pwcs && len > 0; pwcs += chlen)
303 : : {
4429 peter_e@gmx.net 304 : 6675771 : chlen = PQmblen((const char *) pwcs, encoding);
6402 bruce@momjian.us 305 [ - + ]: 6675771 : if (len < (size_t) chlen)
6638 bruce@momjian.us 306 :UBC 0 : break;
4429 peter_e@gmx.net 307 :CBC 6675771 : w = PQdsplen((const char *) pwcs, encoding);
308 : :
6318 tgl@sss.pgh.pa.us 309 [ + + ]: 6675771 : if (chlen == 1) /* single-byte char */
310 : : {
6402 bruce@momjian.us 311 [ + + ]: 6675107 : if (*pwcs == '\n') /* Newline */
312 : : {
6318 tgl@sss.pgh.pa.us 313 : 9925 : *ptr++ = '\0';
6638 bruce@momjian.us 314 : 9925 : lines->width = linewidth;
315 : 9925 : linewidth = 0;
316 : 9925 : lines++;
317 : 9925 : count--;
5819 tgl@sss.pgh.pa.us 318 [ - + ]: 9925 : if (count <= 0)
6402 bruce@momjian.us 319 :UBC 0 : exit(1); /* Screwup */
320 : :
321 : : /* make next line point to remaining memory */
6638 bruce@momjian.us 322 :CBC 9925 : lines->ptr = ptr;
323 : : }
2489 tgl@sss.pgh.pa.us 324 [ + + ]: 6665182 : else if (*pwcs == '\r') /* Linefeed */
325 : : {
6638 326 : 4 : strcpy((char *) ptr, "\\r");
bruce@momjian.us 327 : 4 : linewidth += 2;
328 : 4 : ptr += 2;
329 : : }
2489 tgl@sss.pgh.pa.us 330 [ + + ]: 6665178 : else if (*pwcs == '\t') /* Tab */
331 : : {
332 : : do
333 : : {
5820 bruce@momjian.us 334 : 759 : *ptr++ = ' ';
335 : 759 : linewidth++;
336 [ + + ]: 759 : } while (linewidth % 8 != 0);
337 : : }
6318 tgl@sss.pgh.pa.us 338 [ + + ]: 6665081 : else if (w < 0) /* Other control char */
339 : : {
6638 340 : 36 : sprintf((char *) ptr, "\\x%02X", *pwcs);
bruce@momjian.us 341 : 36 : linewidth += 4;
342 : 36 : ptr += 4;
343 : : }
344 : : else /* Output it as-is */
345 : : {
6318 tgl@sss.pgh.pa.us 346 : 6665045 : linewidth += w;
6638 bruce@momjian.us 347 : 6665045 : *ptr++ = *pwcs;
348 : : }
349 : : }
6318 tgl@sss.pgh.pa.us 350 [ - + ]: 664 : else if (w < 0) /* Non-ascii control char */
351 : : {
6638 bruce@momjian.us 352 [ # # ]:UBC 0 : if (encoding == PG_UTF8)
4988 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 : : */
6638 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 : :
6402 bruce@momjian.us 370 [ + + ]:CBC 2094 : for (i = 0; i < chlen; i++)
6638 371 : 1430 : *ptr++ = pwcs[i];
372 : 664 : linewidth += w;
373 : : }
374 : 6675771 : len -= chlen;
375 : : }
376 : 633094 : lines->width = linewidth;
5421 377 : 633094 : *ptr++ = '\0'; /* Terminate formatted string */
378 : :
5819 tgl@sss.pgh.pa.us 379 [ - + ]: 633094 : if (count <= 0)
5421 bruce@momjian.us 380 :UBC 0 : exit(1); /* Screwup */
381 : :
5421 bruce@momjian.us 382 :CBC 633094 : (lines + 1)->ptr = NULL; /* terminate line array */
8217 ishii@postgresql.org 383 : 633094 : }
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 *
6638 bruce@momjian.us 392 : 2542441 : mbvalidate(unsigned char *pwcs, int encoding)
393 : : {
7698 peter_e@gmx.net 394 [ + + ]: 2542441 : if (encoding == PG_UTF8)
4412 395 : 2540892 : mb_utf_validate(pwcs);
396 : : else
397 : : {
398 : : /*
399 : : * other encodings needing validation should add their own routines
400 : : * here
401 : : */
402 : : }
403 : :
6777 tgl@sss.pgh.pa.us 404 : 2542441 : return pwcs;
405 : : }
|