Imported new UTF-8 routines from my 1.1 tree.
[silc.git] / lib / silcutil / silcutf8.c
1 /*
2
3   silcutf8.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2004 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silcincludes.h"
21 #include "silcutf8.h"
22
23 /* Encodes the string `bin' of which encoding is `bin_encoding' to the
24    UTF-8 encoding into the buffer `utf8' which is of size of `utf8_size'.
25    Returns the length of the UTF-8 encoded string, or zero (0) on error.
26    By default `bin_encoding' is ASCII, and the caller needs to know the
27    encoding of the input string if it is anything else. */
28
29 SilcUInt32 silc_utf8_encode(const unsigned char *bin, SilcUInt32 bin_len,
30                             SilcStringEncoding bin_encoding,
31                             unsigned char *utf8, SilcUInt32 utf8_size)
32 {
33   SilcUInt32 enclen = 0, i, charval = 0;
34
35   if (!bin || !bin_len)
36     return 0;
37
38   if (bin_encoding == SILC_STRING_UTF8 ||
39       (silc_utf8_valid(bin, bin_len) && bin_len <= utf8_size)) {
40     memcpy(utf8, bin, bin_len);
41     return bin_len;
42   }
43
44   if (bin_encoding == SILC_STRING_LOCALE) {
45 #if defined(HAVE_ICONV) && defined(HAVE_NL_LANGINFO) && defined(CODESET)
46     char *fromconv, *icp, *ocp;
47     iconv_t icd;
48     size_t inlen, outlen;
49
50     setlocale(LC_CTYPE, "");
51     fromconv = nl_langinfo(CODESET);
52     if (fromconv && strlen(fromconv)) {
53       icd = iconv_open("UTF-8", fromconv);
54       icp = (char *)bin;
55       ocp = (char *)utf8;
56       inlen = bin_len;
57       outlen = utf8_size;
58       if (icp && ocp && icd != (iconv_t)-1) {
59         if (iconv(icd, &icp, &inlen, &ocp, &outlen) != -1) {
60           utf8_size -= outlen;
61           iconv_close(icd);
62           return utf8_size;
63         }
64       }
65       if (icd != (iconv_t)-1)
66         iconv_close(icd);
67     }
68 #endif
69
70     /* Fallback to 8-bit ASCII */
71     bin_encoding = SILC_STRING_ASCII;
72   }
73
74   for (i = 0; i < bin_len; i++) {
75     switch (bin_encoding) {
76     case SILC_STRING_ASCII:
77     case SILC_STRING_TELETEX:
78       charval = bin[i];
79       break;
80     case SILC_STRING_ASCII_ESC:
81       SILC_NOT_IMPLEMENTED("SILC_STRING_ASCII_ESC");
82       return 0;
83       break;
84     case SILC_STRING_BMP:
85       if (i + 1 >= bin_len)
86         return 0;
87       SILC_GET16_MSB(charval, bin + i);
88       i += 1;
89       break;
90     case SILC_STRING_BMP_LSB:
91       if (i + 1 >= bin_len)
92         return 0;
93       SILC_GET16_LSB(charval, bin + i);
94       i += 1;
95       break;
96     case SILC_STRING_UNIVERSAL:
97       if (i + 3 >= bin_len)
98         return 0;
99       SILC_GET32_MSB(charval, bin + i);
100       i += 3;
101       break;
102     case SILC_STRING_UNIVERSAL_LSB:
103       if (i + 3 >= bin_len)
104         return 0;
105       SILC_GET32_LSB(charval, bin + i);
106       i += 3;
107       break;
108     case SILC_STRING_PRINTABLE:
109     case SILC_STRING_VISIBLE:
110       if (!isprint(bin[i]))
111         return 0;
112       charval = bin[i];
113       break;
114     case SILC_STRING_NUMERICAL:
115       if (bin[i] != 0x20 && !isdigit(bin[i]))
116         return 0;
117       charval = bin[i];
118       break;
119     case SILC_STRING_LDAP_DN:
120       /* Remove any escaping */
121       if (bin[i] == '\\') {
122         unsigned char cv;
123         if (i + 1 >= bin_len)
124           return 0;
125
126         /* If escaped character is any of the following no processing is
127            needed, otherwise it is a hex value and we need to read it. */
128         cv = bin[++i];
129         if (cv != ',' && cv != '+' && cv != '"' && cv != '\\' && cv != '<' &&
130             cv != '>' && cv != ';' && cv != ' ' && cv != '#') {
131           unsigned int hexval;
132           if (i + 1 >= bin_len)
133             return 0;
134           if (sscanf(&bin[++i], "%02X", &hexval) != 1)
135             return 0;
136           cv = (unsigned char)hexval;
137         }
138
139         charval = cv;
140         break;
141       }
142       charval = bin[i];
143       break;
144     default:
145       return 0;
146       break;
147     }
148
149     if (charval < 0x80) {
150       if (utf8) {
151         if (enclen > utf8_size)
152           return 0;
153
154         utf8[enclen] = (unsigned char)charval;
155       }
156       enclen++;
157     } else if (charval < 0x800) {
158       if (utf8) {
159         if (enclen + 2 > utf8_size)
160           return 0;
161
162         utf8[enclen    ] = (unsigned char )(((charval >> 6)  & 0x1f) | 0xc0);
163         utf8[enclen + 1] = (unsigned char )((charval & 0x3f) | 0x80);
164       }
165       enclen += 2;
166     } else if (charval < 0x10000) {
167       if (utf8) {
168         if (enclen + 3 > utf8_size)
169           return 0;
170
171         utf8[enclen    ] = (unsigned char )(((charval >> 12) & 0xf)  | 0xe0);
172         utf8[enclen + 1] = (unsigned char )(((charval >> 6)  & 0x3f) | 0x80);
173         utf8[enclen + 2] = (unsigned char )((charval & 0x3f) | 0x80);
174       }
175       enclen += 3;
176     } else if (charval < 0x200000) {
177       if (utf8) {
178         if (enclen + 4 > utf8_size)
179           return 0;
180
181         utf8[enclen    ] = (unsigned char )(((charval >> 18) & 0x7)  | 0xf0);
182         utf8[enclen + 1] = (unsigned char )(((charval >> 12) & 0x3f) | 0x80);
183         utf8[enclen + 2] = (unsigned char )(((charval >> 6)  & 0x3f) | 0x80);
184         utf8[enclen + 3] = (unsigned char )((charval & 0x3f) | 0x80);
185       }
186       enclen += 4;
187     } else if (charval < 0x4000000) {
188       if (utf8) {
189         if (enclen + 5 > utf8_size)
190           return 0;
191
192         utf8[enclen    ] = (unsigned char )(((charval >> 24) & 0x3)  | 0xf8);
193         utf8[enclen + 1] = (unsigned char )(((charval >> 18) & 0x3f) | 0x80);
194         utf8[enclen + 2] = (unsigned char )(((charval >> 12) & 0x3f) | 0x80);
195         utf8[enclen + 3] = (unsigned char )(((charval >> 6)  & 0x3f) | 0x80);
196         utf8[enclen + 4] = (unsigned char )((charval & 0x3f) | 0x80);
197       }
198       enclen += 5;
199     } else {
200       if (utf8) {
201         if (enclen + 6 > utf8_size)
202           return 0;
203
204         utf8[enclen    ] = (unsigned char )(((charval >> 30) & 0x1)  | 0xfc);
205         utf8[enclen + 1] = (unsigned char )(((charval >> 24) & 0x3f) | 0x80);
206         utf8[enclen + 2] = (unsigned char )(((charval >> 18) & 0x3f) | 0x80);
207         utf8[enclen + 3] = (unsigned char )(((charval >> 12) & 0x3f) | 0x80);
208         utf8[enclen + 4] = (unsigned char )(((charval >> 6)  & 0x3f) | 0x80);
209         utf8[enclen + 5] = (unsigned char )((charval & 0x3f) | 0x80);
210       }
211       enclen += 6;
212     }
213   }
214
215   return enclen;
216 }
217
218 /* Decodes UTF-8 encoded string `utf8' to string of which encoding is
219    to be `bin_encoding', into the `bin' buffer of size of `bin_size'.
220    Returns the length of the decoded buffer, or zero (0) on error.
221    By default `bin_encoding' is ASCII, and the caller needs to know to
222    which encoding the output string is to be encoded if ASCII is not
223    desired. */
224
225 SilcUInt32 silc_utf8_decode(const unsigned char *utf8, SilcUInt32 utf8_len,
226                             SilcStringEncoding bin_encoding,
227                             unsigned char *bin, SilcUInt32 bin_size)
228 {
229   SilcUInt32 enclen = 0, i, charval;
230
231   if (!utf8 || !utf8_len)
232     return 0;
233
234   if (bin_encoding == SILC_STRING_UTF8) {
235     if (!silc_utf8_valid(utf8, utf8_len) ||
236         utf8_len > bin_size)
237       return 0;
238     memcpy(bin, utf8, utf8_len);
239     return utf8_len;
240   }
241
242   if (bin_encoding == SILC_STRING_LOCALE) {
243 #if defined(HAVE_ICONV) && defined(HAVE_NL_LANGINFO) && defined(CODESET)
244     char *toconv, *icp, *ocp;
245     iconv_t icd;
246     size_t inlen, outlen;
247
248     setlocale(LC_CTYPE, "");
249     toconv = nl_langinfo(CODESET);
250     if (toconv && strlen(toconv)) {
251       icd = iconv_open(toconv, "UTF-8");
252       icp = (char *)utf8;
253       ocp = (char *)bin;
254       inlen = utf8_len;
255       outlen = bin_size;
256       if (icp && ocp && icd != (iconv_t)-1) {
257         if (iconv(icd, &icp, &inlen, &ocp, &outlen) != -1) {
258           bin_size -= outlen;
259           iconv_close(icd);
260           return bin_size;
261         }
262       }
263       if (icd != (iconv_t)-1)
264         iconv_close(icd);
265     }
266 #endif
267
268     /* Fallback to 8-bit ASCII */
269     bin_encoding = SILC_STRING_ASCII;
270   }
271
272   for (i = 0; i < utf8_len; i++) {
273     if ((utf8[i] & 0x80) == 0x00) {
274       charval = utf8[i] & 0x7f;
275     } else if ((utf8[i] & 0xe0) == 0xc0) {
276       if (i + 1 >= utf8_len)
277         return 0;
278
279       if ((utf8[i + 1] & 0xc0) != 0x80)
280         return 0;
281
282       charval = (utf8[i++] & 0x1f) << 6;
283       charval |= utf8[i] & 0x3f;
284       if (charval < 0x80)
285         return 0;
286     } else if ((utf8[i] & 0xf0) == 0xe0) {
287       if (i + 2 >= utf8_len)
288         return 0;
289
290       if (((utf8[i + 1] & 0xc0) != 0x80) ||
291           ((utf8[i + 2] & 0xc0) != 0x80))
292         return 0;
293
294       /* Surrogates not allowed (D800-DFFF) */
295       if (utf8[i] == 0xed &&
296           utf8[i + 1] >= 0xa0 && utf8[i + 1] <= 0xbf &&
297           utf8[i + 2] >= 0x80 && utf8[i + 2] <= 0xbf)
298         return 0;
299
300       charval = (utf8[i++]  & 0xf)  << 12;
301       charval |= (utf8[i++] & 0x3f) << 6;
302       charval |= utf8[i] & 0x3f;
303       if (charval < 0x800)
304         return 0;
305     } else if ((utf8[i] & 0xf8) == 0xf0) {
306       if (i + 3 >= utf8_len)
307         return 0;
308
309       if (((utf8[i + 1] & 0xc0) != 0x80) ||
310           ((utf8[i + 2] & 0xc0) != 0x80) ||
311           ((utf8[i + 3] & 0xc0) != 0x80))
312         return 0;
313
314       charval = ((SilcUInt32)(utf8[i++] & 0x7)) << 18;
315       charval |= (utf8[i++] & 0x3f) << 12;
316       charval |= (utf8[i++] & 0x3f) << 6;
317       charval |= utf8[i] & 0x3f;
318       if (charval < 0x10000)
319         return 0;
320     } else if ((utf8[i] & 0xfc) == 0xf8) {
321       if (i + 4 >= utf8_len)
322         return 0;
323
324       if (((utf8[i + 1] & 0xc0) != 0x80) ||
325           ((utf8[i + 2] & 0xc0) != 0x80) ||
326           ((utf8[i + 3] & 0xc0) != 0x80) ||
327           ((utf8[i + 4] & 0xc0) != 0x80))
328         return 0;
329
330       charval = ((SilcUInt32)(utf8[i++]  & 0x3))  << 24;
331       charval |= ((SilcUInt32)(utf8[i++] & 0x3f)) << 18;
332       charval |= ((SilcUInt32)(utf8[i++] & 0x3f)) << 12;
333       charval |= (utf8[i++] & 0x3f) << 6;
334       charval |= utf8[i] & 0x3f;
335       if (charval < 0x200000)
336         return 0;
337     } else if ((utf8[i] & 0xfe) == 0xfc) {
338       if (i + 5 >= utf8_len)
339         return 0;
340
341       if (((utf8[i + 1] & 0xc0) != 0x80) ||
342           ((utf8[i + 2] & 0xc0) != 0x80) ||
343           ((utf8[i + 3] & 0xc0) != 0x80) ||
344           ((utf8[i + 4] & 0xc0) != 0x80) ||
345           ((utf8[i + 5] & 0xc0) != 0x80))
346         return 0;
347
348       charval = ((SilcUInt32)(utf8[i++]  & 0x1))  << 30;
349       charval |= ((SilcUInt32)(utf8[i++] & 0x3f)) << 24;
350       charval |= ((SilcUInt32)(utf8[i++] & 0x3f)) << 18;
351       charval |= ((SilcUInt32)(utf8[i++] & 0x3f)) << 12;
352       charval |= (utf8[i++] & 0x3f) << 6;
353       charval |= utf8[i] & 0x3f;
354       if (charval < 0x4000000)
355         return 0;
356     } else {
357       return 0;
358     }
359
360     switch (bin_encoding) {
361     case SILC_STRING_ASCII:
362     case SILC_STRING_PRINTABLE:
363     case SILC_STRING_VISIBLE:
364     case SILC_STRING_TELETEX:
365     case SILC_STRING_NUMERICAL:
366       if (bin) {
367         if (enclen + 1 > bin_size)
368           return 0;
369
370         bin[enclen] = (unsigned char)charval;
371       }
372       enclen++;
373       break;
374     case SILC_STRING_ASCII_ESC:
375       SILC_NOT_IMPLEMENTED("SILC_STRING_ASCII_ESC");
376       return 0;
377       break;
378     case SILC_STRING_BMP:
379       if (bin) {
380         if (enclen + 2 > bin_size)
381           return 0;
382         SILC_PUT16_MSB(charval, bin + enclen);
383       }
384       enclen += 2;
385       break;
386     case SILC_STRING_BMP_LSB:
387       if (bin) {
388         if (enclen + 2 > bin_size)
389           return 0;
390         SILC_PUT16_LSB(charval, bin + enclen);
391       }
392       enclen += 2;
393       break;
394     case SILC_STRING_UNIVERSAL:
395       if (bin) {
396         if (enclen + 4 > bin_size)
397           return 0;
398         SILC_PUT32_MSB(charval, bin + enclen);
399       }
400       enclen += 4;
401       break;
402     case SILC_STRING_UNIVERSAL_LSB:
403       if (bin) {
404         if (enclen + 4 > bin_size)
405           return 0;
406         SILC_PUT32_LSB(charval, bin + enclen);
407       }
408       enclen += 4;
409       break;
410     case SILC_STRING_LDAP_DN:
411       {
412         /* XXX multibyte handling */
413         unsigned char cv = (unsigned char)charval;
414
415         /* If string starts with space or # escape it */
416         if (!enclen && (cv == '#' || cv == ' ')) {
417           if (bin) {
418             if (enclen + 2 > bin_size)
419               return 0;
420             bin[enclen] = '\\';
421             bin[enclen + 1] = cv;
422           }
423           enclen += 2;
424           break;
425         }
426
427         /* If string ends with space escape it */
428         if (i == utf8_len - 1 && cv == ' ') {
429           if (bin) {
430             if (enclen + 2 > bin_size)
431               return 0;
432             bin[enclen] = '\\';
433             bin[enclen + 1] = cv;
434           }
435           enclen += 2;
436           break;
437         }
438
439         /* If character is any of following then escape */
440         if (cv == ',' || cv == '+' || cv == '"' || cv == '\\' || cv == '<' ||
441             cv == '>' || cv == ';') {
442           if (bin) {
443             if (enclen + 2 > bin_size)
444               return 0;
445             bin[enclen] = '\\';
446             bin[enclen + 1] = cv;
447           }
448           enclen += 2;
449           break;
450         }
451
452         /* If character is not printable escape it with hex character */
453         if (!isprint((int)cv)) {
454           if (bin) {
455             if (enclen + 2 > bin_size)
456               return 0;
457             bin[enclen] = '\\';
458             snprintf(bin + enclen + 1, 3, "%02X", cv);
459           }
460           enclen += 2;
461           break;
462         }
463
464         if (bin) {
465           if (enclen + 1 > bin_size)
466             return 0;
467           bin[enclen] = cv;
468         }
469         enclen++;
470       }
471       break;
472     default:
473       return 0;
474       break;
475     }
476   }
477
478   return enclen;
479 }
480
481 /* Returns the length of UTF-8 encoded string if the `bin' of
482    encoding of `bin_encoding' is encoded with silc_utf8_encode. */
483
484 SilcUInt32 silc_utf8_encoded_len(const unsigned char *bin, SilcUInt32 bin_len,
485                                  SilcStringEncoding bin_encoding)
486 {
487   return silc_utf8_encode(bin, bin_len, bin_encoding, NULL, 0);
488 }
489
490 /* Returns the length of decoded string if the `bin' of encoding of
491    `bin_encoding' is decoded with silc_utf8_decode. */
492
493 SilcUInt32 silc_utf8_decoded_len(const unsigned char *bin, SilcUInt32 bin_len,
494                                  SilcStringEncoding bin_encoding)
495 {
496   return silc_utf8_decode(bin, bin_len, bin_encoding, NULL, 0);
497 }
498
499 /* Returns TRUE if the `utf8' string of length of `utf8_len' is valid
500    UTF-8 encoded string, FALSE if it is not UTF-8 encoded string. */
501
502 bool silc_utf8_valid(const unsigned char *utf8, SilcUInt32 utf8_len)
503 {
504   return silc_utf8_decode(utf8, utf8_len, 0, NULL, 0) != 0;
505 }