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