xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/valid_utf8_string.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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