1*22dc650dSSadaf Ebrahimi /****************************************************
2*22dc650dSSadaf Ebrahimi * PCRE maintainers' helper program: UTF-8 converter *
3*22dc650dSSadaf Ebrahimi ****************************************************/
4*22dc650dSSadaf Ebrahimi
5*22dc650dSSadaf Ebrahimi /* This is a test program for converting character code points to UTF-8 and
6*22dc650dSSadaf Ebrahimi vice versa. Note that this program conforms to the original definition of
7*22dc650dSSadaf Ebrahimi UTF-8, which allows codepoints up to 7fffffff. The more recent definition
8*22dc650dSSadaf Ebrahimi limits the validity of Unicode UTF-8 codepoints to a maximum of 10ffffff, and
9*22dc650dSSadaf Ebrahimi forbids the "surrogate" code points. This program now gives warnings for these
10*22dc650dSSadaf Ebrahimi invalid code points.
11*22dc650dSSadaf Ebrahimi
12*22dc650dSSadaf Ebrahimi The arguments are either single code point values written as U+hh.. or 0xhh..
13*22dc650dSSadaf Ebrahimi for conversion to UTF-8, or sequences of hex values, written without 0x and
14*22dc650dSSadaf Ebrahimi optionally including spaces (but such arguments must be quoted), for conversion
15*22dc650dSSadaf Ebrahimi from UTF-8 to codepoints. For example:
16*22dc650dSSadaf Ebrahimi
17*22dc650dSSadaf Ebrahimi ./utf8 0x1234
18*22dc650dSSadaf Ebrahimi U+00001234 => e1 88 b4
19*22dc650dSSadaf Ebrahimi
20*22dc650dSSadaf Ebrahimi ./utf8 "e1 88 b4"
21*22dc650dSSadaf Ebrahimi U+00001234 <= e1 88 b4
22*22dc650dSSadaf Ebrahimi
23*22dc650dSSadaf Ebrahimi In the second case, a number of UTF-8 characters can be present in one
24*22dc650dSSadaf Ebrahimi argument. In other words, each such argument is interpreted (after ignoring
25*22dc650dSSadaf Ebrahimi spaces) as a string of UTF-8 bytes representing a string of characters:
26*22dc650dSSadaf Ebrahimi
27*22dc650dSSadaf Ebrahimi ./utf8 "65 e188b4 77"
28*22dc650dSSadaf Ebrahimi 0x00000065 <= 65
29*22dc650dSSadaf Ebrahimi 0x00001234 <= e1 88 b4
30*22dc650dSSadaf Ebrahimi 0x00000077 <= 77
31*22dc650dSSadaf Ebrahimi
32*22dc650dSSadaf Ebrahimi If the option -s is given, the sequence of UTF-bytes is written out between
33*22dc650dSSadaf Ebrahimi angle brackets at the end of the line. On a UTF-8 terminal, this will show the
34*22dc650dSSadaf Ebrahimi appropriate graphic for the code point.
35*22dc650dSSadaf Ebrahimi
36*22dc650dSSadaf Ebrahimi Errors provoke error messages, but the program carries on with the next
37*22dc650dSSadaf Ebrahimi argument. The return code is always zero.
38*22dc650dSSadaf Ebrahimi
39*22dc650dSSadaf Ebrahimi Philip Hazel
40*22dc650dSSadaf Ebrahimi Original creation data: unknown
41*22dc650dSSadaf Ebrahimi Code extended and tidied to avoid compiler warnings: 26 March 2020
42*22dc650dSSadaf Ebrahimi */
43*22dc650dSSadaf Ebrahimi
44*22dc650dSSadaf Ebrahimi
45*22dc650dSSadaf Ebrahimi #include <stdio.h>
46*22dc650dSSadaf Ebrahimi #include <stdlib.h>
47*22dc650dSSadaf Ebrahimi #include <ctype.h>
48*22dc650dSSadaf Ebrahimi #include <string.h>
49*22dc650dSSadaf Ebrahimi
50*22dc650dSSadaf Ebrahimi /* The valid ranges for UTF-8 characters are:
51*22dc650dSSadaf Ebrahimi
52*22dc650dSSadaf Ebrahimi 0000 0000 to 0000 007f 1 byte (ascii)
53*22dc650dSSadaf Ebrahimi 0000 0080 to 0000 07ff 2 bytes
54*22dc650dSSadaf Ebrahimi 0000 0800 to 0000 ffff 3 bytes
55*22dc650dSSadaf Ebrahimi 0001 0000 to 001f ffff 4 bytes
56*22dc650dSSadaf Ebrahimi 0020 0000 to 03ff ffff 5 bytes
57*22dc650dSSadaf Ebrahimi 0400 0000 to 7fff ffff 6 bytes
58*22dc650dSSadaf Ebrahimi */
59*22dc650dSSadaf Ebrahimi
60*22dc650dSSadaf Ebrahimi
61*22dc650dSSadaf Ebrahimi static const unsigned int utf8_table1[] = {
62*22dc650dSSadaf Ebrahimi 0x0000007f, 0x000007ff, 0x0000ffff, 0x001fffff, 0x03ffffff, 0x7fffffff};
63*22dc650dSSadaf Ebrahimi
64*22dc650dSSadaf Ebrahimi static const int utf8_table2[] = {
65*22dc650dSSadaf Ebrahimi 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc};
66*22dc650dSSadaf Ebrahimi
67*22dc650dSSadaf Ebrahimi static const int utf8_table3[] = {
68*22dc650dSSadaf Ebrahimi 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
69*22dc650dSSadaf Ebrahimi
70*22dc650dSSadaf Ebrahimi
71*22dc650dSSadaf Ebrahimi /*************************************************
72*22dc650dSSadaf Ebrahimi * Convert character value to UTF-8 *
73*22dc650dSSadaf Ebrahimi *************************************************/
74*22dc650dSSadaf Ebrahimi
75*22dc650dSSadaf Ebrahimi /* This function takes an unsigned long integer value in the range 0 -
76*22dc650dSSadaf Ebrahimi 0x7fffffff and encodes it as a UTF-8 character in 1 to 6 bytes.
77*22dc650dSSadaf Ebrahimi
78*22dc650dSSadaf Ebrahimi Arguments:
79*22dc650dSSadaf Ebrahimi cvalue the character value
80*22dc650dSSadaf Ebrahimi buffer pointer to buffer for result - at least 6 bytes long
81*22dc650dSSadaf Ebrahimi
82*22dc650dSSadaf Ebrahimi Returns: number of bytes placed in the buffer
83*22dc650dSSadaf Ebrahimi 0 if input code point is too big
84*22dc650dSSadaf Ebrahimi */
85*22dc650dSSadaf Ebrahimi
86*22dc650dSSadaf Ebrahimi static size_t
ord2utf8(unsigned long int cvalue,unsigned char * buffer)87*22dc650dSSadaf Ebrahimi ord2utf8(unsigned long int cvalue, unsigned char *buffer)
88*22dc650dSSadaf Ebrahimi {
89*22dc650dSSadaf Ebrahimi size_t i, j;
90*22dc650dSSadaf Ebrahimi for (i = 0; i < sizeof(utf8_table1)/sizeof(int); i++)
91*22dc650dSSadaf Ebrahimi if (cvalue <= utf8_table1[i]) break;
92*22dc650dSSadaf Ebrahimi if (i >= sizeof(utf8_table1)/sizeof(int)) return 0;
93*22dc650dSSadaf Ebrahimi buffer += i;
94*22dc650dSSadaf Ebrahimi for (j = i; j > 0; j--)
95*22dc650dSSadaf Ebrahimi {
96*22dc650dSSadaf Ebrahimi *buffer-- = 0x80 | (cvalue & 0x3f);
97*22dc650dSSadaf Ebrahimi cvalue >>= 6;
98*22dc650dSSadaf Ebrahimi }
99*22dc650dSSadaf Ebrahimi *buffer = utf8_table2[i] | cvalue;
100*22dc650dSSadaf Ebrahimi return i + 1;
101*22dc650dSSadaf Ebrahimi }
102*22dc650dSSadaf Ebrahimi
103*22dc650dSSadaf Ebrahimi
104*22dc650dSSadaf Ebrahimi
105*22dc650dSSadaf Ebrahimi /*************************************************
106*22dc650dSSadaf Ebrahimi * Convert UTF-8 string to value *
107*22dc650dSSadaf Ebrahimi *************************************************/
108*22dc650dSSadaf Ebrahimi
109*22dc650dSSadaf Ebrahimi /* This function takes one or more bytes that represent a UTF-8 character from
110*22dc650dSSadaf Ebrahimi the start of a string of bytes. It returns the value of the character, or the
111*22dc650dSSadaf Ebrahimi offset of a malformation. For an overlong encoding that works but is not the
112*22dc650dSSadaf Ebrahimi correct (shortest) one, the error offset is just after the last byte.
113*22dc650dSSadaf Ebrahimi
114*22dc650dSSadaf Ebrahimi Argument:
115*22dc650dSSadaf Ebrahimi buffer a pointer to the byte vector
116*22dc650dSSadaf Ebrahimi buffend a pointer to the end of the buffer
117*22dc650dSSadaf Ebrahimi vptr a pointer to a variable to receive the value
118*22dc650dSSadaf Ebrahimi lenptr a pointer to a variable to receive the offset when error detected
119*22dc650dSSadaf Ebrahimi
120*22dc650dSSadaf Ebrahimi Returns: > 0 => the number of bytes consumed
121*22dc650dSSadaf Ebrahimi 0 => invalid UTF-8: first byte missing 0x40 bit
122*22dc650dSSadaf Ebrahimi -1 => invalid UTF-8: first byte has too many high-order 1-bits
123*22dc650dSSadaf Ebrahimi -2 => incomplete sequence at end of string
124*22dc650dSSadaf Ebrahimi -3 => incomplete sequence within string
125*22dc650dSSadaf Ebrahimi -4 => overlong code sequence
126*22dc650dSSadaf Ebrahimi */
127*22dc650dSSadaf Ebrahimi
128*22dc650dSSadaf Ebrahimi static int
utf82ord(unsigned char * buffer,unsigned char * buffend,long unsigned int * vptr,int * lenptr)129*22dc650dSSadaf Ebrahimi utf82ord(unsigned char *buffer, unsigned char *buffend,
130*22dc650dSSadaf Ebrahimi long unsigned int *vptr, int *lenptr)
131*22dc650dSSadaf Ebrahimi {
132*22dc650dSSadaf Ebrahimi unsigned int c = *buffer++;
133*22dc650dSSadaf Ebrahimi unsigned int d = c;
134*22dc650dSSadaf Ebrahimi int i, j, s;
135*22dc650dSSadaf Ebrahimi
136*22dc650dSSadaf Ebrahimi /* Check for an ASCII character, or find the number of additional bytes in a
137*22dc650dSSadaf Ebrahimi multibyte character. */
138*22dc650dSSadaf Ebrahimi
139*22dc650dSSadaf Ebrahimi for (i = -1; i < 6; i++)
140*22dc650dSSadaf Ebrahimi {
141*22dc650dSSadaf Ebrahimi if ((d & 0x80) == 0) break;
142*22dc650dSSadaf Ebrahimi d <<= 1;
143*22dc650dSSadaf Ebrahimi }
144*22dc650dSSadaf Ebrahimi
145*22dc650dSSadaf Ebrahimi switch (i)
146*22dc650dSSadaf Ebrahimi {
147*22dc650dSSadaf Ebrahimi case -1: /* ASCII character; first byte does not have 0x80 bit */
148*22dc650dSSadaf Ebrahimi *vptr = c;
149*22dc650dSSadaf Ebrahimi return 1;
150*22dc650dSSadaf Ebrahimi
151*22dc650dSSadaf Ebrahimi case 0: /* First byte has 0x80 but is missing 0x40 bit */
152*22dc650dSSadaf Ebrahimi *lenptr = 0;
153*22dc650dSSadaf Ebrahimi return 0;
154*22dc650dSSadaf Ebrahimi
155*22dc650dSSadaf Ebrahimi case 6:
156*22dc650dSSadaf Ebrahimi *lenptr = 0; /* Too many high bits */
157*22dc650dSSadaf Ebrahimi return -1;
158*22dc650dSSadaf Ebrahimi
159*22dc650dSSadaf Ebrahimi default:
160*22dc650dSSadaf Ebrahimi break;
161*22dc650dSSadaf Ebrahimi }
162*22dc650dSSadaf Ebrahimi
163*22dc650dSSadaf Ebrahimi /* i now has a value in the range 1-5 */
164*22dc650dSSadaf Ebrahimi
165*22dc650dSSadaf Ebrahimi s = 6*i;
166*22dc650dSSadaf Ebrahimi d = (c & utf8_table3[i]) << s;
167*22dc650dSSadaf Ebrahimi
168*22dc650dSSadaf Ebrahimi for (j = 0; j < i; j++)
169*22dc650dSSadaf Ebrahimi {
170*22dc650dSSadaf Ebrahimi if (buffer >= buffend)
171*22dc650dSSadaf Ebrahimi {
172*22dc650dSSadaf Ebrahimi *lenptr = j + 1;
173*22dc650dSSadaf Ebrahimi return -2;
174*22dc650dSSadaf Ebrahimi }
175*22dc650dSSadaf Ebrahimi c = *buffer++;
176*22dc650dSSadaf Ebrahimi if ((c & 0xc0) != 0x80)
177*22dc650dSSadaf Ebrahimi {
178*22dc650dSSadaf Ebrahimi *lenptr = j + 1;
179*22dc650dSSadaf Ebrahimi return -3;
180*22dc650dSSadaf Ebrahimi }
181*22dc650dSSadaf Ebrahimi s -= 6;
182*22dc650dSSadaf Ebrahimi d |= (c & 0x3f) << s;
183*22dc650dSSadaf Ebrahimi }
184*22dc650dSSadaf Ebrahimi
185*22dc650dSSadaf Ebrahimi /* Valid UTF-8 syntax */
186*22dc650dSSadaf Ebrahimi
187*22dc650dSSadaf Ebrahimi *vptr = d;
188*22dc650dSSadaf Ebrahimi
189*22dc650dSSadaf Ebrahimi /* Check that encoding was the correct one, not overlong */
190*22dc650dSSadaf Ebrahimi
191*22dc650dSSadaf Ebrahimi for (j = 0; j < (int)(sizeof(utf8_table1)/sizeof(int)); j++)
192*22dc650dSSadaf Ebrahimi if (d <= utf8_table1[j]) break;
193*22dc650dSSadaf Ebrahimi if (j != i)
194*22dc650dSSadaf Ebrahimi {
195*22dc650dSSadaf Ebrahimi *lenptr = i + 1;
196*22dc650dSSadaf Ebrahimi return -4;
197*22dc650dSSadaf Ebrahimi }
198*22dc650dSSadaf Ebrahimi
199*22dc650dSSadaf Ebrahimi /* Valid value */
200*22dc650dSSadaf Ebrahimi
201*22dc650dSSadaf Ebrahimi return i + 1;
202*22dc650dSSadaf Ebrahimi }
203*22dc650dSSadaf Ebrahimi
204*22dc650dSSadaf Ebrahimi
205*22dc650dSSadaf Ebrahimi
206*22dc650dSSadaf Ebrahimi /*************************************************
207*22dc650dSSadaf Ebrahimi * Main Program *
208*22dc650dSSadaf Ebrahimi *************************************************/
209*22dc650dSSadaf Ebrahimi
210*22dc650dSSadaf Ebrahimi int
main(int argc,char ** argv)211*22dc650dSSadaf Ebrahimi main(int argc, char **argv)
212*22dc650dSSadaf Ebrahimi {
213*22dc650dSSadaf Ebrahimi int i = 1;
214*22dc650dSSadaf Ebrahimi int show = 0;
215*22dc650dSSadaf Ebrahimi unsigned char buffer[64];
216*22dc650dSSadaf Ebrahimi
217*22dc650dSSadaf Ebrahimi if (argc > 1 && strcmp(argv[1], "-s") == 0)
218*22dc650dSSadaf Ebrahimi {
219*22dc650dSSadaf Ebrahimi show = 1;
220*22dc650dSSadaf Ebrahimi i = 2;
221*22dc650dSSadaf Ebrahimi }
222*22dc650dSSadaf Ebrahimi
223*22dc650dSSadaf Ebrahimi for (; i < argc; i++)
224*22dc650dSSadaf Ebrahimi {
225*22dc650dSSadaf Ebrahimi char *x = argv[i];
226*22dc650dSSadaf Ebrahimi char *endptr;
227*22dc650dSSadaf Ebrahimi if (strncmp(x, "0x", 2) == 0 || strncmp(x, "U+", 2) == 0)
228*22dc650dSSadaf Ebrahimi {
229*22dc650dSSadaf Ebrahimi size_t rc, j;
230*22dc650dSSadaf Ebrahimi unsigned long int d = strtoul(x+2, &endptr, 16);
231*22dc650dSSadaf Ebrahimi if (*endptr != 0)
232*22dc650dSSadaf Ebrahimi {
233*22dc650dSSadaf Ebrahimi printf("** Invalid hex number %s\n", x);
234*22dc650dSSadaf Ebrahimi continue; /* With next argument */
235*22dc650dSSadaf Ebrahimi }
236*22dc650dSSadaf Ebrahimi rc = ord2utf8(d, buffer);
237*22dc650dSSadaf Ebrahimi printf("U+%08lx => ", d);
238*22dc650dSSadaf Ebrahimi if (rc == 0)
239*22dc650dSSadaf Ebrahimi printf("** Code point greater than 0x7fffffff cannot be encoded");
240*22dc650dSSadaf Ebrahimi else
241*22dc650dSSadaf Ebrahimi {
242*22dc650dSSadaf Ebrahimi for (j = 0; j < rc; j++) printf("%02x ", buffer[j]);
243*22dc650dSSadaf Ebrahimi if (show)
244*22dc650dSSadaf Ebrahimi {
245*22dc650dSSadaf Ebrahimi printf(">");
246*22dc650dSSadaf Ebrahimi for (j = 0; j < rc; j++) printf("%c", buffer[j]);
247*22dc650dSSadaf Ebrahimi printf("< ");
248*22dc650dSSadaf Ebrahimi }
249*22dc650dSSadaf Ebrahimi if (d >= 0xd800 && d <= 0xdfff)
250*22dc650dSSadaf Ebrahimi printf("** Invalid Unicode (surrogate)");
251*22dc650dSSadaf Ebrahimi else if (d > 0x10ffff)
252*22dc650dSSadaf Ebrahimi printf("** Invalid Unicode (greater than U+10ffff)");
253*22dc650dSSadaf Ebrahimi }
254*22dc650dSSadaf Ebrahimi printf("\n");
255*22dc650dSSadaf Ebrahimi }
256*22dc650dSSadaf Ebrahimi else
257*22dc650dSSadaf Ebrahimi {
258*22dc650dSSadaf Ebrahimi unsigned char *bptr;
259*22dc650dSSadaf Ebrahimi unsigned char *buffend;
260*22dc650dSSadaf Ebrahimi int len = 0;
261*22dc650dSSadaf Ebrahimi int y = 0;
262*22dc650dSSadaf Ebrahimi int z = 0;
263*22dc650dSSadaf Ebrahimi
264*22dc650dSSadaf Ebrahimi for (;;)
265*22dc650dSSadaf Ebrahimi {
266*22dc650dSSadaf Ebrahimi while (*x == ' ') x++;
267*22dc650dSSadaf Ebrahimi if (*x == 0 && !z) break;
268*22dc650dSSadaf Ebrahimi if (!isxdigit(*x))
269*22dc650dSSadaf Ebrahimi {
270*22dc650dSSadaf Ebrahimi printf("** Malformed hex string: %s\n", argv[i]);
271*22dc650dSSadaf Ebrahimi len = -1;
272*22dc650dSSadaf Ebrahimi break;
273*22dc650dSSadaf Ebrahimi }
274*22dc650dSSadaf Ebrahimi y = y * 16 + tolower(*x) - ((isdigit(*x))? '0' : 'W');
275*22dc650dSSadaf Ebrahimi x++;
276*22dc650dSSadaf Ebrahimi if (z)
277*22dc650dSSadaf Ebrahimi {
278*22dc650dSSadaf Ebrahimi buffer[len++] = y;
279*22dc650dSSadaf Ebrahimi y = 0;
280*22dc650dSSadaf Ebrahimi }
281*22dc650dSSadaf Ebrahimi z ^= 1;
282*22dc650dSSadaf Ebrahimi }
283*22dc650dSSadaf Ebrahimi
284*22dc650dSSadaf Ebrahimi if (len < 0) continue; /* With next argument after malformation */
285*22dc650dSSadaf Ebrahimi
286*22dc650dSSadaf Ebrahimi bptr = buffer;
287*22dc650dSSadaf Ebrahimi buffend = buffer + len;
288*22dc650dSSadaf Ebrahimi
289*22dc650dSSadaf Ebrahimi while (bptr < buffend)
290*22dc650dSSadaf Ebrahimi {
291*22dc650dSSadaf Ebrahimi unsigned long int d;
292*22dc650dSSadaf Ebrahimi int j;
293*22dc650dSSadaf Ebrahimi int offset;
294*22dc650dSSadaf Ebrahimi int rc = utf82ord(bptr, buffend, &d, &offset);
295*22dc650dSSadaf Ebrahimi
296*22dc650dSSadaf Ebrahimi if (rc > 0)
297*22dc650dSSadaf Ebrahimi {
298*22dc650dSSadaf Ebrahimi printf("U+%08lx <= ", d);
299*22dc650dSSadaf Ebrahimi for (j = 0; j < rc; j++) printf("%02x ", bptr[j]);
300*22dc650dSSadaf Ebrahimi if (show)
301*22dc650dSSadaf Ebrahimi {
302*22dc650dSSadaf Ebrahimi printf(">");
303*22dc650dSSadaf Ebrahimi for (j = 0; j < rc; j++) printf("%c", bptr[j]);
304*22dc650dSSadaf Ebrahimi printf("<");
305*22dc650dSSadaf Ebrahimi }
306*22dc650dSSadaf Ebrahimi printf("\n");
307*22dc650dSSadaf Ebrahimi bptr += rc;
308*22dc650dSSadaf Ebrahimi }
309*22dc650dSSadaf Ebrahimi else if (rc == -4)
310*22dc650dSSadaf Ebrahimi {
311*22dc650dSSadaf Ebrahimi printf("U+%08lx <= ", d);
312*22dc650dSSadaf Ebrahimi for (j = 0; j < offset; j++) printf("%02x ", bptr[j]);
313*22dc650dSSadaf Ebrahimi printf("** Overlong UTF-8 sequence\n");
314*22dc650dSSadaf Ebrahimi bptr += offset;
315*22dc650dSSadaf Ebrahimi }
316*22dc650dSSadaf Ebrahimi else
317*22dc650dSSadaf Ebrahimi {
318*22dc650dSSadaf Ebrahimi switch (rc)
319*22dc650dSSadaf Ebrahimi {
320*22dc650dSSadaf Ebrahimi case 0: printf("** First byte missing 0x40 bit");
321*22dc650dSSadaf Ebrahimi break;
322*22dc650dSSadaf Ebrahimi
323*22dc650dSSadaf Ebrahimi case -1: printf("** First byte has too many high-order bits");
324*22dc650dSSadaf Ebrahimi break;
325*22dc650dSSadaf Ebrahimi
326*22dc650dSSadaf Ebrahimi case -2: printf("** Incomplete UTF-8 sequence at end of string");
327*22dc650dSSadaf Ebrahimi break;
328*22dc650dSSadaf Ebrahimi
329*22dc650dSSadaf Ebrahimi case -3: printf("** Incomplete UTF-8 sequence");
330*22dc650dSSadaf Ebrahimi break;
331*22dc650dSSadaf Ebrahimi
332*22dc650dSSadaf Ebrahimi default: printf("** Unexpected return %d from utf82ord()", rc);
333*22dc650dSSadaf Ebrahimi break;
334*22dc650dSSadaf Ebrahimi }
335*22dc650dSSadaf Ebrahimi printf(" at offset %d in string ", offset);
336*22dc650dSSadaf Ebrahimi while (bptr < buffend) printf("%02x ", *bptr++);
337*22dc650dSSadaf Ebrahimi printf("\n");
338*22dc650dSSadaf Ebrahimi break;
339*22dc650dSSadaf Ebrahimi }
340*22dc650dSSadaf Ebrahimi }
341*22dc650dSSadaf Ebrahimi }
342*22dc650dSSadaf Ebrahimi }
343*22dc650dSSadaf Ebrahimi
344*22dc650dSSadaf Ebrahimi return 0;
345*22dc650dSSadaf Ebrahimi }
346*22dc650dSSadaf Ebrahimi
347*22dc650dSSadaf Ebrahimi /* End */
348