aboutsummaryrefslogtreecommitdiff
path: root/src/util/support/utf8.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/support/utf8.c')
-rw-r--r--src/util/support/utf8.c539
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;
+}