Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: valid_utf8_string.c,v 1.3 2025/02/25 19:15:52 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	valid_utf8_string 3
      6 /* SUMMARY
      7 /*	predicate if string is valid UTF-8
      8 /* SYNOPSIS
      9 /*	#include <stringops.h>
     10 /*
     11 /*	int	valid_utf8_string(str, len)
     12 /*	const char *str;
     13 /*	ssize_t	len;
     14 /*
     15 /*	int	valid_utf8_stringz(str)
     16 /*	const char *str;
     17 /*	ssize_t	len;
     18 /* DESCRIPTION
     19 /*	valid_utf8_string() determines if all bytes in a string
     20 /*	satisfy parse_utf8_char(3h) checks. See there for any
     21 /*	implementation limitations.
     22 /*
     23 /*	valid_utf8_stringz() determines the same for zero-terminated
     24 /*	strings.
     25 /*
     26 /*	A zero-length string is considered valid.
     27 /* DIAGNOSTICS
     28 /*	The result value is zero when the caller specifies a negative
     29 /*	length, or a string that does not pass parse_utf8_char(3h) checks.
     30 /* SEE ALSO
     31 /*	parse_utf8_char(3h), parse one UTF-8 multibyte character
     32 /* LICENSE
     33 /* .ad
     34 /* .fi
     35 /*	The Secure Mailer license must be distributed with this software.
     36 /* AUTHOR(S)
     37 /*	Wietse Venema
     38 /*	IBM T.J. Watson Research
     39 /*	P.O. Box 704
     40 /*	Yorktown Heights, NY 10598, USA
     41 /*
     42 /*	Wietse Venema
     43 /*	porcupine.org
     44 /*	Amawalk, NY 10501, USA
     45 /*--*/
     46 
     47 /* System library. */
     48 
     49 #include <sys_defs.h>
     50 
     51 /* Utility library. */
     52 
     53 #include <stringops.h>
     54 #include <parse_utf8_char.h>
     55 
     56 /* valid_utf8_string - validate string according to RFC 3629 */
     57 
     58 int     valid_utf8_string(const char *str, ssize_t len)
     59 {
     60     const char *ep = str + len;
     61     const char *cp;
     62     const char *last;
     63 
     64     if (len < 0)
     65 	return (0);
     66     if (len == 0)
     67 	return (1);
     68 
     69     /*
     70      * Ideally, the compiler will inline parse_utf8_char().
     71      */
     72     for (cp = str; cp < ep; cp++) {
     73 	if ((last = parse_utf8_char(cp, ep)) != 0)
     74 	    cp = last;
     75 	else
     76 	    return (0);
     77     }
     78     return (1);
     79 }
     80 
     81 /* valid_utf8_stringz - validate string according to RFC 3629 */
     82 
     83 int     valid_utf8_stringz(const char *str)
     84 {
     85     const char *cp;
     86     const char *last;
     87 
     88     /*
     89      * Ideally, the compiler will inline parse_utf8_char(), propagate the
     90      * null pointer constant value, and eliminate code branches that test
     91      * whether 0 != 0.
     92      */
     93     for (cp = str; *cp; cp++) {
     94 	if ((last = parse_utf8_char(cp, 0)) != 0)
     95 	    cp = last;
     96 	else
     97 	    return (0);
     98     }
     99     return (1);
    100 }
    101 
    102  /*
    103   * Stand-alone test program. Each string is a line without line terminator.
    104   */
    105 #ifdef TEST
    106 #include <stdlib.h>
    107 #include <string.h>
    108 #include <msg.h>
    109 #include <vstream.h>
    110 #include <msg_vstream.h>
    111 
    112  /*
    113   * Test cases for 1-, 2-, and 3-byte encodings. See printable.c for UTF8
    114   * parser resychronization tests.
    115   *
    116   * XXX Need a test for 4-byte encodings, preferably with strings that can be
    117   * displayed.
    118   *
    119   * XXX Need tests with hand-crafted over-long encodings and surrogates.
    120   */
    121 struct testcase {
    122     const char *name;
    123     const char *input;
    124     int     expected;
    125 };
    126 
    127 #define T_VALID		(1)
    128 #define T_INVALID	(0)
    129 #define valid_to_str(v)	((v) ? "VALID" : "INVALID")
    130 
    131 static const struct testcase testcases[] = {
    132     {"Printable ASCII",
    133 	"printable", T_VALID,
    134     },
    135     {"Latin script, accented, no error",
    136 	"na\303\257ve", T_VALID,
    137     },
    138     {"Latin script, accented, missing non-leading byte",
    139 	"na\303ve", T_INVALID,
    140     },
    141     {"Latin script, accented, missing leading byte",
    142 	"na\257ve", T_INVALID,
    143     },
    144     {"Viktor, Cyrillic, no error",
    145 	"\320\262\320\270\320\272\321\202\320\276\321\200", T_VALID,
    146     },
    147     {"Viktor, Cyrillic, missing non-leading byte",
    148 	"\320\262\320\320\272\321\202\320\276\321\200", T_INVALID,
    149     },
    150     {"Viktor, Cyrillic, missing leading byte",
    151 	"\320\262\270\320\272\321\202\320\276\321\200", T_INVALID,
    152     },
    153     {"Viktor, Cyrillic, truncated",
    154 	"\320\262\320\270\320\272\321\202\320\276\321", T_INVALID,
    155     },
    156     {"Viktor, Hebrew, no error",
    157 	"\327\225\327\231\327\247\327\230\327\225\326\274\327\250", T_VALID,
    158     },
    159     {"Viktor, Hebrew, missing leading byte",
    160 	"\327\225\231\327\247\327\230\327\225\326\274\327\250", T_INVALID,
    161     },
    162     {"Chinese (Simplified), no error",
    163 	"\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
    164 	"\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
    165 	"\237\350\256\241\346\212\245\345\221\212", T_VALID,
    166     },
    167     {"Chinese (Simplified), missing leading byte",
    168 	"\344\270\255\345\233\275\344\272\222\350\201\224\275\221\347"
    169 	"\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
    170 	"\237\350\256\241\346\212\245\345\221\212", T_INVALID,
    171     },
    172     {"Chinese (Simplified), missing first non-leading byte",
    173 	"\344\270\255\345\233\275\344\272\222\350\201\224\347\221\347"
    174 	"\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
    175 	"\237\350\256\241\346\212\245\345\221\212", T_INVALID,
    176     },
    177     {"Chinese (Simplified), missing second non-leading byte",
    178 	"\344\270\255\345\233\275\344\272\222\350\201\224\347\275\347"
    179 	"\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
    180 	"\237\350\256\241\346\212\245\345\221\212", T_INVALID,
    181     },
    182     {"Chinese (Simplified), truncated",
    183 	"\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
    184 	"\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
    185 	"\237\350\256\241\346\212\245\345", T_INVALID,
    186     },
    187 };
    188 
    189 int     main(int argc, char **argv)
    190 {
    191     const struct testcase *tp;
    192     int     pass;
    193     int     fail;
    194 
    195 #define NUM_TESTS       sizeof(testcases)/sizeof(testcases[0])
    196 
    197     msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
    198     util_utf8_enable = 1;
    199 
    200     for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
    201 	int     actual_l;
    202 	int     actual_z;
    203 	int     ok = 0;
    204 
    205 	/*
    206 	 * Notes:
    207 	 *
    208 	 * - The msg(3) functions use printable() which interferes when logging
    209 	 * inputs and outputs. Use vstream_fprintf() instead.
    210 	 */
    211 	vstream_fprintf(VSTREAM_ERR, "RUN  %s\n", tp->name);
    212 	actual_l = valid_utf8_string(tp->input, strlen(tp->input));
    213 	actual_z = valid_utf8_stringz(tp->input);
    214 
    215 	if (actual_l != tp->expected) {
    216 	    vstream_fprintf(VSTREAM_ERR,
    217 			  "input: >%s<, 'actual_l' got: >%s<, want: >%s<\n",
    218 			    tp->input, valid_to_str(actual_l),
    219 			    valid_to_str(tp->expected));
    220 	} else if (actual_z != tp->expected) {
    221 	    vstream_fprintf(VSTREAM_ERR,
    222 			  "input: >%s<, 'actual_z' got: >%s<, want: >%s<\n",
    223 			    tp->input, valid_to_str(actual_z),
    224 			    valid_to_str(tp->expected));
    225 	} else {
    226 	    vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
    227 			    tp->input, valid_to_str(actual_l));
    228 	    ok = 1;
    229 	}
    230 	if (ok) {
    231 	    vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
    232 	    pass++;
    233 	} else {
    234 	    vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
    235 	    fail++;
    236 	}
    237     }
    238     msg_info("PASS=%d FAIL=%d", pass, fail);
    239     return (fail > 0);
    240 }
    241 
    242 #endif
    243