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