1*e89934bbSchristos /* $NetBSD: valid_utf8_string.c,v 1.2 2017/02/14 01:16:49 christos Exp $ */
2e262b48eSchristos
3e262b48eSchristos /*++
4e262b48eSchristos /* NAME
5e262b48eSchristos /* valid_utf8_string 3
6e262b48eSchristos /* SUMMARY
7e262b48eSchristos /* predicate if string is valid UTF-8
8e262b48eSchristos /* SYNOPSIS
9e262b48eSchristos /* #include <stringops.h>
10e262b48eSchristos /*
11e262b48eSchristos /* int valid_utf8_string(str, len)
12e262b48eSchristos /* const char *str;
13e262b48eSchristos /* ssize_t len;
14e262b48eSchristos /* DESCRIPTION
15e262b48eSchristos /* valid_utf8_string() determines if a string satisfies the UTF-8
16e262b48eSchristos /* definition in RFC 3629. That is, it contains proper encodings
17e262b48eSchristos /* of code points U+0000..U+10FFFF, excluding over-long encodings
18e262b48eSchristos /* and excluding U+D800..U+DFFF surrogates.
19e262b48eSchristos /*
20e262b48eSchristos /* A zero-length string is considered valid.
21e262b48eSchristos /* DIAGNOSTICS
22e262b48eSchristos /* The result value is zero when the caller specifies a negative
23e262b48eSchristos /* length, or a string that violates RFC 3629, for example a
24e262b48eSchristos /* string that is truncated in the middle of a multi-byte
25e262b48eSchristos /* sequence.
26e262b48eSchristos /* BUGS
27e262b48eSchristos /* But wait, there is more. Code points in the range U+FDD0..U+FDEF
28e262b48eSchristos /* and ending in FFFE or FFFF are non-characters in UNICODE. This
29e262b48eSchristos /* function does not block these.
30e262b48eSchristos /* SEE ALSO
31e262b48eSchristos /* RFC 3629
32e262b48eSchristos /* LICENSE
33e262b48eSchristos /* .ad
34e262b48eSchristos /* .fi
35e262b48eSchristos /* The Secure Mailer license must be distributed with this software.
36e262b48eSchristos /* AUTHOR(S)
37e262b48eSchristos /* Wietse Venema
38e262b48eSchristos /* IBM T.J. Watson Research
39e262b48eSchristos /* P.O. Box 704
40e262b48eSchristos /* Yorktown Heights, NY 10598, USA
41e262b48eSchristos /*--*/
42e262b48eSchristos
43e262b48eSchristos /* System library. */
44e262b48eSchristos
45e262b48eSchristos #include <sys_defs.h>
46e262b48eSchristos
47e262b48eSchristos /* Utility library. */
48e262b48eSchristos
49e262b48eSchristos #include <stringops.h>
50e262b48eSchristos
51e262b48eSchristos /* valid_utf8_string - validate string according to RFC 3629 */
52e262b48eSchristos
valid_utf8_string(const char * str,ssize_t len)53e262b48eSchristos int valid_utf8_string(const char *str, ssize_t len)
54e262b48eSchristos {
55e262b48eSchristos const unsigned char *end = (const unsigned char *) str + len;
56e262b48eSchristos const unsigned char *cp;
57e262b48eSchristos unsigned char c0, ch;
58e262b48eSchristos
59e262b48eSchristos if (len < 0)
60e262b48eSchristos return (0);
61e262b48eSchristos if (len <= 0)
62e262b48eSchristos return (1);
63e262b48eSchristos
64e262b48eSchristos /*
65e262b48eSchristos * Optimized for correct input, time, space, and for CPUs that have a
66e262b48eSchristos * decent number of registers.
67e262b48eSchristos */
68e262b48eSchristos for (cp = (const unsigned char *) str; cp < end; cp++) {
69e262b48eSchristos /* Single-byte encodings. */
70e262b48eSchristos if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) {
71e262b48eSchristos /* void */ ;
72e262b48eSchristos }
73e262b48eSchristos /* Two-byte encodings. */
74e262b48eSchristos else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) {
75e262b48eSchristos /* Exclude over-long encodings. */
76e262b48eSchristos if (UNEXPECTED(c0 < 0xc2)
77e262b48eSchristos || UNEXPECTED(cp + 1 >= end)
78e262b48eSchristos /* Require UTF-8 tail byte. */
79e262b48eSchristos || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
80e262b48eSchristos return (0);
81e262b48eSchristos }
82e262b48eSchristos /* Three-byte encodings. */
83e262b48eSchristos else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) {
84e262b48eSchristos if (UNEXPECTED(cp + 2 >= end)
85e262b48eSchristos /* Exclude over-long encodings. */
86e262b48eSchristos || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80))
87e262b48eSchristos /* Exclude U+D800..U+DFFF. */
88e262b48eSchristos || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf))
89e262b48eSchristos /* Require UTF-8 tail byte. */
90e262b48eSchristos || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
91e262b48eSchristos return (0);
92e262b48eSchristos }
93e262b48eSchristos /* Four-byte encodings. */
94e262b48eSchristos else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) {
95e262b48eSchristos if (UNEXPECTED(cp + 3 >= end)
96e262b48eSchristos /* Exclude over-long encodings. */
97e262b48eSchristos || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80))
98e262b48eSchristos /* Exclude code points above U+10FFFF. */
99e262b48eSchristos || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf))
100e262b48eSchristos /* Require UTF-8 tail byte. */
101e262b48eSchristos || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)
102e262b48eSchristos /* Require UTF-8 tail byte. */
103e262b48eSchristos || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
104e262b48eSchristos return (0);
105e262b48eSchristos }
106e262b48eSchristos /* Invalid: c0 >= 0xf5 */
107e262b48eSchristos else {
108e262b48eSchristos return (0);
109e262b48eSchristos }
110e262b48eSchristos }
111e262b48eSchristos return (1);
112e262b48eSchristos }
113e262b48eSchristos
114e262b48eSchristos /*
115e262b48eSchristos * Stand-alone test program. Each string is a line without line terminator.
116e262b48eSchristos */
117e262b48eSchristos #ifdef TEST
118e262b48eSchristos #include <stdlib.h>
119e262b48eSchristos #include <vstream.h>
120e262b48eSchristos #include <vstring.h>
121e262b48eSchristos #include <vstring_vstream.h>
122e262b48eSchristos
123e262b48eSchristos #define STR(x) vstring_str(x)
124e262b48eSchristos #define LEN(x) VSTRING_LEN(x)
125e262b48eSchristos
main(void)126e262b48eSchristos int main(void)
127e262b48eSchristos {
128e262b48eSchristos VSTRING *buf = vstring_alloc(1);
129e262b48eSchristos
130e262b48eSchristos while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
131e262b48eSchristos vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ?
132e262b48eSchristos '!' : ' ');
133e262b48eSchristos vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf));
134e262b48eSchristos vstream_printf("\n");
135e262b48eSchristos }
136e262b48eSchristos vstream_fflush(VSTREAM_OUT);
137e262b48eSchristos vstring_free(buf);
138e262b48eSchristos exit(0);
139e262b48eSchristos }
140e262b48eSchristos
141e262b48eSchristos #endif
142