libc/locale: limit utf8 illegal input detection to 10FF FFFF
[dragonfly.git] / lib / libc / locale / utf8.c
1 /*
2  * Copyright 2013 Garrett D'Amore <garrett@damore.org>
3  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
4  * Copyright (c) 2002-2004 Tim J. Robbins
5  * All rights reserved.
6  *
7  * Copyright (c) 2011 The FreeBSD Foundation
8  * All rights reserved.
9  * Portions of this software were developed by David Chisnall
10  * under sponsorship from the FreeBSD Foundation.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include <sys/param.h>
35
36 #include <errno.h>
37 #include <limits.h>
38 #include <runetype.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <wchar.h>
42 #include "mblocal.h"
43
44 extern int __mb_sb_limit;
45
46 static size_t   _UTF8_mbrtowc(wchar_t * __restrict, const char * __restrict,
47                     size_t, mbstate_t * __restrict);
48 static int      _UTF8_mbsinit(const mbstate_t *);
49 static size_t   _UTF8_mbsnrtowcs(wchar_t * __restrict,
50                     const char ** __restrict, size_t, size_t,
51                     mbstate_t * __restrict);
52 static size_t   _UTF8_wcrtomb(char * __restrict, wchar_t,
53                     mbstate_t * __restrict);
54 static size_t   _UTF8_wcsnrtombs(char * __restrict, const wchar_t ** __restrict,
55                     size_t, size_t, mbstate_t * __restrict);
56
57 typedef struct {
58         wchar_t ch;
59         int     want;
60         wchar_t lbound;
61 } _UTF8State;
62
63 int
64 _UTF8_init(struct xlocale_ctype *l, _RuneLocale *rl)
65 {
66
67         l->__mbrtowc = _UTF8_mbrtowc;
68         l->__wcrtomb = _UTF8_wcrtomb;
69         l->__mbsinit = _UTF8_mbsinit;
70         l->__mbsnrtowcs = _UTF8_mbsnrtowcs;
71         l->__wcsnrtombs = _UTF8_wcsnrtombs;
72         l->runes = rl;
73         l->__mb_cur_max = 4;
74         /*
75          * UCS-4 encoding used as the internal representation, so
76          * slots 0x0080-0x00FF are occuped and must be excluded
77          * from the single byte ctype by setting the limit.
78          */
79         l->__mb_sb_limit = 128;
80
81         return (0);
82 }
83
84 static int
85 _UTF8_mbsinit(const mbstate_t *ps)
86 {
87
88         return (ps == NULL || ((const _UTF8State *)ps)->want == 0);
89 }
90
91 static size_t
92 _UTF8_mbrtowc(wchar_t * __restrict pwc, const char * __restrict s, size_t n,
93     mbstate_t * __restrict ps)
94 {
95         _UTF8State *us;
96         int ch, i, mask, want;
97         wchar_t lbound, wch;
98
99         us = (_UTF8State *)ps;
100
101         if (us->want < 0 || us->want > 4) {
102                 errno = EINVAL;
103                 return ((size_t)-1);
104         }
105
106         if (s == NULL) {
107                 s = "";
108                 n = 1;
109                 pwc = NULL;
110         }
111
112         if (n == 0)
113                 /* Incomplete multibyte sequence */
114                 return ((size_t)-2);
115
116         if (us->want == 0) {
117                 /*
118                  * Determine the number of octets that make up this character
119                  * from the first octet, and a mask that extracts the
120                  * interesting bits of the first octet. We already know
121                  * the character is at least two bytes long.
122                  *
123                  * We also specify a lower bound for the character code to
124                  * detect redundant, non-"shortest form" encodings. For
125                  * example, the sequence C0 80 is _not_ a legal representation
126                  * of the null character. This enforces a 1-to-1 mapping
127                  * between character codes and their multibyte representations.
128                  */
129                 ch = (unsigned char)*s;
130                 if ((ch & 0x80) == 0) {
131                         /* Fast path for plain ASCII characters. */
132                         if (pwc != NULL)
133                                 *pwc = ch;
134                         return (ch != '\0' ? 1 : 0);
135                 }
136                 if ((ch & 0xe0) == 0xc0) {
137                         mask = 0x1f;
138                         want = 2;
139                         lbound = 0x80;
140                 } else if ((ch & 0xf0) == 0xe0) {
141                         mask = 0x0f;
142                         want = 3;
143                         lbound = 0x800;
144                 } else if ((ch & 0xf8) == 0xf0) {
145                         mask = 0x07;
146                         want = 4;
147                         lbound = 0x10000;
148                 } else {
149                         /*
150                          * Malformed input; input is not UTF-8.
151                          */
152                         errno = EILSEQ;
153                         return ((size_t)-1);
154                 }
155         } else {
156                 want = us->want;
157                 lbound = us->lbound;
158         }
159
160         /*
161          * Decode the octet sequence representing the character in chunks
162          * of 6 bits, most significant first.
163          */
164         if (us->want == 0)
165                 wch = (unsigned char)*s++ & mask;
166         else
167                 wch = us->ch;
168
169         for (i = (us->want == 0) ? 1 : 0; i < MIN(want, n); i++) {
170                 if ((*s & 0xc0) != 0x80) {
171                         /*
172                          * Malformed input; bad characters in the middle
173                          * of a character.
174                          */
175                         errno = EILSEQ;
176                         return ((size_t)-1);
177                 }
178                 wch <<= 6;
179                 wch |= *s++ & 0x3f;
180         }
181         if (i < want) {
182                 /* Incomplete multibyte sequence. */
183                 us->want = want - i;
184                 us->lbound = lbound;
185                 us->ch = wch;
186                 return ((size_t)-2);
187         }
188         if (wch < lbound || (wch & ~10ffffff)) {
189                 /*
190                  * Malformed input; redundant encoding.
191                  */
192                 errno = EILSEQ;
193                 return ((size_t)-1);
194         }
195         if (pwc != NULL)
196                 *pwc = wch;
197         us->want = 0;
198         return (wch == L'\0' ? 0 : want);
199 }
200
201 static size_t
202 _UTF8_mbsnrtowcs(wchar_t * __restrict dst, const char ** __restrict src,
203     size_t nms, size_t len, mbstate_t * __restrict ps)
204 {
205         _UTF8State *us;
206         const char *s;
207         size_t nchr;
208         wchar_t wc;
209         size_t nb;
210
211         us = (_UTF8State *)ps;
212
213         s = *src;
214         nchr = 0;
215
216         if (dst == NULL) {
217                 /*
218                  * The fast path in the loop below is not safe if an ASCII
219                  * character appears as anything but the first byte of a
220                  * multibyte sequence. Check now to avoid doing it in the loop.
221                  */
222                 if (nms > 0 && us->want > 0 && (signed char)*s > 0) {
223                         errno = EILSEQ;
224                         return ((size_t)-1);
225                 }
226                 for (;;) {
227                         if (nms > 0 && (signed char)*s > 0)
228                                 /*
229                                  * Fast path for plain ASCII characters
230                                  * excluding NUL.
231                                  */
232                                 nb = 1;
233                         else if ((nb = _UTF8_mbrtowc(&wc, s, nms, ps)) ==
234                             (size_t)-1)
235                                 /* Invalid sequence - mbrtowc() sets errno. */
236                                 return ((size_t)-1);
237                         else if (nb == 0 || nb == (size_t)-2)
238                                 return (nchr);
239                         s += nb;
240                         nms -= nb;
241                         nchr++;
242                 }
243                 /*NOTREACHED*/
244         }
245
246         /*
247          * The fast path in the loop below is not safe if an ASCII
248          * character appears as anything but the first byte of a
249          * multibyte sequence. Check now to avoid doing it in the loop.
250          */
251         if (nms > 0 && len > 0 && us->want > 0 && (signed char)*s > 0) {
252                 errno = EILSEQ;
253                 return ((size_t)-1);
254         }
255         while (len-- > 0) {
256                 if (nms > 0 && (signed char)*s > 0) {
257                         /*
258                          * Fast path for plain ASCII characters
259                          * excluding NUL.
260                          */
261                         *dst = (wchar_t)*s;
262                         nb = 1;
263                 } else if ((nb = _UTF8_mbrtowc(dst, s, nms, ps)) ==
264                     (size_t)-1) {
265                         *src = s;
266                         return ((size_t)-1);
267                 } else if (nb == (size_t)-2) {
268                         *src = s + nms;
269                         return (nchr);
270                 } else if (nb == 0) {
271                         *src = NULL;
272                         return (nchr);
273                 }
274                 s += nb;
275                 nms -= nb;
276                 nchr++;
277                 dst++;
278         }
279         *src = s;
280         return (nchr);
281 }
282
283 static size_t
284 _UTF8_wcrtomb(char * __restrict s, wchar_t wc, mbstate_t * __restrict ps)
285 {
286         _UTF8State *us;
287         unsigned char lead;
288         int i, len;
289
290         us = (_UTF8State *)ps;
291
292         if (us->want != 0) {
293                 errno = EINVAL;
294                 return ((size_t)-1);
295         }
296
297         if (s == NULL)
298                 /* Reset to initial shift state (no-op) */
299                 return (1);
300
301         /*
302          * Determine the number of octets needed to represent this character.
303          * We always output the shortest sequence possible. Also specify the
304          * first few bits of the first octet, which contains the information
305          * about the sequence length.
306          */
307         if ((wc & ~0x7f) == 0) {
308                 /* Fast path for plain ASCII characters. */
309                 *s = (char)wc;
310                 return (1);
311         } else if ((wc & ~0x7ff) == 0) {
312                 lead = 0xc0;
313                 len = 2;
314         } else if ((wc & ~0xffff) == 0) {
315                 lead = 0xe0;
316                 len = 3;
317         } else if ((wc & ~0x10ffff) == 0) {
318                 lead = 0xf0;
319                 len = 4;
320         } else {
321                 errno = EILSEQ;
322                 return ((size_t)-1);
323         }
324
325         /*
326          * Output the octets representing the character in chunks
327          * of 6 bits, least significant last. The first octet is
328          * a special case because it contains the sequence length
329          * information.
330          */
331         for (i = len - 1; i > 0; i--) {
332                 s[i] = (wc & 0x3f) | 0x80;
333                 wc >>= 6;
334         }
335         *s = (wc & 0xff) | lead;
336
337         return (len);
338 }
339
340 static size_t
341 _UTF8_wcsnrtombs(char * __restrict dst, const wchar_t ** __restrict src,
342     size_t nwc, size_t len, mbstate_t * __restrict ps)
343 {
344         _UTF8State *us;
345         char buf[MB_LEN_MAX];
346         const wchar_t *s;
347         size_t nbytes;
348         size_t nb;
349
350         us = (_UTF8State *)ps;
351
352         if (us->want != 0) {
353                 errno = EINVAL;
354                 return ((size_t)-1);
355         }
356
357         s = *src;
358         nbytes = 0;
359
360         if (dst == NULL) {
361                 while (nwc-- > 0) {
362                         if (0 <= *s && *s < 0x80)
363                                 /* Fast path for plain ASCII characters. */
364                                 nb = 1;
365                         else if ((nb = _UTF8_wcrtomb(buf, *s, ps)) ==
366                             (size_t)-1)
367                                 /* Invalid character - wcrtomb() sets errno. */
368                                 return ((size_t)-1);
369                         if (*s == L'\0')
370                                 return (nbytes + nb - 1);
371                         s++;
372                         nbytes += nb;
373                 }
374                 return (nbytes);
375         }
376
377         while (len > 0 && nwc-- > 0) {
378                 if (0 <= *s && *s < 0x80) {
379                         /* Fast path for plain ASCII characters. */
380                         nb = 1;
381                         *dst = *s;
382                 } else if (len > (size_t)MB_CUR_MAX) {
383                         /* Enough space to translate in-place. */
384                         if ((nb = _UTF8_wcrtomb(dst, *s, ps)) == (size_t)-1) {
385                                 *src = s;
386                                 return ((size_t)-1);
387                         }
388                 } else {
389                         /*
390                          * May not be enough space; use temp. buffer.
391                          */
392                         if ((nb = _UTF8_wcrtomb(buf, *s, ps)) == (size_t)-1) {
393                                 *src = s;
394                                 return ((size_t)-1);
395                         }
396                         if (nb > (int)len)
397                                 /* MB sequence for character won't fit. */
398                                 break;
399                         (void) memcpy(dst, buf, nb);
400                 }
401                 if (*s == L'\0') {
402                         *src = NULL;
403                         return (nbytes + nb - 1);
404                 }
405                 s++;
406                 dst += nb;
407                 len -= nb;
408                 nbytes += nb;
409         }
410         *src = s;
411         return (nbytes);
412 }