diff options
Diffstat (limited to 'src/util/support/utf8.c')
-rw-r--r-- | src/util/support/utf8.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/src/util/support/utf8.c b/src/util/support/utf8.c new file mode 100644 index 0000000..8712c67 --- /dev/null +++ b/src/util/support/utf8.c @@ -0,0 +1,539 @@ +/* + * util/support/utf8.c + * + * Copyright 2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2008 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* Basic UTF-8 routines + * + * These routines are "dumb". Though they understand UTF-8, + * they don't grok Unicode. That is, they can push bits, + * but don't have a clue what the bits represent. That's + * good enough for use with the KRB5 Client SDK. + * + * These routines are not optimized. + */ + +#include "k5-platform.h" +#include "k5-utf8.h" +#include "supp-int.h" + +/* + * return the number of bytes required to hold the + * NULL-terminated UTF-8 string NOT INCLUDING the + * termination. + */ +size_t krb5int_utf8_bytes(const char *p) +{ + size_t bytes; + + for (bytes = 0; p[bytes]; bytes++) + ; + + return bytes; +} + +size_t krb5int_utf8_chars(const char *p) +{ + /* could be optimized and could check for invalid sequences */ + size_t chars = 0; + + for ( ; *p ; KRB5_UTF8_INCR(p)) + chars++; + + return chars; +} + +size_t krb5int_utf8c_chars(const char *p, size_t length) +{ + /* could be optimized and could check for invalid sequences */ + size_t chars = 0; + const char *end = p + length; + + for ( ; p < end; KRB5_UTF8_INCR(p)) + chars++; + + return chars; +} + +/* return offset to next character */ +int krb5int_utf8_offset(const char *p) +{ + return KRB5_UTF8_NEXT(p) - p; +} + +/* + * Returns length indicated by first byte. + */ +const char krb5int_utf8_lentab[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0 }; + +int krb5int_utf8_charlen(const char *p) +{ + if (!(*p & 0x80)) + return 1; + + return krb5int_utf8_lentab[*(const unsigned char *)p ^ 0x80]; +} + +/* + * Make sure the UTF-8 char used the shortest possible encoding + * returns charlen if valid, 0 if not. + * + * Here are the valid UTF-8 encodings, taken from RFC 2279 page 4. + * The table is slightly modified from that of the RFC. + * + * UCS-4 range (hex) UTF-8 sequence (binary) + * 0000 0000-0000 007F 0....... + * 0000 0080-0000 07FF 110++++. 10...... + * 0000 0800-0000 FFFF 1110++++ 10+..... 10...... + * 0001 0000-001F FFFF 11110+++ 10++.... 10...... 10...... + * 0020 0000-03FF FFFF 111110++ 10+++... 10...... 10...... 10...... + * 0400 0000-7FFF FFFF 1111110+ 10++++.. 10...... 10...... 10...... 10...... + * + * The '.' bits are "don't cares". When validating a UTF-8 sequence, + * at least one of the '+' bits must be set, otherwise the character + * should have been encoded in fewer octets. Note that in the two-octet + * case, only the first octet needs to be validated, and this is done + * in the krb5int_utf8_lentab[] above. + */ + +/* mask of required bits in second octet */ +#undef c +#define c const char +c krb5int_utf8_mintab[] = { + (c)0x20, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x30, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, (c)0x80, + (c)0x38, (c)0x80, (c)0x80, (c)0x80, (c)0x3c, (c)0x80, (c)0x00, (c)0x00 }; +#undef c + +int krb5int_utf8_charlen2(const char *p) +{ + int i = KRB5_UTF8_CHARLEN(p); + + if (i > 2) { + if (!(krb5int_utf8_mintab[*p & 0x1f] & p[1])) + i = 0; + } + + return i; +} + +krb5_ucs4 krb5int_utf8_to_ucs4(const char *p) +{ + const unsigned char *c = (const unsigned char *) p; + krb5_ucs4 ch; + int len, i; + static unsigned char mask[] = { + 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + + len = KRB5_UTF8_CHARLEN2(p, len); + + if (len == 0) + return KRB5_UCS4_INVALID; + + ch = c[0] & mask[len]; + + for (i = 1; i < len; i++) { + if ((c[i] & 0xc0) != 0x80) { + return KRB5_UCS4_INVALID; + } + + ch <<= 6; + ch |= c[i] & 0x3f; + } + + return ch; +} + +krb5_ucs2 krb5int_utf8_to_ucs2(const char *p) +{ + krb5_ucs4 ch = krb5int_utf8_to_ucs4(p); + + if (ch == KRB5_UCS4_INVALID || ch > SHRT_MAX) + return KRB5_UCS2_INVALID; + + return (krb5_ucs2)ch; +} + +/* conv UCS-2 to UTF-8, not used */ +size_t krb5int_ucs4_to_utf8(krb5_ucs4 c, char *buf) +{ + size_t len = 0; + unsigned char *p = (unsigned char *) buf; + + /* not a valid Unicode character */ + if (c < 0) + return 0; + + /* Just return length, don't convert */ + if (buf == NULL) { + if (c < 0x80) return 1; + else if (c < 0x800) return 2; + else if (c < 0x10000) return 3; + else if (c < 0x200000) return 4; + else if (c < 0x4000000) return 5; + else return 6; + } + + if (c < 0x80) { + p[len++] = c; + } else if (c < 0x800) { + p[len++] = 0xc0 | ( c >> 6 ); + p[len++] = 0x80 | ( c & 0x3f ); + } else if (c < 0x10000) { + p[len++] = 0xe0 | ( c >> 12 ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + } else if (c < 0x200000) { + p[len++] = 0xf0 | ( c >> 18 ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + } else if (c < 0x4000000) { + p[len++] = 0xf8 | ( c >> 24 ); + p[len++] = 0x80 | ( (c >> 18) & 0x3f ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + } else /* if( c < 0x80000000 ) */ { + p[len++] = 0xfc | ( c >> 30 ); + p[len++] = 0x80 | ( (c >> 24) & 0x3f ); + p[len++] = 0x80 | ( (c >> 18) & 0x3f ); + p[len++] = 0x80 | ( (c >> 12) & 0x3f ); + p[len++] = 0x80 | ( (c >> 6) & 0x3f ); + p[len++] = 0x80 | ( c & 0x3f ); + } + + return len; +} + +size_t krb5int_ucs2_to_utf8(krb5_ucs2 c, char *buf) +{ + return krb5int_ucs4_to_utf8((krb5_ucs4)c, buf); +} + +#define KRB5_UCS_UTF8LEN(c) \ + c < 0 ? 0 : (c < 0x80 ? 1 : (c < 0x800 ? 2 : (c < 0x10000 ? 3 : \ + (c < 0x200000 ? 4 : (c < 0x4000000 ? 5 : 6))))) + +/* + * Advance to the next UTF-8 character + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +char *krb5int_utf8_next(const char *p) +{ + int i; + const unsigned char *u = (const unsigned char *) p; + + if (KRB5_UTF8_ISASCII(u)) { + return (char *) &p[1]; + } + + for (i = 1; i < 6; i++) { + if ((u[i] & 0xc0) != 0x80) { + return (char *) &p[i]; + } + } + + return (char *) &p[i]; +} + +/* + * Advance to the previous UTF-8 character + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +char *krb5int_utf8_prev(const char *p) +{ + int i; + const unsigned char *u = (const unsigned char *) p; + + for (i = -1; i>-6 ; i--) { + if ((u[i] & 0xc0 ) != 0x80) { + return (char *) &p[i]; + } + } + + return (char *) &p[i]; +} + +/* + * Copy one UTF-8 character from src to dst returning + * number of bytes copied. + * + * Ignores length of multibyte character, instead rely on + * continuation markers to find start of next character. + * This allows for "resyncing" of when invalid characters + * are provided provided the start of the next character + * is appears within the 6 bytes examined. + */ +int krb5int_utf8_copy(char* dst, const char *src) +{ + int i; + const unsigned char *u = (const unsigned char *) src; + + dst[0] = src[0]; + + if (KRB5_UTF8_ISASCII(u)) { + return 1; + } + + for (i=1; i<6; i++) { + if ((u[i] & 0xc0) != 0x80) { + return i; + } + dst[i] = src[i]; + } + + return i; +} + +#ifndef UTF8_ALPHA_CTYPE +/* + * UTF-8 ctype routines + * Only deals with characters < 0x80 (ie: US-ASCII) + */ + +int krb5int_utf8_isascii(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + return KRB5_ASCII(c); +} + +int krb5int_utf8_isdigit(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_DIGIT( c ); +} + +int krb5int_utf8_isxdigit(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_HEX(c); +} + +int krb5int_utf8_isspace(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + switch(c) { + case ' ': + case '\t': + case '\n': + case '\r': + case '\v': + case '\f': + return 1; + } + + return 0; +} + +/* + * These are not needed by the C SDK and are + * not "good enough" for general use. + */ +int krb5int_utf8_isalpha(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_ALPHA(c); +} + +int krb5int_utf8_isalnum(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_ALNUM(c); +} + +int krb5int_utf8_islower(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_LOWER(c); +} + +int krb5int_utf8_isupper(const char * p) +{ + unsigned c = * (const unsigned char *) p; + + if (!KRB5_ASCII(c)) + return 0; + + return KRB5_UPPER(c); +} +#endif + + +/* + * UTF-8 string routines + */ + +/* like strchr() */ +char *krb5int_utf8_strchr(const char *str, const char *chr) +{ + for ( ; *str != '\0'; KRB5_UTF8_INCR(str)) { + if (krb5int_utf8_to_ucs4(str) == krb5int_utf8_to_ucs4(chr)) { + return (char *)str; + } + } + + return NULL; +} + +/* like strcspn() but returns number of bytes, not characters */ +size_t krb5int_utf8_strcspn(const char *str, const char *set) +{ + const char *cstr; + const char *cset; + + for (cstr = str; *cstr != '\0'; KRB5_UTF8_INCR(cstr)) { + for (cset = set; *cset != '\0'; KRB5_UTF8_INCR(cset)) { + if (krb5int_utf8_to_ucs4(cstr) == krb5int_utf8_to_ucs4(cset)) { + return cstr - str; + } + } + } + + return cstr - str; +} + +/* like strspn() but returns number of bytes, not characters */ +size_t krb5int_utf8_strspn(const char *str, const char *set) +{ + const char *cstr; + const char *cset; + + for (cstr = str; *cstr != '\0'; KRB5_UTF8_INCR(cstr)) { + for (cset = set; ; KRB5_UTF8_INCR(cset)) { + if (*cset == '\0') { + return cstr - str; + } + + if (krb5int_utf8_to_ucs4(cstr) == krb5int_utf8_to_ucs4(cset)) { + break; + } + } + } + + return cstr - str; +} + +/* like strpbrk(), replaces strchr() as well */ +char *krb5int_utf8_strpbrk(const char *str, const char *set) +{ + for ( ; *str != '\0'; KRB5_UTF8_INCR(str)) { + const char *cset; + + for (cset = set; *cset != '\0'; KRB5_UTF8_INCR(cset)) { + if (krb5int_utf8_to_ucs4(str) == krb5int_utf8_to_ucs4(cset)) { + return (char *)str; + } + } + } + + return NULL; +} + +/* like strtok_r(), not strtok() */ +char *krb5int_utf8_strtok(char *str, const char *sep, char **last) +{ + char *begin; + char *end; + + if (last == NULL) + return NULL; + + begin = str ? str : *last; + + begin += krb5int_utf8_strspn(begin, sep); + + if (*begin == '\0') { + *last = NULL; + return NULL; + } + + end = &begin[krb5int_utf8_strcspn(begin, sep)]; + + if (*end != '\0') { + char *next = KRB5_UTF8_NEXT(end); + *end = '\0'; + end = next; + } + + *last = end; + + return begin; +} |